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.
- In `sq key adopt`, change the certificate file parameter from a
positional parameter to a named parameter, `--cert-file`.
- `sq key adopt` can now use the key store.
* Changes in 0.36.0
- Missing
* Changes in 0.35.0

View File

@ -1,9 +1,6 @@
use anyhow::Context;
use itertools::Itertools;
use openpgp::cert::amalgamation::ValidAmalgamation;
use openpgp::cert::CertParser;
use openpgp::packet::key;
use openpgp::packet::signature::subpacket::SubpacketTag;
use openpgp::packet::signature::SignatureBuilder;
@ -21,132 +18,118 @@ use sequoia_openpgp as openpgp;
use crate::Sq;
use crate::cli;
use crate::decrypt_key;
pub fn adopt(sq: Sq, command: cli::key::AdoptCommand) -> Result<()>
{
let input = command.cert_file.open()?;
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 {
null_policy
null_policy_ = openpgp::policy::NullPolicy::new();
&null_policy_
} else {
sq.policy
};
// Find the corresponding keys.
for keyring in sq.keyrings.iter() {
for cert in CertParser::from_file(&keyring)
.context(format!("Parsing: {}", &keyring.display()))?
{
let cert = cert.context(format!("Parsing {}", keyring.display()))?;
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;
}
let wanted: Vec<(
KeyHandle,
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)
{
Ok(cert) => cert,
Err(err) => return (kh, Err(err)),
};
for key in vc.keys() {
for (id, ref mut keyo) in wanted.iter_mut() {
if id.aliases(key.key_handle()) {
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 vc = match cert.with_policy(adoptee_policy, sq.time) {
Ok(vc) => vc,
Err(err) => return (kh, Err(err)),
};
let builder = builder
.set_signature_creation_time(sq.time)?;
let key = vc.keys().key_handle(kh.clone())
.next().expect("have key");
*keyo = Some((
key.key().clone().role_into_subordinate(),
builder,
));
}
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 = 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 missing: Vec<&KeyHandle> = wanted
.iter()
.filter_map(|(id, keyo)| match keyo {
Some(_) => None,
None => Some(id),
let builder = match builder.set_signature_creation_time(sq.time) {
Ok(b) => b,
Err(err) => return (kh, Err(err)),
};
let key = key.key().clone().role_into_subordinate();
(kh, Ok((cert, key, builder)))
})
.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.
let pk = cert.primary_key().key();
let mut pk_signer =
decrypt_key(pk.clone().parts_into_secret()?, passwords)?
.into_keypair()?;
let mut pk_signer = sq.get_primary_key(&cert, None)
.with_context(|| {
format!("Getting signer for {}'s primary key",
cert.fingerprint())
})?.0;
// Add the keys and signatures to cert.
let mut packets: Vec<Packet> = vec![];
for (_, ka) in wanted.into_iter() {
let (key, mut builder) = ka.expect("Checked for missing keys above.");
for (cert, key, mut builder) in wanted.into_iter() {
// Set key expiration.
if let Some(e) = &command.expire {
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 {
// Derive a signer.
let mut subkey_signer =
decrypt_key(key.clone().parts_into_secret()?, passwords)?
.into_keypair()?;
let ka = cert.keys().key_handle(key.fingerprint())
.next()
.expect("have key");
let mut subkey_signer = sq.get_signer(&ka)
.with_context(|| {
format!("Getting signer for {}", ka.fingerprint())
})?.0;
let backsig = builder
.embedded_signatures()

View File

@ -143,19 +143,28 @@ mod integration {
fn adopt_encryption() -> Result<()> {
let sq = Sq::new();
// Have Bob adopt alice's encryption subkey.
let cert = sq.key_adopt(
[ alice() ].to_vec(),
bob(),
[ alice_encryption().0.clone() ].to_vec(),
None,
false,
"-",
true)
.unwrap();
for keystore in [false, true] {
let keyrings = if keystore {
sq.key_import(alice());
Vec::new()
} else {
vec![ alice() ]
};
assert!(
check(&cert, 2, (bob_primary(), &[alice_encryption()])).is_ok());
// Have Bob adopt alice's encryption subkey.
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(())
}
@ -164,19 +173,28 @@ mod integration {
fn adopt_signing() -> Result<()> {
let sq = Sq::new();
// Adopt a signing subkey (subkey has secret key material).
let cert = sq.key_adopt(
[ alice() ].to_vec(),
bob(),
[ alice_signing().0.clone() ].to_vec(),
None,
false,
"-",
true)
.unwrap();
for keystore in [false, true] {
let keyrings = if keystore {
sq.key_import(alice());
Vec::new()
} else {
vec![ alice() ]
};
assert!(
check(&cert, 2, (bob_primary(), &[alice_signing()])).is_ok());
// Adopt a signing subkey (subkey has secret key material).
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(())
}
@ -185,19 +203,28 @@ mod integration {
fn adopt_certification() -> Result<()> {
let sq = Sq::new();
// Adopt a certification subkey (subkey has secret key
// material).
let cert = sq.key_adopt(
[ alice() ].to_vec(),
carol(),
[ alice_primary().0.clone() ].to_vec(),
None,
false,
"-",
true)
.unwrap();
for keystore in [false, true] {
let keyrings = if keystore {
sq.key_import(alice());
Vec::new()
} else {
vec![ alice() ]
};
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(())
}
@ -207,24 +234,33 @@ mod integration {
let sq = Sq::new();
// Adopt an encryption subkey and a signing subkey.
let cert = sq.key_adopt(
[ alice() ].to_vec(),
bob(),
[
alice_signing().0.clone(),
alice_encryption().0.clone(),
].to_vec(),
None,
false,
"-",
true)
.unwrap();
for keystore in [false, true] {
let keyrings = if keystore {
sq.key_import(alice());
Vec::new()
} else {
vec![ alice() ]
};
assert!(
check(&cert, 3,
(bob_primary(),
&[alice_signing(), alice_encryption()]))
.is_ok());
let cert = sq.key_adopt(
keyrings,
bob(),
[
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(())
}
@ -233,22 +269,31 @@ mod integration {
fn adopt_twice() -> Result<()> {
let sq = Sq::new();
// Adopt the same an encryption subkey twice.
let cert = sq.key_adopt(
[ alice() ].to_vec(),
bob(),
[
alice_encryption().0.clone(),
alice_encryption().0.clone(),
].to_vec(),
None,
false,
"-",
true)
.unwrap();
for keystore in [false, true] {
let keyrings = if keystore {
sq.key_import(alice());
Vec::new()
} else {
vec![ alice() ]
};
assert!(
check(&cert, 2, (bob_primary(), &[alice_encryption()])).is_ok());
// Adopt the same an encryption subkey twice.
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(())
}
@ -280,21 +325,30 @@ mod integration {
fn adopt_own_encryption() -> Result<()> {
let sq = Sq::new();
// Adopt its own encryption subkey. This should be a noop.
let cert = sq.key_adopt(
[ alice(), ].to_vec(),
alice(),
[
alice_encryption().0.clone(),
].to_vec(),
None,
false,
"-",
true)
.unwrap();
for keystore in [false, true] {
let keyrings = if keystore {
sq.key_import(alice());
Vec::new()
} else {
vec![ alice() ]
};
assert!(
check(&cert, 3, (alice_primary(), &[alice_encryption()])).is_ok());
// Adopt its own encryption subkey. This should be a noop.
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(())
}
@ -303,21 +357,30 @@ mod integration {
fn adopt_own_primary() -> Result<()> {
let sq = Sq::new();
// Adopt own primary key.
let cert = sq.key_adopt(
[ bob(), ].to_vec(),
bob(),
[
bob_primary().0.clone(),
].to_vec(),
None,
false,
"-",
true)
.unwrap();
for keystore in [false, true] {
let keyrings = if keystore {
sq.key_import(bob());
Vec::new()
} else {
vec![ bob() ]
};
assert!(
check(&cert, 2, (bob_primary(), &[bob_primary()])).is_ok());
// Adopt own primary key.
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(())
}
@ -326,21 +389,30 @@ mod integration {
fn adopt_missing() -> Result<()> {
let sq = Sq::new();
// Adopt a key that is not present.
let r = sq.key_adopt(
[ bob(), ].to_vec(),
bob(),
[
"1234 5678 90AB CDEF 1234 5678 90AB CDEF"
.parse::<KeyHandle>()
.expect("valid fingerprint")
].to_vec(),
None,
false,
"-",
false);
for keystore in [false, true] {
let keyrings = if keystore {
sq.key_import(bob());
Vec::new()
} else {
vec![ bob() ]
};
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(())
}
@ -349,30 +421,40 @@ mod integration {
fn adopt_from_multiple() -> Result<()> {
let sq = Sq::new();
// Adopt own primary key.
let cert = sq.key_adopt(
[ alice(), carol(), ].to_vec(),
bob(),
[
alice_signing().0.clone(),
alice_encryption().0.clone(),
carol_signing().0.clone(),
carol_encryption().0.clone(),
].to_vec(),
None,
false,
"-",
true)
.unwrap();
for keystore in [false, true] {
let keyrings = if keystore {
sq.key_import(alice());
sq.key_import(carol());
Vec::new()
} else {
vec![ alice(), carol() ]
};
assert!(
check(&cert, 5,
(bob_primary(),
&[
alice_signing(), alice_encryption(),
carol_signing(), carol_encryption()
]))
.is_ok());
// Adopt own primary key.
let cert = sq.key_adopt(
keyrings,
bob(),
[
alice_signing().0.clone(),
alice_encryption().0.clone(),
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(())
}