use std::{collections::HashMap, fmt, time::SystemTime};
use crypto_bigint::BoxedUint;
use dsa::pkcs8::{DecodePublicKey as DsaDecodePublicKey, EncodePublicKey as DsaEncodePublicKey};
use hmac::{KeyInit, Mac};
use x509_parser::{
prelude::{FromDer, X509Certificate},
public_key::PublicKey,
x509::SubjectPublicKeyInfo,
};
use zeroize::Zeroizing;
use super::signature::{
signature_value_matches_spki, signature_value_matches_spki_with_encoding,
validate_dsa_signature_spki_with_minimum, 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,
};
#[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 {
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 validate_policy(&self, policy: &crate::policy::VerificationPolicy) -> Result<(), DsigError> {
let result = match self.algorithm {
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 => 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 {
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 => 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 matches!(
algorithm,
SignatureAlgorithm::EcdsaSha1
| SignatureAlgorithm::EcdsaSha224
| SignatureAlgorithm::EcdsaSha256
| SignatureAlgorithm::EcdsaSha384
| SignatureAlgorithm::EcdsaSha512
) {
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 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,
)
}
SignatureAlgorithm::RsaSha1
| SignatureAlgorithm::RsaSha224
| SignatureAlgorithm::RsaSha256
| SignatureAlgorithm::RsaSha384
| SignatureAlgorithm::RsaSha512 => 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 matches!(
algorithm,
SignatureAlgorithm::EcdsaSha1
| SignatureAlgorithm::EcdsaSha224
| SignatureAlgorithm::EcdsaSha256
| SignatureAlgorithm::EcdsaSha384
| SignatureAlgorithm::EcdsaSha512
) {
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 named_keys: HashMap<String, VerificationKey>,
}
#[derive(Debug, Clone, Default)]
pub struct DefaultKeyResolver {
config: KeyResolverConfig,
}
struct InspectedKeyCandidateBudget {
maximum: usize,
attempted: usize,
}
impl InspectedKeyCandidateBudget {
fn new(maximum: usize) -> Self {
Self {
maximum,
attempted: 0,
}
}
fn charge(&mut self) -> Result<(), DsigError> {
self.charge_many(1)
}
fn charge_many(&mut self, count: usize) -> Result<(), DsigError> {
self.attempted = self.attempted.saturating_add(count);
if self.attempted > self.maximum {
return Err(crate::policy::PolicyViolation::ResourceLimit {
resource: crate::policy::resource_name::KEY_CANDIDATES,
maximum: self.maximum,
actual: self.attempted,
}
.into());
}
Ok(())
}
}
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 {
#[must_use]
pub fn new(config: KeyResolverConfig) -> Self {
Self { config }
}
#[must_use]
pub fn config(&self) -> &KeyResolverConfig {
&self.config
}
fn resolve_x509(
&self,
info: &X509DataInfo,
algorithm: SignatureAlgorithm,
trust: &crate::policy::KeyTrustPolicy,
provider: &dyn crate::provider::CryptoProvider,
budget: &mut InspectedKeyCandidateBudget,
) -> Result<Option<VerificationKey>, DsigError> {
let certificate_der = if let Some(&signing_index) = info.certificate_chain.first() {
if trust.verify_x509_chains {
self.prepare_embedded_x509(info, signing_index, trust, provider, budget)?;
} 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);
};
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,
}))
}
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,
};
verify_x509_certificate_chain_with_provider(info, &options, provider)?;
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,
)
.map_err(|error| match error {
X509ChainBuildError::AmbiguousIssuer => KeyResolutionError::AmbiguousCertificate,
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,
)
.map_err(|error| match error {
X509ChainBuildError::AmbiguousIssuer => KeyResolutionError::AmbiguousCertificate,
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 !matches!(
algorithm,
SignatureAlgorithm::RsaSha1
| SignatureAlgorithm::RsaSha224
| SignatureAlgorithm::RsaSha256
| SignatureAlgorithm::RsaSha384
| SignatureAlgorithm::RsaSha512
) {
return Err(KeyResolutionError::AlgorithmMismatch);
}
rsa_key_value_to_spki_der(modulus, exponent)?
}
KeyValueInfo::Ec {
curve_oid,
public_key,
} => {
if !matches!(
algorithm,
SignatureAlgorithm::EcdsaSha1
| SignatureAlgorithm::EcdsaSha224
| SignatureAlgorithm::EcdsaSha256
| SignatureAlgorithm::EcdsaSha384
| SignatureAlgorithm::EcdsaSha512
) {
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<'a>(
&'a self,
key_info: Option<&KeyInfo>,
algorithm: SignatureAlgorithm,
sources: crate::policy::KeySourcePolicy,
trust: &crate::policy::KeyTrustPolicy,
resources: &crate::policy::ResourcePolicy,
provider: &dyn crate::provider::CryptoProvider,
) -> Result<Option<Box<dyn VerifyingKey + 'a>>, DsigError> {
trust.validate()?;
resources.validate()?;
let Some(key_info) = key_info else {
return Ok(None);
};
validate_key_info_source_permissions(key_info, sources)?;
let mut candidate_budget = InspectedKeyCandidateBudget::new(resources.max_key_candidates);
let mut deferred_key_value_error = None;
for source in &key_info.sources {
let resolved = match source {
KeyInfoSource::X509Data(info) => {
self.resolve_x509(info, algorithm, trust, provider, &mut candidate_budget)?
}
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 {
return Ok(Some(Box::new(PolicyBoundVerificationKey {
key,
rsa_minimum_bits: trust.rsa_keys.minimum_modulus_bits,
dsa_minimum_bits: trust.dsa_keys.minimum_modulus_bits,
})));
}
}
if let Some(error) = deferred_key_value_error {
return Err(error.into());
}
Ok(None)
}
}
impl KeyResolver for DefaultKeyResolver {
fn resolve<'a>(
&'a self,
key_info: Option<&KeyInfo>,
algorithm: SignatureAlgorithm,
) -> Result<Option<Box<dyn VerifyingKey + 'a>>, DsigError> {
let policy = crate::policy::VerificationPolicy::default();
self.resolve_with_trust(
key_info,
algorithm,
policy.key_sources,
&policy.key_trust,
&policy.resources,
crate::provider::default_provider(),
)
}
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> {
self.resolve_with_trust(
key_info,
algorithm,
policy.key_sources,
&policy.key_trust,
&policy.resources,
provider,
)
}
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 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())
}
fn dsa_key_value_to_spki_der(
p: &[u8],
q: &[u8],
g: &[u8],
y: &[u8],
) -> Result<Vec<u8>, KeyResolutionError> {
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> {
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 _ = dsa::VerifyingKey::from_public_key_der(public_key_bytes)
.map_err(|_| KeyResolutionError::AlgorithmMismatch)?;
Ok(())
}
(
SignatureAlgorithm::RsaSha1
| SignatureAlgorithm::RsaSha224
| SignatureAlgorithm::RsaSha256
| SignatureAlgorithm::RsaSha384
| SignatureAlgorithm::RsaSha512,
PublicKey::RSA(_),
) => Ok(()),
(
SignatureAlgorithm::EcdsaSha1
| SignatureAlgorithm::EcdsaSha224
| SignatureAlgorithm::EcdsaSha256
| SignatureAlgorithm::EcdsaSha384
| SignatureAlgorithm::EcdsaSha512,
PublicKey::EC(_),
) if matches!(
curve_oid.as_deref(),
Some(EC_P256_OID | EC_P384_OID | EC_P521_OID)
) =>
{
Ok(())
}
_ => Err(KeyResolutionError::AlgorithmMismatch),
}
}
#[cfg(test)]
mod tests {
use crate::xml::dom as roxmltree;
use std::sync::atomic::{AtomicUsize, Ordering};
use base64::{Engine, engine::general_purpose::STANDARD};
use rsa::{pkcs8::DecodePublicKey, traits::PublicKeyParts};
use super::*;
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: Some(algorithm.oid().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 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 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()
.key_resolver(&resolver)
.verify(SIGNED_SAML)
.expect("embedded certificate should resolve");
assert_eq!(result.status, super::super::DsigStatus::Valid);
}
#[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()
.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(),
),
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_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,
)
.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,
)
.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(),
)
.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(),
),
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::Revoked(0)
))
));
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 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()
.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()
.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);
}
#[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()
.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()
.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 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:?}"
);
}
#[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()
.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()
.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()
.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()
.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()
.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)
));
}
}