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,132 +18,118 @@ 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,
let cert = cert.context(format!("Parsing {}", keyring.display()))?; Key<key::PublicParts, key::SubordinateRole>,
SignatureBuilder,
let vc = match cert.with_policy(adoptee_policy, None) { )>,
Ok(vc) => vc, )> = command.key
Err(err) => { .into_iter()
wprintln!( .map(|kh| {
"Ignoring {} from '{}': {}", let cert = match sq.lookup_one_with_policy(
cert.keyid().to_hex(), kh.clone(), None, false, adoptee_policy, sq.time)
keyring.display(), {
err Ok(cert) => cert,
); Err(err) => return (kh, Err(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.
{
()
}
None => {
let sig = key.binding_signature();
let builder: SignatureBuilder = match sig.typ()
{
SignatureType::SubkeyBinding => {
sig.clone().into()
}
SignatureType::DirectKey
| SignatureType::PositiveCertification
| SignatureType::CasualCertification
| SignatureType::PersonaCertification
| SignatureType::GenericCertification => {
// Convert to a binding
// signature.
let kf = sig.key_flags().context(
"Missing required \
subpacket, KeyFlags",
)?;
SignatureBuilder::new(
SignatureType::SubkeyBinding,
)
.set_key_flags(kf)?
}
_ => panic!(
"Unsupported binding \
signature: {:?}",
sig
),
};
let builder = builder let key = vc.keys().key_handle(kh.clone())
.set_signature_creation_time(sq.time)?; .next().expect("have key");
*keyo = Some(( let sig = key.binding_signature();
key.key().clone().role_into_subordinate(), let builder: SignatureBuilder = match sig.typ() {
builder, SignatureType::SubkeyBinding => {
)); sig.clone().into()
} }
SignatureType::DirectKey
| SignatureType::PositiveCertification
| SignatureType::CasualCertification
| SignatureType::PersonaCertification
| SignatureType::GenericCertification => {
// Convert to a binding signature.
let kf = match sig.key_flags().context(
"Missing required subpacket, KeyFlags")
{
Ok(kh) => kh,
Err(err) => return (kh, Err(err)),
};
match SignatureBuilder::new(SignatureType::SubkeyBinding)
.set_key_flags(kf)
{
Ok(b) => b,
Err(err) => return (kh, Err(err)),
} }
} }
} _ => panic!("Unsupported binding signature: {:?}", sig),
} };
}
}
// If we are missing any keys, stop now. let builder = match builder.set_signature_creation_time(sq.time) {
let missing: Vec<&KeyHandle> = wanted Ok(b) => b,
.iter() Err(err) => return (kh, Err(err)),
.filter_map(|(id, keyo)| match keyo { };
Some(_) => None,
None => Some(id), let key = key.key().clone().role_into_subordinate();
(kh, Ok((cert, key, builder)))
}) })
.collect(); .collect();
if !missing.is_empty() {
return Err(anyhow::anyhow!(
"Keys not found: {}",
missing.iter().map(|&h| h.to_hex()).join(", ")
));
}
let passwords = &mut Vec::new(); // If we are missing any keys, stop now.
let mut missing = false;
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:");
}
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,19 +143,28 @@ mod integration {
fn adopt_encryption() -> Result<()> { fn adopt_encryption() -> Result<()> {
let sq = Sq::new(); let sq = Sq::new();
// Have Bob adopt alice's encryption subkey. for keystore in [false, true] {
let cert = sq.key_adopt( let keyrings = if keystore {
[ alice() ].to_vec(), sq.key_import(alice());
bob(), Vec::new()
[ alice_encryption().0.clone() ].to_vec(), } else {
None, vec![ alice() ]
false, };
"-",
true)
.unwrap();
assert!( // Have Bob adopt alice's encryption subkey.
check(&cert, 2, (bob_primary(), &[alice_encryption()])).is_ok()); let cert = sq.key_adopt(
keyrings,
bob(),
[ alice_encryption().0.clone() ].to_vec(),
None,
false,
"-",
true)
.unwrap();
assert!(
check(&cert, 2, (bob_primary(), &[alice_encryption()])).is_ok());
}
Ok(()) Ok(())
} }
@ -164,19 +173,28 @@ mod integration {
fn adopt_signing() -> Result<()> { fn adopt_signing() -> Result<()> {
let sq = Sq::new(); let sq = Sq::new();
// Adopt a signing subkey (subkey has secret key material). for keystore in [false, true] {
let cert = sq.key_adopt( let keyrings = if keystore {
[ alice() ].to_vec(), sq.key_import(alice());
bob(), Vec::new()
[ alice_signing().0.clone() ].to_vec(), } else {
None, vec![ alice() ]
false, };
"-",
true)
.unwrap();
assert!( // Adopt a signing subkey (subkey has secret key material).
check(&cert, 2, (bob_primary(), &[alice_signing()])).is_ok()); let cert = sq.key_adopt(
keyrings,
bob(),
[ alice_signing().0.clone() ].to_vec(),
None,
false,
"-",
true)
.unwrap();
assert!(
check(&cert, 2, (bob_primary(), &[alice_signing()])).is_ok());
}
Ok(()) Ok(())
} }
@ -185,19 +203,28 @@ mod integration {
fn adopt_certification() -> Result<()> { fn adopt_certification() -> Result<()> {
let sq = Sq::new(); let sq = Sq::new();
// Adopt a certification subkey (subkey has secret key for keystore in [false, true] {
// material). let keyrings = if keystore {
let cert = sq.key_adopt( sq.key_import(alice());
[ alice() ].to_vec(), Vec::new()
carol(), } else {
[ alice_primary().0.clone() ].to_vec(), vec![ alice() ]
None, };
false,
"-",
true)
.unwrap();
assert!(check(&cert, 4, (carol_primary(), &[alice_primary()])).is_ok()); // Adopt a certification subkey (subkey has secret key
// material).
let cert = sq.key_adopt(
keyrings,
carol(),
[ alice_primary().0.clone() ].to_vec(),
None,
false,
"-",
true)
.unwrap();
assert!(check(&cert, 4, (carol_primary(), &[alice_primary()])).is_ok());
}
Ok(()) Ok(())
} }
@ -207,24 +234,33 @@ 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.
let cert = sq.key_adopt( for keystore in [false, true] {
[ alice() ].to_vec(), let keyrings = if keystore {
bob(), sq.key_import(alice());
[ Vec::new()
alice_signing().0.clone(), } else {
alice_encryption().0.clone(), vec![ alice() ]
].to_vec(), };
None,
false,
"-",
true)
.unwrap();
assert!( let cert = sq.key_adopt(
check(&cert, 3, keyrings,
(bob_primary(), bob(),
&[alice_signing(), alice_encryption()])) [
.is_ok()); alice_signing().0.clone(),
alice_encryption().0.clone(),
].to_vec(),
None,
false,
"-",
true)
.unwrap();
assert!(
check(&cert, 3,
(bob_primary(),
&[alice_signing(), alice_encryption()]))
.is_ok());
}
Ok(()) Ok(())
} }
@ -233,22 +269,31 @@ mod integration {
fn adopt_twice() -> Result<()> { fn adopt_twice() -> Result<()> {
let sq = Sq::new(); let sq = Sq::new();
// Adopt the same an encryption subkey twice. for keystore in [false, true] {
let cert = sq.key_adopt( let keyrings = if keystore {
[ alice() ].to_vec(), sq.key_import(alice());
bob(), Vec::new()
[ } else {
alice_encryption().0.clone(), vec![ alice() ]
alice_encryption().0.clone(), };
].to_vec(),
None,
false,
"-",
true)
.unwrap();
assert!( // Adopt the same an encryption subkey twice.
check(&cert, 2, (bob_primary(), &[alice_encryption()])).is_ok()); let cert = sq.key_adopt(
keyrings,
bob(),
[
alice_encryption().0.clone(),
alice_encryption().0.clone(),
].to_vec(),
None,
false,
"-",
true)
.unwrap();
assert!(
check(&cert, 2, (bob_primary(), &[alice_encryption()])).is_ok());
}
Ok(()) Ok(())
} }
@ -280,21 +325,30 @@ mod integration {
fn adopt_own_encryption() -> Result<()> { fn adopt_own_encryption() -> Result<()> {
let sq = Sq::new(); let sq = Sq::new();
// Adopt its own encryption subkey. This should be a noop. for keystore in [false, true] {
let cert = sq.key_adopt( let keyrings = if keystore {
[ alice(), ].to_vec(), sq.key_import(alice());
alice(), Vec::new()
[ } else {
alice_encryption().0.clone(), vec![ alice() ]
].to_vec(), };
None,
false,
"-",
true)
.unwrap();
assert!( // Adopt its own encryption subkey. This should be a noop.
check(&cert, 3, (alice_primary(), &[alice_encryption()])).is_ok()); let cert = sq.key_adopt(
keyrings,
alice(),
[
alice_encryption().0.clone(),
].to_vec(),
None,
false,
"-",
true)
.unwrap();
assert!(
check(&cert, 3, (alice_primary(), &[alice_encryption()])).is_ok());
}
Ok(()) Ok(())
} }
@ -303,21 +357,30 @@ mod integration {
fn adopt_own_primary() -> Result<()> { fn adopt_own_primary() -> Result<()> {
let sq = Sq::new(); let sq = Sq::new();
// Adopt own primary key. for keystore in [false, true] {
let cert = sq.key_adopt( let keyrings = if keystore {
[ bob(), ].to_vec(), sq.key_import(bob());
bob(), Vec::new()
[ } else {
bob_primary().0.clone(), vec![ bob() ]
].to_vec(), };
None,
false,
"-",
true)
.unwrap();
assert!( // Adopt own primary key.
check(&cert, 2, (bob_primary(), &[bob_primary()])).is_ok()); let cert = sq.key_adopt(
keyrings,
bob(),
[
bob_primary().0.clone(),
].to_vec(),
None,
false,
"-",
true)
.unwrap();
assert!(
check(&cert, 2, (bob_primary(), &[bob_primary()])).is_ok());
}
Ok(()) Ok(())
} }
@ -326,21 +389,30 @@ mod integration {
fn adopt_missing() -> Result<()> { fn adopt_missing() -> Result<()> {
let sq = Sq::new(); let sq = Sq::new();
// Adopt a key that is not present. for keystore in [false, true] {
let r = sq.key_adopt( let keyrings = if keystore {
[ bob(), ].to_vec(), sq.key_import(bob());
bob(), Vec::new()
[ } else {
"1234 5678 90AB CDEF 1234 5678 90AB CDEF" vec![ bob() ]
.parse::<KeyHandle>() };
.expect("valid fingerprint")
].to_vec(),
None,
false,
"-",
false);
assert!(r.is_err()); // Adopt a key that is not present.
let r = sq.key_adopt(
keyrings,
bob(),
[
"1234 5678 90AB CDEF 1234 5678 90AB CDEF"
.parse::<KeyHandle>()
.expect("valid fingerprint")
].to_vec(),
None,
false,
"-",
false);
assert!(r.is_err());
}
Ok(()) Ok(())
} }
@ -349,30 +421,40 @@ mod integration {
fn adopt_from_multiple() -> Result<()> { fn adopt_from_multiple() -> Result<()> {
let sq = Sq::new(); let sq = Sq::new();
// Adopt own primary key. for keystore in [false, true] {
let cert = sq.key_adopt( let keyrings = if keystore {
[ alice(), carol(), ].to_vec(), sq.key_import(alice());
bob(), sq.key_import(carol());
[ Vec::new()
alice_signing().0.clone(), } else {
alice_encryption().0.clone(), vec![ alice(), carol() ]
carol_signing().0.clone(), };
carol_encryption().0.clone(),
].to_vec(),
None,
false,
"-",
true)
.unwrap();
assert!( // Adopt own primary key.
check(&cert, 5, let cert = sq.key_adopt(
(bob_primary(), keyrings,
&[ bob(),
alice_signing(), alice_encryption(), [
carol_signing(), carol_encryption() alice_signing().0.clone(),
])) alice_encryption().0.clone(),
.is_ok()); carol_signing().0.clone(),
carol_encryption().0.clone(),
].to_vec(),
None,
false,
"-",
true)
.unwrap();
assert!(
check(&cert, 5,
(bob_primary(),
&[
alice_signing(), alice_encryption(),
carol_signing(), carol_encryption()
]))
.is_ok());
}
Ok(()) Ok(())
} }