//! Ed25519 release signatures (verification only; the Python module `odian_packs.signing`). //! //! Key text: `ed25519:`. Signature: Ed25519 over //! [`crate::manifest::signing_payload`], written as `ed25519:`. use base64::alphabet; use base64::engine::{DecodePaddingMode, GeneralPurpose, GeneralPurposeConfig}; use base64::Engine; use ed25519_dalek::{Signature, VerifyingKey}; use serde_json::Value; use crate::error::{Error, IntegrityKind, Result}; use crate::manifest::signing_payload; /// Python's `base64.b64decode(text, validate=True)`: standard alphabet, correct padding, /// no whitespace, and (like CPython) non-zero trailing bits tolerated. const B64: GeneralPurpose = GeneralPurpose::new( &alphabet::STANDARD, GeneralPurposeConfig::new() .with_decode_allow_trailing_bits(true) .with_decode_padding_mode(DecodePaddingMode::RequireCanonical), ); fn signature_error(detail: &str) -> Error { Error::integrity(IntegrityKind::Signature, "manifest", detail) } /// Parse `ed25519:` (surrounding whitespace ignored). pub fn load_public_key(text: &str) -> Result { let body = text .trim() .strip_prefix("ed25519:") .ok_or_else(|| signature_error("public key must look like 'ed25519:'"))?; let raw = B64.decode(body).map_err(|_| signature_error("public key is not valid base64"))?; let bytes: [u8; 32] = raw.try_into().map_err(|_| signature_error("an Ed25519 public key is 32 bytes"))?; VerifyingKey::from_bytes(&bytes).map_err(|_| signature_error("public key is not a valid Ed25519 point")) } /// Error ([`IntegrityKind::Signature`]) unless `manifest` carries a valid signature by that key. pub fn verify(manifest: &Value, public_key: &str) -> Result<()> { let body = manifest .get("signature") .and_then(Value::as_str) .and_then(|s| s.strip_prefix("ed25519:")) .ok_or_else(|| signature_error("manifest is not signed"))?; let raw = B64.decode(body).map_err(|_| signature_error("signature is not valid base64"))?; let key = load_public_key(public_key)?; let payload = signing_payload(manifest)?; let mismatch = || signature_error("signature does not match: manifest altered, or signed by another key"); let signature = Signature::from_slice(&raw).map_err(|_| mismatch())?; key.verify_strict(&payload, &signature).map_err(|_| mismatch()) }