use std::{collections::HashMap, fmt, time::SystemTime};
use crypto_bigint::BoxedUint;
use dsa::pkcs8::EncodePublicKey as DsaEncodePublicKey;
use hmac::{KeyInit, Mac};
use rsa::pkcs8::DecodePublicKey as _;
use x509_parser::{
prelude::{FromDer, X509Certificate},
public_key::PublicKey,
x509::SubjectPublicKeyInfo,
};
use zeroize::Zeroizing;
use super::signature::{
decode_dsa_verifying_key, signature_value_matches_spki,
signature_value_matches_spki_with_encoding, validate_dsa_signature_spki_with_minimum,
validate_ec_public_key_encoding, validate_rsa_signature_spki_with_minimum,
verify_dsa_signature_spki_primitive, verify_dsa_signature_spki_with_minimum,
verify_rsa_signature_spki_primitive, verify_rsa_signature_spki_with_minimum,
};
use super::{
DsigError, KeyInfo, KeyInfoSource, KeyResolver, KeyValueInfo, SignatureAlgorithm, VerifyingKey,
X509ChainOptions, X509DataInfo,
parse::{
EC_P256_OID, EC_P384_OID, EC_P521_OID, ParseError, X509ChainBuildError,
build_x509_certificate_paths_to_selector_targets,
build_x509_certificate_paths_to_trusted_prefix, distinguished_names_equal,
parse_x509_certificate, x509_certificate_matches_any_selector,
x509_data_has_lookup_identifiers, x509_selector_categories_match_chain,
},
verify_ecdsa_signature_spki, verify_ecdsa_signature_spki_with_encoding,
x509::verify_x509_certificate_chain_with_provider_and_crls,
};
use super::{ResolvedVerificationKey, TrustedPublicKey, X509TrustEvidence};
#[derive(Clone)]
pub struct HmacVerificationKey {
secret: Zeroizing<Vec<u8>>,
}
impl fmt::Debug for HmacVerificationKey {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("HmacVerificationKey")
.finish_non_exhaustive()
}
}
impl HmacVerificationKey {
pub fn new(secret: impl Into<Vec<u8>>) -> Result<Self, KeyResolutionError> {
let secret = secret.into();
if secret.is_empty() {
return Err(KeyResolutionError::InvalidPublicKey);
}
Ok(Self {
secret: Zeroizing::new(secret),
})
}
fn validate_output(
&self,
policy: crate::policy::HmacPolicy,
algorithm: SignatureAlgorithm,
signature_value: &[u8],
) -> Result<(), DsigError> {
if algorithm.hmac_output_bits().is_none() {
return Err(KeyResolutionError::AlgorithmMismatch.into());
}
policy.validate_key_bytes(self.secret.len())?;
policy.validate_output(algorithm, signature_value.len().saturating_mul(8))?;
Ok(())
}
fn verify_with_hmac_policy(
&self,
policy: crate::policy::HmacPolicy,
algorithm: SignatureAlgorithm,
signed_data: &[u8],
signature_value: &[u8],
) -> Result<bool, DsigError> {
self.validate_output(policy, algorithm, signature_value)?;
macro_rules! verify_hmac {
($digest:ty) => {{
let mut mac = hmac::Hmac::<$digest>::new_from_slice(&self.secret)
.map_err(|_| KeyResolutionError::InvalidPublicKey)?;
mac.update(signed_data);
let expected = mac.finalize().into_bytes();
subtle::ConstantTimeEq::ct_eq(&expected[..signature_value.len()], signature_value)
.into()
}};
}
Ok(match algorithm {
#[cfg(feature = "legacy-algorithms")]
SignatureAlgorithm::HmacMd5 => verify_hmac!(md5::Md5),
#[cfg(feature = "legacy-algorithms")]
SignatureAlgorithm::HmacRipemd160 => verify_hmac!(ripemd::Ripemd160),
SignatureAlgorithm::HmacSha1 => verify_hmac!(sha1::Sha1),
SignatureAlgorithm::HmacSha224 => verify_hmac!(sha2::Sha224),
SignatureAlgorithm::HmacSha256 => verify_hmac!(sha2::Sha256),
SignatureAlgorithm::HmacSha384 => verify_hmac!(sha2::Sha384),
SignatureAlgorithm::HmacSha512 => verify_hmac!(sha2::Sha512),
_ => return Err(KeyResolutionError::AlgorithmMismatch.into()),
})
}
}
impl VerifyingKey for HmacVerificationKey {
fn validate_policy(&self, policy: &crate::policy::VerificationPolicy) -> Result<(), DsigError> {
policy
.hmac
.validate_key_bytes(self.secret.len())
.map_err(Into::into)
}
fn validate_signature_value(
&self,
algorithm: SignatureAlgorithm,
signature_value: &[u8],
) -> Result<bool, DsigError> {
self.validate_output(
crate::policy::HmacPolicy::default(),
algorithm,
signature_value,
)?;
Ok(true)
}
fn validate_signature_value_with_policy(
&self,
policy: &crate::policy::VerificationPolicy,
algorithm: SignatureAlgorithm,
signature_value: &[u8],
) -> Result<bool, DsigError> {
self.validate_output(policy.hmac, algorithm, signature_value)?;
Ok(true)
}
fn verify(
&self,
algorithm: SignatureAlgorithm,
signed_data: &[u8],
signature_value: &[u8],
) -> Result<bool, DsigError> {
self.verify_with_hmac_policy(
crate::policy::HmacPolicy::default(),
algorithm,
signed_data,
signature_value,
)
}
fn verify_with_policy(
&self,
policy: &crate::policy::VerificationPolicy,
algorithm: SignatureAlgorithm,
signed_data: &[u8],
signature_value: &[u8],
) -> Result<bool, DsigError> {
self.verify_with_hmac_policy(policy.hmac, algorithm, signed_data, signature_value)
}
}
pub type HmacSha1VerificationKey = HmacVerificationKey;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VerificationKey {
pub algorithm: SignatureAlgorithm,
pub public_key_bytes: Vec<u8>,
pub certificate_der: Option<Vec<u8>>,
pub name: Option<String>,
}
impl VerifyingKey for VerificationKey {
fn verification_spki(&self, algorithm: SignatureAlgorithm) -> Result<Option<&[u8]>, DsigError> {
if algorithm != self.algorithm {
return Err(KeyResolutionError::AlgorithmMismatch.into());
}
Ok(Some(&self.public_key_bytes))
}
fn verify_with_context(
&self,
algorithm: SignatureAlgorithm,
context: &super::SignatureContext,
data: &[u8],
signature: &[u8],
) -> Result<bool, DsigError> {
if algorithm != self.algorithm {
return Err(KeyResolutionError::AlgorithmMismatch.into());
}
if algorithm.eddsa_signature_len().is_some()
|| matches!(algorithm, SignatureAlgorithm::PostQuantum(_))
{
return super::modern::verify_with_context(
algorithm,
&self.public_key_bytes,
context,
data,
signature,
)
.map_err(DsigError::Crypto);
}
if !context.as_bytes().is_empty() {
return Err(KeyResolutionError::AlgorithmMismatch.into());
}
self.verify(algorithm, data, signature)
}
fn validate_policy(&self, policy: &crate::policy::VerificationPolicy) -> Result<(), DsigError> {
let result = match self.algorithm {
#[cfg(feature = "legacy-algorithms")]
SignatureAlgorithm::RsaMd5 | SignatureAlgorithm::RsaRipemd160 => {
validate_rsa_signature_spki_with_minimum(
self.algorithm,
&self.public_key_bytes,
policy.key_trust.rsa_keys.minimum_modulus_bits,
)
}
#[cfg(feature = "legacy-algorithms")]
SignatureAlgorithm::HmacMd5
| SignatureAlgorithm::HmacRipemd160
| SignatureAlgorithm::EcdsaRipemd160 => Ok(()),
SignatureAlgorithm::Ed25519
| SignatureAlgorithm::Ed25519Ctx
| SignatureAlgorithm::Ed25519Ph
| SignatureAlgorithm::Ed448
| SignatureAlgorithm::Ed448Ph
| SignatureAlgorithm::PostQuantum(_) => {
super::modern::validate_public_key(self.algorithm, &self.public_key_bytes)
}
SignatureAlgorithm::DsaSha1 | SignatureAlgorithm::DsaSha256 => {
validate_dsa_signature_spki_with_minimum(
&self.public_key_bytes,
policy.key_trust.dsa_keys.minimum_modulus_bits,
)
}
SignatureAlgorithm::RsaSha1
| SignatureAlgorithm::RsaSha224
| SignatureAlgorithm::RsaSha256
| SignatureAlgorithm::RsaSha384
| SignatureAlgorithm::RsaSha512 => validate_rsa_signature_spki_with_minimum(
self.algorithm,
&self.public_key_bytes,
policy.key_trust.rsa_keys.minimum_modulus_bits,
),
SignatureAlgorithm::HmacSha1
| SignatureAlgorithm::HmacSha224
| SignatureAlgorithm::HmacSha256
| SignatureAlgorithm::HmacSha384
| SignatureAlgorithm::HmacSha512
| SignatureAlgorithm::EcdsaSha1
| SignatureAlgorithm::EcdsaSha224
| SignatureAlgorithm::EcdsaSha256
| SignatureAlgorithm::EcdsaSha384
| SignatureAlgorithm::EcdsaSha512
| SignatureAlgorithm::EcdsaSha3_224
| SignatureAlgorithm::EcdsaSha3_256
| SignatureAlgorithm::EcdsaSha3_384
| SignatureAlgorithm::EcdsaSha3_512 => Ok(()),
};
result.map_err(DsigError::Crypto)
}
fn validate_signature_value(
&self,
algorithm: SignatureAlgorithm,
signature_value: &[u8],
) -> Result<bool, DsigError> {
if algorithm != self.algorithm {
return Err(KeyResolutionError::AlgorithmMismatch.into());
}
signature_value_matches_spki(algorithm, &self.public_key_bytes, signature_value)
.map_err(DsigError::Crypto)
}
fn validate_signature_value_with_policy(
&self,
policy: &crate::policy::VerificationPolicy,
algorithm: SignatureAlgorithm,
signature_value: &[u8],
) -> Result<bool, DsigError> {
if algorithm != self.algorithm {
return Err(KeyResolutionError::AlgorithmMismatch.into());
}
signature_value_matches_spki_with_encoding(
algorithm,
&self.public_key_bytes,
signature_value,
policy.ecdsa_signature_value_encoding,
)
.map_err(DsigError::Crypto)
}
fn verify(
&self,
algorithm: SignatureAlgorithm,
signed_data: &[u8],
signature_value: &[u8],
) -> Result<bool, DsigError> {
if algorithm != self.algorithm {
return Err(KeyResolutionError::AlgorithmMismatch.into());
}
let result = match algorithm {
#[cfg(feature = "legacy-algorithms")]
SignatureAlgorithm::RsaMd5 | SignatureAlgorithm::RsaRipemd160 => {
verify_rsa_signature_spki_primitive(
algorithm,
&self.public_key_bytes,
signed_data,
signature_value,
)
}
#[cfg(feature = "legacy-algorithms")]
SignatureAlgorithm::EcdsaRipemd160 => verify_ecdsa_signature_spki(
algorithm,
&self.public_key_bytes,
signed_data,
signature_value,
),
#[cfg(feature = "legacy-algorithms")]
SignatureAlgorithm::HmacMd5 | SignatureAlgorithm::HmacRipemd160 => {
return Err(KeyResolutionError::AlgorithmMismatch.into());
}
SignatureAlgorithm::Ed25519
| SignatureAlgorithm::Ed25519Ctx
| SignatureAlgorithm::Ed25519Ph
| SignatureAlgorithm::Ed448
| SignatureAlgorithm::Ed448Ph
| SignatureAlgorithm::PostQuantum(_) => super::modern::verify(
algorithm,
&self.public_key_bytes,
signed_data,
signature_value,
),
SignatureAlgorithm::DsaSha1 | SignatureAlgorithm::DsaSha256 => {
verify_dsa_signature_spki_primitive(
algorithm,
&self.public_key_bytes,
signed_data,
signature_value,
)
}
SignatureAlgorithm::HmacSha1
| SignatureAlgorithm::HmacSha224
| SignatureAlgorithm::HmacSha256
| SignatureAlgorithm::HmacSha384
| SignatureAlgorithm::HmacSha512 => {
return Err(KeyResolutionError::AlgorithmMismatch.into());
}
SignatureAlgorithm::RsaSha1
| SignatureAlgorithm::RsaSha224
| SignatureAlgorithm::RsaSha256
| SignatureAlgorithm::RsaSha384
| SignatureAlgorithm::RsaSha512 => verify_rsa_signature_spki_primitive(
algorithm,
&self.public_key_bytes,
signed_data,
signature_value,
),
SignatureAlgorithm::EcdsaSha1
| SignatureAlgorithm::EcdsaSha224
| SignatureAlgorithm::EcdsaSha256
| SignatureAlgorithm::EcdsaSha384
| SignatureAlgorithm::EcdsaSha512
| SignatureAlgorithm::EcdsaSha3_224
| SignatureAlgorithm::EcdsaSha3_256
| SignatureAlgorithm::EcdsaSha3_384
| SignatureAlgorithm::EcdsaSha3_512 => verify_ecdsa_signature_spki(
algorithm,
&self.public_key_bytes,
signed_data,
signature_value,
),
};
result.map_err(DsigError::Crypto)
}
fn verify_with_policy(
&self,
policy: &crate::policy::VerificationPolicy,
algorithm: SignatureAlgorithm,
signed_data: &[u8],
signature_value: &[u8],
) -> Result<bool, DsigError> {
if algorithm != self.algorithm {
return Err(KeyResolutionError::AlgorithmMismatch.into());
}
if algorithm.ecdsa_digest().is_some() {
return verify_ecdsa_signature_spki_with_encoding(
algorithm,
&self.public_key_bytes,
signed_data,
signature_value,
policy.ecdsa_signature_value_encoding,
)
.map_err(DsigError::Crypto);
}
self.verify(algorithm, signed_data, signature_value)
}
}
struct PolicyBoundVerificationKey {
key: VerificationKey,
rsa_minimum_bits: usize,
dsa_minimum_bits: usize,
}
impl VerifyingKey for PolicyBoundVerificationKey {
fn verification_spki(&self, algorithm: SignatureAlgorithm) -> Result<Option<&[u8]>, DsigError> {
if algorithm.is_rsa() {
validate_rsa_signature_spki_with_minimum(
algorithm,
&self.key.public_key_bytes,
self.rsa_minimum_bits,
)
.map_err(DsigError::Crypto)?;
}
self.key.verification_spki(algorithm)
}
fn verify_with_context(
&self,
algorithm: SignatureAlgorithm,
context: &super::SignatureContext,
data: &[u8],
signature: &[u8],
) -> Result<bool, DsigError> {
if context.as_bytes().is_empty() {
return self.verify(algorithm, data, signature);
}
self.key
.verify_with_context(algorithm, context, data, signature)
}
fn validate_signature_value(
&self,
algorithm: SignatureAlgorithm,
signature_value: &[u8],
) -> Result<bool, DsigError> {
self.key
.validate_signature_value(algorithm, signature_value)
}
fn validate_signature_value_with_policy(
&self,
policy: &crate::policy::VerificationPolicy,
algorithm: SignatureAlgorithm,
signature_value: &[u8],
) -> Result<bool, DsigError> {
self.key
.validate_signature_value_with_policy(policy, algorithm, signature_value)
}
fn verify(
&self,
algorithm: SignatureAlgorithm,
signed_data: &[u8],
signature_value: &[u8],
) -> Result<bool, DsigError> {
if algorithm != self.key.algorithm {
return Err(KeyResolutionError::AlgorithmMismatch.into());
}
let result = match algorithm {
SignatureAlgorithm::DsaSha1 | SignatureAlgorithm::DsaSha256 => {
verify_dsa_signature_spki_with_minimum(
algorithm,
&self.key.public_key_bytes,
signed_data,
signature_value,
self.dsa_minimum_bits,
)
}
algorithm if algorithm.is_rsa() => verify_rsa_signature_spki_with_minimum(
algorithm,
&self.key.public_key_bytes,
signed_data,
signature_value,
self.rsa_minimum_bits,
),
_ => return self.key.verify(algorithm, signed_data, signature_value),
};
result.map_err(DsigError::Crypto)
}
fn verify_with_policy(
&self,
policy: &crate::policy::VerificationPolicy,
algorithm: SignatureAlgorithm,
signed_data: &[u8],
signature_value: &[u8],
) -> Result<bool, DsigError> {
if algorithm.ecdsa_digest().is_some() {
return self
.key
.verify_with_policy(policy, algorithm, signed_data, signature_value);
}
self.verify(algorithm, signed_data, signature_value)
}
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum KeyResolutionError {
#[error("verification key does not match the signature algorithm")]
AlgorithmMismatch,
#[error("invalid embedded certificate DER")]
InvalidCertificate,
#[error("invalid public key DER")]
InvalidPublicKey,
#[error("X.509 lookup selectors match multiple configured certificates")]
AmbiguousCertificate,
#[error("unsupported X.509 digest algorithm: {0}")]
UnsupportedDigestAlgorithm(String),
#[error("certificate chain validation failed: {0}")]
Chain(#[from] super::X509ChainError),
#[error("system time is unavailable")]
SystemTime,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct KeyResolverConfig {
pub lookup_certs: Vec<Vec<u8>>,
pub trusted_certs: Vec<Vec<u8>>,
pub crls: Vec<Vec<u8>>,
pub named_keys: HashMap<String, VerificationKey>,
}
#[derive(Debug, Clone, Default)]
pub struct DefaultKeyResolver {
config: KeyResolverConfig,
}
#[derive(Clone, Copy)]
pub(crate) enum ResolutionScope {
Document,
Trusted,
DocumentPrefix(usize),
TrustedPrefix(usize),
DocumentSuffix(usize),
TrustedSuffix(usize),
}
pub(crate) struct SourceResolution {
pub(crate) key: Option<ResolvedVerificationKey<'static>>,
pub(crate) deferred_error: Option<KeyResolutionError>,
}
impl SourceResolution {
pub(crate) fn finish(self) -> Result<Option<ResolvedVerificationKey<'static>>, DsigError> {
if let Some(error) = self.deferred_error {
return Err(error.into());
}
Ok(self.key)
}
}
#[derive(Debug)]
pub struct InspectedKeyCandidateBudget {
maximum: usize,
attempted: usize,
}
impl InspectedKeyCandidateBudget {
#[must_use]
pub fn new(policy: &crate::policy::VerificationPolicy) -> Self {
Self {
maximum: policy.resources.max_key_candidates,
attempted: 0,
}
}
pub fn charge(&mut self) -> Result<(), DsigError> {
self.charge_many(1)
}
pub(crate) fn charge_many(&mut self, count: usize) -> Result<(), DsigError> {
if self.attempted > self.maximum || count > self.maximum - self.attempted {
return Err(crate::policy::PolicyViolation::ResourceLimit {
resource: crate::policy::resource_name::KEY_CANDIDATES,
maximum: self.maximum,
actual: self.attempted.saturating_add(count),
}
.into());
}
debug_assert!(self.attempted <= self.maximum);
self.attempted += count;
Ok(())
}
}
pub(crate) fn validate_key_info_source_permissions(
key_info: &KeyInfo,
allowed: crate::policy::KeySourcePolicy,
) -> Result<(), crate::policy::PolicyViolation> {
for source in &key_info.sources {
let disabled_reason = match source {
KeyInfoSource::X509Data(_) if !allowed.x509_data => {
Some("X509Data key sources are disabled")
}
KeyInfoSource::DerEncodedKeyValue(_) if !allowed.der_encoded_key_value => {
Some("DEREncodedKeyValue key sources are disabled")
}
KeyInfoSource::KeyName(_) if !allowed.key_name => {
Some("KeyName key sources are disabled")
}
KeyInfoSource::KeyValue(_) if !allowed.key_value => {
Some("KeyValue key sources are disabled")
}
KeyInfoSource::KeyInfoReference { .. } if !allowed.key_info_reference => {
Some("KeyInfoReference key sources are disabled")
}
KeyInfoSource::X509Data(_)
| KeyInfoSource::DerEncodedKeyValue(_)
| KeyInfoSource::KeyName(_)
| KeyInfoSource::KeyValue(_)
| KeyInfoSource::RetrievalMethod { .. }
| KeyInfoSource::KeyInfoReference { .. } => None,
};
if let Some(reason) = disabled_reason {
return Err(crate::policy::PolicyViolation::KeyTrust { reason });
}
}
Ok(())
}
impl DefaultKeyResolver {
pub fn resolve_with_candidate_budget(
&self,
key_info: Option<&KeyInfo>,
algorithm: SignatureAlgorithm,
policy: &crate::policy::VerificationPolicy,
provider: &dyn crate::provider::CryptoProvider,
candidate_budget: &mut InspectedKeyCandidateBudget,
) -> Result<Option<ResolvedVerificationKey<'static>>, DsigError> {
candidate_budget.maximum = candidate_budget
.maximum
.min(policy.resources.max_key_candidates);
candidate_budget.charge_many(0)?;
self.resolve_with_trust(
key_info,
algorithm,
policy,
provider,
candidate_budget,
ResolutionScope::Document,
)
}
pub(crate) fn resolve_trusted_material_with_candidate_budget(
&self,
key_info: &KeyInfo,
algorithm: SignatureAlgorithm,
policy: &crate::policy::VerificationPolicy,
provider: &dyn crate::provider::CryptoProvider,
candidate_budget: &mut InspectedKeyCandidateBudget,
) -> Result<Option<ResolvedVerificationKey<'static>>, DsigError> {
self.resolve_with_trust(
Some(key_info),
algorithm,
policy,
provider,
candidate_budget,
ResolutionScope::Trusted,
)
}
pub(crate) fn resolve_sources_with_candidate_budget(
&self,
key_info: &KeyInfo,
algorithm: SignatureAlgorithm,
policy: &crate::policy::VerificationPolicy,
provider: &dyn crate::provider::CryptoProvider,
candidate_budget: &mut InspectedKeyCandidateBudget,
scope: ResolutionScope,
) -> Result<SourceResolution, DsigError> {
self.resolve_source_range(
Some(key_info),
algorithm,
policy,
provider,
candidate_budget,
scope,
)
}
#[must_use]
pub fn new(config: KeyResolverConfig) -> Self {
Self { config }
}
#[must_use]
pub fn config(&self) -> &KeyResolverConfig {
&self.config
}
fn check_configured_x509_material(
&self,
info: &X509DataInfo,
resources: &crate::policy::ResourcePolicy,
trust: &crate::policy::KeyTrustPolicy,
budget: &mut InspectedKeyCandidateBudget,
charge_crls: bool,
) -> Result<(), DsigError> {
if charge_crls && trust.check_crls && trust.verify_x509_chains {
budget.charge_many(self.config.crls.len())?;
}
let certificates = self
.config
.trusted_certs
.iter()
.chain(&self.config.lookup_certs);
let crls = self
.config
.crls
.iter()
.filter(|_| trust.check_crls && trust.verify_x509_chains);
let mut total = 0_usize;
for material in info
.certificates
.iter()
.chain(&info.crls)
.chain(certificates)
.chain(crls)
{
if material.len() > resources.max_external_resource_bytes {
return Err(crate::policy::PolicyViolation::ResourceLimit {
resource: crate::policy::resource_name::EXTERNAL_RESOURCE_BYTES,
maximum: resources.max_external_resource_bytes,
actual: material.len(),
}
.into());
}
debug_assert!(total <= resources.max_external_resource_total_bytes);
if material.len() > resources.max_external_resource_total_bytes - total {
return Err(crate::policy::PolicyViolation::ResourceLimit {
resource: crate::policy::resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES,
maximum: resources.max_external_resource_total_bytes,
actual: total.saturating_add(material.len()),
}
.into());
}
total += material.len();
}
Ok(())
}
fn resolve_x509(
&self,
info: &X509DataInfo,
algorithm: SignatureAlgorithm,
trust: &crate::policy::KeyTrustPolicy,
provider: &dyn crate::provider::CryptoProvider,
budget: &mut InspectedKeyCandidateBudget,
) -> Result<Option<(VerificationKey, Option<X509TrustEvidence>)>, DsigError> {
let mut evidence = None;
let effective_trust;
let trust = if trust.verify_x509_chains && trust.verification_time.is_none() {
effective_trust = {
let mut captured = trust.clone();
captured.verification_time = Some(SystemTime::now());
captured
};
&effective_trust
} else {
trust
};
let certificate_der = if let Some(&signing_index) = info.certificate_chain.first() {
if trust.verify_x509_chains {
let path =
self.prepare_embedded_x509(info, signing_index, trust, provider, budget)?;
evidence = Some(Self::x509_trust_evidence(&path, trust)?);
} else {
budget.charge_many(info.certificates.len())?;
}
info.certificates
.get(signing_index)
.ok_or(KeyResolutionError::InvalidCertificate)?
.clone()
} else {
let Some(selected) = self.resolve_configured_x509(info, trust, provider, budget)?
else {
return Ok(None);
};
if trust.verify_x509_chains {
evidence = Some(Self::x509_trust_evidence(&selected, trust)?);
}
selected
.certificate_chain
.first()
.and_then(|index| selected.certificates.get(*index))
.ok_or(KeyResolutionError::InvalidCertificate)?
.clone()
};
let (rest, certificate) = X509Certificate::from_der(&certificate_der)
.map_err(|_| KeyResolutionError::InvalidCertificate)?;
if !rest.is_empty() {
return Err(KeyResolutionError::InvalidCertificate.into());
}
let public_key_bytes = certificate.public_key().raw.to_vec();
validate_spki_algorithm(&public_key_bytes, algorithm)?;
Ok(Some((
VerificationKey {
algorithm,
public_key_bytes,
certificate_der: Some(certificate_der),
name: None,
},
evidence,
)))
}
fn x509_trust_evidence(
info: &X509DataInfo,
trust: &crate::policy::KeyTrustPolicy,
) -> Result<X509TrustEvidence, DsigError> {
let time = trust
.verification_time
.ok_or(KeyResolutionError::SystemTime)?;
X509TrustEvidence::new(
info.certificate_chain
.iter()
.map(|index| info.certificates[*index].as_slice()),
time,
trust.check_crls,
)
}
fn verify_x509_policy(
&self,
info: &X509DataInfo,
trust: &crate::policy::KeyTrustPolicy,
provider: &dyn crate::provider::CryptoProvider,
) -> Result<(), KeyResolutionError> {
let options = X509ChainOptions {
trusted_certs: &self.config.trusted_certs,
verification_time: trust.verification_time.unwrap_or_else(SystemTime::now),
max_chain_depth: trust.max_x509_chain_depth,
check_crls: trust.check_crls,
allowed_extended_key_usages: Some(&trust.allowed_extended_key_usages),
rsa_keys: trust.rsa_keys,
dsa_keys: trust.dsa_keys,
certificate_signature_algorithms: trust.certificate_signature_algorithms.as_ref(),
};
verify_x509_certificate_chain_with_provider_and_crls(
info,
&options,
provider,
&self.config.crls,
)?;
Ok(())
}
fn prepare_embedded_x509(
&self,
info: &X509DataInfo,
signing_index: usize,
trust: &crate::policy::KeyTrustPolicy,
provider: &dyn crate::provider::CryptoProvider,
budget: &mut InspectedKeyCandidateBudget,
) -> Result<X509DataInfo, DsigError> {
let signing_der = info
.certificates
.get(signing_index)
.ok_or(KeyResolutionError::InvalidCertificate)?;
let mut available = X509DataInfo {
crls: info.crls.clone(),
..X509DataInfo::default()
};
let mut trusted_prefix_len = 0;
for certificate in &self.config.trusted_certs {
budget.charge()?;
if available
.certificates
.iter()
.any(|known| known == certificate)
{
continue;
}
available.parsed_certificates.push(
parse_x509_certificate(certificate)
.map_err(|_| KeyResolutionError::InvalidCertificate)?,
);
available.certificates.push(certificate.clone());
trusted_prefix_len += 1;
}
for certificate in self.config.lookup_certs.iter().chain(&info.certificates) {
budget.charge()?;
if available
.certificates
.iter()
.any(|known| known == certificate)
{
continue;
}
available.parsed_certificates.push(
parse_x509_certificate(certificate)
.map_err(|_| KeyResolutionError::InvalidCertificate)?,
);
available.certificates.push(certificate.clone());
}
let signing_index = available
.certificates
.iter()
.position(|certificate| certificate == signing_der)
.ok_or(KeyResolutionError::InvalidCertificate)?;
self.select_valid_x509_path(
&mut available,
signing_index,
trusted_prefix_len,
trust,
provider,
None,
)?;
Ok(available)
}
fn select_valid_x509_path(
&self,
available: &mut X509DataInfo,
signing_index: usize,
trusted_prefix_len: usize,
trust: &crate::policy::KeyTrustPolicy,
provider: &dyn crate::provider::CryptoProvider,
selectors: Option<&X509DataInfo>,
) -> Result<bool, KeyResolutionError> {
let candidates = build_x509_certificate_paths_to_trusted_prefix(
available,
signing_index,
trusted_prefix_len,
trust.max_x509_chain_depth,
trust.max_x509_candidate_paths,
provider,
trust.certificate_signature_algorithms.as_ref(),
)
.map_err(|error| match error {
X509ChainBuildError::AmbiguousIssuer => KeyResolutionError::AmbiguousCertificate,
X509ChainBuildError::Policy(error) => {
KeyResolutionError::Chain(super::X509ChainError::Policy(error))
}
X509ChainBuildError::Provider(error) => {
KeyResolutionError::Chain(super::X509ChainError::Provider(error))
}
X509ChainBuildError::UnsupportedSignatureAlgorithm { oid } => {
KeyResolutionError::Chain(super::X509ChainError::UnsupportedSignatureAlgorithm {
oid,
})
}
_ => KeyResolutionError::InvalidCertificate,
})?;
let mut first_error = None;
let mut valid_path_without_selector_match = false;
for candidate in candidates {
available.certificate_chain = candidate;
match self.verify_x509_policy(available, trust, provider) {
Ok(()) => {
if match selectors {
Some(selectors) => {
selected_x509_path_matches_selectors(available, selectors, provider)?
}
None => true,
} {
return Ok(true);
}
valid_path_without_selector_match = true;
}
Err(error) => {
first_error.get_or_insert(error);
}
}
}
if valid_path_without_selector_match {
return Ok(false);
}
Err(first_error.unwrap_or(KeyResolutionError::Chain(
super::X509ChainError::UntrustedRoot,
)))
}
fn select_x509_selector_path(
&self,
available: &mut X509DataInfo,
signing_index: usize,
matching_indices: &[usize],
trust: &crate::policy::KeyTrustPolicy,
provider: &dyn crate::provider::CryptoProvider,
selectors: &X509DataInfo,
) -> Result<bool, KeyResolutionError> {
let targets = matching_indices
.iter()
.copied()
.filter(|index| *index != signing_index)
.collect::<Vec<_>>();
if targets.is_empty() {
return Ok(false);
}
let candidates = build_x509_certificate_paths_to_selector_targets(
available,
signing_index,
&targets,
trust.max_x509_chain_depth,
trust.max_x509_candidate_paths,
provider,
trust.certificate_signature_algorithms.as_ref(),
)
.map_err(|error| match error {
X509ChainBuildError::AmbiguousIssuer => KeyResolutionError::AmbiguousCertificate,
X509ChainBuildError::Policy(error) => {
KeyResolutionError::Chain(super::X509ChainError::Policy(error))
}
X509ChainBuildError::Provider(error) => {
KeyResolutionError::Chain(super::X509ChainError::Provider(error))
}
X509ChainBuildError::UnsupportedSignatureAlgorithm { oid } => {
KeyResolutionError::Chain(super::X509ChainError::UnsupportedSignatureAlgorithm {
oid,
})
}
_ => KeyResolutionError::InvalidCertificate,
})?;
for candidate in candidates {
available.certificate_chain = candidate;
if selected_x509_path_matches_selectors(available, selectors, provider)? {
return Ok(true);
}
}
Ok(false)
}
fn resolve_configured_x509(
&self,
info: &X509DataInfo,
trust: &crate::policy::KeyTrustPolicy,
provider: &dyn crate::provider::CryptoProvider,
budget: &mut InspectedKeyCandidateBudget,
) -> Result<Option<X509DataInfo>, DsigError> {
if !x509_data_has_lookup_identifiers(info) {
return Ok(None);
}
let mut available = X509DataInfo {
subject_names: info.subject_names.clone(),
issuer_serials: info.issuer_serials.clone(),
skis: info.skis.clone(),
crls: info.crls.clone(),
digests: info.digests.clone(),
..X509DataInfo::default()
};
let mut matches = Vec::new();
let mut trusted_prefix_len = 0usize;
for (trusted, certificate_der) in self
.config
.trusted_certs
.iter()
.map(|certificate| (true, certificate))
.chain(
self.config
.lookup_certs
.iter()
.map(|certificate| (false, certificate)),
)
{
budget.charge()?;
if available
.certificates
.iter()
.any(|available_der| available_der == certificate_der)
{
continue;
}
let parsed = parse_x509_certificate(certificate_der)
.map_err(|_| KeyResolutionError::InvalidCertificate)?;
let is_match =
x509_certificate_matches_any_selector(info, &parsed, certificate_der, provider)
.map_err(map_x509_selector_error)?;
if is_match {
matches.push((available.certificates.len(), parsed.clone()));
}
available.certificates.push(certificate_der.clone());
available.parsed_certificates.push(parsed);
if trusted {
trusted_prefix_len += 1;
}
}
let matched_chain = X509DataInfo {
certificates: matches
.iter()
.map(|(index, _)| available.certificates[*index].clone())
.collect(),
parsed_certificates: matches.iter().map(|(_, parsed)| parsed.clone()).collect(),
..X509DataInfo::default()
};
if !x509_selector_categories_match_chain(
&X509DataInfo {
subject_names: info.subject_names.clone(),
issuer_serials: info.issuer_serials.clone(),
skis: info.skis.clone(),
digests: info.digests.clone(),
..matched_chain
},
provider,
)
.map_err(map_x509_selector_error)?
{
return Ok(None);
}
let signing_index = match matches.as_slice() {
[] => return Ok(None),
[(index, _)] => *index,
_ => {
let leaves = matches
.iter()
.filter(|(_, candidate)| {
!distinguished_names_equal(&candidate.subject_dn, &candidate.issuer_dn)
&& !matches.iter().any(|(_, other)| {
distinguished_names_equal(&other.issuer_dn, &candidate.subject_dn)
})
})
.collect::<Vec<_>>();
match leaves.as_slice() {
[(index, _)] => *index,
_ => return Err(KeyResolutionError::AmbiguousCertificate.into()),
}
}
};
let matching_indices = matches.iter().map(|(index, _)| *index).collect::<Vec<_>>();
available.certificate_chain =
if signing_index < trusted_prefix_len || !trust.verify_x509_chains {
vec![signing_index]
} else {
if !self.select_valid_x509_path(
&mut available,
signing_index,
trusted_prefix_len,
trust,
provider,
Some(info),
)? {
return Ok(None);
}
available.certificate_chain.clone()
};
if trust.verify_x509_chains && signing_index < trusted_prefix_len {
self.verify_x509_policy(&available, trust, provider)?;
}
if !trust.verify_x509_chains || signing_index < trusted_prefix_len {
let direct_match = selected_x509_path_matches_selectors(&available, info, provider)?;
if !direct_match
&& (signing_index < trusted_prefix_len
|| !self.select_x509_selector_path(
&mut available,
signing_index,
&matching_indices,
trust,
provider,
info,
)?)
{
return Ok(None);
}
}
Ok(Some(available))
}
fn resolve_key_value(
key_value: &KeyValueInfo,
algorithm: SignatureAlgorithm,
) -> Result<Option<VerificationKey>, KeyResolutionError> {
let public_key_bytes = match key_value {
KeyValueInfo::Dsa { p, q, g, y } => {
if !matches!(
algorithm,
SignatureAlgorithm::DsaSha1 | SignatureAlgorithm::DsaSha256
) {
return Err(KeyResolutionError::AlgorithmMismatch);
}
let (Some(p), Some(q), Some(g)) = (p.as_deref(), q.as_deref(), g.as_deref()) else {
return Err(KeyResolutionError::InvalidPublicKey);
};
dsa_key_value_to_spki_der(p, q, g, y)?
}
KeyValueInfo::Rsa { modulus, exponent } => {
if !algorithm.is_rsa() {
return Err(KeyResolutionError::AlgorithmMismatch);
}
rsa_key_value_to_spki_der(modulus, exponent)?
}
KeyValueInfo::Ec {
curve_oid,
public_key,
} => {
if algorithm.ecdsa_digest().is_none() {
return Ok(None);
}
ec_key_value_to_spki_der(curve_oid, public_key)?
}
KeyValueInfo::InvalidEcKeyValue => return Err(KeyResolutionError::InvalidPublicKey),
KeyValueInfo::Unsupported { .. } => return Ok(None),
};
validate_spki_algorithm(&public_key_bytes, algorithm)?;
Ok(Some(VerificationKey {
algorithm,
public_key_bytes,
certificate_der: None,
name: None,
}))
}
fn resolve_with_trust(
&self,
key_info: Option<&KeyInfo>,
algorithm: SignatureAlgorithm,
policy: &crate::policy::VerificationPolicy,
provider: &dyn crate::provider::CryptoProvider,
candidate_budget: &mut InspectedKeyCandidateBudget,
scope: ResolutionScope,
) -> Result<Option<ResolvedVerificationKey<'static>>, DsigError> {
self.resolve_source_range(
key_info,
algorithm,
policy,
provider,
candidate_budget,
scope,
)?
.finish()
}
fn resolve_source_range(
&self,
key_info: Option<&KeyInfo>,
algorithm: SignatureAlgorithm,
policy: &crate::policy::VerificationPolicy,
provider: &dyn crate::provider::CryptoProvider,
candidate_budget: &mut InspectedKeyCandidateBudget,
scope: ResolutionScope,
) -> Result<SourceResolution, DsigError> {
let trust = &policy.key_trust;
let resources = &policy.resources;
trust.validate()?;
resources.validate()?;
let Some(key_info) = key_info else {
return Ok(SourceResolution {
key: None,
deferred_error: None,
});
};
let document_sources = matches!(
scope,
ResolutionScope::Document
| ResolutionScope::DocumentPrefix(_)
| ResolutionScope::DocumentSuffix(_)
);
if document_sources {
validate_key_info_source_permissions(key_info, policy.key_sources)?;
}
let source_end = match scope {
ResolutionScope::DocumentPrefix(end) | ResolutionScope::TrustedPrefix(end) => end,
_ => key_info.sources.len(),
};
let source_start = match scope {
ResolutionScope::DocumentSuffix(start) | ResolutionScope::TrustedSuffix(start) => start,
_ => 0,
};
let mut deferred_key_value_error = None;
let mut configured_material_checked = false;
for source in &key_info.sources[source_start..source_end] {
if !document_sources && matches!(source, KeyInfoSource::KeyName(_)) {
continue;
}
let mut path_evidence = None;
let mut caller_trusted =
!document_sources || matches!(source, KeyInfoSource::KeyName(_));
let resolved = match source {
KeyInfoSource::X509Data(info) => {
for (uri, _) in &info.digests {
let algorithm = super::DigestAlgorithm::from_uri(uri).ok_or_else(|| {
KeyResolutionError::UnsupportedDigestAlgorithm(uri.clone())
})?;
policy.check_digest_algorithm(algorithm)?;
}
if if info.certificate_chain.is_empty() {
x509_data_has_lookup_identifiers(info)
} else {
trust.verify_x509_chains
} {
self.check_configured_x509_material(
info,
resources,
trust,
candidate_budget,
!configured_material_checked,
)?;
configured_material_checked = true;
}
match self.resolve_x509(info, algorithm, trust, provider, candidate_budget)? {
Some((key, evidence)) => {
if evidence.is_none()
&& !caller_trusted
&& trust.mode
== crate::policy::VerificationTrustMode::RequireTrustedKey
&& let Some(der) = key.certificate_der.as_ref()
{
for anchor in &self.config.trusted_certs {
candidate_budget.charge()?;
if anchor == der {
caller_trusted = true;
break;
}
}
}
path_evidence = evidence;
Some(key)
}
None => None,
}
}
KeyInfoSource::DerEncodedKeyValue(public_key_bytes) => {
candidate_budget.charge()?;
validate_spki_algorithm(public_key_bytes, algorithm)?;
Some(VerificationKey {
algorithm,
public_key_bytes: public_key_bytes.clone(),
certificate_der: None,
name: None,
})
}
KeyInfoSource::KeyName(name) => {
candidate_budget.charge()?;
self.config
.named_keys
.get(name)
.map(|key| {
if key.algorithm != algorithm {
return Err(KeyResolutionError::AlgorithmMismatch);
}
validate_spki_algorithm(&key.public_key_bytes, algorithm)?;
Ok(key.clone())
})
.transpose()?
}
KeyInfoSource::KeyValue(key_value) => {
candidate_budget.charge()?;
match Self::resolve_key_value(key_value, algorithm) {
Ok(resolved) => resolved,
Err(error) if key_value_error_allows_fallback(key_value, &error) => {
deferred_key_value_error.get_or_insert(error);
None
}
Err(error) => return Err(error.into()),
}
}
KeyInfoSource::RetrievalMethod { .. } => {
candidate_budget.charge()?;
None
}
KeyInfoSource::KeyInfoReference { .. } => {
candidate_budget.charge()?;
None
}
};
if let Some(key) = resolved {
let key = Box::new(PolicyBoundVerificationKey {
key,
rsa_minimum_bits: trust.rsa_keys.minimum_modulus_bits,
dsa_minimum_bits: trust.dsa_keys.minimum_modulus_bits,
});
let key = if policy.key_trust.mode
== crate::policy::VerificationTrustMode::CryptographicOnly
{
ResolvedVerificationKey::Candidate(key)
} else if let Some(evidence) = path_evidence {
ResolvedVerificationKey::Validated(TrustedPublicKey::new(
key,
trust.clone(),
evidence,
))
} else if caller_trusted {
ResolvedVerificationKey::CallerTrusted(key)
} else {
ResolvedVerificationKey::Candidate(key)
};
return Ok(SourceResolution {
key: Some(key),
deferred_error: None,
});
}
}
Ok(SourceResolution {
key: None,
deferred_error: deferred_key_value_error,
})
}
}
impl KeyResolver for DefaultKeyResolver {
fn resolve_for_verification<'a>(
&'a self,
key_info: Option<&KeyInfo>,
algorithm: SignatureAlgorithm,
policy: &crate::policy::VerificationPolicy,
provider: &dyn crate::provider::CryptoProvider,
) -> Result<Option<ResolvedVerificationKey<'a>>, DsigError> {
let mut budget = InspectedKeyCandidateBudget::new(policy);
self.resolve_with_candidate_budget(key_info, algorithm, policy, provider, &mut budget)
}
fn resolve<'a>(
&'a self,
key_info: Option<&KeyInfo>,
algorithm: SignatureAlgorithm,
) -> Result<Option<Box<dyn VerifyingKey + 'a>>, DsigError> {
let policy = crate::policy::VerificationPolicy::default();
let mut candidate_budget = InspectedKeyCandidateBudget::new(&policy);
self.resolve_with_trust(
key_info,
algorithm,
&policy,
crate::provider::default_provider(),
&mut candidate_budget,
ResolutionScope::Document,
)
.map(|key| key.map(ResolvedVerificationKey::into_key))
}
fn resolve_with_policy<'a>(
&'a self,
key_info: Option<&KeyInfo>,
algorithm: SignatureAlgorithm,
policy: &crate::policy::VerificationPolicy,
) -> Result<Option<Box<dyn VerifyingKey + 'a>>, DsigError> {
self.resolve_with_policy_and_provider(
key_info,
algorithm,
policy,
crate::provider::default_provider(),
)
}
fn resolve_with_policy_and_provider<'a>(
&'a self,
key_info: Option<&KeyInfo>,
algorithm: SignatureAlgorithm,
policy: &crate::policy::VerificationPolicy,
provider: &dyn crate::provider::CryptoProvider,
) -> Result<Option<Box<dyn VerifyingKey + 'a>>, DsigError> {
let mut candidate_budget = InspectedKeyCandidateBudget::new(policy);
self.resolve_with_candidate_budget(
key_info,
algorithm,
policy,
provider,
&mut candidate_budget,
)
.map(|key| key.map(ResolvedVerificationKey::into_key))
}
fn consumes_document_key_info(&self) -> bool {
true
}
}
fn map_x509_selector_error(error: ParseError) -> DsigError {
match error {
ParseError::Provider(error) => DsigError::Provider(error),
ParseError::UnsupportedAlgorithm { uri } => {
KeyResolutionError::UnsupportedDigestAlgorithm(uri).into()
}
_ => KeyResolutionError::InvalidCertificate.into(),
}
}
fn selected_x509_path_matches_selectors(
available: &X509DataInfo,
selectors: &X509DataInfo,
provider: &dyn crate::provider::CryptoProvider,
) -> Result<bool, KeyResolutionError> {
let selected = X509DataInfo {
subject_names: selectors.subject_names.clone(),
issuer_serials: selectors.issuer_serials.clone(),
skis: selectors.skis.clone(),
digests: selectors.digests.clone(),
certificates: available
.certificate_chain
.iter()
.map(|index| available.certificates[*index].clone())
.collect(),
parsed_certificates: available
.certificate_chain
.iter()
.map(|index| available.parsed_certificates[*index].clone())
.collect(),
..X509DataInfo::default()
};
x509_selector_categories_match_chain(&selected, provider).map_err(|error| match error {
ParseError::Provider(error) => {
KeyResolutionError::Chain(super::X509ChainError::Provider(error))
}
ParseError::UnsupportedAlgorithm { uri } => {
KeyResolutionError::UnsupportedDigestAlgorithm(uri)
}
_ => KeyResolutionError::InvalidCertificate,
})
}
fn rsa_key_value_to_spki_der(
modulus: &[u8],
exponent: &[u8],
) -> Result<Vec<u8>, KeyResolutionError> {
let (modulus, exponent) = bounded_rsa_public_components(modulus, exponent)?;
let key = rsa::RsaPublicKey::new(
BoxedUint::from_be_slice_vartime(modulus),
BoxedUint::from_be_slice_vartime(exponent),
)
.map_err(|_| KeyResolutionError::InvalidPublicKey)?;
key.to_public_key_der()
.map_err(|_| KeyResolutionError::InvalidPublicKey)
.map(|der| der.as_bytes().to_vec())
}
pub(crate) fn bounded_rsa_public_components<'a>(
modulus: &'a [u8],
exponent: &'a [u8],
) -> Result<(&'a [u8], &'a [u8]), KeyResolutionError> {
let modulus = &modulus[modulus
.iter()
.position(|byte| *byte != 0)
.unwrap_or(modulus.len())..];
let exponent = &exponent[exponent
.iter()
.position(|byte| *byte != 0)
.unwrap_or(exponent.len())..];
let maximum = crate::hard_limits::RSA_MODULUS_BIT_CEILING;
if modulus.is_empty()
|| exponent.is_empty()
|| modulus.len() > maximum.div_ceil(8)
|| exponent.len() > maximum.div_ceil(8)
|| (modulus.len() * 8 - modulus[0].leading_zeros() as usize) > maximum
{
return Err(KeyResolutionError::InvalidPublicKey);
}
Ok((modulus, exponent))
}
fn dsa_key_value_to_spki_der(
p: &[u8],
q: &[u8],
g: &[u8],
y: &[u8],
) -> Result<Vec<u8>, KeyResolutionError> {
let trim = |bytes: &[u8]| bytes.iter().take_while(|byte| **byte == 0).count();
let p = &p[trim(p)..];
let q = &q[trim(q)..];
let g = &g[trim(g)..];
let y = &y[trim(y)..];
for value in [p, q, g, y] {
if value.is_empty() || value.len() > crate::hard_limits::DSA_KEY_COMPONENT_BYTE_CEILING {
return Err(KeyResolutionError::InvalidPublicKey);
}
}
let components = dsa::Components::from_components(
BoxedUint::from_be_slice_vartime(p),
BoxedUint::from_be_slice_vartime(q),
BoxedUint::from_be_slice_vartime(g),
)
.map_err(|_| KeyResolutionError::InvalidPublicKey)?;
dsa::VerifyingKey::from_components(components, BoxedUint::from_be_slice_vartime(y))
.map_err(|_| KeyResolutionError::InvalidPublicKey)?
.to_public_key_der()
.map_err(|_| KeyResolutionError::InvalidPublicKey)
.map(|der| der.as_bytes().to_vec())
}
fn ec_key_value_to_spki_der(
curve_oid: &str,
public_key: &[u8],
) -> Result<Vec<u8>, KeyResolutionError> {
match curve_oid {
EC_P256_OID => p256::PublicKey::from_sec1_bytes(public_key)
.map_err(|_| KeyResolutionError::InvalidPublicKey)?
.to_public_key_der()
.map_err(|_| KeyResolutionError::InvalidPublicKey)
.map(|der| der.as_bytes().to_vec()),
EC_P384_OID => p384::PublicKey::from_sec1_bytes(public_key)
.map_err(|_| KeyResolutionError::InvalidPublicKey)?
.to_public_key_der()
.map_err(|_| KeyResolutionError::InvalidPublicKey)
.map(|der| der.as_bytes().to_vec()),
EC_P521_OID => p521::PublicKey::from_sec1_bytes(public_key)
.map_err(|_| KeyResolutionError::InvalidPublicKey)?
.to_public_key_der()
.map_err(|_| KeyResolutionError::InvalidPublicKey)
.map(|der| der.as_bytes().to_vec()),
_ => Err(KeyResolutionError::InvalidPublicKey),
}
}
fn key_value_error_allows_fallback(key_value: &KeyValueInfo, error: &KeyResolutionError) -> bool {
matches!(
key_value,
KeyValueInfo::Dsa { .. } | KeyValueInfo::Ec { .. } | KeyValueInfo::InvalidEcKeyValue
) && matches!(
error,
KeyResolutionError::InvalidPublicKey | KeyResolutionError::AlgorithmMismatch
)
}
fn validate_spki_algorithm(
public_key_bytes: &[u8],
algorithm: SignatureAlgorithm,
) -> Result<(), KeyResolutionError> {
if algorithm.eddsa_signature_len().is_some()
|| matches!(algorithm, SignatureAlgorithm::PostQuantum(_))
{
return super::modern::validate_public_key(algorithm, public_key_bytes)
.map_err(|_| KeyResolutionError::InvalidPublicKey);
}
let (rest, spki) = SubjectPublicKeyInfo::from_der(public_key_bytes)
.map_err(|_| KeyResolutionError::InvalidPublicKey)?;
if !rest.is_empty() {
return Err(KeyResolutionError::InvalidPublicKey);
}
let parsed = spki
.parsed()
.map_err(|_| KeyResolutionError::InvalidPublicKey)?;
let curve_oid = spki
.algorithm
.parameters
.as_ref()
.and_then(|value| value.as_oid().ok())
.map(|oid| oid.to_id_string());
match (algorithm, parsed) {
(SignatureAlgorithm::DsaSha1 | SignatureAlgorithm::DsaSha256, PublicKey::DSA(_)) => {
let _ = decode_dsa_verifying_key(public_key_bytes)
.map_err(|_| KeyResolutionError::InvalidPublicKey)?;
Ok(())
}
(method, PublicKey::RSA(_)) if method.is_rsa() => Ok(()),
(method, PublicKey::EC(ec))
if method.ecdsa_digest().is_some()
&& matches!(
curve_oid.as_deref(),
Some(EC_P256_OID | EC_P384_OID | EC_P521_OID)
) =>
{
validate_ec_public_key_encoding(&ec, spki.subject_public_key.data.as_ref())
.map_err(|_| KeyResolutionError::InvalidPublicKey)?;
validate_ec_point(curve_oid.as_deref(), spki.subject_public_key.data.as_ref())?;
Ok(())
}
_ => Err(KeyResolutionError::AlgorithmMismatch),
}
}
pub(crate) fn supported_parsed_spki_is_rsa(
spki: &SubjectPublicKeyInfo<'_>,
public_key_bytes: &[u8],
) -> Result<bool, KeyResolutionError> {
let borrowed = pkcs8::SubjectPublicKeyInfoRef::try_from(public_key_bytes)
.map_err(|_| KeyResolutionError::InvalidPublicKey)?;
if let Some(parameter) = super::PqAlgorithm::from_oid(borrowed.algorithm.oid) {
super::modern::validate_public_key(
SignatureAlgorithm::PostQuantum(parameter),
public_key_bytes,
)
.map_err(|_| KeyResolutionError::InvalidPublicKey)?;
return Ok(false);
}
let eddsa = match spki.algorithm.algorithm.to_id_string().as_str() {
"1.3.101.112" => Some(SignatureAlgorithm::Ed25519),
"1.3.101.113" => Some(SignatureAlgorithm::Ed448),
_ => None,
};
if let Some(algorithm) = eddsa {
super::modern::validate_public_key(algorithm, public_key_bytes)
.map_err(|_| KeyResolutionError::InvalidPublicKey)?;
return Ok(false);
}
let parsed = spki
.parsed()
.map_err(|_| KeyResolutionError::InvalidPublicKey)?;
match parsed {
PublicKey::RSA(key) => {
bounded_rsa_public_components(key.modulus, key.exponent)?;
rsa::RsaPublicKey::from_public_key_der(public_key_bytes)
.map_err(|_| KeyResolutionError::InvalidPublicKey)?;
Ok(true)
}
PublicKey::DSA(_) => {
let _ = decode_dsa_verifying_key(public_key_bytes)
.map_err(|_| KeyResolutionError::InvalidPublicKey)?;
Ok(false)
}
PublicKey::EC(ec) => {
validate_ec_public_key_encoding(&ec, spki.subject_public_key.data.as_ref())
.map_err(|_| KeyResolutionError::InvalidPublicKey)?;
let curve_oid = spki
.algorithm
.parameters
.as_ref()
.and_then(|value| value.as_oid().ok())
.map(|oid| oid.to_id_string());
validate_ec_point(curve_oid.as_deref(), spki.subject_public_key.data.as_ref())?;
Ok(false)
}
_ => Err(KeyResolutionError::AlgorithmMismatch),
}
}
fn validate_ec_point(curve_oid: Option<&str>, point: &[u8]) -> Result<(), KeyResolutionError> {
match curve_oid {
Some(EC_P256_OID) => p256::PublicKey::from_sec1_bytes(point).map(|_| ()),
Some(EC_P384_OID) => p384::PublicKey::from_sec1_bytes(point).map(|_| ()),
Some(EC_P521_OID) => p521::PublicKey::from_sec1_bytes(point).map(|_| ()),
_ => return Err(KeyResolutionError::AlgorithmMismatch),
}
.map_err(|_| KeyResolutionError::InvalidPublicKey)
}
pub(crate) fn supported_key_value_is_rsa(value: &KeyValueInfo) -> Result<bool, KeyResolutionError> {
let (spki, is_rsa) = match value {
KeyValueInfo::Rsa { modulus, exponent } => {
(rsa_key_value_to_spki_der(modulus, exponent)?, true)
}
KeyValueInfo::Dsa {
p: Some(p),
q: Some(q),
g: Some(g),
y,
} => (dsa_key_value_to_spki_der(p, q, g, y)?, false),
KeyValueInfo::Ec {
curve_oid,
public_key,
} => (ec_key_value_to_spki_der(curve_oid, public_key)?, false),
_ => return Err(KeyResolutionError::InvalidPublicKey),
};
let algorithm = if is_rsa {
SignatureAlgorithm::RsaSha256
} else if matches!(value, KeyValueInfo::Dsa { .. }) {
SignatureAlgorithm::DsaSha256
} else {
SignatureAlgorithm::EcdsaSha256
};
validate_spki_algorithm(&spki, algorithm)?;
Ok(is_rsa)
}
#[cfg(test)]
mod tests {
use crate::xml::dom as roxmltree;
use std::sync::atomic::{AtomicUsize, Ordering};
use base64::{Engine, engine::general_purpose::STANDARD};
use der::Decode as _;
use rcgen::{
CertificateRevocationListParams, Issuer, KeyIdMethod, KeyPair, KeyUsagePurpose,
RevokedCertParams, SerialNumber, date_time_ymd,
};
use rsa::{pkcs8::DecodePublicKey, traits::PublicKeyParts};
use super::*;
#[test]
fn shared_candidate_budget_cannot_relax_active_policy() {
let resolver = DefaultKeyResolver::new(KeyResolverConfig::default());
let info = KeyInfo {
sources: vec![KeyInfoSource::KeyName("missing".into())],
};
let mut policy = crate::policy::VerificationPolicy::default();
policy.resources.max_key_candidates = 1;
let mut budget = InspectedKeyCandidateBudget::new(&policy);
assert!(
resolver
.resolve_with_candidate_budget(
Some(&info),
SignatureAlgorithm::RsaSha256,
&policy,
crate::provider::default_provider(),
&mut budget
)
.expect("first candidate fits the active policy")
.is_none()
);
assert!(matches!(
resolver.resolve_with_candidate_budget(
Some(&info),
SignatureAlgorithm::RsaSha256,
&policy,
crate::provider::default_provider(),
&mut budget
),
Err(DsigError::Policy(
crate::policy::PolicyViolation::ResourceLimit {
maximum: 1,
actual: 2,
..
}
))
));
let mut spent =
InspectedKeyCandidateBudget::new(&crate::policy::VerificationPolicy::default());
spent
.charge_many(2)
.expect("initial budget admits two candidates");
assert!(matches!(
resolver.resolve_with_candidate_budget(
None,
SignatureAlgorithm::RsaSha256,
&policy,
crate::provider::default_provider(),
&mut spent
),
Err(DsigError::Policy(
crate::policy::PolicyViolation::ResourceLimit {
maximum: 1,
actual: 2,
..
}
))
));
}
#[test]
fn dsa_key_value_components_are_bounded_before_bigint_decode() {
let public = dsa::VerifyingKey::from_public_key_pem(include_str!(
"../../tests/fixtures/keys/dsa/dsa-2048-public.pem"
))
.expect("DSA fixture");
let components = public.components();
let values = [
components.p().to_be_bytes_trimmed_vartime().to_vec(),
components.q().to_be_bytes_trimmed_vartime().to_vec(),
components.g().to_be_bytes_trimmed_vartime().to_vec(),
public.y().to_be_bytes_trimmed_vartime().to_vec(),
];
let expected = dsa_key_value_to_spki_der(&values[0], &values[1], &values[2], &values[3])
.expect("valid DSA");
for index in 0..4 {
let mut oversized = values.clone();
oversized[index] = vec![1; crate::hard_limits::DSA_KEY_COMPONENT_BYTE_CEILING + 1];
assert!(
dsa_key_value_to_spki_der(
&oversized[0],
&oversized[1],
&oversized[2],
&oversized[3]
)
.is_err()
);
}
let padded = values.map(|value| {
let mut padded = vec![0; 1024];
padded.extend(value);
padded
});
assert_eq!(
dsa_key_value_to_spki_der(&padded[0], &padded[1], &padded[2], &padded[3])
.expect("zero padding preserves unsigned DSA value"),
expected
);
}
#[test]
fn xml_rsa_components_are_bounded_before_bigint_decode() {
let oversized = vec![1_u8; crate::hard_limits::RSA_MODULUS_BIT_CEILING.div_ceil(8) + 1];
assert!(matches!(
rsa_key_value_to_spki_der(&oversized, &[1, 0, 1]),
Err(KeyResolutionError::InvalidPublicKey)
));
}
#[test]
fn oversized_dsa_spki_parameter_is_rejected_before_bigint_decode() {
let oversized = vec![1_u8; crate::hard_limits::DSA_KEY_COMPONENT_BYTE_CEILING + 1];
let one = [1_u8];
let params = der::Encode::to_der(&super::super::signature::BorrowedDsaPublicParameters {
p: der::asn1::UintRef::new(&oversized).expect("positive P"),
q: der::asn1::UintRef::new(&one).expect("positive Q"),
g: der::asn1::UintRef::new(&one).expect("positive G"),
})
.expect("parameters encode");
let y = der::Encode::to_der(&der::asn1::UintRef::new(&one).expect("positive Y"))
.expect("public value encodes");
let spki = rsa::pkcs8::SubjectPublicKeyInfoRef {
algorithm: rsa::pkcs8::AlgorithmIdentifierRef {
oid: dsa::OID,
parameters: Some(der::asn1::AnyRef::from_der(¶ms).expect("parameters")),
},
subject_public_key: der::asn1::BitStringRef::new(0, &y).expect("bit string"),
};
let encoded = der::Encode::to_der(&spki).expect("SPKI encodes");
assert!(matches!(
decode_dsa_verifying_key(&encoded),
Err(super::super::signature::SignatureVerificationError::InvalidKeyDer)
));
assert!(matches!(
validate_spki_algorithm(&encoded, SignatureAlgorithm::DsaSha256),
Err(KeyResolutionError::InvalidPublicKey)
));
}
struct RejectSecondSha512Provider {
sha512_calls: AtomicUsize,
verification_calls: AtomicUsize,
reject_verification_call: Option<usize>,
rejected_verification_data: Option<Vec<u8>>,
}
impl crate::provider::CryptoProvider for RejectSecondSha512Provider {
fn name(&self) -> &'static str {
"reject-second-sha512"
}
fn supports(&self, capability: crate::provider::ProviderCapability<'_>) -> bool {
crate::provider::default_provider().supports(capability)
}
fn fill_random(&self, output: &mut [u8]) -> Result<(), crate::provider::ProviderError> {
crate::provider::default_provider().fill_random(output)
}
fn derive_key(
&self,
parameters: &crate::provider::KdfParameters<'_>,
secret: &[u8],
) -> Result<Vec<u8>, crate::provider::ProviderError> {
crate::provider::default_provider().derive_key(parameters, secret)
}
fn digest(
&self,
algorithm: super::super::DigestAlgorithm,
data: &[u8],
) -> Result<Vec<u8>, crate::provider::ProviderError> {
if algorithm == super::super::DigestAlgorithm::Sha512
&& self.sha512_calls.fetch_add(1, Ordering::Relaxed) > 0
{
return Err(crate::provider::ProviderError::Unsupported {
operation: crate::provider::ProviderOperation::Digest,
algorithm: Some(algorithm.uri().to_owned()),
});
}
crate::provider::default_provider().digest(algorithm, data)
}
fn sign(
&self,
key: &dyn super::super::SigningKey,
algorithm: SignatureAlgorithm,
data: &[u8],
) -> Result<Vec<u8>, super::super::SigningKeyError> {
crate::provider::default_provider().sign(key, algorithm, data)
}
fn verify(
&self,
key: &dyn VerifyingKey,
algorithm: SignatureAlgorithm,
data: &[u8],
signature: &[u8],
) -> Result<bool, DsigError> {
let call = self.verification_calls.fetch_add(1, Ordering::Relaxed);
if self.reject_verification_call == Some(call)
|| self
.rejected_verification_data
.as_deref()
.is_some_and(|rejected| rejected == data)
{
return Err(crate::provider::ProviderError::Unsupported {
operation: crate::provider::ProviderOperation::Verify,
algorithm: Some(algorithm.uri().to_owned()),
}
.into());
}
crate::provider::default_provider().verify(key, algorithm, data, signature)
}
fn verify_x509_signature(
&self,
algorithm: crate::provider::X509SignatureAlgorithm,
data: &[u8],
signature: &[u8],
issuer_spki_der: &[u8],
) -> Result<bool, crate::provider::ProviderError> {
let call = self.verification_calls.fetch_add(1, Ordering::Relaxed);
if self.reject_verification_call == Some(call)
|| self
.rejected_verification_data
.as_deref()
.is_some_and(|rejected| rejected == data)
{
return Err(crate::provider::ProviderError::Unsupported {
operation: crate::provider::ProviderOperation::VerifyCertificate,
algorithm: algorithm.oid().map(str::to_owned),
});
}
crate::provider::default_provider().verify_x509_signature(
algorithm,
data,
signature,
issuer_spki_der,
)
}
#[cfg(feature = "xmlenc")]
fn encrypt_data(
&self,
algorithm: crate::xmlenc::DataEncryptionAlgorithm,
key: &[u8],
plaintext: &[u8],
) -> Result<Vec<u8>, crate::provider::ProviderError> {
crate::provider::default_provider().encrypt_data(algorithm, key, plaintext)
}
#[cfg(feature = "xmlenc")]
fn decrypt_data(
&self,
algorithm: crate::xmlenc::DataEncryptionAlgorithm,
key: &[u8],
ciphertext: &[u8],
) -> Result<Vec<u8>, crate::provider::ProviderError> {
crate::provider::default_provider().decrypt_data(algorithm, key, ciphertext)
}
#[cfg(feature = "xmlenc")]
fn wrap_key(
&self,
algorithm: crate::xmlenc::KeyWrapAlgorithm,
kek: &[u8],
key: &[u8],
) -> Result<Vec<u8>, crate::provider::ProviderError> {
crate::provider::default_provider().wrap_key(algorithm, kek, key)
}
#[cfg(feature = "xmlenc")]
fn unwrap_key(
&self,
algorithm: crate::xmlenc::KeyWrapAlgorithm,
kek: &[u8],
wrapped: &[u8],
) -> Result<Vec<u8>, crate::provider::ProviderError> {
crate::provider::default_provider().unwrap_key(algorithm, kek, wrapped)
}
#[cfg(feature = "xmlenc")]
fn transport_key(
&self,
key: &dyn crate::provider::KeyTransportKey,
parameters: &crate::xmlenc::RsaOaepParameters,
plaintext: &[u8],
) -> Result<Vec<u8>, crate::provider::ProviderError> {
crate::provider::default_provider().transport_key(key, parameters, plaintext)
}
#[cfg(feature = "xmlenc")]
fn recover_key(
&self,
key: &dyn crate::provider::KeyRecoveryKey,
parameters: &crate::xmlenc::RsaOaepParameters,
ciphertext: &[u8],
) -> Result<Vec<u8>, crate::provider::ProviderError> {
crate::provider::default_provider().recover_key(key, parameters, ciphertext)
}
}
fn chain_policy() -> crate::policy::KeyTrustPolicy {
crate::policy::KeyTrustPolicy {
verify_x509_chains: true,
..crate::policy::KeyTrustPolicy::default()
}
}
fn cryptographic_policy() -> crate::policy::VerificationPolicy {
verification_policy_with_trust(crate::policy::KeyTrustPolicy {
mode: crate::policy::VerificationTrustMode::CryptographicOnly,
..crate::policy::KeyTrustPolicy::default()
})
}
fn chain_policy_at(verification_time: SystemTime) -> crate::policy::KeyTrustPolicy {
crate::policy::KeyTrustPolicy {
verification_time: Some(verification_time),
..chain_policy()
}
}
fn verification_policy_with_trust(
key_trust: crate::policy::KeyTrustPolicy,
) -> crate::policy::VerificationPolicy {
crate::policy::VerificationPolicy {
key_trust,
..crate::policy::VerificationPolicy::default()
}
}
const SIGNED_SAML: &str =
include_str!("../../tests/fixtures/saml/response_signed_by_idp_ecdsa.xml");
const SAML_PUBLIC_KEY: &str =
include_str!("../../tests/fixtures/keys/ec/saml-idp-ecdsa-pubkey.pem");
const RSA_PUBLIC_KEY: &str = include_str!("../../tests/fixtures/keys/rsa/rsa-2048-pubkey.pem");
const RSA_4096_CERTIFICATE: &str =
include_str!("../../tests/fixtures/keys/rsa/rsa-4096-cert.pem");
const X509_DIGEST_SIGNATURE: &str = include_str!(
"../../tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloped-x509-digest-sha512.xml"
);
const X509_DIGEST_SHA256_SIGNATURE: &str = include_str!(
"../../tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloped-x509-digest-sha256.xml"
);
const RSA_KEY_VALUE_SIGNATURE: &str = include_str!(
"../../tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloping-sha256-rsa-sha256.xml"
);
const LEGACY_RSA_KEY_VALUE_SIGNATURE: &str = include_str!(
"../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-enveloping-rsa.xml"
);
const EC_P256_KEY_VALUE_SIGNATURE: &str = include_str!(
"../../tests/fixtures/xmldsig/xmldsig11-interop-2012/signature-enveloping-p256_sha256.xml"
);
const EC_P384_KEY_VALUE_SIGNATURE: &str = include_str!(
"../../tests/fixtures/xmldsig/xmldsig11-interop-2012/signature-enveloping-p384_sha384.xml"
);
fn replace_key_info(xml: &str, replacement: &str) -> String {
let start = xml.find("<ds:KeyInfo>").expect("fixture has KeyInfo");
let end = xml
.find("</ds:KeyInfo>")
.expect("fixture has closing KeyInfo")
+ "</ds:KeyInfo>".len();
format!("{}{}{}", &xml[..start], replacement, &xml[end..])
}
fn replace_unprefixed_key_info(xml: &str, replacement: &str) -> String {
let start = xml.find("<KeyInfo>").expect("fixture has KeyInfo");
let end = xml.find("</KeyInfo>").expect("fixture has closing KeyInfo") + "</KeyInfo>".len();
format!("{}{}{}", &xml[..start], replacement, &xml[end..])
}
fn rsa_key_value_parts(public_key: &rsa::RsaPublicKey) -> (String, String) {
(
STANDARD.encode(public_key.n().to_be_bytes_trimmed_vartime()),
STANDARD.encode(public_key.e().to_be_bytes_trimmed_vartime()),
)
}
fn x509_signature_with_leaf_subject() -> String {
replace_unprefixed_key_info(
X509_DIGEST_SIGNATURE,
"<KeyInfo><X509Data><X509SubjectName>CN=Test Key rsa-4096,O=XML Security Library (http://www.aleksey.com/xmlsec),ST=California,C=US</X509SubjectName></X509Data></KeyInfo>",
)
}
fn fixture_certificate_time() -> SystemTime {
SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(1_800_000_000)
}
fn public_key_der(pem_text: &str) -> Vec<u8> {
let (rest, pem) = x509_parser::pem::parse_x509_pem(pem_text.as_bytes())
.expect("fixture public key is PEM");
assert!(rest.iter().all(|byte| byte.is_ascii_whitespace()));
assert_eq!(pem.label, "PUBLIC KEY");
pem.contents
}
fn certificate_der(pem_text: &str) -> Vec<u8> {
let (rest, pem) = x509_parser::pem::parse_x509_pem(pem_text.as_bytes())
.expect("fixture certificate is PEM");
assert!(rest.iter().all(|byte| byte.is_ascii_whitespace()));
assert_eq!(pem.label, "CERTIFICATE");
pem.contents
}
fn crl_der(pem_text: &str) -> Vec<u8> {
let (rest, pem) =
x509_parser::pem::parse_x509_pem(pem_text.as_bytes()).expect("fixture CRL is PEM");
assert!(rest.iter().all(|byte| byte.is_ascii_whitespace()));
assert_eq!(pem.label, "X509 CRL");
pem.contents
}
fn generated_certificate_params(common_name: &str, is_ca: bool) -> rcgen::CertificateParams {
let mut params = rcgen::CertificateParams::new(Vec::new())
.expect("empty SAN list should produce valid certificate parameters");
params
.distinguished_name
.push(rcgen::DnType::CommonName, common_name);
if is_ca {
params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
params.key_usages = vec![rcgen::KeyUsagePurpose::KeyCertSign];
}
params
}
fn authorized_revoked_chain() -> (X509DataInfo, Vec<u8>) {
let mut root_params = generated_certificate_params("revocation root", true);
root_params.key_usages = vec![KeyUsagePurpose::KeyCertSign, KeyUsagePurpose::CrlSign];
let root = rcgen::CertifiedIssuer::self_signed(
root_params,
KeyPair::generate().expect("root key generation should succeed"),
)
.expect("root should be self-signable");
let issuer_key = KeyPair::generate().expect("issuer key generation should succeed");
let mut issuer_params = generated_certificate_params("revocation issuer", true);
issuer_params.key_usages = vec![KeyUsagePurpose::KeyCertSign, KeyUsagePurpose::CrlSign];
let issuer_certificate = issuer_params
.signed_by(&issuer_key, &root)
.expect("root should sign issuer certificate");
let issuer = Issuer::new(issuer_params, issuer_key);
let mut leaf_params = generated_certificate_params("revoked signer", false);
leaf_params.serial_number = Some(SerialNumber::from(42_u64));
leaf_params.key_usages = vec![KeyUsagePurpose::DigitalSignature];
let leaf = leaf_params
.signed_by(
&KeyPair::generate().expect("leaf key generation should succeed"),
&issuer,
)
.expect("issuer should sign leaf certificate");
let crl = CertificateRevocationListParams {
this_update: date_time_ymd(2026, 3, 15),
next_update: date_time_ymd(2026, 4, 15),
crl_number: SerialNumber::from(1_u64),
issuing_distribution_point: None,
revoked_certs: vec![RevokedCertParams {
serial_number: SerialNumber::from(42_u64),
revocation_time: date_time_ymd(2026, 3, 16),
reason_code: None,
invalidity_date: None,
}],
key_identifier_method: KeyIdMethod::Sha256,
}
.signed_by(&issuer)
.expect("issuer should sign CRL");
let mut info = x509_info(
vec![
leaf.der().to_vec(),
issuer_certificate.der().to_vec(),
root.der().to_vec(),
],
0,
);
info.crls.push(crl.der().to_vec());
(info, root.der().to_vec())
}
fn x509_info(certificates: Vec<Vec<u8>>, signing_index: usize) -> X509DataInfo {
let parsed_certificates = certificates
.iter()
.map(|certificate| {
parse_x509_certificate(certificate)
.expect("generated certificate should have supported metadata")
})
.collect();
X509DataInfo {
certificates,
parsed_certificates,
certificate_chain: vec![signing_index],
..X509DataInfo::default()
}
}
#[test]
fn defaults_match_key_resolution_policy() {
let config = KeyResolverConfig::default();
assert!(config.trusted_certs.is_empty());
assert!(config.lookup_certs.is_empty());
assert!(config.named_keys.is_empty());
let trust = crate::policy::VerificationPolicy::default().key_trust;
assert!(!trust.verify_x509_chains);
assert!(!trust.check_crls);
assert_eq!(trust.verification_time, None);
assert_eq!(trust.max_x509_chain_depth, 9);
}
#[test]
fn verification_policy_controls_leaf_extended_key_usage() {
let root = rcgen::CertifiedIssuer::self_signed(
generated_certificate_params("EKU policy root", true),
rcgen::KeyPair::generate().expect("root key generation should succeed"),
)
.expect("root should be self-signable");
let mut leaf_params = generated_certificate_params("TLS-only XML signer", false);
leaf_params.key_usages = vec![rcgen::KeyUsagePurpose::DigitalSignature];
leaf_params.extended_key_usages = vec![rcgen::ExtendedKeyUsagePurpose::ServerAuth];
let leaf = leaf_params
.signed_by(
&rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
&root,
)
.expect("root should sign leaf certificate");
let key_info = KeyInfo {
sources: vec![KeyInfoSource::X509Data(x509_info(
vec![leaf.der().to_vec(), root.der().to_vec()],
0,
))],
};
let resolver = DefaultKeyResolver::new(KeyResolverConfig {
trusted_certs: vec![root.der().to_vec()],
..KeyResolverConfig::default()
});
let mut policy = crate::policy::VerificationPolicy::default();
policy.key_trust.verify_x509_chains = true;
let error = match resolver.resolve_with_policy(
Some(&key_info),
SignatureAlgorithm::EcdsaSha256,
&policy,
) {
Ok(_) => panic!("unapproved restricted EKU must be rejected"),
Err(error) => error,
};
assert!(matches!(
error,
DsigError::KeyResolution(KeyResolutionError::Chain(
super::super::X509ChainError::InvalidKeyUsage {
position: 0,
required: "an approved extended key usage",
}
))
));
policy.key_trust.allowed_extended_key_usages =
std::collections::HashSet::from([crate::policy::ExtendedKeyPurpose::ServerAuth]);
assert!(
resolver
.resolve_with_policy(Some(&key_info), SignatureAlgorithm::EcdsaSha256, &policy,)
.expect("approved restricted EKU must pass path validation")
.is_some()
);
}
#[test]
fn operation_policy_rejects_zero_x509_resource_limits() {
for trust in [
crate::policy::KeyTrustPolicy {
verify_x509_chains: true,
max_x509_chain_depth: 0,
..crate::policy::KeyTrustPolicy::default()
},
crate::policy::KeyTrustPolicy {
verify_x509_chains: true,
max_x509_candidate_paths: 0,
..crate::policy::KeyTrustPolicy::default()
},
] {
let certificate = certificate_der(RSA_4096_CERTIFICATE);
let resolver = DefaultKeyResolver::new(KeyResolverConfig {
trusted_certs: vec![certificate],
..KeyResolverConfig::default()
});
let policy = crate::policy::VerificationPolicy {
key_trust: trust,
..crate::policy::VerificationPolicy::default()
};
let error = super::super::VerifyContext::new()
.policy(policy)
.key_resolver(&resolver)
.verify(&x509_signature_with_leaf_subject())
.expect_err("zero composed X.509 limits must fail as policy errors");
assert!(matches!(
error,
DsigError::Policy(crate::policy::PolicyViolation::InvalidResourceLimit {
requirement: "limit must be nonzero",
actual: 0,
..
})
));
}
}
#[test]
fn operation_policy_rejects_crl_checking_without_chain_validation() {
let resolver = DefaultKeyResolver::new(KeyResolverConfig {
lookup_certs: vec![certificate_der(RSA_4096_CERTIFICATE)],
..KeyResolverConfig::default()
});
let policy = crate::policy::VerificationPolicy {
key_trust: crate::policy::KeyTrustPolicy {
check_crls: true,
..crate::policy::KeyTrustPolicy::default()
},
..crate::policy::VerificationPolicy::default()
};
let error = super::super::VerifyContext::new()
.policy(policy)
.key_resolver(&resolver)
.verify(&x509_signature_with_leaf_subject())
.expect_err("CRL-only trust policy must fail before certificate use");
assert!(matches!(
error,
DsigError::Policy(crate::policy::PolicyViolation::KeyTrust {
reason: "CRL checking requires X.509 chain validation"
})
));
}
#[test]
fn hmac_key_rejects_empty_secret_and_wrong_algorithm() {
assert!(matches!(
HmacSha1VerificationKey::new(Vec::new()),
Err(KeyResolutionError::InvalidPublicKey)
));
let key = HmacSha1VerificationKey::new(b"secret".to_vec())
.expect("non-empty HMAC secret must be accepted");
assert!(matches!(
key.verify(SignatureAlgorithm::RsaSha256, b"data", b"signature"),
Err(DsigError::KeyResolution(
KeyResolutionError::AlgorithmMismatch
))
));
}
#[test]
fn hmac_key_uses_the_operation_policy_for_truncation() {
let key = HmacVerificationKey::new(b"secret".to_vec())
.expect("the fixture HMAC secret is non-empty");
let mut mac = hmac::Hmac::<sha1::Sha1>::new_from_slice(b"secret")
.expect("HMAC accepts an arbitrary non-empty secret");
mac.update(b"data");
let expected = mac.finalize().into_bytes();
let policy = crate::policy::VerificationPolicy {
hmac: crate::policy::HmacPolicy {
minimum_key_bits: 40,
minimum_output_bits: 80,
},
..crate::policy::VerificationPolicy::default()
};
assert!(
key.verify_with_policy(
&policy,
SignatureAlgorithm::HmacSha1,
b"data",
&expected[..10],
)
.expect("the compatibility policy and algorithm match")
);
assert!(
!key.verify_with_policy(&policy, SignatureAlgorithm::HmacSha1, b"data", &[0_u8; 10],)
.expect("a mismatched truncated MAC must be rejected")
);
}
#[test]
fn hmac_key_direct_api_rejects_attacker_selected_short_output() {
let key = HmacVerificationKey::new([0x42; 16]).expect("fixed HMAC key must parse");
let mut mac = hmac::Hmac::<sha2::Sha256>::new_from_slice(&[0x42; 16])
.expect("HMAC accepts the fixed secret");
mac.update(b"data");
let expected = mac.finalize().into_bytes();
assert!(matches!(
key.verify(SignatureAlgorithm::HmacSha256, b"data", &expected[..1]),
Err(DsigError::Policy(
crate::policy::PolicyViolation::HmacOutputLength {
minimum: 128,
maximum: 256,
actual: 8,
}
))
));
}
#[test]
fn hmac_key_debug_redacts_secret_material() {
let secret = b"unique-debug-secret-marker";
let key = HmacVerificationKey::new(secret.to_vec())
.expect("the fixture HMAC secret is non-empty");
let debug = format!("{key:?}");
assert!(
!debug
.contains(std::str::from_utf8(secret).expect("the debug marker is literal ASCII"))
);
assert!(!debug.contains(&format!("{secret:?}")));
assert!(!debug.contains("output_length_bits"));
}
#[test]
fn stores_named_verification_key_metadata() {
let key = VerificationKey {
algorithm: SignatureAlgorithm::RsaSha256,
public_key_bytes: vec![1, 2, 3],
certificate_der: Some(vec![4, 5, 6]),
name: Some("idp-signing".into()),
};
let mut config = KeyResolverConfig::default();
config.named_keys.insert("idp-signing".into(), key.clone());
assert_eq!(config.named_keys.get("idp-signing"), Some(&key));
}
#[test]
fn resolves_embedded_certificate_end_to_end() {
let resolver = DefaultKeyResolver::default();
let result = super::super::VerifyContext::new()
.policy(cryptographic_policy())
.key_resolver(&resolver)
.verify(SIGNED_SAML)
.expect("embedded certificate should resolve");
assert_eq!(result.status, super::super::DsigStatus::Valid);
assert_eq!(
result.key_trust,
super::super::KeyTrustEvidence::NotEstablished
);
}
#[test]
fn embedded_certificate_requires_trust_but_explicit_pin_authorizes_it() {
let untrusted = DefaultKeyResolver::default();
let error = super::super::VerifyContext::new()
.key_resolver(&untrusted)
.verify(SIGNED_SAML)
.expect_err("an embedded certificate is not a trust declaration");
assert!(matches!(
error,
DsigError::Policy(crate::policy::PolicyViolation::KeyTrust { .. })
));
let xml = roxmltree::Document::parse(SIGNED_SAML).expect("signed SAML fixture must parse");
let certificate = xml
.descendants()
.find(|node| {
node.has_tag_name(("http://www.w3.org/2000/09/xmldsig#", "X509Certificate"))
})
.expect("signed SAML must embed its certificate");
let der = STANDARD
.decode(
certificate
.text()
.expect("fixture certificate must contain Base64 text")
.split_whitespace()
.collect::<String>(),
)
.expect("fixture certificate must be valid Base64");
let trusted = DefaultKeyResolver::new(KeyResolverConfig {
trusted_certs: vec![der],
..KeyResolverConfig::default()
});
let result = super::super::VerifyContext::new()
.key_resolver(&trusted)
.verify(SIGNED_SAML)
.expect("an exact caller certificate pin authorizes the key");
assert_eq!(result.status, super::super::DsigStatus::Valid);
assert_eq!(
result.key_trust,
super::super::KeyTrustEvidence::CallerTrusted
);
assert!(
super::super::VerifyContext::new()
.key_resolver(&untrusted)
.verify(SIGNED_SAML)
.is_err()
);
}
#[test]
fn certificate_pin_inspections_share_the_candidate_budget() {
let xml = roxmltree::Document::parse(SIGNED_SAML).expect("fixture must parse");
let text = xml
.descendants()
.find(|node| {
node.has_tag_name(("http://www.w3.org/2000/09/xmldsig#", "X509Certificate"))
})
.and_then(|node| node.text())
.expect("fixture embeds a certificate");
let certificate = STANDARD
.decode(text.split_whitespace().collect::<String>())
.expect("fixture certificate must decode");
let resolver = DefaultKeyResolver::new(KeyResolverConfig {
trusted_certs: vec![certificate],
..KeyResolverConfig::default()
});
let mut policy = crate::policy::VerificationPolicy::default();
policy.resources.max_key_candidates = 1;
let error = super::super::VerifyContext::new()
.policy(policy)
.key_resolver(&resolver)
.verify(SIGNED_SAML)
.expect_err("the certificate and pin are separate inspections");
assert!(matches!(
error,
DsigError::Policy(crate::policy::PolicyViolation::ResourceLimit {
resource: crate::policy::resource_name::KEY_CANDIDATES,
maximum: 1,
actual: 2,
})
));
}
#[test]
fn resolves_x509_digest_from_configured_certificates() {
let leaf_certificate_der = certificate_der(RSA_4096_CERTIFICATE);
let resolver = DefaultKeyResolver::new(KeyResolverConfig {
lookup_certs: vec![leaf_certificate_der],
trusted_certs: vec![
certificate_der(include_str!("../../tests/fixtures/keys/ca2cert.pem")),
certificate_der(include_str!("../../tests/fixtures/keys/cacert.pem")),
],
..KeyResolverConfig::default()
});
for signature in [X509_DIGEST_SHA256_SIGNATURE, X509_DIGEST_SIGNATURE] {
let result = super::super::VerifyContext::new()
.policy(cryptographic_policy())
.key_resolver(&resolver)
.verify(signature)
.expect("X509Digest should resolve a configured certificate");
assert_eq!(result.status, super::super::DsigStatus::Valid);
}
}
#[test]
fn selector_resolved_certificate_obeys_chain_policy() {
let leaf_certificate_der = certificate_der(RSA_4096_CERTIFICATE);
let resolver = DefaultKeyResolver::new(KeyResolverConfig {
lookup_certs: vec![leaf_certificate_der],
trusted_certs: vec![
certificate_der(include_str!("../../tests/fixtures/keys/ca2cert.pem")),
certificate_der(include_str!("../../tests/fixtures/keys/cacert.pem")),
],
..KeyResolverConfig::default()
});
let error = super::super::VerifyContext::new()
.policy(verification_policy_with_trust(chain_policy_at(
SystemTime::UNIX_EPOCH,
)))
.key_resolver(&resolver)
.verify(&x509_signature_with_leaf_subject())
.expect_err("selector-resolved certificate must satisfy chain policy");
assert!(
matches!(
&error,
DsigError::KeyResolution(KeyResolutionError::Chain(
super::super::X509ChainError::CertificateNotValid(_)
))
),
"unexpected selector policy error: {error:?}"
);
}
#[test]
fn selector_resolved_configured_root_remains_a_trust_anchor() {
let mut params = rcgen::CertificateParams::new(Vec::new())
.expect("empty SAN list should produce valid certificate parameters");
params
.distinguished_name
.push(rcgen::DnType::CommonName, "configured root");
params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
let key_pair = rcgen::KeyPair::generate().expect("test key generation should succeed");
let certificate = params
.self_signed(&key_pair)
.expect("test root should be self-signable");
let certificate_der = certificate.der().to_vec();
let key_info_xml = concat!(
"<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">",
"<X509Data><X509SubjectName>CN=configured root</X509SubjectName></X509Data>",
"</KeyInfo>"
);
let document = roxmltree::Document::parse(key_info_xml)
.expect("static selector KeyInfo should parse as XML");
let key_info = super::super::parse_key_info(document.root_element())
.expect("static selector KeyInfo should satisfy XMLDSig structure");
let resolver = DefaultKeyResolver::new(KeyResolverConfig {
trusted_certs: vec![certificate_der],
..KeyResolverConfig::default()
});
let resolved = resolver
.resolve_with_policy(
Some(&key_info),
SignatureAlgorithm::EcdsaSha256,
&verification_policy_with_trust(chain_policy()),
)
.expect("configured self-signed certificate should validate as its own anchor");
assert!(resolved.is_some());
}
#[test]
fn selector_resolved_non_self_signed_trust_anchor_terminates_the_path() {
let mut issuer_params = rcgen::CertificateParams::new(Vec::new())
.expect("empty issuer SAN list should be valid");
issuer_params
.distinguished_name
.push(rcgen::DnType::CommonName, "lookup-only issuer");
issuer_params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
issuer_params.key_usages = vec![rcgen::KeyUsagePurpose::KeyCertSign];
let issuer = rcgen::CertifiedIssuer::self_signed(
issuer_params,
rcgen::KeyPair::generate().expect("issuer key generation should succeed"),
)
.expect("issuer certificate should be self-signable");
let mut anchor_params = rcgen::CertificateParams::new(Vec::new())
.expect("empty anchor SAN list should be valid");
anchor_params
.distinguished_name
.push(rcgen::DnType::CommonName, "direct trust anchor");
let anchor = anchor_params
.signed_by(
&rcgen::KeyPair::generate().expect("anchor key generation should succeed"),
&issuer,
)
.expect("issuer should sign the directly trusted certificate");
let key_info_xml = concat!(
"<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">",
"<X509Data><X509SubjectName>CN=direct trust anchor</X509SubjectName></X509Data>",
"</KeyInfo>"
);
let document = roxmltree::Document::parse(key_info_xml)
.expect("static selector KeyInfo should parse as XML");
let key_info = super::super::parse_key_info(document.root_element())
.expect("static selector KeyInfo should satisfy XMLDSig structure");
let resolver = DefaultKeyResolver::new(KeyResolverConfig {
trusted_certs: vec![anchor.der().to_vec()],
lookup_certs: vec![issuer.der().to_vec()],
..KeyResolverConfig::default()
});
let resolved = resolver
.resolve_with_policy(
Some(&key_info),
SignatureAlgorithm::EcdsaSha256,
&verification_policy_with_trust(chain_policy()),
)
.expect("an explicitly trusted selected certificate must terminate its path");
assert!(resolved.is_some());
}
#[test]
fn selector_resolved_leaf_stops_at_non_self_signed_trust_anchor() {
let external_issuer = rcgen::CertifiedIssuer::self_signed(
generated_certificate_params("external issuer", true),
rcgen::KeyPair::generate().expect("external issuer key generation should succeed"),
)
.expect("external issuer should be self-signable");
let anchor = rcgen::CertifiedIssuer::signed_by(
generated_certificate_params("non-self-signed anchor", true),
rcgen::KeyPair::generate().expect("anchor key generation should succeed"),
&external_issuer,
)
.expect("external issuer should sign the anchor");
let leaf = generated_certificate_params("anchor leaf", false)
.signed_by(
&rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
&anchor,
)
.expect("anchor should sign the leaf");
let leaf_metadata = parse_x509_certificate(leaf.der())
.expect("generated leaf should have supported metadata");
let key_info = KeyInfo {
sources: vec![KeyInfoSource::X509Data(X509DataInfo {
subject_names: vec![leaf_metadata.subject_dn],
..X509DataInfo::default()
})],
};
let resolver = DefaultKeyResolver::new(KeyResolverConfig {
trusted_certs: vec![anchor.der().to_vec()],
lookup_certs: vec![leaf.der().to_vec(), external_issuer.der().to_vec()],
..KeyResolverConfig::default()
});
let resolved = resolver
.resolve_with_policy(
Some(&key_info),
SignatureAlgorithm::EcdsaSha256,
&verification_policy_with_trust(chain_policy()),
)
.expect("path construction must stop at the configured anchor");
assert!(resolved.is_some());
}
#[test]
fn selector_resolved_leaf_does_not_anchor_itself() {
let certificate_der = certificate_der(RSA_4096_CERTIFICATE);
let resolver = DefaultKeyResolver::new(KeyResolverConfig {
lookup_certs: vec![certificate_der],
..KeyResolverConfig::default()
});
let error = super::super::VerifyContext::new()
.policy(verification_policy_with_trust(chain_policy_at(
fixture_certificate_time(),
)))
.key_resolver(&resolver)
.verify(&x509_signature_with_leaf_subject())
.expect_err("selector-resolved leaf must not trust itself");
assert!(matches!(
error,
DsigError::KeyResolution(KeyResolutionError::Chain(
super::super::X509ChainError::UntrustedRoot
))
));
}
#[test]
fn selector_resolved_leaf_uses_separate_anchor() {
let leaf = certificate_der(RSA_4096_CERTIFICATE);
let issuer = certificate_der(include_str!("../../tests/fixtures/keys/ca2cert.pem"));
let resolver = DefaultKeyResolver::new(KeyResolverConfig {
lookup_certs: vec![leaf],
trusted_certs: vec![issuer],
..KeyResolverConfig::default()
});
let result = super::super::VerifyContext::new()
.policy(verification_policy_with_trust(chain_policy_at(
fixture_certificate_time(),
)))
.key_resolver(&resolver)
.verify(&x509_signature_with_leaf_subject())
.expect("selector-resolved leaf should chain to its configured issuer");
assert_eq!(result.status, super::super::DsigStatus::Valid);
}
#[test]
fn selector_resolved_leaf_uses_lookup_intermediate() {
let mut root_params =
rcgen::CertificateParams::new(Vec::new()).expect("empty root SAN list should be valid");
root_params
.distinguished_name
.push(rcgen::DnType::CommonName, "lookup root");
root_params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
root_params.key_usages = vec![rcgen::KeyUsagePurpose::KeyCertSign];
let root = rcgen::CertifiedIssuer::self_signed(
root_params,
rcgen::KeyPair::generate().expect("root key generation should succeed"),
)
.expect("root certificate should be self-signable");
let mut intermediate_params = rcgen::CertificateParams::new(Vec::new())
.expect("empty intermediate SAN list should be valid");
intermediate_params
.distinguished_name
.push(rcgen::DnType::CommonName, "lookup intermediate");
intermediate_params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
intermediate_params.key_usages = vec![rcgen::KeyUsagePurpose::KeyCertSign];
let intermediate = rcgen::CertifiedIssuer::signed_by(
intermediate_params,
rcgen::KeyPair::generate().expect("intermediate key generation should succeed"),
&root,
)
.expect("root should sign the intermediate certificate");
let mut leaf_params =
rcgen::CertificateParams::new(Vec::new()).expect("empty leaf SAN list should be valid");
leaf_params
.distinguished_name
.push(rcgen::DnType::CommonName, "lookup leaf");
let leaf = leaf_params
.signed_by(
&rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
&intermediate,
)
.expect("intermediate should sign the leaf certificate");
let key_info_xml = concat!(
"<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">",
"<X509Data><X509SubjectName>CN=lookup leaf</X509SubjectName></X509Data>",
"</KeyInfo>"
);
let document = roxmltree::Document::parse(key_info_xml)
.expect("static selector KeyInfo should parse as XML");
let key_info = super::super::parse_key_info(document.root_element())
.expect("static selector KeyInfo should satisfy XMLDSig structure");
let resolver = DefaultKeyResolver::new(KeyResolverConfig {
lookup_certs: vec![leaf.der().to_vec(), intermediate.der().to_vec()],
trusted_certs: vec![root.der().to_vec()],
..KeyResolverConfig::default()
});
let resolved = resolver
.resolve_with_policy(
Some(&key_info),
SignatureAlgorithm::EcdsaSha256,
&verification_policy_with_trust(chain_policy()),
)
.expect("selector-resolved leaf should chain through the lookup intermediate");
assert!(resolved.is_some());
}
#[test]
fn x509_path_signatures_use_the_operation_provider() {
let root = rcgen::CertifiedIssuer::self_signed(
generated_certificate_params("provider root", true),
rcgen::KeyPair::generate().expect("root key generation should succeed"),
)
.expect("root should be self-signable");
let leaf = generated_certificate_params("provider leaf", false)
.signed_by(
&rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
&root,
)
.expect("root should sign the leaf");
let leaf_der = leaf.der().to_vec();
let leaf_metadata =
parse_x509_certificate(&leaf_der).expect("generated leaf metadata should parse");
let policy = crate::policy::VerificationPolicy {
key_trust: chain_policy(),
..crate::policy::VerificationPolicy::default()
};
let cases = [
(
KeyInfo {
sources: vec![KeyInfoSource::X509Data(x509_info(
vec![leaf_der.clone()],
0,
))],
},
Vec::new(),
),
(
KeyInfo {
sources: vec![KeyInfoSource::X509Data(X509DataInfo {
subject_names: vec![leaf_metadata.subject_dn],
..X509DataInfo::default()
})],
},
vec![leaf_der],
),
];
for (key_info, lookup_certs) in cases {
let provider = RejectSecondSha512Provider {
sha512_calls: AtomicUsize::new(0),
verification_calls: AtomicUsize::new(0),
reject_verification_call: Some(0),
rejected_verification_data: None,
};
let resolver = DefaultKeyResolver::new(KeyResolverConfig {
trusted_certs: vec![root.der().to_vec()],
lookup_certs,
..KeyResolverConfig::default()
});
let error = match resolver.resolve_with_policy_and_provider(
Some(&key_info),
SignatureAlgorithm::EcdsaSha256,
&policy,
&provider,
) {
Ok(_) => panic!("the operation provider must gate every X.509 path signature"),
Err(error) => error,
};
assert!(matches!(
error,
DsigError::KeyResolution(KeyResolutionError::Chain(
super::super::X509ChainError::UnsupportedSignatureAlgorithm { ref oid }
)) if oid == "1.2.840.10045.4.3.2"
));
assert_eq!(provider.verification_calls.load(Ordering::Relaxed), 1);
}
let provider = RejectSecondSha512Provider {
sha512_calls: AtomicUsize::new(0),
verification_calls: AtomicUsize::new(0),
reject_verification_call: Some(1),
rejected_verification_data: None,
};
let resolver = DefaultKeyResolver::new(KeyResolverConfig {
trusted_certs: vec![root.der().to_vec()],
..KeyResolverConfig::default()
});
let key_info = KeyInfo {
sources: vec![KeyInfoSource::X509Data(x509_info(
vec![leaf.der().to_vec()],
0,
))],
};
let error = match resolver.resolve_with_policy_and_provider(
Some(&key_info),
SignatureAlgorithm::EcdsaSha256,
&policy,
&provider,
) {
Ok(_) => panic!("complete-path validation must retain the operation provider"),
Err(error) => error,
};
assert!(matches!(
error,
DsigError::KeyResolution(KeyResolutionError::Chain(
super::super::X509ChainError::Provider(_)
))
));
assert_eq!(provider.verification_calls.load(Ordering::Relaxed), 2);
}
#[test]
fn embedded_leaf_uses_lookup_intermediate_with_duplicate_anchor() {
let trusted_root = rcgen::CertifiedIssuer::self_signed(
generated_certificate_params("unrelated trusted root", true),
rcgen::KeyPair::generate().expect("root key generation should succeed"),
)
.expect("root should be self-signable");
let issuer_root = rcgen::CertifiedIssuer::self_signed(
generated_certificate_params("untrusted issuer root", true),
rcgen::KeyPair::generate().expect("issuer root key generation should succeed"),
)
.expect("issuer root should be self-signable");
let intermediate = rcgen::CertifiedIssuer::signed_by(
generated_certificate_params("embedded intermediate", true),
rcgen::KeyPair::generate().expect("intermediate key generation should succeed"),
&issuer_root,
)
.expect("issuer root should sign the intermediate");
let leaf = generated_certificate_params("embedded leaf", false)
.signed_by(
&rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
&intermediate,
)
.expect("intermediate should sign the leaf");
let key_info = KeyInfo {
sources: vec![KeyInfoSource::X509Data(x509_info(
vec![leaf.der().to_vec()],
0,
))],
};
let resolver = DefaultKeyResolver::new(KeyResolverConfig {
lookup_certs: vec![intermediate.der().to_vec()],
trusted_certs: vec![trusted_root.der().to_vec(), trusted_root.der().to_vec()],
..KeyResolverConfig::default()
});
let policy = verification_policy_with_trust(chain_policy());
let error = match resolver.resolve_with_policy(
Some(&key_info),
SignatureAlgorithm::EcdsaSha256,
&policy,
) {
Ok(_) => panic!("an untrusted lookup intermediate must not become a trust anchor"),
Err(error) => error,
};
assert!(matches!(
error,
DsigError::KeyResolution(KeyResolutionError::Chain(
super::super::X509ChainError::UntrustedRoot
))
));
}
#[test]
fn selector_resolved_leaf_chooses_unique_valid_same_key_path() {
let trusted_root = rcgen::CertifiedIssuer::self_signed(
generated_certificate_params("trusted cross-sign root", true),
rcgen::KeyPair::generate().expect("trusted root key generation should succeed"),
)
.expect("trusted root should be self-signable");
let untrusted_root = rcgen::CertifiedIssuer::self_signed(
generated_certificate_params("untrusted cross-sign root", true),
rcgen::KeyPair::generate().expect("untrusted root key generation should succeed"),
)
.expect("untrusted root should be self-signable");
let shared_params = generated_certificate_params("shared cross-sign issuer", true);
let shared_key =
rcgen::KeyPair::generate().expect("shared issuer key generation should succeed");
let trusted_intermediate = shared_params
.signed_by(&shared_key, &trusted_root)
.expect("trusted root should cross-sign the shared issuer key");
let untrusted_intermediate = shared_params
.signed_by(&shared_key, &untrusted_root)
.expect("untrusted root should cross-sign the shared issuer key");
let shared_issuer = rcgen::Issuer::from_params(&shared_params, &shared_key);
let leaf = generated_certificate_params("cross-signed leaf", false)
.signed_by(
&rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
&shared_issuer,
)
.expect("shared issuer key should sign the leaf");
let leaf_metadata = parse_x509_certificate(leaf.der())
.expect("generated leaf should have supported metadata");
let key_info = KeyInfo {
sources: vec![KeyInfoSource::X509Data(X509DataInfo {
subject_names: vec![leaf_metadata.subject_dn],
..X509DataInfo::default()
})],
};
let resolver = DefaultKeyResolver::new(KeyResolverConfig {
trusted_certs: vec![trusted_root.der().to_vec()],
lookup_certs: vec![
leaf.der().to_vec(),
untrusted_intermediate.der().to_vec(),
trusted_intermediate.der().to_vec(),
untrusted_root.der().to_vec(),
],
..KeyResolverConfig::default()
});
let resolved = resolver
.resolve_with_policy(
Some(&key_info),
SignatureAlgorithm::EcdsaSha256,
&verification_policy_with_trust(chain_policy()),
)
.expect("the sole path to a configured anchor should be selected");
assert!(resolved.is_some());
}
#[test]
fn self_issued_rollover_continues_to_same_name_trusted_signer() {
let root = rcgen::CertifiedIssuer::self_signed(
generated_certificate_params("rollover authority", true),
rcgen::KeyPair::generate().expect("root key generation should succeed"),
)
.expect("root should be self-signable");
let rollover_params = generated_certificate_params("rollover authority", true);
let rollover_key =
rcgen::KeyPair::generate().expect("rollover key generation should succeed");
let rollover_certificate = rollover_params
.signed_by(&rollover_key, &root)
.expect("root should sign the same-name rollover certificate");
let rollover_issuer = rcgen::Issuer::from_params(&rollover_params, &rollover_key);
let leaf = generated_certificate_params("rollover leaf", false)
.signed_by(
&rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
&rollover_issuer,
)
.expect("rollover key should sign the leaf");
let leaf_metadata =
parse_x509_certificate(leaf.der()).expect("generated leaf metadata should parse");
let key_info = KeyInfo {
sources: vec![KeyInfoSource::X509Data(X509DataInfo {
subject_names: vec![leaf_metadata.subject_dn],
..X509DataInfo::default()
})],
};
let resolver = DefaultKeyResolver::new(KeyResolverConfig {
trusted_certs: vec![root.der().to_vec()],
lookup_certs: vec![leaf.der().to_vec(), rollover_certificate.der().to_vec()],
..KeyResolverConfig::default()
});
let resolved = resolver
.resolve_with_policy(
Some(&key_info),
SignatureAlgorithm::EcdsaSha256,
&verification_policy_with_trust(chain_policy()),
)
.expect("same-name rollover path must reach its configured signer");
assert!(resolved.is_some());
}
#[test]
fn x509_candidate_limit_counts_generated_partial_paths() {
let root = rcgen::CertifiedIssuer::self_signed(
generated_certificate_params("candidate root", true),
rcgen::KeyPair::generate().expect("root key generation should succeed"),
)
.expect("root should be self-signable");
let intermediate = rcgen::CertifiedIssuer::signed_by(
generated_certificate_params("candidate intermediate", true),
rcgen::KeyPair::generate().expect("intermediate key generation should succeed"),
&root,
)
.expect("root should sign the intermediate");
let leaf = generated_certificate_params("candidate leaf", false)
.signed_by(
&rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
&intermediate,
)
.expect("intermediate should sign the leaf");
let info = x509_info(
vec![
root.der().to_vec(),
intermediate.der().to_vec(),
leaf.der().to_vec(),
],
2,
);
assert!(matches!(
build_x509_certificate_paths_to_trusted_prefix(
&info,
2,
1,
9,
2,
crate::provider::default_provider(),
None,
),
Err(X509ChainBuildError::AmbiguousIssuer)
));
}
#[test]
fn selector_resolved_leaf_disambiguates_same_subject_issuers_by_signature() {
let mut root_params =
rcgen::CertificateParams::new(Vec::new()).expect("empty root SAN list should be valid");
root_params
.distinguished_name
.push(rcgen::DnType::CommonName, "shared-issuer root");
root_params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
root_params.key_usages = vec![rcgen::KeyUsagePurpose::KeyCertSign];
let root = rcgen::CertifiedIssuer::self_signed(
root_params,
rcgen::KeyPair::generate().expect("root key generation should succeed"),
)
.expect("root certificate should be self-signable");
let intermediate = |key: rcgen::KeyPair| {
let mut params = rcgen::CertificateParams::new(Vec::new())
.expect("empty intermediate SAN list should be valid");
params
.distinguished_name
.push(rcgen::DnType::CommonName, "renewed intermediate");
params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
params.key_usages = vec![rcgen::KeyUsagePurpose::KeyCertSign];
rcgen::CertifiedIssuer::signed_by(params, key, &root)
.expect("root should sign the intermediate certificate")
};
let unrelated_intermediate = intermediate(
rcgen::KeyPair::generate().expect("unrelated intermediate key generation should work"),
);
let signing_intermediate = intermediate(
rcgen::KeyPair::generate().expect("signing intermediate key generation should work"),
);
let mut leaf_params =
rcgen::CertificateParams::new(Vec::new()).expect("empty leaf SAN list should be valid");
leaf_params
.distinguished_name
.push(rcgen::DnType::CommonName, "same-subject leaf");
let leaf = leaf_params
.signed_by(
&rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
&signing_intermediate,
)
.expect("the selected intermediate should sign the leaf certificate");
let key_info_xml = concat!(
"<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">",
"<X509Data><X509SubjectName>CN=same-subject leaf</X509SubjectName></X509Data>",
"</KeyInfo>"
);
let document = roxmltree::Document::parse(key_info_xml)
.expect("static selector KeyInfo should parse as XML");
let key_info = super::super::parse_key_info(document.root_element())
.expect("static selector KeyInfo should satisfy XMLDSig structure");
let resolver = DefaultKeyResolver::new(KeyResolverConfig {
lookup_certs: vec![
leaf.der().to_vec(),
unrelated_intermediate.der().to_vec(),
signing_intermediate.der().to_vec(),
],
trusted_certs: vec![root.der().to_vec()],
..KeyResolverConfig::default()
});
let resolved = resolver
.resolve_with_policy(
Some(&key_info),
SignatureAlgorithm::EcdsaSha256,
&verification_policy_with_trust(chain_policy()),
)
.expect("the leaf signature should select its unique same-subject issuer");
assert!(resolved.is_some());
}
#[test]
fn x509_path_builder_skips_policy_rejected_alternatives() {
use crate::provider::X509SignatureAlgorithm;
use std::collections::HashSet;
let root = rcgen::CertifiedIssuer::self_signed(
generated_certificate_params("policy root", true),
rcgen::KeyPair::generate().expect("root key"),
)
.expect("root certificate");
let alternate_root = rcgen::CertifiedIssuer::self_signed(
generated_certificate_params("policy alternate root", true),
rcgen::KeyPair::generate_for(&rcgen::PKCS_ECDSA_P384_SHA384)
.expect("alternate root key"),
)
.expect("alternate root certificate");
let shared_params = generated_certificate_params("policy shared issuer", true);
let shared_key = rcgen::KeyPair::generate().expect("issuer key");
let rejected = shared_params
.signed_by(&shared_key, &alternate_root)
.expect("cross-signed issuer");
let shared = rcgen::CertifiedIssuer::signed_by(shared_params, shared_key, &root)
.expect("issuer certificate");
let leaf = generated_certificate_params("policy leaf", false)
.signed_by(&rcgen::KeyPair::generate().expect("leaf key"), &shared)
.expect("leaf certificate");
let rejected = rejected.der().to_vec();
let allowed = crate::policy::CertificateSignatureAlgorithms::Allowlist(HashSet::from([
X509SignatureAlgorithm::Ecdsa(super::super::DigestAlgorithm::Sha256),
]));
for rejected_first in [true, false] {
let mut alternatives = vec![shared.der().to_vec(), rejected.clone()];
if rejected_first {
alternatives.reverse();
}
let permitted_index = if rejected_first { 3 } else { 2 };
let info = x509_info(
vec![
root.der().to_vec(),
leaf.der().to_vec(),
alternatives.remove(0),
alternatives.remove(0),
alternate_root.der().to_vec(),
],
1,
);
assert_eq!(
build_x509_certificate_paths_to_trusted_prefix(
&info,
1,
1,
4,
8,
crate::provider::default_provider(),
Some(&allowed),
)
.expect("forbidden alternative must not abort search"),
vec![vec![1, permitted_index, 0]]
);
}
let info = x509_info(
vec![
root.der().to_vec(),
leaf.der().to_vec(),
rejected,
alternate_root.der().to_vec(),
],
1,
);
assert!(matches!(
build_x509_certificate_paths_to_trusted_prefix(
&info,
1,
1,
4,
8,
crate::provider::default_provider(),
Some(&allowed),
),
Err(X509ChainBuildError::Policy(_))
));
}
#[test]
fn x509_path_builder_skips_branch_local_unsupported_algorithms() {
let root = rcgen::CertifiedIssuer::self_signed(
generated_certificate_params("unsupported-edge root", true),
rcgen::KeyPair::generate().expect("root key generation should succeed"),
)
.expect("root certificate should be self-signable");
let signing_intermediate = rcgen::CertifiedIssuer::signed_by(
generated_certificate_params("shared unsupported-edge issuer", true),
rcgen::KeyPair::generate().expect("signing issuer key generation should succeed"),
&root,
)
.expect("root should sign the intermediate certificate");
let key_unsupported_intermediate = rcgen::CertifiedIssuer::signed_by(
generated_certificate_params("shared unsupported-edge issuer", true),
rcgen::KeyPair::generate().expect("unsupported issuer key generation should succeed"),
&root,
)
.expect("root should sign the alternate intermediate certificate");
let leaf = generated_certificate_params("unsupported-edge leaf", false)
.signed_by(
&rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
&signing_intermediate,
)
.expect("signing intermediate should sign the leaf");
let ordered = x509_info(
vec![
leaf.der().to_vec(),
key_unsupported_intermediate.der().to_vec(),
signing_intermediate.der().to_vec(),
root.der().to_vec(),
],
0,
);
let key_selective_provider = RejectSecondSha512Provider {
sha512_calls: AtomicUsize::new(0),
verification_calls: AtomicUsize::new(0),
reject_verification_call: Some(0),
rejected_verification_data: None,
};
assert_eq!(
super::super::parse::build_x509_certificate_chain_from(
&ordered,
0,
&key_selective_provider,
None,
)
.expect("one unsupported issuer key must not suppress a usable candidate"),
vec![0, 2, 3]
);
let anchored_same_edge = x509_info(
vec![
root.der().to_vec(),
leaf.der().to_vec(),
key_unsupported_intermediate.der().to_vec(),
signing_intermediate.der().to_vec(),
],
1,
);
let first_candidate_unsupported = RejectSecondSha512Provider {
sha512_calls: AtomicUsize::new(0),
verification_calls: AtomicUsize::new(0),
reject_verification_call: Some(0),
rejected_verification_data: None,
};
assert_eq!(
build_x509_certificate_paths_to_trusted_prefix(
&anchored_same_edge,
1,
1,
4,
8,
&first_candidate_unsupported,
None,
)
.expect("a later same-DN issuer must survive an earlier provider capability miss"),
vec![vec![1, 3, 0]]
);
let mut unsupported_intermediate = signing_intermediate.der().to_vec();
let ecdsa_sha256_oid = [0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02];
let offsets = unsupported_intermediate
.windows(ecdsa_sha256_oid.len())
.enumerate()
.filter_map(|(offset, window)| (window == ecdsa_sha256_oid).then_some(offset))
.collect::<Vec<_>>();
assert_eq!(
offsets.len(),
2,
"certificate must repeat its signature OID"
);
for offset in offsets {
unsupported_intermediate[offset + ecdsa_sha256_oid.len() - 1] = 0x05;
}
let anchored = x509_info(
vec![
root.der().to_vec(),
leaf.der().to_vec(),
signing_intermediate.der().to_vec(),
unsupported_intermediate,
],
1,
);
assert_eq!(
build_x509_certificate_paths_to_trusted_prefix(
&anchored,
1,
1,
4,
8,
crate::provider::default_provider(),
None,
)
.expect("a branch-local provider gap must not abort path enumeration"),
vec![vec![1, 2, 0]]
);
let unsupported_only = x509_info(
vec![
root.der().to_vec(),
leaf.der().to_vec(),
anchored.certificates[3].clone(),
],
1,
);
assert!(matches!(
build_x509_certificate_paths_to_trusted_prefix(
&unsupported_only,
1,
1,
4,
8,
crate::provider::default_provider(),
None,
),
Err(X509ChainBuildError::UnsupportedSignatureAlgorithm { ref oid })
if oid == "1.2.840.10045.4.3.5"
));
}
#[test]
fn selector_resolved_certificate_preserves_supplied_crls() {
let selector = "<KeyInfo><X509Data><X509SubjectName>CN=Test Key rsa-2048,O=XML Security Library (http://www.aleksey.com/xmlsec),ST=California,C=US</X509SubjectName><X509CRL>CRL_PLACEHOLDER</X509CRL></X509Data></KeyInfo>";
let crl = crl_der(include_str!(
"../../tests/fixtures/keys/rsa/rsa-2048-cert-revoked-crl.pem"
));
let (_, parsed_crl) =
x509_parser::revocation_list::CertificateRevocationList::from_der(&crl)
.expect("tracked CRL must parse");
let crl_signed_data = parsed_crl.tbs_cert_list.as_ref().to_vec();
let xml = replace_unprefixed_key_info(
RSA_KEY_VALUE_SIGNATURE,
&selector.replace("CRL_PLACEHOLDER", &STANDARD.encode(&crl)),
);
let resolver = DefaultKeyResolver::new(KeyResolverConfig {
lookup_certs: vec![certificate_der(include_str!(
"../../tests/fixtures/keys/rsa/rsa-2048-cert.pem"
))],
trusted_certs: vec![
certificate_der(include_str!("../../tests/fixtures/keys/ca2cert.pem")),
certificate_der(include_str!("../../tests/fixtures/keys/cacert.pem")),
],
..KeyResolverConfig::default()
});
let policy = verification_policy_with_trust(crate::policy::KeyTrustPolicy {
check_crls: true,
max_x509_chain_depth: 3,
..chain_policy_at(
SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(1_773_964_800),
)
});
let error = super::super::VerifyContext::new()
.policy(policy.clone())
.key_resolver(&resolver)
.verify(&xml)
.expect_err("selector lookup must retain and enforce the supplied CRL");
assert!(matches!(
error,
DsigError::KeyResolution(KeyResolutionError::Chain(
super::super::X509ChainError::InvalidKeyUsage {
position: 1,
required: "cRLSign"
}
))
));
let provider = RejectSecondSha512Provider {
sha512_calls: AtomicUsize::new(0),
verification_calls: AtomicUsize::new(0),
reject_verification_call: None,
rejected_verification_data: Some(crl_signed_data),
};
let error = super::super::VerifyContext::new()
.policy(policy)
.key_resolver(&resolver)
.provider(&provider)
.verify(&xml)
.expect_err("CRL authentication must retain the operation provider");
assert!(matches!(
error,
DsigError::KeyResolution(KeyResolutionError::Chain(
super::super::X509ChainError::Provider(_)
))
));
}
#[test]
fn resolver_rejects_revoked_leaf_with_authorized_crl_issuer() {
let (mut info, root) = authorized_revoked_chain();
let issuer = info.certificates[1].clone();
info.certificates.truncate(1);
info.parsed_certificates.truncate(1);
let key_info = KeyInfo {
sources: vec![KeyInfoSource::X509Data(info)],
};
let resolver = DefaultKeyResolver::new(KeyResolverConfig {
lookup_certs: vec![issuer],
trusted_certs: vec![root],
..KeyResolverConfig::default()
});
let policy = verification_policy_with_trust(crate::policy::KeyTrustPolicy {
check_crls: true,
max_x509_chain_depth: 3,
..chain_policy_at(
SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(1_773_964_800),
)
});
let error = match resolver.resolve_with_policy(
Some(&key_info),
SignatureAlgorithm::EcdsaSha256,
&policy,
) {
Ok(_) => panic!("an authenticated CRL must reject its revoked leaf"),
Err(error) => error,
};
assert!(matches!(
error,
DsigError::KeyResolution(KeyResolutionError::Chain(
super::super::X509ChainError::Revoked(0)
))
));
}
#[test]
fn resolves_each_x509_selector_from_configured_certificates() {
let selectors = [
"<X509SubjectName>CN=Test Key rsa-2048,O=XML Security Library (http://www.aleksey.com/xmlsec),ST=California,C=US</X509SubjectName>",
"<X509SubjectName>CN= test key rsa-2048 ,O=xml security library (HTTP://WWW.ALEKSEY.COM/XMLSEC),ST=california,C=us</X509SubjectName>",
"<X509IssuerSerial><X509IssuerName>Email=xmlsec@aleksey.com,CN=Aleksey Sanin,OU=Second level CA,O=XML Security Library (http://www.aleksey.com/xmlsec),ST=California,C=US</X509IssuerName><X509SerialNumber>680572598617295163017172295025714171905498632019</X509SerialNumber></X509IssuerSerial>",
"<X509SKI>bcOXN/nsVl8GatRbcKrPbzIbw0Y=</X509SKI>",
];
let configured_certificate = certificate_der(include_str!(
"../../tests/fixtures/keys/rsa/rsa-2048-cert.pem"
));
for selector in selectors {
let key_info = format!("<KeyInfo><X509Data>{selector}</X509Data></KeyInfo>");
let xml = replace_unprefixed_key_info(RSA_KEY_VALUE_SIGNATURE, &key_info);
let resolver = DefaultKeyResolver::new(KeyResolverConfig {
lookup_certs: vec![configured_certificate.clone()],
..KeyResolverConfig::default()
});
let result = super::super::VerifyContext::new()
.policy(cryptographic_policy())
.key_resolver(&resolver)
.verify(&xml)
.expect("X509 selector should resolve configured certificate");
assert_eq!(result.status, super::super::DsigStatus::Valid);
}
}
#[test]
fn resolves_configured_chain_selectors_across_certificates() {
let key_info = r#"<KeyInfo><X509Data><X509SubjectName>CN=Test Key rsa-2048,O=XML Security Library (http://www.aleksey.com/xmlsec),ST=California,C=US</X509SubjectName><X509SKI>0X0XrEVCio75sBcl1TxymJ2IOiU=</X509SKI></X509Data></KeyInfo>"#;
let xml = replace_unprefixed_key_info(RSA_KEY_VALUE_SIGNATURE, key_info);
let resolver = DefaultKeyResolver::new(KeyResolverConfig {
lookup_certs: vec![
certificate_der(include_str!(
"../../tests/fixtures/keys/rsa/rsa-2048-cert.pem"
)),
certificate_der(include_str!("../../tests/fixtures/keys/ca2cert.pem")),
],
..KeyResolverConfig::default()
});
let result = super::super::VerifyContext::new()
.policy(cryptographic_policy())
.key_resolver(&resolver)
.verify(&xml)
.expect("selectors across one configured chain should resolve its leaf");
assert_eq!(result.status, super::super::DsigStatus::Valid);
}
#[test]
fn selectors_must_all_match_the_selected_certificate_path() {
let signing_certificate = certificate_der(include_str!(
"../../tests/fixtures/keys/rsa/rsa-2048-cert.pem"
));
let issuer_certificate =
certificate_der(include_str!("../../tests/fixtures/keys/ca2cert.pem"));
let unrelated = generated_certificate_params("unrelated selector certificate", false)
.self_signed(
&rcgen::KeyPair::generate().expect("unrelated key generation should succeed"),
)
.expect("unrelated certificate should be self-signable")
.der()
.to_vec();
let digest = crate::provider::default_provider()
.digest(super::super::DigestAlgorithm::Sha256, &unrelated)
.expect("SHA-256 selector digest must be available");
let key_info_xml = format!(
"<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\" xmlns:dsig11=\"http://www.w3.org/2009/xmldsig11#\"><X509Data><X509SubjectName>CN=Test Key rsa-2048,O=XML Security Library (http://www.aleksey.com/xmlsec),ST=California,C=US</X509SubjectName><dsig11:X509Digest Algorithm=\"http://www.w3.org/2001/04/xmlenc#sha256\">{}</dsig11:X509Digest></X509Data></KeyInfo>",
STANDARD.encode(digest)
);
let document = roxmltree::Document::parse(&key_info_xml)
.expect("generated selector KeyInfo must be XML");
let key_info = super::super::parse_key_info(document.root_element())
.expect("generated selector KeyInfo must be structurally valid");
let resolver = DefaultKeyResolver::new(KeyResolverConfig {
lookup_certs: vec![signing_certificate, issuer_certificate, unrelated],
..KeyResolverConfig::default()
});
assert!(
resolver
.resolve(Some(&key_info), SignatureAlgorithm::RsaSha256)
.expect("disjoint selector matches are a key miss")
.is_none()
);
}
#[test]
fn unmatched_x509_selector_does_not_resolve() {
let key_info = "<KeyInfo><X509Data><X509SubjectName>CN=not-the-signer</X509SubjectName></X509Data></KeyInfo>";
let xml = replace_unprefixed_key_info(RSA_KEY_VALUE_SIGNATURE, key_info);
let resolver = DefaultKeyResolver::new(KeyResolverConfig {
lookup_certs: vec![certificate_der(include_str!(
"../../tests/fixtures/keys/rsa/rsa-2048-cert.pem"
))],
..KeyResolverConfig::default()
});
let result = super::super::VerifyContext::new()
.key_resolver(&resolver)
.verify(&xml)
.expect("an unmatched selector is a key miss, not a parser failure");
assert!(matches!(
result.status,
super::super::DsigStatus::Invalid(super::super::FailureReason::KeyNotFound)
));
}
#[test]
fn overlapping_trusted_and_lookup_certificate_preserves_trust() {
let certificate = certificate_der(RSA_4096_CERTIFICATE);
let resolver = DefaultKeyResolver::new(KeyResolverConfig {
trusted_certs: vec![certificate.clone()],
lookup_certs: vec![certificate],
..KeyResolverConfig::default()
});
let result = super::super::VerifyContext::new()
.policy(verification_policy_with_trust(chain_policy_at(
fixture_certificate_time(),
)))
.key_resolver(&resolver)
.verify(&x509_signature_with_leaf_subject())
.expect("trusted/lookup overlap must resolve as one trusted candidate");
assert_eq!(result.status, super::super::DsigStatus::Valid);
let super::super::KeyTrustEvidence::ValidatedX509(evidence) = result.key_trust else {
panic!("successful path validation must retain typed evidence");
};
assert_eq!(evidence.verification_time(), fixture_certificate_time());
assert!(!evidence.revocation_checked());
assert_eq!(evidence.certificate_fingerprints().len(), 1);
assert_eq!(
evidence.anchor_fingerprint(),
&evidence.certificate_fingerprints()[0]
);
let document = crate::XmlDocument::parse(x509_signature_with_leaf_subject())
.expect("signed certificate selector fixture must parse");
let report = super::super::VerifyContext::new()
.policy(verification_policy_with_trust(chain_policy_at(
fixture_certificate_time(),
)))
.key_resolver(&resolver)
.verify_all(&document)
.expect("batch verification must preserve the validated path");
assert!(
report
.accepted(&document)
.expect("current document acceptance")
);
assert_eq!(
report.signatures()[0]
.result()
.as_ref()
.expect("the individually processed Signature must verify")
.key_trust,
super::super::KeyTrustEvidence::ValidatedX509(evidence)
);
}
#[test]
fn distinct_x509_selector_matches_remain_ambiguous() {
let certificate = || {
generated_certificate_params("ambiguous selector", false)
.self_signed(
&rcgen::KeyPair::generate().expect("test key generation should succeed"),
)
.expect("test certificate should be self-signable")
.der()
.to_vec()
};
let xml = replace_unprefixed_key_info(
X509_DIGEST_SIGNATURE,
"<KeyInfo><X509Data><X509SubjectName>CN=ambiguous selector</X509SubjectName></X509Data></KeyInfo>",
);
let resolver = DefaultKeyResolver::new(KeyResolverConfig {
lookup_certs: vec![certificate(), certificate()],
..KeyResolverConfig::default()
});
let error = super::super::VerifyContext::new()
.key_resolver(&resolver)
.verify(&xml)
.expect_err("distinct selector matches must fail closed");
assert!(matches!(
error,
DsigError::KeyResolution(KeyResolutionError::AmbiguousCertificate)
));
}
#[test]
fn unsupported_x509_digest_selector_fails_closed() {
let key_info = "<KeyInfo xmlns:dsig11=\"http://www.w3.org/2009/xmldsig11#\"><X509Data><dsig11:X509Digest Algorithm=\"urn:unsupported\">AQ==</dsig11:X509Digest></X509Data></KeyInfo>";
let xml = replace_unprefixed_key_info(RSA_KEY_VALUE_SIGNATURE, key_info);
let resolver = DefaultKeyResolver::new(KeyResolverConfig {
lookup_certs: vec![certificate_der(include_str!(
"../../tests/fixtures/keys/rsa/rsa-2048-cert.pem"
))],
..KeyResolverConfig::default()
});
let error = super::super::VerifyContext::new()
.key_resolver(&resolver)
.verify(&xml)
.expect_err("unsupported X509Digest algorithm must fail closed");
assert!(matches!(
error,
DsigError::KeyResolution(KeyResolutionError::UnsupportedDigestAlgorithm(uri))
if uri == "urn:unsupported"
));
}
#[test]
fn x509_digest_selector_uses_operation_provider() {
let resolver = DefaultKeyResolver::new(KeyResolverConfig {
lookup_certs: vec![certificate_der(RSA_4096_CERTIFICATE)],
trusted_certs: vec![
certificate_der(include_str!("../../tests/fixtures/keys/ca2cert.pem")),
certificate_der(include_str!("../../tests/fixtures/keys/cacert.pem")),
],
..KeyResolverConfig::default()
});
let provider = RejectSecondSha512Provider {
sha512_calls: AtomicUsize::new(0),
verification_calls: AtomicUsize::new(0),
reject_verification_call: None,
rejected_verification_data: None,
};
let error = super::super::VerifyContext::new()
.key_resolver(&resolver)
.provider(&provider)
.verify(X509_DIGEST_SIGNATURE)
.expect_err("X509Digest selection must use the operation provider");
assert!(
matches!(
error,
DsigError::Provider(crate::provider::ProviderError::Unsupported {
operation: crate::provider::ProviderOperation::Digest,
algorithm: Some(ref uri),
}) if uri == super::super::DigestAlgorithm::Sha512.uri()
),
"unexpected error: {error:?}"
);
}
#[test]
fn resolves_named_key_end_to_end() {
let xml = replace_key_info(
SIGNED_SAML,
"<ds:KeyInfo><ds:KeyName>idp-signing</ds:KeyName></ds:KeyInfo>",
);
let mut config = KeyResolverConfig::default();
config.named_keys.insert(
"idp-signing".into(),
VerificationKey {
algorithm: SignatureAlgorithm::EcdsaSha256,
public_key_bytes: public_key_der(SAML_PUBLIC_KEY),
certificate_der: None,
name: Some("idp-signing".into()),
},
);
let resolver = DefaultKeyResolver::new(config);
let result = super::super::VerifyContext::new()
.key_resolver(&resolver)
.verify(&xml)
.expect("named key should resolve");
assert_eq!(result.status, super::super::DsigStatus::Valid);
}
#[test]
fn resolves_der_encoded_key_end_to_end() {
let encoded = STANDARD.encode(public_key_der(SAML_PUBLIC_KEY));
let xml = replace_key_info(
SIGNED_SAML,
&format!(
"<ds:KeyInfo><dsig11:DEREncodedKeyValue xmlns:dsig11=\"http://www.w3.org/2009/xmldsig11#\">{encoded}</dsig11:DEREncodedKeyValue></ds:KeyInfo>"
),
);
let resolver = DefaultKeyResolver::default();
let result = super::super::VerifyContext::new()
.policy(cryptographic_policy())
.key_resolver(&resolver)
.verify(&xml)
.expect("DER key should resolve");
assert_eq!(result.status, super::super::DsigStatus::Valid);
}
#[test]
fn resolves_rsa_key_value_end_to_end() {
let public_key = rsa::RsaPublicKey::from_public_key_pem(RSA_PUBLIC_KEY)
.expect("fixture must contain an RSA public key");
let (modulus, exponent) = rsa_key_value_parts(&public_key);
let key_info = format!(
"<KeyInfo><KeyValue><RSAKeyValue><Modulus>{}</Modulus><Exponent>{}</Exponent></RSAKeyValue></KeyValue></KeyInfo>",
modulus, exponent,
);
let xml = replace_unprefixed_key_info(RSA_KEY_VALUE_SIGNATURE, &key_info);
let resolver = DefaultKeyResolver::default();
let result = super::super::VerifyContext::new()
.policy(cryptographic_policy())
.key_resolver(&resolver)
.verify(&xml)
.expect("RSAKeyValue should resolve");
assert_eq!(result.status, super::super::DsigStatus::Valid);
}
#[test]
fn rsa_key_value_rejects_legacy_weak_modulus() {
let resolver = DefaultKeyResolver::default();
let error = super::super::VerifyContext::new()
.key_resolver(&resolver)
.verify(LEGACY_RSA_KEY_VALUE_SIGNATURE)
.expect_err("context policy must override permissive resolver defaults");
assert!(matches!(
error,
DsigError::Policy(crate::policy::PolicyViolation::Algorithm {
operation: "verification",
..
})
));
}
#[test]
fn operation_policy_rejects_disabled_embedded_key_source() {
let key_info = KeyInfo {
sources: vec![KeyInfoSource::KeyValue(KeyValueInfo::Rsa {
modulus: vec![0x80; 256],
exponent: vec![1, 0, 1],
})],
};
let mut policy = crate::policy::VerificationPolicy::default();
policy.key_sources.key_value = false;
let error = match DefaultKeyResolver::default().resolve_with_policy(
Some(&key_info),
SignatureAlgorithm::RsaSha256,
&policy,
) {
Ok(_) => panic!("disabled KeyValue must fail before key construction"),
Err(error) => error,
};
assert!(matches!(
error,
DsigError::Policy(crate::policy::PolicyViolation::KeyTrust {
reason: "KeyValue key sources are disabled"
})
));
}
#[test]
fn operation_policy_preflights_every_key_info_source_before_resolution() {
let mut config = KeyResolverConfig::default();
config.named_keys.insert(
"idp-signing".into(),
VerificationKey {
algorithm: SignatureAlgorithm::EcdsaSha256,
public_key_bytes: public_key_der(SAML_PUBLIC_KEY),
certificate_der: None,
name: Some("idp-signing".into()),
},
);
let resolver = DefaultKeyResolver::new(config);
let mut policy = crate::policy::VerificationPolicy::default();
policy.key_sources.x509_data = false;
for sources in [
vec![
KeyInfoSource::KeyName("idp-signing".into()),
KeyInfoSource::X509Data(X509DataInfo::default()),
],
vec![
KeyInfoSource::X509Data(X509DataInfo::default()),
KeyInfoSource::KeyName("idp-signing".into()),
],
] {
let error = match resolver.resolve_with_policy(
Some(&KeyInfo { sources }),
SignatureAlgorithm::EcdsaSha256,
&policy,
) {
Ok(_) => panic!("source order must not hide disabled X509Data"),
Err(error) => error,
};
assert!(matches!(
error,
DsigError::Policy(crate::policy::PolicyViolation::KeyTrust {
reason: "X509Data key sources are disabled"
})
));
}
}
#[test]
fn operation_policy_bounds_ordered_key_info_candidates() {
let mut config = KeyResolverConfig::default();
config.named_keys.insert(
"idp-signing".into(),
VerificationKey {
algorithm: SignatureAlgorithm::EcdsaSha256,
public_key_bytes: public_key_der(SAML_PUBLIC_KEY),
certificate_der: None,
name: Some("idp-signing".into()),
},
);
let resolver = DefaultKeyResolver::new(config);
let key_info = KeyInfo {
sources: vec![
KeyInfoSource::KeyValue(KeyValueInfo::Ec {
curve_oid: "1.3.132.0.35".into(),
public_key: vec![4],
}),
KeyInfoSource::KeyName("idp-signing".into()),
],
};
for maximum in [0, 1] {
let mut policy = crate::policy::VerificationPolicy::default();
policy.resources.max_key_candidates = maximum;
let error = match resolver.resolve_with_policy(
Some(&key_info),
SignatureAlgorithm::EcdsaSha256,
&policy,
) {
Ok(_) => panic!("candidate ceiling {maximum} must stop resolution"),
Err(error) => error,
};
assert!(matches!(
error,
DsigError::Policy(crate::policy::PolicyViolation::ResourceLimit {
resource: crate::policy::resource_name::KEY_CANDIDATES,
maximum: observed,
actual,
}) if observed == maximum && actual == maximum + 1
));
}
let mut policy = crate::policy::VerificationPolicy::default();
policy.resources.max_key_candidates = 2;
assert!(
resolver
.resolve_with_policy(Some(&key_info), SignatureAlgorithm::EcdsaSha256, &policy,)
.expect("two allowed attempts must reach the named key")
.is_some()
);
}
#[test]
fn operation_policy_bounds_configured_x509_selector_candidates() {
let resolver = DefaultKeyResolver::new(KeyResolverConfig {
lookup_certs: vec![
certificate_der(include_str!("../../tests/fixtures/keys/ca2cert.pem")),
certificate_der(RSA_4096_CERTIFICATE),
],
..KeyResolverConfig::default()
});
let mut policy = crate::policy::VerificationPolicy::default();
policy.resources.max_key_candidates = 1;
let error = super::super::VerifyContext::new()
.policy(policy)
.key_resolver(&resolver)
.verify(&x509_signature_with_leaf_subject())
.expect_err("the second configured certificate must exceed the candidate budget");
assert!(matches!(
error,
DsigError::Policy(crate::policy::PolicyViolation::ResourceLimit {
resource: crate::policy::resource_name::KEY_CANDIDATES,
maximum: 1,
actual: 2,
})
));
}
#[test]
fn configured_crls_are_bounded_before_der_parsing() {
let key_info = KeyInfo {
sources: vec![KeyInfoSource::X509Data(X509DataInfo {
subject_names: vec!["CN=selected".into()],
..X509DataInfo::default()
})],
};
let mut policy = crate::policy::VerificationPolicy::default();
policy.key_trust.check_crls = true;
policy.key_trust.verify_x509_chains = true;
policy.resources.max_external_resource_bytes = 4;
let resolver = DefaultKeyResolver::new(KeyResolverConfig {
crls: vec![vec![0; 5]],
..KeyResolverConfig::default()
});
let error = resolver
.resolve_with_policy(Some(&key_info), SignatureAlgorithm::RsaSha256, &policy)
.err()
.expect("oversized CRL must fail before DER parsing");
assert!(
matches!(
error,
DsigError::Policy(crate::policy::PolicyViolation::ResourceLimit {
resource: crate::policy::resource_name::EXTERNAL_RESOURCE_BYTES,
..
})
),
"{error:?}"
);
policy.resources.max_external_resource_bytes = 4;
policy.resources.max_external_resource_total_bytes = 7;
let resolver = DefaultKeyResolver::new(KeyResolverConfig {
crls: vec![vec![0; 4], vec![0; 4]],
..KeyResolverConfig::default()
});
let error = resolver
.resolve_with_policy(Some(&key_info), SignatureAlgorithm::RsaSha256, &policy)
.err()
.expect("aggregate CRL bytes must fail before DER parsing");
assert!(matches!(
error,
DsigError::Policy(crate::policy::PolicyViolation::ResourceLimit {
resource: crate::policy::resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES,
..
})
));
policy.resources.max_external_resource_total_bytes = 8;
policy.resources.max_key_candidates = 1;
let error = resolver
.resolve_with_policy(Some(&key_info), SignatureAlgorithm::RsaSha256, &policy)
.err()
.expect("CRL candidate count must fail before DER parsing");
assert!(matches!(
error,
DsigError::Policy(crate::policy::PolicyViolation::ResourceLimit {
resource: crate::policy::resource_name::KEY_CANDIDATES,
..
})
));
}
#[test]
fn configured_certificates_and_crls_share_external_byte_budget() {
let key_info = KeyInfo {
sources: vec![KeyInfoSource::X509Data(X509DataInfo {
subject_names: vec!["CN=selected".into()],
..X509DataInfo::default()
})],
};
let mut policy = crate::policy::VerificationPolicy::default();
policy.key_trust.check_crls = true;
policy.key_trust.verify_x509_chains = true;
policy.resources.max_external_resource_bytes = 8;
policy.resources.max_external_resource_total_bytes = 12;
let resolver = DefaultKeyResolver::new(KeyResolverConfig {
trusted_certs: vec![vec![0; 8]],
crls: vec![vec![0; 8]],
..KeyResolverConfig::default()
});
let error = resolver
.resolve_with_policy(Some(&key_info), SignatureAlgorithm::RsaSha256, &policy)
.err()
.expect("combined external material exceeds the operation limit");
assert!(matches!(
error,
DsigError::Policy(crate::policy::PolicyViolation::ResourceLimit {
resource: crate::policy::resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES,
..
})
));
}
#[test]
fn embedded_and_configured_x509_share_one_byte_budget() {
let mut info = KeyInfo {
sources: vec![KeyInfoSource::X509Data(X509DataInfo {
certificates: vec![vec![0; 8]],
certificate_chain: vec![0],
crls: vec![vec![0; 2]],
..X509DataInfo::default()
})],
};
let resolver = DefaultKeyResolver::new(KeyResolverConfig {
crls: vec![vec![0; 8]],
..KeyResolverConfig::default()
});
let mut policy = crate::policy::VerificationPolicy::default();
policy.key_trust.check_crls = true;
policy.key_trust.verify_x509_chains = true;
policy.resources.max_external_resource_total_bytes = 17;
let error = resolver
.resolve_with_policy(Some(&info), SignatureAlgorithm::RsaSha256, &policy)
.err()
.expect("combined material must be rejected");
assert!(
matches!(
error,
DsigError::Policy(crate::policy::PolicyViolation::ResourceLimit {
resource: crate::policy::resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES,
actual: 18,
..
})
),
"{error:?}"
);
policy.resources.max_external_resource_total_bytes = 18;
let error = resolver
.resolve_with_policy(Some(&info), SignatureAlgorithm::RsaSha256, &policy)
.err()
.expect("exact byte allowance reaches DER parsing");
assert!(
matches!(
error,
DsigError::KeyResolution(KeyResolutionError::InvalidCertificate)
),
"{error:?}"
);
info.sources.insert(
0,
KeyInfoSource::X509Data(X509DataInfo {
subject_names: vec!["CN=absent".into()],
..X509DataInfo::default()
}),
);
policy.resources.max_external_resource_total_bytes = 17;
policy.resources.max_key_candidates = 2;
assert!(matches!(
resolver.resolve_with_policy(Some(&info), SignatureAlgorithm::RsaSha256, &policy),
Err(DsigError::Policy(
crate::policy::PolicyViolation::ResourceLimit {
resource: crate::policy::resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES,
actual: 18,
..
}
))
));
}
#[test]
fn direct_certificate_does_not_charge_unused_configured_store() {
let certificate = certificate_der(RSA_4096_CERTIFICATE);
let key_info = KeyInfo {
sources: vec![KeyInfoSource::X509Data(X509DataInfo {
certificates: vec![certificate],
certificate_chain: vec![0],
subject_names: vec!["CN=unused-selector".into()],
..X509DataInfo::default()
})],
};
let resolver = DefaultKeyResolver::new(KeyResolverConfig {
lookup_certs: vec![vec![0; 4096]],
trusted_certs: vec![vec![0; 4096]],
..KeyResolverConfig::default()
});
let mut policy = crate::policy::VerificationPolicy::default();
policy.resources.max_external_resource_bytes = 1024;
policy.resources.max_external_resource_total_bytes = 1024;
assert!(
resolver
.resolve_with_policy(Some(&key_info), SignatureAlgorithm::RsaSha256, &policy)
.expect("unused configured certificates are not charged")
.is_some()
);
}
#[test]
fn operation_policy_bounds_embedded_x509_certificate_candidates() {
let key_info = KeyInfo {
sources: vec![KeyInfoSource::X509Data(X509DataInfo {
certificates: vec![
certificate_der(RSA_4096_CERTIFICATE),
certificate_der(include_str!("../../tests/fixtures/keys/ca2cert.pem")),
],
certificate_chain: vec![0],
..X509DataInfo::default()
})],
};
let mut policy = crate::policy::VerificationPolicy::default();
policy.resources.max_key_candidates = 1;
let error = match DefaultKeyResolver::default().resolve_with_policy(
Some(&key_info),
SignatureAlgorithm::RsaSha256,
&policy,
) {
Ok(_) => panic!("the second embedded certificate must exceed the candidate budget"),
Err(error) => error,
};
assert!(matches!(
error,
DsigError::Policy(crate::policy::PolicyViolation::ResourceLimit {
resource: crate::policy::resource_name::KEY_CANDIDATES,
maximum: 1,
actual: 2,
})
));
}
#[test]
fn operation_policy_charges_duplicate_configured_x509_candidates() {
let certificate = certificate_der(RSA_4096_CERTIFICATE);
let resolver = DefaultKeyResolver::new(KeyResolverConfig {
lookup_certs: vec![certificate.clone(), certificate],
..KeyResolverConfig::default()
});
let mut policy = crate::policy::VerificationPolicy::default();
policy.resources.max_key_candidates = 1;
let error = super::super::VerifyContext::new()
.policy(policy)
.key_resolver(&resolver)
.verify(&x509_signature_with_leaf_subject())
.expect_err("the duplicate configured entry must consume candidate work");
assert!(matches!(
error,
DsigError::Policy(crate::policy::PolicyViolation::ResourceLimit {
resource: crate::policy::resource_name::KEY_CANDIDATES,
maximum: 1,
actual: 2,
})
));
}
#[test]
fn operation_policy_charges_duplicate_embedded_x509_candidates() {
let certificate = certificate_der(RSA_4096_CERTIFICATE);
let key_info = KeyInfo {
sources: vec![KeyInfoSource::X509Data(X509DataInfo {
certificates: vec![certificate.clone(), certificate],
certificate_chain: vec![0],
..X509DataInfo::default()
})],
};
let mut policy = crate::policy::VerificationPolicy::default();
policy.resources.max_key_candidates = 1;
let error = match DefaultKeyResolver::default().resolve_with_policy(
Some(&key_info),
SignatureAlgorithm::RsaSha256,
&policy,
) {
Ok(_) => panic!("the duplicate embedded entry must consume candidate work"),
Err(error) => error,
};
assert!(matches!(
error,
DsigError::Policy(crate::policy::PolicyViolation::ResourceLimit {
resource: crate::policy::resource_name::KEY_CANDIDATES,
maximum: 1,
actual: 2,
})
));
}
#[test]
fn policy_aware_resolver_rejects_resources_above_hard_ceiling() {
let mut policy = crate::policy::VerificationPolicy::default();
policy.resources.max_key_candidates = usize::MAX;
let error = match DefaultKeyResolver::default().resolve_with_policy(
None,
SignatureAlgorithm::RsaSha256,
&policy,
) {
Ok(_) => panic!("invalid resource policy must fail before key resolution"),
Err(error) => error,
};
assert!(matches!(
error,
DsigError::Policy(crate::policy::PolicyViolation::ResourceLimit {
resource: crate::policy::resource_name::KEY_CANDIDATES,
actual: usize::MAX,
..
})
));
}
#[test]
fn embedded_x509_digest_selection_uses_operation_provider() {
let certificate = certificate_der(RSA_4096_CERTIFICATE);
let digest =
super::super::compute_digest(super::super::DigestAlgorithm::Sha512, &certificate);
let xml = format!(
"<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\"><X509Data><X509Certificate>{}</X509Certificate><X509Digest xmlns=\"http://www.w3.org/2009/xmldsig11#\" Algorithm=\"{}\">{}</X509Digest></X509Data></KeyInfo>",
STANDARD.encode(&certificate),
super::super::DigestAlgorithm::Sha512.uri(),
STANDARD.encode(digest),
);
let document = roxmltree::Document::parse(&xml).expect("generated KeyInfo must be XML");
let provider = RejectSecondSha512Provider {
sha512_calls: AtomicUsize::new(1),
verification_calls: AtomicUsize::new(0),
reject_verification_call: None,
rejected_verification_data: None,
};
let error =
super::super::parse::parse_key_info_with_provider(document.root_element(), &provider)
.expect_err("embedded X509Digest selection must use the operation provider");
assert!(
matches!(
error,
ParseError::Provider(crate::provider::ProviderError::Unsupported {
operation: crate::provider::ProviderOperation::Digest,
algorithm: Some(ref uri),
}) if uri == super::super::DigestAlgorithm::Sha512.uri()
),
"unexpected error: {error:?}"
);
}
#[cfg(feature = "legacy-algorithms")]
#[test]
fn x509_digest_selectors_obey_operation_policy() {
for algorithm in [
super::super::DigestAlgorithm::Md5,
super::super::DigestAlgorithm::Ripemd160,
] {
let certificate = certificate_der(RSA_4096_CERTIFICATE);
let digest = super::super::compute_digest(algorithm, &certificate);
let resolver = DefaultKeyResolver::new(KeyResolverConfig {
lookup_certs: vec![certificate.clone()],
trusted_certs: vec![
certificate.clone(),
certificate_der(include_str!("../../tests/fixtures/keys/ca2cert.pem")),
certificate_der(include_str!("../../tests/fixtures/keys/cacert.pem")),
],
..KeyResolverConfig::default()
});
for embedded in [false, true] {
let cert_xml = if embedded {
format!(
"<X509Certificate>{}</X509Certificate>",
STANDARD.encode(&certificate)
)
} else {
String::new()
};
let xml = replace_unprefixed_key_info(
X509_DIGEST_SIGNATURE,
&format!(
"<KeyInfo><X509Data>{cert_xml}<X509Digest xmlns=\"http://www.w3.org/2009/xmldsig11#\" Algorithm=\"{}\">{}</X509Digest></X509Data></KeyInfo>",
algorithm.uri(),
STANDARD.encode(&digest)
),
);
let provider = RejectSecondSha512Provider {
sha512_calls: AtomicUsize::new(0),
verification_calls: AtomicUsize::new(0),
reject_verification_call: None,
rejected_verification_data: None,
};
assert!(
matches!(super::super::VerifyContext::new().key_resolver(&resolver).provider(&provider).verify(&xml),
Err(DsigError::Policy(crate::policy::PolicyViolation::Algorithm { algorithm: uri, .. })) if uri == algorithm.uri())
);
assert_eq!(provider.sha512_calls.load(Ordering::SeqCst), 0);
assert_eq!(provider.verification_calls.load(Ordering::SeqCst), 0);
let info = KeyInfo {
sources: vec![KeyInfoSource::X509Data(X509DataInfo {
digests: vec![(algorithm.uri().into(), digest.clone())],
..X509DataInfo::default()
})],
};
assert!(matches!(
resolver.resolve_with_policy(
Some(&info),
SignatureAlgorithm::RsaSha512,
&crate::policy::VerificationPolicy::default()
),
Err(DsigError::Policy(_))
));
let policy = crate::policy::VerificationPolicy {
digest_algorithms: Some(
[algorithm, super::super::DigestAlgorithm::Sha512].into(),
),
..crate::policy::VerificationPolicy::default()
};
assert_eq!(
super::super::VerifyContext::new()
.key_resolver(&resolver)
.policy(policy.clone())
.verify(&xml)
.expect("explicitly permitted selector must verify")
.status,
super::super::DsigStatus::Valid
);
let start = xml.find("<KeyInfo>").expect("fixture has KeyInfo");
let end =
xml.find("</KeyInfo>").expect("fixture closes KeyInfo") + "</KeyInfo>".len();
let target = &xml[start..end];
let referenced = replace_unprefixed_key_info(
&xml,
&format!(
"<KeyInfo><KeyInfoReference xmlns=\"http://www.w3.org/2009/xmldsig11#\" URI=\"#selector\"/></KeyInfo><Object>{}</Object>",
target.replace("<KeyInfo>", "<KeyInfo Id=\"selector\">")
),
);
let retrieved = replace_unprefixed_key_info(
&xml,
&format!(
"<KeyInfo><RetrievalMethod URI=\"#selector\" Type=\"http://www.w3.org/2000/09/xmldsig#X509Data\"/></KeyInfo><Object>{}</Object>",
target
.strip_prefix("<KeyInfo>")
.expect("selected range starts at KeyInfo")
.strip_suffix("</KeyInfo>")
.expect("selected range ends at KeyInfo")
.replace("<X509Data>", "<X509Data Id=\"selector\">")
),
);
for indirect in [referenced, retrieved] {
assert!(
matches!(super::super::VerifyContext::new().key_resolver(&resolver).verify(&indirect),
Err(DsigError::Policy(crate::policy::PolicyViolation::Algorithm { algorithm: uri, .. })) if uri == algorithm.uri())
);
assert_eq!(
super::super::VerifyContext::new()
.key_resolver(&resolver)
.policy(policy.clone())
.verify(&indirect)
.expect("permitted indirect selector must verify")
.status,
super::super::DsigStatus::Valid
);
}
}
}
}
#[test]
fn generic_key_resolution_keeps_legacy_capability_source_independent() {
let certificate =
include_bytes!("../../tests/fixtures/xmldsig/phaos-xmldsig-three/certs/rsa-cert.der")
.to_vec();
let (_, parsed_certificate) = X509Certificate::from_der(&certificate)
.expect("the Phaos fixture is a DER certificate");
let public_key = parsed_certificate.public_key().raw.to_vec();
let rsa_public_key = rsa::RsaPublicKey::from_public_key_der(&public_key)
.expect("the Phaos certificate contains an RSA public key");
let certificate_metadata = parse_x509_certificate(&certificate)
.expect("the Phaos fixture has supported X.509 metadata");
let named_key = VerificationKey {
algorithm: SignatureAlgorithm::RsaSha1,
public_key_bytes: public_key.clone(),
certificate_der: None,
name: Some("legacy".into()),
};
let key_infos = [
KeyInfo {
sources: vec![KeyInfoSource::KeyName("legacy".into())],
},
KeyInfo {
sources: vec![KeyInfoSource::DerEncodedKeyValue(public_key.clone())],
},
KeyInfo {
sources: vec![KeyInfoSource::KeyValue(KeyValueInfo::Rsa {
modulus: rsa_public_key.n().to_be_bytes_trimmed_vartime().to_vec(),
exponent: rsa_public_key.e().to_be_bytes_trimmed_vartime().to_vec(),
})],
},
KeyInfo {
sources: vec![KeyInfoSource::X509Data(X509DataInfo {
certificates: vec![certificate],
parsed_certificates: vec![certificate_metadata],
certificate_chain: vec![0],
..X509DataInfo::default()
})],
},
];
let resolver = DefaultKeyResolver::new(KeyResolverConfig {
named_keys: HashMap::from([("legacy".into(), named_key.clone())]),
..KeyResolverConfig::default()
});
let mut policy = crate::policy::VerificationPolicy::default();
policy.key_trust.rsa_keys.minimum_modulus_bits = 1024;
policy
.key_trust
.allowed_legacy_signature_algorithms
.insert(SignatureAlgorithm::RsaSha1);
for key_info in &key_infos {
let key = resolver
.resolve_with_policy(Some(key_info), SignatureAlgorithm::RsaSha1, &policy)
.expect("the key source is valid")
.expect("key resolution remains independent from operation policy");
assert!(
!key.verify(SignatureAlgorithm::RsaSha1, b"data", &[0; 128])
.expect("the legacy RSA key is structurally valid")
);
}
}
#[test]
fn rsa_key_value_rejects_ecdsa_signature_method() {
let public_key = rsa::RsaPublicKey::from_public_key_pem(RSA_PUBLIC_KEY)
.expect("fixture must contain an RSA public key");
let (modulus, exponent) = rsa_key_value_parts(&public_key);
let key_info = format!(
"<ds:KeyInfo><ds:KeyValue><ds:RSAKeyValue><ds:Modulus>{}</ds:Modulus><ds:Exponent>{}</ds:Exponent></ds:RSAKeyValue></ds:KeyValue></ds:KeyInfo>",
modulus, exponent,
);
let xml = replace_key_info(SIGNED_SAML, &key_info);
let resolver = DefaultKeyResolver::default();
let error = super::super::VerifyContext::new()
.key_resolver(&resolver)
.verify(&xml)
.expect_err("RSAKeyValue must not resolve for ECDSA");
assert!(matches!(
error,
DsigError::KeyResolution(KeyResolutionError::AlgorithmMismatch)
));
}
#[test]
fn resolves_ec_p256_key_value_end_to_end() {
let resolver = DefaultKeyResolver::default();
let result = super::super::VerifyContext::new()
.policy(cryptographic_policy())
.key_resolver(&resolver)
.verify(EC_P256_KEY_VALUE_SIGNATURE)
.expect("P-256 ECKeyValue should resolve");
assert_eq!(result.status, super::super::DsigStatus::Valid);
}
#[test]
fn resolves_ec_p384_key_value_end_to_end() {
let resolver = DefaultKeyResolver::default();
let result = super::super::VerifyContext::new()
.policy(cryptographic_policy())
.key_resolver(&resolver)
.verify(EC_P384_KEY_VALUE_SIGNATURE)
.expect("P-384 ECKeyValue should resolve");
assert_eq!(result.status, super::super::DsigStatus::Valid);
}
#[test]
fn ec_key_value_ignored_for_rsa_signature_method() {
let key_info = r#"<KeyInfo xmlns:dsig11="http://www.w3.org/2009/xmldsig11#"><KeyValue><dsig11:ECKeyValue><dsig11:NamedCurve URI="urn:oid:1.2.840.10045.3.1.7"/><dsig11:PublicKey>BJ/yaXNlq4FRObyJCBhb5jAz8GVzinK3bBGLjSDfjbJwNfydtgjnlS4EsDmxSRhWyJWq6GIqy5wvnaiARK04uB4=</dsig11:PublicKey></dsig11:ECKeyValue></KeyValue></KeyInfo>"#;
let xml = replace_unprefixed_key_info(RSA_KEY_VALUE_SIGNATURE, key_info);
let resolver = DefaultKeyResolver::default();
let result = super::super::VerifyContext::new()
.key_resolver(&resolver)
.verify(&xml)
.expect("single incompatible ECKeyValue should be ignored");
assert_eq!(
result.status,
super::super::DsigStatus::Invalid(super::super::FailureReason::KeyNotFound)
);
}
#[test]
fn incompatible_ec_key_value_falls_back_to_later_rsa_key_value() {
let public_key = rsa::RsaPublicKey::from_public_key_pem(RSA_PUBLIC_KEY)
.expect("fixture must contain an RSA public key");
let (modulus, exponent) = rsa_key_value_parts(&public_key);
let key_info = format!(
r#"<KeyInfo xmlns:dsig11="http://www.w3.org/2009/xmldsig11#"><KeyValue><dsig11:ECKeyValue><dsig11:NamedCurve URI="urn:oid:1.2.840.10045.3.1.7"/><dsig11:PublicKey>BJ/yaXNlq4FRObyJCBhb5jAz8GVzinK3bBGLjSDfjbJwNfydtgjnlS4EsDmxSRhWyJWq6GIqy5wvnaiARK04uB4=</dsig11:PublicKey></dsig11:ECKeyValue></KeyValue><KeyValue><RSAKeyValue><Modulus>{}</Modulus><Exponent>{}</Exponent></RSAKeyValue></KeyValue></KeyInfo>"#,
modulus, exponent,
);
let xml = replace_unprefixed_key_info(RSA_KEY_VALUE_SIGNATURE, &key_info);
let resolver = DefaultKeyResolver::default();
let result = super::super::VerifyContext::new()
.policy(cryptographic_policy())
.key_resolver(&resolver)
.verify(&xml)
.expect("later RSAKeyValue should resolve");
assert_eq!(result.status, super::super::DsigStatus::Valid);
}
#[test]
fn unsupported_ec_key_value_falls_back_to_later_key_name() {
let key_info = r#"<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:dsig11="http://www.w3.org/2009/xmldsig11#"><ds:KeyValue><dsig11:ECKeyValue><dsig11:NamedCurve URI="urn:oid:1.3.132.0.35"/><dsig11:PublicKey>BA==</dsig11:PublicKey></dsig11:ECKeyValue></ds:KeyValue><ds:KeyName>idp-signing</ds:KeyName></ds:KeyInfo>"#;
let xml = replace_key_info(SIGNED_SAML, key_info);
let mut config = KeyResolverConfig::default();
config.named_keys.insert(
"idp-signing".into(),
VerificationKey {
algorithm: SignatureAlgorithm::EcdsaSha256,
public_key_bytes: public_key_der(SAML_PUBLIC_KEY),
certificate_der: None,
name: Some("idp-signing".into()),
},
);
let resolver = DefaultKeyResolver::new(config);
let result = super::super::VerifyContext::new()
.key_resolver(&resolver)
.verify(&xml)
.expect("later KeyName should resolve");
assert_eq!(result.status, super::super::DsigStatus::Valid);
}
#[test]
fn invalid_ec_key_value_falls_back_to_later_key_name() {
let key_info = r#"<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:dsig11="http://www.w3.org/2009/xmldsig11#"><ds:KeyValue><dsig11:ECKeyValue><dsig11:NamedCurve URI="urn:oid:1.2.840.10045.3.1.7"/><dsig11:PublicKey>BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</dsig11:PublicKey></dsig11:ECKeyValue></ds:KeyValue><ds:KeyName>idp-signing</ds:KeyName></ds:KeyInfo>"#;
let xml = replace_key_info(SIGNED_SAML, key_info);
let mut config = KeyResolverConfig::default();
config.named_keys.insert(
"idp-signing".into(),
VerificationKey {
algorithm: SignatureAlgorithm::EcdsaSha256,
public_key_bytes: public_key_der(SAML_PUBLIC_KEY),
certificate_der: None,
name: Some("idp-signing".into()),
},
);
let resolver = DefaultKeyResolver::new(config);
let result = super::super::VerifyContext::new()
.key_resolver(&resolver)
.verify(&xml)
.expect("later KeyName should resolve after invalid ECKeyValue");
assert_eq!(result.status, super::super::DsigStatus::Valid);
}
#[test]
fn malformed_ec_key_value_falls_back_to_later_key_name() {
let key_info = r#"<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:dsig11="http://www.w3.org/2009/xmldsig11#"><ds:KeyValue><dsig11:ECKeyValue><dsig11:NamedCurve URI="urn:oid:1.2.840.10045.3.1.7"/><dsig11:PublicKey>AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</dsig11:PublicKey></dsig11:ECKeyValue></ds:KeyValue><ds:KeyName>idp-signing</ds:KeyName></ds:KeyInfo>"#;
let xml = replace_key_info(SIGNED_SAML, key_info);
let mut config = KeyResolverConfig::default();
config.named_keys.insert(
"idp-signing".into(),
VerificationKey {
algorithm: SignatureAlgorithm::EcdsaSha256,
public_key_bytes: public_key_der(SAML_PUBLIC_KEY),
certificate_der: None,
name: Some("idp-signing".into()),
},
);
let resolver = DefaultKeyResolver::new(config);
let result = super::super::VerifyContext::new()
.key_resolver(&resolver)
.verify(&xml)
.expect("later KeyName should resolve after malformed ECKeyValue");
assert_eq!(result.status, super::super::DsigStatus::Valid);
}
#[test]
fn invalid_base64_ec_key_value_falls_back_to_later_key_name() {
let key_info = r#"<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:dsig11="http://www.w3.org/2009/xmldsig11#"><ds:KeyValue><dsig11:ECKeyValue><dsig11:NamedCurve URI="urn:oid:1.2.840.10045.3.1.7"/><dsig11:PublicKey>not base64!</dsig11:PublicKey></dsig11:ECKeyValue></ds:KeyValue><ds:KeyName>idp-signing</ds:KeyName></ds:KeyInfo>"#;
let xml = replace_key_info(SIGNED_SAML, key_info);
let mut config = KeyResolverConfig::default();
config.named_keys.insert(
"idp-signing".into(),
VerificationKey {
algorithm: SignatureAlgorithm::EcdsaSha256,
public_key_bytes: public_key_der(SAML_PUBLIC_KEY),
certificate_der: None,
name: Some("idp-signing".into()),
},
);
let resolver = DefaultKeyResolver::new(config);
let result = super::super::VerifyContext::new()
.key_resolver(&resolver)
.verify(&xml)
.expect("later KeyName should resolve after bad ECKeyValue base64");
assert_eq!(result.status, super::super::DsigStatus::Valid);
}
#[test]
fn missing_curve_uri_ec_key_value_falls_back_to_later_key_name() {
let key_info = r#"<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:dsig11="http://www.w3.org/2009/xmldsig11#"><ds:KeyValue><dsig11:ECKeyValue><dsig11:NamedCurve/><dsig11:PublicKey>BA==</dsig11:PublicKey></dsig11:ECKeyValue></ds:KeyValue><ds:KeyName>idp-signing</ds:KeyName></ds:KeyInfo>"#;
let xml = replace_key_info(SIGNED_SAML, key_info);
let mut config = KeyResolverConfig::default();
config.named_keys.insert(
"idp-signing".into(),
VerificationKey {
algorithm: SignatureAlgorithm::EcdsaSha256,
public_key_bytes: public_key_der(SAML_PUBLIC_KEY),
certificate_der: None,
name: Some("idp-signing".into()),
},
);
let resolver = DefaultKeyResolver::new(config);
let result = super::super::VerifyContext::new()
.key_resolver(&resolver)
.verify(&xml)
.expect("later KeyName should resolve after missing EC curve URI");
assert_eq!(result.status, super::super::DsigStatus::Valid);
}
#[test]
fn malformed_ec_key_value_children_fall_back_to_later_key_name() {
let malformed_ec_key_values = [
r#"<dsig11:NamedCurve URI="urn:oid:1.2.840.10045.3.1.7"/>"#,
r#"<dsig11:NamedCurve URI="urn:oid:1.2.840.10045.3.1.7"/><dsig11:NamedCurve URI="urn:oid:1.2.840.10045.3.1.7"/>"#,
r#"<dsig11:NamedCurve URI="urn:oid:1.2.840.10045.3.1.7"/><dsig11:PublicKey>BA==</dsig11:PublicKey><dsig11:PublicKey>BA==</dsig11:PublicKey>"#,
];
for malformed_children in malformed_ec_key_values {
let key_info = format!(
r#"<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:dsig11="http://www.w3.org/2009/xmldsig11#"><ds:KeyValue><dsig11:ECKeyValue>{malformed_children}</dsig11:ECKeyValue></ds:KeyValue><ds:KeyName>idp-signing</ds:KeyName></ds:KeyInfo>"#
);
let xml = replace_key_info(SIGNED_SAML, &key_info);
let mut config = KeyResolverConfig::default();
config.named_keys.insert(
"idp-signing".into(),
VerificationKey {
algorithm: SignatureAlgorithm::EcdsaSha256,
public_key_bytes: public_key_der(SAML_PUBLIC_KEY),
certificate_der: None,
name: Some("idp-signing".into()),
},
);
let resolver = DefaultKeyResolver::new(config);
let result = super::super::VerifyContext::new()
.key_resolver(&resolver)
.verify(&xml)
.expect("later KeyName should resolve after malformed EC child shape");
assert_eq!(result.status, super::super::DsigStatus::Valid);
}
}
#[test]
fn supported_ec_curve_does_not_fall_back_to_later_key_name() {
let key_info = r#"<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:dsig11="http://www.w3.org/2009/xmldsig11#"><ds:KeyValue><dsig11:ECKeyValue><dsig11:NamedCurve URI="urn:oid:1.3.132.0.34"/><dsig11:PublicKey>BO/yd/OZzDfjX4qivDY/vsUIuh6KWAxoxW5P4ukvwd+T6pVljWsX2UBJNNy5MdhTwB8e2YwB8kUbJwdsAS/XGi/fz8unFrs+lVlAgIs6s/xBYFbfUoRiAacD2SpVDe6XBA==</dsig11:PublicKey></dsig11:ECKeyValue></ds:KeyValue><ds:KeyName>idp-signing</ds:KeyName></ds:KeyInfo>"#;
let xml = replace_key_info(SIGNED_SAML, key_info);
let mut config = KeyResolverConfig::default();
config.named_keys.insert(
"idp-signing".into(),
VerificationKey {
algorithm: SignatureAlgorithm::EcdsaSha256,
public_key_bytes: public_key_der(SAML_PUBLIC_KEY),
certificate_der: None,
name: Some("idp-signing".into()),
},
);
let resolver = DefaultKeyResolver::new(config);
let error = super::super::VerifyContext::new()
.policy(cryptographic_policy())
.key_resolver(&resolver)
.verify(&xml)
.expect_err("a usable first key source must not fall through after verification");
assert!(matches!(
error,
DsigError::Crypto(super::super::SignatureVerificationError::InvalidSignatureFormat)
));
}
#[test]
fn lone_malformed_ec_key_value_reports_invalid_public_key() {
let key_info = r#"<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:dsig11="http://www.w3.org/2009/xmldsig11#"><ds:KeyValue><dsig11:ECKeyValue><dsig11:NamedCurve URI="urn:oid:1.2.840.10045.3.1.7"/><dsig11:PublicKey>AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</dsig11:PublicKey></dsig11:ECKeyValue></ds:KeyValue></ds:KeyInfo>"#;
let xml = replace_key_info(SIGNED_SAML, key_info);
let error = super::super::VerifyContext::new()
.key_resolver(&DefaultKeyResolver::default())
.verify(&xml)
.expect_err("lone malformed ECKeyValue should surface typed key error");
assert!(matches!(
error,
DsigError::KeyResolution(KeyResolutionError::InvalidPublicKey)
));
}
#[test]
fn lone_supported_ec_curve_reaches_signature_verification() {
let key_info = r#"<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:dsig11="http://www.w3.org/2009/xmldsig11#"><ds:KeyValue><dsig11:ECKeyValue><dsig11:NamedCurve URI="urn:oid:1.3.132.0.34"/><dsig11:PublicKey>BO/yd/OZzDfjX4qivDY/vsUIuh6KWAxoxW5P4ukvwd+T6pVljWsX2UBJNNy5MdhTwB8e2YwB8kUbJwdsAS/XGi/fz8unFrs+lVlAgIs6s/xBYFbfUoRiAacD2SpVDe6XBA==</dsig11:PublicKey></dsig11:ECKeyValue></ds:KeyValue></ds:KeyInfo>"#;
let xml = replace_key_info(SIGNED_SAML, key_info);
let error = super::super::VerifyContext::new()
.policy(cryptographic_policy())
.key_resolver(&DefaultKeyResolver::default())
.verify(&xml)
.expect_err("a supported EC curve must reach signature verification");
assert!(matches!(
error,
DsigError::Crypto(super::super::SignatureVerificationError::InvalidSignatureFormat)
));
}
#[test]
fn chain_verification_rejects_untrusted_embedded_certificate() {
let resolver = DefaultKeyResolver::new(KeyResolverConfig {
..KeyResolverConfig::default()
});
let error = super::super::VerifyContext::new()
.policy(verification_policy_with_trust(chain_policy()))
.key_resolver(&resolver)
.verify(SIGNED_SAML)
.expect_err("untrusted certificate must fail chain validation");
assert!(matches!(
error,
DsigError::KeyResolution(KeyResolutionError::Chain(
super::super::X509ChainError::UntrustedRoot
))
));
}
#[test]
fn named_key_algorithm_mismatch_fails_closed() {
let xml = replace_key_info(
SIGNED_SAML,
"<ds:KeyInfo><ds:KeyName>wrong-algorithm</ds:KeyName></ds:KeyInfo>",
);
let mut config = KeyResolverConfig::default();
config.named_keys.insert(
"wrong-algorithm".into(),
VerificationKey {
algorithm: SignatureAlgorithm::RsaSha256,
public_key_bytes: public_key_der(SAML_PUBLIC_KEY),
certificate_der: None,
name: Some("wrong-algorithm".into()),
},
);
let resolver = DefaultKeyResolver::new(config);
let error = super::super::VerifyContext::new()
.key_resolver(&resolver)
.verify(&xml)
.expect_err("algorithm mismatch must fail closed");
assert!(matches!(
error,
DsigError::KeyResolution(KeyResolutionError::AlgorithmMismatch)
));
}
#[test]
fn named_key_spki_type_mismatch_fails_during_resolution() {
let xml = replace_key_info(
SIGNED_SAML,
"<ds:KeyInfo><ds:KeyName>mislabeled</ds:KeyName></ds:KeyInfo>",
);
let mut config = KeyResolverConfig::default();
config.named_keys.insert(
"mislabeled".into(),
VerificationKey {
algorithm: SignatureAlgorithm::EcdsaSha256,
public_key_bytes: public_key_der(RSA_PUBLIC_KEY),
certificate_der: None,
name: Some("mislabeled".into()),
},
);
let resolver = DefaultKeyResolver::new(config);
let error = super::super::VerifyContext::new()
.key_resolver(&resolver)
.verify(&xml)
.expect_err("mislabeled named key must fail during resolution");
assert!(matches!(
error,
DsigError::KeyResolution(KeyResolutionError::AlgorithmMismatch)
));
}
#[test]
fn malformed_named_key_reports_public_key_error() {
let xml = replace_key_info(
SIGNED_SAML,
"<ds:KeyInfo><ds:KeyName>malformed</ds:KeyName></ds:KeyInfo>",
);
let mut config = KeyResolverConfig::default();
config.named_keys.insert(
"malformed".into(),
VerificationKey {
algorithm: SignatureAlgorithm::EcdsaSha256,
public_key_bytes: vec![1, 2, 3],
certificate_der: None,
name: Some("malformed".into()),
},
);
let resolver = DefaultKeyResolver::new(config);
let error = super::super::VerifyContext::new()
.key_resolver(&resolver)
.verify(&xml)
.expect_err("malformed named key must fail during resolution");
assert!(matches!(
error,
DsigError::KeyResolution(KeyResolutionError::InvalidPublicKey)
));
}
}