Upgrade sequoia-keystore.

This commit is contained in:
Neal H. Walfield 2024-05-19 23:22:11 +02:00
parent 81009e984d
commit 1d162d214b
No known key found for this signature in database
GPG Key ID: 6863C9AD5B4D22D3
7 changed files with 126 additions and 88 deletions

57
Cargo.lock generated
View File

@ -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",

View File

@ -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"

View File

@ -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

View File

@ -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.

View File

@ -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) => {

View File

@ -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.

View File

@ -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.