Change sq key adopt to support the key store.

- Change `sq key adopt` to use the key store.

  - See #205.
This commit is contained in:
Neal H. Walfield 2024-06-09 12:22:35 +02:00
parent 54ae8eda30
commit f3037392f6
No known key found for this signature in database
GPG Key ID: 6863C9AD5B4D22D3
3 changed files with 308 additions and 237 deletions

1
NEWS
View File

@ -58,6 +58,7 @@
- `sq pki certify` can now use the cert store and the key store. - `sq pki certify` can now use the cert store and the key store.
- In `sq key adopt`, change the certificate file parameter from a - In `sq key adopt`, change the certificate file parameter from a
positional parameter to a named parameter, `--cert-file`. positional parameter to a named parameter, `--cert-file`.
- `sq key adopt` can now use the key store.
* Changes in 0.36.0 * Changes in 0.36.0
- Missing - Missing
* Changes in 0.35.0 * Changes in 0.35.0

View File

@ -1,9 +1,6 @@
use anyhow::Context; use anyhow::Context;
use itertools::Itertools;
use openpgp::cert::amalgamation::ValidAmalgamation; use openpgp::cert::amalgamation::ValidAmalgamation;
use openpgp::cert::CertParser;
use openpgp::packet::key; use openpgp::packet::key;
use openpgp::packet::signature::subpacket::SubpacketTag; use openpgp::packet::signature::subpacket::SubpacketTag;
use openpgp::packet::signature::SignatureBuilder; use openpgp::packet::signature::SignatureBuilder;
@ -21,63 +18,48 @@ use sequoia_openpgp as openpgp;
use crate::Sq; use crate::Sq;
use crate::cli; use crate::cli;
use crate::decrypt_key;
pub fn adopt(sq: Sq, command: cli::key::AdoptCommand) -> Result<()> pub fn adopt(sq: Sq, command: cli::key::AdoptCommand) -> Result<()>
{ {
let input = command.cert_file.open()?; let input = command.cert_file.open()?;
let cert = Cert::from_buffered_reader(input)?; let cert = Cert::from_buffered_reader(input)?;
let mut wanted: Vec<(
KeyHandle,
Option<(
Key<key::PublicParts, key::SubordinateRole>,
SignatureBuilder,
)>,
)> = command.key
.into_iter()
.map(|kh| (kh, None))
.collect::<Vec<_>>();
let null_policy = &openpgp::policy::NullPolicy::new(); let null_policy_;
let adoptee_policy: &dyn Policy = if command.allow_broken_crypto { let adoptee_policy: &dyn Policy = if command.allow_broken_crypto {
null_policy null_policy_ = openpgp::policy::NullPolicy::new();
&null_policy_
} else { } else {
sq.policy sq.policy
}; };
// Find the corresponding keys. // Find the corresponding keys.
for keyring in sq.keyrings.iter() { let wanted: Vec<(
for cert in CertParser::from_file(&keyring) KeyHandle,
.context(format!("Parsing: {}", &keyring.display()))? Result<(
Cert,
Key<key::PublicParts, key::SubordinateRole>,
SignatureBuilder,
)>,
)> = command.key
.into_iter()
.map(|kh| {
let cert = match sq.lookup_one_with_policy(
kh.clone(), None, false, adoptee_policy, sq.time)
{ {
let cert = cert.context(format!("Parsing {}", keyring.display()))?; Ok(cert) => cert,
Err(err) => return (kh, Err(err)),
let vc = match cert.with_policy(adoptee_policy, None) {
Ok(vc) => vc,
Err(err) => {
wprintln!(
"Ignoring {} from '{}': {}",
cert.keyid().to_hex(),
keyring.display(),
err
);
continue;
}
}; };
for key in vc.keys() { let vc = match cert.with_policy(adoptee_policy, sq.time) {
for (id, ref mut keyo) in wanted.iter_mut() { Ok(vc) => vc,
if id.aliases(key.key_handle()) { Err(err) => return (kh, Err(err)),
match keyo { };
Some((_, _)) =>
// We already saw this key. let key = vc.keys().key_handle(kh.clone())
{ .next().expect("have key");
()
}
None => {
let sig = key.binding_signature(); let sig = key.binding_signature();
let builder: SignatureBuilder = match sig.typ() let builder: SignatureBuilder = match sig.typ() {
{
SignatureType::SubkeyBinding => { SignatureType::SubkeyBinding => {
sig.clone().into() sig.clone().into()
} }
@ -86,67 +68,68 @@ pub fn adopt(sq: Sq, command: cli::key::AdoptCommand) -> Result<()>
| SignatureType::CasualCertification | SignatureType::CasualCertification
| SignatureType::PersonaCertification | SignatureType::PersonaCertification
| SignatureType::GenericCertification => { | SignatureType::GenericCertification => {
// Convert to a binding // Convert to a binding signature.
// signature. let kf = match sig.key_flags().context(
let kf = sig.key_flags().context( "Missing required subpacket, KeyFlags")
"Missing required \ {
subpacket, KeyFlags", Ok(kh) => kh,
)?; Err(err) => return (kh, Err(err)),
SignatureBuilder::new( };
SignatureType::SubkeyBinding, match SignatureBuilder::new(SignatureType::SubkeyBinding)
) .set_key_flags(kf)
.set_key_flags(kf)? {
Ok(b) => b,
Err(err) => return (kh, Err(err)),
} }
_ => panic!( }
"Unsupported binding \ _ => panic!("Unsupported binding signature: {:?}", sig),
signature: {:?}",
sig
),
}; };
let builder = builder let builder = match builder.set_signature_creation_time(sq.time) {
.set_signature_creation_time(sq.time)?; Ok(b) => b,
Err(err) => return (kh, Err(err)),
};
*keyo = Some(( let key = key.key().clone().role_into_subordinate();
key.key().clone().role_into_subordinate(),
builder,
));
}
}
}
}
}
}
}
// If we are missing any keys, stop now. (kh, Ok((cert, key, builder)))
let missing: Vec<&KeyHandle> = wanted
.iter()
.filter_map(|(id, keyo)| match keyo {
Some(_) => None,
None => Some(id),
}) })
.collect(); .collect();
if !missing.is_empty() {
return Err(anyhow::anyhow!( // If we are missing any keys, stop now.
"Keys not found: {}", let mut missing = false;
missing.iter().map(|&h| h.to_hex()).join(", ") let wanted = wanted.into_iter()
)); .filter_map(|(id, keyo)| {
match keyo {
Ok((cert, key, builder)) => Some((cert, key, builder)),
Err(err) => {
if ! missing {
eprintln!("Missing keys:");
} }
let passwords = &mut Vec::new(); eprintln!(" - {}: {}", id, err);
missing = true;
None
}
}
})
.collect::<Vec<_>>();
if missing {
return Err(anyhow::anyhow!("Missing some keys"));
}
// Get a signer. // Get a signer.
let pk = cert.primary_key().key(); let pk = cert.primary_key().key();
let mut pk_signer = let mut pk_signer = sq.get_primary_key(&cert, None)
decrypt_key(pk.clone().parts_into_secret()?, passwords)? .with_context(|| {
.into_keypair()?; format!("Getting signer for {}'s primary key",
cert.fingerprint())
})?.0;
// Add the keys and signatures to cert. // Add the keys and signatures to cert.
let mut packets: Vec<Packet> = vec![]; let mut packets: Vec<Packet> = vec![];
for (_, ka) in wanted.into_iter() { for (cert, key, mut builder) in wanted.into_iter() {
let (key, mut builder) = ka.expect("Checked for missing keys above.");
// Set key expiration. // Set key expiration.
if let Some(e) = &command.expire { if let Some(e) = &command.expire {
builder = builder.set_key_expiration_time(&key, e.timestamp())?; builder = builder.set_key_expiration_time(&key, e.timestamp())?;
@ -160,9 +143,14 @@ pub fn adopt(sq: Sq, command: cli::key::AdoptCommand) -> Result<()>
if need_backsig { if need_backsig {
// Derive a signer. // Derive a signer.
let mut subkey_signer = let ka = cert.keys().key_handle(key.fingerprint())
decrypt_key(key.clone().parts_into_secret()?, passwords)? .next()
.into_keypair()?; .expect("have key");
let mut subkey_signer = sq.get_signer(&ka)
.with_context(|| {
format!("Getting signer for {}", ka.fingerprint())
})?.0;
let backsig = builder let backsig = builder
.embedded_signatures() .embedded_signatures()

View File

@ -143,9 +143,17 @@ mod integration {
fn adopt_encryption() -> Result<()> { fn adopt_encryption() -> Result<()> {
let sq = Sq::new(); let sq = Sq::new();
for keystore in [false, true] {
let keyrings = if keystore {
sq.key_import(alice());
Vec::new()
} else {
vec![ alice() ]
};
// Have Bob adopt alice's encryption subkey. // Have Bob adopt alice's encryption subkey.
let cert = sq.key_adopt( let cert = sq.key_adopt(
[ alice() ].to_vec(), keyrings,
bob(), bob(),
[ alice_encryption().0.clone() ].to_vec(), [ alice_encryption().0.clone() ].to_vec(),
None, None,
@ -156,6 +164,7 @@ mod integration {
assert!( assert!(
check(&cert, 2, (bob_primary(), &[alice_encryption()])).is_ok()); check(&cert, 2, (bob_primary(), &[alice_encryption()])).is_ok());
}
Ok(()) Ok(())
} }
@ -164,9 +173,17 @@ mod integration {
fn adopt_signing() -> Result<()> { fn adopt_signing() -> Result<()> {
let sq = Sq::new(); let sq = Sq::new();
for keystore in [false, true] {
let keyrings = if keystore {
sq.key_import(alice());
Vec::new()
} else {
vec![ alice() ]
};
// Adopt a signing subkey (subkey has secret key material). // Adopt a signing subkey (subkey has secret key material).
let cert = sq.key_adopt( let cert = sq.key_adopt(
[ alice() ].to_vec(), keyrings,
bob(), bob(),
[ alice_signing().0.clone() ].to_vec(), [ alice_signing().0.clone() ].to_vec(),
None, None,
@ -177,6 +194,7 @@ mod integration {
assert!( assert!(
check(&cert, 2, (bob_primary(), &[alice_signing()])).is_ok()); check(&cert, 2, (bob_primary(), &[alice_signing()])).is_ok());
}
Ok(()) Ok(())
} }
@ -185,10 +203,18 @@ mod integration {
fn adopt_certification() -> Result<()> { fn adopt_certification() -> Result<()> {
let sq = Sq::new(); let sq = Sq::new();
for keystore in [false, true] {
let keyrings = if keystore {
sq.key_import(alice());
Vec::new()
} else {
vec![ alice() ]
};
// Adopt a certification subkey (subkey has secret key // Adopt a certification subkey (subkey has secret key
// material). // material).
let cert = sq.key_adopt( let cert = sq.key_adopt(
[ alice() ].to_vec(), keyrings,
carol(), carol(),
[ alice_primary().0.clone() ].to_vec(), [ alice_primary().0.clone() ].to_vec(),
None, None,
@ -198,6 +224,7 @@ mod integration {
.unwrap(); .unwrap();
assert!(check(&cert, 4, (carol_primary(), &[alice_primary()])).is_ok()); assert!(check(&cert, 4, (carol_primary(), &[alice_primary()])).is_ok());
}
Ok(()) Ok(())
} }
@ -207,8 +234,16 @@ mod integration {
let sq = Sq::new(); let sq = Sq::new();
// Adopt an encryption subkey and a signing subkey. // Adopt an encryption subkey and a signing subkey.
for keystore in [false, true] {
let keyrings = if keystore {
sq.key_import(alice());
Vec::new()
} else {
vec![ alice() ]
};
let cert = sq.key_adopt( let cert = sq.key_adopt(
[ alice() ].to_vec(), keyrings,
bob(), bob(),
[ [
alice_signing().0.clone(), alice_signing().0.clone(),
@ -225,6 +260,7 @@ mod integration {
(bob_primary(), (bob_primary(),
&[alice_signing(), alice_encryption()])) &[alice_signing(), alice_encryption()]))
.is_ok()); .is_ok());
}
Ok(()) Ok(())
} }
@ -233,9 +269,17 @@ mod integration {
fn adopt_twice() -> Result<()> { fn adopt_twice() -> Result<()> {
let sq = Sq::new(); let sq = Sq::new();
for keystore in [false, true] {
let keyrings = if keystore {
sq.key_import(alice());
Vec::new()
} else {
vec![ alice() ]
};
// Adopt the same an encryption subkey twice. // Adopt the same an encryption subkey twice.
let cert = sq.key_adopt( let cert = sq.key_adopt(
[ alice() ].to_vec(), keyrings,
bob(), bob(),
[ [
alice_encryption().0.clone(), alice_encryption().0.clone(),
@ -249,6 +293,7 @@ mod integration {
assert!( assert!(
check(&cert, 2, (bob_primary(), &[alice_encryption()])).is_ok()); check(&cert, 2, (bob_primary(), &[alice_encryption()])).is_ok());
}
Ok(()) Ok(())
} }
@ -280,9 +325,17 @@ mod integration {
fn adopt_own_encryption() -> Result<()> { fn adopt_own_encryption() -> Result<()> {
let sq = Sq::new(); let sq = Sq::new();
for keystore in [false, true] {
let keyrings = if keystore {
sq.key_import(alice());
Vec::new()
} else {
vec![ alice() ]
};
// Adopt its own encryption subkey. This should be a noop. // Adopt its own encryption subkey. This should be a noop.
let cert = sq.key_adopt( let cert = sq.key_adopt(
[ alice(), ].to_vec(), keyrings,
alice(), alice(),
[ [
alice_encryption().0.clone(), alice_encryption().0.clone(),
@ -295,6 +348,7 @@ mod integration {
assert!( assert!(
check(&cert, 3, (alice_primary(), &[alice_encryption()])).is_ok()); check(&cert, 3, (alice_primary(), &[alice_encryption()])).is_ok());
}
Ok(()) Ok(())
} }
@ -303,9 +357,17 @@ mod integration {
fn adopt_own_primary() -> Result<()> { fn adopt_own_primary() -> Result<()> {
let sq = Sq::new(); let sq = Sq::new();
for keystore in [false, true] {
let keyrings = if keystore {
sq.key_import(bob());
Vec::new()
} else {
vec![ bob() ]
};
// Adopt own primary key. // Adopt own primary key.
let cert = sq.key_adopt( let cert = sq.key_adopt(
[ bob(), ].to_vec(), keyrings,
bob(), bob(),
[ [
bob_primary().0.clone(), bob_primary().0.clone(),
@ -318,6 +380,7 @@ mod integration {
assert!( assert!(
check(&cert, 2, (bob_primary(), &[bob_primary()])).is_ok()); check(&cert, 2, (bob_primary(), &[bob_primary()])).is_ok());
}
Ok(()) Ok(())
} }
@ -326,9 +389,17 @@ mod integration {
fn adopt_missing() -> Result<()> { fn adopt_missing() -> Result<()> {
let sq = Sq::new(); let sq = Sq::new();
for keystore in [false, true] {
let keyrings = if keystore {
sq.key_import(bob());
Vec::new()
} else {
vec![ bob() ]
};
// Adopt a key that is not present. // Adopt a key that is not present.
let r = sq.key_adopt( let r = sq.key_adopt(
[ bob(), ].to_vec(), keyrings,
bob(), bob(),
[ [
"1234 5678 90AB CDEF 1234 5678 90AB CDEF" "1234 5678 90AB CDEF 1234 5678 90AB CDEF"
@ -341,6 +412,7 @@ mod integration {
false); false);
assert!(r.is_err()); assert!(r.is_err());
}
Ok(()) Ok(())
} }
@ -349,9 +421,18 @@ mod integration {
fn adopt_from_multiple() -> Result<()> { fn adopt_from_multiple() -> Result<()> {
let sq = Sq::new(); let sq = Sq::new();
for keystore in [false, true] {
let keyrings = if keystore {
sq.key_import(alice());
sq.key_import(carol());
Vec::new()
} else {
vec![ alice(), carol() ]
};
// Adopt own primary key. // Adopt own primary key.
let cert = sq.key_adopt( let cert = sq.key_adopt(
[ alice(), carol(), ].to_vec(), keyrings,
bob(), bob(),
[ [
alice_signing().0.clone(), alice_signing().0.clone(),
@ -373,6 +454,7 @@ mod integration {
carol_signing(), carol_encryption() carol_signing(), carol_encryption()
])) ]))
.is_ok()); .is_ok());
}
Ok(()) Ok(())
} }