This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

pkgs.age-plugin-gpg: fall back to gpg keyring for recipient keys

Encrypting to an age1gpg recipient asked gpg-agent (READKEY) for the
public key, which fails on hosts that only have the public key imported
and no secret key or smartcard stub. Export the key by keygrip with gpg
instead in that case and build the same key as the agent path, so
stanzas are unchanged. Decryption still goes through gpg-agent.

+90
+86
pkgs/age-plugin-gpg/keyring-fallback.patch
··· 1 + --- a/src/main.rs 2 + +++ b/src/main.rs 3 + @@ -238,7 +238,7 @@ 4 + })?; 5 + 6 + let v = Handle::current() 7 + - .block_on(fetch_key(&mut self.agent, keygrip)) 8 + + .block_on(fetch_recipient_key(&mut self.agent, keygrip)) 9 + .map_err(|e| RecipientError::Recipient { 10 + index, 11 + message: format!("failed to fetch recipient key by keygrip: {e}"), 12 + @@ -337,6 +337,65 @@ 13 + let key = parse_public_key(key)?; 14 + Ok(key) 15 + } 16 + +/// Encryption only needs the public key, which gpg-agent doesn't know unless 17 + +/// the secret key (or a smartcard stub) is available. Fall back to the gpg 18 + +/// keyring in that case. 19 + +async fn fetch_recipient_key( 20 + + agent: &mut Agent, 21 + + keygrip: Keygrip, 22 + +) -> Result<Key<PublicParts, UnspecifiedRole>> { 23 + + match fetch_key(agent, keygrip.clone()).await { 24 + + Ok(key) => Ok(key), 25 + + Err(agent_err) => fetch_key_from_keyring(&keygrip) 26 + + .map_err(|e| anyhow!("{agent_err}; gpg keyring: {e}")), 27 + + } 28 + +} 29 + +/// Exports the key by keygrip with gpg and builds the same S-expression 30 + +/// READKEY would return, so the resulting key is identical to the agent path. 31 + +fn fetch_key_from_keyring(keygrip: &Keygrip) -> Result<Key<PublicParts, UnspecifiedRole>> { 32 + + use sequoia_openpgp::cert::CertParser; 33 + + use sequoia_openpgp::parse::Parse as _; 34 + + 35 + + let out = std::process::Command::new("gpg") 36 + + .args(["--batch", "--export"]) 37 + + .arg(format!("&{keygrip}")) 38 + + .stderr(std::process::Stdio::null()) 39 + + .output() 40 + + .context("failed to run gpg")?; 41 + + ensure!(out.status.success(), "gpg --export failed: {}", out.status); 42 + + ensure!(!out.stdout.is_empty(), "key not found"); 43 + + 44 + + let s = |v: &[u8]| Sexp::String(String_::new(v)); 45 + + let l = |v: Vec<Sexp>| Sexp::List(v); 46 + + for cert in CertParser::from_bytes(&out.stdout)? { 47 + + for ka in cert?.keys() { 48 + + let mpis = ka.key().mpis(); 49 + + if Keygrip::of(mpis).ok().as_ref() != Some(keygrip) { 50 + + continue; 51 + + } 52 + + let alg = match mpis { 53 + + PublicKey::RSA { e, n } => l(vec![ 54 + + s(b"rsa"), 55 + + l(vec![s(b"n"), s(n.value())]), 56 + + l(vec![s(b"e"), s(e.value())]), 57 + + ]), 58 + + PublicKey::ECDH { 59 + + curve: Curve::Cv25519, 60 + + q, 61 + + .. 62 + + } => l(vec![ 63 + + s(b"ecc"), 64 + + l(vec![s(b"curve"), s(b"Curve25519")]), 65 + + l(vec![s(b"flags"), s(b"djb-tweak")]), 66 + + l(vec![s(b"q"), s(q.value())]), 67 + + ]), 68 + + _ => bail!("unsupported key algorithm: {}", ka.key().pk_algo()), 69 + + }; 70 + + return parse_public_key(l(vec![s(b"public-key"), alg])); 71 + + } 72 + + } 73 + + bail!("key not found") 74 + +} 75 + async fn fetch_keypair(agent: &mut Agent, keygrip: Keygrip) -> Result<KeyPair> { 76 + let key = fetch_key(agent, keygrip).await?; 77 + 78 + @@ -368,7 +427,7 @@ 79 + let keygripid: Keygrip = keygrip.parse()?; 80 + let keygripbytes = hex::decode(keygripid.to_string()).expect("keygrip is hex"); 81 + 82 + - Handle::current().block_on(fetch_key(&mut agent, keygripid))?; 83 + + Handle::current().block_on(fetch_recipient_key(&mut agent, keygripid))?; 84 + 85 + println!( 86 + "{}",
+4
pkgs/age-plugin-gpg/package.nix
··· 17 17 hash = "sha256-HfCl+YAjKPNFtLAHjNyjlHhRxD85qZLWsfKzqORtj5U="; 18 18 }; 19 19 20 + # Encrypt to keys that are only in the gpg keyring (no secret key or 21 + # smartcard stub in gpg-agent), e.g. on hosts generating clan vars. 22 + patches = [ ./keyring-fallback.patch ]; 23 + 20 24 cargoLock = { 21 25 lockFile = "${finalAttrs.src}/Cargo.lock"; 22 26 };