diff options
Diffstat (limited to 'sq/src/commands/key.rs')
-rw-r--r-- | sq/src/commands/key.rs | 204 |
1 files changed, 201 insertions, 3 deletions
diff --git a/sq/src/commands/key.rs b/sq/src/commands/key.rs index dc1df48a..b10da74a 100644 --- a/sq/src/commands/key.rs +++ b/sq/src/commands/key.rs @@ -3,14 +3,21 @@ use clap::ArgMatches; use itertools::Itertools; use std::time::{SystemTime, Duration}; -use crate::openpgp::Result; +use crate::openpgp::KeyHandle; use crate::openpgp::Packet; -use crate::openpgp::cert::prelude::*; -use crate::openpgp::types::KeyFlags; +use crate::openpgp::Result; use crate::openpgp::armor::{Writer, Kind}; +use crate::openpgp::cert::prelude::*; +use crate::openpgp::packet::prelude::*; +use crate::openpgp::packet::signature::subpacket::SubpacketTag; +use crate::openpgp::parse::Parse; +use crate::openpgp::policy::Policy; use crate::openpgp::serialize::Serialize; +use crate::openpgp::types::KeyFlags; +use crate::openpgp::types::SignatureType; use crate::create_or_stdout; +use crate::decrypt_key; const SECONDS_IN_DAY : u64 = 24 * 60 * 60; const SECONDS_IN_YEAR : u64 = @@ -246,3 +253,194 @@ fn parse_duration(expiry: &str) -> Result<Duration> { Ok(Duration::new(count * factor, 0)) } + +pub fn adopt(m: &ArgMatches, p: &dyn Policy) -> Result<()> { + let cert = m.value_of("certificate").unwrap(); + let cert = Cert::from_file(cert) + .context(format!("Parsing {}", cert))?; + let mut wanted: Vec<(KeyHandle, + Option<(Key<key::PublicParts, key::SubordinateRole>, + SignatureBuilder)>)> + = vec![]; + + // Gather the Key IDs / Fingerprints and make sure they are valid. + for id in m.values_of("key").unwrap_or_default() { + let h = id.parse::<KeyHandle>()?; + if h.is_invalid() { + return Err(anyhow::anyhow!( + "Invalid Fingerprint or KeyID ('{:?}')", id)); + } + wanted.push((h, None)); + } + + // Find the corresponding keys. + for keyring in m.values_of("keyring").unwrap_or_default() { + for cert in CertParser::from_file(keyring) + .context(format!("Parsing: {}", keyring))? + { + let cert = cert.context(format!("Parsing {}", keyring))?; + let vc = match cert.with_policy(p, None) { + Ok(vc) => vc, + Err(err) => { + eprintln!("Ignoring {} from '{}': {}", + cert.keyid().to_hex(), keyring, err); + continue; + } + }; + + 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), + }; + + *keyo = Some( + (key.key().clone().role_into_subordinate(), + builder)); + } + } + } + } + } + } + } + + + // 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), + } + }) + .collect(); + if missing.len() > 0 { + return Err(anyhow::anyhow!( + "Keys not found: {}", + missing.iter().map(|&h| h.to_hex()).join(", "))); + } + + + let passwords = &mut Vec::new(); + + // Get a signer. + let pk = cert.primary_key().key(); + let mut pk_signer = + decrypt_key( + pk.clone().parts_into_secret()?, + passwords)? + .into_keypair()?; + + + // Add the keys and signatues to cert. + let mut packets: Vec<Packet> = vec![]; + for (_, ka) in wanted.into_iter() { + let (key, builder) = ka.expect("Checked for missing keys above."); + let mut builder = builder; + + // If there is a valid backsig, recreate it. + let need_backsig = builder.key_flags() + .map(|kf| kf.for_signing() || kf.for_certification()) + .expect("Missing keyflags"); + + if need_backsig { + // Derive a signer. + let mut subkey_signer + = decrypt_key( + key.clone().parts_into_secret()?, + passwords)? + .into_keypair()?; + + let backsig = builder.embedded_signatures() + .filter(|backsig| { + (*backsig).clone().verify_primary_key_binding( + &cert.primary_key(), + &key).is_ok() + }) + .nth(0) + .map(|sig| SignatureBuilder::from(sig.clone())) + .unwrap_or_else(|| { + SignatureBuilder::new(SignatureType::PrimaryKeyBinding) + }) + .sign_primary_key_binding(&mut subkey_signer, pk, &key)?; + + builder = builder.set_embedded_signature(backsig)?; + } else { + builder = builder.modify_hashed_area(|mut a| { + a.remove_all(SubpacketTag::EmbeddedSignature); + Ok(a) + })?; + } + + let mut sig = builder.sign_subkey_binding(&mut pk_signer, pk, &key)?; + + // Verify it. + assert!(sig.verify_subkey_binding(pk_signer.public(), pk, &key) + .is_ok()); + + packets.push(key.into()); + packets.push(sig.into()); + } + + let cert = cert.clone().insert_packets(packets.clone())?; + + cert.as_tsk().armored().serialize(&mut std::io::stdout())?; + + let vc = cert.with_policy(p, None).expect("still valid"); + for pair in packets[..].chunks(2) { + let newkey: &Key<key::PublicParts, key::UnspecifiedRole> = match pair[0] { + Packet::PublicKey(ref k) => k.into(), + Packet::PublicSubkey(ref k) => k.into(), + Packet::SecretKey(ref k) => k.into(), + Packet::SecretSubkey(ref k) => k.into(), + ref p => panic!("Expected a key, got: {:?}", p), + }; + let newsig: &Signature = match pair[1] { + Packet::Signature(ref s) => s, + ref p => panic!("Expected a sig, got: {:?}", p), + }; + + let mut found = false; + for key in vc.keys() { + if key.fingerprint() == newkey.fingerprint() { + for sig in key.self_signatures() { + if sig == newsig { + found = true; + break; + } + } + } + } + assert!(found, "Subkey: {:?}\nSignature: {:?}", newkey, newsig); + } + + Ok(()) +} |