use crate::crypto::algorithm::{
DigestAlgorithm, OID_DSA_WITH_SHA1, OID_DSA_WITH_SHA256, OID_ECDSA_WITH_SHA1, OID_ED25519,
OID_MD5_WITH_RSA, OID_RSASSA_PSS, OID_SHA1_WITH_RSA, OID_SHA224_WITH_RSA,
};
use crate::error::TrustError;
const OID_MGF1: const_oid::ObjectIdentifier =
const_oid::ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.8");
const OID_EC_PUBLIC_KEY: const_oid::ObjectIdentifier =
const_oid::ObjectIdentifier::new_unwrap("1.2.840.10045.2.1");
const OID_CURVE_P256: const_oid::ObjectIdentifier =
const_oid::ObjectIdentifier::new_unwrap("1.2.840.10045.3.1.7");
const OID_CURVE_P384: const_oid::ObjectIdentifier =
const_oid::ObjectIdentifier::new_unwrap("1.3.132.0.34");
const OID_CURVE_P521: const_oid::ObjectIdentifier =
const_oid::ObjectIdentifier::new_unwrap("1.3.132.0.35");
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum EcCurve {
P256,
P384,
P521,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum EcdsaHash {
Sha1,
Sha256,
Sha384,
Sha512,
}
fn ec_named_curve(spki: &spki::SubjectPublicKeyInfoRef<'_>) -> Result<EcCurve, TrustError> {
if spki.algorithm.oid != OID_EC_PUBLIC_KEY {
return Err(TrustError::SignatureVerification(format!(
"ECDSA signature but SPKI algorithm is not id-ecPublicKey (got {})",
spki.algorithm.oid
)));
}
let params = spki.algorithm.parameters.ok_or_else(|| {
TrustError::SignatureVerification("EC SPKI is missing its namedCurve parameter".into())
})?;
let curve_oid: const_oid::ObjectIdentifier = params.decode_as().map_err(|e| {
TrustError::SignatureVerification(format!("EC namedCurve parameter is not an OID: {e}"))
})?;
if curve_oid == OID_CURVE_P256 {
Ok(EcCurve::P256)
} else if curve_oid == OID_CURVE_P384 {
Ok(EcCurve::P384)
} else if curve_oid == OID_CURVE_P521 {
Ok(EcCurve::P521)
} else {
Err(TrustError::SignatureVerification(format!(
"unsupported EC named curve OID: {curve_oid}"
)))
}
}
fn verify_ecdsa_bound(
tbs: &[u8],
sig: &[u8],
spki_der: &[u8],
hash: EcdsaHash,
) -> Result<(), TrustError> {
use der::Decode;
use spki::SubjectPublicKeyInfoRef;
let spki = SubjectPublicKeyInfoRef::from_der(spki_der)
.map_err(|e| TrustError::SignatureVerification(format!("SPKI decode failed: {e}")))?;
let curve = ec_named_curve(&spki)?;
let (curve, hash) = match (curve, hash) {
(EcCurve::P256, EcdsaHash::Sha256) => {
(kryptering::EcCurve::P256, kryptering::HashAlgorithm::Sha256)
}
(EcCurve::P384, EcdsaHash::Sha384) => {
(kryptering::EcCurve::P384, kryptering::HashAlgorithm::Sha384)
}
(EcCurve::P521, EcdsaHash::Sha512) => {
(kryptering::EcCurve::P521, kryptering::HashAlgorithm::Sha512)
}
(EcCurve::P521, EcdsaHash::Sha256) => {
(kryptering::EcCurve::P521, kryptering::HashAlgorithm::Sha256)
}
(EcCurve::P521, EcdsaHash::Sha384) => {
(kryptering::EcCurve::P521, kryptering::HashAlgorithm::Sha384)
}
(EcCurve::P256, EcdsaHash::Sha1) => {
(kryptering::EcCurve::P256, kryptering::HashAlgorithm::Sha1)
}
(EcCurve::P384, EcdsaHash::Sha1) => {
(kryptering::EcCurve::P384, kryptering::HashAlgorithm::Sha1)
}
(curve, hash) => {
return Err(TrustError::SignatureVerification(format!(
"ECDSA curve {curve:?} is not a supported pairing with the declared {hash:?} hash"
)))
}
};
verify_with_backend(
tbs,
sig,
spki_der,
kryptering::KeyAlgorithm::Ec(curve),
kryptering::SignatureAlgorithm::Ecdsa(curve, hash),
None,
)
}
fn map_backend_error(error: kryptering::Error) -> TrustError {
match error {
kryptering::Error::UnsupportedAlgorithm { .. } => {
TrustError::UnsupportedAlgorithm(error.to_string())
}
other => TrustError::SignatureVerification(other.to_string()),
}
}
fn verify_legacy_md5_rsa(tbs: &[u8], signature: &[u8], spki_der: &[u8]) -> Result<(), TrustError> {
#[cfg(feature = "legacy-algorithms")]
return verify_with_backend(
tbs,
signature,
spki_der,
kryptering::KeyAlgorithm::Rsa,
kryptering::SignatureAlgorithm::RsaPkcs1v15(kryptering::HashAlgorithm::Md5),
None,
);
#[cfg(not(feature = "legacy-algorithms"))]
{
let _ = (tbs, signature, spki_der);
Err(TrustError::UnsupportedAlgorithm(
"MD5 with RSA requires legacy-algorithms".into(),
))
}
}
fn verify_dsa(
tbs: &[u8],
signature: &[u8],
spki_der: &[u8],
hash: kryptering::HashAlgorithm,
) -> Result<(), TrustError> {
#[cfg(feature = "legacy-algorithms")]
return verify_with_backend(
tbs,
signature,
spki_der,
kryptering::KeyAlgorithm::Dsa,
kryptering::SignatureAlgorithm::Dsa(hash),
None,
);
#[cfg(not(feature = "legacy-algorithms"))]
{
let _ = (tbs, signature, spki_der, hash);
Err(TrustError::UnsupportedAlgorithm(
"DSA verification requires legacy-algorithms".into(),
))
}
}
fn verify_with_backend(
tbs: &[u8],
signature: &[u8],
spki_der: &[u8],
key_algorithm: kryptering::KeyAlgorithm,
signature_algorithm: kryptering::SignatureAlgorithm,
rsa_pss_salt_len: Option<usize>,
) -> Result<(), TrustError> {
let key = kryptering::SoftwareKey::from_spki_der(key_algorithm, spki_der)
.map_err(map_backend_error)?;
let verifier = match (signature_algorithm, rsa_pss_salt_len) {
(kryptering::SignatureAlgorithm::RsaPss(hash), Some(salt_len)) => {
kryptering::SoftwareVerifier::new_rsa_pss_with_salt(hash, salt_len, key)
}
_ => kryptering::SoftwareVerifier::new(signature_algorithm, key),
}
.map_err(map_backend_error)?;
if verifier
.verify_der_signature(tbs, signature)
.map_err(map_backend_error)?
{
Ok(())
} else {
Err(TrustError::SignatureVerification(format!(
"{signature_algorithm:?} signature is invalid"
)))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SignaturePolicy {
allow_legacy: bool,
}
impl SignaturePolicy {
pub const fn strict() -> Self {
Self {
allow_legacy: false,
}
}
pub const fn allow_legacy() -> Self {
Self { allow_legacy: true }
}
pub const fn legacy_allowed(&self) -> bool {
self.allow_legacy
}
}
impl Default for SignaturePolicy {
fn default() -> Self {
Self::strict()
}
}
pub fn is_weak_signature_oid(oid: &const_oid::ObjectIdentifier) -> bool {
*oid == OID_MD5_WITH_RSA
|| *oid == OID_SHA1_WITH_RSA
|| *oid == OID_SHA224_WITH_RSA
|| *oid == OID_ECDSA_WITH_SHA1
|| *oid == OID_DSA_WITH_SHA1
}
pub fn verify_signature_by_oid(
tbs_bytes: &[u8],
signature_bytes: &[u8],
spki_der: &[u8],
sig_alg_oid: &const_oid::ObjectIdentifier,
) -> Result<(), TrustError> {
verify_signature_by_oid_with_policy(
tbs_bytes,
signature_bytes,
spki_der,
sig_alg_oid,
&SignaturePolicy::default(),
)
}
pub fn verify_signature_by_oid_with_policy(
tbs_bytes: &[u8],
signature_bytes: &[u8],
spki_der: &[u8],
sig_alg_oid: &const_oid::ObjectIdentifier,
policy: &SignaturePolicy,
) -> Result<(), TrustError> {
use const_oid::db;
if !policy.allow_legacy && is_weak_signature_oid(sig_alg_oid) {
return Err(TrustError::WeakAlgorithm(format!(
"signature algorithm OID {sig_alg_oid} relies on a weak digest \
(MD5/SHA-1/SHA-224); pass SignaturePolicy::allow_legacy() to accept it"
)));
}
if *sig_alg_oid == OID_MD5_WITH_RSA {
verify_legacy_md5_rsa(tbs_bytes, signature_bytes, spki_der)
} else if *sig_alg_oid == OID_SHA1_WITH_RSA {
verify_with_backend(
tbs_bytes,
signature_bytes,
spki_der,
kryptering::KeyAlgorithm::Rsa,
kryptering::SignatureAlgorithm::RsaPkcs1v15(kryptering::HashAlgorithm::Sha1),
None,
)
} else if *sig_alg_oid == OID_SHA224_WITH_RSA {
verify_with_backend(
tbs_bytes,
signature_bytes,
spki_der,
kryptering::KeyAlgorithm::Rsa,
kryptering::SignatureAlgorithm::RsaPkcs1v15(kryptering::HashAlgorithm::Sha224),
None,
)
}
else if *sig_alg_oid == db::rfc5912::SHA_256_WITH_RSA_ENCRYPTION {
verify_with_backend(
tbs_bytes,
signature_bytes,
spki_der,
kryptering::KeyAlgorithm::Rsa,
kryptering::SignatureAlgorithm::RsaPkcs1v15(kryptering::HashAlgorithm::Sha256),
None,
)
} else if *sig_alg_oid == db::rfc5912::SHA_384_WITH_RSA_ENCRYPTION {
verify_with_backend(
tbs_bytes,
signature_bytes,
spki_der,
kryptering::KeyAlgorithm::Rsa,
kryptering::SignatureAlgorithm::RsaPkcs1v15(kryptering::HashAlgorithm::Sha384),
None,
)
} else if *sig_alg_oid == db::rfc5912::SHA_512_WITH_RSA_ENCRYPTION {
verify_with_backend(
tbs_bytes,
signature_bytes,
spki_der,
kryptering::KeyAlgorithm::Rsa,
kryptering::SignatureAlgorithm::RsaPkcs1v15(kryptering::HashAlgorithm::Sha512),
None,
)
} else if *sig_alg_oid == OID_RSASSA_PSS {
[
kryptering::HashAlgorithm::Sha256,
kryptering::HashAlgorithm::Sha384,
kryptering::HashAlgorithm::Sha512,
]
.into_iter()
.find_map(|hash| {
verify_with_backend(
tbs_bytes,
signature_bytes,
spki_der,
kryptering::KeyAlgorithm::Rsa,
kryptering::SignatureAlgorithm::RsaPss(hash),
None,
)
.ok()
})
.ok_or_else(|| {
TrustError::SignatureVerification(
"RSA-PSS signature is invalid for every supported digest".into(),
)
})
}
else if *sig_alg_oid == OID_ECDSA_WITH_SHA1 {
verify_ecdsa_bound(tbs_bytes, signature_bytes, spki_der, EcdsaHash::Sha1)
}
else if *sig_alg_oid == OID_DSA_WITH_SHA1 {
verify_dsa(
tbs_bytes,
signature_bytes,
spki_der,
kryptering::HashAlgorithm::Sha1,
)
}
else if *sig_alg_oid == OID_DSA_WITH_SHA256 {
verify_dsa(
tbs_bytes,
signature_bytes,
spki_der,
kryptering::HashAlgorithm::Sha256,
)
}
else if *sig_alg_oid == db::rfc5912::ECDSA_WITH_SHA_256 {
verify_ecdsa_bound(tbs_bytes, signature_bytes, spki_der, EcdsaHash::Sha256)
} else if *sig_alg_oid == db::rfc5912::ECDSA_WITH_SHA_384 {
verify_ecdsa_bound(tbs_bytes, signature_bytes, spki_der, EcdsaHash::Sha384)
} else if *sig_alg_oid == db::rfc5912::ECDSA_WITH_SHA_512 {
verify_ecdsa_bound(tbs_bytes, signature_bytes, spki_der, EcdsaHash::Sha512)
} else if *sig_alg_oid == OID_ED25519 {
verify_with_backend(
tbs_bytes,
signature_bytes,
spki_der,
kryptering::KeyAlgorithm::Ed25519,
kryptering::SignatureAlgorithm::Ed25519,
None,
)
} else {
Err(TrustError::UnsupportedAlgorithm(format!(
"signature algorithm OID: {sig_alg_oid}"
)))
}
}
pub fn verify_signature_by_algid(
tbs_bytes: &[u8],
signature_bytes: &[u8],
spki_der: &[u8],
sig_alg: &spki::AlgorithmIdentifierOwned,
) -> Result<(), TrustError> {
verify_signature_by_algid_with_policy(
tbs_bytes,
signature_bytes,
spki_der,
sig_alg,
&SignaturePolicy::default(),
)
}
pub fn verify_signature_by_algid_with_policy(
tbs_bytes: &[u8],
signature_bytes: &[u8],
spki_der: &[u8],
sig_alg: &spki::AlgorithmIdentifierOwned,
policy: &SignaturePolicy,
) -> Result<(), TrustError> {
if sig_alg.oid == OID_RSASSA_PSS {
verify_rsa_pss_signature_strict(
tbs_bytes,
signature_bytes,
spki_der,
sig_alg.parameters.as_ref(),
)
.map(|_| ())
} else {
verify_signature_by_oid_with_policy(
tbs_bytes,
signature_bytes,
spki_der,
&sig_alg.oid,
policy,
)
}
}
pub fn verify_rsa_pss_signature_strict(
tbs: &[u8],
sig: &[u8],
spki_der: &[u8],
parameters: Option<&der::Any>,
) -> Result<DigestAlgorithm, TrustError> {
use der::{Decode, Encode};
let params_any = parameters.ok_or_else(|| {
TrustError::UnsupportedAlgorithm(
"RSASSA-PSS signatureAlgorithm is missing its required parameters".into(),
)
})?;
let params_der = params_any.to_der().map_err(|e| {
TrustError::SignatureVerification(format!("failed to re-encode RSASSA-PSS parameters: {e}"))
})?;
let params = RsaPssParamsRef::from_der(¶ms_der).map_err(|e| {
TrustError::SignatureVerification(format!("failed to decode RSASSA-PSS parameters: {e}"))
})?;
let hash = DigestAlgorithm::from_oid(¶ms.hash.oid).ok_or_else(|| {
TrustError::UnsupportedAlgorithm(format!(
"unsupported RSASSA-PSS hashAlgorithm OID: {}",
params.hash.oid
))
})?;
if params.mask_gen.oid != OID_MGF1 {
return Err(TrustError::UnsupportedAlgorithm(format!(
"unsupported RSASSA-PSS maskGenAlgorithm OID: {}",
params.mask_gen.oid
)));
}
let mgf1_hash_oid = params.mask_gen.parameters.map(|h| h.oid);
if mgf1_hash_oid != Some(params.hash.oid) {
return Err(TrustError::UnsupportedAlgorithm(format!(
"RSASSA-PSS MGF1 hash ({}) differs from the message hash ({}); not supported",
mgf1_hash_oid
.map(|o| o.to_string())
.unwrap_or_else(|| "absent".into()),
params.hash.oid,
)));
}
if params.trailer_field != 1 {
return Err(TrustError::UnsupportedAlgorithm(format!(
"unsupported RSASSA-PSS trailerField: {}",
params.trailer_field
)));
}
let salt_len = params.salt_len as usize;
let backend_hash = match hash {
DigestAlgorithm::Sha256 => kryptering::HashAlgorithm::Sha256,
DigestAlgorithm::Sha384 => kryptering::HashAlgorithm::Sha384,
DigestAlgorithm::Sha512 => kryptering::HashAlgorithm::Sha512,
other => {
return Err(TrustError::UnsupportedAlgorithm(format!(
"RSASSA-PSS with digest {other:?}"
)))
}
};
verify_with_backend(
tbs,
sig,
spki_der,
kryptering::KeyAlgorithm::Rsa,
kryptering::SignatureAlgorithm::RsaPss(backend_hash),
Some(salt_len),
)?;
Ok(hash)
}
struct RsaPssParamsRef<'a> {
hash: spki::AlgorithmIdentifierRef<'a>,
mask_gen: spki::AlgorithmIdentifier<spki::AlgorithmIdentifierRef<'a>>,
salt_len: u64,
trailer_field: u64,
}
impl<'a> der::DecodeValue<'a> for RsaPssParamsRef<'a> {
fn decode_value<R: der::Reader<'a>>(reader: &mut R, header: der::Header) -> der::Result<Self> {
use der::{Reader, TagMode, TagNumber};
const SHA1_OID: const_oid::ObjectIdentifier =
const_oid::ObjectIdentifier::new_unwrap("1.3.14.3.2.26");
let default_hash = spki::AlgorithmIdentifierRef {
oid: SHA1_OID,
parameters: Some(der::AnyRef::NULL),
};
let default_mgf = spki::AlgorithmIdentifier {
oid: OID_MGF1,
parameters: Some(default_hash),
};
reader.read_nested(header.length, |reader| {
Ok(Self {
hash: reader
.context_specific(TagNumber::N0, TagMode::Explicit)?
.unwrap_or(default_hash),
mask_gen: reader
.context_specific(TagNumber::N1, TagMode::Explicit)?
.unwrap_or(default_mgf),
salt_len: reader
.context_specific(TagNumber::N2, TagMode::Explicit)?
.unwrap_or(20),
trailer_field: reader
.context_specific(TagNumber::N3, TagMode::Explicit)?
.unwrap_or(1),
})
})
}
}
impl der::FixedTag for RsaPssParamsRef<'_> {
const TAG: der::Tag = der::Tag::Sequence;
}
#[cfg(test)]
fn decode_spki(spki_der: &[u8]) -> Result<spki::SubjectPublicKeyInfoRef<'_>, TrustError> {
use der::Decode;
spki::SubjectPublicKeyInfoRef::from_der(spki_der)
.map_err(|e| TrustError::SignatureVerification(format!("SPKI decode failed: {e}")))
}
pub fn verify_certificate_signature(
cert: &x509_cert::Certificate,
issuer: &x509_cert::Certificate,
) -> Result<(), TrustError> {
verify_certificate_signature_with_policy(cert, issuer, &SignaturePolicy::default())
}
pub fn verify_certificate_signature_with_policy(
cert: &x509_cert::Certificate,
issuer: &x509_cert::Certificate,
policy: &SignaturePolicy,
) -> Result<(), TrustError> {
use der::Encode;
if cert.signature_algorithm != cert.tbs_certificate.signature {
return Err(TrustError::SignatureVerification(format!(
"certificate outer signatureAlgorithm (oid={}, params={:?}) does not match \
the signed tbsCertificate.signature (oid={}, params={:?})",
cert.signature_algorithm.oid,
cert.signature_algorithm.parameters,
cert.tbs_certificate.signature.oid,
cert.tbs_certificate.signature.parameters
)));
}
let issuer_spki = &issuer.tbs_certificate.subject_public_key_info;
let tbs_bytes = cert
.tbs_certificate
.to_der()
.map_err(|e| TrustError::SignatureVerification(format!("TBS encoding failed: {e}")))?;
let signature_bytes = cert.signature.raw_bytes();
let spki_der = issuer_spki
.to_der()
.map_err(|e| TrustError::SignatureVerification(format!("SPKI encoding failed: {e}")))?;
verify_signature_by_algid_with_policy(
&tbs_bytes,
signature_bytes,
&spki_der,
&cert.signature_algorithm,
policy,
)
}
#[cfg(test)]
mod tests {
use super::*;
use der::Decode;
use x509_cert::Certificate;
fn load_test_cert(pem_str: &str) -> Certificate {
let (_, der) = pem_rfc7468::decode_vec(pem_str.as_bytes()).unwrap();
Certificate::from_der(&der).unwrap()
}
#[test]
fn test_verify_certificate_signature_ca_self_signed() {
let ca_pem = include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/fixtures/ca_cert.pem"
));
let ca = load_test_cert(ca_pem);
let result = verify_certificate_signature(&ca, &ca);
assert!(
result.is_ok(),
"CA self-signature should verify: {result:?}"
);
}
#[test]
fn test_verify_dsa_sha1_self_signed_under_legacy() {
let dsa_pem = include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/fixtures/dsa_ca_cert.pem"
));
let dsa = load_test_cert(dsa_pem);
let ok =
verify_certificate_signature_with_policy(&dsa, &dsa, &SignaturePolicy::allow_legacy());
#[cfg(not(feature = "legacy-algorithms"))]
assert!(
matches!(ok, Err(TrustError::UnsupportedAlgorithm(ref message)) if message.contains("legacy-algorithms")),
"without legacy-algorithms, DSA must fail closed: {ok:?}"
);
#[cfg(all(feature = "legacy-algorithms", feature = "aws-lc"))]
assert!(
matches!(ok, Err(TrustError::UnsupportedAlgorithm(ref message)) if message.contains("KeyImport(Dsa)")),
"AWS-LC must report DSA as deterministically unsupported: {ok:?}"
);
#[cfg(all(feature = "legacy-algorithms", not(feature = "aws-lc")))]
assert!(
ok.is_ok(),
"DSA self-signature should verify under legacy: {ok:?}"
);
let strict =
verify_certificate_signature_with_policy(&dsa, &dsa, &SignaturePolicy::strict());
assert!(
matches!(strict, Err(TrustError::WeakAlgorithm(_))),
"dsaWithSHA1 must be rejected as weak under strict policy: {strict:?}"
);
}
#[test]
fn test_dsa_oid_dispatches_not_unsupported() {
let result = verify_signature_by_oid_with_policy(
b"tbs",
b"sig",
b"bad_spki",
&OID_DSA_WITH_SHA1,
&SignaturePolicy::allow_legacy(),
);
let err = result.unwrap_err();
#[cfg(not(feature = "legacy-algorithms"))]
assert!(
matches!(err, TrustError::UnsupportedAlgorithm(ref message) if message.contains("legacy-algorithms")),
"without legacy-algorithms, DSA-SHA1 must fail closed in the DSA branch: {err:?}"
);
#[cfg(all(feature = "legacy-algorithms", feature = "aws-lc"))]
assert!(
matches!(err, TrustError::UnsupportedAlgorithm(ref message) if message.contains("KeyImport(Dsa)")),
"AWS-LC must report DSA-SHA1 as unsupported: {err:?}"
);
#[cfg(all(feature = "legacy-algorithms", not(feature = "aws-lc")))]
assert!(
!matches!(err, TrustError::UnsupportedAlgorithm(_)),
"DSA-SHA1 should be dispatched to the DSA branch, not UnsupportedAlgorithm: {err:?}"
);
}
#[test]
fn test_dsa_sha256_oid_dispatches_not_unsupported() {
let result = verify_signature_by_oid_with_policy(
b"tbs",
b"sig",
b"bad_spki",
&OID_DSA_WITH_SHA256,
&SignaturePolicy::strict(),
);
let err = result.unwrap_err();
#[cfg(not(feature = "legacy-algorithms"))]
assert!(
matches!(err, TrustError::UnsupportedAlgorithm(ref message) if message.contains("legacy-algorithms")),
"without legacy-algorithms, DSA-SHA256 must fail closed in the DSA branch: {err:?}"
);
#[cfg(all(feature = "legacy-algorithms", feature = "aws-lc"))]
assert!(
matches!(err, TrustError::UnsupportedAlgorithm(ref message) if message.contains("KeyImport(Dsa)")),
"AWS-LC must report DSA-SHA256 as unsupported: {err:?}"
);
#[cfg(all(feature = "legacy-algorithms", not(feature = "aws-lc")))]
assert!(
!matches!(err, TrustError::UnsupportedAlgorithm(_)),
"DSA-SHA256 should be dispatched to the DSA branch, not UnsupportedAlgorithm: {err:?}"
);
}
#[test]
fn test_verify_dsa_sha256_self_signed_under_strict() {
let dsa_pem = include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/fixtures/dsa_sha256_ca_cert.pem"
));
let dsa = load_test_cert(dsa_pem);
let ok = verify_certificate_signature(&dsa, &dsa);
#[cfg(not(feature = "legacy-algorithms"))]
assert!(
matches!(ok, Err(TrustError::UnsupportedAlgorithm(ref message)) if message.contains("legacy-algorithms")),
"without legacy-algorithms, DSA-SHA256 must fail closed: {ok:?}"
);
#[cfg(all(feature = "legacy-algorithms", feature = "aws-lc"))]
assert!(
matches!(ok, Err(TrustError::UnsupportedAlgorithm(ref message)) if message.contains("KeyImport(Dsa)")),
"AWS-LC must report DSA-SHA256 as deterministically unsupported: {ok:?}"
);
#[cfg(all(feature = "legacy-algorithms", not(feature = "aws-lc")))]
assert!(
ok.is_ok(),
"DSA-SHA256 self-signature should verify under strict: {ok:?}"
);
let other_pem = include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/fixtures/dsa_ca_cert.pem"
));
let other = load_test_cert(other_pem);
let bad = verify_certificate_signature(&dsa, &other);
#[cfg(not(feature = "legacy-algorithms"))]
assert!(
matches!(bad, Err(TrustError::UnsupportedAlgorithm(ref message)) if message.contains("legacy-algorithms")),
"without legacy-algorithms, DSA must fail closed before key comparison: {bad:?}"
);
#[cfg(all(feature = "legacy-algorithms", feature = "aws-lc"))]
assert!(
matches!(bad, Err(TrustError::UnsupportedAlgorithm(ref message)) if message.contains("KeyImport(Dsa)")),
"AWS-LC must reject DSA before key comparison: {bad:?}"
);
#[cfg(all(feature = "legacy-algorithms", not(feature = "aws-lc")))]
assert!(
matches!(bad, Err(TrustError::SignatureVerification(_))),
"DSA-SHA256 cert must fail against the wrong issuer key: {bad:?}"
);
}
#[test]
fn test_verify_certificate_signature_chain() {
let ca_pem = include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/fixtures/ca_cert.pem"
));
let intermediate_pem = include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/fixtures/intermediate_ca_cert.pem"
));
let signer_pem = include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/fixtures/signer_cert.pem"
));
let ca = load_test_cert(ca_pem);
let intermediate = load_test_cert(intermediate_pem);
let signer = load_test_cert(signer_pem);
let result = verify_certificate_signature(&signer, &intermediate);
assert!(
result.is_ok(),
"signer cert should verify against intermediate: {result:?}"
);
let result = verify_certificate_signature(&intermediate, &ca);
assert!(
result.is_ok(),
"intermediate cert should verify against CA: {result:?}"
);
}
#[test]
fn test_verify_certificate_signature_wrong_issuer() {
let signer_pem = include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/fixtures/signer_cert.pem"
));
let signer = load_test_cert(signer_pem);
let result = verify_certificate_signature(&signer, &signer);
assert!(result.is_err(), "wrong issuer should fail verification");
}
#[test]
fn test_unsupported_algorithm_oid() {
let fake_oid = const_oid::ObjectIdentifier::new_unwrap("1.2.3.4.5.6.7.8.9");
let result = verify_signature_by_oid(b"tbs", b"sig", b"spki", &fake_oid);
assert!(result.is_err());
let err_msg = format!("{}", result.unwrap_err());
assert!(
err_msg.contains("unsupported"),
"error should mention unsupported: {err_msg}"
);
}
#[test]
fn test_rsassa_pss_oid_dispatches() {
let pss_oid = OID_RSASSA_PSS;
let result = verify_signature_by_oid(b"tbs", b"sig", b"bad_spki", &pss_oid);
assert!(result.is_err());
let err_msg = format!("{}", result.unwrap_err());
assert!(
!err_msg.contains("unsupported"),
"RSA-PSS should be dispatched, not unsupported: {err_msg}"
);
}
fn pss_algid<D>(salt_len: u8) -> spki::AlgorithmIdentifierOwned
where
D: const_oid::AssociatedOid,
{
use der::Encode;
use rsa::pkcs1::RsaPssParams;
let params = RsaPssParams::new::<D>(salt_len);
let params_der = params.to_der().unwrap();
spki::AlgorithmIdentifierOwned {
oid: OID_RSASSA_PSS,
parameters: Some(der::Any::from_der(¶ms_der).unwrap()),
}
}
#[test]
fn test_pss_algid_uses_declared_salt_length() {
use rsa::pkcs8::EncodePublicKey;
use rsa::pss::SigningKey;
use rsa::signature::{RandomizedSigner, SignatureEncoding};
use sha2::Sha256;
let key = rsa::RsaPrivateKey::new(&mut rand::thread_rng(), 2048).unwrap();
let spki_der = rsa::RsaPublicKey::from(&key)
.to_public_key_der()
.unwrap()
.as_bytes()
.to_vec();
let msg = b"tbs bytes to be signed with PSS";
let signing = SigningKey::<Sha256>::new_with_salt_len(key, 48);
let sig = signing.sign_with_rng(&mut rand::thread_rng(), msg).to_vec();
let matching = verify_signature_by_algid(msg, &sig, &spki_der, &pss_algid::<Sha256>(48));
#[cfg(feature = "aws-lc")]
assert!(
matches!(matching, Err(TrustError::UnsupportedAlgorithm(ref message)) if message.contains("salt length 48")),
"AWS-LC must report non-digest-length PSS salt as unsupported: {matching:?}"
);
#[cfg(not(feature = "aws-lc"))]
matching.expect("PSS with declared salt 48 must verify");
assert!(
verify_signature_by_algid(msg, &sig, &spki_der, &pss_algid::<Sha256>(32)).is_err(),
"PSS with mismatched declared salt length must fail"
);
}
#[test]
fn test_pss_strict_requires_parameters() {
let bare = spki::AlgorithmIdentifierOwned {
oid: OID_RSASSA_PSS,
parameters: None,
};
let err = verify_signature_by_algid(b"tbs", b"sig", b"spki", &bare).unwrap_err();
assert!(
matches!(err, TrustError::UnsupportedAlgorithm(_)),
"PSS without parameters must be rejected, got {err:?}"
);
}
#[test]
fn test_weak_signature_oids_rejected_by_default() {
for oid in [
OID_MD5_WITH_RSA,
OID_SHA1_WITH_RSA,
OID_SHA224_WITH_RSA,
OID_ECDSA_WITH_SHA1,
] {
let err = verify_signature_by_oid(b"tbs", b"sig", b"spki", &oid).unwrap_err();
assert!(
matches!(err, TrustError::WeakAlgorithm(_)),
"{oid} should be rejected as weak, got {err:?}"
);
}
}
#[test]
fn test_weak_signature_oids_accepted_with_legacy_policy() {
let legacy = SignaturePolicy::allow_legacy();
for oid in [OID_MD5_WITH_RSA, OID_SHA1_WITH_RSA, OID_SHA224_WITH_RSA] {
let err =
verify_signature_by_oid_with_policy(b"tbs", b"sig", b"bad_spki", &oid, &legacy)
.unwrap_err();
assert!(
!matches!(err, TrustError::WeakAlgorithm(_)),
"{oid} should pass the gate under allow_legacy, got {err:?}"
);
}
}
#[test]
fn test_unknown_oid_is_unsupported_not_weak() {
let fake = const_oid::ObjectIdentifier::new_unwrap("1.2.3.4.5.6.7.8.9");
let err = verify_signature_by_oid(b"tbs", b"sig", b"spki", &fake).unwrap_err();
assert!(
matches!(err, TrustError::UnsupportedAlgorithm(_)),
"unknown OID should be unsupported, got {err:?}"
);
}
#[test]
fn test_signature_policy_default_is_strict() {
assert_eq!(SignaturePolicy::default(), SignaturePolicy::strict());
assert!(!SignaturePolicy::default().legacy_allowed());
assert!(SignaturePolicy::allow_legacy().legacy_allowed());
}
#[test]
fn test_ed25519_oid_dispatches() {
let ed_oid = OID_ED25519;
let result = verify_signature_by_oid(b"tbs", b"sig", b"bad_spki", &ed_oid);
assert!(result.is_err());
let err_msg = format!("{}", result.unwrap_err());
assert!(
!err_msg.contains("unsupported"),
"Ed25519 should be dispatched, not unsupported: {err_msg}"
);
}
#[test]
fn test_outer_inner_signature_algorithm_mismatch_rejected() {
let ca_pem = include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/fixtures/ca_cert.pem"
));
let ca = load_test_cert(ca_pem);
verify_certificate_signature(&ca, &ca).expect("untampered CA must verify");
let mut tampered = ca.clone();
tampered.signature_algorithm = spki::AlgorithmIdentifierOwned {
oid: const_oid::db::rfc5912::SHA_384_WITH_RSA_ENCRYPTION,
parameters: None,
};
let err = verify_certificate_signature(&tampered, &ca).unwrap_err();
assert!(
matches!(err, TrustError::SignatureVerification(ref m) if m.contains("does not match")),
"outer/inner signatureAlgorithm mismatch must be rejected, got {err:?}"
);
}
#[test]
fn test_ecdsa_curve_is_bound_to_declared_hash() {
use const_oid::db;
use p256::ecdsa::{signature::Signer, Signature, SigningKey};
use rsa::pkcs8::EncodePublicKey;
let sk = SigningKey::random(&mut rand::thread_rng());
let spki_der = sk
.verifying_key()
.to_public_key_der()
.unwrap()
.as_bytes()
.to_vec();
let spki = decode_spki(&spki_der).unwrap();
assert_eq!(ec_named_curve(&spki).unwrap(), EcCurve::P256);
let msg = b"message bound to a P-256 ECDSA signature";
let sig: Signature = sk.sign(msg);
let sig_der = sig.to_der().as_bytes().to_vec();
verify_signature_by_oid(msg, &sig_der, &spki_der, &db::rfc5912::ECDSA_WITH_SHA_256)
.expect("P-256 + ecdsa-with-SHA256 must verify");
let err =
verify_signature_by_oid(msg, &sig_der, &spki_der, &db::rfc5912::ECDSA_WITH_SHA_512)
.unwrap_err();
assert!(
matches!(err, TrustError::SignatureVerification(_)),
"P-256 key under a SHA-512 ECDSA OID must be rejected, got {err:?}"
);
}
#[test]
fn test_ec_named_curve_rejects_non_ec_key() {
let ca_pem = include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/fixtures/ca_cert.pem"
));
let ca = load_test_cert(ca_pem);
let spki_der = der::Encode::to_der(&ca.tbs_certificate.subject_public_key_info).unwrap();
let spki = decode_spki(&spki_der).unwrap();
let err = ec_named_curve(&spki).unwrap_err();
assert!(
matches!(err, TrustError::SignatureVerification(ref m) if m.contains("id-ecPublicKey")),
"RSA SPKI must be rejected by ec_named_curve, got {err:?}"
);
}
}