use core::ops::Deref;
use crate::error::SamlError;
#[cfg(any(
feature = "crypto-rustcrypto",
feature = "crypto-aws-lc",
feature = "crypto-fips"
))]
use crate::error::SignatureVerificationReason;
use crate::metadata::Metadata;
#[cfg(any(
feature = "crypto-rustcrypto",
feature = "crypto-aws-lc",
feature = "crypto-fips"
))]
use crate::xml::XmlLimits;
use super::credentials::CertificatePem;
use super::descriptors::EntityId;
use super::metadata_signature_shape::{ensure_import_signature_shape, MetadataSignatureTransforms};
#[derive(Debug, Clone, Copy)]
pub enum MetadataTrustPolicy<'a> {
UnsignedForCompatibility,
RequireSignature {
trusted_certificates: &'a [CertificatePem],
},
RequireSignatureAllowingOtherCanonicalization {
trusted_certificates: &'a [CertificatePem],
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(
not(any(
feature = "crypto-rustcrypto",
feature = "crypto-aws-lc",
feature = "crypto-fips"
)),
allow(dead_code)
)]
pub(super) enum AppliedMetadataTrust {
UnsignedForCompatibility,
SignedByPinnedCertificates {
signed_entity_descriptor_xml: String,
},
}
pub(super) fn metadata_entity_id<M>(metadata: &M) -> Result<&str, SamlError>
where
M: Deref<Target = Metadata>,
{
metadata
.get_entity_id()
.ok_or_else(|| SamlError::MissingMetadata("entityID".into()))
}
pub(super) fn ensure_expected_entity_id(
expected: &EntityId,
actual: &str,
) -> Result<(), SamlError> {
if expected.as_str() == actual {
return Ok(());
}
Err(SamlError::Invalid(format!(
"metadata entityID `{actual}` did not match expected `{}`",
expected.as_str()
)))
}
pub(super) fn ensure_metadata_trust<M>(
metadata: &M,
trust: MetadataTrustPolicy<'_>,
) -> Result<AppliedMetadataTrust, SamlError>
where
M: Deref<Target = Metadata>,
{
match trust {
MetadataTrustPolicy::UnsignedForCompatibility => {
Ok(AppliedMetadataTrust::UnsignedForCompatibility)
}
MetadataTrustPolicy::RequireSignature {
trusted_certificates,
} => verify_pinned_metadata_signature(
metadata,
trusted_certificates,
MetadataSignatureTransforms::Profile,
),
MetadataTrustPolicy::RequireSignatureAllowingOtherCanonicalization {
trusted_certificates,
} => verify_pinned_metadata_signature(
metadata,
trusted_certificates,
MetadataSignatureTransforms::AllowOtherCanonicalization,
),
}
}
#[cfg(any(
feature = "crypto-rustcrypto",
feature = "crypto-aws-lc",
feature = "crypto-fips"
))]
fn verify_pinned_metadata_signature<M>(
metadata: &M,
trusted_certificates: &[CertificatePem],
transforms: MetadataSignatureTransforms,
) -> Result<AppliedMetadataTrust, SamlError>
where
M: Deref<Target = Metadata>,
{
ensure_import_signature_shape(metadata.get_metadata(), transforms)?;
let trusted_certificates: Vec<String> = trusted_certificates
.iter()
.map(|certificate| certificate.as_str().to_string())
.collect();
let verification = crate::crypto::verify::verify_metadata_signature_coverage_with_limits(
metadata.get_metadata(),
&trusted_certificates,
XmlLimits::default(),
)?;
if verification.verified() {
let signed_entity_descriptor_xml = verification
.into_signed_entity_descriptor_xml()
.ok_or(SamlError::SignedReferenceMismatch)?;
return Ok(AppliedMetadataTrust::SignedByPinnedCertificates {
signed_entity_descriptor_xml,
});
}
Err(SamlError::SignatureVerification {
reason: SignatureVerificationReason::XmlSignature,
})
}
#[cfg(not(any(
feature = "crypto-rustcrypto",
feature = "crypto-aws-lc",
feature = "crypto-fips"
)))]
fn verify_pinned_metadata_signature<M>(
metadata: &M,
_trusted_certificates: &[CertificatePem],
transforms: MetadataSignatureTransforms,
) -> Result<AppliedMetadataTrust, SamlError>
where
M: Deref<Target = Metadata>,
{
ensure_import_signature_shape(metadata.get_metadata(), transforms)?;
Err(SamlError::Unsupported(
"signed metadata verification requires a crypto provider feature".into(),
))
}