#![expect(
clippy::disallowed_types,
reason = "dev/verification tooling over JSON artifacts (the catalogue, results, wire \
exchanges), whose shapes belong to the artifacts and the SUT"
)]
use base64::Engine as _;
use serde_json::Value;
use sha2::{Digest as _, Sha256};
#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize)]
#[serde(tag = "mode", rename_all = "snake_case", deny_unknown_fields)]
pub enum SigningMode {
Digest {
algorithm: String,
encoding: String,
#[serde(default)]
prefix: String,
},
Pgp {
public_key: String,
},
}
pub fn canonical_form(envelope: &Value) -> Result<String, String> {
let mut value = envelope.clone();
if let Value::Object(map) = &mut value {
map.remove("signature");
}
serde_jcs::to_string(&value).map_err(|e| format!("canonical-form (jcs): {e}"))
}
pub fn verify(envelope: &Value, signature: &str, mode: &SigningMode) -> Result<bool, String> {
let canonical = canonical_form(envelope)?;
match mode {
SigningMode::Digest {
algorithm,
encoding,
prefix,
} => {
let body = signature.strip_prefix(prefix.as_str()).unwrap_or(signature);
let recomputed = recompute_digest(canonical.as_bytes(), algorithm, encoding)?;
Ok(recomputed == body)
}
SigningMode::Pgp { public_key } => verify_pgp(canonical.as_bytes(), signature, public_key),
}
}
fn recompute_digest(bytes: &[u8], algorithm: &str, encoding: &str) -> Result<String, String> {
let raw: Vec<u8> = match algorithm {
"sha256" => Sha256::digest(bytes).to_vec(),
other => return Err(format!("unknown digest algorithm {other:?}")),
};
match encoding {
"base64" => Ok(base64::engine::general_purpose::STANDARD.encode(raw)),
"base64url" => Ok(base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(raw)),
other => Err(format!("unknown digest encoding {other:?}")),
}
}
fn verify_pgp(
bytes: &[u8],
signature_armored: &str,
public_key_armored: &str,
) -> Result<bool, String> {
use pgp::composed::{Deserializable as _, DetachedSignature, SignedPublicKey};
let (key, _) = SignedPublicKey::from_string(public_key_armored)
.map_err(|e| format!("pgp public key: {e}"))?;
let (sig, _) = DetachedSignature::from_string(signature_armored)
.map_err(|e| format!("pgp signature: {e}"))?;
if sig.verify(&key, bytes).is_ok() {
return Ok(true);
}
Ok(key
.public_subkeys
.iter()
.any(|subkey| sig.verify(subkey, bytes).is_ok()))
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
fn digest_mode() -> SigningMode {
SigningMode::Digest {
algorithm: "sha256".to_owned(),
encoding: "base64".to_owned(),
prefix: "sha256:".to_owned(),
}
}
#[test]
fn canonical_form_drops_signature_and_is_jcs() {
let env = json!({ "signature": "sha256:x", "b": 2, "a": 1 });
assert_eq!(canonical_form(&env).unwrap(), r#"{"a":1,"b":2}"#);
}
#[test]
fn digest_verifies_and_detects_tamper() {
let env = json!({ "_type": "ORIGINAL_VERSION", "data": { "b": 2, "a": 1 } });
let canonical = canonical_form(&env).unwrap();
let good = format!(
"sha256:{}",
base64::engine::general_purpose::STANDARD.encode(Sha256::digest(canonical.as_bytes()))
);
assert!(verify(&env, &good, &digest_mode()).unwrap());
assert!(!verify(&env, "sha256:AAAA", &digest_mode()).unwrap());
let env2 = json!({ "_type": "ORIGINAL_VERSION", "data": { "b": 3, "a": 1 } });
assert!(!verify(&env2, &good, &digest_mode()).unwrap());
}
#[test]
#[expect(
clippy::panic_in_result_fn,
reason = "test assertions in the Book's Result-returning test shape"
)]
fn subkey_signature_verifies_against_the_certificate() -> Result<(), Box<dyn std::error::Error>>
{
use pgp::composed::{
ArmorOptions, Deserializable as _, DetachedSignature, SignedSecretKey,
};
use pgp::crypto::hash::HashAlgorithm;
use pgp::types::Password;
use rand::rngs::OsRng;
let keys = std::path::Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/../.."))
.join("artifacts/corpus/keys");
let (secret, _) = SignedSecretKey::from_string(&std::fs::read_to_string(
keys.join("cnf-signing.sec.asc"),
)?)?;
let public_armored = std::fs::read_to_string(keys.join("cnf-signing.pub.asc"))?;
let subkey = secret
.secret_subkeys
.iter()
.find(|sub| sub.signatures.iter().any(|sig| sig.key_flags().sign()))
.ok_or("the corpus certificate carries no signing subkey")?;
let data = b"ferroehr-cnf subkey verification";
let sig = DetachedSignature::sign_binary_data(
OsRng,
&subkey.key,
&Password::empty(),
HashAlgorithm::Sha256,
&data[..],
)?
.to_armored_string(ArmorOptions::default())?;
assert!(
verify_pgp(data, &sig, &public_armored)?,
"a signature by the certificate's signing subkey must verify"
);
assert!(!verify_pgp(b"tampered", &sig, &public_armored)?);
let primary = DetachedSignature::sign_binary_data(
OsRng,
&secret.primary_key,
&Password::empty(),
HashAlgorithm::Sha256,
&data[..],
)?
.to_armored_string(ArmorOptions::default())?;
assert!(
verify_pgp(data, &primary, &public_armored)?,
"a signature by the certificate's primary key must verify"
);
assert!(!verify_pgp(b"tampered", &primary, &public_armored)?);
Ok(())
}
#[test]
fn present_is_signature_nonempty() {
let env = json!({ "signature": "sha256:abc", "data": {} });
assert!(
env.get("signature")
.and_then(Value::as_str)
.is_some_and(|s| !s.is_empty())
);
assert!(canonical_form(&env).unwrap().contains("data"));
}
#[test]
fn an_undeclarable_digest_posture_is_an_error_not_a_verdict() {
let env = json!({ "_type": "ORIGINAL_VERSION", "data": { "a": 1 } });
let unknown_algorithm = SigningMode::Digest {
algorithm: "sha3-512".to_owned(),
encoding: "base64".to_owned(),
prefix: String::new(),
};
assert_eq!(
verify(&env, "anything", &unknown_algorithm),
Err(r#"unknown digest algorithm "sha3-512""#.to_owned())
);
let unknown_encoding = SigningMode::Digest {
algorithm: "sha256".to_owned(),
encoding: "hex".to_owned(),
prefix: String::new(),
};
assert_eq!(
verify(&env, "anything", &unknown_encoding),
Err(r#"unknown digest encoding "hex""#.to_owned())
);
}
#[test]
fn the_declared_prefix_and_the_declared_encoding_both_drive_the_comparison() {
let env = json!({ "_type": "ORIGINAL_VERSION", "data": { "a": 1 } });
let canonical = canonical_form(&env).unwrap();
let digest = Sha256::digest(canonical.as_bytes());
let url_safe = SigningMode::Digest {
algorithm: "sha256".to_owned(),
encoding: "base64url".to_owned(),
prefix: String::new(),
};
let body = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(digest);
assert!(verify(&env, &body, &url_safe).unwrap());
let standard = base64::engine::general_purpose::STANDARD.encode(digest);
assert_ne!(
standard, body,
"the padded standard alphabet and base64url encode the same digest differently"
);
assert!(
!verify(&env, &standard, &url_safe).unwrap(),
"a standard-alphabet body must not satisfy a base64url declaration"
);
let prefixed = SigningMode::Digest {
algorithm: "sha256".to_owned(),
encoding: "base64".to_owned(),
prefix: "sha256:".to_owned(),
};
assert!(verify(&env, &format!("sha256:{standard}"), &prefixed).unwrap());
assert!(verify(&env, &standard, &prefixed).unwrap());
assert!(!verify(&env, &format!("sha256:{standard}x"), &prefixed).unwrap());
}
#[test]
fn malformed_pgp_material_is_an_error_not_a_failed_verification() {
let key_failure = verify_pgp(
b"payload",
"-----BEGIN PGP SIGNATURE-----\nx\n",
"not a key",
)
.expect_err("a non-armored key cannot be read");
assert!(key_failure.starts_with("pgp public key:"), "{key_failure}");
let keys = std::path::Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/../.."))
.join("artifacts/corpus/keys");
let public_armored =
std::fs::read_to_string(keys.join("cnf-signing.pub.asc")).expect("the committed key");
let signature_failure = verify_pgp(b"payload", "not a signature", &public_armored)
.expect_err("a non-armored signature cannot be read");
assert!(
signature_failure.starts_with("pgp signature:"),
"{signature_failure}"
);
let mode = SigningMode::Pgp {
public_key: public_armored,
};
assert!(verify(&json!({ "data": {} }), "not a signature", &mode).is_err());
}
#[test]
fn the_signing_posture_is_read_from_the_ixit_declaration() {
let digest: SigningMode = serde_json::from_value(json!({
"mode": "digest", "algorithm": "sha256", "encoding": "base64"
}))
.expect("the digest posture, with the prefix defaulted");
assert_eq!(
digest,
SigningMode::Digest {
algorithm: "sha256".to_owned(),
encoding: "base64".to_owned(),
prefix: String::new(),
}
);
let pgp: SigningMode =
serde_json::from_value(json!({ "mode": "pgp", "public_key": "-----BEGIN-----" }))
.expect("the openPGP posture");
assert_eq!(
pgp,
SigningMode::Pgp {
public_key: "-----BEGIN-----".to_owned()
}
);
assert!(
serde_json::from_value::<SigningMode>(json!({ "mode": "hmac", "key": "k" })).is_err()
);
}
}