Skip to main content

runner_core/packs/
verify.rs

1use 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}