1use super::*;
4use crate::provider::{CryptoProvider, ProviderCapability};
5
6mod composition;
7mod constraints;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum PolicyProfile {
13 XmlSecCompatible,
15 Modern,
17 SecureSaml,
19 WsSecurity,
21 Xades,
23 LegacyVerify,
25}
26
27#[derive(Debug, Clone, PartialEq, Eq)]
31pub struct PolicyValidity {
32 pub authorization_id: String,
34 pub authority: String,
36 pub reason: String,
38 pub not_before: SystemTime,
40 pub expires_at: SystemTime,
42}
43
44impl PolicyValidity {
45 pub fn validate_at(&self, now: SystemTime) -> Result<(), PolicyViolation> {
47 if self.authorization_id.trim().is_empty()
48 || self.authority.trim().is_empty()
49 || self.reason.trim().is_empty()
50 || self.not_before >= self.expires_at
51 {
52 return Err(configuration(
53 "authorization needs identity, authority, reason and a nonempty validity interval",
54 ));
55 }
56 if now < self.not_before || now >= self.expires_at {
57 return Err(PolicyViolation::AuthorizationExpired);
58 }
59 Ok(())
60 }
61}
62
63#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
65pub enum SignatureConstraints {
66 #[default]
68 General,
69 Saml,
71 WsSecurity,
73 Xades,
75}
76
77#[derive(Debug, Clone, Default)]
80pub struct PolicySet {
81 pub verification: Option<VerificationPolicy>,
83 pub signing: Option<SigningPolicy>,
85 #[cfg(feature = "xmlenc")]
87 pub encryption: Option<EncryptionPolicy>,
88 #[cfg(feature = "xmlenc")]
90 pub decryption: Option<DecryptionPolicy>,
91}
92
93pub struct PolicyCompileContext {
97 pub external_resources: bool,
99 pub target_evidence: bool,
101}
102
103#[derive(Debug, Clone)]
106pub struct CompiledPolicies {
107 profile: PolicyProfile,
108 policies: PolicySet,
109}
110
111impl CompiledPolicies {
112 pub const fn profile(&self) -> PolicyProfile {
114 self.profile
115 }
116 pub fn verification(&self) -> Option<&VerificationPolicy> {
118 self.policies.verification.as_ref()
119 }
120 pub fn signing(&self) -> Option<&SigningPolicy> {
122 self.policies.signing.as_ref()
123 }
124 #[cfg(feature = "xmlenc")]
126 pub fn encryption(&self) -> Option<&EncryptionPolicy> {
127 self.policies.encryption.as_ref()
128 }
129 #[cfg(feature = "xmlenc")]
131 pub fn decryption(&self) -> Option<&DecryptionPolicy> {
132 self.policies.decryption.as_ref()
133 }
134}
135
136pub struct PolicyCompiler<'a> {
139 provider: &'a dyn CryptoProvider,
140 profile: PolicyProfile,
141 policies: PolicySet,
142}
143
144impl<'a> PolicyCompiler<'a> {
145 pub fn new(
148 profile: PolicyProfile,
149 provider: &'a dyn CryptoProvider,
150 authorization: Option<PolicyValidity>,
151 ) -> Result<Self, PolicyViolation> {
152 if profile == PolicyProfile::LegacyVerify && authorization.is_none() {
153 return Err(configuration(
154 "LegacyVerify requires expiring authorization",
155 ));
156 }
157 if let Some(validity) = &authorization {
158 validity.validate_at(SystemTime::now())?;
159 }
160 let policies = profile_policies(profile, provider, authorization);
161 Ok(Self {
162 provider,
163 profile,
164 policies,
165 })
166 }
167
168 pub fn restrict(mut self, layer: &PolicySet) -> Result<Self, PolicyViolation> {
171 self.policies = self.policies.intersect(layer)?;
172 Ok(self)
173 }
174
175 pub fn authorize_replacement(
179 mut self,
180 mut replacement: PolicySet,
181 authorization: PolicyValidity,
182 ) -> Result<Self, PolicyViolation> {
183 authorization.validate_at(SystemTime::now())?;
184 replacement.bind_authorization(authorization, &self.policies)?;
185 self.policies = replacement;
186 Ok(self)
187 }
188
189 pub fn compile(
191 self,
192 context: &PolicyCompileContext,
193 ) -> Result<CompiledPolicies, PolicyViolation> {
194 self.policies.validate(context, self.provider)?;
195 Ok(CompiledPolicies {
196 profile: self.profile,
197 policies: self.policies,
198 })
199 }
200
201 pub fn policies(&self) -> &PolicySet {
203 &self.policies
204 }
205}
206
207fn configuration(reason: &'static str) -> PolicyViolation {
208 PolicyViolation::Configuration { reason }
209}
210
211fn profile_policies(
212 profile: PolicyProfile,
213 provider: &dyn CryptoProvider,
214 validity: Option<PolicyValidity>,
215) -> PolicySet {
216 let compatibility = matches!(
217 profile,
218 PolicyProfile::XmlSecCompatible | PolicyProfile::LegacyVerify
219 );
220 let constraints = match profile {
221 PolicyProfile::SecureSaml => SignatureConstraints::Saml,
222 PolicyProfile::WsSecurity => SignatureConstraints::WsSecurity,
223 PolicyProfile::Xades => SignatureConstraints::Xades,
224 _ => SignatureConstraints::General,
225 };
226 let signatures: HashSet<_> = SignatureAlgorithm::ALL
227 .into_iter()
228 .filter(|a| provider.supports(ProviderCapability::Verify(*a)))
229 .filter(|a| {
230 compatibility
231 || (a.signing_allowed() && a.hmac_output_bits().is_none_or(|bits| bits >= 256))
232 })
233 .filter(|a| !matches!(a, SignatureAlgorithm::PostQuantum(_)))
234 .collect();
235 let digests: HashSet<_> = DigestAlgorithm::ALL
236 .into_iter()
237 .filter(|a| provider.supports(ProviderCapability::Digest(*a)))
238 .filter(|a| compatibility || (a.signing_allowed() && a.output_len() >= 32))
239 .collect();
240 let transforms = if compatibility {
241 TransformPolicy {
244 xslt_stylesheet_semantics: XsltStylesheetSemantics::Libxmlsec1,
245 ..TransformPolicy::default()
246 }
247 } else {
248 TransformPolicy {
249 allowed_algorithms: Some(
250 [
251 "http://www.w3.org/2000/09/xmldsig#enveloped-signature",
252 "http://www.w3.org/2001/10/xml-exc-c14n#",
253 "http://www.w3.org/2001/10/xml-exc-c14n#WithComments",
254 "http://www.w3.org/TR/2001/REC-xml-c14n-20010315",
255 "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments",
256 "http://www.w3.org/2006/12/xml-c14n11",
257 "http://www.w3.org/2006/12/xml-c14n11#WithComments",
258 ]
259 .into_iter()
260 .map(str::to_owned)
261 .collect(),
262 ),
263 ..TransformPolicy::default()
264 }
265 };
266 let mut verification = VerificationPolicy {
267 validity: validity.clone(),
268 signature_constraints: constraints,
269 signature_algorithms: Some(signatures),
270 parameterized_rsa_pss: if compatibility {
271 RsaPssPermission::AllSupported
272 } else {
273 RsaPssPermission::SecureSupported
274 },
275 digest_algorithms: Some(digests),
276 transforms: transforms.clone(),
277 ..VerificationPolicy::default()
278 };
279 verification.key_trust.parameterized_rsa_pss = verification.parameterized_rsa_pss;
280 if compatibility {
281 verification.key_trust.allowed_legacy_signature_algorithms = verification
282 .signature_algorithms
283 .clone()
284 .unwrap_or_default();
285 verification.key_trust.rsa_keys.minimum_modulus_bits = 1024;
286 verification.key_trust.dsa_keys.minimum_modulus_bits = 1024;
287 verification.hmac.minimum_output_bits = 80;
288 verification.transforms.same_document_id_semantics =
289 SameDocumentIdSemantics::XmlSecBarename;
290 }
291 if constraints != SignatureConstraints::General {
292 verification.key_sources = KeySourcePolicy {
293 preset_key: true,
294 key_name: true,
295 x509_data: true,
296 key_value: false,
297 der_encoded_key_value: false,
298 key_info_reference: false,
299 };
300 }
301 if constraints == SignatureConstraints::Saml {
302 verification.uris.references = UriTypeSet::new(false, true, false);
303 verification.transforms.allowed_algorithms = Some(
304 [
305 "http://www.w3.org/2000/09/xmldsig#enveloped-signature",
306 "http://www.w3.org/2001/10/xml-exc-c14n#",
307 "http://www.w3.org/2001/10/xml-exc-c14n#WithComments",
308 ]
309 .into_iter()
310 .map(str::to_owned)
311 .collect(),
312 );
313 }
314 let signing = SigningPolicy {
315 validity: validity.clone(),
316 signature_constraints: constraints,
317 parameterized_rsa_pss: RsaPssPermission::SecureSupported,
318 signature_algorithms: Some(
319 SignatureAlgorithm::ALL
320 .into_iter()
321 .filter(|a| a.signing_allowed() && !matches!(a, SignatureAlgorithm::PostQuantum(_)))
322 .filter(|a| a.hmac_output_bits().is_none_or(|bits| bits >= 256))
323 .filter(|a| provider.supports(ProviderCapability::Sign(*a)))
324 .collect(),
325 ),
326 digest_algorithms: Some(
327 DigestAlgorithm::ALL
328 .into_iter()
329 .filter(|a| a.signing_allowed() && a.output_len() >= 32)
330 .filter(|a| provider.supports(ProviderCapability::Digest(*a)))
331 .collect(),
332 ),
333 transforms: if constraints == SignatureConstraints::Saml {
334 verification.transforms.clone()
335 } else {
336 transforms
337 },
338 uris: if constraints == SignatureConstraints::Saml {
339 verification.uris
340 } else {
341 UriPolicy::default()
342 },
343 ..SigningPolicy::default()
344 };
345 let legacy = profile == PolicyProfile::LegacyVerify;
346 PolicySet {
347 verification: Some(verification),
348 signing: (!legacy).then_some(signing),
349 #[cfg(feature = "xmlenc")]
350 encryption: (!legacy).then(|| encryption_profile(provider, validity.clone())),
351 #[cfg(feature = "xmlenc")]
352 decryption: (!legacy).then(|| decryption_profile(provider, compatibility, validity)),
353 }
354}
355
356#[cfg(feature = "xmlenc")]
357const MODERN_CONTENT_METHODS: [DataEncryptionAlgorithm; 3] = [
358 DataEncryptionAlgorithm::Aes128Gcm,
359 DataEncryptionAlgorithm::Aes256Gcm,
360 DataEncryptionAlgorithm::ChaCha20Poly1305,
361];
362#[cfg(feature = "xmlenc")]
363const MODERN_WRAP_METHODS: [KeyWrapAlgorithm; 2] =
364 [KeyWrapAlgorithm::AesKw128, KeyWrapAlgorithm::AesKw256];
365#[cfg(feature = "xmlenc")]
366const MODERN_OAEP_DIGESTS: [OaepDigestAlgorithm; 3] = [
367 OaepDigestAlgorithm::Sha256,
368 OaepDigestAlgorithm::Sha384,
369 OaepDigestAlgorithm::Sha512,
370];
371
372#[cfg(feature = "xmlenc")]
373const COMPATIBLE_CONTENT_METHODS: &[DataEncryptionAlgorithm] = &[
374 DataEncryptionAlgorithm::Aes128Cbc,
375 DataEncryptionAlgorithm::Aes256Cbc,
376 DataEncryptionAlgorithm::Aes128Gcm,
377 DataEncryptionAlgorithm::Aes256Gcm,
378 DataEncryptionAlgorithm::Camellia128Cbc,
379 DataEncryptionAlgorithm::Camellia192Cbc,
380 DataEncryptionAlgorithm::Camellia256Cbc,
381 DataEncryptionAlgorithm::ChaCha20,
382 DataEncryptionAlgorithm::ChaCha20Poly1305,
383 #[cfg(feature = "legacy-algorithms")]
384 DataEncryptionAlgorithm::TripleDesCbc,
385 #[cfg(feature = "legacy-algorithms")]
386 DataEncryptionAlgorithm::Aes192Cbc,
387 #[cfg(feature = "legacy-algorithms")]
388 DataEncryptionAlgorithm::Aes192Gcm,
389];
390#[cfg(feature = "xmlenc")]
391const COMPATIBLE_WRAP_METHODS: &[KeyWrapAlgorithm] = &[
392 KeyWrapAlgorithm::AesKw128,
393 KeyWrapAlgorithm::AesKw256,
394 KeyWrapAlgorithm::CamelliaKw128,
395 KeyWrapAlgorithm::CamelliaKw192,
396 KeyWrapAlgorithm::CamelliaKw256,
397 #[cfg(feature = "legacy-algorithms")]
398 KeyWrapAlgorithm::TripleDes,
399 #[cfg(feature = "legacy-algorithms")]
400 KeyWrapAlgorithm::AesKw192,
401];
402#[cfg(feature = "xmlenc")]
403const COMPATIBLE_TRANSPORT_METHODS: &[KeyTransportAlgorithm] = &[
404 KeyTransportAlgorithm::RsaOaepMgf1p,
405 KeyTransportAlgorithm::RsaOaep11,
406 #[cfg(feature = "legacy-algorithms")]
407 KeyTransportAlgorithm::RsaPkcs1v15,
408];
409#[cfg(feature = "xmlenc")]
410const OAEP_MGF_DIGESTS: [OaepDigestAlgorithm; 5] = [
411 OaepDigestAlgorithm::Sha1,
412 OaepDigestAlgorithm::Sha224,
413 OaepDigestAlgorithm::Sha256,
414 OaepDigestAlgorithm::Sha384,
415 OaepDigestAlgorithm::Sha512,
416];
417
418#[cfg(feature = "xmlenc")]
419fn encryption_profile(
420 provider: &dyn CryptoProvider,
421 validity: Option<PolicyValidity>,
422) -> EncryptionPolicy {
423 EncryptionPolicy {
424 validity,
425 data_algorithms: Some(
426 MODERN_CONTENT_METHODS
427 .into_iter()
428 .filter(|a| provider.supports(ProviderCapability::Encrypt(*a)))
429 .collect(),
430 ),
431 key_transport_algorithms: Some(HashSet::from([KeyTransportAlgorithm::RsaOaep11])),
432 key_wrap_algorithms: Some(
433 MODERN_WRAP_METHODS
434 .into_iter()
435 .filter(|a| provider.supports(ProviderCapability::KeyWrap(*a)))
436 .collect(),
437 ),
438 oaep_digests: Some(HashSet::from(MODERN_OAEP_DIGESTS)),
439 ..EncryptionPolicy::default()
440 }
441}
442
443#[cfg(feature = "xmlenc")]
444fn decryption_profile(
445 provider: &dyn CryptoProvider,
446 compatibility: bool,
447 validity: Option<PolicyValidity>,
448) -> DecryptionPolicy {
449 let content = if compatibility {
450 COMPATIBLE_CONTENT_METHODS
451 } else {
452 &MODERN_CONTENT_METHODS
453 };
454 let wraps = if compatibility {
455 COMPATIBLE_WRAP_METHODS
456 } else {
457 &MODERN_WRAP_METHODS
458 };
459 let cbc_methods = if compatibility {
460 COMPATIBLE_CONTENT_METHODS
461 } else {
462 &[]
463 };
464 let oaep_digests = if compatibility {
465 DigestAlgorithm::ALL
466 .into_iter()
467 .filter(|digest| provider.supports(ProviderCapability::Digest(*digest)))
468 .filter_map(|digest| OaepDigestAlgorithm::from_uri(digest.uri()))
469 .collect()
470 } else {
471 HashSet::from(MODERN_OAEP_DIGESTS)
472 };
473 let transports = if compatibility {
474 COMPATIBLE_TRANSPORT_METHODS
475 .iter()
476 .copied()
477 .filter(|method| recovery_method_available(provider, *method, &oaep_digests))
478 .collect()
479 } else {
480 HashSet::from([KeyTransportAlgorithm::RsaOaep11])
481 };
482 DecryptionPolicy {
483 validity,
484 data_algorithms: Some(
485 content
486 .iter()
487 .copied()
488 .filter(|algorithm| provider.supports(ProviderCapability::Decrypt(*algorithm)))
489 .collect(),
490 ),
491 key_transport_algorithms: Some(transports),
492 key_wrap_algorithms: Some(
493 wraps
494 .iter()
495 .copied()
496 .chain(
497 cbc_methods
498 .iter()
499 .copied()
500 .filter(|algorithm| algorithm.cbc_block_len().is_some())
501 .map(KeyWrapAlgorithm::Cbc),
502 )
503 .filter(|algorithm| provider.supports(ProviderCapability::KeyUnwrap(*algorithm)))
504 .collect(),
505 ),
506 oaep_digests: Some(oaep_digests),
507 ..DecryptionPolicy::default()
508 }
509}
510
511#[cfg(feature = "xmlenc")]
512fn recovery_method_available(
513 provider: &dyn CryptoProvider,
514 method: KeyTransportAlgorithm,
515 digests: &HashSet<OaepDigestAlgorithm>,
516) -> bool {
517 #[cfg(feature = "legacy-algorithms")]
518 if method == KeyTransportAlgorithm::RsaPkcs1v15 {
519 return provider.supports(ProviderCapability::Pkcs1v15Recovery);
520 }
521 digests.iter().any(|&digest| {
524 OAEP_MGF_DIGESTS.into_iter().any(|mgf| {
525 digests.contains(&mgf)
526 && provider.supports(ProviderCapability::KeyRecovery(
527 &crate::xmlenc::RsaOaepParameters {
528 algorithm: method,
529 digest,
530 mgf_digest: mgf,
531 label: Vec::new(),
532 },
533 ))
534 })
535 })
536}