runner_core/packs/
verify.rs1use std::convert::TryInto;
2
3use anyhow::{Context, Result, anyhow, bail};
4use base64::Engine;
5use base64::engine::general_purpose::STANDARD_NO_PAD;
6use ed25519_dalek::{Signature, Verifier, VerifyingKey};
7
8pub struct PackVerifier {
9 key: VerifyingKey,
10}
11
12impl PackVerifier {
13 pub fn from_env_value(value: &str) -> Result<Self> {
14 let (alg, key_b64) = value
15 .split_once(':')
16 .ok_or_else(|| anyhow!("invalid PACK_PUBLIC_KEY format"))?;
17 if !alg.eq_ignore_ascii_case("ed25519") {
18 bail!("unsupported public key algorithm `{alg}`");
19 }
20 let raw = STANDARD_NO_PAD
21 .decode(key_b64.trim())
22 .or_else(|_| base64::engine::general_purpose::STANDARD.decode(key_b64.trim()))
23 .context("PACK_PUBLIC_KEY is not valid base64")?;
24 let key_bytes: &[u8; 32] = raw
25 .as_slice()
26 .try_into()
27 .map_err(|_| anyhow!("PACK_PUBLIC_KEY must be 32 bytes"))?;
28 let key = VerifyingKey::from_bytes(key_bytes)
29 .map_err(|_| anyhow!("PACK_PUBLIC_KEY is not a valid Ed25519 key"))?;
30 Ok(Self { key })
31 }
32
33 pub fn verify(&self, message: &[u8], signature_b64: &str) -> Result<()> {
34 let sig_bytes = STANDARD_NO_PAD
35 .decode(signature_b64.trim())
36 .or_else(|_| base64::engine::general_purpose::STANDARD.decode(signature_b64.trim()))
37 .context("pack signature is not valid base64")?;
38 let signature = Signature::from_slice(&sig_bytes)
39 .map_err(|_| anyhow!("pack signature is not a valid Ed25519 signature"))?;
40 self.key
41 .verify(message, &signature)
42 .context("pack signature verification failed")
43 }
44}
45
46#[cfg(test)]
47mod tests {
48 use super::*;
49 use base64::engine::general_purpose::STANDARD;
50 use ed25519_dalek::{Signer, SigningKey};
51
52 #[test]
53 fn accepts_valid_signatures() {
54 let secret = SigningKey::from_bytes(&[7u8; 32]);
55 let public_b64 = STANDARD.encode(secret.verifying_key().as_bytes());
56 let verifier = PackVerifier::from_env_value(&format!("ed25519:{public_b64}")).unwrap();
57 let message = b"sha256:deadbeef";
58 let signature = secret.sign(message);
59 let signature_b64 = STANDARD.encode(signature.to_bytes());
60 verifier.verify(message, &signature_b64).unwrap();
61 }
62
63 #[test]
64 fn rejects_invalid_signatures() {
65 let secret = SigningKey::from_bytes(&[1u8; 32]);
66 let public_b64 = STANDARD.encode(secret.verifying_key().as_bytes());
67 let verifier = PackVerifier::from_env_value(&format!("ed25519:{public_b64}")).unwrap();
68 let bad_sig = STANDARD.encode([0xAAu8; 64]);
69 assert!(verifier.verify(b"msg", &bad_sig).is_err());
70 }
71}