use binrw::binrw;
use super::block::{Blob, Prefixed, Seq, read_exact, read_leading, write_vec};
use super::{Certificate, PublicKey, Scheme, SignError, SignatureAlgorithm, SigningIdentity, VerifyError};
use crate::digest::ContentDigest;
pub(crate) const V3_MIN_SDK: u32 = 28;
pub(crate) const MAX_SDK: u32 = i32::MAX as u32;
const STRIPPING_PROTECTION: u32 = 0xbeef_f00d;
const ROTATION_ATTRIBUTES: [u32; 3] = [PROOF_OF_ROTATION, 0x559f_8b02, 0xc2a6_b3ba];
const PROOF_OF_ROTATION: u32 = 0x3ba0_6f8c;
#[binrw]
#[brw(little)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) struct SdkRange {
pub min: u32,
pub max: u32,
}
#[binrw]
#[brw(little, import(v3: bool))]
#[derive(Clone, Debug, Eq, PartialEq)]
struct SignerRecord {
signed_data: Prefixed<Blob>,
#[brw(if(v3))]
sdk: Option<SdkRange>,
signatures: Prefixed<Seq<Prefixed<SignatureRecord>>>,
public_key: Prefixed<Blob>,
}
#[binrw]
#[brw(little, import(v3: bool))]
#[derive(Clone, Debug, Eq, PartialEq)]
struct SignedData {
digests: Prefixed<Seq<Prefixed<AlgorithmValue>>>,
certificates: Prefixed<Seq<Prefixed<Blob>>>,
#[brw(if(v3))]
sdk: Option<SdkRange>,
attributes: Prefixed<Seq<Prefixed<Attribute>>>,
}
#[binrw]
#[brw(little)]
#[derive(Clone, Debug, Eq, PartialEq)]
struct AlgorithmValue {
algorithm: u32,
value: Prefixed<Blob>,
}
type SignatureRecord = AlgorithmValue;
#[binrw]
#[brw(little)]
#[derive(Clone, Debug, Eq, PartialEq)]
struct Attribute {
id: u32,
value: Blob,
}
type Signers = Prefixed<Seq<Prefixed<SignerRecord>>>;
pub(crate) fn sign(
scheme: Scheme,
identities: &[SigningIdentity],
digests: &[(ContentDigest, Vec<u8>)],
stronger: &[Scheme],
) -> Result<Vec<u8>, SignError> {
let v3 = scheme == Scheme::V3;
let sdk = v3.then_some(SdkRange { min: V3_MIN_SDK, max: MAX_SDK });
let attributes = stronger
.iter()
.filter(|_| scheme == Scheme::V2)
.map(|s| Prefixed(Attribute { id: STRIPPING_PROTECTION, value: Blob(s.number().to_le_bytes().to_vec()) }))
.collect();
let attributes = Prefixed(Seq(attributes));
let signers = identities
.iter()
.map(|identity| {
let content = identity.key().signature_algorithm().content_digest();
let content = content.expect("signing algorithms have computable digests");
let digest = digests.iter().find(|(d, _)| *d == content).map(|(_, v)| v.clone());
let digest = digest.expect("digest computed per signer");
signer_record(identity, &signed_data(identity, digest, sdk, attributes.clone()), v3)
})
.collect::<Result<_, _>>()?;
Ok(write_vec::<Signers>(&Prefixed(Seq(signers)), (v3,)))
}
fn signed_data(
identity: &SigningIdentity,
digest: Vec<u8>,
sdk: Option<SdkRange>,
attributes: Prefixed<Seq<Prefixed<Attribute>>>,
) -> SignedData {
let algorithm = identity.key().signature_algorithm();
SignedData {
digests: Prefixed(Seq(vec![Prefixed(AlgorithmValue {
algorithm: algorithm.id(),
value: Prefixed(Blob(digest)),
})])),
certificates: Prefixed(Seq(identity.chain().iter().map(|c| Prefixed(Blob(c.der().to_vec()))).collect())),
sdk,
attributes,
}
}
fn signer_record(
identity: &SigningIdentity,
signed: &SignedData,
v3: bool,
) -> Result<Prefixed<SignerRecord>, SignError> {
let algorithm = identity.key().signature_algorithm();
let signed_bytes = write_vec(signed, (v3,));
let signature = identity.key().sign(algorithm, &signed_bytes)?;
Ok(Prefixed(SignerRecord {
signed_data: Prefixed(Blob(signed_bytes)),
sdk: signed.sdk,
signatures: Prefixed(Seq(vec![Prefixed(AlgorithmValue {
algorithm: algorithm.id(),
value: Prefixed(Blob(signature)),
})])),
public_key: Prefixed(Blob(identity.certificate().public_key_der()?)),
}))
}
#[derive(Clone, Debug)]
pub(crate) struct SchemeSigner {
pub certificates: Vec<Certificate>,
pub algorithm: SignatureAlgorithm,
pub sdk: Option<SdkRange>,
pub protected_schemes: Vec<u32>,
}
pub(crate) fn verify(
scheme: Scheme,
value: &[u8],
content_digest: &mut dyn FnMut(ContentDigest) -> Result<Option<Vec<u8>>, VerifyError>,
) -> Result<Vec<SchemeSigner>, VerifyError> {
let v3 = scheme == Scheme::V3;
let malformed = |offset| VerifyError::MalformedScheme { scheme, offset };
let signers: Signers = read_exact(value, (v3,)).map_err(malformed)?;
if signers.0.0.is_empty() {
return Err(VerifyError::NoSigners { scheme });
}
signers
.0
.0
.into_iter()
.enumerate()
.map(|(index, Prefixed(signer))| verify_signer(scheme, index, signer, content_digest))
.collect()
}
fn verify_signer(
scheme: Scheme,
signer: usize,
record: SignerRecord,
content_digest: &mut dyn FnMut(ContentDigest) -> Result<Option<Vec<u8>>, VerifyError>,
) -> Result<SchemeSigner, VerifyError> {
let v3 = scheme == Scheme::V3;
let signatures: Vec<AlgorithmValue> = record.signatures.0.0.into_iter().map(|p| p.0).collect();
if signatures.is_empty() {
return Err(VerifyError::NoSignatures { scheme, signer });
}
let mut best: Option<(SignatureAlgorithm, &AlgorithmValue)> = None;
for candidate in &signatures {
if let Some(algorithm) = SignatureAlgorithm::from_id(candidate.algorithm)
&& best.is_none_or(|(best, _)| algorithm.strength() > best.strength())
{
best = Some((algorithm, candidate));
}
}
let (algorithm, signature) = best.ok_or_else(|| VerifyError::NoSupportedAlgorithm {
scheme,
signer,
ids: signatures.iter().map(|s| s.algorithm).collect(),
})?;
let public_key_der = record.public_key.0.0;
let public_key = PublicKey::from_spki_der(&public_key_der)?;
let signed_bytes = record.signed_data.0.0;
public_key.verify(algorithm, &signed_bytes, &signature.value.0.0).map_err(|source| VerifyError::BadSignature {
scheme,
signer,
algorithm,
source,
})?;
let signed: SignedData =
read_leading(&signed_bytes, (v3,)).map_err(|offset| VerifyError::MalformedScheme { scheme, offset })?;
let digests: Vec<AlgorithmValue> = signed.digests.0.0.into_iter().map(|p| p.0).collect();
let signature_ids: Vec<u32> = signatures.iter().map(|s| s.algorithm).collect();
let digest_ids: Vec<u32> = digests.iter().map(|d| d.algorithm).collect();
if signature_ids != digest_ids {
return Err(VerifyError::AlgorithmListMismatch { scheme, signer });
}
let certificates =
signed.certificates.0.0.iter().map(|c| Certificate::from_der(&c.0.0)).collect::<Result<Vec<_>, _>>()?;
let leaf = certificates.first().ok_or(VerifyError::NoCertificates { scheme, signer })?;
if leaf.public_key_der()? != public_key_der {
return Err(VerifyError::PublicKeyMismatch { scheme, signer });
}
if v3 && record.sdk != signed.sdk {
return Err(VerifyError::SdkRangeMismatch { scheme, signer });
}
if let Some(SdkRange { min, max }) = signed.sdk
&& (min > max || max > MAX_SDK)
{
return Err(VerifyError::InvalidSdkRange { scheme, signer, min, max });
}
if signed.attributes.0.0.iter().any(|a| ROTATION_ATTRIBUTES.contains(&a.0.id)) {
return Err(VerifyError::KeyRotationUnsupported);
}
let expected = digests
.iter()
.find(|d| d.algorithm == algorithm.id())
.map(|d| &d.value.0.0)
.ok_or(VerifyError::MissingDigest { scheme, signer, algorithm })?;
let digest = algorithm.content_digest().expect("verifiable algorithms have computable digests");
if let Some(actual) = content_digest(digest)?
&& actual != *expected
{
return Err(VerifyError::DigestMismatch { scheme, signer });
}
let protected_schemes = signed
.attributes
.0
.0
.iter()
.filter(|a| a.0.id == STRIPPING_PROTECTION)
.filter_map(|a| a.0.value.0.get(..4).map(|b| u32::from_le_bytes([b[0], b[1], b[2], b[3]])))
.collect();
Ok(SchemeSigner { certificates, algorithm, sdk: signed.sdk, protected_schemes })
}
#[cfg(test)]
mod tests {
use super::*;
fn identity() -> SigningIdentity {
SigningIdentity::from_pem(include_str!("../../tests/fixtures/keys/debug.pem")).unwrap()
}
fn v3_block(sdk: SdkRange, attributes: Vec<Attribute>) -> Vec<u8> {
let identity = identity();
let attributes = Prefixed(Seq(attributes.into_iter().map(Prefixed).collect()));
let signed = signed_data(&identity, vec![0; 32], Some(sdk), attributes);
let record = signer_record(&identity, &signed, true).unwrap();
write_vec::<Signers>(&Prefixed(Seq(vec![record])), (true,))
}
fn verify_v3(block: &[u8]) -> Result<Vec<SchemeSigner>, VerifyError> {
verify(Scheme::V3, block, &mut |_| Ok(None))
}
#[test]
fn well_formed_signers_verify() {
let signers = verify_v3(&v3_block(SdkRange { min: V3_MIN_SDK, max: MAX_SDK }, Vec::new())).unwrap();
assert_eq!(signers[0].certificates[0], *identity().certificate());
}
#[test]
fn rotation_proofs_are_never_ignored() {
let proof = Attribute { id: PROOF_OF_ROTATION, value: Blob(b"not a lineage".to_vec()) };
let block = v3_block(SdkRange { min: V3_MIN_SDK, max: MAX_SDK }, vec![proof]);
assert!(matches!(verify_v3(&block), Err(VerifyError::KeyRotationUnsupported)));
}
#[test]
fn empty_sdk_ranges_are_rejected() {
let block = v3_block(SdkRange { min: 40, max: 28 }, Vec::new());
assert!(matches!(verify_v3(&block), Err(VerifyError::InvalidSdkRange { min: 40, max: 28, .. })));
}
}