use super::*;
use crate::provider::{CryptoProvider, ProviderCapability};
mod composition;
mod constraints;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PolicyProfile {
XmlSecCompatible,
Modern,
SecureSaml,
WsSecurity,
Xades,
LegacyVerify,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PolicyValidity {
pub authorization_id: String,
pub authority: String,
pub reason: String,
pub not_before: SystemTime,
pub expires_at: SystemTime,
}
impl PolicyValidity {
pub fn validate_at(&self, now: SystemTime) -> Result<(), PolicyViolation> {
if self.authorization_id.trim().is_empty()
|| self.authority.trim().is_empty()
|| self.reason.trim().is_empty()
|| self.not_before >= self.expires_at
{
return Err(configuration(
"authorization needs identity, authority, reason and a nonempty validity interval",
));
}
if now < self.not_before || now >= self.expires_at {
return Err(PolicyViolation::AuthorizationExpired);
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum SignatureConstraints {
#[default]
General,
Saml,
WsSecurity,
Xades,
}
#[derive(Debug, Clone, Default)]
pub struct PolicySet {
pub verification: Option<VerificationPolicy>,
pub signing: Option<SigningPolicy>,
#[cfg(feature = "xmlenc")]
pub encryption: Option<EncryptionPolicy>,
#[cfg(feature = "xmlenc")]
pub decryption: Option<DecryptionPolicy>,
}
pub struct PolicyCompileContext {
pub external_resources: bool,
pub target_evidence: bool,
}
#[derive(Debug, Clone)]
pub struct CompiledPolicies {
profile: PolicyProfile,
policies: PolicySet,
}
impl CompiledPolicies {
pub const fn profile(&self) -> PolicyProfile {
self.profile
}
pub fn verification(&self) -> Option<&VerificationPolicy> {
self.policies.verification.as_ref()
}
pub fn signing(&self) -> Option<&SigningPolicy> {
self.policies.signing.as_ref()
}
#[cfg(feature = "xmlenc")]
pub fn encryption(&self) -> Option<&EncryptionPolicy> {
self.policies.encryption.as_ref()
}
#[cfg(feature = "xmlenc")]
pub fn decryption(&self) -> Option<&DecryptionPolicy> {
self.policies.decryption.as_ref()
}
}
pub struct PolicyCompiler<'a> {
provider: &'a dyn CryptoProvider,
profile: PolicyProfile,
policies: PolicySet,
}
impl<'a> PolicyCompiler<'a> {
pub fn new(
profile: PolicyProfile,
provider: &'a dyn CryptoProvider,
authorization: Option<PolicyValidity>,
) -> Result<Self, PolicyViolation> {
if profile == PolicyProfile::LegacyVerify && authorization.is_none() {
return Err(configuration(
"LegacyVerify requires expiring authorization",
));
}
if let Some(validity) = &authorization {
validity.validate_at(SystemTime::now())?;
}
let policies = profile_policies(profile, provider, authorization);
Ok(Self {
provider,
profile,
policies,
})
}
pub fn restrict(mut self, layer: &PolicySet) -> Result<Self, PolicyViolation> {
self.policies = self.policies.intersect(layer)?;
Ok(self)
}
pub fn authorize_replacement(
mut self,
mut replacement: PolicySet,
authorization: PolicyValidity,
) -> Result<Self, PolicyViolation> {
authorization.validate_at(SystemTime::now())?;
replacement.bind_authorization(authorization, &self.policies)?;
self.policies = replacement;
Ok(self)
}
pub fn compile(
self,
context: &PolicyCompileContext,
) -> Result<CompiledPolicies, PolicyViolation> {
self.policies.validate(context, self.provider)?;
Ok(CompiledPolicies {
profile: self.profile,
policies: self.policies,
})
}
pub fn policies(&self) -> &PolicySet {
&self.policies
}
}
fn configuration(reason: &'static str) -> PolicyViolation {
PolicyViolation::Configuration { reason }
}
fn profile_policies(
profile: PolicyProfile,
provider: &dyn CryptoProvider,
validity: Option<PolicyValidity>,
) -> PolicySet {
let compatibility = matches!(
profile,
PolicyProfile::XmlSecCompatible | PolicyProfile::LegacyVerify
);
let constraints = match profile {
PolicyProfile::SecureSaml => SignatureConstraints::Saml,
PolicyProfile::WsSecurity => SignatureConstraints::WsSecurity,
PolicyProfile::Xades => SignatureConstraints::Xades,
_ => SignatureConstraints::General,
};
let signatures: HashSet<_> = SignatureAlgorithm::ALL
.into_iter()
.filter(|a| provider.supports(ProviderCapability::Verify(*a)))
.filter(|a| {
compatibility
|| (a.signing_allowed() && a.hmac_output_bits().is_none_or(|bits| bits >= 256))
})
.filter(|a| !matches!(a, SignatureAlgorithm::PostQuantum(_)))
.collect();
let digests: HashSet<_> = DigestAlgorithm::ALL
.into_iter()
.filter(|a| provider.supports(ProviderCapability::Digest(*a)))
.filter(|a| compatibility || (a.signing_allowed() && a.output_len() >= 32))
.collect();
let transforms = if compatibility {
TransformPolicy::default()
} else {
TransformPolicy {
allowed_algorithms: Some(
[
"http://www.w3.org/2000/09/xmldsig#enveloped-signature",
"http://www.w3.org/2001/10/xml-exc-c14n#",
"http://www.w3.org/2001/10/xml-exc-c14n#WithComments",
"http://www.w3.org/TR/2001/REC-xml-c14n-20010315",
"http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments",
"http://www.w3.org/2006/12/xml-c14n11",
"http://www.w3.org/2006/12/xml-c14n11#WithComments",
]
.into_iter()
.map(str::to_owned)
.collect(),
),
..TransformPolicy::default()
}
};
let mut verification = VerificationPolicy {
validity: validity.clone(),
signature_constraints: constraints,
signature_algorithms: Some(signatures),
parameterized_rsa_pss: if compatibility {
RsaPssPermission::AllSupported
} else {
RsaPssPermission::SecureSupported
},
digest_algorithms: Some(digests),
transforms: transforms.clone(),
..VerificationPolicy::default()
};
verification.key_trust.parameterized_rsa_pss = verification.parameterized_rsa_pss;
if compatibility {
verification.key_trust.allowed_legacy_signature_algorithms = verification
.signature_algorithms
.clone()
.unwrap_or_default();
verification.key_trust.rsa_keys.minimum_modulus_bits = 1024;
verification.key_trust.dsa_keys.minimum_modulus_bits = 1024;
verification.hmac.minimum_output_bits = 80;
verification.transforms.same_document_id_semantics =
SameDocumentIdSemantics::XmlSecBarename;
}
if constraints != SignatureConstraints::General {
verification.key_sources = KeySourcePolicy {
preset_key: true,
key_name: true,
x509_data: true,
key_value: false,
der_encoded_key_value: false,
key_info_reference: false,
};
}
if constraints == SignatureConstraints::Saml {
verification.uris.references = UriTypeSet::new(false, true, false);
verification.transforms.allowed_algorithms = Some(
[
"http://www.w3.org/2000/09/xmldsig#enveloped-signature",
"http://www.w3.org/2001/10/xml-exc-c14n#",
"http://www.w3.org/2001/10/xml-exc-c14n#WithComments",
]
.into_iter()
.map(str::to_owned)
.collect(),
);
}
let signing = SigningPolicy {
validity: validity.clone(),
signature_constraints: constraints,
parameterized_rsa_pss: RsaPssPermission::SecureSupported,
signature_algorithms: Some(
SignatureAlgorithm::ALL
.into_iter()
.filter(|a| a.signing_allowed() && !matches!(a, SignatureAlgorithm::PostQuantum(_)))
.filter(|a| a.hmac_output_bits().is_none_or(|bits| bits >= 256))
.filter(|a| provider.supports(ProviderCapability::Sign(*a)))
.collect(),
),
digest_algorithms: Some(
DigestAlgorithm::ALL
.into_iter()
.filter(|a| a.signing_allowed() && a.output_len() >= 32)
.filter(|a| provider.supports(ProviderCapability::Digest(*a)))
.collect(),
),
transforms: if constraints == SignatureConstraints::Saml {
verification.transforms.clone()
} else {
transforms
},
uris: if constraints == SignatureConstraints::Saml {
verification.uris
} else {
UriPolicy::default()
},
..SigningPolicy::default()
};
let legacy = profile == PolicyProfile::LegacyVerify;
PolicySet {
verification: Some(verification),
signing: (!legacy).then_some(signing),
#[cfg(feature = "xmlenc")]
encryption: (!legacy).then(|| encryption_profile(provider, validity.clone())),
#[cfg(feature = "xmlenc")]
decryption: (!legacy).then(|| decryption_profile(provider, compatibility, validity)),
}
}
#[cfg(feature = "xmlenc")]
const MODERN_CONTENT_METHODS: [DataEncryptionAlgorithm; 3] = [
DataEncryptionAlgorithm::Aes128Gcm,
DataEncryptionAlgorithm::Aes256Gcm,
DataEncryptionAlgorithm::ChaCha20Poly1305,
];
#[cfg(feature = "xmlenc")]
const MODERN_WRAP_METHODS: [KeyWrapAlgorithm; 2] =
[KeyWrapAlgorithm::AesKw128, KeyWrapAlgorithm::AesKw256];
#[cfg(feature = "xmlenc")]
const MODERN_OAEP_DIGESTS: [OaepDigestAlgorithm; 3] = [
OaepDigestAlgorithm::Sha256,
OaepDigestAlgorithm::Sha384,
OaepDigestAlgorithm::Sha512,
];
#[cfg(feature = "xmlenc")]
const COMPATIBLE_CONTENT_METHODS: &[DataEncryptionAlgorithm] = &[
DataEncryptionAlgorithm::Aes128Cbc,
DataEncryptionAlgorithm::Aes256Cbc,
DataEncryptionAlgorithm::Aes128Gcm,
DataEncryptionAlgorithm::Aes256Gcm,
DataEncryptionAlgorithm::Camellia128Cbc,
DataEncryptionAlgorithm::Camellia192Cbc,
DataEncryptionAlgorithm::Camellia256Cbc,
DataEncryptionAlgorithm::ChaCha20,
DataEncryptionAlgorithm::ChaCha20Poly1305,
#[cfg(feature = "legacy-algorithms")]
DataEncryptionAlgorithm::TripleDesCbc,
#[cfg(feature = "legacy-algorithms")]
DataEncryptionAlgorithm::Aes192Cbc,
#[cfg(feature = "legacy-algorithms")]
DataEncryptionAlgorithm::Aes192Gcm,
];
#[cfg(feature = "xmlenc")]
const COMPATIBLE_WRAP_METHODS: &[KeyWrapAlgorithm] = &[
KeyWrapAlgorithm::AesKw128,
KeyWrapAlgorithm::AesKw256,
KeyWrapAlgorithm::CamelliaKw128,
KeyWrapAlgorithm::CamelliaKw192,
KeyWrapAlgorithm::CamelliaKw256,
#[cfg(feature = "legacy-algorithms")]
KeyWrapAlgorithm::TripleDes,
#[cfg(feature = "legacy-algorithms")]
KeyWrapAlgorithm::AesKw192,
];
#[cfg(feature = "xmlenc")]
const COMPATIBLE_TRANSPORT_METHODS: &[KeyTransportAlgorithm] = &[
KeyTransportAlgorithm::RsaOaepMgf1p,
KeyTransportAlgorithm::RsaOaep11,
#[cfg(feature = "legacy-algorithms")]
KeyTransportAlgorithm::RsaPkcs1v15,
];
#[cfg(feature = "xmlenc")]
const OAEP_MGF_DIGESTS: [OaepDigestAlgorithm; 5] = [
OaepDigestAlgorithm::Sha1,
OaepDigestAlgorithm::Sha224,
OaepDigestAlgorithm::Sha256,
OaepDigestAlgorithm::Sha384,
OaepDigestAlgorithm::Sha512,
];
#[cfg(feature = "xmlenc")]
fn encryption_profile(
provider: &dyn CryptoProvider,
validity: Option<PolicyValidity>,
) -> EncryptionPolicy {
EncryptionPolicy {
validity,
data_algorithms: Some(
MODERN_CONTENT_METHODS
.into_iter()
.filter(|a| provider.supports(ProviderCapability::Encrypt(*a)))
.collect(),
),
key_transport_algorithms: Some(HashSet::from([KeyTransportAlgorithm::RsaOaep11])),
key_wrap_algorithms: Some(
MODERN_WRAP_METHODS
.into_iter()
.filter(|a| provider.supports(ProviderCapability::KeyWrap(*a)))
.collect(),
),
oaep_digests: Some(HashSet::from(MODERN_OAEP_DIGESTS)),
..EncryptionPolicy::default()
}
}
#[cfg(feature = "xmlenc")]
fn decryption_profile(
provider: &dyn CryptoProvider,
compatibility: bool,
validity: Option<PolicyValidity>,
) -> DecryptionPolicy {
let content = if compatibility {
COMPATIBLE_CONTENT_METHODS
} else {
&MODERN_CONTENT_METHODS
};
let wraps = if compatibility {
COMPATIBLE_WRAP_METHODS
} else {
&MODERN_WRAP_METHODS
};
let cbc_methods = if compatibility {
COMPATIBLE_CONTENT_METHODS
} else {
&[]
};
let oaep_digests = if compatibility {
DigestAlgorithm::ALL
.into_iter()
.filter(|digest| provider.supports(ProviderCapability::Digest(*digest)))
.filter_map(|digest| OaepDigestAlgorithm::from_uri(digest.uri()))
.collect()
} else {
HashSet::from(MODERN_OAEP_DIGESTS)
};
let transports = if compatibility {
COMPATIBLE_TRANSPORT_METHODS
.iter()
.copied()
.filter(|method| recovery_method_available(provider, *method, &oaep_digests))
.collect()
} else {
HashSet::from([KeyTransportAlgorithm::RsaOaep11])
};
DecryptionPolicy {
validity,
data_algorithms: Some(
content
.iter()
.copied()
.filter(|algorithm| provider.supports(ProviderCapability::Decrypt(*algorithm)))
.collect(),
),
key_transport_algorithms: Some(transports),
key_wrap_algorithms: Some(
wraps
.iter()
.copied()
.chain(
cbc_methods
.iter()
.copied()
.filter(|algorithm| algorithm.cbc_block_len().is_some())
.map(KeyWrapAlgorithm::Cbc),
)
.filter(|algorithm| provider.supports(ProviderCapability::KeyUnwrap(*algorithm)))
.collect(),
),
oaep_digests: Some(oaep_digests),
..DecryptionPolicy::default()
}
}
#[cfg(feature = "xmlenc")]
fn recovery_method_available(
provider: &dyn CryptoProvider,
method: KeyTransportAlgorithm,
digests: &HashSet<OaepDigestAlgorithm>,
) -> bool {
#[cfg(feature = "legacy-algorithms")]
if method == KeyTransportAlgorithm::RsaPkcs1v15 {
return provider.supports(ProviderCapability::Pkcs1v15Recovery);
}
digests.iter().any(|&digest| {
OAEP_MGF_DIGESTS.into_iter().any(|mgf| {
digests.contains(&mgf)
&& provider.supports(ProviderCapability::KeyRecovery(
&crate::xmlenc::RsaOaepParameters {
algorithm: method,
digest,
mgf_digest: mgf,
label: Vec::new(),
},
))
})
})
}