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::default()
242 } else {
243 TransformPolicy {
244 allowed_algorithms: Some(
245 [
246 "http://www.w3.org/2000/09/xmldsig#enveloped-signature",
247 "http://www.w3.org/2001/10/xml-exc-c14n#",
248 "http://www.w3.org/2001/10/xml-exc-c14n#WithComments",
249 "http://www.w3.org/TR/2001/REC-xml-c14n-20010315",
250 "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments",
251 "http://www.w3.org/2006/12/xml-c14n11",
252 "http://www.w3.org/2006/12/xml-c14n11#WithComments",
253 ]
254 .into_iter()
255 .map(str::to_owned)
256 .collect(),
257 ),
258 ..TransformPolicy::default()
259 }
260 };
261 let mut verification = VerificationPolicy {
262 validity: validity.clone(),
263 signature_constraints: constraints,
264 signature_algorithms: Some(signatures),
265 parameterized_rsa_pss: if compatibility {
266 RsaPssPermission::AllSupported
267 } else {
268 RsaPssPermission::SecureSupported
269 },
270 digest_algorithms: Some(digests),
271 transforms: transforms.clone(),
272 ..VerificationPolicy::default()
273 };
274 verification.key_trust.parameterized_rsa_pss = verification.parameterized_rsa_pss;
275 if compatibility {
276 verification.key_trust.allowed_legacy_signature_algorithms = verification
277 .signature_algorithms
278 .clone()
279 .unwrap_or_default();
280 verification.key_trust.rsa_keys.minimum_modulus_bits = 1024;
281 verification.key_trust.dsa_keys.minimum_modulus_bits = 1024;
282 verification.hmac.minimum_output_bits = 80;
283 verification.transforms.same_document_id_semantics =
284 SameDocumentIdSemantics::XmlSecBarename;
285 }
286 if constraints != SignatureConstraints::General {
287 verification.key_sources = KeySourcePolicy {
288 preset_key: true,
289 key_name: true,
290 x509_data: true,
291 key_value: false,
292 der_encoded_key_value: false,
293 key_info_reference: false,
294 };
295 }
296 if constraints == SignatureConstraints::Saml {
297 verification.uris.references = UriTypeSet::new(false, true, false);
298 verification.transforms.allowed_algorithms = Some(
299 [
300 "http://www.w3.org/2000/09/xmldsig#enveloped-signature",
301 "http://www.w3.org/2001/10/xml-exc-c14n#",
302 "http://www.w3.org/2001/10/xml-exc-c14n#WithComments",
303 ]
304 .into_iter()
305 .map(str::to_owned)
306 .collect(),
307 );
308 }
309 let signing = SigningPolicy {
310 validity: validity.clone(),
311 signature_constraints: constraints,
312 parameterized_rsa_pss: RsaPssPermission::SecureSupported,
313 signature_algorithms: Some(
314 SignatureAlgorithm::ALL
315 .into_iter()
316 .filter(|a| a.signing_allowed() && !matches!(a, SignatureAlgorithm::PostQuantum(_)))
317 .filter(|a| a.hmac_output_bits().is_none_or(|bits| bits >= 256))
318 .filter(|a| provider.supports(ProviderCapability::Sign(*a)))
319 .collect(),
320 ),
321 digest_algorithms: Some(
322 DigestAlgorithm::ALL
323 .into_iter()
324 .filter(|a| a.signing_allowed() && a.output_len() >= 32)
325 .filter(|a| provider.supports(ProviderCapability::Digest(*a)))
326 .collect(),
327 ),
328 transforms: if constraints == SignatureConstraints::Saml {
329 verification.transforms.clone()
330 } else {
331 transforms
332 },
333 uris: if constraints == SignatureConstraints::Saml {
334 verification.uris
335 } else {
336 UriPolicy::default()
337 },
338 ..SigningPolicy::default()
339 };
340 let legacy = profile == PolicyProfile::LegacyVerify;
341 PolicySet {
342 verification: Some(verification),
343 signing: (!legacy).then_some(signing),
344 #[cfg(feature = "xmlenc")]
345 encryption: (!legacy).then(|| encryption_profile(provider, validity.clone())),
346 #[cfg(feature = "xmlenc")]
347 decryption: (!legacy).then(|| decryption_profile(provider, compatibility, validity)),
348 }
349}
350
351#[cfg(feature = "xmlenc")]
352const MODERN_CONTENT_METHODS: [DataEncryptionAlgorithm; 3] = [
353 DataEncryptionAlgorithm::Aes128Gcm,
354 DataEncryptionAlgorithm::Aes256Gcm,
355 DataEncryptionAlgorithm::ChaCha20Poly1305,
356];
357#[cfg(feature = "xmlenc")]
358const MODERN_WRAP_METHODS: [KeyWrapAlgorithm; 2] =
359 [KeyWrapAlgorithm::AesKw128, KeyWrapAlgorithm::AesKw256];
360#[cfg(feature = "xmlenc")]
361const MODERN_OAEP_DIGESTS: [OaepDigestAlgorithm; 3] = [
362 OaepDigestAlgorithm::Sha256,
363 OaepDigestAlgorithm::Sha384,
364 OaepDigestAlgorithm::Sha512,
365];
366
367#[cfg(feature = "xmlenc")]
368const COMPATIBLE_CONTENT_METHODS: &[DataEncryptionAlgorithm] = &[
369 DataEncryptionAlgorithm::Aes128Cbc,
370 DataEncryptionAlgorithm::Aes256Cbc,
371 DataEncryptionAlgorithm::Aes128Gcm,
372 DataEncryptionAlgorithm::Aes256Gcm,
373 DataEncryptionAlgorithm::Camellia128Cbc,
374 DataEncryptionAlgorithm::Camellia192Cbc,
375 DataEncryptionAlgorithm::Camellia256Cbc,
376 DataEncryptionAlgorithm::ChaCha20,
377 DataEncryptionAlgorithm::ChaCha20Poly1305,
378 #[cfg(feature = "legacy-algorithms")]
379 DataEncryptionAlgorithm::TripleDesCbc,
380 #[cfg(feature = "legacy-algorithms")]
381 DataEncryptionAlgorithm::Aes192Cbc,
382 #[cfg(feature = "legacy-algorithms")]
383 DataEncryptionAlgorithm::Aes192Gcm,
384];
385#[cfg(feature = "xmlenc")]
386const COMPATIBLE_WRAP_METHODS: &[KeyWrapAlgorithm] = &[
387 KeyWrapAlgorithm::AesKw128,
388 KeyWrapAlgorithm::AesKw256,
389 KeyWrapAlgorithm::CamelliaKw128,
390 KeyWrapAlgorithm::CamelliaKw192,
391 KeyWrapAlgorithm::CamelliaKw256,
392 #[cfg(feature = "legacy-algorithms")]
393 KeyWrapAlgorithm::TripleDes,
394 #[cfg(feature = "legacy-algorithms")]
395 KeyWrapAlgorithm::AesKw192,
396];
397#[cfg(feature = "xmlenc")]
398const COMPATIBLE_TRANSPORT_METHODS: &[KeyTransportAlgorithm] = &[
399 KeyTransportAlgorithm::RsaOaepMgf1p,
400 KeyTransportAlgorithm::RsaOaep11,
401 #[cfg(feature = "legacy-algorithms")]
402 KeyTransportAlgorithm::RsaPkcs1v15,
403];
404#[cfg(feature = "xmlenc")]
405const OAEP_MGF_DIGESTS: [OaepDigestAlgorithm; 5] = [
406 OaepDigestAlgorithm::Sha1,
407 OaepDigestAlgorithm::Sha224,
408 OaepDigestAlgorithm::Sha256,
409 OaepDigestAlgorithm::Sha384,
410 OaepDigestAlgorithm::Sha512,
411];
412
413#[cfg(feature = "xmlenc")]
414fn encryption_profile(
415 provider: &dyn CryptoProvider,
416 validity: Option<PolicyValidity>,
417) -> EncryptionPolicy {
418 EncryptionPolicy {
419 validity,
420 data_algorithms: Some(
421 MODERN_CONTENT_METHODS
422 .into_iter()
423 .filter(|a| provider.supports(ProviderCapability::Encrypt(*a)))
424 .collect(),
425 ),
426 key_transport_algorithms: Some(HashSet::from([KeyTransportAlgorithm::RsaOaep11])),
427 key_wrap_algorithms: Some(
428 MODERN_WRAP_METHODS
429 .into_iter()
430 .filter(|a| provider.supports(ProviderCapability::KeyWrap(*a)))
431 .collect(),
432 ),
433 oaep_digests: Some(HashSet::from(MODERN_OAEP_DIGESTS)),
434 ..EncryptionPolicy::default()
435 }
436}
437
438#[cfg(feature = "xmlenc")]
439fn decryption_profile(
440 provider: &dyn CryptoProvider,
441 compatibility: bool,
442 validity: Option<PolicyValidity>,
443) -> DecryptionPolicy {
444 let content = if compatibility {
445 COMPATIBLE_CONTENT_METHODS
446 } else {
447 &MODERN_CONTENT_METHODS
448 };
449 let wraps = if compatibility {
450 COMPATIBLE_WRAP_METHODS
451 } else {
452 &MODERN_WRAP_METHODS
453 };
454 let cbc_methods = if compatibility {
455 COMPATIBLE_CONTENT_METHODS
456 } else {
457 &[]
458 };
459 let oaep_digests = if compatibility {
460 DigestAlgorithm::ALL
461 .into_iter()
462 .filter(|digest| provider.supports(ProviderCapability::Digest(*digest)))
463 .filter_map(|digest| OaepDigestAlgorithm::from_uri(digest.uri()))
464 .collect()
465 } else {
466 HashSet::from(MODERN_OAEP_DIGESTS)
467 };
468 let transports = if compatibility {
469 COMPATIBLE_TRANSPORT_METHODS
470 .iter()
471 .copied()
472 .filter(|method| recovery_method_available(provider, *method, &oaep_digests))
473 .collect()
474 } else {
475 HashSet::from([KeyTransportAlgorithm::RsaOaep11])
476 };
477 DecryptionPolicy {
478 validity,
479 data_algorithms: Some(
480 content
481 .iter()
482 .copied()
483 .filter(|algorithm| provider.supports(ProviderCapability::Decrypt(*algorithm)))
484 .collect(),
485 ),
486 key_transport_algorithms: Some(transports),
487 key_wrap_algorithms: Some(
488 wraps
489 .iter()
490 .copied()
491 .chain(
492 cbc_methods
493 .iter()
494 .copied()
495 .filter(|algorithm| algorithm.cbc_block_len().is_some())
496 .map(KeyWrapAlgorithm::Cbc),
497 )
498 .filter(|algorithm| provider.supports(ProviderCapability::KeyUnwrap(*algorithm)))
499 .collect(),
500 ),
501 oaep_digests: Some(oaep_digests),
502 ..DecryptionPolicy::default()
503 }
504}
505
506#[cfg(feature = "xmlenc")]
507fn recovery_method_available(
508 provider: &dyn CryptoProvider,
509 method: KeyTransportAlgorithm,
510 digests: &HashSet<OaepDigestAlgorithm>,
511) -> bool {
512 #[cfg(feature = "legacy-algorithms")]
513 if method == KeyTransportAlgorithm::RsaPkcs1v15 {
514 return provider.supports(ProviderCapability::Pkcs1v15Recovery);
515 }
516 digests.iter().any(|&digest| {
519 OAEP_MGF_DIGESTS.into_iter().any(|mgf| {
520 digests.contains(&mgf)
521 && provider.supports(ProviderCapability::KeyRecovery(
522 &crate::xmlenc::RsaOaepParameters {
523 algorithm: method,
524 digest,
525 mgf_digest: mgf,
526 label: Vec::new(),
527 },
528 ))
529 })
530 })
531}