Upgrade sequoia-keystore.
This commit is contained in:
parent
81009e984d
commit
1d162d214b
57
Cargo.lock
generated
57
Cargo.lock
generated
@ -857,6 +857,15 @@ dependencies = [
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "directories"
|
||||
version = "5.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35"
|
||||
dependencies = [
|
||||
"dirs-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "5.0.1"
|
||||
@ -1980,7 +1989,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-targets 0.48.5",
|
||||
"windows-targets 0.52.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3140,37 +3149,50 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sequoia-gpg-agent"
|
||||
version = "0.3.1"
|
||||
name = "sequoia-directories"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f29f0e04d9d161c65384a02282380ceaf1b506e768cab95837a428439c2596a4"
|
||||
checksum = "b01dd48960c5cf8617ab77e5c9f8ebeb55a1d694e3eabf830fa70453ffa637d5"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"directories",
|
||||
"same-file",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sequoia-gpg-agent"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c929d572dee98c48d286cef43e2ade4201962f3454c015f52bf43b5a8e40d42"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
"futures",
|
||||
"lalrpop",
|
||||
"lalrpop-util",
|
||||
"libc",
|
||||
"sequoia-cert-store",
|
||||
"sequoia-ipc",
|
||||
"sequoia-openpgp",
|
||||
"stfu8",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sequoia-ipc"
|
||||
version = "0.34.1"
|
||||
version = "0.35.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d67c3c28da4a3483fa969e6a6cde9e5487fe44632b5f285224d79df05bd99dd4"
|
||||
checksum = "b4a7e644ec9e1055fde8dcdaa65c58fa4636c615b5e955a9b1942444145e308a"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"buffered-reader",
|
||||
"capnp-rpc",
|
||||
"crossbeam-utils",
|
||||
"ctor",
|
||||
"dirs",
|
||||
"fs2",
|
||||
"futures",
|
||||
"lalrpop",
|
||||
"lalrpop-util",
|
||||
"lazy_static",
|
||||
@ -3188,9 +3210,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sequoia-keystore"
|
||||
version = "0.3.0"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5dd7c49aa034452f70772ecb86fc4ecff3bd41b7a92590fe1925fc5cceea2d9"
|
||||
checksum = "76eaefe78ca373001382f2434ca021145d9b702ba52c04cebc398b8e5956d28a"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"capnp",
|
||||
@ -3200,6 +3222,7 @@ dependencies = [
|
||||
"lazy_static",
|
||||
"log",
|
||||
"paste",
|
||||
"sequoia-directories",
|
||||
"sequoia-ipc",
|
||||
"sequoia-keystore-backend",
|
||||
"sequoia-keystore-gpg-agent",
|
||||
@ -3212,9 +3235,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sequoia-keystore-backend"
|
||||
version = "0.3.0"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f36e37e531a03fb52e8f1281e0b221ddbf2f1df2d51c3d4d9bca5551fd99f2d3"
|
||||
checksum = "5ab69a90e3455e15aa0ff47d676e84bf1a085716691b72156badc50d0a01dab1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@ -3230,9 +3253,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sequoia-keystore-gpg-agent"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d888cad1e8b0f1de24c53aa9e3f0829703bfe2bf7597e8d31c0fabc1c2241458"
|
||||
checksum = "454e8d580617e07d595b8df718d7fa3e26cdc58f35d1ad89f9fecc78ef0d55a7"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@ -3249,9 +3272,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sequoia-keystore-softkeys"
|
||||
version = "0.3.0"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3441d9708124eb5e2b310f1d8337687ddbbab8d5bda2ffa227165b888d8be10"
|
||||
checksum = "e1affc41cb24e491cd38a0a47928bc221b7db4318f0e4b17c49131c9a4a13eb1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
|
@ -44,7 +44,7 @@ indicatif = "0.17"
|
||||
itertools = ">=0.10, <0.13"
|
||||
once_cell = "1.17"
|
||||
sequoia-cert-store = "0.5.3"
|
||||
sequoia-keystore = { version = "0.3" }
|
||||
sequoia-keystore = { version = "0.4" }
|
||||
sequoia-wot = { version = "0.11", default-features = false }
|
||||
tempfile = "3.1"
|
||||
thiserror = "1"
|
||||
|
@ -232,8 +232,8 @@ key store."
|
||||
long_help = "\
|
||||
A key store server manages and protects secret key material. By
|
||||
default, `sq` connects to the key store server listening on
|
||||
`$XDG_DATA_HOME/sequoia`. If no key store server is running, one is
|
||||
started.
|
||||
`$XDG_DATA_HOME/sequoia/keystore`. If no key store server is running,
|
||||
one is started.
|
||||
|
||||
This option causes `sq` to use an alternate key store server. If
|
||||
necessary, a key store server is started, and configured to look for
|
||||
|
@ -24,6 +24,8 @@ use openpgp::serialize::stream::padding::Padder;
|
||||
use openpgp::types::CompressionAlgorithm;
|
||||
use openpgp::types::KeyFlags;
|
||||
|
||||
use sequoia_keystore::Protection;
|
||||
|
||||
use crate::best_effort_primary_uid;
|
||||
use crate::cli;
|
||||
use crate::cli::types::EncryptPurpose;
|
||||
@ -156,7 +158,7 @@ pub fn encrypt<'a, 'b: 'a>(
|
||||
let mut key = keys.into_iter().next().expect("checked for one");
|
||||
|
||||
match key.locked() {
|
||||
Ok(true) => {
|
||||
Ok(Protection::Password(msg)) => {
|
||||
let fpr = key.fingerprint();
|
||||
let cert = config.lookup_one(
|
||||
&KeyHandle::from(&fpr), None, true);
|
||||
@ -173,6 +175,9 @@ pub fn encrypt<'a, 'b: 'a>(
|
||||
};
|
||||
let keyid = KeyID::from(&fpr);
|
||||
|
||||
if let Some(msg) = msg {
|
||||
eprintln!("{}", msg);
|
||||
}
|
||||
loop {
|
||||
let password = Password::from(rpassword::prompt_password(
|
||||
format!("Enter password to unlock {}{}: ",
|
||||
@ -185,9 +190,21 @@ pub fn encrypt<'a, 'b: 'a>(
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(false) => {
|
||||
Ok(Protection::Unlocked) => {
|
||||
// Already unlocked, nothing to do.
|
||||
}
|
||||
Ok(Protection::UnknownProtection(msg))
|
||||
| Ok(Protection::ExternalPassword(msg))
|
||||
| Ok(Protection::ExternalTouch(msg))
|
||||
| Ok(Protection::ExternalOther(msg)) =>
|
||||
{
|
||||
// Locked.
|
||||
eprint!("Key is locked");
|
||||
if let Some(msg) = msg {
|
||||
eprint!(": {}", msg);
|
||||
}
|
||||
eprintln!();
|
||||
}
|
||||
Err(err) => {
|
||||
// Failed to get the key's locked status. Just print
|
||||
// a warning now. We'll (probably) fail more later.
|
||||
|
@ -1,6 +1,9 @@
|
||||
use sequoia_openpgp as openpgp;
|
||||
use openpgp::KeyHandle;
|
||||
|
||||
use sequoia_keystore as keystore;
|
||||
use keystore::Protection;
|
||||
|
||||
use crate::best_effort_primary_uid;
|
||||
use crate::cli;
|
||||
use crate::Config;
|
||||
@ -55,10 +58,10 @@ pub fn list(config: Config, _command: cli::key::ListCommand) -> Result<()> {
|
||||
} else {
|
||||
"not available"
|
||||
},
|
||||
if key.locked().unwrap_or(false) {
|
||||
"locked"
|
||||
} else {
|
||||
"not locked"
|
||||
match key.locked() {
|
||||
Ok(Protection::Unlocked) => "unlocked",
|
||||
Ok(_) => "locked",
|
||||
Err(_) => "unknown protection",
|
||||
},
|
||||
match (signing_capable, decryption_capable) {
|
||||
(true, true) => {
|
||||
|
@ -23,6 +23,8 @@ use openpgp::serialize::stream::{
|
||||
};
|
||||
use openpgp::types::SignatureType;
|
||||
|
||||
use sequoia_keystore::Protection;
|
||||
|
||||
use crate::best_effort_primary_uid;
|
||||
use crate::Config;
|
||||
use crate::load_certs;
|
||||
@ -201,7 +203,7 @@ fn sign_data<'a, 'store, 'rstore>(
|
||||
let mut key = keys.into_iter().next().expect("checked for one");
|
||||
|
||||
match key.locked() {
|
||||
Ok(true) => {
|
||||
Ok(Protection::Password(msg)) => {
|
||||
let fpr = key.fingerprint();
|
||||
let cert = config.lookup_one(
|
||||
&KeyHandle::from(&fpr), None, true);
|
||||
@ -218,6 +220,9 @@ fn sign_data<'a, 'store, 'rstore>(
|
||||
};
|
||||
let keyid = KeyID::from(&fpr);
|
||||
|
||||
if let Some(msg) = msg {
|
||||
eprintln!("{}", msg);
|
||||
}
|
||||
loop {
|
||||
let password = Password::from(rpassword::prompt_password(
|
||||
format!("Enter password to unlock {}{}: ",
|
||||
@ -230,9 +235,21 @@ fn sign_data<'a, 'store, 'rstore>(
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(false) => {
|
||||
Ok(Protection::Unlocked) => {
|
||||
// Already unlocked, nothing to do.
|
||||
}
|
||||
Ok(Protection::UnknownProtection(msg))
|
||||
| Ok(Protection::ExternalPassword(msg))
|
||||
| Ok(Protection::ExternalTouch(msg))
|
||||
| Ok(Protection::ExternalOther(msg)) =>
|
||||
{
|
||||
// Locked.
|
||||
eprint!("Key is locked");
|
||||
if let Some(msg) = msg {
|
||||
eprint!(": {}", msg);
|
||||
}
|
||||
eprintln!();
|
||||
}
|
||||
Err(err) => {
|
||||
// Failed to get the key's locked status. Just print
|
||||
// a warning now. We'll (probably) fail more later.
|
||||
|
98
src/sq.rs
98
src/sq.rs
@ -11,7 +11,6 @@ use anyhow::Context as _;
|
||||
use std::borrow::Borrow;
|
||||
use std::collections::btree_map::{BTreeMap, Entry};
|
||||
use std::fmt;
|
||||
use std::fs::File;
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
@ -440,15 +439,26 @@ pub struct Config<'store, 'rstore>
|
||||
/// Whether a cert or key was freshly imported, updated, or unchanged.
|
||||
///
|
||||
/// Returned by [`Config::import_key`].
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
enum ImportStatus {
|
||||
/// The certificate or key is unchanged.
|
||||
Unchanged,
|
||||
|
||||
/// The certificate or key is new.
|
||||
New,
|
||||
|
||||
/// The certificate or key has been updated.
|
||||
Updated,
|
||||
}
|
||||
|
||||
/// The certificate or key is unchanged.
|
||||
Unchanged,
|
||||
impl From<keystore::ImportStatus> for ImportStatus {
|
||||
fn from(status: keystore::ImportStatus) -> ImportStatus {
|
||||
match status {
|
||||
keystore::ImportStatus::Unchanged => ImportStatus::Unchanged,
|
||||
keystore::ImportStatus::New => ImportStatus::New,
|
||||
keystore::ImportStatus::Updated => ImportStatus::Updated,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'store: 'rstore, 'rstore> Config<'store, 'rstore> {
|
||||
@ -685,7 +695,7 @@ impl<'store: 'rstore, 'rstore> Config<'store, 'rstore> {
|
||||
} else if let Some(dir) = self.key_store_path.as_ref() {
|
||||
Ok(Some(dir.clone()))
|
||||
} else if let Some(dir) = dirs::data_dir() {
|
||||
Ok(Some(dir.join("sequoia")))
|
||||
Ok(Some(dir.join("sequoia/keystore")))
|
||||
} else {
|
||||
Err(anyhow::anyhow!(
|
||||
"No key store, the XDG data directory is not defined"))
|
||||
@ -1163,72 +1173,44 @@ impl<'store: 'rstore, 'rstore> Config<'store, 'rstore> {
|
||||
///
|
||||
/// On success, returns whether the key was imported, updated, or
|
||||
/// unchanged.
|
||||
fn import_key(&self, mut cert: Cert) -> Result<ImportStatus> {
|
||||
fn import_key(&self, cert: Cert) -> Result<ImportStatus> {
|
||||
if ! cert.is_tsk() {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Certificate does not contain any secret key material"));
|
||||
}
|
||||
|
||||
let softkeys = self.key_store_path_or_else()?
|
||||
.join("keystore").join("softkeys");
|
||||
let keystore = self.key_store_or_else()?;
|
||||
let mut keystore = keystore.lock().unwrap();
|
||||
|
||||
std::fs::create_dir_all(&softkeys)?;
|
||||
|
||||
let fpr = cert.fingerprint();
|
||||
|
||||
let filename = softkeys.join(format!("{}.pgp", fpr));
|
||||
|
||||
let mut update = false;
|
||||
match Cert::from_file(&filename) {
|
||||
Ok(old) => {
|
||||
if old.fingerprint() != fpr {
|
||||
return Err(anyhow::anyhow!(
|
||||
"{} contains {}, but expected {}",
|
||||
filename.display(),
|
||||
old.fingerprint(),
|
||||
fpr));
|
||||
}
|
||||
|
||||
update = true;
|
||||
|
||||
// Prefer secret key material from `cert`.
|
||||
cert = old.clone().merge_public_and_secret(cert.clone())?;
|
||||
|
||||
if cert == old {
|
||||
return Ok(ImportStatus::Unchanged);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
// If the file doesn't exist yet, it's not an
|
||||
// error: it just means that we don't have to
|
||||
// merge.
|
||||
if let Some(ioerr) = err.downcast_ref::<std::io::Error>() {
|
||||
if ioerr.kind() == std::io::ErrorKind::NotFound {
|
||||
// Not found. No problem.
|
||||
} else {
|
||||
return Err(err);
|
||||
}
|
||||
} else {
|
||||
return Err(err);
|
||||
}
|
||||
let mut softkeys = None;
|
||||
for mut backend in keystore.backends()?.into_iter() {
|
||||
if backend.id()? == "softkeys" {
|
||||
softkeys = Some(backend);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// We write to a temporary file and then move it into
|
||||
// place. This doesn't eliminate races, but it does
|
||||
// prevent a partial update from destroying the existing
|
||||
// data.
|
||||
let mut tmp_filename = filename.clone();
|
||||
tmp_filename.set_extension("pgp~");
|
||||
drop(keystore);
|
||||
|
||||
let mut f = File::create(&tmp_filename)?;
|
||||
cert.as_tsk().serialize(&mut f)?;
|
||||
let mut softkeys = if let Some(softkeys) = softkeys {
|
||||
softkeys
|
||||
} else {
|
||||
return Err(anyhow::anyhow!("softkeys backend is not configured."));
|
||||
};
|
||||
|
||||
std::fs::rename(&tmp_filename, &filename)?;
|
||||
let mut import_status = ImportStatus::Unchanged;
|
||||
for (s, key) in softkeys.import(&cert)? {
|
||||
self.info(format_args!(
|
||||
"Importing {} into key store: {:?}",
|
||||
key.fingerprint(), s));
|
||||
|
||||
import_status = import_status.max(s.into());
|
||||
}
|
||||
|
||||
// Also insert the certificate into the certificate store.
|
||||
// If we can't, we don't fail. This allows, in
|
||||
// particular, `sq --no-cert-store key import` to work.
|
||||
let fpr = cert.fingerprint();
|
||||
match self.cert_store_or_else() {
|
||||
Ok(cert_store) => {
|
||||
if let Err(err) = cert_store.update(
|
||||
@ -1246,11 +1228,7 @@ impl<'store: 'rstore, 'rstore> Config<'store, 'rstore> {
|
||||
}
|
||||
}
|
||||
|
||||
if update {
|
||||
return Ok(ImportStatus::Updated);
|
||||
} else {
|
||||
return Ok(ImportStatus::New);
|
||||
}
|
||||
Ok(import_status)
|
||||
}
|
||||
|
||||
/// Prints additional information in verbose mode.
|
||||
|
Loading…
Reference in New Issue
Block a user