1use std::collections::HashSet;
4
5#[cfg(test)]
6use crate::xmldsig::DsaSigningKey;
7#[cfg(test)]
8use base64::Engine as _;
9use crypto_bigint::{
10 BoxedUint,
11 modular::{BoxedMontyForm, BoxedMontyParams},
12};
13use der::Decode as _;
14use dsa::{
15 Components as DsaComponents, SigningKey as NativeDsaSigningKey,
16 VerifyingKey as DsaVerifyingKey, pkcs8::EncodePrivateKey as _,
17};
18mod pkcs12_import;
19use crate::rsa_encoding::RsaPrivateKeyEncoding as _;
20#[cfg(feature = "xmlenc")]
21use crate::rsa_encoding::RsaPublicKeyEncoding as _;
22use pkcs8::{DecodePrivateKey as _, EncryptedPrivateKeyInfoRef, PrivateKeyInfoRef};
23use pkcs12_import::Limits as Pkcs12Limits;
24use rsa::RsaPrivateKey;
25#[cfg(feature = "xmlenc")]
26use rsa::RsaPublicKey;
27use x509_parser::prelude::{FromDer as _, X509Certificate};
28use zeroize::Zeroizing;
29
30#[cfg(feature = "xmlenc")]
31use crate::xmldsig::parse::X509PublicKeyInfo;
32use crate::{
33 XmlBackend, XmlDomNode as Node,
34 document::{
35 DocumentParseSettings, XmlParseWorkBudget, parse_borrowed_with_settings_and_budget,
36 },
37 policy::ResourcePolicy,
38 xmldsig::keys::InspectedKeyCandidateBudget,
39 xmldsig::parse::XMLDSIG11_NS,
40 xmldsig::{
41 DefaultKeyResolver, DsigError, HmacSigningKey, HmacVerificationKey, KeyInfo, KeyInfoSource,
42 KeyResolver, KeyResolverConfig, KeyValueInfo, SignatureAlgorithm, SigningKey, VerifyingKey,
43 X509DataInfo, validate_signing_key,
44 },
45};
46
47const XMLSEC_NS: &str = "http://www.aleksey.com/xmlsec/2002";
48const XMLDSIG_NS: &str = "http://www.w3.org/2000/09/xmldsig#";
49
50enum PublicDerFormat {
51 KeyOrCertificate,
52 SubjectPublicKeyInfo,
53}
54
55fn check_selected_public_material(
56 info: &KeyInfo,
57 resources: &ResourcePolicy,
58) -> Result<usize, DsigError> {
59 let mut total = 0_usize;
60 for source in &info.sources {
61 let mut charge = |length: usize| -> Result<(), DsigError> {
62 if length > resources.max_external_resource_bytes {
63 return Err(crate::policy::PolicyViolation::ResourceLimit {
64 resource: crate::policy::resource_name::EXTERNAL_RESOURCE_BYTES,
65 maximum: resources.max_external_resource_bytes,
66 actual: length,
67 }
68 .into());
69 }
70 total = total.checked_add(length).ok_or({
71 crate::policy::PolicyViolation::ResourceLimit {
72 resource: crate::policy::resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES,
73 maximum: resources.max_external_resource_total_bytes,
74 actual: usize::MAX,
75 }
76 })?;
77 if total > resources.max_external_resource_total_bytes {
78 return Err(crate::policy::PolicyViolation::ResourceLimit {
79 resource: crate::policy::resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES,
80 maximum: resources.max_external_resource_total_bytes,
81 actual: total,
82 }
83 .into());
84 }
85 Ok(())
86 };
87 match source {
88 KeyInfoSource::KeyValue(value) => {
89 let lengths = match value {
93 KeyValueInfo::Rsa { modulus, exponent } => {
94 [modulus.len(), exponent.len(), 0, 0, 0, 0]
95 }
96 KeyValueInfo::Dsa { p, q, g, y } => [
97 p.as_ref().map_or(0, Vec::len),
98 q.as_ref().map_or(0, Vec::len),
99 g.as_ref().map_or(0, Vec::len),
100 y.len(),
101 0,
102 0,
103 ],
104 KeyValueInfo::Dh {
105 p,
106 q,
107 generator,
108 public,
109 seed,
110 pgen_counter,
111 } => [
112 p.as_ref().map_or(0, Vec::len),
113 q.as_ref().map_or(0, Vec::len),
114 generator.as_ref().map_or(0, Vec::len),
115 public.len(),
116 seed.as_ref().map_or(0, Vec::len),
117 pgen_counter.as_ref().map_or(0, Vec::len),
118 ],
119 KeyValueInfo::Ec {
120 curve_oid,
121 public_key,
122 } => [curve_oid.len(), public_key.len(), 0, 0, 0, 0],
123 KeyValueInfo::InvalidEcKeyValue | KeyValueInfo::Unsupported { .. } => continue,
124 };
125 let length = lengths.into_iter().try_fold(0_usize, |sum, length| {
126 sum.checked_add(length)
127 .ok_or(crate::policy::PolicyViolation::ResourceLimit {
128 resource: crate::policy::resource_name::EXTERNAL_RESOURCE_BYTES,
129 maximum: resources.max_external_resource_bytes,
130 actual: usize::MAX,
131 })
132 })?;
133 charge(length)?;
134 }
135 KeyInfoSource::DerEncodedKeyValue(bytes) => charge(bytes.len())?,
136 KeyInfoSource::X509Data(data) => {
137 for certificate in &data.certificates {
138 charge(certificate.len())?;
139 }
140 for crl in &data.crls {
141 charge(crl.len())?;
142 }
143 }
144 _ => {}
145 }
146 }
147 Ok(total)
148}
149
150pub struct StoredSymmetricKey {
152 pub name: String,
154 pub kind: SymmetricKeyKind,
156 pub bytes: Zeroizing<Vec<u8>>,
158 pub usages: KeyUsages,
160}
161
162#[derive(Clone, Copy, Debug, PartialEq, Eq)]
163pub enum SymmetricKeyKind {
165 Hmac,
167 Aes,
169 Des,
171}
172
173impl SymmetricKeyKind {
174 pub fn is_encryption_key(self) -> bool {
176 self == Self::Aes || (cfg!(feature = "legacy-algorithms") && self == Self::Des)
177 }
178
179 #[cfg(feature = "xmlenc")]
180 fn accepts_algorithm(self, algorithm: crate::xmlenc::DataEncryptionAlgorithm) -> bool {
181 #[cfg(feature = "legacy-algorithms")]
182 if algorithm == crate::xmlenc::DataEncryptionAlgorithm::TripleDesCbc {
183 return self == Self::Des;
184 }
185 #[cfg(not(feature = "legacy-algorithms"))]
186 let _ = algorithm;
187 self == Self::Aes
188 }
189}
190
191fn des_key_usages(bytes: &[u8]) -> Result<KeyUsages, KeyStoreError> {
192 if cfg!(feature = "legacy-algorithms") && bytes.len() == 24 {
193 Ok(KeyUsages::ENCRYPT.union(KeyUsages::DECRYPT))
194 } else {
195 Err(KeyStoreError::Selection(
196 "unsupported DES key length or mechanism",
197 ))
198 }
199}
200
201#[derive(Default)]
202pub struct KeyInventory {
204 entry_count: usize,
206 symmetric_keys: Vec<StoredSymmetricKey>,
208 public_keys: Vec<StoredPublicKey>,
210 private_keys: Vec<StoredPrivateKey>,
212 lookup_certificates: Vec<Vec<u8>>,
214 trusted_certificates: Vec<Vec<u8>>,
216 crls: Vec<Vec<u8>>,
218 material_bytes: usize,
219 kdf_work: usize,
220}
221
222pub struct XmlKeyStoreImporter<'a, P: crate::document::XmlDocumentPolicy> {
226 policy: &'a P,
227 backend: XmlBackend,
228 parse_work: XmlParseWorkBudget,
229 inspected: usize,
230 inventory: KeyInventory,
231 live_material_bytes: usize,
232}
233
234impl<'a, P: crate::document::XmlDocumentPolicy> XmlKeyStoreImporter<'a, P> {
235 pub fn new(policy: &'a P, backend: XmlBackend) -> Result<Self, KeyStoreError> {
237 Self::with_live_material(policy, backend, 0)
238 }
239
240 pub fn with_live_material(
243 policy: &'a P,
244 backend: XmlBackend,
245 live_material_bytes: usize,
246 ) -> Result<Self, KeyStoreError> {
247 ensure_resource_policy(policy.resource_policy())?;
248 if live_material_bytes > policy.resource_policy().max_external_resource_total_bytes {
249 return Err(import_resource_limit(
250 crate::policy::resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES,
251 policy.resource_policy().max_external_resource_total_bytes,
252 ));
253 }
254 Ok(Self {
255 policy,
256 backend,
257 parse_work: XmlParseWorkBudget::from_resources(policy.resource_policy()),
258 inspected: 0,
259 inventory: KeyInventory::default(),
260 live_material_bytes,
261 })
262 }
263
264 pub fn import(&mut self, bytes: &[u8]) -> Result<(), KeyStoreError> {
267 let store = KeyInventory::from_xml_bytes_with_budget(
268 bytes,
269 self.policy,
270 self.backend,
271 &self.parse_work,
272 &mut self.inspected,
273 self.inventory
274 .material_bytes
275 .checked_add(self.live_material_bytes)
276 .ok_or_else(|| {
277 import_resource_limit(
278 crate::policy::resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES,
279 self.policy
280 .resource_policy()
281 .max_external_resource_total_bytes,
282 )
283 })?,
284 )?;
285 self.inventory.extend(store, self.policy.resource_policy())
286 }
287
288 #[must_use]
290 pub fn finish(self) -> KeyInventory {
291 self.inventory
292 }
293}
294
295#[derive(Default)]
297pub struct SigningLookupBudget {
298 inspected: usize,
299}
300
301#[derive(Clone, Copy, Debug, PartialEq, Eq)]
303pub enum KeyUsage {
304 Sign,
306 Verify,
308 Encrypt,
310 Decrypt,
312}
313
314#[derive(Clone, Copy, Debug, PartialEq, Eq)]
316pub struct KeyUsages(u8);
317
318impl KeyUsages {
319 pub const SIGN: Self = Self(1);
321 pub const VERIFY: Self = Self(2);
323 pub const ENCRYPT: Self = Self(4);
325 pub const DECRYPT: Self = Self(8);
327
328 #[must_use]
330 pub const fn union(self, other: Self) -> Self {
331 Self(self.0 | other.0)
332 }
333
334 #[must_use]
336 pub const fn allows(self, usage: KeyUsage) -> bool {
337 let bit = match usage {
338 KeyUsage::Sign => Self::SIGN.0,
339 KeyUsage::Verify => Self::VERIFY.0,
340 KeyUsage::Encrypt => Self::ENCRYPT.0,
341 KeyUsage::Decrypt => Self::DECRYPT.0,
342 };
343 self.0 & bit != 0
344 }
345}
346
347pub struct StoredPrivateKey {
349 pub name: String,
351 pub pkcs8_der: Zeroizing<Vec<u8>>,
353 pub usages: KeyUsages,
355 pub certificate_chain: Vec<Vec<u8>>,
357 has_matching_leaf: bool,
358}
359
360impl StoredPrivateKey {
361 pub fn decapsulation_key(
363 &self,
364 provider: &dyn crate::provider::CryptoProvider,
365 algorithm: crate::provider::KeyEncapsulationAlgorithm,
366 policy: &crate::policy::KeyEstablishmentPolicy,
367 resources: &ResourcePolicy,
368 ) -> Result<std::sync::Arc<dyn crate::provider::KeyDecapsulationKey>, KeyStoreError> {
369 policy.validate()?;
370 policy.check_encapsulation(algorithm)?;
371 if !self.usages.allows(KeyUsage::Decrypt) {
372 return Err(KeyStoreError::Selection(
373 "key is not authorized for decapsulation",
374 ));
375 }
376 check_operation_material_size(self.pkcs8_der.len(), resources)?;
377 provider
378 .import_decapsulation_key(algorithm, &self.pkcs8_der)
379 .map_err(KeyStoreError::Provider)
380 }
381
382 #[must_use]
384 pub fn matching_certificate_chain(&self) -> Option<&[Vec<u8>]> {
385 self.has_matching_leaf.then_some(&self.certificate_chain)
386 }
387}
388
389pub struct StoredPublicKey {
391 pub name: String,
393 pub key_info: KeyInfo,
395 pub usages: KeyUsages,
397}
398
399impl StoredPublicKey {
400 pub fn encapsulation_key(
402 &self,
403 provider: &dyn crate::provider::CryptoProvider,
404 algorithm: crate::provider::KeyEncapsulationAlgorithm,
405 policy: &crate::policy::KeyEstablishmentPolicy,
406 resources: &ResourcePolicy,
407 ) -> Result<std::sync::Arc<dyn crate::provider::KeyEncapsulationKey>, KeyStoreError> {
408 policy.validate()?;
409 policy.check_encapsulation(algorithm)?;
410 if !self.usages.allows(KeyUsage::Encrypt) {
411 return Err(KeyStoreError::Selection(
412 "key is not authorized for encapsulation",
413 ));
414 }
415 let der = self
416 .key_info
417 .sources
418 .iter()
419 .find_map(|source| match source {
420 KeyInfoSource::DerEncodedKeyValue(der) => Some(der.as_slice()),
421 _ => None,
422 })
423 .ok_or(KeyStoreError::Selection(
424 "recipient requires an ML-KEM SPKI",
425 ))?;
426 check_operation_material_size(der.len(), resources)?;
427 provider
428 .import_encapsulation_key(algorithm, der)
429 .map_err(KeyStoreError::Provider)
430 }
431
432 #[cfg(feature = "xmlenc")]
434 pub fn rsa_encryption_key(
435 &self,
436 policy: &crate::policy::EncryptionPolicy,
437 ) -> Result<RsaPublicKey, KeyStoreError> {
438 policy.validate()?;
439 if !self.usages.allows(KeyUsage::Encrypt) {
440 return Err(KeyStoreError::Selection(
441 "key is not authorized for encryption",
442 ));
443 }
444 for source in &self.key_info.sources {
445 return match source {
446 KeyInfoSource::KeyValue(KeyValueInfo::Rsa { modulus, exponent }) => {
447 rsa_recipient_from_components(modulus, exponent, policy)
448 }
449 KeyInfoSource::DerEncodedKeyValue(der) => {
450 check_encryption_material_size(der.len(), &policy.resources)?;
451 let (rest, spki) = x509_parser::x509::SubjectPublicKeyInfo::from_der(der)
452 .map_err(|_| KeyStoreError::Selection("invalid RSA encryption key"))?;
453 if !rest.is_empty() {
454 return Err(KeyStoreError::Selection("invalid RSA encryption key"));
455 }
456 let x509_parser::public_key::PublicKey::RSA(raw) = spki
457 .parsed()
458 .map_err(|_| KeyStoreError::Selection("invalid RSA encryption key"))?
459 else {
460 return Err(KeyStoreError::Selection(
461 "named key is not an RSA public key",
462 ));
463 };
464 rsa_recipient_preflight(raw.modulus, raw.exponent, policy)?;
465 RsaPublicKey::from_public_key_der(der)
466 .map_err(|_| KeyStoreError::Selection("invalid RSA encryption key"))
467 }
468 KeyInfoSource::X509Data(data) if data.parsed_certificates.len() == 1 => {
469 if let Some(certificate) = data.certificates.first() {
470 check_encryption_material_size(certificate.len(), &policy.resources)?;
471 }
472 match &data.parsed_certificates[0].public_key {
473 X509PublicKeyInfo::Rsa { modulus, exponent } => {
474 rsa_recipient_from_components(modulus, exponent, policy)
475 }
476 _ => Err(KeyStoreError::Selection("certificate does not contain RSA")),
477 }
478 }
479 _ => continue,
480 };
481 }
482 Err(KeyStoreError::Selection(
483 "named key is not an RSA public key",
484 ))
485 }
486}
487
488pub struct InventoryVerificationResolver<'a> {
490 inventory: &'a KeyInventory,
491}
492
493impl<'a> KeyResolver for InventoryVerificationResolver<'a> {
494 fn resolve<'k>(
495 &'k self,
496 key_info: Option<&KeyInfo>,
497 algorithm: SignatureAlgorithm,
498 ) -> Result<Option<Box<dyn VerifyingKey + 'k>>, DsigError> {
499 self.resolve_with_policy_and_provider(
500 key_info,
501 algorithm,
502 &crate::policy::VerificationPolicy::default(),
503 crate::provider::default_provider(),
504 )
505 }
506
507 fn resolve_with_policy<'k>(
508 &'k self,
509 key_info: Option<&KeyInfo>,
510 algorithm: SignatureAlgorithm,
511 policy: &crate::policy::VerificationPolicy,
512 ) -> Result<Option<Box<dyn VerifyingKey + 'k>>, DsigError> {
513 self.resolve_with_policy_and_provider(
514 key_info,
515 algorithm,
516 policy,
517 crate::provider::default_provider(),
518 )
519 }
520
521 fn resolve_with_policy_and_provider<'k>(
522 &'k self,
523 key_info: Option<&KeyInfo>,
524 algorithm: SignatureAlgorithm,
525 policy: &crate::policy::VerificationPolicy,
526 provider: &dyn crate::provider::CryptoProvider,
527 ) -> Result<Option<Box<dyn VerifyingKey + 'k>>, DsigError> {
528 self.resolve_for_verification(key_info, algorithm, policy, provider)
529 .map(|key| key.map(crate::xmldsig::ResolvedVerificationKey::into_key))
530 }
531
532 fn resolve_for_verification<'k>(
533 &'k self,
534 key_info: Option<&KeyInfo>,
535 algorithm: SignatureAlgorithm,
536 policy: &crate::policy::VerificationPolicy,
537 provider: &dyn crate::provider::CryptoProvider,
538 ) -> Result<Option<crate::xmldsig::ResolvedVerificationKey<'k>>, DsigError> {
539 policy.validate()?;
540 if let Some(info) = key_info {
541 crate::xmldsig::keys::validate_key_info_source_permissions(info, policy.key_sources)?;
542 }
543 let mut candidate: Option<&StoredPublicKey> = None;
544 let mut secret_candidate: Option<&StoredSymmetricKey> = None;
545 let mut inspected_candidates = InspectedKeyCandidateBudget::new(policy);
546 for name in key_info
547 .into_iter()
548 .flat_map(|info| &info.sources)
549 .filter_map(|source| match source {
550 KeyInfoSource::KeyName(name) => Some(name.as_str()),
551 _ => None,
552 })
553 {
554 if self.inventory.symmetric_keys.is_empty() && self.inventory.public_keys.is_empty() {
555 inspected_candidates.charge()?;
556 }
557 let mut symmetric_match = None;
558 for entry in &self.inventory.symmetric_keys {
559 inspected_candidates.charge()?;
560 if entry.name == name {
561 symmetric_match = Some(entry);
562 break;
563 }
564 }
565 if let Some(found) = symmetric_match {
566 if !found.usages.allows(KeyUsage::Verify) || found.kind != SymmetricKeyKind::Hmac {
567 return Err(DsigError::InvalidStructure {
568 reason: "named key is not authorized for verification",
569 });
570 }
571 if candidate.is_some()
572 || secret_candidate.is_some_and(|previous| !core::ptr::eq(previous, found))
573 {
574 return Err(DsigError::InvalidStructure {
575 reason: "more than one named verification key matches",
576 });
577 }
578 secret_candidate = Some(found);
579 continue;
580 }
581 let mut public_match = None;
582 for entry in &self.inventory.public_keys {
583 inspected_candidates.charge()?;
584 if entry.name == name {
585 public_match = Some(entry);
586 break;
587 }
588 }
589 if let Some(found) = public_match {
590 if !found.usages.allows(KeyUsage::Verify) {
591 return Err(DsigError::InvalidStructure {
592 reason: "named key is not authorized for verification",
593 });
594 }
595 if secret_candidate.is_some()
596 || candidate.is_some_and(|previous| !core::ptr::eq(previous, found))
597 {
598 return Err(DsigError::InvalidStructure {
599 reason: "more than one named verification key matches",
600 });
601 }
602 candidate = Some(found);
603 }
604 }
605 if let Some(candidate) = secret_candidate {
606 if algorithm.hmac_output_bits().is_none() {
607 return Err(DsigError::InvalidStructure {
608 reason: "named HMAC key is incompatible with signature method",
609 });
610 }
611 for (resource, maximum) in [
612 (
613 crate::policy::resource_name::EXTERNAL_RESOURCE_BYTES,
614 policy.resources.max_external_resource_bytes,
615 ),
616 (
617 crate::policy::resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES,
618 policy.resources.max_external_resource_total_bytes,
619 ),
620 ] {
621 if candidate.bytes.len() > maximum {
622 return Err(crate::policy::PolicyViolation::ResourceLimit {
623 resource,
624 maximum,
625 actual: candidate.bytes.len(),
626 }
627 .into());
628 }
629 }
630 let key = HmacVerificationKey::new(candidate.bytes.to_vec()).map_err(|_| {
631 DsigError::InvalidStructure {
632 reason: "invalid named HMAC key",
633 }
634 })?;
635 return Ok(Some(
636 crate::xmldsig::ResolvedVerificationKey::CallerTrusted(Box::new(key)),
637 ));
638 }
639 let selected_material_bytes = candidate
640 .map(|candidate| check_selected_public_material(&candidate.key_info, &policy.resources))
641 .transpose()?
642 .unwrap_or(0);
643 let selected_info = candidate.map_or(key_info, |entry| Some(&entry.key_info));
644 let configured_x509_index = selected_info.and_then(|info| {
645 info.sources.iter().position(|source| match source {
646 KeyInfoSource::X509Data(data) if data.certificate_chain.is_empty() => {
647 crate::xmldsig::parse::x509_data_has_lookup_identifiers(data)
648 }
649 KeyInfoSource::X509Data(_) => {
650 policy.key_trust.verify_x509_chains
651 || !self.inventory.trusted_certificates.is_empty()
652 }
653 _ => false,
654 })
655 });
656 let mut prefix_error = None;
659 if let Some(info) = selected_info
660 && let Some(first_x509) = configured_x509_index
661 && first_x509 != 0
662 {
663 let prefix_resolver = DefaultKeyResolver::new(KeyResolverConfig::default());
664 let outcome = prefix_resolver.resolve_sources_with_candidate_budget(
665 info,
666 algorithm,
667 policy,
668 provider,
669 &mut inspected_candidates,
670 if candidate.is_some() {
671 crate::xmldsig::keys::ResolutionScope::TrustedPrefix(first_x509)
672 } else {
673 crate::xmldsig::keys::ResolutionScope::DocumentPrefix(first_x509)
674 },
675 )?;
676 if let Some(key) = outcome.key {
677 return Ok(Some(key));
678 }
679 prefix_error = outcome.deferred_error;
680 }
681 let fallback = if configured_x509_index.is_some() {
682 let certificates = self
683 .inventory
684 .lookup_certificates
685 .iter()
686 .chain(&self.inventory.trusted_certificates);
687 let document_crls = key_info
691 .filter(|_| {
692 candidate.is_some()
693 && policy.key_trust.check_crls
694 && policy.key_trust.verify_x509_chains
695 })
696 .into_iter()
697 .flat_map(|info| &info.sources)
698 .filter_map(|source| match source {
699 KeyInfoSource::X509Data(data) => Some(data.crls.as_slice()),
700 _ => None,
701 })
702 .flatten();
703 let crls = self
704 .inventory
705 .crls
706 .iter()
707 .chain(document_crls)
708 .filter(|_| policy.key_trust.check_crls && policy.key_trust.verify_x509_chains);
709 let mut total = selected_material_bytes;
710 for material in certificates.chain(crls.clone()) {
711 if material.len() > policy.resources.max_external_resource_bytes {
712 return Err(crate::policy::PolicyViolation::ResourceLimit {
713 resource: crate::policy::resource_name::EXTERNAL_RESOURCE_BYTES,
714 maximum: policy.resources.max_external_resource_bytes,
715 actual: material.len(),
716 }
717 .into());
718 }
719 for _ in 0..2 {
723 debug_assert!(total <= policy.resources.max_external_resource_total_bytes);
724 if material.len() > policy.resources.max_external_resource_total_bytes - total {
725 return Err(crate::policy::PolicyViolation::ResourceLimit {
726 resource:
727 crate::policy::resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES,
728 maximum: policy.resources.max_external_resource_total_bytes,
729 actual: total.saturating_add(material.len()),
730 }
731 .into());
732 }
733 total += material.len();
734 }
735 }
736 DefaultKeyResolver::new(KeyResolverConfig {
737 lookup_certs: self.inventory.lookup_certificates.clone(),
738 trusted_certs: self.inventory.trusted_certificates.clone(),
739 crls: crls.cloned().collect(),
740 ..KeyResolverConfig::default()
741 })
742 } else {
743 DefaultKeyResolver::new(KeyResolverConfig::default())
744 };
745 if let Some(first_x509) = configured_x509_index
746 && let Some(info) = selected_info
747 {
748 let mut outcome = fallback.resolve_sources_with_candidate_budget(
751 info,
752 algorithm,
753 policy,
754 provider,
755 &mut inspected_candidates,
756 if candidate.is_some() {
757 crate::xmldsig::keys::ResolutionScope::TrustedSuffix(first_x509)
758 } else {
759 crate::xmldsig::keys::ResolutionScope::DocumentSuffix(first_x509)
760 },
761 )?;
762 if outcome.key.is_none()
765 && let Some(error) = prefix_error
766 {
767 outcome.deferred_error = Some(error);
768 }
769 return outcome.finish();
770 }
771 if let Some(candidate) = candidate {
772 return fallback.resolve_trusted_material_with_candidate_budget(
773 &candidate.key_info,
774 algorithm,
775 policy,
776 provider,
777 &mut inspected_candidates,
778 );
779 }
780 fallback.resolve_with_candidate_budget(
781 key_info,
782 algorithm,
783 policy,
784 provider,
785 &mut inspected_candidates,
786 )
787 }
788
789 fn consumes_document_key_info(&self) -> bool {
790 true
791 }
792}
793
794enum ParsedMaterial {
795 Symmetric(SymmetricKeyKind, Zeroizing<Vec<u8>>),
796 Public(KeyValueInfo),
797 PublicDer(Vec<u8>),
798 Dsa(KeyValueInfo, Option<Zeroizing<Vec<u8>>>),
799 Unsupported,
800}
801
802type ParsedDsaKey = (KeyValueInfo, Option<Zeroizing<Vec<u8>>>);
803
804#[cfg(feature = "xmlenc")]
805struct InventoryDirectSymmetric {
806 bytes: Zeroizing<Vec<u8>>,
807 kind: SymmetricKeyKind,
808}
809
810#[cfg(feature = "xmlenc")]
811impl crate::xmlenc::DecryptionKeyResolver for InventoryDirectSymmetric {
812 fn resolve_key(
813 &self,
814 _provider: &dyn crate::provider::CryptoProvider,
815 algorithm: crate::xmlenc::DataEncryptionAlgorithm,
816 encrypted_key: Option<&crate::xmlenc::EncryptedKey>,
817 ) -> Result<Vec<u8>, crate::xmlenc::XmlEncError> {
818 if encrypted_key.is_some() || !self.kind.accepts_algorithm(algorithm) {
819 return Err(crate::xmlenc::XmlEncError::KeyNotFound);
820 }
821 crate::xmlenc::validate_key_len(algorithm, &self.bytes)?;
822 Ok(self.bytes.to_vec())
823 }
824
825 fn resolve_key_candidates(
826 &self,
827 provider: &dyn crate::provider::CryptoProvider,
828 algorithm: crate::xmlenc::DataEncryptionAlgorithm,
829 encrypted_key: Option<&crate::xmlenc::EncryptedKey>,
830 budget: &mut crate::xmlenc::KeyCandidateBudget,
831 ) -> Result<Vec<Vec<u8>>, crate::xmlenc::XmlEncError> {
832 if encrypted_key.is_some() {
835 return Err(crate::xmlenc::XmlEncError::KeyNotFound);
836 }
837 budget.consume(1)?;
838 self.resolve_key(provider, algorithm, None)
839 .map(|key| vec![key])
840 }
841
842 fn resolve_key_candidates_with_policy(
843 &self,
844 provider: &dyn crate::provider::CryptoProvider,
845 algorithm: crate::xmlenc::DataEncryptionAlgorithm,
846 encrypted_key: Option<&crate::xmlenc::EncryptedKey>,
847 policy: &crate::policy::DecryptionPolicy,
848 budget: &mut crate::xmlenc::KeyCandidateBudget,
849 ) -> Result<Vec<Vec<u8>>, crate::xmlenc::XmlEncError> {
850 policy.validate()?;
851 if encrypted_key.is_some() {
852 return Err(crate::xmlenc::XmlEncError::KeyNotFound);
853 }
854 check_selected_material_size(self.bytes.len(), &policy.resources).map_err(|error| {
855 match error {
856 KeyStoreError::Policy(violation) => crate::xmlenc::XmlEncError::Policy(violation),
857 other => crate::xmlenc::XmlEncError::InvalidStructure(other.to_string()),
858 }
859 })?;
860 self.resolve_key_candidates(provider, algorithm, None, budget)
861 }
862}
863
864#[derive(Debug, thiserror::Error)]
865pub enum KeyStoreError {
867 #[error(transparent)]
869 Provider(#[from] crate::provider::ProviderError),
870 #[error("invalid xmlsec keys.xml: {0}")]
872 Invalid(String),
873 #[error("key inventory selection failed: {0}")]
875 Selection(&'static str),
876 #[error("key inventory policy violation: {0}")]
878 Policy(#[from] crate::policy::PolicyViolation),
879 #[error("protected key container could not be decoded")]
881 ProtectedContainer,
882}
883
884impl KeyInventory {
885 #[must_use]
889 pub fn key_import_kdf_work(&self) -> usize {
890 self.kdf_work
891 }
892
893 fn retained_material_bytes(&self) -> Result<usize, KeyStoreError> {
894 let mut total = 0_usize;
895 let mut add = |length: usize| -> Result<(), KeyStoreError> {
896 total = total
897 .checked_add(length)
898 .ok_or(KeyStoreError::Selection("key material size overflow"))?;
899 Ok(())
900 };
901 for key in &self.symmetric_keys {
902 add(key.name.len())?;
903 add(key.bytes.len())?;
904 }
905 for key in &self.private_keys {
906 add(key.name.len())?;
907 add(key.pkcs8_der.len())?;
908 for certificate in &key.certificate_chain {
909 add(certificate.len())?;
910 }
911 }
912 for key in &self.public_keys {
913 add(key.name.len())?;
914 for source in &key.key_info.sources {
915 match source {
916 KeyInfoSource::KeyName(name) => add(name.len())?,
917 KeyInfoSource::KeyValue(KeyValueInfo::Dsa { p, q, g, y }) => {
918 add(p.as_ref().map_or(0, Vec::len))?;
919 add(q.as_ref().map_or(0, Vec::len))?;
920 add(g.as_ref().map_or(0, Vec::len))?;
921 add(y.len())?;
922 }
923 KeyInfoSource::KeyValue(KeyValueInfo::Rsa { modulus, exponent }) => {
924 add(modulus.len())?;
925 add(exponent.len())?;
926 }
927 KeyInfoSource::KeyValue(KeyValueInfo::Ec {
928 curve_oid,
929 public_key,
930 }) => {
931 add(curve_oid.len())?;
932 add(public_key.len())?;
933 }
934 KeyInfoSource::DerEncodedKeyValue(bytes) => add(bytes.len())?,
935 _ => {}
936 }
937 }
938 }
939 for certificate in self
940 .lookup_certificates
941 .iter()
942 .chain(&self.trusted_certificates)
943 {
944 add(certificate.len())?;
945 }
946 for crl in &self.crls {
947 add(crl.len())?;
948 }
949 Ok(total)
950 }
951
952 #[must_use]
954 pub fn entry_count(&self) -> usize {
955 self.entry_count
956 }
957
958 #[must_use]
960 pub fn symmetric_keys(&self) -> &[StoredSymmetricKey] {
961 &self.symmetric_keys
962 }
963
964 #[must_use]
966 pub fn public_keys(&self) -> &[StoredPublicKey] {
967 &self.public_keys
968 }
969
970 #[must_use]
972 pub fn private_keys(&self) -> &[StoredPrivateKey] {
973 &self.private_keys
974 }
975
976 pub fn extend(
979 &mut self,
980 mut other: Self,
981 resources: &ResourcePolicy,
982 ) -> Result<(), KeyStoreError> {
983 ensure_resource_policy(resources)?;
984 let work = self
985 .kdf_work
986 .checked_add(other.kdf_work)
987 .filter(|work| *work <= resources.max_key_import_kdf_work)
988 .ok_or_else(|| {
989 import_resource_limit(
990 crate::policy::resource_name::KEY_IMPORT_KDF_WORK,
991 resources.max_key_import_kdf_work,
992 )
993 })?;
994 let candidates = self
995 .entry_count
996 .checked_add(other.entry_count)
997 .ok_or(KeyStoreError::Selection("key candidate count overflow"))?;
998 if candidates > resources.max_key_candidates {
999 return Err(import_resource_limit(
1000 crate::policy::resource_name::KEY_CANDIDATES,
1001 resources.max_key_candidates,
1002 ));
1003 }
1004 let bytes = self
1005 .material_bytes
1006 .checked_add(other.material_bytes)
1007 .ok_or(KeyStoreError::Selection("key material size overflow"))?;
1008 if bytes > resources.max_external_resource_total_bytes {
1009 return Err(import_resource_limit(
1010 crate::policy::resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES,
1011 resources.max_external_resource_total_bytes,
1012 ));
1013 }
1014 let mut names = HashSet::new();
1015 for name in other
1016 .symmetric_keys
1017 .iter()
1018 .map(|entry| entry.name.as_str())
1019 .chain(other.public_keys.iter().map(|entry| entry.name.as_str()))
1020 .chain(other.private_keys.iter().map(|entry| entry.name.as_str()))
1021 {
1022 names.insert(name);
1023 }
1024 if names.iter().any(|name| {
1025 self.symmetric_keys.iter().any(|entry| entry.name == *name)
1026 || self.public_keys.iter().any(|entry| entry.name == *name)
1027 || self.private_keys.iter().any(|entry| entry.name == *name)
1028 }) {
1029 return Err(KeyStoreError::Selection("duplicate key name"));
1030 }
1031 self.entry_count = candidates;
1032 self.material_bytes = bytes;
1033 self.kdf_work = work;
1034 self.symmetric_keys.append(&mut other.symmetric_keys);
1035 self.public_keys.append(&mut other.public_keys);
1036 self.private_keys.append(&mut other.private_keys);
1037 self.lookup_certificates
1038 .append(&mut other.lookup_certificates);
1039 self.trusted_certificates
1040 .append(&mut other.trusted_certificates);
1041 self.crls.append(&mut other.crls);
1042 Ok(())
1043 }
1044
1045 #[cfg(feature = "xmlenc")]
1048 pub fn rsa_encryption_key(
1049 &self,
1050 name: &str,
1051 policy: &crate::policy::EncryptionPolicy,
1052 ) -> Result<RsaPublicKey, KeyStoreError> {
1053 policy.validate()?;
1054 let entry = find_named_entry(
1055 &self.public_keys,
1056 name,
1057 &policy.resources,
1058 &mut 0,
1059 |entry| &entry.name,
1060 )?
1061 .ok_or(KeyStoreError::Selection("named encryption key not found"))?;
1062 entry.rsa_encryption_key(policy)
1063 }
1064
1065 pub fn signing_key(
1067 &self,
1068 name: &str,
1069 algorithm: SignatureAlgorithm,
1070 policy: &crate::policy::SigningPolicy,
1071 ) -> Result<Box<dyn SigningKey>, KeyStoreError> {
1072 self.signing_key_with_budget(name, algorithm, policy, &mut SigningLookupBudget::default())
1073 }
1074
1075 pub fn signing_key_with_budget(
1077 &self,
1078 name: &str,
1079 algorithm: SignatureAlgorithm,
1080 policy: &crate::policy::SigningPolicy,
1081 budget: &mut SigningLookupBudget,
1082 ) -> Result<Box<dyn SigningKey>, KeyStoreError> {
1083 self.signing_key_with_provider_and_budget(
1084 name,
1085 algorithm,
1086 policy,
1087 crate::provider::default_provider(),
1088 budget,
1089 )
1090 }
1091
1092 pub fn signing_key_with_provider(
1094 &self,
1095 name: &str,
1096 algorithm: SignatureAlgorithm,
1097 policy: &crate::policy::SigningPolicy,
1098 provider: &dyn crate::provider::CryptoProvider,
1099 ) -> Result<Box<dyn SigningKey>, KeyStoreError> {
1100 self.signing_key_with_provider_and_budget(
1101 name,
1102 algorithm,
1103 policy,
1104 provider,
1105 &mut SigningLookupBudget::default(),
1106 )
1107 }
1108
1109 pub fn signing_key_with_provider_and_budget(
1111 &self,
1112 name: &str,
1113 algorithm: SignatureAlgorithm,
1114 policy: &crate::policy::SigningPolicy,
1115 provider: &dyn crate::provider::CryptoProvider,
1116 budget: &mut SigningLookupBudget,
1117 ) -> Result<Box<dyn SigningKey>, KeyStoreError> {
1118 policy.validate()?;
1119 policy.check_signature_algorithm(algorithm)?;
1120 provider
1121 .require_capability(crate::provider::ProviderCapability::Sign(algorithm))
1122 .map_err(|_| KeyStoreError::Selection("provider does not support signing method"))?;
1123 if algorithm.hmac_output_bits().is_some() {
1124 let entry = find_named_entry(
1125 &self.symmetric_keys,
1126 name,
1127 &policy.resources,
1128 &mut budget.inspected,
1129 |entry| &entry.name,
1130 )?
1131 .ok_or(KeyStoreError::Selection("named signing key not found"))?;
1132 if entry.kind != SymmetricKeyKind::Hmac || !entry.usages.allows(KeyUsage::Sign) {
1133 return Err(KeyStoreError::Selection(
1134 "key is not authorized for signing",
1135 ));
1136 }
1137 check_operation_material_size(entry.bytes.len(), &policy.resources)?;
1138 let key = HmacSigningKey::new(entry.bytes.to_vec())
1139 .map_err(|_| KeyStoreError::Selection("invalid HMAC key"))?;
1140 validate_signing_key(&key, algorithm, policy).map_err(signing_policy_error)?;
1141 return Ok(Box::new(key));
1142 }
1143 let entry = find_named_entry(
1144 &self.private_keys,
1145 name,
1146 &policy.resources,
1147 &mut budget.inspected,
1148 |entry| &entry.name,
1149 )?
1150 .ok_or(KeyStoreError::Selection("named signing key not found"))?;
1151 if !entry.usages.allows(KeyUsage::Sign) {
1152 return Err(KeyStoreError::Selection(
1153 "key is not authorized for signing",
1154 ));
1155 }
1156 let der = entry.pkcs8_der.as_slice();
1157 check_operation_material_size(der.len(), &policy.resources)?;
1158 if let Ok(info) = PrivateKeyInfoRef::try_from(der)
1159 && info.algorithm.oid == dsa::OID
1160 {
1161 preflight_dsa_pkcs8_components(&info)?;
1162 }
1163 let key = provider
1164 .import_signing_key(algorithm, der)
1165 .map_err(|_| KeyStoreError::Selection("incompatible signing key for provider"))?;
1166 validate_signing_key(key.as_ref(), algorithm, policy).map_err(signing_policy_error)?;
1167 Ok(key)
1168 }
1169
1170 #[cfg(feature = "xmlenc")]
1172 pub fn decryption_resolver(
1173 &self,
1174 name: &str,
1175 policy: &crate::policy::DecryptionPolicy,
1176 ) -> Result<Box<dyn crate::xmlenc::DecryptionKeyResolver>, KeyStoreError> {
1177 self.decryption_resolver_with_provider(name, policy, crate::provider::default_provider())
1178 }
1179
1180 #[cfg(feature = "xmlenc")]
1182 pub fn decryption_resolver_with_provider(
1183 &self,
1184 name: &str,
1185 policy: &crate::policy::DecryptionPolicy,
1186 provider: &dyn crate::provider::CryptoProvider,
1187 ) -> Result<Box<dyn crate::xmlenc::DecryptionKeyResolver>, KeyStoreError> {
1188 policy.validate()?;
1189 let mut visited = 0;
1190 if let Some(entry) = find_named_entry(
1191 &self.symmetric_keys,
1192 name,
1193 &policy.resources,
1194 &mut visited,
1195 |entry| &entry.name,
1196 )? {
1197 if !entry.kind.is_encryption_key() || !entry.usages.allows(KeyUsage::Decrypt) {
1198 return Err(KeyStoreError::Selection(
1199 "key is not authorized for decryption",
1200 ));
1201 }
1202 check_selected_material_size(entry.bytes.len(), &policy.resources)?;
1203 return Ok(Box::new(InventoryDirectSymmetric {
1204 bytes: Zeroizing::new(entry.bytes.to_vec()),
1205 kind: entry.kind,
1206 }));
1207 }
1208 let entry = find_named_entry(
1209 &self.private_keys,
1210 name,
1211 &policy.resources,
1212 &mut visited,
1213 |entry| &entry.name,
1214 )?
1215 .ok_or(KeyStoreError::Selection("named decryption key not found"))?;
1216 if !entry.usages.allows(KeyUsage::Decrypt) {
1217 return Err(KeyStoreError::Selection(
1218 "key is not authorized for decryption",
1219 ));
1220 }
1221 check_selected_material_size(entry.pkcs8_der.len(), &policy.resources)?;
1222 let info = PrivateKeyInfoRef::try_from(entry.pkcs8_der.as_slice())
1225 .map_err(|_| KeyStoreError::Selection("incompatible RSA decryption key"))?;
1226 if let Some(algorithm) =
1227 crate::provider::KeyEncapsulationAlgorithm::from_oid(info.algorithm.oid)
1228 {
1229 let key = entry.decapsulation_key(
1230 provider,
1231 algorithm,
1232 &policy.key_establishment,
1233 &policy.resources,
1234 )?;
1235 return Ok(Box::new(
1236 crate::xmlenc::EncapsulationDecryptor::provider_key(key),
1237 ));
1238 }
1239 let components = pkcs1::RsaPrivateKeyRef::from_der(info.private_key.as_bytes())
1240 .map_err(|_| KeyStoreError::Selection("incompatible RSA decryption key"))?;
1241 policy.rsa_keys.validate_components(
1242 "decryption",
1243 components.modulus.as_bytes(),
1244 components.public_exponent.as_bytes(),
1245 )?;
1246 let key = provider
1247 .import_recovery_key(&entry.pkcs8_der)
1248 .map_err(|_| KeyStoreError::Selection("incompatible RSA decryption key"))?;
1249 Ok(Box::new(crate::xmlenc::PrivateKeyDecryptor::provider_key(
1250 key,
1251 )))
1252 }
1253 #[must_use]
1256 pub fn verification_resolver(&self) -> InventoryVerificationResolver<'_> {
1257 InventoryVerificationResolver { inventory: self }
1258 }
1259 pub fn add_symmetric(
1261 &mut self,
1262 name: String,
1263 kind: SymmetricKeyKind,
1264 bytes: Vec<u8>,
1265 usages: KeyUsages,
1266 resources: &ResourcePolicy,
1267 ) -> Result<(), KeyStoreError> {
1268 self.check_new_name(&name, resources)?;
1269 if bytes.len() > resources.max_external_resource_bytes {
1270 return Err(import_resource_limit(
1271 crate::policy::resource_name::EXTERNAL_RESOURCE_BYTES,
1272 resources.max_external_resource_bytes,
1273 ));
1274 }
1275 if bytes.is_empty()
1276 || (kind == SymmetricKeyKind::Aes && !matches!(bytes.len(), 16 | 24 | 32))
1277 {
1278 return Err(KeyStoreError::Selection("invalid symmetric key length"));
1279 }
1280 let permitted = match kind {
1281 SymmetricKeyKind::Hmac => KeyUsages::SIGN.union(KeyUsages::VERIFY),
1282 SymmetricKeyKind::Aes => KeyUsages::ENCRYPT.union(KeyUsages::DECRYPT),
1283 SymmetricKeyKind::Des => des_key_usages(&bytes)?,
1284 };
1285 if usages.0 == 0 || usages.0 & !permitted.0 != 0 {
1286 return Err(KeyStoreError::Selection(
1287 "symmetric key usage is incompatible",
1288 ));
1289 }
1290 self.reserve_material(named_material_length(&name, bytes.len(), 1)?, resources)?;
1291 self.symmetric_keys.push(StoredSymmetricKey {
1292 name,
1293 kind,
1294 bytes: Zeroizing::new(bytes),
1295 usages,
1296 });
1297 self.entry_count += 1;
1298 Ok(())
1299 }
1300
1301 pub fn add_public_der(
1304 &mut self,
1305 name: String,
1306 der: Vec<u8>,
1307 resources: &ResourcePolicy,
1308 ) -> Result<(), KeyStoreError> {
1309 self.add_public_der_inner(name, der, None, resources)
1310 }
1311
1312 pub fn add_public_der_with_usages(
1314 &mut self,
1315 name: String,
1316 der: Vec<u8>,
1317 usages: KeyUsages,
1318 resources: &ResourcePolicy,
1319 ) -> Result<(), KeyStoreError> {
1320 self.add_public_der_inner(name, der, Some(usages), resources)
1321 }
1322
1323 fn add_public_der_inner(
1324 &mut self,
1325 name: String,
1326 der: Vec<u8>,
1327 usages: Option<KeyUsages>,
1328 resources: &ResourcePolicy,
1329 ) -> Result<(), KeyStoreError> {
1330 self.add_public_der_payload(
1331 name,
1332 der,
1333 usages,
1334 resources,
1335 PublicDerFormat::KeyOrCertificate,
1336 )
1337 }
1338
1339 fn add_public_der_payload(
1340 &mut self,
1341 name: String,
1342 der: Vec<u8>,
1343 usages: Option<KeyUsages>,
1344 resources: &ResourcePolicy,
1345 format: PublicDerFormat,
1346 ) -> Result<(), KeyStoreError> {
1347 self.check_new_name(&name, resources)?;
1348 let permitted = KeyUsages::VERIFY.union(KeyUsages::ENCRYPT);
1349 if usages.is_some_and(|usages| usages.0 == 0 || usages.0 & !permitted.0 != 0) {
1350 return Err(KeyStoreError::Selection("public key usage is incompatible"));
1351 }
1352 if der.len() > resources.max_external_resource_bytes {
1353 return Err(import_resource_limit(
1354 crate::policy::resource_name::EXTERNAL_RESOURCE_BYTES,
1355 resources.max_external_resource_bytes,
1356 ));
1357 }
1358 self.check_material_capacity(named_material_length(&name, der.len(), 2)?, resources)?;
1359 #[cfg(feature = "experimental-pq")]
1360 if let Ok(spki) = pkcs8::SubjectPublicKeyInfoRef::from_der(&der)
1361 && crate::provider::KeyEncapsulationAlgorithm::from_oid(spki.algorithm.oid).is_some()
1362 {
1363 crate::provider::RustCryptoMlKemPublicKey::from_spki_der(&der)
1364 .map_err(|_| KeyStoreError::Selection("invalid ML-KEM public key"))?;
1365 let usages = usages.unwrap_or(KeyUsages::ENCRYPT);
1366 if usages != KeyUsages::ENCRYPT {
1367 return Err(KeyStoreError::Selection(
1368 "ML-KEM public keys establish keys, not signatures",
1369 ));
1370 }
1371 self.reserve_material(named_material_length(&name, der.len(), 2)?, resources)?;
1372 self.public_keys.push(StoredPublicKey {
1373 key_info: KeyInfo {
1374 sources: vec![
1375 KeyInfoSource::KeyName(name.clone()),
1376 KeyInfoSource::DerEncodedKeyValue(der),
1377 ],
1378 ..KeyInfo::default()
1379 },
1380 name,
1381 usages,
1382 });
1383 self.entry_count += 1;
1384 return Ok(());
1385 }
1386 let material_len = der.len();
1387 let mut key_info = KeyInfo::default();
1388 key_info.sources.push(KeyInfoSource::KeyName(name.clone()));
1389 let is_rsa = if let Ok((rest, spki)) =
1390 x509_parser::x509::SubjectPublicKeyInfo::from_der(&der)
1391 && rest.is_empty()
1392 && spki.raw == der
1393 {
1394 let is_rsa = crate::xmldsig::keys::supported_parsed_spki_is_rsa(&spki, &der)
1395 .map_err(|_| KeyStoreError::Selection("unsupported public key algorithm"))?;
1396 key_info
1397 .sources
1398 .push(KeyInfoSource::DerEncodedKeyValue(der));
1399 is_rsa
1400 } else {
1401 if matches!(format, PublicDerFormat::SubjectPublicKeyInfo) {
1402 return Err(KeyStoreError::Selection("invalid PUBLIC KEY payload"));
1408 }
1409 let (rest, certificate) = X509Certificate::from_der(&der)
1410 .map_err(|_| KeyStoreError::Selection("invalid public key or X.509 certificate"))?;
1411 if !rest.is_empty() {
1412 return Err(KeyStoreError::Selection("invalid X.509 certificate"));
1413 }
1414 let parsed = crate::xmldsig::parse::parse_x509_certificate(&der)
1415 .map_err(|_| KeyStoreError::Selection("unsupported X.509 certificate key"))?;
1416 let is_rsa = crate::xmldsig::keys::supported_parsed_spki_is_rsa(
1417 certificate.public_key(),
1418 certificate.public_key().raw,
1419 )
1420 .map_err(|_| KeyStoreError::Selection("unsupported X.509 certificate key"))?;
1421 key_info.sources.push(KeyInfoSource::X509Data(X509DataInfo {
1422 certificates: vec![der],
1423 parsed_certificates: vec![parsed],
1424 certificate_chain: vec![0],
1425 ..X509DataInfo::default()
1426 }));
1427 is_rsa
1428 };
1429 let usages = usages.unwrap_or(if is_rsa { permitted } else { KeyUsages::VERIFY });
1430 if usages.allows(KeyUsage::Encrypt) && !is_rsa {
1431 return Err(KeyStoreError::Selection(
1432 "only RSA public keys can be used for encryption",
1433 ));
1434 }
1435 self.reserve_material(named_material_length(&name, material_len, 2)?, resources)?;
1436 self.public_keys.push(StoredPublicKey {
1437 name,
1438 key_info,
1439 usages,
1440 });
1441 self.entry_count += 1;
1442 Ok(())
1443 }
1444
1445 pub fn add_public_pem(
1450 &mut self,
1451 name: String,
1452 bytes: &[u8],
1453 resources: &ResourcePolicy,
1454 ) -> Result<(), KeyStoreError> {
1455 self.add_public_pem_inner(name, bytes, None, resources)
1456 }
1457
1458 pub fn add_public_pem_with_usages(
1460 &mut self,
1461 name: String,
1462 bytes: &[u8],
1463 usages: KeyUsages,
1464 resources: &ResourcePolicy,
1465 ) -> Result<(), KeyStoreError> {
1466 self.add_public_pem_inner(name, bytes, Some(usages), resources)
1467 }
1468
1469 fn add_public_pem_inner(
1470 &mut self,
1471 name: String,
1472 bytes: &[u8],
1473 usages: Option<KeyUsages>,
1474 resources: &ResourcePolicy,
1475 ) -> Result<(), KeyStoreError> {
1476 ensure_resource_policy(resources)?;
1477 self.check_new_name(&name, resources)?;
1478 self.check_material_capacity(named_material_length(&name, bytes.len(), 2)?, resources)?;
1479 let block = BorrowedPem::parse(bytes, resources.max_external_resource_bytes)?;
1480 if !matches!(block.label, "PUBLIC KEY" | "RSA PUBLIC KEY") {
1481 return Err(KeyStoreError::Selection("unsupported public PEM label"));
1482 }
1483 self.check_material_capacity(
1484 named_material_length(
1485 &name,
1486 bytes
1487 .len()
1488 .checked_add(block.decoded_len)
1489 .ok_or(KeyStoreError::Selection("key material size overflow"))?,
1490 2,
1491 )?,
1492 resources,
1493 )?;
1494 let decoded = block.decode()?;
1495 let der = match block.label {
1496 "PUBLIC KEY" => decoded,
1497 "RSA PUBLIC KEY" => {
1498 use der::Encode as _;
1499 let components = pkcs1::RsaPublicKeyRef::from_der(&decoded)
1500 .map_err(|_| KeyStoreError::Selection("invalid RSA public key"))?;
1501 crate::xmldsig::keys::bounded_rsa_public_components(
1502 components.modulus.as_bytes(),
1503 components.public_exponent.as_bytes(),
1504 )
1505 .map_err(|_| KeyStoreError::Selection("RSA public key exceeds safety limit"))?;
1506 let spki = pkcs8::SubjectPublicKeyInfoRef {
1509 algorithm: pkcs1::ALGORITHM_ID,
1510 subject_public_key: der::asn1::BitStringRef::new(0, &decoded)
1511 .map_err(|_| KeyStoreError::Selection("invalid RSA public key"))?,
1512 };
1513 let encoded_len = usize::try_from(
1514 spki.encoded_len()
1515 .map_err(|_| KeyStoreError::Selection("invalid RSA public key"))?,
1516 )
1517 .map_err(|_| KeyStoreError::Selection("invalid RSA public key"))?;
1518 if encoded_len > resources.max_external_resource_bytes {
1519 return Err(import_resource_limit(
1520 crate::policy::resource_name::EXTERNAL_RESOURCE_BYTES,
1521 resources.max_external_resource_bytes,
1522 ));
1523 }
1524 self.check_material_capacity(
1525 named_material_length(
1526 &name,
1527 bytes
1528 .len()
1529 .checked_add(decoded.capacity())
1530 .and_then(|total| total.checked_add(encoded_len))
1531 .ok_or(KeyStoreError::Selection("key material size overflow"))?,
1532 2,
1533 )?,
1534 resources,
1535 )?;
1536 spki.to_der()
1537 .map_err(|_| KeyStoreError::Selection("invalid RSA public key"))?
1538 }
1539 _ => return Err(KeyStoreError::Selection("unsupported public PEM label")),
1540 };
1541 let previous_total = self.material_bytes;
1542 let charged_total = self.check_material_capacity(
1543 named_material_length(&name, bytes.len().max(der.len()), 2)?,
1544 resources,
1545 )?;
1546 self.add_public_der_payload(
1547 name,
1548 der,
1549 usages,
1550 resources,
1551 PublicDerFormat::SubjectPublicKeyInfo,
1552 )?;
1553 debug_assert!(self.material_bytes >= previous_total);
1554 self.material_bytes = charged_total;
1555 Ok(())
1556 }
1557
1558 pub fn add_private_der(
1562 &mut self,
1563 name: String,
1564 bytes: &[u8],
1565 password: Option<&[u8]>,
1566 usages: KeyUsages,
1567 resources: &ResourcePolicy,
1568 ) -> Result<(), KeyStoreError> {
1569 self.add_private_der_inner(name, bytes, password, usages, resources, 0)
1570 }
1571
1572 fn add_private_der_inner(
1573 &mut self,
1574 name: String,
1575 bytes: &[u8],
1576 password: Option<&[u8]>,
1577 usages: KeyUsages,
1578 resources: &ResourcePolicy,
1579 live_encoded_bytes: usize,
1580 ) -> Result<(), KeyStoreError> {
1581 self.check_new_name(&name, resources)?;
1582 if bytes.len() > resources.max_external_resource_bytes {
1583 return Err(import_resource_limit(
1584 crate::policy::resource_name::EXTERNAL_RESOURCE_BYTES,
1585 resources.max_external_resource_bytes,
1586 ));
1587 }
1588 let retained_with_input =
1589 self.check_material_capacity(named_material_length(&name, bytes.len(), 1)?, resources)?;
1590 validate_private_key_usages(usages)?;
1591 let check_plain_output = |output: usize| {
1594 if output > resources.max_external_resource_bytes {
1595 return Err(import_resource_limit(
1596 crate::policy::resource_name::EXTERNAL_RESOURCE_BYTES,
1597 resources.max_external_resource_bytes,
1598 ));
1599 }
1600 let live = bytes
1601 .len()
1602 .checked_add(live_encoded_bytes)
1603 .and_then(|live| live.checked_add(output))
1604 .ok_or_else(|| {
1605 import_resource_limit(
1606 crate::policy::resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES,
1607 resources.max_external_resource_total_bytes,
1608 )
1609 })?;
1610 self.check_material_capacity(named_material_length(&name, live, 1)?, resources)
1611 };
1612 let der = if PrivateKeyInfoRef::try_from(bytes).is_ok() {
1613 check_plain_output(bytes.len())?;
1614 Zeroizing::new(bytes.to_vec())
1615 } else if let Ok(encrypted) = EncryptedPrivateKeyInfoRef::try_from(bytes) {
1616 let kdf_live_bytes = retained_with_input
1619 .checked_add(live_encoded_bytes)
1620 .ok_or_else(|| {
1621 import_resource_limit(
1622 crate::policy::resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES,
1623 resources.max_external_resource_total_bytes,
1624 )
1625 })?;
1626 let remaining = self.remaining_kdf_resources(resources)?;
1627 enforce_pkcs8_kdf_policy(&encrypted, &remaining, kdf_live_bytes)?;
1628 let password = password.ok_or(KeyStoreError::ProtectedContainer)?;
1629 let work =
1630 enforce_pkcs8_password_policy(&encrypted, password, &remaining, kdf_live_bytes)?;
1631 self.kdf_work += work;
1632 let mut plain = Zeroizing::new(encrypted.encrypted_data.as_bytes().to_vec());
1635 let plaintext_len = encrypted
1636 .encryption_algorithm
1637 .decrypt_in_place(password, &mut plain)
1638 .map_err(|_| KeyStoreError::ProtectedContainer)?;
1639 let plaintext_len = plaintext_len.len();
1640 plain.truncate(plaintext_len);
1641 PrivateKeyInfoRef::try_from(plain.as_slice())
1642 .map_err(|_| KeyStoreError::ProtectedContainer)?;
1643 plain
1644 } else {
1645 use der::Encode as _;
1646 preflight_rsa_pkcs1_components(bytes)?;
1647 let normalized = PrivateKeyInfoRef::new(
1651 pkcs1::ALGORITHM_ID,
1652 der::asn1::OctetStringRef::new(bytes)
1653 .map_err(|_| KeyStoreError::Selection("invalid RSA private key"))?,
1654 );
1655 let size = usize::try_from(
1656 normalized
1657 .encoded_len()
1658 .map_err(|_| KeyStoreError::Selection("invalid RSA private key"))?,
1659 )
1660 .map_err(|_| KeyStoreError::Selection("key material size overflow"))?;
1661 check_plain_output(size)?;
1662 let normalized = normalized
1663 .to_der()
1664 .map_err(|_| KeyStoreError::Selection("invalid RSA private key"))?;
1665 Zeroizing::new(normalized)
1666 };
1667 if der.len() > resources.max_external_resource_bytes {
1668 return Err(import_resource_limit(
1669 crate::policy::resource_name::EXTERNAL_RESOURCE_BYTES,
1670 resources.max_external_resource_bytes,
1671 ));
1672 }
1673 let identity = PrivateKeyIdentity::decode(&der)?;
1674 if usages.allows(KeyUsage::Decrypt) && !identity.can_decrypt() {
1675 return Err(KeyStoreError::Selection(
1676 "private key cannot recover encryption keys",
1677 ));
1678 }
1679 identity.validate_usages(usages)?;
1680 drop(identity);
1681 self.reserve_material(
1682 named_material_length(&name, bytes.len().max(der.len()), 1)?,
1683 resources,
1684 )?;
1685 self.private_keys.push(StoredPrivateKey {
1686 name,
1687 pkcs8_der: der,
1688 usages,
1689 certificate_chain: Vec::new(),
1690 has_matching_leaf: false,
1691 });
1692 self.entry_count += 1;
1693 Ok(())
1694 }
1695
1696 pub fn add_private_der_with_password_callback<F>(
1699 &mut self,
1700 name: String,
1701 bytes: &[u8],
1702 password: F,
1703 usages: KeyUsages,
1704 resources: &ResourcePolicy,
1705 ) -> Result<(), KeyStoreError>
1706 where
1707 F: FnOnce() -> Option<Zeroizing<Vec<u8>>>,
1708 {
1709 self.check_new_name(&name, resources)?;
1710 validate_private_key_usages(usages)?;
1711 if bytes.len() > resources.max_external_resource_bytes {
1712 return Err(import_resource_limit(
1713 crate::policy::resource_name::EXTERNAL_RESOURCE_BYTES,
1714 resources.max_external_resource_bytes,
1715 ));
1716 }
1717 let retained_with_input =
1718 self.check_material_capacity(named_material_length(&name, bytes.len(), 1)?, resources)?;
1719 let encrypted = EncryptedPrivateKeyInfoRef::try_from(bytes).ok();
1720 let secret = if let Some(encrypted) = &encrypted {
1721 let remaining = self.remaining_kdf_resources(resources)?;
1722 enforce_pkcs8_kdf_policy(encrypted, &remaining, retained_with_input)?;
1725 Some(password().ok_or(KeyStoreError::ProtectedContainer)?)
1726 } else {
1727 None
1728 };
1729 if let Some(secret) = &secret {
1730 self.check_material_capacity(
1732 named_material_length(
1733 &name,
1734 bytes.len().checked_add(secret.capacity()).ok_or_else(|| {
1735 import_resource_limit(
1736 crate::policy::resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES,
1737 resources.max_external_resource_total_bytes,
1738 )
1739 })?,
1740 1,
1741 )?,
1742 resources,
1743 )?;
1744 let encrypted = encrypted
1745 .as_ref()
1746 .ok_or(KeyStoreError::ProtectedContainer)?;
1747 enforce_pkcs8_password_policy(
1748 encrypted,
1749 secret,
1750 &self.remaining_kdf_resources(resources)?,
1751 retained_with_input + secret.capacity() - secret.len(),
1752 )?;
1753 }
1754 self.add_private_der(
1755 name,
1756 bytes,
1757 secret.as_deref().map(Vec::as_slice),
1758 usages,
1759 resources,
1760 )
1761 }
1762
1763 pub fn add_private_pem(
1766 &mut self,
1767 name: String,
1768 bytes: &[u8],
1769 password: Option<&[u8]>,
1770 usages: KeyUsages,
1771 resources: &ResourcePolicy,
1772 ) -> Result<(), KeyStoreError> {
1773 ensure_resource_policy(resources)?;
1774 self.check_new_name(&name, resources)?;
1775 self.check_material_capacity(named_material_length(&name, bytes.len(), 1)?, resources)?;
1776 let block = BorrowedPem::parse(bytes, resources.max_external_resource_bytes)?;
1777 validate_private_key_usages(usages)?;
1778 self.check_material_capacity(
1779 named_material_length(
1780 &name,
1781 bytes
1782 .len()
1783 .checked_add(block.decoded_len)
1784 .ok_or(KeyStoreError::Selection("key material size overflow"))?,
1785 1,
1786 )?,
1787 resources,
1788 )?;
1789 enum Payload {
1790 Plain,
1791 Encrypted,
1792 Rsa,
1793 }
1794 let payload = match block.label {
1795 "PRIVATE KEY" => Payload::Plain,
1796 "ENCRYPTED PRIVATE KEY" => Payload::Encrypted,
1797 "RSA PRIVATE KEY" => Payload::Rsa,
1798 _ => return Err(KeyStoreError::Selection("unsupported private PEM label")),
1799 };
1800 let mut der = Zeroizing::new(vec![0; block.decoded_len]);
1801 block.decode_into(&mut der)?;
1802 match payload {
1806 Payload::Encrypted => {
1807 EncryptedPrivateKeyInfoRef::try_from(der.as_slice())
1808 .map_err(|_| KeyStoreError::ProtectedContainer)?;
1809 }
1810 Payload::Plain => {
1811 PrivateKeyInfoRef::try_from(der.as_slice())
1812 .map_err(|_| KeyStoreError::Selection("invalid PRIVATE KEY payload"))?;
1813 }
1814 Payload::Rsa => preflight_rsa_pkcs1_components(&der)?,
1815 }
1816 let previous_total = self.material_bytes;
1817 let name_len = name.len();
1818 self.add_private_der_inner(name, &der, password, usages, resources, bytes.len())?;
1819 let retained_len = self.material_bytes - previous_total;
1820 self.material_bytes = previous_total + name_len + bytes.len().max(retained_len - name_len);
1821 Ok(())
1822 }
1823
1824 pub fn add_pkcs12(
1829 &mut self,
1830 name: String,
1831 bytes: &[u8],
1832 password: &str,
1833 resources: &ResourcePolicy,
1834 ) -> Result<(), KeyStoreError> {
1835 self.add_pkcs12_inner(
1836 name,
1837 bytes,
1838 password,
1839 KeyUsages::SIGN.union(KeyUsages::DECRYPT),
1840 resources,
1841 true,
1842 )
1843 }
1844
1845 pub fn add_pkcs12_with_password_callback<F>(
1848 &mut self,
1849 name: String,
1850 bytes: &[u8],
1851 password: F,
1852 usages: KeyUsages,
1853 resources: &ResourcePolicy,
1854 ) -> Result<(), KeyStoreError>
1855 where
1856 F: FnOnce() -> Option<Zeroizing<String>>,
1857 {
1858 let limits = self.pkcs12_import_limits(&name, bytes, usages, resources)?;
1859 let prepared = pkcs12_import::prepare_with_work(bytes, &limits, self.kdf_work)?;
1860 let secret = password().ok_or(KeyStoreError::ProtectedContainer)?;
1861 let contents =
1862 prepared.decrypt_with_work(&secret, secret.capacity(), &mut self.kdf_work)?;
1863 self.add_pkcs12_contents(name, bytes.len(), contents, usages, resources, false)
1864 }
1865
1866 pub fn add_pkcs12_with_usages(
1868 &mut self,
1869 name: String,
1870 bytes: &[u8],
1871 password: &str,
1872 usages: KeyUsages,
1873 resources: &ResourcePolicy,
1874 ) -> Result<(), KeyStoreError> {
1875 self.add_pkcs12_inner(name, bytes, password, usages, resources, false)
1876 }
1877
1878 fn add_pkcs12_inner(
1879 &mut self,
1880 name: String,
1881 bytes: &[u8],
1882 password: &str,
1883 usages: KeyUsages,
1884 resources: &ResourcePolicy,
1885 auto_decrypt: bool,
1886 ) -> Result<(), KeyStoreError> {
1887 let limits = self.pkcs12_import_limits(&name, bytes, usages, resources)?;
1888 let contents = pkcs12_import::prepare_with_work(bytes, &limits, self.kdf_work)?
1889 .decrypt_with_work(password, password.len(), &mut self.kdf_work)?;
1890 self.add_pkcs12_contents(name, bytes.len(), contents, usages, resources, auto_decrypt)
1891 }
1892
1893 fn add_pkcs12_contents(
1894 &mut self,
1895 name: String,
1896 encoded_len: usize,
1897 mut contents: pkcs12_import::Contents,
1898 mut usages: KeyUsages,
1899 resources: &ResourcePolicy,
1900 auto_decrypt: bool,
1901 ) -> Result<(), KeyStoreError> {
1902 if contents.private_keys.len() != 1 {
1903 return Err(KeyStoreError::Selection(
1904 "PKCS#12 bundle must contain exactly one private key",
1905 ));
1906 }
1907 let private_key = contents
1908 .private_keys
1909 .pop()
1910 .ok_or(KeyStoreError::ProtectedContainer)?;
1911 let mut certificates = contents.certificates;
1912 let identity = PrivateKeyIdentity::decode(private_key.as_ref())?;
1913 if auto_decrypt {
1914 usages = identity.automatic_usages();
1915 } else if usages.allows(KeyUsage::Decrypt) && !identity.can_decrypt() {
1916 return Err(KeyStoreError::Selection(
1917 "private key cannot recover encryption keys",
1918 ));
1919 }
1920 identity.validate_usages(usages)?;
1921 let mut matching_leaf = None;
1922 for certificate in &certificates {
1923 let (rest, parsed) = X509Certificate::from_der(certificate)
1924 .map_err(|_| KeyStoreError::Selection("invalid certificate in PKCS#12"))?;
1925 if !rest.is_empty() {
1926 return Err(KeyStoreError::Selection("invalid certificate in PKCS#12"));
1927 }
1928 if identity.matches_spki(parsed.public_key().raw) {
1929 if matching_leaf.is_some_and(|leaf: &[u8]| leaf != certificate.as_slice()) {
1930 return Err(KeyStoreError::Selection(
1931 "ambiguous certificate for PKCS#12 private key",
1932 ));
1933 }
1934 matching_leaf = Some(certificate.as_slice());
1937 }
1938 }
1939 drop(identity);
1940 let has_matching_leaf = matching_leaf.is_some();
1943 if let Some(leaf) = matching_leaf {
1944 let position = certificates
1945 .iter()
1946 .position(|candidate| candidate == leaf)
1947 .ok_or(KeyStoreError::ProtectedContainer)?;
1948 certificates.swap(0, position);
1949 let mut index = 1;
1950 while index < certificates.len() {
1951 if certificates[index] == certificates[0] {
1952 certificates.remove(index);
1953 } else {
1954 index += 1;
1955 }
1956 }
1957 }
1958 let decoded_bytes = certificates
1959 .iter()
1960 .try_fold(private_key.len(), |total, cert| {
1961 total
1962 .checked_add(cert.len())
1963 .ok_or(KeyStoreError::Selection("key material size overflow"))
1964 })?;
1965 let retained_candidates = 1_usize
1966 .checked_add(certificates.len())
1967 .ok_or(KeyStoreError::Selection("key candidate count overflow"))?;
1968 let remaining_candidates = resources.max_key_candidates - self.entry_count;
1969 if retained_candidates > remaining_candidates {
1970 return Err(crate::policy::PolicyViolation::ResourceLimitExceeded {
1971 resource: crate::policy::resource_name::KEY_CANDIDATES,
1972 maximum: remaining_candidates,
1973 }
1974 .into());
1975 }
1976 self.reserve_material(
1977 named_material_length(&name, decoded_bytes.max(encoded_len), 1)?,
1978 resources,
1979 )?;
1980 self.private_keys.push(StoredPrivateKey {
1981 name,
1982 pkcs8_der: private_key,
1983 usages,
1984 certificate_chain: certificates,
1985 has_matching_leaf,
1986 });
1987 self.entry_count += retained_candidates;
1988 Ok(())
1989 }
1990
1991 fn pkcs12_import_limits(
1992 &self,
1993 name: &str,
1994 bytes: &[u8],
1995 usages: KeyUsages,
1996 resources: &ResourcePolicy,
1997 ) -> Result<Pkcs12Limits, KeyStoreError> {
1998 self.check_new_name(name, resources)?;
1999 validate_private_key_usages(usages)?;
2000 if bytes.len() > resources.max_external_resource_bytes {
2001 return Err(KeyStoreError::Policy(
2002 crate::policy::PolicyViolation::ResourceLimitExceeded {
2003 resource: crate::policy::resource_name::EXTERNAL_RESOURCE_BYTES,
2004 maximum: resources.max_external_resource_bytes,
2005 },
2006 ));
2007 }
2008 let retained_with_input =
2009 self.check_material_capacity(named_material_length(name, bytes.len(), 1)?, resources)?;
2010 let remaining_candidates = resources.max_key_candidates - self.entry_count;
2011 Ok(Pkcs12Limits {
2012 resources: resources.clone(),
2013 candidates: remaining_candidates,
2014 memory_available: resources.max_external_resource_total_bytes - retained_with_input,
2016 })
2017 }
2018
2019 fn check_new_name(&self, name: &str, resources: &ResourcePolicy) -> Result<(), KeyStoreError> {
2020 ensure_resource_policy(resources)?;
2021 if name.is_empty() {
2022 return Err(KeyStoreError::Selection("empty key name"));
2023 }
2024 if self.entry_count >= resources.max_key_candidates {
2025 return Err(import_resource_limit(
2026 crate::policy::resource_name::KEY_CANDIDATES,
2027 resources.max_key_candidates,
2028 ));
2029 }
2030 if name.len() > resources.max_external_resource_bytes {
2031 return Err(import_resource_limit(
2032 crate::policy::resource_name::EXTERNAL_RESOURCE_BYTES,
2033 resources.max_external_resource_bytes,
2034 ));
2035 }
2036 self.check_material_capacity(name.len(), resources)?;
2037 if self.symmetric_keys.iter().any(|key| key.name == name)
2038 || self.public_keys.iter().any(|key| key.name == name)
2039 || self.private_keys.iter().any(|key| key.name == name)
2040 {
2041 return Err(KeyStoreError::Selection("duplicate key name"));
2042 }
2043 Ok(())
2044 }
2045
2046 fn remaining_kdf_resources(
2047 &self,
2048 resources: &ResourcePolicy,
2049 ) -> Result<ResourcePolicy, KeyStoreError> {
2050 if self.kdf_work > resources.max_key_import_kdf_work {
2051 return Err(import_resource_limit(
2052 crate::policy::resource_name::KEY_IMPORT_KDF_WORK,
2053 resources.max_key_import_kdf_work,
2054 ));
2055 }
2056 let mut remaining = resources.clone();
2057 remaining.max_key_import_kdf_work -= self.kdf_work;
2058 Ok(remaining)
2059 }
2060
2061 fn check_material_capacity(
2062 &self,
2063 length: usize,
2064 resources: &ResourcePolicy,
2065 ) -> Result<usize, KeyStoreError> {
2066 let total = self.material_bytes.checked_add(length).ok_or_else(|| {
2067 import_resource_limit(
2068 crate::policy::resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES,
2069 resources.max_external_resource_total_bytes,
2070 )
2071 })?;
2072 if total > resources.max_external_resource_total_bytes {
2073 return Err(import_resource_limit(
2074 crate::policy::resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES,
2075 resources.max_external_resource_total_bytes,
2076 ));
2077 }
2078 Ok(total)
2079 }
2080
2081 fn reserve_material(
2082 &mut self,
2083 length: usize,
2084 resources: &ResourcePolicy,
2085 ) -> Result<(), KeyStoreError> {
2086 self.material_bytes = self.check_material_capacity(length, resources)?;
2087 Ok(())
2088 }
2089
2090 pub fn add_certificate_der(
2093 &mut self,
2094 der: Vec<u8>,
2095 trusted_anchor: bool,
2096 resources: &ResourcePolicy,
2097 ) -> Result<(), KeyStoreError> {
2098 let material_bytes = self.preflight_x509_import(&der, resources)?;
2099 let (rest, _) = X509Certificate::from_der(&der)
2100 .map_err(|_| KeyStoreError::Selection("invalid X.509 certificate"))?;
2101 if !rest.is_empty() {
2102 return Err(KeyStoreError::Selection("invalid X.509 certificate"));
2103 }
2104 self.material_bytes = material_bytes;
2105 if trusted_anchor {
2106 self.trusted_certificates.push(der);
2107 } else {
2108 self.lookup_certificates.push(der);
2109 }
2110 self.entry_count += 1;
2111 Ok(())
2112 }
2113
2114 pub fn add_crl_der(
2116 &mut self,
2117 der: Vec<u8>,
2118 resources: &ResourcePolicy,
2119 ) -> Result<(), KeyStoreError> {
2120 let material_bytes = self.preflight_x509_import(&der, resources)?;
2121 let (rest, _) = x509_parser::revocation_list::CertificateRevocationList::from_der(&der)
2122 .map_err(|_| KeyStoreError::Selection("invalid X.509 CRL"))?;
2123 if !rest.is_empty() {
2124 return Err(KeyStoreError::Selection("invalid X.509 CRL"));
2125 }
2126 self.material_bytes = material_bytes;
2127 self.crls.push(der);
2128 self.entry_count += 1;
2129 Ok(())
2130 }
2131
2132 fn preflight_x509_import(
2133 &self,
2134 der: &[u8],
2135 resources: &ResourcePolicy,
2136 ) -> Result<usize, KeyStoreError> {
2137 ensure_resource_policy(resources)?;
2139 if der.len() > resources.max_external_resource_bytes {
2140 return Err(import_resource_limit(
2141 crate::policy::resource_name::EXTERNAL_RESOURCE_BYTES,
2142 resources.max_external_resource_bytes,
2143 ));
2144 }
2145 if self.entry_count >= resources.max_key_candidates {
2146 return Err(import_resource_limit(
2147 crate::policy::resource_name::KEY_CANDIDATES,
2148 resources.max_key_candidates,
2149 ));
2150 }
2151 self.check_material_capacity(der.len(), resources)
2152 }
2153 pub fn from_xml_bytes<P: crate::document::XmlDocumentPolicy>(
2155 bytes: &[u8],
2156 policy: &P,
2157 backend: XmlBackend,
2158 ) -> Result<Self, KeyStoreError> {
2159 let budget = XmlParseWorkBudget::from_resources(policy.resource_policy());
2160 Self::from_xml_bytes_with_budget(bytes, policy, backend, &budget, &mut 0, 0)
2161 }
2162
2163 fn from_xml_bytes_with_budget<P: crate::document::XmlDocumentPolicy>(
2164 bytes: &[u8],
2165 policy: &P,
2166 backend: XmlBackend,
2167 budget: &XmlParseWorkBudget,
2168 inspected: &mut usize,
2169 live_material: usize,
2170 ) -> Result<Self, KeyStoreError> {
2171 let resources = policy.resource_policy();
2172 ensure_resource_policy(resources)?;
2173 if bytes.len() > resources.max_external_resource_bytes {
2174 return Err(import_resource_limit(
2175 crate::policy::resource_name::EXTERNAL_RESOURCE_BYTES,
2176 resources.max_external_resource_bytes,
2177 ));
2178 }
2179 if bytes.len() > resources.max_external_resource_total_bytes - live_material {
2180 return Err(import_resource_limit(
2181 crate::policy::resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES,
2182 resources.max_external_resource_total_bytes,
2183 ));
2184 }
2185 let settings = DocumentParseSettings::from_policy(policy.xml_input_policy(), resources)
2186 .with_backend(backend);
2187 let text = crate::document::decode_xml_with_budget(
2188 bytes,
2189 resources
2190 .max_xml_document_bytes
2191 .min(resources.max_external_resource_bytes),
2192 Some(budget),
2193 )
2194 .map_err(|error| match error.into_policy_violation(settings) {
2195 Ok(violation) => KeyStoreError::Policy(violation),
2196 Err(error) => KeyStoreError::Invalid(error.to_string()),
2197 })?;
2198 let document = parse_borrowed_with_settings_and_budget(&text, settings, Some(budget))
2199 .map_err(|error| match error.into_policy_violation(settings) {
2200 Ok(violation) => KeyStoreError::Policy(violation),
2201 Err(error) => KeyStoreError::Invalid(error.to_string()),
2202 })?;
2203 let root = document.root_element();
2204 if !root.has_tag_name((XMLSEC_NS, "Keys")) {
2205 return Err(KeyStoreError::Invalid("expected xmlsec Keys root".into()));
2206 }
2207 let mut material_budget = XmlImportMaterialBudget {
2208 used: live_material + bytes.len(),
2209 maximum: resources.max_external_resource_total_bytes,
2210 };
2211 let mut names = HashSet::new();
2212 let mut store = Self::default();
2213 let mut entry_count = 0_usize;
2214 for info in root.children().filter(|child| child.is_element()) {
2215 if !info.has_tag_name((XMLDSIG_NS, "KeyInfo")) {
2216 return Err(KeyStoreError::Invalid("unexpected child of Keys".into()));
2217 }
2218 if *inspected >= resources.max_key_candidates {
2219 return Err(import_resource_limit(
2220 crate::policy::resource_name::KEY_CANDIDATES,
2221 resources.max_key_candidates,
2222 ));
2223 }
2224 *inspected += 1;
2225 entry_count += 1;
2226 preflight_xml_material(info, &mut material_budget)?;
2227 let mut name = None;
2228 let mut value = None;
2229 for child in info.children().filter(|child| child.is_element()) {
2230 if child.has_tag_name((XMLDSIG_NS, "KeyName")) {
2231 if name.is_some() {
2232 return Err(KeyStoreError::Invalid("duplicate KeyName".into()));
2233 }
2234 let text = element_text(child)?;
2235 if text.is_empty() {
2236 return Err(KeyStoreError::Invalid("empty KeyName".into()));
2237 }
2238 name = Some(text);
2239 } else if child.has_tag_name((XMLDSIG_NS, "KeyValue")) {
2240 if value.is_some() {
2241 return Err(KeyStoreError::Invalid("duplicate KeyValue".into()));
2242 }
2243 let mut values = child.children().filter(|node| node.is_element());
2244 let key = values.next().ok_or_else(|| {
2245 KeyStoreError::Invalid("KeyValue has no key material".into())
2246 })?;
2247 if values.next().is_some() {
2248 return Err(KeyStoreError::Invalid("ambiguous KeyValue".into()));
2249 }
2250 let material = if key.has_tag_name((XMLSEC_NS, "HMACKeyValue")) {
2251 Some(SymmetricKeyKind::Hmac)
2252 } else if key.has_tag_name((XMLSEC_NS, "AESKeyValue")) {
2253 Some(SymmetricKeyKind::Aes)
2254 } else if key.has_tag_name((XMLSEC_NS, "DESKeyValue")) {
2255 Some(SymmetricKeyKind::Des)
2256 } else {
2257 None
2258 };
2259 value = Some(if let Some(kind) = material {
2260 ParsedMaterial::Symmetric(kind, decode_xml_base64_secret(key)?)
2261 } else if key.has_tag_name((XMLDSIG_NS, "DSAKeyValue")) {
2262 let (public, private) =
2263 parse_xmlsec_dsa_key_value(key, &mut material_budget)?;
2264 ParsedMaterial::Dsa(public, private)
2265 } else if key.has_tag_name((XMLDSIG_NS, "RSAKeyValue"))
2266 || key.has_tag_name((XMLDSIG11_NS, "ECKeyValue"))
2269 {
2270 ParsedMaterial::Public(
2271 crate::xmldsig::parse::parse_key_value_dispatch(child)
2272 .map_err(|error| KeyStoreError::Invalid(error.to_string()))?,
2273 )
2274 } else {
2275 ParsedMaterial::Unsupported
2276 });
2277 } else if child.has_tag_name((XMLDSIG11_NS, "DEREncodedKeyValue")) {
2278 if value.is_some() {
2281 return Err(KeyStoreError::Invalid("ambiguous key material".into()));
2282 }
2283 value = Some(ParsedMaterial::PublicDer(decode_xml_base64(child)?));
2284 } else {
2285 return Err(KeyStoreError::Invalid("unsupported KeyInfo child".into()));
2286 }
2287 }
2288 let name = name.ok_or_else(|| KeyStoreError::Invalid("missing KeyName".into()))?;
2289 let material =
2290 value.ok_or_else(|| KeyStoreError::Invalid("missing KeyValue".into()))?;
2291 if !names.insert(name.clone()) {
2292 return Err(KeyStoreError::Invalid("duplicate key name".into()));
2293 }
2294 match material {
2295 ParsedMaterial::PublicDer(der) => {
2296 store.add_public_der_payload(
2297 name,
2298 der,
2299 None,
2300 resources,
2301 PublicDerFormat::SubjectPublicKeyInfo,
2302 )?;
2303 }
2304 ParsedMaterial::Symmetric(kind, bytes) => {
2305 if bytes.is_empty() {
2306 return Err(KeyStoreError::Invalid("empty symmetric key".into()));
2307 }
2308 if kind == SymmetricKeyKind::Aes && !matches!(bytes.len(), 16 | 24 | 32) {
2309 return Err(KeyStoreError::Invalid("invalid AES key length".into()));
2310 }
2311 let usages = match kind {
2312 SymmetricKeyKind::Hmac => KeyUsages::SIGN.union(KeyUsages::VERIFY),
2313 SymmetricKeyKind::Aes => KeyUsages::ENCRYPT.union(KeyUsages::DECRYPT),
2314 SymmetricKeyKind::Des => des_key_usages(&bytes)?,
2315 };
2316 store.symmetric_keys.push(StoredSymmetricKey {
2317 name,
2318 kind,
2319 bytes,
2320 usages,
2321 });
2322 }
2323 ParsedMaterial::Public(value) => {
2324 let mut key_info = KeyInfo::default();
2325 key_info.sources.push(KeyInfoSource::KeyName(name.clone()));
2326 key_info.sources.push(KeyInfoSource::KeyValue(value));
2327 let is_rsa = key_info
2328 .sources
2329 .iter()
2330 .find_map(|source| match source {
2331 KeyInfoSource::KeyValue(value) => Some(value),
2332 _ => None,
2333 })
2334 .ok_or_else(|| KeyStoreError::Invalid("invalid public KeyValue".into()))?;
2335 let is_rsa = crate::xmldsig::keys::supported_key_value_is_rsa(is_rsa)
2336 .map_err(|_| KeyStoreError::Invalid("invalid public KeyValue".into()))?;
2337 let usages = if is_rsa {
2338 KeyUsages::VERIFY.union(KeyUsages::ENCRYPT)
2339 } else {
2340 KeyUsages::VERIFY
2341 };
2342 store.public_keys.push(StoredPublicKey {
2343 name,
2344 key_info,
2345 usages,
2346 });
2347 }
2348 ParsedMaterial::Dsa(public, private) => {
2349 crate::xmldsig::keys::supported_key_value_is_rsa(&public)
2352 .map_err(|_| KeyStoreError::Invalid("invalid public DSAKeyValue".into()))?;
2353 let mut key_info = KeyInfo::default();
2354 key_info.sources.push(KeyInfoSource::KeyName(name.clone()));
2355 key_info.sources.push(KeyInfoSource::KeyValue(public));
2356 if let Some(pkcs8_der) = private {
2357 store.private_keys.push(StoredPrivateKey {
2358 name: name.clone(),
2359 pkcs8_der,
2360 usages: KeyUsages::SIGN,
2361 certificate_chain: Vec::new(),
2362 has_matching_leaf: false,
2363 });
2364 }
2365 store.public_keys.push(StoredPublicKey {
2366 name,
2367 key_info,
2368 usages: KeyUsages::VERIFY,
2369 });
2370 }
2371 ParsedMaterial::Unsupported => {}
2372 }
2373 }
2374 store.entry_count = entry_count;
2375 store.material_bytes = bytes
2378 .len()
2379 .checked_add(store.retained_material_bytes()?)
2380 .ok_or(KeyStoreError::Selection("key material size overflow"))?;
2381 if store.material_bytes > resources.max_external_resource_total_bytes - live_material {
2382 return Err(import_resource_limit(
2383 crate::policy::resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES,
2384 resources.max_external_resource_total_bytes,
2385 ));
2386 }
2387 Ok(store)
2388 }
2389}
2390
2391fn validate_private_key_usages(usages: KeyUsages) -> Result<(), KeyStoreError> {
2392 let permitted = KeyUsages::SIGN.union(KeyUsages::DECRYPT);
2393 if usages.0 == 0 || usages.0 & !permitted.0 != 0 {
2394 return Err(KeyStoreError::Selection(
2395 "private key usage is incompatible",
2396 ));
2397 }
2398 Ok(())
2399}
2400
2401fn import_resource_limit(resource: &'static str, maximum: usize) -> KeyStoreError {
2402 crate::policy::PolicyViolation::ResourceLimitExceeded { resource, maximum }.into()
2403}
2404
2405fn check_operation_material_size(
2406 length: usize,
2407 resources: &ResourcePolicy,
2408) -> Result<(), KeyStoreError> {
2409 if length > resources.max_external_resource_bytes {
2410 return Err(crate::policy::PolicyViolation::ResourceLimit {
2411 resource: crate::policy::resource_name::EXTERNAL_RESOURCE_BYTES,
2412 maximum: resources.max_external_resource_bytes,
2413 actual: length,
2414 }
2415 .into());
2416 }
2417 if length > resources.max_external_resource_total_bytes {
2418 return Err(crate::policy::PolicyViolation::ResourceLimit {
2419 resource: crate::policy::resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES,
2420 maximum: resources.max_external_resource_total_bytes,
2421 actual: length,
2422 }
2423 .into());
2424 }
2425 Ok(())
2426}
2427
2428#[cfg(feature = "xmlenc")]
2429fn check_selected_material_size(
2430 length: usize,
2431 resources: &ResourcePolicy,
2432) -> Result<(), KeyStoreError> {
2433 check_operation_material_size(length, resources)
2434}
2435
2436#[cfg(feature = "xmlenc")]
2437fn check_encryption_material_size(
2438 length: usize,
2439 resources: &ResourcePolicy,
2440) -> Result<(), KeyStoreError> {
2441 check_operation_material_size(length, resources)
2442}
2443
2444#[cfg(feature = "xmlenc")]
2445fn rsa_recipient_preflight(
2446 modulus: &[u8],
2447 exponent: &[u8],
2448 policy: &crate::policy::EncryptionPolicy,
2449) -> Result<(), KeyStoreError> {
2450 let combined = modulus
2451 .len()
2452 .checked_add(exponent.len())
2453 .ok_or(KeyStoreError::Selection("encryption key size overflow"))?;
2454 check_encryption_material_size(combined, &policy.resources)?;
2455 policy
2456 .rsa_keys
2457 .validate_components("encryption", modulus, exponent)?;
2458 Ok(())
2459}
2460
2461#[cfg(feature = "xmlenc")]
2462fn rsa_recipient_from_components(
2463 modulus: &[u8],
2464 exponent: &[u8],
2465 policy: &crate::policy::EncryptionPolicy,
2466) -> Result<RsaPublicKey, KeyStoreError> {
2467 rsa_recipient_preflight(modulus, exponent, policy)?;
2468 let first_nonzero = modulus
2469 .iter()
2470 .position(|byte| *byte != 0)
2471 .ok_or(KeyStoreError::Selection("invalid RSA encryption key"))?;
2472 RsaPublicKey::new(
2473 BoxedUint::from_be_slice_vartime(&modulus[first_nonzero..]),
2474 BoxedUint::from_be_slice_vartime(exponent),
2475 )
2476 .map_err(|_| KeyStoreError::Selection("invalid RSA encryption key"))
2477}
2478
2479fn signing_policy_error(error: crate::xmldsig::SigningError) -> KeyStoreError {
2480 match error {
2481 crate::xmldsig::SigningError::Policy(violation) => violation.into(),
2482 _ => KeyStoreError::Selection("signing key violates operation policy"),
2483 }
2484}
2485
2486fn ensure_resource_policy(resources: &ResourcePolicy) -> Result<(), KeyStoreError> {
2487 resources.validate().map_err(Into::into)
2488}
2489
2490fn find_named_entry<'a, T>(
2491 entries: &'a [T],
2492 name: &str,
2493 resources: &ResourcePolicy,
2494 visited: &mut usize,
2495 entry_name: impl Fn(&T) -> &str,
2496) -> Result<Option<&'a T>, KeyStoreError> {
2497 for entry in entries {
2498 let next = visited
2499 .checked_add(1)
2500 .ok_or(KeyStoreError::Selection("key candidate count overflow"))?;
2501 resources.validate_key_candidates(next)?;
2502 *visited = next;
2503 if entry_name(entry) == name {
2504 return Ok(Some(entry));
2505 }
2506 }
2507 Ok(None)
2508}
2509
2510fn named_material_length(
2511 name: &str,
2512 payload: usize,
2513 retained_names: usize,
2514) -> Result<usize, KeyStoreError> {
2515 name.len()
2516 .checked_mul(retained_names)
2517 .and_then(|names| names.checked_add(payload))
2518 .ok_or(KeyStoreError::Selection("key material size overflow"))
2519}
2520
2521#[cfg_attr(
2524 feature = "experimental-pq",
2525 expect(
2526 clippy::large_enum_variant,
2527 reason = "A transient, wire-bounded 2592-byte public key avoids a heap allocation during identity comparison"
2528 )
2529)]
2530enum PrivateKeyIdentity<'a> {
2531 Rsa(pkcs1::RsaPrivateKeyRef<'a>),
2532 #[cfg(feature = "experimental-pq")]
2533 Encapsulation {
2534 algorithm: pkcs8::AlgorithmIdentifierRef<'a>,
2535 public: [u8; 1568],
2536 length: usize,
2537 },
2538 Dsa {
2539 algorithm: pkcs8::AlgorithmIdentifierRef<'a>,
2540 key: NativeDsaSigningKey,
2541 },
2542 Ec {
2543 algorithm: pkcs8::AlgorithmIdentifierRef<'a>,
2544 point: [u8; 133],
2545 length: usize,
2546 },
2547 EdDsa {
2548 algorithm: pkcs8::AlgorithmIdentifierRef<'a>,
2549 public: [u8; 57],
2550 length: usize,
2551 },
2552 #[cfg(feature = "experimental-pq")]
2553 PostQuantum {
2554 algorithm: pkcs8::AlgorithmIdentifierRef<'a>,
2555 public: [u8; 2592],
2556 length: usize,
2557 },
2558}
2559
2560impl<'a> PrivateKeyIdentity<'a> {
2561 fn automatic_usages(&self) -> KeyUsages {
2562 match self {
2563 Self::Rsa(_) => KeyUsages::SIGN.union(KeyUsages::DECRYPT),
2564 #[cfg(feature = "experimental-pq")]
2565 Self::Encapsulation { .. } => KeyUsages::DECRYPT,
2566 _ => KeyUsages::SIGN,
2567 }
2568 }
2569
2570 fn can_decrypt(&self) -> bool {
2571 match self {
2572 Self::Rsa(_) => true,
2573 #[cfg(feature = "experimental-pq")]
2574 Self::Encapsulation { .. } => true,
2575 _ => false,
2576 }
2577 }
2578
2579 fn validate_usages(&self, usages: KeyUsages) -> Result<(), KeyStoreError> {
2580 #[cfg(feature = "experimental-pq")]
2581 if matches!(self, Self::Encapsulation { .. }) && usages != KeyUsages::DECRYPT {
2582 return Err(KeyStoreError::Selection(
2583 "ML-KEM private keys require DECRYPT-only usage",
2584 ));
2585 }
2586 #[cfg(not(feature = "experimental-pq"))]
2587 let _ = usages;
2588 Ok(())
2589 }
2590
2591 fn decode(der: &'a [u8]) -> Result<Self, KeyStoreError> {
2592 let info = PrivateKeyInfoRef::try_from(der)
2593 .map_err(|_| KeyStoreError::Selection("unsupported PKCS#12 private key"))?;
2594 #[cfg(feature = "experimental-pq")]
2595 if crate::provider::KeyEncapsulationAlgorithm::from_oid(info.algorithm.oid).is_some() {
2596 let key = crate::provider::RustCryptoMlKemPrivateKey::from_pkcs8_der(der)
2597 .map_err(|_| KeyStoreError::Selection("invalid ML-KEM private key"))?;
2598 let mut public = [0; 1568];
2602 let length = key
2603 .copy_public(&mut public)
2604 .map_err(|_| KeyStoreError::Selection("invalid ML-KEM identity"))?;
2605 return Ok(Self::Encapsulation {
2606 algorithm: info.algorithm,
2607 public,
2608 length,
2609 });
2610 }
2611 #[cfg(feature = "experimental-pq")]
2612 if let Some(parameter) = crate::xmldsig::PqAlgorithm::from_oid(info.algorithm.oid) {
2613 let key = crate::xmldsig::PostQuantumSigningKey::from_pkcs8_der(parameter, der)
2614 .map_err(|_| KeyStoreError::Selection("invalid PQ private key"))?;
2615 let mut public = [0; 2592];
2618 let length = key.copy_public(&mut public);
2619 return Ok(Self::PostQuantum {
2620 algorithm: info.algorithm,
2621 public,
2622 length,
2623 });
2624 }
2625 if info.algorithm.oid == der::asn1::ObjectIdentifier::new_unwrap("1.3.101.112") {
2629 let key = ed25519_dalek::SigningKey::from_pkcs8_der(der)
2630 .map_err(|_| KeyStoreError::Selection("invalid Ed25519 private key"))?;
2631 let mut public = [0; 57];
2632 public[..32].copy_from_slice(&key.verifying_key().to_bytes());
2633 return Ok(Self::EdDsa {
2634 algorithm: info.algorithm,
2635 public,
2636 length: 32,
2637 });
2638 }
2639 if info.algorithm.oid == der::asn1::ObjectIdentifier::new_unwrap("1.3.101.113") {
2640 let key = ed448_goldilocks::SigningKey::from_pkcs8_der(der)
2641 .map_err(|_| KeyStoreError::Selection("invalid Ed448 private key"))?;
2642 return Ok(Self::EdDsa {
2643 algorithm: info.algorithm,
2644 public: key.verifying_key().to_bytes(),
2645 length: 57,
2646 });
2647 }
2648 if info.algorithm.oid == pkcs1::ALGORITHM_OID {
2649 preflight_rsa_pkcs1_components(info.private_key.as_bytes())?;
2650 RsaPrivateKey::from_pkcs8_der(der)
2651 .map_err(|_| KeyStoreError::Selection("invalid RSA private key"))?;
2652 return pkcs1::RsaPrivateKeyRef::from_der(info.private_key.as_bytes())
2653 .map(Self::Rsa)
2654 .map_err(|_| KeyStoreError::Selection("invalid RSA private key"));
2655 } else if info.algorithm.oid == dsa::OID {
2656 let algorithm = info.algorithm;
2657 return crate::xmldsig::sign::decode_dsa_signing_key(info)
2658 .map(|key| Self::Dsa { algorithm, key })
2659 .map_err(|_| KeyStoreError::Selection("invalid or oversized DSA private key"));
2660 }
2661 macro_rules! try_ec {
2662 ($key:ty) => {
2663 if let Ok(key) = <$key>::from_pkcs8_der(der) {
2664 use p256::elliptic_curve::sec1::ToSec1Point as _;
2665 let encoded = key.public_key().to_sec1_point(false);
2666 let bytes = encoded.as_bytes();
2667 let mut point = [0; 133];
2668 point[..bytes.len()].copy_from_slice(bytes);
2669 return Ok(Self::Ec {
2670 algorithm: info.algorithm,
2671 point,
2672 length: bytes.len(),
2673 });
2674 }
2675 };
2676 }
2677 try_ec!(p256::SecretKey);
2678 try_ec!(p384::SecretKey);
2679 try_ec!(p521::SecretKey);
2680 Err(KeyStoreError::Selection("unsupported PKCS#12 private key"))
2681 }
2682
2683 fn matches_spki(&self, der: &[u8]) -> bool {
2684 let Ok(spki) = pkcs8::SubjectPublicKeyInfoRef::from_der(der) else {
2685 return false;
2686 };
2687 let Some(bytes) = spki.subject_public_key.as_bytes() else {
2688 return false;
2689 };
2690 match self {
2691 #[cfg(feature = "experimental-pq")]
2692 Self::Encapsulation {
2693 algorithm,
2694 public,
2695 length,
2696 } => spki.algorithm == *algorithm && bytes == &public[..*length],
2697 #[cfg(feature = "experimental-pq")]
2698 Self::PostQuantum {
2699 algorithm,
2700 public,
2701 length,
2702 } => spki.algorithm == *algorithm && bytes == &public[..*length],
2703 Self::EdDsa {
2704 algorithm,
2705 public,
2706 length,
2707 } => spki.algorithm == *algorithm && bytes == &public[..*length],
2708 Self::Rsa(key) => {
2709 if spki.algorithm != pkcs1::ALGORITHM_ID {
2710 return false;
2711 }
2712 let Ok(public) = pkcs1::RsaPublicKeyRef::from_der(bytes) else {
2713 return false;
2714 };
2715 key.modulus == public.modulus && key.public_exponent == public.public_exponent
2716 }
2717 Self::Dsa { algorithm, key } => {
2718 if spki.algorithm != *algorithm {
2719 return false;
2720 }
2721 let Ok(y) = der::asn1::UintRef::from_der(bytes) else {
2722 return false;
2723 };
2724 key.verifying_key()
2727 .y()
2728 .as_words()
2729 .iter()
2730 .rev()
2731 .flat_map(|word| word.to_be_bytes())
2732 .skip_while(|byte| *byte == 0)
2733 .eq(y.as_bytes().iter().copied())
2734 }
2735 Self::Ec {
2736 algorithm,
2737 point,
2738 length,
2739 } => spki.algorithm == *algorithm && bytes == &point[..*length],
2740 }
2741 }
2742}
2743
2744pub fn preflight_rsa_pkcs1_components(der: &[u8]) -> Result<(), KeyStoreError> {
2749 let key = pkcs1::RsaPrivateKeyRef::from_der(der)
2750 .map_err(|_| KeyStoreError::Selection("invalid RSA private key"))?;
2751 let modulus = key.modulus.as_bytes();
2752 let maximum = crate::hard_limits::RSA_MODULUS_BIT_CEILING;
2753 if modulus.len() > maximum.div_ceil(8)
2754 || modulus
2755 .first()
2756 .is_some_and(|first| modulus.len() * 8 - first.leading_zeros() as usize > maximum)
2757 {
2758 return Err(KeyStoreError::Selection("RSA modulus exceeds safety limit"));
2759 }
2760 if [
2761 key.public_exponent,
2762 key.private_exponent,
2763 key.prime1,
2764 key.prime2,
2765 key.exponent1,
2766 key.exponent2,
2767 key.coefficient,
2768 ]
2769 .into_iter()
2770 .any(|component| component.as_bytes().len() > maximum.div_ceil(8))
2771 || key.other_prime_infos.as_ref().is_some_and(|infos| {
2772 infos.iter().any(|info| {
2773 [info.prime, info.exponent, info.coefficient]
2774 .into_iter()
2775 .any(|component| component.as_bytes().len() > maximum.div_ceil(8))
2776 })
2777 })
2778 {
2779 return Err(KeyStoreError::Selection(
2780 "RSA component exceeds safety limit",
2781 ));
2782 }
2783 Ok(())
2784}
2785
2786fn preflight_dsa_pkcs8_components(info: &PrivateKeyInfoRef<'_>) -> Result<(), KeyStoreError> {
2787 crate::xmldsig::sign::preflight_dsa_private_key(info)
2788 .map_err(|_| KeyStoreError::Selection("invalid or oversized DSA private key"))
2789}
2790
2791struct BorrowedPem<'a> {
2794 label: &'a str,
2795 data: &'a str,
2796 decoded_len: usize,
2797}
2798
2799impl<'a> BorrowedPem<'a> {
2800 fn parse(bytes: &'a [u8], maximum: usize) -> Result<Self, KeyStoreError> {
2801 if bytes.len() > maximum {
2802 return Err(import_resource_limit(
2803 crate::policy::resource_name::EXTERNAL_RESOURCE_BYTES,
2804 maximum,
2805 ));
2806 }
2807 let invalid = || KeyStoreError::Selection("invalid PEM key");
2808 let text = std::str::from_utf8(bytes)
2809 .map_err(|_| invalid())?
2810 .trim_matches(|character: char| character.is_ascii_whitespace());
2811 let (label, rest) = text
2812 .strip_prefix("-----BEGIN ")
2813 .and_then(|rest| rest.split_once("-----"))
2814 .ok_or_else(invalid)?;
2815 let (payload, end) = rest
2816 .trim_start_matches([' ', '\t', '\n', '\r'])
2817 .split_once("-----END ")
2818 .ok_or_else(invalid)?;
2819 if end.strip_prefix(label) != Some("-----") || payload.contains("-----BEGIN ") {
2820 return Err(KeyStoreError::Selection(
2821 "PEM must contain one complete block",
2822 ));
2823 }
2824 let data = if let Some((headers, data)) = payload
2827 .split_once("\n\n")
2828 .or_else(|| payload.split_once("\r\n\r\n"))
2829 {
2830 if headers.lines().any(|line| !line.contains(':')) {
2831 return Err(invalid());
2832 }
2833 data
2834 } else {
2835 payload
2836 };
2837 let mut characters = 0_usize;
2838 let mut padding = 0_usize;
2839 for part in data.split_whitespace() {
2840 for byte in part.bytes() {
2841 if byte == b'=' {
2842 padding += 1;
2843 } else if padding != 0
2844 || !matches!(byte, b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'+' | b'/')
2845 {
2846 return Err(invalid());
2847 }
2848 characters += 1;
2849 }
2850 }
2851 if !characters.is_multiple_of(4) || padding > 2 {
2852 return Err(invalid());
2853 }
2854 let decoded_len = (characters / 4 * 3)
2855 .checked_sub(padding)
2856 .ok_or_else(invalid)?;
2857 Ok(Self {
2858 label,
2859 data,
2860 decoded_len,
2861 })
2862 }
2863
2864 fn decode(&self) -> Result<Vec<u8>, KeyStoreError> {
2865 let mut decoded = vec![0; self.decoded_len];
2866 self.decode_into(&mut decoded)?;
2867 Ok(decoded)
2868 }
2869
2870 fn decode_into(&self, decoded: &mut [u8]) -> Result<(), KeyStoreError> {
2871 use std::io::Read as _;
2872 let input = PemBase64Reader {
2873 parts: self.data.split_whitespace(),
2874 current: &[],
2875 };
2876 let mut decoder =
2877 base64::read::DecoderReader::new(input, &base64::engine::general_purpose::STANDARD);
2878 decoder
2879 .read_exact(decoded)
2880 .map_err(|_| KeyStoreError::Selection("invalid PEM key"))?;
2881 let mut eof = [0];
2882 if decoder
2883 .read(&mut eof)
2884 .map_err(|_| KeyStoreError::Selection("invalid PEM key"))?
2885 != 0
2886 {
2887 return Err(KeyStoreError::Selection("invalid PEM key"));
2888 }
2889 Ok(())
2890 }
2891}
2892
2893struct PemBase64Reader<'a> {
2894 parts: std::str::SplitWhitespace<'a>,
2895 current: &'a [u8],
2896}
2897
2898impl std::io::Read for PemBase64Reader<'_> {
2899 fn read(&mut self, buffer: &mut [u8]) -> std::io::Result<usize> {
2900 let mut written = 0;
2901 while written < buffer.len() {
2902 if self.current.is_empty() {
2903 let Some(part) = self.parts.next() else {
2904 break;
2905 };
2906 self.current = part.as_bytes();
2907 }
2908 let length = self.current.len().min(buffer.len() - written);
2909 buffer[written..written + length].copy_from_slice(&self.current[..length]);
2910 self.current = &self.current[length..];
2911 written += length;
2912 }
2913 Ok(written)
2914 }
2915}
2916
2917#[cfg(test)]
2918fn single_pem_block(bytes: &[u8], maximum: usize) -> Result<pem::Pem, KeyStoreError> {
2919 if bytes.len() > maximum {
2920 return Err(import_resource_limit(
2921 crate::policy::resource_name::EXTERNAL_RESOURCE_BYTES,
2922 maximum,
2923 ));
2924 }
2925 let text = std::str::from_utf8(bytes)
2926 .map_err(|_| KeyStoreError::Selection("PEM key is not ASCII text"))?
2927 .trim_matches(|character: char| character.is_ascii_whitespace());
2928 let block = pem::parse(text).map_err(|_| KeyStoreError::Selection("invalid PEM key"))?;
2929 let begin = format!("-----BEGIN {}-----", block.tag());
2930 let end = format!("-----END {}-----", block.tag());
2931 if !text.starts_with(&begin)
2932 || !text.ends_with(&end)
2933 || text.matches(&begin).count() != 1
2934 || text.matches(&end).count() != 1
2935 {
2936 return Err(KeyStoreError::Selection(
2937 "PEM must contain one complete block",
2938 ));
2939 }
2940 Ok(block)
2941}
2942
2943fn enforce_pkcs8_kdf_policy(
2944 encrypted: &EncryptedPrivateKeyInfoRef<'_>,
2945 resources: &ResourcePolicy,
2946 retained_with_input: usize,
2947) -> Result<usize, KeyStoreError> {
2948 use pkcs8::pkcs5::{EncryptionScheme, pbes2::Kdf};
2949 let EncryptionScheme::Pbes2(params) = &encrypted.encryption_algorithm else {
2953 return Err(KeyStoreError::ProtectedContainer);
2954 };
2955 let live_with_output =
2959 retained_with_input.checked_add(encrypted.encrypted_data.as_bytes().len());
2960 if live_with_output.is_none_or(|total| total > resources.max_external_resource_total_bytes) {
2961 return Err(kdf_policy_violation(
2962 crate::policy::resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES,
2963 resources.max_external_resource_total_bytes,
2964 live_with_output.map(|total| total as u64),
2965 ));
2966 }
2967 let live_with_output = live_with_output.ok_or(KeyStoreError::ProtectedContainer)?;
2968 let work = match ¶ms.kdf {
2969 Kdf::Pbkdf2(kdf) => {
2970 if kdf.iteration_count == 0 {
2971 return Err(KeyStoreError::ProtectedContainer);
2972 }
2973 use pkcs8::pkcs5::pbes2::Pbkdf2Prf;
2974 let hash_len = match kdf.prf {
2975 Pbkdf2Prf::HmacWithSha1 => 20,
2976 Pbkdf2Prf::HmacWithSha224 => 28,
2977 Pbkdf2Prf::HmacWithSha256 => 32,
2978 Pbkdf2Prf::HmacWithSha384 => 48,
2979 Pbkdf2Prf::HmacWithSha512 => 64,
2980 _ => return Err(KeyStoreError::ProtectedContainer),
2981 };
2982 let blocks = params.encryption.key_size().div_ceil(hash_len) as u64;
2986 let work = u64::from(kdf.iteration_count).checked_mul(blocks);
2987 if work.is_none_or(|work| work > resources.max_key_import_kdf_work as u64) {
2988 return Err(kdf_policy_violation(
2989 crate::policy::resource_name::KEY_IMPORT_KDF_WORK,
2990 resources.max_key_import_kdf_work,
2991 work,
2992 ));
2993 }
2994 work.ok_or(KeyStoreError::ProtectedContainer)? as usize
2995 }
2996 Kdf::Scrypt(kdf) => enforce_scrypt_kdf_limits(
2997 kdf.cost_parameter,
2998 u64::from(kdf.block_size),
2999 u64::from(kdf.parallelization),
3000 resources,
3001 live_with_output,
3002 )?,
3003 _ => return Err(KeyStoreError::ProtectedContainer),
3004 };
3005 Ok(work)
3006}
3007
3008fn enforce_pkcs8_password_policy(
3009 encrypted: &EncryptedPrivateKeyInfoRef<'_>,
3010 password: &[u8],
3011 resources: &ResourcePolicy,
3012 live_bytes: usize,
3013) -> Result<usize, KeyStoreError> {
3014 if password.len() > resources.max_external_resource_bytes {
3015 return Err(kdf_policy_violation(
3016 crate::policy::resource_name::EXTERNAL_RESOURCE_BYTES,
3017 resources.max_external_resource_bytes,
3018 Some(password.len() as u64),
3019 ));
3020 }
3021 let live = live_bytes.checked_add(password.len()).ok_or_else(|| {
3022 import_resource_limit(
3023 crate::policy::resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES,
3024 resources.max_external_resource_total_bytes,
3025 )
3026 })?;
3027 let hashing_passes = match &encrypted.encryption_algorithm {
3030 pkcs8::pkcs5::EncryptionScheme::Pbes2(params)
3031 if matches!(¶ms.kdf, pkcs8::pkcs5::pbes2::Kdf::Scrypt(_)) =>
3032 {
3033 2
3034 }
3035 _ => 1,
3036 };
3037 let password_work = password.len().div_ceil(64) * hashing_passes;
3039 if password_work > resources.max_key_import_kdf_work {
3040 return Err(kdf_policy_violation(
3041 crate::policy::resource_name::KEY_IMPORT_KDF_WORK,
3042 resources.max_key_import_kdf_work,
3043 Some(password_work as u64),
3044 ));
3045 }
3046 let mut remaining = resources.clone();
3047 remaining.max_key_import_kdf_work -= password_work;
3048 enforce_pkcs8_kdf_policy(encrypted, &remaining, live)
3049 .map(|work| work + password_work)
3050 .map_err(|error| match error {
3051 KeyStoreError::Policy(crate::policy::PolicyViolation::ResourceLimit {
3052 resource: crate::policy::resource_name::KEY_IMPORT_KDF_WORK,
3053 actual,
3054 ..
3055 }) => kdf_policy_violation(
3056 crate::policy::resource_name::KEY_IMPORT_KDF_WORK,
3057 resources.max_key_import_kdf_work,
3058 Some(actual.saturating_add(password_work) as u64),
3059 ),
3060 error => error,
3061 })
3062}
3063
3064fn enforce_scrypt_kdf_limits(
3065 n: u64,
3066 r: u64,
3067 p: u64,
3068 resources: &ResourcePolicy,
3069 retained_with_input: usize,
3070) -> Result<usize, KeyStoreError> {
3071 if n == 0 || r == 0 || p == 0 {
3072 return Err(KeyStoreError::ProtectedContainer);
3073 }
3074 let work = n.checked_mul(r).and_then(|value| value.checked_mul(p));
3075 if work.is_none_or(|value| value > resources.max_key_import_kdf_work as u64) {
3076 return Err(kdf_policy_violation(
3077 crate::policy::resource_name::KEY_IMPORT_KDF_WORK,
3078 resources.max_key_import_kdf_work,
3079 work,
3080 ));
3081 }
3082 let memory = n
3085 .checked_add(2)
3086 .and_then(|blocks| blocks.checked_mul(p))
3087 .and_then(|blocks| blocks.checked_mul(r))
3088 .and_then(|blocks| blocks.checked_mul(128));
3089 if memory.is_none_or(|value| value > resources.max_key_import_kdf_memory_bytes as u64) {
3090 return Err(kdf_policy_violation(
3091 crate::policy::resource_name::KEY_IMPORT_KDF_MEMORY,
3092 resources.max_key_import_kdf_memory_bytes,
3093 memory,
3094 ));
3095 }
3096 let total = memory.and_then(|memory| memory.checked_add(retained_with_input as u64));
3099 if total.is_none_or(|total| total > resources.max_external_resource_total_bytes as u64) {
3100 return Err(kdf_policy_violation(
3101 crate::policy::resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES,
3102 resources.max_external_resource_total_bytes,
3103 total,
3104 ));
3105 }
3106 Ok(work.ok_or(KeyStoreError::ProtectedContainer)? as usize)
3108}
3109
3110fn kdf_policy_violation(
3111 resource: &'static str,
3112 maximum: usize,
3113 actual: Option<u64>,
3114) -> KeyStoreError {
3115 KeyStoreError::Policy(crate::policy::PolicyViolation::ResourceLimit {
3116 resource,
3117 maximum,
3118 actual: actual
3119 .and_then(|value| usize::try_from(value).ok())
3120 .unwrap_or(usize::MAX),
3121 })
3122}
3123
3124fn element_text(node: Node<'_, '_>) -> Result<String, KeyStoreError> {
3125 let length = node
3126 .children()
3127 .filter(|child| child.is_text())
3128 .filter_map(|child| child.text())
3129 .map(str::len)
3130 .sum();
3131 let mut text = String::with_capacity(length);
3132 for child in node.children() {
3133 if child.is_element() {
3134 return Err(KeyStoreError::Invalid("unexpected nested element".into()));
3135 }
3136 if child.is_text() {
3137 text.push_str(child.text().unwrap_or_default());
3138 }
3139 }
3140 Ok(text)
3141}
3142
3143fn decode_xml_base64(node: Node<'_, '_>) -> Result<Vec<u8>, KeyStoreError> {
3144 xml_base64_payload(node)?
3145 .decode()
3146 .map_err(|reason| KeyStoreError::Invalid(reason.into()))
3147}
3148
3149fn decode_xml_base64_secret(node: Node<'_, '_>) -> Result<Zeroizing<Vec<u8>>, KeyStoreError> {
3150 let payload = xml_base64_payload(node)?;
3151 let mut output = Zeroizing::new(vec![0; payload.decoded_len]);
3154 payload
3155 .decode_into(&mut output)
3156 .map_err(|reason| KeyStoreError::Invalid(reason.into()))?;
3157 Ok(output)
3158}
3159
3160fn xml_base64_payload<'a, 'input>(
3161 node: Node<'a, 'input>,
3162) -> Result<crate::xmldsig::whitespace::XmlBase64Payload<'a, 'input>, KeyStoreError> {
3163 crate::xmldsig::whitespace::XmlBase64Payload::new(node)
3164 .map_err(|reason| KeyStoreError::Invalid(reason.into()))
3165}
3166
3167struct XmlImportMaterialBudget {
3168 used: usize,
3169 maximum: usize,
3170}
3171
3172impl XmlImportMaterialBudget {
3173 fn reserve(&mut self, length: usize) -> Result<(), KeyStoreError> {
3174 if self.used > self.maximum || length > self.maximum - self.used {
3175 return Err(import_resource_limit(
3176 crate::policy::resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES,
3177 self.maximum,
3178 ));
3179 }
3180 self.used += length;
3181 Ok(())
3182 }
3183}
3184
3185fn preflight_xml_material(
3186 info: Node<'_, '_>,
3187 budget: &mut XmlImportMaterialBudget,
3188) -> Result<(), KeyStoreError> {
3189 let key = info
3192 .children()
3193 .find(|child| child.has_tag_name((XMLDSIG_NS, "KeyValue")))
3194 .and_then(|value| value.children().find(|child| child.is_element()));
3195 let has_der = info
3196 .children()
3197 .any(|child| child.has_tag_name((XMLDSIG11_NS, "DEREncodedKeyValue")));
3198 let name_copies = match key {
3199 Some(key) if key.has_tag_name((XMLDSIG_NS, "DSAKeyValue")) => {
3200 if key
3201 .children()
3202 .any(|child| child.has_tag_name((XMLSEC_NS, "X")))
3203 {
3204 4
3205 } else {
3206 3
3207 }
3208 }
3209 Some(key)
3210 if key.has_tag_name((XMLDSIG_NS, "RSAKeyValue"))
3211 || key.has_tag_name((XMLDSIG11_NS, "ECKeyValue")) =>
3212 {
3213 3
3214 }
3215 _ if has_der => 3,
3216 _ => 2,
3217 };
3218 let uses_shared_public_parser = has_der
3219 || key.is_some_and(|key| {
3220 key.has_tag_name((XMLDSIG_NS, "RSAKeyValue"))
3221 || key.has_tag_name((XMLDSIG11_NS, "ECKeyValue"))
3222 });
3223 if uses_shared_public_parser {
3224 crate::xmldsig::parse::validate_key_info_container(info)
3225 .map_err(|error| KeyStoreError::Invalid(error.to_string()))?;
3226 }
3227 for child in info.children().filter(|child| child.is_element()) {
3228 if child.has_tag_name((XMLDSIG_NS, "KeyName")) {
3229 let length = child
3230 .children()
3231 .filter(|node| node.is_text())
3232 .filter_map(|node| node.text())
3233 .try_fold(0_usize, |sum, text| {
3234 sum.checked_add(text.len())
3235 .ok_or(KeyStoreError::Selection("key material size overflow"))
3236 })?;
3237 if uses_shared_public_parser && length > crate::xmldsig::parse::MAX_KEY_NAME_TEXT_LEN {
3239 return Err(KeyStoreError::Invalid(
3240 "KeyName exceeds maximum allowed text length".into(),
3241 ));
3242 }
3243 for _ in 0..name_copies {
3244 budget.reserve(length)?;
3245 }
3246 } else if child.has_tag_name((XMLDSIG11_NS, "DEREncodedKeyValue")) {
3247 let payload = xml_base64_payload(child)?;
3248 budget.reserve(payload.decoded_len)?;
3249 } else if child.has_tag_name((XMLDSIG_NS, "KeyValue")) {
3250 for key in child.children().filter(|node| node.is_element()) {
3251 if key.has_tag_name((XMLSEC_NS, "HMACKeyValue"))
3252 || key.has_tag_name((XMLSEC_NS, "AESKeyValue"))
3253 || key.has_tag_name((XMLSEC_NS, "DESKeyValue"))
3254 {
3255 budget.reserve(xml_base64_payload(key)?.decoded_len)?;
3256 } else if key.has_tag_name((XMLDSIG_NS, "RSAKeyValue"))
3257 || key.has_tag_name((XMLDSIG_NS, "DSAKeyValue"))
3258 || key.has_tag_name((XMLDSIG11_NS, "ECKeyValue"))
3259 {
3260 for component in key.children().filter(|node| node.is_element()) {
3261 if component.has_tag_name((XMLDSIG11_NS, "NamedCurve")) {
3262 budget.reserve(component.attribute("URI").map_or(0, str::len))?;
3263 } else {
3264 budget.reserve(xml_base64_payload(component)?.decoded_len)?;
3265 }
3266 }
3267 }
3268 }
3269 }
3270 }
3271 Ok(())
3272}
3273
3274fn parse_xmlsec_dsa_key_value(
3275 node: Node<'_, '_>,
3276 budget: &mut XmlImportMaterialBudget,
3277) -> Result<ParsedDsaKey, KeyStoreError> {
3278 let mut p = None;
3279 let mut q = None;
3280 let mut g = None;
3281 let mut y = None;
3282 let mut seed = None;
3283 let mut counter = None;
3284 let mut private_x = None;
3285 let mut previous_position = None;
3286 for child in node.children().filter(|child| child.is_element()) {
3287 let (position, slot) = if child.has_tag_name((XMLDSIG_NS, "P")) {
3288 (0, Some(&mut p))
3289 } else if child.has_tag_name((XMLDSIG_NS, "Q")) {
3290 (1, Some(&mut q))
3291 } else if child.has_tag_name((XMLDSIG_NS, "G")) {
3292 (2, Some(&mut g))
3293 } else if child.has_tag_name((XMLDSIG_NS, "Y")) {
3294 (4, Some(&mut y))
3295 } else if child.has_tag_name((XMLSEC_NS, "X")) {
3296 if private_x.is_some() {
3297 return Err(KeyStoreError::Invalid("duplicate DSA X".into()));
3298 }
3299 private_x = Some(decode_xml_base64_secret(child)?);
3303 (3, None)
3304 } else if child.has_tag_name((XMLDSIG_NS, "J")) {
3305 (5, None)
3306 } else if child.has_tag_name((XMLDSIG_NS, "Seed")) {
3307 (6, Some(&mut seed))
3308 } else if child.has_tag_name((XMLDSIG_NS, "PgenCounter")) {
3309 (7, Some(&mut counter))
3310 } else {
3311 return Err(KeyStoreError::Invalid(
3312 "unsupported DSAKeyValue child".into(),
3313 ));
3314 };
3315 if previous_position.is_some_and(|previous| position <= previous) {
3317 return Err(KeyStoreError::Invalid(
3318 "DSA parameters are out of order".into(),
3319 ));
3320 }
3321 previous_position = Some(position);
3322 if let Some(slot) = slot {
3323 if slot.replace(decode_xml_base64(child)?).is_some() {
3324 return Err(KeyStoreError::Invalid("duplicate DSA parameter".into()));
3325 }
3326 } else if position == 5 {
3327 let _ = decode_xml_base64(child)?;
3328 }
3329 }
3330 if p.is_some() != q.is_some() {
3331 return Err(KeyStoreError::Invalid(
3332 "DSA P and Q must occur together".into(),
3333 ));
3334 }
3335 if seed.is_some() != counter.is_some() {
3336 return Err(KeyStoreError::Invalid(
3337 "DSA Seed and PgenCounter must occur together".into(),
3338 ));
3339 }
3340 if [p.as_ref(), q.as_ref(), g.as_ref(), y.as_ref()]
3341 .into_iter()
3342 .flatten()
3343 .any(|component| component.len() > crate::hard_limits::DSA_KEY_COMPONENT_BYTE_CEILING)
3344 || private_x.as_ref().is_some_and(|component| {
3345 component.len() > crate::hard_limits::DSA_KEY_COMPONENT_BYTE_CEILING
3346 })
3347 {
3348 return Err(KeyStoreError::Invalid(
3349 "DSA component exceeds safety limit".into(),
3350 ));
3351 }
3352 let y = y.ok_or_else(|| KeyStoreError::Invalid("DSAKeyValue requires Y".into()))?;
3353 let private =
3354 if let Some(x) = private_x {
3355 let (Some(p), Some(q), Some(g)) = (&p, &q, &g) else {
3356 return Err(KeyStoreError::Invalid(
3357 "private DSA key requires P, Q, and G".into(),
3358 ));
3359 };
3360 let components = DsaComponents::from_components(
3361 BoxedUint::from_be_slice_vartime(p),
3362 BoxedUint::from_be_slice_vartime(q),
3363 BoxedUint::from_be_slice_vartime(g),
3364 )
3365 .map_err(|_| KeyStoreError::Invalid("invalid DSA parameters".into()))?;
3366 let x_value = BoxedUint::from_be_slice_vartime(&x);
3367 let monty = BoxedMontyParams::new(components.p().clone());
3368 let expected_y = BoxedMontyForm::new((**components.g()).clone(), &monty)
3369 .pow(&x_value)
3370 .retrieve();
3371 if expected_y != BoxedUint::from_be_slice_vartime(&y) {
3372 return Err(KeyStoreError::Invalid(
3373 "DSA private and public values differ".into(),
3374 ));
3375 }
3376 let public = DsaVerifyingKey::from_components(components, expected_y)
3377 .map_err(|_| KeyStoreError::Invalid("invalid DSA public key".into()))?;
3378 let private = NativeDsaSigningKey::from_components(public, x_value)
3379 .map_err(|_| KeyStoreError::Invalid("invalid DSA private key".into()))?;
3380 use der::Encode as _;
3381 let parameters_len =
3384 usize::try_from(private.verifying_key().components().encoded_len().map_err(
3385 |_| KeyStoreError::Invalid("DSA private key encoding failed".into()),
3386 )?)
3387 .map_err(|_| KeyStoreError::Selection("key material size overflow"))?;
3388 let x_bytes_len = usize::try_from(private.x().bits_precision() / 8)
3389 .map_err(|_| KeyStoreError::Selection("key material size overflow"))?;
3390 let header = 2 + std::mem::size_of::<usize>();
3393 let x_der_len = x_bytes_len
3394 .checked_add(header + 1)
3395 .ok_or(KeyStoreError::Selection("key material size overflow"))?;
3396 let pkcs8_len = parameters_len
3397 .checked_add(x_der_len)
3398 .and_then(|length| length.checked_add(4 * header + 16))
3399 .ok_or(KeyStoreError::Selection("key material size overflow"))?;
3400 budget.reserve(parameters_len)?;
3401 budget.reserve(x_bytes_len)?;
3402 budget.reserve(x_der_len)?;
3403 budget.reserve(pkcs8_len)?;
3404 budget.reserve(pkcs8_len)?;
3405 Some(Zeroizing::new(
3406 private
3407 .to_pkcs8_der()
3408 .map_err(|_| KeyStoreError::Invalid("DSA private key encoding failed".into()))?
3409 .as_bytes()
3410 .to_vec(),
3411 ))
3412 } else {
3413 None
3414 };
3415 Ok((KeyValueInfo::Dsa { p, q, g, y }, private))
3416}
3417
3418#[cfg(test)]
3419mod tests {
3420 #[cfg(not(feature = "xmlenc"))]
3421 use crate::rsa_encoding::RsaPublicKeyEncoding as _;
3422 use pkcs8::EncodePublicKey as _;
3423 use rand_chacha::{ChaCha8Rng, rand_core::SeedableRng as _};
3424 use rsa::RsaPublicKey;
3425
3426 use super::*;
3427
3428 fn xml_policy(resources: ResourcePolicy) -> crate::policy::VerificationPolicy {
3429 crate::policy::VerificationPolicy {
3430 resources,
3431 ..crate::policy::VerificationPolicy::default()
3432 }
3433 }
3434
3435 #[test]
3436 fn inventory_merge_preserves_import_work() {
3437 let resources = ResourcePolicy {
3439 max_key_import_kdf_work: 5,
3440 ..Default::default()
3441 };
3442 let mut inventory = KeyInventory {
3443 kdf_work: 2,
3444 ..Default::default()
3445 };
3446 inventory
3447 .extend(
3448 KeyInventory {
3449 kdf_work: 3,
3450 ..Default::default()
3451 },
3452 &resources,
3453 )
3454 .expect("exact work allowance");
3455 assert_eq!(inventory.key_import_kdf_work(), 5);
3456 assert!(matches!(
3457 inventory.extend(
3458 KeyInventory {
3459 kdf_work: 1,
3460 ..Default::default()
3461 },
3462 &resources
3463 ),
3464 Err(KeyStoreError::Policy(_))
3465 ));
3466 assert_eq!(inventory.key_import_kdf_work(), 5, "failed merge is atomic");
3467 }
3468
3469 #[test]
3470 fn repeated_pkcs12_imports_share_kdf_work() {
3471 let bytes = include_bytes!("../tests/fixtures/xmlenc/01-phaos-xmlenc-3/rsa-priv-key.p12");
3473 let resources = ResourcePolicy {
3474 max_key_import_kdf_work: 10_000,
3475 ..ResourcePolicy::default()
3476 };
3477 let mut inventory = KeyInventory::default();
3478 inventory
3479 .add_pkcs12("first".into(), bytes, "secret", &resources)
3480 .expect("first bundle fits");
3481 assert!(matches!(
3482 inventory.add_pkcs12("second".into(), bytes, "secret", &resources),
3483 Err(KeyStoreError::Policy(_))
3484 ));
3485 let work = inventory.key_import_kdf_work();
3486 let exact = ResourcePolicy {
3487 max_key_import_kdf_work: work * 2,
3488 ..resources
3489 };
3490 let mut inventory = KeyInventory::default();
3491 inventory
3492 .add_pkcs12("first".into(), bytes, "secret", &exact)
3493 .expect("first exact-boundary bundle");
3494 inventory
3495 .add_pkcs12("second".into(), bytes, "secret", &exact)
3496 .expect("second exact-boundary bundle");
3497 assert_eq!(inventory.key_import_kdf_work(), work * 2);
3498 }
3499
3500 #[test]
3501 fn imports_donor_pkcs12_and_rejects_wrong_password() {
3502 let bytes = include_bytes!("../tests/fixtures/xmlenc/01-phaos-xmlenc-3/rsa-priv-key.p12");
3505 let resources = ResourcePolicy::default();
3506 let mut inventory = KeyInventory::default();
3507 inventory
3508 .add_pkcs12("phaos".into(), bytes, "secret", &resources)
3509 .expect("donor PKCS#12 should import");
3510 assert_eq!(inventory.private_keys.len(), 1);
3511 assert!(!inventory.private_keys[0].certificate_chain.is_empty());
3512 assert!(inventory.private_keys[0].usages.allows(KeyUsage::Sign));
3513
3514 let mut wrong = KeyInventory::default();
3515 assert!(matches!(
3516 wrong.add_pkcs12("phaos".into(), bytes, "wrong", &resources),
3517 Err(KeyStoreError::ProtectedContainer)
3518 ));
3519 assert!(wrong.private_keys.is_empty());
3520
3521 let oversized = ResourcePolicy {
3522 max_external_resource_bytes: bytes.len() - 1,
3523 ..ResourcePolicy::default()
3524 };
3525 assert!(matches!(
3526 KeyInventory::default().add_pkcs12("phaos".into(), bytes, "secret", &oversized),
3527 Err(KeyStoreError::Policy(
3528 crate::policy::PolicyViolation::ResourceLimitExceeded {
3529 resource: crate::policy::resource_name::EXTERNAL_RESOURCE_BYTES,
3530 maximum,
3531 }
3532 )) if maximum == bytes.len() - 1
3533 ));
3534 }
3535
3536 #[test]
3537 fn pkcs12_kdf_limit_is_a_typed_policy_denial() {
3538 let bytes = include_bytes!("../tests/fixtures/xmlenc/01-phaos-xmlenc-3/rsa-priv-key.p12");
3540 let resources = ResourcePolicy {
3541 max_key_import_kdf_work: 1,
3542 ..ResourcePolicy::default()
3543 };
3544 assert!(matches!(
3545 KeyInventory::default().add_pkcs12("phaos".into(), bytes, "secret", &resources),
3546 Err(KeyStoreError::Policy(
3547 crate::policy::PolicyViolation::KdfIterationsOutsideLimit { maximum: 1 }
3548 ))
3549 ));
3550 assert!(matches!(
3551 KeyInventory::default().add_pkcs12(
3552 "phaos".into(),
3553 bytes,
3554 "wrong",
3555 &ResourcePolicy::default(),
3556 ),
3557 Err(KeyStoreError::ProtectedContainer)
3558 ));
3559 }
3560
3561 #[test]
3562 fn pkcs12_visible_encryption_kdf_is_checked_before_password() {
3563 let mut bytes =
3566 include_bytes!("../tests/fixtures/xmlenc/01-phaos-xmlenc-3/rsa-priv-key.p12").to_vec();
3567 let offset = bytes
3568 .windows(4)
3569 .position(|v| v == [2, 2, 8, 0])
3570 .expect("PBKDF2 iterations");
3571 bytes[offset + 3] = 1;
3572 let resources = ResourcePolicy {
3573 max_key_import_kdf_work: 2048,
3574 ..ResourcePolicy::default()
3575 };
3576 let result = KeyInventory::default().add_pkcs12_with_password_callback(
3577 "limited".into(),
3578 &bytes,
3579 || panic!("visible KDF must be checked first"),
3580 KeyUsages::SIGN,
3581 &resources,
3582 );
3583 assert!(matches!(result, Err(KeyStoreError::Policy(_))));
3584 }
3585
3586 #[test]
3587 fn pkcs12_content_count_denial_is_not_a_password_error() {
3588 let bytes = include_bytes!("../tests/fixtures/xmlenc/01-phaos-xmlenc-3/rsa-priv-key.p12");
3591 let resources = ResourcePolicy {
3592 max_key_candidates: 1,
3593 ..ResourcePolicy::default()
3594 };
3595 let result = KeyInventory::default().add_pkcs12_with_password_callback(
3596 "limited".into(),
3597 bytes,
3598 || panic!("container limit must be checked first"),
3599 KeyUsages::SIGN,
3600 &resources,
3601 );
3602 assert!(matches!(
3603 result,
3604 Err(KeyStoreError::Policy(
3605 crate::policy::PolicyViolation::ResourceLimitExceeded {
3606 resource: crate::policy::resource_name::KEY_CANDIDATES,
3607 maximum: 1,
3608 }
3609 ))
3610 ));
3611 }
3612
3613 #[test]
3614 fn pkcs12_aggregate_kdf_work_preserves_policy_error() {
3615 let resources = ResourcePolicy {
3617 max_key_import_kdf_work: 3000,
3618 ..ResourcePolicy::default()
3619 };
3620 let bytes = include_bytes!("../tests/fixtures/xmlenc/01-phaos-xmlenc-3/rsa-priv-key.p12");
3621 assert!(matches!(
3622 KeyInventory::default().add_pkcs12("limited".into(), bytes, "secret", &resources),
3623 Err(KeyStoreError::Policy(
3624 crate::policy::PolicyViolation::ResourceLimitExceeded {
3625 resource: crate::policy::resource_name::KEY_IMPORT_KDF_WORK,
3626 maximum: 3000,
3627 }
3628 ))
3629 ));
3630 }
3631
3632 #[test]
3633 fn pkcs12_workspace_denial_preserves_policy_error() {
3634 let resources = ResourcePolicy {
3636 max_key_import_kdf_memory_bytes: 1,
3637 ..ResourcePolicy::default()
3638 };
3639 let bytes = include_bytes!("../tests/fixtures/xmlenc/01-phaos-xmlenc-3/rsa-priv-key.p12");
3640 assert!(matches!(
3641 KeyInventory::default().add_pkcs12("limited".into(), bytes, "secret", &resources),
3642 Err(KeyStoreError::Policy(
3643 crate::policy::PolicyViolation::ResourceLimitExceeded {
3644 resource: crate::policy::resource_name::KEY_IMPORT_KDF_MEMORY,
3645 maximum: 1,
3646 }
3647 ))
3648 ));
3649 }
3650
3651 #[test]
3652 fn pkcs12_temporary_memory_shares_existing_inventory_budget() {
3653 let resources = ResourcePolicy {
3656 max_external_resource_bytes: 3000,
3657 max_external_resource_total_bytes: 5000,
3658 ..ResourcePolicy::default()
3659 };
3660 let mut inventory = KeyInventory::default();
3661 inventory
3662 .add_symmetric(
3663 "hmac".into(),
3664 SymmetricKeyKind::Hmac,
3665 vec![1; 2000],
3666 KeyUsages::SIGN,
3667 &resources,
3668 )
3669 .expect("existing key fits");
3670 let bytes = include_bytes!("../tests/fixtures/xmlenc/01-phaos-xmlenc-3/rsa-priv-key.p12");
3671 assert!(matches!(
3672 inventory.add_pkcs12("bundle".into(), bytes, "secret", &resources),
3673 Err(KeyStoreError::Policy(
3674 crate::policy::PolicyViolation::ResourceLimitExceeded {
3675 resource: crate::policy::resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES,
3676 maximum: 5000
3677 }
3678 ))
3679 ));
3680 assert_eq!(inventory.symmetric_keys().len(), 1);
3681 assert!(inventory.private_keys().is_empty());
3682 }
3683
3684 #[test]
3685 fn private_bundle_certificates_do_not_authorize_verification() {
3686 let bytes = include_bytes!("../tests/fixtures/xmlenc/01-phaos-xmlenc-3/rsa-priv-key.p12");
3689 let resources = ResourcePolicy::default();
3690 let mut inventory = KeyInventory::default();
3691 inventory
3692 .add_pkcs12("private".into(), bytes, "secret", &resources)
3693 .expect("bundle imports");
3694 let leaf = inventory.private_keys()[0].certificate_chain[0].clone();
3695 let subject = crate::xmldsig::parse::parse_x509_certificate(&leaf)
3696 .expect("bundle leaf parses")
3697 .subject_dn;
3698 let key_info = KeyInfo {
3699 sources: vec![KeyInfoSource::X509Data(X509DataInfo {
3700 subject_names: vec![subject],
3701 ..X509DataInfo::default()
3702 })],
3703 };
3704 let resolver = inventory.verification_resolver();
3705 assert!(
3706 resolver
3707 .resolve(Some(&key_info), SignatureAlgorithm::RsaSha256)
3708 .expect("lookup completes")
3709 .is_none()
3710 );
3711
3712 inventory
3713 .add_certificate_der(leaf, false, &resources)
3714 .expect("explicit lookup certificate imports");
3715 assert!(
3716 inventory
3717 .verification_resolver()
3718 .resolve(Some(&key_info), SignatureAlgorithm::RsaSha256)
3719 .expect("explicit lookup completes")
3720 .is_some()
3721 );
3722 }
3723
3724 #[test]
3725 fn stored_signing_keys_obey_operation_material_limits() {
3726 let resources = ResourcePolicy::default();
3729 let mut inventory = KeyInventory::default();
3730 inventory
3731 .add_symmetric(
3732 "hmac".into(),
3733 SymmetricKeyKind::Hmac,
3734 b"0123456789abcdef0123456789abcdef".to_vec(),
3735 KeyUsages::SIGN,
3736 &resources,
3737 )
3738 .expect("HMAC imports");
3739 inventory
3740 .add_private_pem(
3741 "rsa".into(),
3742 include_bytes!("../tests/fixtures/keys/rsa/rsa-2048-key.pem"),
3743 None,
3744 KeyUsages::SIGN,
3745 &resources,
3746 )
3747 .expect("RSA imports");
3748
3749 for (name, algorithm, length) in [
3750 (
3751 "hmac",
3752 SignatureAlgorithm::HmacSha256,
3753 inventory.symmetric_keys[0].bytes.len(),
3754 ),
3755 (
3756 "rsa",
3757 SignatureAlgorithm::RsaSha256,
3758 inventory.private_keys[0].pkcs8_der.len(),
3759 ),
3760 ] {
3761 let mut policy = crate::policy::SigningPolicy::default();
3762 policy.resources.max_external_resource_bytes = length - 1;
3763 assert!(matches!(
3764 inventory.signing_key(name, algorithm, &policy),
3765 Err(KeyStoreError::Policy(
3766 crate::policy::PolicyViolation::ResourceLimit {
3767 resource: crate::policy::resource_name::EXTERNAL_RESOURCE_BYTES,
3768 maximum,
3769 actual,
3770 }
3771 )) if maximum == length - 1 && actual == length
3772 ));
3773
3774 policy.resources.max_external_resource_bytes = length;
3775 policy.resources.max_external_resource_total_bytes = length - 1;
3776 assert!(matches!(
3777 inventory.signing_key(name, algorithm, &policy),
3778 Err(KeyStoreError::Policy(
3779 crate::policy::PolicyViolation::ResourceLimit {
3780 resource: crate::policy::resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES,
3781 maximum,
3782 actual,
3783 }
3784 )) if maximum == length - 1 && actual == length
3785 ));
3786 }
3787 }
3788
3789 #[test]
3790 fn named_signing_lookup_obeys_active_candidate_budget() {
3791 let resources = ResourcePolicy::default();
3793 let mut inventory = KeyInventory::default();
3794 for name in ["first", "second"] {
3795 inventory
3796 .add_symmetric(
3797 name.into(),
3798 SymmetricKeyKind::Hmac,
3799 vec![0x42; 32],
3800 KeyUsages::SIGN,
3801 &resources,
3802 )
3803 .expect("HMAC imports");
3804 }
3805 let mut policy = crate::policy::SigningPolicy::default();
3806 policy.resources.max_key_candidates = 1;
3807 assert!(
3808 inventory
3809 .signing_key("first", SignatureAlgorithm::HmacSha256, &policy)
3810 .is_ok()
3811 );
3812 assert!(matches!(
3813 inventory.signing_key("second", SignatureAlgorithm::HmacSha256, &policy),
3814 Err(KeyStoreError::Policy(
3815 crate::policy::PolicyViolation::ResourceLimit {
3816 resource: crate::policy::resource_name::KEY_CANDIDATES,
3817 ..
3818 }
3819 ))
3820 ));
3821 }
3822
3823 #[cfg(feature = "xmlenc")]
3824 #[test]
3825 fn named_decryption_lookup_shares_candidate_budget_across_key_kinds() {
3826 let resources = ResourcePolicy::default();
3829 let mut inventory = KeyInventory::default();
3830 inventory
3831 .add_symmetric(
3832 "aes".into(),
3833 SymmetricKeyKind::Aes,
3834 vec![0x42; 32],
3835 KeyUsages::DECRYPT,
3836 &resources,
3837 )
3838 .expect("AES imports");
3839 inventory
3840 .add_private_pem(
3841 "rsa".into(),
3842 include_bytes!("../tests/fixtures/keys/rsa/rsa-2048-key.pem"),
3843 None,
3844 KeyUsages::DECRYPT,
3845 &resources,
3846 )
3847 .expect("RSA imports");
3848 let mut policy = crate::policy::DecryptionPolicy::default();
3849 policy.resources.max_key_candidates = 1;
3850 assert!(inventory.decryption_resolver("aes", &policy).is_ok());
3851 assert!(matches!(
3852 inventory.decryption_resolver("rsa", &policy),
3853 Err(KeyStoreError::Policy(
3854 crate::policy::PolicyViolation::ResourceLimit {
3855 resource: crate::policy::resource_name::KEY_CANDIDATES,
3856 ..
3857 }
3858 ))
3859 ));
3860 }
3861
3862 #[test]
3863 fn pkcs12_imports_share_candidate_budget() {
3864 let bundle = include_bytes!("../tests/fixtures/xmlenc/01-phaos-xmlenc-3/rsa-priv-key.p12");
3868 let resources = ResourcePolicy {
3869 max_key_candidates: 4,
3870 ..ResourcePolicy::default()
3871 };
3872 let mut inventory = KeyInventory::default();
3873 inventory
3874 .add_pkcs12("first".into(), bundle, "secret", &resources)
3875 .expect("first bundle fits");
3876 assert_eq!(inventory.entry_count(), 2);
3877 assert!(
3878 inventory.private_keys()[0]
3879 .matching_certificate_chain()
3880 .is_some()
3881 );
3882 assert!(
3883 inventory
3884 .add_pkcs12("second".into(), bundle, "secret", &resources)
3885 .is_err()
3886 );
3887 }
3888
3889 #[test]
3890 fn pkcs12_temporary_budget_excludes_live_input() {
3891 let resources = ResourcePolicy::default();
3894 let mut inventory = KeyInventory::default();
3895 inventory
3896 .add_symmetric(
3897 "old".into(),
3898 SymmetricKeyKind::Hmac,
3899 vec![7; 32],
3900 KeyUsages::SIGN,
3901 &resources,
3902 )
3903 .expect("retained key");
3904 let input = [0; 128];
3905 let limits = inventory
3906 .pkcs12_import_limits("new", &input, KeyUsages::SIGN, &resources)
3907 .expect("import allowance");
3908 assert_eq!(
3909 limits.memory_available,
3910 resources.max_external_resource_total_bytes
3911 - inventory.material_bytes
3912 - 3
3913 - input.len()
3914 );
3915 }
3916
3917 #[test]
3918 fn pkcs12_input_consumes_aggregate_import_budget() {
3919 let bundle = include_bytes!("../tests/fixtures/xmlenc/01-phaos-xmlenc-3/rsa-priv-key.p12");
3921 let resources = ResourcePolicy::default();
3922 let mut inventory = KeyInventory::default();
3923 inventory
3924 .add_pkcs12("first".into(), bundle, "secret", &resources)
3925 .expect("first bundle imports");
3926 assert!(inventory.material_bytes >= bundle.len());
3927 let limited = ResourcePolicy {
3928 max_external_resource_total_bytes: bundle.len() * 2 - 1,
3929 ..resources
3930 };
3931 assert!(
3932 inventory
3933 .add_pkcs12("second".into(), bundle, "secret", &limited)
3934 .is_err()
3935 );
3936 }
3937
3938 #[test]
3939 fn pkcs12_unrelated_ca_does_not_block_private_key() {
3940 let bundle = base64::engine::general_purpose::STANDARD
3943 .decode(
3944 include_str!("../tests/fixtures/keys/pkcs12/rsa-key-unrelated-ca.p12.b64").trim(),
3945 )
3946 .expect("fixture base64 decodes");
3947 let mut inventory = KeyInventory::default();
3948 inventory
3949 .add_pkcs12(
3950 "ca-only".into(),
3951 &bundle,
3952 "secret",
3953 &ResourcePolicy::default(),
3954 )
3955 .expect("unrelated CA must not invalidate the private key");
3956 assert_eq!(inventory.private_keys.len(), 1);
3957 assert_eq!(inventory.private_keys[0].certificate_chain.len(), 1);
3958 assert!(!inventory.private_keys[0].has_matching_leaf);
3959 assert!(
3960 inventory
3961 .signing_key(
3962 "ca-only",
3963 SignatureAlgorithm::RsaSha256,
3964 &crate::policy::SigningPolicy::default(),
3965 )
3966 .is_ok()
3967 );
3968 }
3969
3970 #[test]
3971 fn pkcs12_identical_leaf_bags_are_one_candidate() {
3972 let bundle = base64::engine::general_purpose::STANDARD
3975 .decode(include_str!("../tests/fixtures/keys/pkcs12/rsa-duplicate-leaf.p12.b64").trim())
3976 .expect("fixture base64 decodes");
3977 let mut inventory = KeyInventory::default();
3978 inventory
3979 .add_pkcs12(
3980 "duplicate-leaf".into(),
3981 &bundle,
3982 "secret",
3983 &ResourcePolicy::default(),
3984 )
3985 .expect("identical leaf bags are not ambiguous");
3986 assert_eq!(inventory.private_keys[0].certificate_chain.len(), 1);
3987 assert!(inventory.private_keys[0].has_matching_leaf);
3988 }
3989
3990 #[cfg(feature = "experimental-pq")]
3991 #[test]
3992 fn kem_pkcs12_auto_usage_is_decrypt_only() {
3993 let key = crate::provider::RustCryptoMlKemPrivateKey::from_seed(
3996 crate::provider::KeyEncapsulationAlgorithm::MlKem512,
3997 &[3; 64],
3998 )
3999 .expect("valid ML-KEM seed");
4000 let der = key
4001 .to_pkcs8_der(crate::provider::MlKemPrivateKeyEncoding::Seed)
4002 .expect("seed PKCS#8 export");
4003 let contents = || pkcs12_import::Contents {
4004 private_keys: vec![Zeroizing::new(der.as_bytes().to_vec())],
4005 certificates: Vec::new(),
4006 };
4007 let resources = ResourcePolicy::default();
4008 let mut inventory = KeyInventory::default();
4009 inventory
4010 .add_pkcs12_contents(
4011 "kem".into(),
4012 der.as_bytes().len(),
4013 contents(),
4014 KeyUsages::SIGN.union(KeyUsages::DECRYPT),
4015 &resources,
4016 true,
4017 )
4018 .expect("automatic recipient usages");
4019 assert_eq!(inventory.private_keys()[0].usages, KeyUsages::DECRYPT);
4020 for usages in [
4023 KeyUsages::VERIFY,
4024 KeyUsages::ENCRYPT,
4025 KeyUsages::DECRYPT.union(KeyUsages::VERIFY),
4026 KeyUsages::DECRYPT.union(KeyUsages::ENCRYPT),
4027 KeyUsages::DECRYPT.union(KeyUsages::SIGN),
4028 ] {
4029 let mut rejected = KeyInventory::default();
4030 assert!(
4031 rejected
4032 .add_pkcs12_contents(
4033 "kem".into(),
4034 der.as_bytes().len(),
4035 contents(),
4036 usages,
4037 &resources,
4038 false,
4039 )
4040 .is_err()
4041 );
4042 assert!(rejected.private_keys().is_empty());
4043 }
4044 assert!(
4045 KeyInventory::default()
4046 .add_pkcs12_contents(
4047 "kem".into(),
4048 der.as_bytes().len(),
4049 contents(),
4050 KeyUsages::SIGN,
4051 &resources,
4052 false
4053 )
4054 .is_err()
4055 );
4056 }
4057
4058 #[test]
4059 fn ec_pkcs12_import_is_sign_only() {
4060 let bundle = base64::engine::general_purpose::STANDARD
4062 .decode(include_str!("../tests/fixtures/keys/pkcs12/ec-key.p12.b64").trim())
4063 .expect("fixture base64 decodes");
4064 let resources = ResourcePolicy::default();
4065 let mut inventory = KeyInventory::default();
4066 inventory
4067 .add_pkcs12("ec".into(), &bundle, "secret", &resources)
4068 .expect("EC signing bundle imports");
4069 assert!(inventory.private_keys()[0].usages.allows(KeyUsage::Sign));
4070 assert!(!inventory.private_keys()[0].usages.allows(KeyUsage::Decrypt));
4071 assert!(
4072 KeyInventory::default()
4073 .add_pkcs12_with_usages(
4074 "ec".into(),
4075 &bundle,
4076 "secret",
4077 KeyUsages::DECRYPT,
4078 &resources,
4079 )
4080 .is_err()
4081 );
4082 }
4083
4084 #[test]
4085 fn password_callback_runs_for_protected_container() {
4086 let bundle = include_bytes!("../tests/fixtures/xmlenc/01-phaos-xmlenc-3/rsa-priv-key.p12");
4089 let mut inventory = KeyInventory::default();
4090 inventory
4091 .add_pkcs12_with_password_callback(
4092 "bundle".into(),
4093 bundle,
4094 || Some(Zeroizing::new("secret".to_owned())),
4095 KeyUsages::SIGN,
4096 &ResourcePolicy::default(),
4097 )
4098 .expect("callback password decrypts donor bundle");
4099 assert!(inventory.private_keys[0].usages.allows(KeyUsage::Sign));
4100 assert!(matches!(
4101 KeyInventory::default().add_pkcs12_with_password_callback(
4102 "bundle".into(),
4103 bundle,
4104 || None,
4105 KeyUsages::SIGN,
4106 &ResourcePolicy::default(),
4107 ),
4108 Err(KeyStoreError::ProtectedContainer)
4109 ));
4110 let limited = ResourcePolicy {
4111 max_external_resource_total_bytes: bundle.len() - 1,
4112 ..ResourcePolicy::default()
4113 };
4114 assert!(matches!(
4115 KeyInventory::default().add_pkcs12_with_password_callback(
4116 "bundle".into(),
4117 bundle,
4118 || panic!("budget must be checked before password delivery"),
4119 KeyUsages::SIGN,
4120 &limited,
4121 ),
4122 Err(KeyStoreError::Policy(
4123 crate::policy::PolicyViolation::ResourceLimitExceeded {
4124 resource: crate::policy::resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES,
4125 ..
4126 }
4127 ))
4128 ));
4129 let limited_kdf = ResourcePolicy {
4130 max_key_import_kdf_work: 1,
4131 ..ResourcePolicy::default()
4132 };
4133 assert!(matches!(
4134 KeyInventory::default().add_pkcs12_with_password_callback(
4135 "bundle".into(),
4136 bundle,
4137 || panic!("KDF denial must precede password delivery"),
4138 KeyUsages::SIGN,
4139 &limited_kdf,
4140 ),
4141 Err(KeyStoreError::Policy(
4142 crate::policy::PolicyViolation::KdfIterationsOutsideLimit { maximum: 1 }
4143 ))
4144 ));
4145 let oversized_bundle = ResourcePolicy {
4146 max_external_resource_bytes: bundle.len() - 1,
4147 ..ResourcePolicy::default()
4148 };
4149 assert!(matches!(
4150 KeyInventory::default().add_pkcs12_with_password_callback(
4151 "bundle".into(),
4152 bundle,
4153 || panic!("size denial must precede password delivery"),
4154 KeyUsages::SIGN,
4155 &oversized_bundle,
4156 ),
4157 Err(KeyStoreError::Policy(
4158 crate::policy::PolicyViolation::ResourceLimitExceeded {
4159 resource: crate::policy::resource_name::EXTERNAL_RESOURCE_BYTES,
4160 maximum,
4161 }
4162 )) if maximum == bundle.len() - 1
4163 ));
4164 }
4165
4166 #[test]
4167 fn pkcs12_callback_preflights_indefinite_outer_ber() {
4168 let bundle = include_bytes!("../tests/fixtures/xmlenc/01-phaos-xmlenc-3/rsa-priv-key.p12");
4170 assert_eq!(bundle[0], 0x30);
4171 let length_octets = usize::from(bundle[1] & 0x7f);
4172 assert!(bundle[1] & 0x80 != 0 && length_octets > 0);
4173 let mut ber = vec![0x30, 0x80];
4174 ber.extend_from_slice(&bundle[2 + length_octets..]);
4175 ber.extend_from_slice(&[0, 0]);
4176 let resources = ResourcePolicy {
4177 max_key_import_kdf_work: 1,
4178 ..ResourcePolicy::default()
4179 };
4180 assert!(matches!(
4181 KeyInventory::default().add_pkcs12_with_password_callback(
4182 "bundle".into(),
4183 &ber,
4184 || panic!("BER MAC KDF denial must precede password delivery"),
4185 KeyUsages::SIGN,
4186 &resources,
4187 ),
4188 Err(KeyStoreError::Policy(
4189 crate::policy::PolicyViolation::KdfIterationsOutsideLimit { maximum: 1 }
4190 ))
4191 ));
4192 }
4193
4194 #[test]
4195 fn donor_xml_store_preserves_private_dsa_material() {
4196 let bytes = include_bytes!("../tests/fixtures/keys/xmlsec/mixed-keys.xml");
4199 let inventory = KeyInventory::from_xml_bytes(
4200 bytes,
4201 &xml_policy(ResourcePolicy::default()),
4202 XmlBackend::default(),
4203 )
4204 .expect("donor key store should parse");
4205 let dsa = inventory
4206 .private_keys
4207 .iter()
4208 .find(|entry| entry.name == "test-dsa")
4209 .expect("DSA private X should be imported");
4210 assert!(dsa.usages.allows(KeyUsage::Sign));
4211 assert!(!dsa.usages.allows(KeyUsage::Decrypt));
4212 assert!(DsaSigningKey::from_pkcs8_der(&dsa.pkcs8_der).is_ok());
4213 let dsa_public = inventory
4214 .public_keys()
4215 .iter()
4216 .find(|entry| entry.name == "test-dsa")
4217 .expect("DSA public entry is retained");
4218 assert_eq!(dsa_public.usages, KeyUsages::VERIFY);
4219 }
4220
4221 #[test]
4222 fn xml_store_charges_retained_decoded_material() {
4223 let source = include_str!("../tests/fixtures/keys/xmlsec/mixed-keys.xml");
4226 let marker = source.find("<KeyName>test-dsa</KeyName>").expect("DSA key");
4227 let start = source[..marker].rfind("<KeyInfo").expect("DSA KeyInfo");
4228 let end =
4229 marker + source[marker..].find("</KeyInfo>").expect("DSA end") + "</KeyInfo>".len();
4230 let xml = format!(
4231 "<Keys xmlns=\"{XMLSEC_NS}\">{}</Keys>",
4232 source[start..end]
4233 .replace('\n', "")
4234 .replace("test-dsa", &"name".repeat(512))
4235 );
4236 let inventory = KeyInventory::from_xml_bytes(
4237 xml.as_bytes(),
4238 &xml_policy(ResourcePolicy::default()),
4239 XmlBackend::default(),
4240 )
4241 .expect("compact DSA store imports");
4242 let retained = inventory.retained_material_bytes().expect("bounded tally");
4243 assert_eq!(inventory.material_bytes, xml.len() + retained);
4244 assert!(retained >= inventory.private_keys[0].pkcs8_der.len());
4245 assert!(
4246 retained > xml.len(),
4247 "fixture must distinguish retained bytes from source bytes"
4248 );
4249 let resources = ResourcePolicy {
4250 max_external_resource_total_bytes: retained - 1,
4251 ..ResourcePolicy::default()
4252 };
4253 assert!(matches!(
4254 KeyInventory::from_xml_bytes(
4255 xml.as_bytes(),
4256 &xml_policy(resources),
4257 XmlBackend::default()
4258 ),
4259 Err(KeyStoreError::Policy(
4260 crate::policy::PolicyViolation::ResourceLimitExceeded {
4261 resource: crate::policy::resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES,
4262 ..
4263 }
4264 ))
4265 ));
4266 }
4267
4268 #[test]
4269 fn xml_import_session_keeps_failed_work_and_preflights_live_material() {
4270 let xml = |value: &str| {
4273 format!(
4274 "<Keys xmlns=\"{XMLSEC_NS}\" xmlns:ds=\"{XMLDSIG_NS}\"><ds:KeyInfo><ds:KeyName>a</ds:KeyName><ds:KeyValue><HMACKeyValue>{value}</HMACKeyValue></ds:KeyValue></ds:KeyInfo></Keys>"
4275 )
4276 };
4277 let policy = xml_policy(ResourcePolicy {
4278 max_key_candidates: 1,
4279 ..ResourcePolicy::default()
4280 });
4281 let mut importer =
4282 XmlKeyStoreImporter::new(&policy, XmlBackend::default()).expect("session");
4283 assert!(matches!(
4284 importer.import(xml("!").as_bytes()),
4285 Err(KeyStoreError::Invalid(_))
4286 ));
4287 assert!(matches!(
4288 importer.import(xml("AA==").as_bytes()),
4289 Err(KeyStoreError::Policy(
4290 crate::policy::PolicyViolation::ResourceLimitExceeded {
4291 resource: crate::policy::resource_name::KEY_CANDIDATES,
4292 maximum: 1
4293 }
4294 ))
4295 ));
4296 assert_eq!(importer.finish().entry_count(), 0);
4297 let valid = xml("AA==");
4298 let policy = xml_policy(ResourcePolicy {
4299 max_external_resource_total_bytes: valid.len() + 3,
4301 ..ResourcePolicy::default()
4302 });
4303 let mut importer =
4304 XmlKeyStoreImporter::new(&policy, XmlBackend::default()).expect("session");
4305 importer
4306 .import(valid.as_bytes())
4307 .expect("first source fits exactly");
4308 let consumed = importer.parse_work.consumed();
4309 assert!(matches!(
4310 importer.import(b"!!"),
4311 Err(KeyStoreError::Policy(
4312 crate::policy::PolicyViolation::ResourceLimitExceeded {
4313 resource: crate::policy::resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES,
4314 ..
4315 }
4316 ))
4317 ));
4318 assert_eq!(
4319 importer.parse_work.consumed(),
4320 consumed,
4321 "no parser work after byte denial"
4322 );
4323 assert_eq!(importer.finish().entry_count(), 1);
4324 }
4325
4326 #[test]
4327 fn xml_store_rejects_empty_symmetric_material() {
4328 for kind in ["HMACKeyValue", "AESKeyValue", "DESKeyValue"] {
4330 let xml = format!(
4331 "<Keys xmlns=\"{XMLSEC_NS}\"><KeyInfo xmlns=\"{XMLDSIG_NS}\"><KeyName>empty</KeyName><KeyValue><{kind} xmlns=\"{XMLSEC_NS}\"/></KeyValue></KeyInfo></Keys>"
4332 );
4333 assert!(
4334 KeyInventory::from_xml_bytes(
4335 xml.as_bytes(),
4336 &xml_policy(ResourcePolicy::default()),
4337 XmlBackend::default(),
4338 )
4339 .is_err()
4340 );
4341 }
4342 }
4343
4344 #[test]
4345 fn aes_imports_require_supported_key_widths() {
4346 let resources = ResourcePolicy::default();
4348 for length in [1, 15, 17, 23, 25, 31, 33] {
4349 let mut inventory = KeyInventory::default();
4350 assert!(
4351 inventory
4352 .add_symmetric(
4353 "aes".into(),
4354 SymmetricKeyKind::Aes,
4355 vec![0; length],
4356 KeyUsages::ENCRYPT,
4357 &resources,
4358 )
4359 .is_err()
4360 );
4361 let xml = format!(
4362 "<Keys xmlns=\"{XMLSEC_NS}\"><KeyInfo xmlns=\"{XMLDSIG_NS}\"><KeyName>aes</KeyName><KeyValue><AESKeyValue xmlns=\"{XMLSEC_NS}\">{}</AESKeyValue></KeyValue></KeyInfo></Keys>",
4363 base64::Engine::encode(&base64::engine::general_purpose::STANDARD, vec![0; length])
4364 );
4365 assert!(
4366 KeyInventory::from_xml_bytes(
4367 xml.as_bytes(),
4368 &xml_policy(resources.clone()),
4369 XmlBackend::default(),
4370 )
4371 .is_err()
4372 );
4373 }
4374 for length in [16, 24, 32] {
4375 let mut inventory = KeyInventory::default();
4376 inventory
4377 .add_symmetric(
4378 "aes".into(),
4379 SymmetricKeyKind::Aes,
4380 vec![0; length],
4381 KeyUsages::ENCRYPT,
4382 &resources,
4383 )
4384 .expect("AES-128/192/256 key imports");
4385 }
4386 }
4387
4388 #[test]
4389 fn unsupported_des_material_is_not_authorized() {
4390 let resources = ResourcePolicy::default();
4393 let mut inventory = KeyInventory::default();
4394 assert!(
4395 inventory
4396 .add_symmetric(
4397 "des".into(),
4398 SymmetricKeyKind::Des,
4399 vec![0x42; 8],
4400 KeyUsages::ENCRYPT,
4401 &resources,
4402 )
4403 .is_err()
4404 );
4405 let xml = format!(
4406 "<Keys xmlns=\"{XMLSEC_NS}\"><KeyInfo xmlns=\"{XMLDSIG_NS}\"><KeyName>des</KeyName><KeyValue><DESKeyValue xmlns=\"{XMLSEC_NS}\">QkJCQkJCQkI=</DESKeyValue></KeyValue></KeyInfo></Keys>"
4407 );
4408 assert!(
4409 KeyInventory::from_xml_bytes(
4410 xml.as_bytes(),
4411 &xml_policy(resources.clone()),
4412 XmlBackend::default(),
4413 )
4414 .is_err()
4415 );
4416 }
4417
4418 #[test]
4419 fn xml_store_accepts_xmlsig11_ec_key_value() {
4420 let pem = pem::parse(include_bytes!(
4422 "../tests/fixtures/keys/ec/ec-prime256v1-pubkey.pem"
4423 ))
4424 .expect("EC public PEM decodes");
4425 let (_, spki) = x509_parser::x509::SubjectPublicKeyInfo::from_der(pem.contents())
4426 .expect("EC SPKI parses");
4427 let point =
4428 base64::engine::general_purpose::STANDARD.encode(spki.subject_public_key.data.as_ref());
4429 let xml = format!(
4430 "<Keys xmlns=\"{XMLSEC_NS}\"><KeyInfo xmlns=\"{XMLDSIG_NS}\"><KeyName>ec</KeyName><KeyValue><ECKeyValue xmlns=\"{XMLDSIG11_NS}\"><NamedCurve URI=\"urn:oid:1.2.840.10045.3.1.7\"/><PublicKey>{point}</PublicKey></ECKeyValue></KeyValue></KeyInfo></Keys>"
4431 );
4432 let store = KeyInventory::from_xml_bytes(
4433 xml.as_bytes(),
4434 &xml_policy(ResourcePolicy::default()),
4435 XmlBackend::default(),
4436 )
4437 .expect("ECKeyValue namespace is supported");
4438 assert_eq!(store.public_keys().len(), 1);
4439 assert_eq!(store.public_keys()[0].usages, KeyUsages::VERIFY);
4440 assert!(
4441 store.public_keys()[0]
4442 .key_info
4443 .sources
4444 .iter()
4445 .any(|source| matches!(source, KeyInfoSource::KeyValue(KeyValueInfo::Ec { .. })))
4446 );
4447 }
4448
4449 #[cfg(feature = "legacy-algorithms")]
4450 #[test]
4451 fn xml_store_imports_triple_des_without_granting_operation_permission() {
4452 let encoded = base64::engine::general_purpose::STANDARD.encode([0x31; 24]);
4455 let xml = format!(
4456 "<Keys xmlns=\"{XMLSEC_NS}\"><KeyInfo xmlns=\"{XMLDSIG_NS}\"><KeyName>des</KeyName><KeyValue><DESKeyValue xmlns=\"{XMLSEC_NS}\">{encoded}</DESKeyValue></KeyValue></KeyInfo></Keys>"
4457 );
4458 let store = KeyInventory::from_xml_bytes(
4459 xml.as_bytes(),
4460 &xml_policy(ResourcePolicy::default()),
4461 XmlBackend::default(),
4462 )
4463 .expect("a three-key TDEA inventory entry is supported");
4464 assert_eq!(store.symmetric_keys()[0].kind, SymmetricKeyKind::Des);
4465 assert_eq!(store.symmetric_keys()[0].bytes.as_slice(), &[0x31; 24]);
4466 assert!(store.symmetric_keys()[0].usages.allows(KeyUsage::Decrypt));
4467 }
4468
4469 #[test]
4470 fn xml_store_rejects_unusable_public_key_values() {
4471 let cases = [
4473 "<RSAKeyValue><Modulus></Modulus><Exponent>AQAB</Exponent></RSAKeyValue>",
4474 "<ECKeyValue xmlns=\"http://www.w3.org/2009/xmldsig11#\"><NamedCurve URI=\"urn:oid:1.2.840.10045.3.1.7\"/><PublicKey>AQ==</PublicKey></ECKeyValue>",
4475 ];
4476 for key in cases {
4477 let xml = format!(
4478 "<Keys xmlns=\"{XMLSEC_NS}\"><KeyInfo xmlns=\"{XMLDSIG_NS}\"><KeyName>invalid</KeyName><KeyValue>{key}</KeyValue></KeyInfo></Keys>"
4479 );
4480 assert!(
4481 KeyInventory::from_xml_bytes(
4482 xml.as_bytes(),
4483 &xml_policy(ResourcePolicy::default()),
4484 XmlBackend::default(),
4485 )
4486 .is_err()
4487 );
4488 }
4489 }
4490
4491 #[test]
4492 fn xml_store_import_limits_preserve_policy_errors() {
4493 let xml = format!(
4495 "<Keys xmlns=\"{XMLSEC_NS}\"><KeyInfo xmlns=\"{XMLDSIG_NS}\"><KeyName>key</KeyName><KeyValue><HMACKeyValue xmlns=\"{XMLSEC_NS}\">c2VjcmV0</HMACKeyValue></KeyValue></KeyInfo></Keys>"
4496 );
4497 let two = xml.replace(
4498 "</Keys>",
4499 &format!(
4500 "{} </Keys>",
4501 xml[xml.find("<KeyInfo").expect("fixture has KeyInfo")
4502 ..xml.find("</Keys>").expect("fixture has Keys end tag")]
4503 .replace(">key<", ">other<")
4504 ),
4505 );
4506 for (bytes, resources, expected) in [
4507 (
4508 xml.as_bytes(),
4509 ResourcePolicy {
4510 max_external_resource_bytes: xml.len() - 1,
4511 ..ResourcePolicy::default()
4512 },
4513 crate::policy::resource_name::EXTERNAL_RESOURCE_BYTES,
4514 ),
4515 (
4516 xml.as_bytes(),
4517 ResourcePolicy {
4518 max_external_resource_total_bytes: xml.len() - 1,
4519 ..ResourcePolicy::default()
4520 },
4521 crate::policy::resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES,
4522 ),
4523 (
4524 two.as_bytes(),
4525 ResourcePolicy {
4526 max_key_candidates: 1,
4527 ..ResourcePolicy::default()
4528 },
4529 crate::policy::resource_name::KEY_CANDIDATES,
4530 ),
4531 ] {
4532 assert!(
4533 matches!(KeyInventory::from_xml_bytes(bytes, &xml_policy(resources), XmlBackend::default()),
4534 Err(KeyStoreError::Policy(crate::policy::PolicyViolation::ResourceLimitExceeded { resource, .. })) if resource == expected)
4535 );
4536 }
4537 let mut inventory = KeyInventory::default();
4538 let resources = ResourcePolicy {
4539 max_external_resource_total_bytes: 3,
4540 ..ResourcePolicy::default()
4541 };
4542 assert!(matches!(
4543 inventory.add_symmetric(
4544 "key".into(),
4545 SymmetricKeyKind::Hmac,
4546 vec![7; 32],
4547 KeyUsages::SIGN,
4548 &resources
4549 ),
4550 Err(KeyStoreError::Policy(
4551 crate::policy::PolicyViolation::ResourceLimitExceeded {
4552 resource: crate::policy::resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES,
4553 ..
4554 }
4555 ))
4556 ));
4557 }
4558
4559 #[test]
4560 fn xml_store_enforces_all_parser_resource_limits() {
4561 let xml = format!(
4563 "<Keys xmlns=\"{XMLSEC_NS}\" xmlns:a=\"urn:a\" xmlns:b=\"urn:b\"><KeyInfo xmlns=\"{XMLDSIG_NS}\"><KeyName>key</KeyName><KeyValue><HMACKeyValue xmlns=\"{XMLSEC_NS}\">c2VjcmV0</HMACKeyValue></KeyValue></KeyInfo></Keys>"
4564 );
4565 for resources in [
4566 ResourcePolicy {
4567 max_xml_depth: 2,
4568 ..ResourcePolicy::default()
4569 },
4570 ResourcePolicy {
4571 max_xml_namespace_bindings: 1,
4572 ..ResourcePolicy::default()
4573 },
4574 ResourcePolicy {
4575 max_xml_parse_work_bytes: 1,
4576 ..ResourcePolicy::default()
4577 },
4578 ] {
4579 assert!(
4580 KeyInventory::from_xml_bytes(
4581 xml.as_bytes(),
4582 &xml_policy(resources),
4583 XmlBackend::default()
4584 )
4585 .is_err_and(|error| matches!(error, KeyStoreError::Policy(_))),
4586 "parser policy denial must retain its type in key stores"
4587 );
4588 }
4589 }
4590
4591 #[test]
4592 fn xml_store_bounds_source_before_utf16_decode() {
4593 let xml = format!("<Keys xmlns=\"{XMLSEC_NS}\"/>");
4595 let mut bytes = vec![0xff, 0xfe];
4596 for unit in xml.encode_utf16() {
4597 bytes.extend_from_slice(&unit.to_le_bytes());
4598 }
4599 let resources = ResourcePolicy {
4600 max_xml_document_bytes: xml.len() + 1,
4601 ..ResourcePolicy::default()
4602 };
4603 assert!(bytes.len() > resources.max_xml_document_bytes);
4604 assert!(
4605 KeyInventory::from_xml_bytes(&bytes, &xml_policy(resources), XmlBackend::default())
4606 .is_err()
4607 );
4608 }
4609
4610 #[test]
4611 fn xml_store_uses_operation_xml_policy() {
4612 let xml = format!(
4615 "<!DOCTYPE Keys [<!ENTITY key 'named'>]><Keys xmlns=\"{XMLSEC_NS}\"><KeyInfo xmlns=\"{XMLDSIG_NS}\"><KeyName>&key;</KeyName><KeyValue><HMACKeyValue xmlns=\"{XMLSEC_NS}\">c2VjcmV0</HMACKeyValue></KeyValue></KeyInfo></Keys>"
4616 );
4617 let denied = crate::policy::VerificationPolicy::default();
4618 assert!(
4619 KeyInventory::from_xml_bytes(xml.as_bytes(), &denied, XmlBackend::default()).is_err()
4620 );
4621 let mut allowed = denied;
4622 allowed.xml.allow_internal_dtd = true;
4623 assert!(
4624 KeyInventory::from_xml_bytes(xml.as_bytes(), &allowed, XmlBackend::default()).is_ok()
4625 );
4626 }
4627
4628 #[test]
4629 fn ec_public_key_cannot_authorize_encryption() {
4630 let resources = ResourcePolicy::default();
4632 let ec = include_bytes!("../tests/fixtures/keys/ec/ec-prime256v1-pubkey.pem");
4633 let cert = include_bytes!("../tests/fixtures/keys/ec/ec-prime256v1-cert.pem");
4634 let mut store = KeyInventory::default();
4635 store
4636 .add_public_pem("ec".into(), ec, &resources)
4637 .expect("EC public key imports");
4638 assert_eq!(store.public_keys()[0].usages, KeyUsages::VERIFY);
4639 assert!(
4640 store
4641 .add_public_pem_with_usages("ec-encrypt".into(), ec, KeyUsages::ENCRYPT, &resources)
4642 .is_err()
4643 );
4644 let cert_der = pem::parse(cert)
4645 .expect("EC certificate PEM decodes")
4646 .into_contents();
4647 store
4648 .add_public_der("ec-cert".into(), cert_der.clone(), &resources)
4649 .expect("EC certificate imports");
4650 assert_eq!(store.public_keys()[1].usages, KeyUsages::VERIFY);
4651 assert!(
4652 store
4653 .add_public_der_with_usages(
4654 "ec-cert-encrypt".into(),
4655 cert_der,
4656 KeyUsages::ENCRYPT,
4657 &resources
4658 )
4659 .is_err()
4660 );
4661 }
4662
4663 #[test]
4664 fn xml_store_rejects_policy_above_absolute_ceiling() {
4665 let resources = ResourcePolicy {
4667 max_xml_nodes: crate::hard_limits::XML_DOCUMENT_NODE_CEILING as usize + 1,
4668 ..ResourcePolicy::default()
4669 };
4670 let xml = format!("<Keys xmlns=\"{XMLSEC_NS}\"/>");
4671 assert!(
4672 KeyInventory::from_xml_bytes(
4673 xml.as_bytes(),
4674 &xml_policy(resources.clone()),
4675 XmlBackend::default(),
4676 )
4677 .is_err()
4678 );
4679 assert!(
4680 KeyInventory::default()
4681 .add_symmetric(
4682 "key".into(),
4683 SymmetricKeyKind::Hmac,
4684 b"secret".to_vec(),
4685 KeyUsages::SIGN,
4686 &resources,
4687 )
4688 .is_err()
4689 );
4690 }
4691
4692 #[test]
4693 fn ec_private_key_cannot_advertise_rsa_decryption() {
4694 let pem = include_bytes!("../tests/fixtures/keys/ec/ec-prime256v1-key.pem");
4696 assert!(
4697 KeyInventory::default()
4698 .add_private_pem(
4699 "ec".into(),
4700 pem,
4701 None,
4702 KeyUsages::DECRYPT,
4703 &ResourcePolicy::default(),
4704 )
4705 .is_err()
4706 );
4707 }
4708
4709 #[test]
4710 fn imports_rsa_pkcs1_pem_and_resolves_named_spki() {
4711 let private_pem = include_str!("../tests/fixtures/keys/rsa/rsa-2048-key.pem");
4714 let rsa = RsaPrivateKey::from_pkcs8_pem(private_pem).expect("RSA fixture parses");
4715 let pkcs1 = rsa.to_pkcs1_der().expect("RSA fixture encodes as PKCS#1");
4716 let public = rsa
4717 .to_public_key()
4718 .to_public_key_der()
4719 .expect("RSA fixture has SPKI");
4720 let resources = ResourcePolicy::default();
4721 let mut inventory = KeyInventory::default();
4722 inventory
4723 .add_private_der(
4724 "key".into(),
4725 pkcs1.as_bytes(),
4726 None,
4727 KeyUsages::SIGN.union(KeyUsages::DECRYPT),
4728 &resources,
4729 )
4730 .expect("PKCS#1 private key imports");
4731 inventory
4732 .add_public_der("verify-key".into(), public.as_bytes().to_vec(), &resources)
4733 .expect("public SPKI imports");
4734 assert!(inventory.private_keys[0].usages.allows(KeyUsage::Sign));
4735 let mut info = KeyInfo::default();
4736 info.sources
4737 .push(KeyInfoSource::KeyName("verify-key".into()));
4738 let resolver = inventory.verification_resolver();
4739 let resolved = resolver
4740 .resolve(Some(&info), SignatureAlgorithm::RsaSha256)
4741 .expect("named public key resolves");
4742 assert!(resolved.is_some());
4743 }
4744
4745 #[test]
4746 fn rejects_unknown_pem_text_and_duplicate_names() {
4747 let public = include_bytes!("../tests/fixtures/keys/rsa/rsa-2048-pubkey.pem");
4750 let mut inventory = KeyInventory::default();
4751 let resources = ResourcePolicy::default();
4752 inventory
4753 .add_public_pem("first".into(), public, &resources)
4754 .expect("public PEM imports");
4755 assert!(matches!(
4756 inventory.add_public_pem("first".into(), public, &resources),
4757 Err(KeyStoreError::Selection("duplicate key name"))
4758 ));
4759 let mut trailing = public.to_vec();
4760 trailing.extend_from_slice(b"\nnot a key");
4761 assert!(
4762 inventory
4763 .add_public_pem("other".into(), &trailing, &resources)
4764 .is_err()
4765 );
4766 }
4767
4768 #[test]
4769 fn public_pem_label_rejects_certificate_payload() {
4770 let certificate = pem::parse(include_bytes!(
4772 "../tests/fixtures/keys/rsa/rsa-2048-cert.pem"
4773 ))
4774 .expect("certificate")
4775 .into_contents();
4776 let mislabeled = pem::encode(&pem::Pem::new("PUBLIC KEY", certificate.clone()));
4777 let resources = ResourcePolicy::default();
4778 for usages in [None, Some(KeyUsages::VERIFY)] {
4779 let mut inventory = KeyInventory::default();
4780 assert!(matches!(
4781 inventory.add_public_pem_inner(
4782 "key".into(),
4783 mislabeled.as_bytes(),
4784 usages,
4785 &resources
4786 ),
4787 Err(KeyStoreError::Selection("invalid PUBLIC KEY payload"))
4788 ));
4789 assert!(inventory.public_keys().is_empty());
4790 }
4791 KeyInventory::default()
4792 .add_public_der("cert".into(), certificate, &resources)
4793 .expect("generic DER intentionally accepts certificates");
4794 KeyInventory::default()
4795 .add_public_pem(
4796 "spki".into(),
4797 include_bytes!("../tests/fixtures/keys/rsa/rsa-2048-pubkey.pem"),
4798 &resources,
4799 )
4800 .expect("correct SPKI label");
4801 }
4802
4803 #[test]
4804 fn rsa_spki_import_rejects_even_public_exponent() {
4805 let mut der = single_pem_block(
4807 include_bytes!("../tests/fixtures/keys/rsa/rsa-2048-pubkey.pem"),
4808 ResourcePolicy::default().max_external_resource_bytes,
4809 )
4810 .expect("public key fixture")
4811 .into_contents();
4812 let exponent = der
4813 .windows(5)
4814 .position(|bytes| bytes == [0x02, 0x03, 0x01, 0x00, 0x01])
4815 .expect("RSA exponent in SPKI");
4816 der[exponent + 4] = 0;
4817 assert!(
4818 KeyInventory::default()
4819 .add_public_der("invalid".into(), der, &ResourcePolicy::default())
4820 .is_err()
4821 );
4822 }
4823
4824 #[test]
4825 fn rsa_public_pkcs1_pem_is_bounded_before_bigint_decode() {
4826 let modulus = vec![1_u8; crate::hard_limits::RSA_MODULUS_BIT_CEILING.div_ceil(8) + 1];
4828 let exponent = [1_u8, 0, 1];
4829 let public = pkcs1::RsaPublicKeyRef {
4830 modulus: der::asn1::UintRef::new(&modulus).expect("valid modulus"),
4831 public_exponent: der::asn1::UintRef::new(&exponent).expect("valid exponent"),
4832 };
4833 let pem = pem::encode(&pem::Pem::new(
4834 "RSA PUBLIC KEY",
4835 der::Encode::to_der(&public).expect("encodable RSA public key"),
4836 ));
4837 assert!(matches!(
4838 KeyInventory::default().add_public_pem(
4839 "oversized".into(),
4840 pem.as_bytes(),
4841 &ResourcePolicy::default(),
4842 ),
4843 Err(KeyStoreError::Selection(
4844 "RSA public key exceeds safety limit"
4845 ))
4846 ));
4847 }
4848
4849 #[test]
4850 fn direct_inventory_names_consume_resource_budget() {
4851 let resources = ResourcePolicy {
4853 max_external_resource_bytes: 64,
4854 max_external_resource_total_bytes: 100,
4855 ..ResourcePolicy::default()
4856 };
4857 let mut inventory = KeyInventory::default();
4858 assert!(
4859 inventory
4860 .add_symmetric(
4861 "x".repeat(65),
4862 SymmetricKeyKind::Hmac,
4863 vec![7; 32],
4864 KeyUsages::SIGN,
4865 &resources,
4866 )
4867 .is_err()
4868 );
4869 inventory
4870 .add_symmetric(
4871 "a".repeat(40),
4872 SymmetricKeyKind::Hmac,
4873 vec![7; 32],
4874 KeyUsages::SIGN,
4875 &resources,
4876 )
4877 .expect("first named key fits");
4878 assert!(
4879 inventory
4880 .add_symmetric(
4881 "b".repeat(40),
4882 SymmetricKeyKind::Hmac,
4883 vec![8; 32],
4884 KeyUsages::SIGN,
4885 &resources,
4886 )
4887 .is_err()
4888 );
4889 }
4890
4891 #[test]
4892 fn private_pem_label_cannot_enable_der_fallback() {
4893 let plain = pem::parse(include_bytes!(
4895 "../tests/fixtures/keys/rsa/rsa-2048-key.pem"
4896 ))
4897 .expect("fixture")
4898 .into_contents();
4899 let mislabeled = pem::encode(&pem::Pem::new("ENCRYPTED PRIVATE KEY", plain));
4900 for password in [None, Some(b"ignored".as_slice())] {
4901 let mut inventory = KeyInventory::default();
4902 assert!(matches!(
4903 inventory.add_private_pem(
4904 "key".into(),
4905 mislabeled.as_bytes(),
4906 password,
4907 KeyUsages::SIGN,
4908 &ResourcePolicy::default()
4909 ),
4910 Err(KeyStoreError::ProtectedContainer)
4911 ));
4912 assert!(inventory.private_keys.is_empty());
4913 assert_eq!(inventory.material_bytes, 0);
4914 }
4915 }
4916
4917 #[test]
4918 fn encrypted_pkcs8_callback_checks_remaining_work() {
4919 use der::Encode as _;
4920 use pkcs8::pkcs5::{EncryptionScheme, pbes2};
4921 let envelope = EncryptedPrivateKeyInfoRef {
4924 encryption_algorithm: EncryptionScheme::Pbes2(pbes2::Parameters {
4925 kdf: pbes2::Kdf::Pbkdf2(pbes2::Pbkdf2Params {
4926 salt: pbes2::Salt::new(b"12345678").expect("salt"),
4927 iteration_count: 2,
4928 key_length: None,
4929 prf: pbes2::Pbkdf2Prf::HmacWithSha256,
4930 }),
4931 encryption: pbes2::EncryptionScheme::Aes256Cbc { iv: [0; 16] },
4932 }),
4933 encrypted_data: der::asn1::OctetStringRef::new(&[0; 64]).expect("ciphertext"),
4934 }
4935 .to_der()
4936 .expect("envelope");
4937 let resources = ResourcePolicy {
4938 max_key_import_kdf_work: 4,
4939 ..ResourcePolicy::default()
4940 };
4941 let mut inventory = KeyInventory::default();
4942 assert!(matches!(
4943 inventory.add_private_der(
4944 "first".into(),
4945 &envelope,
4946 Some(b"wrong"),
4947 KeyUsages::SIGN,
4948 &resources,
4949 ),
4950 Err(KeyStoreError::ProtectedContainer)
4951 ));
4952 assert_eq!(inventory.key_import_kdf_work(), 3);
4953 let calls = std::cell::Cell::new(0);
4954 let result = inventory.add_private_der_with_password_callback(
4955 "second".into(),
4956 &envelope,
4957 || {
4958 calls.set(calls.get() + 1);
4959 Some(Zeroizing::new(b"wrong".to_vec()))
4960 },
4961 KeyUsages::SIGN,
4962 &resources,
4963 );
4964 assert_eq!(calls.get(), 0, "preflight precedes secret acquisition");
4965 assert!(matches!(result, Err(KeyStoreError::Policy(_))));
4966 assert_eq!(inventory.key_import_kdf_work(), 3);
4967 let exact = ResourcePolicy {
4969 max_key_import_kdf_work: 6,
4970 ..resources
4971 };
4972 assert!(matches!(
4973 inventory.add_private_der_with_password_callback(
4974 "third".into(),
4975 &envelope,
4976 || {
4977 calls.set(calls.get() + 1);
4978 Some(Zeroizing::new(b"wrong".to_vec()))
4979 },
4980 KeyUsages::SIGN,
4981 &exact,
4982 ),
4983 Err(KeyStoreError::ProtectedContainer)
4984 ));
4985 assert_eq!(calls.get(), 1);
4986 assert_eq!(inventory.key_import_kdf_work(), 6);
4987 }
4988
4989 #[test]
4990 fn encrypted_pkcs8_requires_correct_password_without_plaintext_fallback() {
4991 let private_pem = include_str!("../tests/fixtures/keys/rsa/rsa-2048-key.pem");
4994 let rsa = RsaPrivateKey::from_pkcs8_pem(private_pem).expect("RSA fixture parses");
4995 let plain = rsa.to_pkcs8_der().expect("RSA fixture encodes as PKCS#8");
4996 let mut rng = ChaCha8Rng::seed_from_u64(0xA11C_E501);
4997 let encrypted = PrivateKeyInfoRef::try_from(plain.as_bytes())
4998 .expect("PKCS#8 reference parses")
4999 .encrypt_with_rng(&mut rng, b"correct")
5000 .expect("PKCS#8 fixture encrypts");
5001 let resources = ResourcePolicy::default();
5002 let mut inventory = KeyInventory::default();
5003 let restricted = ResourcePolicy {
5004 max_key_import_kdf_work: 1,
5005 ..ResourcePolicy::default()
5006 };
5007 assert!(matches!(
5008 inventory.add_private_der(
5009 "restricted".into(),
5010 encrypted.as_bytes(),
5011 Some(b"correct"),
5012 KeyUsages::SIGN,
5013 &restricted,
5014 ),
5015 Err(KeyStoreError::Policy(
5016 crate::policy::PolicyViolation::ResourceLimit {
5017 resource: crate::policy::resource_name::KEY_IMPORT_KDF_WORK,
5018 maximum: 1,
5019 ..
5020 }
5021 ))
5022 ));
5023 let callback_calls = std::cell::Cell::new(0);
5024 assert!(matches!(
5025 inventory.add_private_der_with_password_callback(
5026 "restricted-callback".into(),
5027 encrypted.as_bytes(),
5028 || {
5029 callback_calls.set(callback_calls.get() + 1);
5030 Some(Zeroizing::new(b"correct".to_vec()))
5031 },
5032 KeyUsages::SIGN,
5033 &restricted,
5034 ),
5035 Err(KeyStoreError::Policy(
5036 crate::policy::PolicyViolation::ResourceLimit {
5037 resource: crate::policy::resource_name::KEY_IMPORT_KDF_WORK,
5038 maximum: 1,
5039 ..
5040 }
5041 ))
5042 ));
5043 assert_eq!(callback_calls.get(), 0);
5044 assert!(matches!(
5045 inventory.add_private_der(
5046 "protected".into(),
5047 encrypted.as_bytes(),
5048 Some(b"wrong"),
5049 KeyUsages::SIGN,
5050 &resources,
5051 ),
5052 Err(KeyStoreError::ProtectedContainer)
5053 ));
5054 assert!(inventory.private_keys.is_empty());
5055 inventory
5056 .add_private_der(
5057 "protected".into(),
5058 encrypted.as_bytes(),
5059 Some(b"correct"),
5060 KeyUsages::SIGN,
5061 &resources,
5062 )
5063 .expect("correct password imports encrypted PKCS#8");
5064 assert_eq!(inventory.private_keys.len(), 1);
5065 assert!(inventory.material_bytes >= encrypted.as_bytes().len());
5066 let mut callback_inventory = KeyInventory::default();
5067 callback_inventory
5068 .add_private_der_with_password_callback(
5069 "callback".into(),
5070 encrypted.as_bytes(),
5071 || Some(Zeroizing::new(b"correct".to_vec())),
5072 KeyUsages::SIGN,
5073 &resources,
5074 )
5075 .expect("callback decrypts encrypted PKCS#8");
5076 callback_inventory
5077 .add_private_der_with_password_callback(
5078 "plain".into(),
5079 plain.as_bytes(),
5080 || panic!("plaintext PKCS#8 must not request a password"),
5081 KeyUsages::SIGN,
5082 &resources,
5083 )
5084 .expect("plaintext import ignores password callback");
5085 }
5086
5087 #[test]
5088 fn invalid_private_usages_do_not_request_password() {
5089 let private = pem::parse(include_bytes!(
5091 "../tests/fixtures/keys/rsa/rsa-2048-key.pem"
5092 ))
5093 .expect("key")
5094 .into_contents();
5095 let encrypted = PrivateKeyInfoRef::try_from(private.as_slice())
5096 .expect("PKCS8")
5097 .encrypt_with_rng(&mut ChaCha8Rng::seed_from_u64(42), b"correct")
5098 .expect("encrypted PKCS8");
5099 for usages in [
5100 KeyUsages::VERIFY,
5101 KeyUsages(0),
5102 KeyUsages::SIGN.union(KeyUsages::VERIFY),
5103 ] {
5104 let calls = std::cell::Cell::new(0);
5105 let mut inventory = KeyInventory::default();
5106 assert!(matches!(
5107 inventory.add_private_der_with_password_callback(
5108 "invalid".into(),
5109 encrypted.as_bytes(),
5110 || {
5111 calls.set(calls.get() + 1);
5112 Some(Zeroizing::new(b"correct".to_vec()))
5113 },
5114 usages,
5115 &ResourcePolicy::default()
5116 ),
5117 Err(KeyStoreError::Selection(
5118 "private key usage is incompatible"
5119 ))
5120 ));
5121 assert_eq!(calls.get(), 0);
5122 assert_eq!(inventory.entry_count(), 0);
5123 }
5124 }
5125
5126 #[test]
5127 fn certificate_and_crl_capacity_precedes_der_parsing() {
5128 for candidates in [true, false] {
5131 for certificate in [true, false] {
5132 let mut inventory = KeyInventory::default();
5133 let resources = ResourcePolicy {
5134 max_key_candidates: if candidates { 1 } else { 64 },
5135 max_external_resource_total_bytes: 2,
5136 ..ResourcePolicy::default()
5137 };
5138 inventory
5139 .add_symmetric(
5140 "a".into(),
5141 SymmetricKeyKind::Hmac,
5142 vec![0],
5143 KeyUsages::SIGN,
5144 &resources,
5145 )
5146 .expect("fill inventory");
5147 let result = if certificate {
5148 inventory.add_certificate_der(vec![0], false, &resources)
5149 } else {
5150 inventory.add_crl_der(vec![0], &resources)
5151 };
5152 let resource = if candidates {
5153 crate::policy::resource_name::KEY_CANDIDATES
5154 } else {
5155 crate::policy::resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES
5156 };
5157 assert!(matches!(result, Err(KeyStoreError::Policy(
5158 crate::policy::PolicyViolation::ResourceLimitExceeded { resource: actual, .. }
5159 )) if actual == resource));
5160 assert_eq!(inventory.entry_count(), 1);
5161 assert_eq!(inventory.material_bytes, 2);
5162 assert!(inventory.lookup_certificates.is_empty());
5163 assert!(inventory.crls.is_empty());
5164 }
5165 }
5166 assert!(matches!(
5167 KeyInventory::default().add_crl_der(vec![0], &ResourcePolicy::default()),
5168 Err(KeyStoreError::Selection("invalid X.509 CRL"))
5169 ));
5170 }
5171
5172 #[test]
5173 fn pkcs8_pbkdf2_output_blocks_are_checked_before_password() {
5174 use der::Encode as _;
5175 use pkcs8::pkcs5::{EncryptionScheme, pbes2};
5178 let encrypted = EncryptedPrivateKeyInfoRef {
5179 encryption_algorithm: EncryptionScheme::Pbes2(pbes2::Parameters {
5180 kdf: pbes2::Kdf::Pbkdf2(pbes2::Pbkdf2Params {
5181 salt: pbes2::Salt::new(b"12345678").expect("salt"),
5182 iteration_count: 3,
5183 key_length: None,
5184 prf: pbes2::Pbkdf2Prf::HmacWithSha1,
5185 }),
5186 encryption: pbes2::EncryptionScheme::Aes256Cbc { iv: [0; 16] },
5187 }),
5188 encrypted_data: der::asn1::OctetStringRef::new(&[0; 16]).expect("ciphertext"),
5189 };
5190 let bytes = encrypted.to_der().expect("encrypted envelope");
5191 let resources = ResourcePolicy {
5192 max_key_import_kdf_work: 5,
5193 ..ResourcePolicy::default()
5194 };
5195 let calls = std::cell::Cell::new(0);
5196 let result = KeyInventory::default().add_private_der_with_password_callback(
5197 "key".into(),
5198 &bytes,
5199 || {
5200 calls.set(calls.get() + 1);
5201 None
5202 },
5203 KeyUsages::SIGN,
5204 &resources,
5205 );
5206 assert_eq!(calls.get(), 0);
5207 assert!(matches!(
5208 result,
5209 Err(KeyStoreError::Policy(
5210 crate::policy::PolicyViolation::ResourceLimit {
5211 resource: crate::policy::resource_name::KEY_IMPORT_KDF_WORK,
5212 maximum: 5,
5213 actual: 6,
5214 }
5215 ))
5216 ));
5217 let exact = ResourcePolicy {
5218 max_key_import_kdf_work: 6,
5219 ..resources
5220 };
5221 assert!(enforce_pkcs8_kdf_policy(&encrypted, &exact, 0).is_ok());
5222 for (prf, hash_len) in [
5225 (pbes2::Pbkdf2Prf::HmacWithSha1, 20),
5226 (pbes2::Pbkdf2Prf::HmacWithSha224, 28),
5227 (pbes2::Pbkdf2Prf::HmacWithSha256, 32),
5228 (pbes2::Pbkdf2Prf::HmacWithSha384, 48),
5229 (pbes2::Pbkdf2Prf::HmacWithSha512, 64),
5230 ] {
5231 for cipher in [
5232 pbes2::EncryptionScheme::Aes128Cbc { iv: [0; 16] },
5233 pbes2::EncryptionScheme::Aes192Cbc { iv: [0; 16] },
5234 pbes2::EncryptionScheme::Aes256Cbc { iv: [0; 16] },
5235 ] {
5236 let mut envelope = encrypted.clone();
5237 let EncryptionScheme::Pbes2(params) = &mut envelope.encryption_algorithm else {
5238 panic!("PBES2 fixture");
5239 };
5240 let pbes2::Kdf::Pbkdf2(kdf) = &mut params.kdf else {
5241 panic!("PBKDF2 fixture");
5242 };
5243 kdf.prf = prf;
5244 params.encryption = cipher;
5245 let work = 3 * cipher.key_size().div_ceil(hash_len);
5246 let exact = ResourcePolicy {
5247 max_key_import_kdf_work: work,
5248 ..ResourcePolicy::default()
5249 };
5250 assert!(enforce_pkcs8_kdf_policy(&envelope, &exact, 0).is_ok());
5251 let tight = ResourcePolicy {
5252 max_key_import_kdf_work: work - 1,
5253 ..exact
5254 };
5255 assert!(matches!(enforce_pkcs8_kdf_policy(&envelope, &tight, 0),
5256 Err(KeyStoreError::Policy(crate::policy::PolicyViolation::ResourceLimit {
5257 resource: crate::policy::resource_name::KEY_IMPORT_KDF_WORK,
5258 actual, ..
5259 })) if actual == work));
5260 }
5261 }
5262 }
5263
5264 #[test]
5265 fn protected_password_consumes_live_byte_and_work_budgets() {
5266 use der::Encode as _;
5267 use pkcs8::pkcs5::{EncryptionScheme, pbes2};
5268 let envelope = EncryptedPrivateKeyInfoRef {
5271 encryption_algorithm: EncryptionScheme::Pbes2(pbes2::Parameters {
5272 kdf: pbes2::Kdf::Pbkdf2(pbes2::Pbkdf2Params {
5273 salt: pbes2::Salt::new(b"12345678").expect("salt"),
5274 iteration_count: 2,
5275 key_length: None,
5276 prf: pbes2::Pbkdf2Prf::HmacWithSha256,
5277 }),
5278 encryption: pbes2::EncryptionScheme::Aes256Cbc { iv: [0; 16] },
5279 }),
5280 encrypted_data: der::asn1::OctetStringRef::new(&[0; 64]).expect("ciphertext"),
5281 }
5282 .to_der()
5283 .expect("envelope");
5284 let peak = 3 + envelope.len() + 64;
5285 for resources in [
5286 ResourcePolicy {
5287 max_external_resource_bytes: 255,
5288 ..ResourcePolicy::default()
5289 },
5290 ResourcePolicy {
5291 max_external_resource_total_bytes: peak + 255,
5292 ..ResourcePolicy::default()
5293 },
5294 ResourcePolicy {
5295 max_key_import_kdf_work: 5,
5296 ..ResourcePolicy::default()
5297 },
5298 ] {
5299 let mut inventory = KeyInventory::default();
5300 assert!(matches!(
5301 inventory.add_private_der(
5302 "new".into(),
5303 &envelope,
5304 Some(&[0; 256]),
5305 KeyUsages::SIGN,
5306 &resources
5307 ),
5308 Err(KeyStoreError::Policy(_))
5309 ));
5310 assert!(inventory.private_keys().is_empty());
5311 }
5312 let resources = ResourcePolicy {
5313 max_external_resource_total_bytes: peak + 255,
5314 ..ResourcePolicy::default()
5315 };
5316 let mut secret = Vec::with_capacity(256);
5317 secret.push(0);
5318 assert!(matches!(
5319 KeyInventory::default().add_private_der_with_password_callback(
5320 "new".into(),
5321 &envelope,
5322 || Some(Zeroizing::new(secret)),
5323 KeyUsages::SIGN,
5324 &resources
5325 ),
5326 Err(KeyStoreError::Policy(_))
5327 ));
5328 let exact = ResourcePolicy {
5329 max_external_resource_total_bytes: peak + 256,
5330 max_key_import_kdf_work: 6,
5331 ..ResourcePolicy::default()
5332 };
5333 assert!(matches!(
5334 KeyInventory::default().add_private_der(
5335 "new".into(),
5336 &envelope,
5337 Some(&[0; 256]),
5338 KeyUsages::SIGN,
5339 &exact
5340 ),
5341 Err(KeyStoreError::ProtectedContainer)
5342 ));
5343 }
5344
5345 #[test]
5346 fn private_identity_matches_without_owned_public_encoding() {
5347 let rsa_der = pem::parse(include_bytes!(
5350 "../tests/fixtures/keys/rsa/rsa-2048-key.pem"
5351 ))
5352 .expect("RSA PEM fixture")
5353 .into_contents();
5354 let rsa = RsaPrivateKey::from_pkcs8_der(&rsa_der).expect("RSA private key");
5355 let public = rsa
5356 .to_public_key()
5357 .to_public_key_der()
5358 .expect("RSA public encoding");
5359 let identity = PrivateKeyIdentity::decode(&rsa_der).expect("RSA identity");
5360 assert!(identity.matches_spki(public.as_bytes()));
5361 let mut changed = public.as_bytes().to_vec();
5362 *changed.last_mut().expect("nonempty RSA SPKI") ^= 2;
5363 assert!(!identity.matches_spki(&changed));
5364 assert!(!identity.matches_spki(b"invalid DER"));
5365
5366 macro_rules! check_ec {
5367 ($key:ty, $length:expr) => {{
5368 let mut scalar = [0; $length];
5369 scalar[$length - 1] = 1;
5370 let key = <$key>::from_slice(&scalar).expect("valid EC scalar");
5371 let private = key.to_pkcs8_der().expect("EC private encoding");
5372 let public = key
5373 .public_key()
5374 .to_public_key_der()
5375 .expect("EC public encoding");
5376 let identity = PrivateKeyIdentity::decode(private.as_bytes()).expect("EC identity");
5377 assert!(identity.matches_spki(public.as_bytes()));
5378 let mut changed = public.as_bytes().to_vec();
5379 *changed.last_mut().expect("nonempty EC SPKI") ^= 1;
5380 assert!(!identity.matches_spki(&changed));
5381 }};
5382 }
5383 check_ec!(p256::SecretKey, 32);
5384 check_ec!(p384::SecretKey, 48);
5385 check_ec!(p521::SecretKey, 66);
5386
5387 let inventory = KeyInventory::from_xml_bytes(
5388 include_bytes!("../tests/fixtures/keys/xmlsec/mixed-keys.xml"),
5389 &xml_policy(ResourcePolicy::default()),
5390 XmlBackend::default(),
5391 )
5392 .expect("mixed store fixture");
5393 let private = inventory
5394 .private_keys()
5395 .iter()
5396 .find(|entry| entry.name == "test-dsa")
5397 .expect("stored DSA private key");
5398 let key = NativeDsaSigningKey::from_pkcs8_der(&private.pkcs8_der).expect("DSA private key");
5399 let public = key
5400 .verifying_key()
5401 .to_public_key_der()
5402 .expect("DSA public encoding");
5403 let identity = PrivateKeyIdentity::decode(&private.pkcs8_der).expect("DSA identity");
5404 assert!(identity.matches_spki(public.as_bytes()));
5405 let mut changed = public.as_bytes().to_vec();
5406 *changed.last_mut().expect("nonempty DSA SPKI") ^= 1;
5407 assert!(!identity.matches_spki(&changed));
5408 }
5409
5410 #[test]
5411 fn xml_import_counts_source_and_decoded_material_together() {
5412 let xml = format!(
5414 "<Keys xmlns='{XMLSEC_NS}' xmlns:ds='{XMLDSIG_NS}'><ds:KeyInfo><ds:KeyName>hmac</ds:KeyName><ds:KeyValue><HMACKeyValue>{}</HMACKeyValue></ds:KeyValue></ds:KeyInfo></Keys>",
5415 base64::engine::general_purpose::STANDARD.encode(vec![1; 1024])
5416 );
5417 let resources = ResourcePolicy {
5418 max_external_resource_total_bytes: xml.len(),
5419 ..Default::default()
5420 };
5421 assert!(matches!(
5422 KeyInventory::from_xml_bytes(
5423 xml.as_bytes(),
5424 &xml_policy(resources),
5425 XmlBackend::default()
5426 ),
5427 Err(KeyStoreError::Policy(_))
5428 ));
5429 let peak = xml.len() + 1024 + 2 * "hmac".len();
5431 for maximum in [peak - 1, peak] {
5432 let policy = xml_policy(ResourcePolicy {
5433 max_external_resource_total_bytes: maximum,
5434 ..Default::default()
5435 });
5436 let result =
5437 KeyInventory::from_xml_bytes(xml.as_bytes(), &policy, XmlBackend::default());
5438 if maximum == peak {
5439 let store = result.expect("exact live peak fits");
5440 assert_eq!(store.symmetric_keys[0].bytes.as_slice(), &[1; 1024]);
5441 assert_eq!(store.material_bytes, xml.len() + 1024 + "hmac".len());
5442 } else {
5443 assert!(matches!(result, Err(KeyStoreError::Policy(_))));
5444 }
5445 }
5446 }
5447
5448 #[test]
5449 fn private_pem_preflight_precedes_payload_parsing() {
5450 for label in ["PRIVATE KEY", "ENCRYPTED PRIVATE KEY", "RSA PRIVATE KEY"] {
5453 let pem = pem::encode(&pem::Pem::new(label, vec![1, 2, 3]));
5454 let resources = ResourcePolicy {
5455 max_external_resource_total_bytes: pem.len() + "key".len() + 2,
5456 ..Default::default()
5457 };
5458 let mut store = KeyInventory::default();
5459 assert!(matches!(
5460 store.add_private_pem(
5461 "key".into(),
5462 pem.as_bytes(),
5463 None,
5464 KeyUsages::SIGN,
5465 &resources
5466 ),
5467 Err(KeyStoreError::Policy(_))
5468 ));
5469 assert_eq!(store.entry_count(), 0);
5470 }
5471 }
5472
5473 #[test]
5474 fn xml_public_key_import_preserves_key_info_metadata_validation() {
5475 let source = include_str!("../tests/fixtures/keys/xmlsec/mixed-keys.xml");
5478 let marker = source.find("<RSAKeyValue").expect("RSA entry");
5479 let start = source[..marker].rfind("<KeyInfo").expect("RSA KeyInfo");
5480 let end =
5481 marker + source[marker..].find("</KeyInfo>").expect("RSA end") + "</KeyInfo>".len();
5482 let entry = &source[start..end];
5483 let name_start = entry.find("<KeyName>").expect("name") + "<KeyName>".len();
5484 let name_end = name_start + entry[name_start..].find("</KeyName>").expect("name end");
5485 for changed in [
5486 format!(
5487 "{}{}{}",
5488 &entry[..name_start],
5489 "a".repeat(4097),
5490 &entry[name_end..]
5491 ),
5492 entry.replacen("<KeyName>", "unexpected<KeyName>", 1),
5493 ] {
5494 let xml = format!("<Keys xmlns='{XMLSEC_NS}'>{changed}</Keys>");
5495 assert!(
5496 KeyInventory::from_xml_bytes(
5497 xml.as_bytes(),
5498 &xml_policy(ResourcePolicy::default()),
5499 XmlBackend::default()
5500 )
5501 .is_err()
5502 );
5503 }
5504 }
5505
5506 #[test]
5507 fn public_pem_decoder_preserves_framing_and_whitespace_contract() {
5508 for data in [
5511 "AQID",
5512 "A Q\tI\r\nD",
5513 "A\u{2003}QID",
5514 "Header: value\n\nAQID",
5515 "Header: value\r\n\r\nAQID",
5516 ] {
5517 let text = format!("-----BEGIN PUBLIC KEY-----\n{data}\n-----END PUBLIC KEY-----");
5518 let expected = single_pem_block(text.as_bytes(), 4096).expect("existing PEM contract");
5519 let frame = BorrowedPem::parse(text.as_bytes(), 4096).expect("borrowed frame");
5520 assert_eq!(
5521 frame.decode().expect("borrowed decode"),
5522 expected.contents()
5523 );
5524 }
5525 for data in ["AQI=", "AQ==", "AQID"] {
5526 let text =
5527 format!("-----BEGIN RSA PUBLIC KEY-----\n{data}\n-----END RSA PUBLIC KEY-----");
5528 let frame = BorrowedPem::parse(text.as_bytes(), 4096).expect("borrowed frame");
5529 assert_eq!(frame.decode().expect("decode").len(), frame.decoded_len);
5530 }
5531 for length in [0, 1, 2, 3, 1023, 1024, 1025, 4096] {
5534 let bytes = vec![0xa5; length];
5535 let encoded = pem::encode(&pem::Pem::new("PUBLIC KEY", bytes.clone()));
5536 let frame = BorrowedPem::parse(encoded.as_bytes(), 8192).expect("frame");
5537 assert_eq!(frame.decoded_len, length);
5538 assert_eq!(frame.decode().expect("streamed decoding"), bytes);
5539 }
5540 for text in [
5541 "-----BEGIN PUBLIC KEY-----\nAQID\n-----END RSA PUBLIC KEY-----",
5542 "-----BEGIN PUBLIC KEY-----\nAQID\n-----END PUBLIC KEY-----junk",
5543 "-----BEGIN PUBLIC KEY-----\nAQID\n-----END PUBLIC KEY-----\n-----BEGIN PUBLIC KEY-----\nAQID\n-----END PUBLIC KEY-----",
5544 "-----BEGIN PUBLIC KEY-----\nAQI\n-----END PUBLIC KEY-----",
5545 "-----BEGIN PUBLIC KEY-----\nAQ$=\n-----END PUBLIC KEY-----",
5546 "-----BEGIN PUBLIC KEY-----\nAR==\n-----END PUBLIC KEY-----",
5547 "-----BEGIN PUBLIC KEY-----\nA===\n-----END PUBLIC KEY-----",
5548 "-----BEGIN PUBLIC KEY-----\nAQ=I\n-----END PUBLIC KEY-----",
5549 "-----BEGIN PUBLIC KEY-----\nnot a header\n\nAQID\n-----END PUBLIC KEY-----",
5550 ] {
5551 assert!(
5552 BorrowedPem::parse(text.as_bytes(), 4096)
5553 .and_then(|frame| frame.decode())
5554 .is_err(),
5555 "accepted {text}"
5556 );
5557 }
5558 }
5559
5560 #[test]
5561 fn public_pem_import_accounts_for_simultaneous_buffers() {
5562 let pem = include_bytes!("../tests/fixtures/keys/rsa/rsa-2048-pubkey.pem");
5565 let spki = pem::parse(pem).expect("public PEM fixture").into_contents();
5566 let key = RsaPublicKey::from_public_key_der(&spki).expect("RSA key");
5567 let pkcs1 = key.to_pkcs1_der().expect("PKCS1 encoding");
5568 for (label, decoded, extra) in [
5569 ("PUBLIC KEY", spki.as_slice(), 0),
5570 ("RSA PUBLIC KEY", pkcs1.as_bytes(), spki.len()),
5571 ] {
5572 let encoded = pem::encode(&pem::Pem::new(label, decoded.to_vec()));
5573 for exact in [false, true] {
5574 let mut inventory = KeyInventory::default();
5575 inventory
5576 .add_symmetric(
5577 "existing".into(),
5578 SymmetricKeyKind::Hmac,
5579 vec![1; 16],
5580 KeyUsages::SIGN,
5581 &ResourcePolicy::default(),
5582 )
5583 .expect("existing key");
5584 let retained = inventory.material_bytes;
5585 let resources = ResourcePolicy {
5586 max_external_resource_total_bytes: retained
5587 + 2 * "new".len()
5588 + encoded.len()
5589 + decoded.len()
5590 + extra
5591 - usize::from(!exact),
5592 ..ResourcePolicy::default()
5593 };
5594 let result = inventory.add_public_pem("new".into(), encoded.as_bytes(), &resources);
5595 if exact {
5596 result.expect("exact peak allowance");
5597 assert_eq!(inventory.entry_count(), 2);
5598 } else {
5599 assert!(matches!(result, Err(KeyStoreError::Policy(_))));
5600 assert_eq!(inventory.entry_count(), 1);
5601 assert_eq!(inventory.material_bytes, retained);
5602 }
5603 }
5604 }
5605 }
5606
5607 #[test]
5608 fn plaintext_private_import_checks_all_live_copies() {
5609 let der = pem::parse(include_bytes!(
5612 "../tests/fixtures/keys/rsa/rsa-2048-key.pem"
5613 ))
5614 .expect("private key")
5615 .into_contents();
5616 let pkcs1 = RsaPrivateKey::from_pkcs8_der(&der)
5617 .expect("RSA")
5618 .to_pkcs1_der()
5619 .expect("PKCS1");
5620 for (label, input) in [
5621 ("PRIVATE KEY", der.as_slice()),
5622 ("RSA PRIVATE KEY", pkcs1.as_bytes()),
5623 ] {
5624 let pem = pem::encode(&pem::Pem::new(label, input.to_vec()));
5625 for encoded in [false, true] {
5626 let peak = 3 + input.len() + der.len() + if encoded { pem.len() } else { 0 };
5627 for exact in [false, true] {
5628 let resources = ResourcePolicy {
5629 max_external_resource_total_bytes: peak - usize::from(!exact),
5630 ..ResourcePolicy::default()
5631 };
5632 let mut inventory = KeyInventory::default();
5633 let result = if encoded {
5634 inventory.add_private_pem(
5635 "new".into(),
5636 pem.as_bytes(),
5637 None,
5638 KeyUsages::SIGN,
5639 &resources,
5640 )
5641 } else {
5642 inventory.add_private_der(
5643 "new".into(),
5644 input,
5645 None,
5646 KeyUsages::SIGN,
5647 &resources,
5648 )
5649 };
5650 if exact {
5651 result.expect("exact live-byte allowance");
5652 assert_eq!(inventory.entry_count(), 1);
5653 } else {
5654 assert!(matches!(result, Err(KeyStoreError::Policy(_))));
5655 assert_eq!(inventory.entry_count(), 0);
5656 assert_eq!(inventory.material_bytes, 0);
5657 }
5658 }
5659 }
5660 }
5661 }
5662
5663 #[test]
5664 fn encrypted_pkcs8_plaintext_is_reserved_before_password() {
5665 use der::Encode as _;
5666 use pkcs8::pkcs5::{EncryptionScheme, pbes2};
5667 let encrypted = EncryptedPrivateKeyInfoRef {
5670 encryption_algorithm: EncryptionScheme::Pbes2(pbes2::Parameters {
5671 kdf: pbes2::Kdf::Pbkdf2(pbes2::Pbkdf2Params {
5672 salt: pbes2::Salt::new(b"12345678").expect("salt"),
5673 iteration_count: 2,
5674 key_length: None,
5675 prf: pbes2::Pbkdf2Prf::HmacWithSha256,
5676 }),
5677 encryption: pbes2::EncryptionScheme::Aes256Cbc { iv: [0; 16] },
5678 }),
5679 encrypted_data: der::asn1::OctetStringRef::new(&[0; 64]).expect("ciphertext"),
5680 };
5681 let bytes = encrypted.to_der().expect("envelope");
5682 let peak = 3 + bytes.len() + 64;
5683 let tight = ResourcePolicy {
5684 max_external_resource_total_bytes: peak - 1,
5685 ..ResourcePolicy::default()
5686 };
5687 let calls = std::cell::Cell::new(0);
5688 let mut inventory = KeyInventory::default();
5689 let result = inventory.add_private_der_with_password_callback(
5690 "new".into(),
5691 &bytes,
5692 || {
5693 calls.set(calls.get() + 1);
5694 None
5695 },
5696 KeyUsages::SIGN,
5697 &tight,
5698 );
5699 assert_eq!(calls.get(), 0);
5700 assert!(
5701 matches!(result, Err(KeyStoreError::Policy(crate::policy::PolicyViolation::ResourceLimit {
5702 resource: crate::policy::resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES, actual, ..
5703 })) if actual == peak)
5704 );
5705 assert!(matches!(
5706 inventory.add_private_der(
5707 "new".into(),
5708 &bytes,
5709 Some(b"wrong"),
5710 KeyUsages::SIGN,
5711 &tight
5712 ),
5713 Err(KeyStoreError::Policy(_))
5714 ));
5715 let exact = ResourcePolicy {
5716 max_external_resource_total_bytes: peak,
5717 ..tight
5718 };
5719 assert!(matches!(
5720 inventory.add_private_der_with_password_callback(
5721 "new".into(),
5722 &bytes,
5723 || {
5724 calls.set(calls.get() + 1);
5725 None
5726 },
5727 KeyUsages::SIGN,
5728 &exact
5729 ),
5730 Err(KeyStoreError::ProtectedContainer)
5731 ));
5732 assert_eq!(calls.get(), 1);
5733 assert!(inventory.private_keys().is_empty());
5734 assert_eq!(inventory.material_bytes, 0);
5735 }
5736
5737 #[test]
5738 fn encrypted_pem_workspace_accounts_for_live_encoded_input() {
5739 use der::Encode as _;
5740 use pkcs8::pkcs5::{EncryptionScheme, pbes2};
5741 let encrypted = EncryptedPrivateKeyInfoRef {
5744 encryption_algorithm: EncryptionScheme::Pbes2(pbes2::Parameters {
5745 kdf: pbes2::Kdf::Scrypt(pbes2::ScryptParams {
5746 salt: pbes2::Salt::new(b"12345678").expect("salt"),
5747 cost_parameter: 16,
5748 block_size: 1,
5749 parallelization: 1,
5750 key_length: None,
5751 }),
5752 encryption: pbes2::EncryptionScheme::Aes256Cbc { iv: [0; 16] },
5753 }),
5754 encrypted_data: der::asn1::OctetStringRef::new(&[0; 16]).expect("ciphertext"),
5755 };
5756 let der = encrypted.to_der().expect("envelope");
5757 let pem = pem::encode(&pem::Pem::new("ENCRYPTED PRIVATE KEY", der.clone()));
5758 let peak = 3 + pem.len() + der.len() + 16 + 2304;
5759 let mut inventory = KeyInventory::default();
5760 let tight = ResourcePolicy {
5761 max_external_resource_total_bytes: peak - 1,
5762 ..ResourcePolicy::default()
5763 };
5764 assert!(matches!(
5765 inventory.add_private_pem("new".into(), pem.as_bytes(), None, KeyUsages::SIGN, &tight),
5766 Err(KeyStoreError::Policy(crate::policy::PolicyViolation::ResourceLimit {
5767 resource: crate::policy::resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES,
5768 actual, ..
5769 })) if actual == peak
5770 ));
5771 let exact = ResourcePolicy {
5772 max_external_resource_total_bytes: peak,
5773 ..tight
5774 };
5775 assert!(matches!(
5776 inventory.add_private_pem("new".into(), pem.as_bytes(), None, KeyUsages::SIGN, &exact),
5777 Err(KeyStoreError::ProtectedContainer)
5778 ));
5779 assert!(matches!(
5780 inventory.add_private_der("new".into(), &der, None, KeyUsages::SIGN, &tight),
5781 Err(KeyStoreError::ProtectedContainer)
5782 ));
5783 assert!(inventory.private_keys().is_empty());
5784 assert_eq!(inventory.material_bytes, 0);
5785 let plain = pem::parse(include_bytes!(
5788 "../tests/fixtures/keys/rsa/rsa-2048-key.pem"
5789 ))
5790 .expect("private key")
5791 .into_contents();
5792 let EncryptionScheme::Pbes2(params) = &encrypted.encryption_algorithm else {
5793 panic!("PBES2 envelope");
5794 };
5795 let ciphertext = params.encrypt(b"correct", &plain).expect("encrypt");
5796 let real = EncryptedPrivateKeyInfoRef {
5797 encryption_algorithm: encrypted.encryption_algorithm.clone(),
5798 encrypted_data: der::asn1::OctetStringRef::new(&ciphertext).expect("ciphertext"),
5799 }
5800 .to_der()
5801 .expect("envelope");
5802 let pem = pem::encode(&pem::Pem::new("ENCRYPTED PRIVATE KEY", real.clone()));
5803 let resources = ResourcePolicy {
5804 max_external_resource_total_bytes: 3
5805 + pem.len()
5806 + real.len()
5807 + ciphertext.len()
5808 + 2304
5809 + b"correct".len(),
5810 ..ResourcePolicy::default()
5811 };
5812 inventory
5813 .add_private_pem(
5814 "new".into(),
5815 pem.as_bytes(),
5816 Some(b"correct"),
5817 KeyUsages::SIGN,
5818 &resources,
5819 )
5820 .expect("exact PEM peak imports");
5821 assert_eq!(inventory.private_keys().len(), 1);
5822 }
5823
5824 #[test]
5825 fn pkcs8_scrypt_workspace_shares_retained_inventory_budget() {
5826 use der::Encode as _;
5827 use pkcs8::pkcs5::{EncryptionScheme, pbes2};
5830 let encrypted = EncryptedPrivateKeyInfoRef {
5831 encryption_algorithm: EncryptionScheme::Pbes2(pbes2::Parameters {
5832 kdf: pbes2::Kdf::Scrypt(pbes2::ScryptParams {
5833 salt: pbes2::Salt::new(b"12345678").expect("salt"),
5834 cost_parameter: 16,
5835 block_size: 1,
5836 parallelization: 1,
5837 key_length: None,
5838 }),
5839 encryption: pbes2::EncryptionScheme::Aes256Cbc { iv: [0; 16] },
5840 }),
5841 encrypted_data: der::asn1::OctetStringRef::new(&[0; 16]).expect("ciphertext"),
5842 };
5843 let bytes = encrypted.to_der().expect("envelope");
5844 let mut inventory = KeyInventory::default();
5845 inventory
5846 .add_symmetric(
5847 "old".into(),
5848 SymmetricKeyKind::Hmac,
5849 vec![7; 1024],
5850 KeyUsages::SIGN,
5851 &ResourcePolicy::default(),
5852 )
5853 .expect("retained key");
5854 let resources = ResourcePolicy {
5855 max_external_resource_total_bytes: inventory.material_bytes + 2304,
5856 max_key_import_kdf_memory_bytes: 4096,
5857 ..ResourcePolicy::default()
5858 };
5859 let calls = std::cell::Cell::new(0);
5860 let result = inventory.add_private_der_with_password_callback(
5861 "new".into(),
5862 &bytes,
5863 || {
5864 calls.set(calls.get() + 1);
5865 None
5866 },
5867 KeyUsages::SIGN,
5868 &resources,
5869 );
5870 assert_eq!(calls.get(), 0);
5871 assert!(matches!(
5872 result,
5873 Err(KeyStoreError::Policy(
5874 crate::policy::PolicyViolation::ResourceLimit {
5875 resource: crate::policy::resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES,
5876 ..
5877 }
5878 ))
5879 ));
5880 assert!(inventory.private_keys().is_empty());
5881 assert_eq!(inventory.symmetric_keys().len(), 1);
5882 let exact = ResourcePolicy {
5883 max_external_resource_total_bytes: inventory.material_bytes
5884 + bytes.len()
5885 + 3
5886 + 16
5887 + 2304,
5888 ..resources
5889 };
5890 assert!(matches!(
5891 inventory.add_private_der_with_password_callback(
5892 "new".into(),
5893 &bytes,
5894 || {
5895 calls.set(calls.get() + 1);
5896 None
5897 },
5898 KeyUsages::SIGN,
5899 &exact,
5900 ),
5901 Err(KeyStoreError::ProtectedContainer)
5902 ));
5903 assert_eq!(
5904 calls.get(),
5905 1,
5906 "an exactly fitting workspace reaches password delivery"
5907 );
5908 }
5909
5910 #[test]
5911 fn configured_x509_continuation_retains_first_deferred_error() {
5912 let mut inventory = KeyInventory::default();
5915 inventory
5916 .add_certificate_der(
5917 single_pem_block(
5918 include_bytes!("../tests/fixtures/keys/rsa/rsa-2048-cert.pem"),
5919 ResourcePolicy::default().max_external_resource_bytes,
5920 )
5921 .expect("certificate")
5922 .into_contents(),
5923 false,
5924 &ResourcePolicy::default(),
5925 )
5926 .expect("lookup");
5927 let mut info = KeyInfo {
5928 sources: vec![
5929 KeyInfoSource::KeyValue(KeyValueInfo::Dsa {
5930 p: None,
5931 q: None,
5932 g: None,
5933 y: vec![1],
5934 }),
5935 KeyInfoSource::X509Data(X509DataInfo {
5936 subject_names: vec!["CN=absent".into()],
5937 ..X509DataInfo::default()
5938 }),
5939 KeyInfoSource::KeyValue(KeyValueInfo::InvalidEcKeyValue),
5940 ],
5941 };
5942 let unsplit = DefaultKeyResolver::new(KeyResolverConfig::default())
5943 .resolve(Some(&info), SignatureAlgorithm::RsaSha256)
5944 .err()
5945 .expect("mismatch");
5946 let split = inventory
5947 .verification_resolver()
5948 .resolve(Some(&info), SignatureAlgorithm::RsaSha256)
5949 .err()
5950 .expect("mismatch");
5951 assert_eq!(format!("{split:?}"), format!("{unsplit:?}"));
5952 info.sources[2] = KeyInfoSource::DerEncodedKeyValue(vec![0]);
5953 let unsplit = DefaultKeyResolver::new(KeyResolverConfig::default())
5954 .resolve(Some(&info), SignatureAlgorithm::RsaSha256)
5955 .err()
5956 .expect("terminal");
5957 let split = inventory
5958 .verification_resolver()
5959 .resolve(Some(&info), SignatureAlgorithm::RsaSha256)
5960 .err()
5961 .expect("terminal");
5962 assert_eq!(format!("{split:?}"), format!("{unsplit:?}"));
5963 }
5964
5965 #[test]
5966 fn configured_x509_fallback_preserves_terminal_prefix_errors() {
5967 let resources = ResourcePolicy::default();
5970 let mut inventory = KeyInventory::default();
5971 let certificate = single_pem_block(
5972 include_bytes!("../tests/fixtures/keys/rsa/rsa-2048-cert.pem"),
5973 resources.max_external_resource_bytes,
5974 )
5975 .expect("certificate")
5976 .into_contents();
5977 let subject = crate::xmldsig::parse::parse_x509_certificate(&certificate)
5978 .expect("leaf")
5979 .subject_dn;
5980 inventory
5981 .add_certificate_der(certificate, false, &resources)
5982 .expect("lookup");
5983 let mut info = KeyInfo {
5984 sources: vec![
5985 KeyInfoSource::DerEncodedKeyValue(vec![0]),
5986 KeyInfoSource::X509Data(X509DataInfo {
5987 subject_names: vec![subject],
5988 ..X509DataInfo::default()
5989 }),
5990 ],
5991 };
5992 assert!(
5993 inventory
5994 .verification_resolver()
5995 .resolve(Some(&info), SignatureAlgorithm::RsaSha256,)
5996 .is_err(),
5997 "a terminal prefix error must stop resolution"
5998 );
5999 info.sources[0] = KeyInfoSource::KeyValue(KeyValueInfo::InvalidEcKeyValue);
6002 assert!(
6003 inventory
6004 .verification_resolver()
6005 .resolve(Some(&info), SignatureAlgorithm::RsaSha256,)
6006 .expect("deferred prefix permits certificate fallback")
6007 .is_some()
6008 );
6009 let KeyInfoSource::X509Data(data) = &mut info.sources[1] else {
6010 panic!("X509 selector");
6011 };
6012 data.subject_names = vec!["CN=absent".into()];
6013 assert!(
6014 inventory
6015 .verification_resolver()
6016 .resolve(Some(&info), SignatureAlgorithm::RsaSha256,)
6017 .is_err(),
6018 "a complete miss retains its deferred error"
6019 );
6020 }
6021
6022 #[test]
6023 fn configured_x509_fallback_does_not_reinspect_prefix() {
6024 let resources = ResourcePolicy::default();
6027 let mut inventory = KeyInventory::default();
6028 let certificate = single_pem_block(
6029 include_bytes!("../tests/fixtures/keys/rsa/rsa-2048-cert.pem"),
6030 resources.max_external_resource_bytes,
6031 )
6032 .expect("certificate")
6033 .into_contents();
6034 let subject = crate::xmldsig::parse::parse_x509_certificate(&certificate)
6035 .expect("leaf")
6036 .subject_dn;
6037 inventory
6038 .add_certificate_der(certificate, false, &resources)
6039 .expect("lookup");
6040 let info = KeyInfo {
6041 sources: vec![
6042 KeyInfoSource::KeyName("absent".into()),
6043 KeyInfoSource::X509Data(X509DataInfo {
6044 subject_names: vec![subject],
6045 ..X509DataInfo::default()
6046 }),
6047 ],
6048 };
6049 let mut policy = crate::policy::VerificationPolicy::default();
6050 policy.resources.max_key_candidates = 3;
6051 assert!(
6052 inventory
6053 .verification_resolver()
6054 .resolve_with_policy_and_provider(
6055 Some(&info),
6056 SignatureAlgorithm::RsaSha256,
6057 &policy,
6058 crate::provider::default_provider(),
6059 )
6060 .expect("each source inspected once")
6061 .is_some()
6062 );
6063 policy.resources.max_key_candidates = 2;
6064 assert!(matches!(
6065 inventory
6066 .verification_resolver()
6067 .resolve_with_policy_and_provider(
6068 Some(&info),
6069 SignatureAlgorithm::RsaSha256,
6070 &policy,
6071 crate::provider::default_provider(),
6072 ),
6073 Err(DsigError::Policy(_))
6074 ));
6075 }
6076
6077 #[test]
6078 fn scrypt_parallel_buffers_are_checked_before_derivation() {
6079 let mut resources = ResourcePolicy {
6081 max_key_import_kdf_work: 10_000,
6082 max_key_import_kdf_memory_bytes: 4_096,
6083 ..ResourcePolicy::default()
6084 };
6085 assert!(matches!(
6086 enforce_scrypt_kdf_limits(2, 1, 1_000, &resources, 0),
6087 Err(KeyStoreError::Policy(
6088 crate::policy::PolicyViolation::ResourceLimit {
6089 resource: crate::policy::resource_name::KEY_IMPORT_KDF_MEMORY,
6090 maximum: 4_096,
6091 actual: 512_000,
6092 }
6093 ))
6094 ));
6095 resources.max_key_import_kdf_memory_bytes = 512_000;
6096 assert!(enforce_scrypt_kdf_limits(2, 1, 1_000, &resources, 0).is_ok());
6097 }
6098
6099 #[test]
6100 fn named_hmac_resolution_enforces_usage_and_method() {
6101 let resources = ResourcePolicy::default();
6104 let mut inventory = KeyInventory::default();
6105 inventory
6106 .add_symmetric(
6107 "verify".into(),
6108 SymmetricKeyKind::Hmac,
6109 b"sufficiently-long-hmac-secret".to_vec(),
6110 KeyUsages::VERIFY,
6111 &resources,
6112 )
6113 .expect("verification HMAC imports");
6114 inventory
6115 .add_symmetric(
6116 "sign-only".into(),
6117 SymmetricKeyKind::Hmac,
6118 b"another-hmac-secret".to_vec(),
6119 KeyUsages::SIGN,
6120 &resources,
6121 )
6122 .expect("sign-only HMAC imports");
6123 let resolver = inventory.verification_resolver();
6124 let named = |name: &str| KeyInfo {
6125 sources: vec![KeyInfoSource::KeyName(name.into())],
6126 ..KeyInfo::default()
6127 };
6128 assert!(
6129 resolver
6130 .resolve(Some(&named("verify")), SignatureAlgorithm::HmacSha256)
6131 .expect("named HMAC resolves")
6132 .is_some()
6133 );
6134 assert!(
6135 resolver
6136 .resolve(Some(&named("verify")), SignatureAlgorithm::RsaSha256)
6137 .is_err()
6138 );
6139 assert!(
6140 resolver
6141 .resolve(Some(&named("sign-only")), SignatureAlgorithm::HmacSha256)
6142 .is_err()
6143 );
6144 }
6145
6146 #[test]
6147 fn named_hmac_resolution_rejects_invalid_policy_snapshot() {
6148 let mut inventory = KeyInventory::default();
6151 inventory
6152 .add_symmetric(
6153 "verify".into(),
6154 SymmetricKeyKind::Hmac,
6155 b"sufficiently-long-hmac-secret".to_vec(),
6156 KeyUsages::VERIFY,
6157 &ResourcePolicy::default(),
6158 )
6159 .expect("verification HMAC imports");
6160 let info = KeyInfo {
6161 sources: vec![KeyInfoSource::KeyName("verify".into())],
6162 ..KeyInfo::default()
6163 };
6164 let mut policy = crate::policy::VerificationPolicy::default();
6165 policy.resources.max_external_resource_bytes = usize::MAX;
6166 assert!(matches!(
6167 inventory.verification_resolver().resolve_with_policy(
6168 Some(&info),
6169 SignatureAlgorithm::HmacSha256,
6170 &policy
6171 ),
6172 Err(DsigError::Policy(_))
6173 ));
6174 }
6175
6176 #[test]
6177 fn named_hmac_resolution_uses_active_resource_limits() {
6178 let resources = ResourcePolicy::default();
6181 let secret = b"sufficiently-long-hmac-secret";
6182 let mut inventory = KeyInventory::default();
6183 inventory
6184 .add_symmetric(
6185 "verify".into(),
6186 SymmetricKeyKind::Hmac,
6187 secret.to_vec(),
6188 KeyUsages::VERIFY,
6189 &resources,
6190 )
6191 .expect("verification HMAC imports");
6192 let key_info = KeyInfo {
6193 sources: vec![KeyInfoSource::KeyName("verify".into())],
6194 ..KeyInfo::default()
6195 };
6196 let resolver = inventory.verification_resolver();
6197 for (resource, aggregate) in [
6198 (crate::policy::resource_name::EXTERNAL_RESOURCE_BYTES, false),
6199 (
6200 crate::policy::resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES,
6201 true,
6202 ),
6203 ] {
6204 let mut policy = crate::policy::VerificationPolicy::default();
6205 if aggregate {
6206 policy.resources.max_external_resource_total_bytes = secret.len() - 1;
6207 } else {
6208 policy.resources.max_external_resource_bytes = secret.len() - 1;
6209 }
6210 let error = resolver
6211 .resolve_with_policy(Some(&key_info), SignatureAlgorithm::HmacSha256, &policy)
6212 .err()
6213 .expect("active limit must reject stored HMAC material");
6214 assert!(
6215 matches!(
6216 error,
6217 DsigError::Policy(crate::policy::PolicyViolation::ResourceLimit {
6218 resource: actual,
6219 maximum,
6220 actual: size,
6221 }) if actual == resource && maximum == secret.len() - 1 && size == secret.len()
6222 ),
6223 "{error:?}"
6224 );
6225 }
6226 }
6227
6228 #[test]
6229 fn one_named_verification_entry_fits_one_candidate() {
6230 let mut inventory = KeyInventory::default();
6232 let mut policy = crate::policy::VerificationPolicy::default();
6233 inventory
6234 .add_symmetric(
6235 "only".into(),
6236 SymmetricKeyKind::Hmac,
6237 b"sufficiently-long-hmac-secret".to_vec(),
6238 KeyUsages::VERIFY,
6239 &policy.resources,
6240 )
6241 .expect("HMAC imports");
6242 policy.resources.max_key_candidates = 1;
6243 let info = KeyInfo {
6244 sources: vec![KeyInfoSource::KeyName("only".into())],
6245 };
6246 assert!(
6247 inventory
6248 .verification_resolver()
6249 .resolve_with_policy(Some(&info), SignatureAlgorithm::HmacSha256, &policy)
6250 .expect("one lookup fits")
6251 .is_some()
6252 );
6253 }
6254
6255 #[test]
6256 fn pem_imports_charge_encoded_input_to_aggregate_budget() {
6257 let public = include_str!("../tests/fixtures/keys/rsa/rsa-4096-pubkey.pem");
6260 let private = include_str!("../tests/fixtures/keys/rsa/rsa-4096-key.pem");
6261 for (pem, is_private) in [(public, false), (private, true)] {
6262 let padded = format!("{pem}{}", " ".repeat(16 * 1024));
6263 let workspace = pem::parse(pem).expect("PEM payload").contents().len()
6267 * if is_private { 2 } else { 1 };
6268 let resources = ResourcePolicy {
6269 max_external_resource_bytes: padded.len(),
6270 max_external_resource_total_bytes: padded.len()
6271 + if is_private { 5 } else { 10 }
6272 + workspace
6273 + 1,
6274 ..ResourcePolicy::default()
6275 };
6276 let mut inventory = KeyInventory::default();
6277 let import = |inventory: &mut KeyInventory, name: &str| {
6278 if is_private {
6279 inventory.add_private_pem(
6280 name.into(),
6281 padded.as_bytes(),
6282 None,
6283 KeyUsages::SIGN,
6284 &resources,
6285 )
6286 } else {
6287 inventory.add_public_pem(name.into(), padded.as_bytes(), &resources)
6288 }
6289 };
6290 import(&mut inventory, "first").expect("first PEM import fits");
6291 assert!(
6292 matches!(
6293 import(&mut inventory, "second"),
6294 Err(KeyStoreError::Policy(
6295 crate::policy::PolicyViolation::ResourceLimitExceeded {
6296 resource:
6297 crate::policy::resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES,
6298 ..
6299 }
6300 ))
6301 ),
6302 "second PEM input must exceed aggregate budget"
6303 );
6304 }
6305 }
6306
6307 #[test]
6308 fn repeated_key_name_selects_one_inventory_entry() {
6309 let resources = ResourcePolicy::default();
6312 let mut inventory = KeyInventory::default();
6313 inventory
6314 .add_symmetric(
6315 "secret".into(),
6316 SymmetricKeyKind::Hmac,
6317 b"sufficiently-long-hmac-secret".to_vec(),
6318 KeyUsages::VERIFY,
6319 &resources,
6320 )
6321 .expect("HMAC key imports");
6322 inventory
6323 .add_symmetric(
6324 "other-secret".into(),
6325 SymmetricKeyKind::Hmac,
6326 b"another-long-hmac-secret".to_vec(),
6327 KeyUsages::VERIFY,
6328 &resources,
6329 )
6330 .expect("second HMAC key imports");
6331 inventory
6332 .add_public_pem(
6333 "public".into(),
6334 include_bytes!("../tests/fixtures/keys/rsa/rsa-2048-pubkey.pem"),
6335 &resources,
6336 )
6337 .expect("public key imports");
6338 let resolver = inventory.verification_resolver();
6339 for (name, algorithm) in [
6340 ("secret", SignatureAlgorithm::HmacSha256),
6341 ("public", SignatureAlgorithm::RsaSha256),
6342 ] {
6343 let info = KeyInfo {
6344 sources: vec![
6345 KeyInfoSource::KeyName(name.into()),
6346 KeyInfoSource::KeyName(name.into()),
6347 ],
6348 };
6349 assert!(resolver.resolve(Some(&info), algorithm).is_ok(), "{name}");
6350 }
6351 let distinct = KeyInfo {
6352 sources: vec![
6353 KeyInfoSource::KeyName("secret".into()),
6354 KeyInfoSource::KeyName("other-secret".into()),
6355 ],
6356 };
6357 assert!(
6358 resolver
6359 .resolve(Some(&distinct), SignatureAlgorithm::HmacSha256)
6360 .is_err()
6361 );
6362 }
6363
6364 #[test]
6365 fn named_lookup_charges_inspected_inventory_entries() {
6366 let mut inventory = KeyInventory::default();
6368 let resources = ResourcePolicy::default();
6369 for name in ["first", "second"] {
6370 inventory
6371 .add_symmetric(
6372 name.into(),
6373 SymmetricKeyKind::Hmac,
6374 b"sufficiently-long-hmac-secret".to_vec(),
6375 KeyUsages::VERIFY,
6376 &resources,
6377 )
6378 .expect("key imports");
6379 }
6380 let key_info = KeyInfo {
6381 sources: vec![KeyInfoSource::KeyName("second".into())],
6382 ..KeyInfo::default()
6383 };
6384 let policy = crate::policy::VerificationPolicy {
6385 resources: ResourcePolicy {
6386 max_key_candidates: 1,
6387 ..ResourcePolicy::default()
6388 },
6389 ..crate::policy::VerificationPolicy::default()
6390 };
6391 let resolver = inventory.verification_resolver();
6392 assert!(
6393 resolver
6394 .resolve_with_policy_and_provider(
6395 Some(&key_info),
6396 SignatureAlgorithm::HmacSha256,
6397 &policy,
6398 crate::provider::default_provider(),
6399 )
6400 .is_err()
6401 );
6402 }
6403
6404 #[test]
6405 fn private_import_rejects_public_only_usage() {
6406 let private = include_bytes!("../tests/fixtures/keys/rsa/rsa-2048-key.pem");
6409 let mut inventory = KeyInventory::default();
6410 assert!(
6411 inventory
6412 .add_private_pem(
6413 "wrong-use".into(),
6414 private,
6415 None,
6416 KeyUsages::SIGN.union(KeyUsages::VERIFY),
6417 &ResourcePolicy::default(),
6418 )
6419 .is_err()
6420 );
6421 assert!(inventory.private_keys.is_empty());
6422 }
6423
6424 #[test]
6425 fn normalized_private_key_must_fit_resource_limit() {
6426 let rsa = RsaPrivateKey::from_pkcs8_pem(include_str!(
6428 "../tests/fixtures/keys/rsa/rsa-2048-key.pem"
6429 ))
6430 .expect("RSA fixture parses");
6431 let pkcs1 = rsa.to_pkcs1_der().expect("PKCS#1 encodes");
6432 let pkcs8 = rsa.to_pkcs8_der().expect("PKCS#8 encodes");
6433 assert!(pkcs8.as_bytes().len() > pkcs1.as_bytes().len());
6434 let resources = ResourcePolicy {
6435 max_external_resource_bytes: pkcs1.as_bytes().len(),
6436 ..ResourcePolicy::default()
6437 };
6438 let mut inventory = KeyInventory::default();
6439 assert!(
6440 inventory
6441 .add_private_der(
6442 "rsa".into(),
6443 pkcs1.as_bytes(),
6444 None,
6445 KeyUsages::SIGN,
6446 &resources,
6447 )
6448 .is_err()
6449 );
6450 assert!(inventory.private_keys.is_empty());
6451 }
6452
6453 #[test]
6454 fn oversized_rsa_components_are_rejected_before_private_key_decode() {
6455 let pem = include_bytes!("../tests/fixtures/keys/rsa/rsa-2048-key.pem");
6458 let block = single_pem_block(pem, ResourcePolicy::default().max_external_resource_bytes)
6459 .expect("fixture PEM");
6460 let info = PrivateKeyInfoRef::try_from(block.contents()).expect("fixture PKCS#8");
6461 let original =
6462 pkcs1::RsaPrivateKeyRef::from_der(info.private_key.as_bytes()).expect("fixture PKCS#1");
6463 let oversized_modulus = vec![1_u8; 1025];
6464 let oversized = pkcs1::RsaPrivateKeyRef {
6465 modulus: der::asn1::UintRef::new(&oversized_modulus).expect("positive modulus"),
6466 public_exponent: original.public_exponent,
6467 private_exponent: original.private_exponent,
6468 prime1: original.prime1,
6469 prime2: original.prime2,
6470 exponent1: original.exponent1,
6471 exponent2: original.exponent2,
6472 coefficient: original.coefficient,
6473 other_prime_infos: None,
6474 };
6475 let pkcs1 = der::Encode::to_der(&oversized).expect("PKCS#1 encodes");
6476 let octets = der::asn1::OctetStringRef::new(&pkcs1).expect("PKCS#8 octets");
6477 let pkcs8 = der::Encode::to_der(&PrivateKeyInfoRef::new(pkcs1::ALGORITHM_ID, octets))
6478 .expect("PKCS#8 encodes");
6479 let mut inventory = KeyInventory::default();
6480 let error = inventory
6481 .add_private_der(
6482 "oversized".into(),
6483 &pkcs8,
6484 None,
6485 KeyUsages::SIGN,
6486 &ResourcePolicy::default(),
6487 )
6488 .expect_err("oversized RSA modulus must fail at preflight");
6489 assert!(error.to_string().contains("safety limit"), "{error}");
6490
6491 let oversized_exponent = vec![1_u8; 1025];
6492 let oversized = pkcs1::RsaPrivateKeyRef {
6493 modulus: original.modulus,
6494 public_exponent: original.public_exponent,
6495 private_exponent: der::asn1::UintRef::new(&oversized_exponent)
6496 .expect("positive exponent"),
6497 prime1: original.prime1,
6498 prime2: original.prime2,
6499 exponent1: original.exponent1,
6500 exponent2: original.exponent2,
6501 coefficient: original.coefficient,
6502 other_prime_infos: None,
6503 };
6504 let pkcs1 = der::Encode::to_der(&oversized).expect("PKCS#1 encodes");
6505 let error = preflight_rsa_pkcs1_components(&pkcs1)
6506 .expect_err("oversized private exponent must fail before bigint decoding");
6507 assert!(error.to_string().contains("safety limit"), "{error}");
6508 }
6509
6510 #[test]
6511 fn named_certificate_import_is_not_a_trust_anchor() {
6512 let pem = include_bytes!("../tests/fixtures/keys/rsa/rsa-4096-cert.pem");
6515 let certificate =
6516 single_pem_block(pem, ResourcePolicy::default().max_external_resource_bytes)
6517 .expect("certificate fixture is one PEM block");
6518 let mut inventory = KeyInventory::default();
6519 inventory
6520 .add_public_der(
6521 "recipient-cert".into(),
6522 certificate.contents().to_vec(),
6523 &ResourcePolicy::default(),
6524 )
6525 .expect("named X.509 certificate imports");
6526 #[cfg(feature = "xmlenc")]
6527 {
6528 assert!(
6529 inventory
6530 .rsa_encryption_key(
6531 "recipient-cert",
6532 &crate::policy::EncryptionPolicy::default()
6533 )
6534 .is_ok()
6535 );
6536 let restricted = crate::policy::EncryptionPolicy {
6537 resources: ResourcePolicy {
6538 max_external_resource_bytes: 64,
6539 max_external_resource_total_bytes: 64,
6540 ..ResourcePolicy::default()
6541 },
6542 ..crate::policy::EncryptionPolicy::default()
6543 };
6544 assert!(
6545 inventory
6546 .rsa_encryption_key("recipient-cert", &restricted)
6547 .is_err()
6548 );
6549 }
6550 assert!(inventory.trusted_certificates.is_empty());
6551 }
6552
6553 #[test]
6554 fn eddsa_private_identity_matches_only_its_corresponding_spki() {
6555 use pkcs8::{EncodePrivateKey, EncodePublicKey};
6556 let key = ed25519_dalek::SigningKey::from_bytes(&[0x42; 32]);
6559 let der = key.to_pkcs8_der().expect("Ed25519 private encoding");
6560 let public = key
6561 .verifying_key()
6562 .to_public_key_der()
6563 .expect("Ed25519 public encoding");
6564 let identity = PrivateKeyIdentity::decode(der.as_bytes()).expect("Ed25519 identity");
6565 assert!(identity.matches_spki(public.as_bytes()));
6566 let wrong = ed25519_dalek::SigningKey::from_bytes(&[0x43; 32])
6567 .verifying_key()
6568 .to_public_key_der()
6569 .expect("different Ed25519 public encoding");
6570 assert!(!identity.matches_spki(wrong.as_bytes()));
6571 let key = ed448_goldilocks::SigningKey::try_from(&[0x42; 57][..]).expect("Ed448 seed");
6572 let der = key.to_pkcs8_der().expect("Ed448 private encoding");
6573 let public = key
6574 .verifying_key()
6575 .to_public_key_der()
6576 .expect("Ed448 public encoding");
6577 let identity = PrivateKeyIdentity::decode(der.as_bytes()).expect("Ed448 identity");
6578 assert!(identity.matches_spki(public.as_bytes()));
6579 assert!(!identity.matches_spki(wrong.as_bytes()));
6580 }
6581
6582 #[test]
6583 fn unsupported_spki_is_rejected_at_import() {
6584 let mut agreement_spki = vec![
6587 0x30, 0x2a, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x6e, 0x03, 0x21, 0x00,
6588 ];
6589 agreement_spki.extend_from_slice(&[1; 32]);
6590 let mut inventory = KeyInventory::default();
6591 assert!(
6592 inventory
6593 .add_public_der(
6594 "unsupported".into(),
6595 agreement_spki,
6596 &ResourcePolicy::default()
6597 )
6598 .is_err()
6599 );
6600 assert!(inventory.public_keys.is_empty());
6601 }
6602
6603 #[test]
6604 fn named_certificate_resolution_preserves_document_crl() {
6605 use rcgen::{CertificateParams, KeyPair, KeyUsagePurpose, SerialNumber};
6608 let mut root_params = CertificateParams::new(Vec::new()).expect("root params");
6609 root_params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
6610 root_params.key_usages = vec![KeyUsagePurpose::KeyCertSign, KeyUsagePurpose::CrlSign];
6611 let root = rcgen::CertifiedIssuer::self_signed(
6612 root_params,
6613 KeyPair::generate().expect("root key"),
6614 )
6615 .expect("root certificate");
6616 let mut leaf_params = CertificateParams::new(Vec::new()).expect("leaf params");
6617 leaf_params.serial_number = Some(SerialNumber::from(42_u64));
6618 leaf_params.key_usages = vec![KeyUsagePurpose::DigitalSignature];
6619 let leaf = leaf_params
6620 .signed_by(&KeyPair::generate().expect("leaf key"), &root)
6621 .expect("leaf certificate");
6622 let now = time::OffsetDateTime::now_utc();
6623 let crl = rcgen::CertificateRevocationListParams {
6624 this_update: now - time::Duration::days(1),
6625 next_update: now + time::Duration::days(1),
6626 crl_number: SerialNumber::from(1_u64),
6627 issuing_distribution_point: None,
6628 revoked_certs: vec![rcgen::RevokedCertParams {
6629 serial_number: SerialNumber::from(42_u64),
6630 revocation_time: now - time::Duration::hours(1),
6631 reason_code: None,
6632 invalidity_date: None,
6633 }],
6634 key_identifier_method: rcgen::KeyIdMethod::Sha256,
6635 }
6636 .signed_by(&root)
6637 .expect("signed CRL");
6638 let resources = ResourcePolicy::default();
6639 let mut inventory = KeyInventory::default();
6640 inventory
6641 .add_public_der("leaf".into(), leaf.der().to_vec(), &resources)
6642 .expect("leaf imports");
6643 inventory
6644 .add_certificate_der(root.der().to_vec(), true, &resources)
6645 .expect("root imports");
6646 let policy = crate::policy::VerificationPolicy {
6647 key_trust: crate::policy::KeyTrustPolicy {
6648 verify_x509_chains: true,
6649 check_crls: true,
6650 verification_time: Some(std::time::SystemTime::now()),
6651 ..crate::policy::KeyTrustPolicy::default()
6652 },
6653 ..crate::policy::VerificationPolicy::default()
6654 };
6655 let mut info = KeyInfo {
6656 sources: vec![KeyInfoSource::KeyName("leaf".into())],
6657 };
6658 let resolver = inventory.verification_resolver();
6659 assert!(
6660 resolver
6661 .resolve_with_policy_and_provider(
6662 Some(&info),
6663 SignatureAlgorithm::EcdsaSha256,
6664 &policy,
6665 crate::provider::default_provider()
6666 )
6667 .expect("unrevoked chain resolves")
6668 .is_some()
6669 );
6670 info.sources.push(KeyInfoSource::X509Data(X509DataInfo {
6671 crls: vec![crl.der().to_vec()],
6672 ..X509DataInfo::default()
6673 }));
6674 let error = resolver
6675 .resolve_with_policy_and_provider(
6676 Some(&info),
6677 SignatureAlgorithm::EcdsaSha256,
6678 &policy,
6679 crate::provider::default_provider(),
6680 )
6681 .err()
6682 .expect("document CRL revokes leaf");
6683 assert!(
6684 error
6685 .to_string()
6686 .contains("certificate at chain position 0 is revoked"),
6687 "{error}"
6688 );
6689 let mut bounded = policy.clone();
6690 bounded.resources.max_external_resource_total_bytes =
6693 leaf.der().len() + 2 * (root.der().len() + crl.der().len()) - 1;
6694 assert!(matches!(
6695 resolver.resolve_with_policy_and_provider(
6696 Some(&info),
6697 SignatureAlgorithm::EcdsaSha256,
6698 &bounded,
6699 crate::provider::default_provider()
6700 ),
6701 Err(DsigError::Policy(
6702 crate::policy::PolicyViolation::ResourceLimit {
6703 resource: crate::policy::resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES,
6704 ..
6705 }
6706 ))
6707 ));
6708 bounded.key_trust.check_crls = false;
6709 assert!(
6710 resolver
6711 .resolve_with_policy_and_provider(
6712 Some(&info),
6713 SignatureAlgorithm::EcdsaSha256,
6714 &bounded,
6715 crate::provider::default_provider()
6716 )
6717 .expect("disabled CRL checks do not load CRLs")
6718 .is_some()
6719 );
6720 }
6721
6722 #[test]
6723 fn named_certificate_resolution_enforces_inventory_crl() {
6724 fn cert(pem: &[u8]) -> Vec<u8> {
6726 let text = std::str::from_utf8(pem).expect("fixture is UTF-8");
6727 let start = text.find("-----BEGIN ").expect("fixture has PEM armor");
6728 single_pem_block(
6729 &text.as_bytes()[start..],
6730 ResourcePolicy::default().max_external_resource_bytes,
6731 )
6732 .expect("certificate PEM parses")
6733 .into_contents()
6734 }
6735 let resources = ResourcePolicy::default();
6736 let mut inventory = KeyInventory::default();
6737 inventory
6738 .add_public_der(
6739 "leaf".into(),
6740 cert(include_bytes!(
6741 "../tests/fixtures/keys/rsa/rsa-2048-cert.pem"
6742 )),
6743 &resources,
6744 )
6745 .expect("leaf imports");
6746 for anchor in [
6747 include_bytes!("../tests/fixtures/keys/ca2cert.pem").as_slice(),
6748 include_bytes!("../tests/fixtures/keys/cacert.pem").as_slice(),
6749 ] {
6750 inventory
6751 .add_certificate_der(cert(anchor), true, &resources)
6752 .expect("anchor imports");
6753 }
6754 inventory
6755 .add_crl_der(
6756 cert(include_bytes!(
6757 "../tests/fixtures/keys/rsa/rsa-2048-cert-revoked-crl.pem"
6758 )),
6759 &resources,
6760 )
6761 .expect("CRL imports");
6762 let policy = crate::policy::VerificationPolicy {
6763 key_trust: crate::policy::KeyTrustPolicy {
6764 verify_x509_chains: true,
6765 check_crls: true,
6766 max_x509_chain_depth: 3,
6767 verification_time: Some(
6768 std::time::SystemTime::UNIX_EPOCH
6769 + std::time::Duration::from_secs(1_773_964_800),
6770 ),
6771 ..crate::policy::KeyTrustPolicy::default()
6772 },
6773 ..crate::policy::VerificationPolicy::default()
6774 };
6775 let key_info = KeyInfo {
6776 sources: vec![KeyInfoSource::KeyName("leaf".into())],
6777 ..KeyInfo::default()
6778 };
6779 let error = inventory
6780 .verification_resolver()
6781 .resolve_with_policy_and_provider(
6782 Some(&key_info),
6783 SignatureAlgorithm::RsaSha256,
6784 &policy,
6785 crate::provider::default_provider(),
6786 )
6787 .err()
6788 .expect("revocation evidence must reject this chain");
6789 assert!(error.to_string().contains("cRLSign"), "{error}");
6790 }
6791
6792 #[test]
6793 fn hmac_resolution_does_not_load_unrelated_certificate_material() {
6794 let mut inventory = KeyInventory::default();
6797 inventory
6798 .add_symmetric(
6799 "hmac".into(),
6800 SymmetricKeyKind::Hmac,
6801 b"secret".to_vec(),
6802 KeyUsages::VERIFY,
6803 &ResourcePolicy::default(),
6804 )
6805 .expect("HMAC imports");
6806 let pem = include_bytes!("../tests/fixtures/keys/rsa/rsa-2048-cert.pem");
6807 let certificate =
6808 single_pem_block(pem, ResourcePolicy::default().max_external_resource_bytes)
6809 .expect("certificate fixture");
6810 inventory
6811 .add_certificate_der(
6812 certificate.contents().to_vec(),
6813 true,
6814 &ResourcePolicy::default(),
6815 )
6816 .expect("anchor imports");
6817 let policy = crate::policy::VerificationPolicy {
6818 resources: ResourcePolicy {
6819 max_external_resource_bytes: 64,
6820 max_external_resource_total_bytes: 64,
6821 ..ResourcePolicy::default()
6822 },
6823 ..crate::policy::VerificationPolicy::default()
6824 };
6825 let info = KeyInfo {
6826 sources: vec![KeyInfoSource::KeyName("hmac".into())],
6827 ..KeyInfo::default()
6828 };
6829 assert!(
6830 inventory
6831 .verification_resolver()
6832 .resolve_with_policy_and_provider(
6833 Some(&info),
6834 SignatureAlgorithm::HmacSha256,
6835 &policy,
6836 crate::provider::default_provider(),
6837 )
6838 .expect("unrelated certificates cannot consume HMAC budget")
6839 .is_some()
6840 );
6841 }
6842
6843 #[test]
6844 fn named_key_resolution_obeys_source_policy() {
6845 let resources = ResourcePolicy::default();
6848 let mut inventory = KeyInventory::default();
6849 inventory
6850 .add_symmetric(
6851 "blocked-name".into(),
6852 SymmetricKeyKind::Hmac,
6853 b"policy-guarded-hmac-secret".to_vec(),
6854 KeyUsages::VERIFY,
6855 &resources,
6856 )
6857 .expect("HMAC fixture imports");
6858 let info = KeyInfo {
6859 sources: vec![KeyInfoSource::KeyName("blocked-name".into())],
6860 ..KeyInfo::default()
6861 };
6862 let mut policy = crate::policy::VerificationPolicy::default();
6863 policy.key_sources.key_name = false;
6864 assert!(
6865 inventory
6866 .verification_resolver()
6867 .resolve_with_policy_and_provider(
6868 Some(&info),
6869 SignatureAlgorithm::HmacSha256,
6870 &policy,
6871 crate::provider::default_provider(),
6872 )
6873 .is_err()
6874 );
6875 }
6876
6877 #[test]
6878 fn named_lookup_and_fallback_share_one_candidate_budget() {
6879 let mut inventory = KeyInventory::default();
6882 inventory.public_keys.push(StoredPublicKey {
6883 name: "named".into(),
6884 key_info: KeyInfo {
6885 sources: vec![
6886 KeyInfoSource::KeyName("named".into()),
6887 KeyInfoSource::KeyValue(KeyValueInfo::Rsa {
6888 modulus: vec![3],
6889 exponent: vec![3],
6890 }),
6891 ],
6892 },
6893 usages: KeyUsages::VERIFY,
6894 });
6895 let info = KeyInfo {
6896 sources: vec![KeyInfoSource::KeyName("named".into())],
6897 };
6898 let mut policy = crate::policy::VerificationPolicy::default();
6899 policy.resources.max_key_candidates = 1;
6900 let error = inventory
6901 .verification_resolver()
6902 .resolve_with_policy_and_provider(
6903 Some(&info),
6904 SignatureAlgorithm::RsaSha256,
6905 &policy,
6906 crate::provider::default_provider(),
6907 )
6908 .err()
6909 .expect("delegation must not reset the candidate budget");
6910 assert!(matches!(
6911 error,
6912 DsigError::Policy(crate::policy::PolicyViolation::ResourceLimit {
6913 resource: crate::policy::resource_name::KEY_CANDIDATES,
6914 maximum: 1,
6915 actual: 2,
6916 })
6917 ));
6918 }
6919
6920 #[test]
6921 fn prefix_retry_spends_the_same_candidate_budget() {
6922 let mut sources = vec![KeyInfoSource::KeyName("missing".into()); 3];
6925 sources.push(KeyInfoSource::X509Data(X509DataInfo {
6926 subject_names: vec!["missing subject".into()],
6927 ..X509DataInfo::default()
6928 }));
6929 let info = KeyInfo { sources };
6930 let mut policy = crate::policy::VerificationPolicy::default();
6931 policy.resources.max_key_candidates = 5;
6932 let error = KeyInventory::default()
6933 .verification_resolver()
6934 .resolve_with_policy_and_provider(
6935 Some(&info),
6936 SignatureAlgorithm::RsaSha256,
6937 &policy,
6938 crate::provider::default_provider(),
6939 )
6940 .err()
6941 .expect("the repeated prefix must exhaust the candidate limit");
6942 assert!(matches!(
6943 error,
6944 DsigError::Policy(crate::policy::PolicyViolation::ResourceLimit {
6945 resource: crate::policy::resource_name::KEY_CANDIDATES,
6946 maximum: 5,
6947 actual: 6,
6948 })
6949 ));
6950 }
6951
6952 #[test]
6953 fn selected_key_value_is_one_resource() {
6954 for value in [
6957 KeyValueInfo::Rsa {
6958 modulus: vec![1; 256],
6959 exponent: vec![1; 3],
6960 },
6961 KeyValueInfo::Dsa {
6962 p: Some(vec![1; 64]),
6963 q: Some(vec![1; 16]),
6964 g: Some(vec![1; 64]),
6965 y: vec![1; 64],
6966 },
6967 KeyValueInfo::Ec {
6968 curve_oid: "1.2.840.10045.3.1.7".into(),
6969 public_key: vec![1; 65],
6970 },
6971 ] {
6972 let info = KeyInfo {
6973 sources: vec![KeyInfoSource::KeyValue(value)],
6974 };
6975 let size =
6976 check_selected_public_material(&info, &ResourcePolicy::default()).expect("size");
6977 let resources = ResourcePolicy {
6978 max_external_resource_bytes: size,
6979 ..ResourcePolicy::default()
6980 };
6981 assert_eq!(
6982 check_selected_public_material(&info, &resources).expect("exact limit"),
6983 size
6984 );
6985 let resources = ResourcePolicy {
6986 max_external_resource_bytes: size - 1,
6987 ..resources
6988 };
6989 assert!(matches!(check_selected_public_material(&info, &resources),
6990 Err(DsigError::Policy(crate::policy::PolicyViolation::ResourceLimit {
6991 resource: crate::policy::resource_name::EXTERNAL_RESOURCE_BYTES, actual, ..
6992 })) if actual == size));
6993 }
6994 }
6995
6996 #[test]
6997 fn named_verification_bounds_complete_key_value() {
6998 use rsa::traits::PublicKeyParts as _;
7001 let public = RsaPublicKey::from_public_key_pem(include_str!(
7002 "../tests/fixtures/keys/rsa/rsa-2048-pubkey.pem"
7003 ))
7004 .expect("RSA fixture");
7005 let modulus = public.n().to_be_bytes_trimmed_vartime();
7006 let exponent = public.e().to_be_bytes_trimmed_vartime();
7007 let size = modulus.len() + exponent.len();
7008 let base64 = base64::engine::general_purpose::STANDARD;
7009 let xml = format!(
7010 "<Keys xmlns=\"{XMLSEC_NS}\"><KeyInfo xmlns=\"{XMLDSIG_NS}\"><KeyName>named</KeyName><KeyValue><RSAKeyValue><Modulus>{}</Modulus><Exponent>{}</Exponent></RSAKeyValue></KeyValue></KeyInfo></Keys>",
7011 base64.encode(modulus),
7012 base64.encode(exponent)
7013 );
7014 let inventory = KeyInventory::from_xml_bytes(
7015 xml.as_bytes(),
7016 &xml_policy(ResourcePolicy::default()),
7017 XmlBackend::default(),
7018 )
7019 .expect("broad import");
7020 let info = KeyInfo {
7021 sources: vec![KeyInfoSource::KeyName("named".into())],
7022 };
7023 let mut policy = crate::policy::VerificationPolicy::default();
7024 policy.resources.max_external_resource_bytes = size;
7025 assert!(
7026 inventory
7027 .verification_resolver()
7028 .resolve_with_policy_and_provider(
7029 Some(&info),
7030 SignatureAlgorithm::RsaSha256,
7031 &policy,
7032 crate::provider::default_provider()
7033 )
7034 .expect("exact complete-key limit")
7035 .is_some()
7036 );
7037 policy.resources.max_external_resource_bytes = size - 1;
7038 assert!(
7039 matches!(inventory.verification_resolver().resolve_with_policy_and_provider(Some(&info), SignatureAlgorithm::RsaSha256,
7040 &policy, crate::provider::default_provider()),
7041 Err(DsigError::Policy(crate::policy::PolicyViolation::ResourceLimit {
7042 resource: crate::policy::resource_name::EXTERNAL_RESOURCE_BYTES, actual, ..
7043 })) if actual == size)
7044 );
7045 }
7046
7047 #[test]
7048 fn configured_x509_fallback_budgets_live_copies() {
7049 let resources = ResourcePolicy::default();
7052 let certificate = single_pem_block(
7053 include_bytes!("../tests/fixtures/keys/rsa/rsa-2048-cert.pem"),
7054 resources.max_external_resource_bytes,
7055 )
7056 .expect("certificate")
7057 .into_contents();
7058 for trusted in [false, true] {
7059 for with_crl in [false, true] {
7060 let mut inventory = KeyInventory::default();
7061 inventory
7062 .add_certificate_der(certificate.clone(), trusted, &resources)
7063 .expect("certificate imports");
7064 let document_crl = vec![0; 16];
7065 if with_crl {
7066 inventory.crls.push(vec![0; 32]);
7067 }
7068 let info = KeyInfo {
7069 sources: vec![KeyInfoSource::X509Data(X509DataInfo {
7070 subject_names: vec!["CN=absent".into()],
7071 crls: if with_crl {
7072 vec![document_crl]
7073 } else {
7074 Vec::new()
7075 },
7076 ..X509DataInfo::default()
7077 })],
7078 };
7079 let mut policy = crate::policy::VerificationPolicy::default();
7080 policy.key_trust.verify_x509_chains = true;
7081 policy.key_trust.check_crls = with_crl;
7082 let configured = certificate.len() + if with_crl { 32 } else { 0 };
7083 let peak = 2 * configured;
7086 policy.resources.max_external_resource_total_bytes = peak - 1;
7087 assert!(
7088 matches!(
7089 inventory
7090 .verification_resolver()
7091 .resolve_with_policy_and_provider(
7092 Some(&info),
7093 SignatureAlgorithm::RsaSha256,
7094 &policy,
7095 crate::provider::default_provider(),
7096 ),
7097 Err(DsigError::Policy(
7098 crate::policy::PolicyViolation::ResourceLimit {
7099 resource:
7100 crate::policy::resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES,
7101 ..
7102 }
7103 ))
7104 ),
7105 "each owned fallback copy must fit before allocation"
7106 );
7107 policy.resources.max_external_resource_total_bytes = peak;
7108 let resolver = inventory.verification_resolver();
7109 let outcome = resolver.resolve_with_policy_and_provider(
7110 Some(&info),
7111 SignatureAlgorithm::RsaSha256,
7112 &policy,
7113 crate::provider::default_provider(),
7114 );
7115 assert!(
7116 !matches!(outcome, Err(DsigError::Policy(_))),
7117 "exact copy budget passes preflight"
7118 );
7119 }
7120 }
7121 }
7122
7123 #[test]
7124 fn named_x509_fallback_budgets_document_crl_copies() {
7125 let resources = ResourcePolicy::default();
7128 let certificate = single_pem_block(
7129 include_bytes!("../tests/fixtures/keys/rsa/rsa-2048-cert.pem"),
7130 resources.max_external_resource_bytes,
7131 )
7132 .expect("certificate")
7133 .into_contents();
7134 let mut inventory = KeyInventory::default();
7135 inventory
7136 .add_public_der("leaf".into(), certificate.clone(), &resources)
7137 .expect("named certificate");
7138 let info = KeyInfo {
7139 sources: vec![
7140 KeyInfoSource::KeyName("leaf".into()),
7141 KeyInfoSource::X509Data(X509DataInfo {
7142 crls: vec![vec![0; 32]],
7143 ..X509DataInfo::default()
7144 }),
7145 ],
7146 };
7147 let mut policy = crate::policy::VerificationPolicy::default();
7148 policy.key_trust.verify_x509_chains = true;
7149 policy.key_trust.check_crls = true;
7150 policy.resources.max_external_resource_total_bytes = certificate.len() + 64 - 1;
7151 assert!(matches!(
7152 inventory
7153 .verification_resolver()
7154 .resolve_with_policy_and_provider(
7155 Some(&info),
7156 SignatureAlgorithm::RsaSha256,
7157 &policy,
7158 crate::provider::default_provider(),
7159 ),
7160 Err(DsigError::Policy(
7161 crate::policy::PolicyViolation::ResourceLimit {
7162 resource: crate::policy::resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES,
7163 ..
7164 }
7165 ))
7166 ));
7167 }
7168
7169 #[test]
7170 fn selected_certificate_and_anchors_share_aggregate_budget() {
7171 let certificate = single_pem_block(
7174 include_bytes!("../tests/fixtures/keys/rsa/rsa-2048-cert.pem"),
7175 ResourcePolicy::default().max_external_resource_bytes,
7176 )
7177 .expect("leaf PEM")
7178 .into_contents();
7179 let anchor = single_pem_block(
7180 include_bytes!("../tests/fixtures/keys/rsa/rsa-4096-cert.pem"),
7181 ResourcePolicy::default().max_external_resource_bytes,
7182 )
7183 .expect("anchor PEM")
7184 .into_contents();
7185 let mut inventory = KeyInventory::default();
7186 inventory
7187 .add_public_der(
7188 "leaf".into(),
7189 certificate.clone(),
7190 &ResourcePolicy::default(),
7191 )
7192 .expect("leaf imports");
7193 inventory
7194 .add_certificate_der(anchor.clone(), true, &ResourcePolicy::default())
7195 .expect("anchor imports");
7196 let mut policy = crate::policy::VerificationPolicy::default();
7197 policy.key_trust.verify_x509_chains = true;
7198 policy.resources.max_external_resource_total_bytes = certificate.len() + anchor.len() - 1;
7199 let info = KeyInfo {
7200 sources: vec![KeyInfoSource::KeyName("leaf".into())],
7201 };
7202 let error = inventory
7203 .verification_resolver()
7204 .resolve_with_policy_and_provider(
7205 Some(&info),
7206 SignatureAlgorithm::RsaSha256,
7207 &policy,
7208 crate::provider::default_provider(),
7209 )
7210 .err()
7211 .expect("combined selected and configured material exceeds the budget");
7212 assert!(matches!(
7213 error,
7214 DsigError::Policy(crate::policy::PolicyViolation::ResourceLimit {
7215 resource: crate::policy::resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES,
7216 ..
7217 })
7218 ));
7219 }
7220
7221 #[test]
7222 fn named_key_does_not_bypass_other_source_permissions() {
7223 let resources = ResourcePolicy::default();
7226 let mut inventory = KeyInventory::default();
7227 inventory
7228 .add_symmetric(
7229 "named".into(),
7230 SymmetricKeyKind::Hmac,
7231 b"verification-secret".to_vec(),
7232 KeyUsages::VERIFY,
7233 &resources,
7234 )
7235 .expect("HMAC fixture imports");
7236 let info = KeyInfo {
7237 sources: vec![
7238 KeyInfoSource::KeyName("named".into()),
7239 KeyInfoSource::KeyValue(KeyValueInfo::Rsa {
7240 modulus: vec![3],
7241 exponent: vec![3],
7242 }),
7243 ],
7244 ..KeyInfo::default()
7245 };
7246 let mut policy = crate::policy::VerificationPolicy::default();
7247 policy.key_sources.key_value = false;
7248 assert!(
7249 inventory
7250 .verification_resolver()
7251 .resolve_with_policy_and_provider(
7252 Some(&info),
7253 SignatureAlgorithm::HmacSha256,
7254 &policy,
7255 crate::provider::default_provider(),
7256 )
7257 .is_err()
7258 );
7259 }
7260
7261 #[test]
7262 fn named_public_key_is_trusted_inventory_material() {
7263 let mut inventory = KeyInventory::default();
7266 inventory
7267 .add_public_pem(
7268 "named".into(),
7269 include_bytes!("../tests/fixtures/keys/rsa/rsa-2048-pubkey.pem"),
7270 &ResourcePolicy::default(),
7271 )
7272 .expect("public key imports");
7273 let info = KeyInfo {
7274 sources: vec![KeyInfoSource::KeyName("named".into())],
7275 ..KeyInfo::default()
7276 };
7277 let mut policy = crate::policy::VerificationPolicy::default();
7278 policy.key_sources.key_value = false;
7279 policy.key_sources.der_encoded_key_value = false;
7280 policy.key_sources.x509_data = false;
7281 assert!(
7282 inventory
7283 .verification_resolver()
7284 .resolve_with_policy_and_provider(
7285 Some(&info),
7286 SignatureAlgorithm::RsaSha256,
7287 &policy,
7288 crate::provider::default_provider(),
7289 )
7290 .expect("trusted inventory material resolves")
7291 .is_some()
7292 );
7293 }
7294
7295 #[test]
7296 fn named_public_key_obeys_current_verification_limits() {
7297 let mut inventory = KeyInventory::default();
7299 inventory
7300 .add_public_pem(
7301 "named".into(),
7302 include_bytes!("../tests/fixtures/keys/rsa/rsa-2048-pubkey.pem"),
7303 &ResourcePolicy::default(),
7304 )
7305 .expect("public key imports");
7306 let info = KeyInfo {
7307 sources: vec![KeyInfoSource::KeyName("named".into())],
7308 };
7309 let mut policy = crate::policy::VerificationPolicy::default();
7310 policy.resources.max_external_resource_bytes = 1;
7311 assert!(
7312 inventory
7313 .verification_resolver()
7314 .resolve_with_policy(Some(&info), SignatureAlgorithm::RsaSha256, &policy)
7315 .is_err()
7316 );
7317 let certificate = pem::parse(include_bytes!(
7318 "../tests/fixtures/keys/rsa/rsa-2048-cert.pem"
7319 ))
7320 .expect("certificate PEM")
7321 .into_contents();
7322 let mut certificate_inventory = KeyInventory::default();
7323 certificate_inventory
7324 .add_public_der("named".into(), certificate, &ResourcePolicy::default())
7325 .expect("certificate imports as a named public key");
7326 assert!(
7327 certificate_inventory
7328 .verification_resolver()
7329 .resolve_with_policy(Some(&info), SignatureAlgorithm::RsaSha256, &policy)
7330 .is_err()
7331 );
7332 }
7333
7334 #[test]
7335 fn unused_certificates_do_not_block_earlier_public_keys() {
7336 let mut inventory = KeyInventory::default();
7338 let certificate = pem::parse(include_bytes!(
7339 "../tests/fixtures/keys/rsa/rsa-2048-cert.pem"
7340 ))
7341 .expect("certificate PEM")
7342 .into_contents();
7343 inventory
7344 .add_certificate_der(certificate, false, &ResourcePolicy::default())
7345 .expect("certificate imports");
7346 let public = pem::parse(include_bytes!(
7347 "../tests/fixtures/keys/rsa/rsa-2048-pubkey.pem"
7348 ))
7349 .expect("public PEM")
7350 .into_contents();
7351 let info = KeyInfo {
7352 sources: vec![
7353 KeyInfoSource::DerEncodedKeyValue(public.clone()),
7354 KeyInfoSource::X509Data(X509DataInfo {
7355 subject_names: vec!["unused".into()],
7356 ..X509DataInfo::default()
7357 }),
7358 ],
7359 };
7360 let mut policy = crate::policy::VerificationPolicy::default();
7361 policy.resources.max_external_resource_bytes = public.len();
7362 assert!(
7363 inventory
7364 .verification_resolver()
7365 .resolve_with_policy(Some(&info), SignatureAlgorithm::RsaSha256, &policy)
7366 .expect("earlier public key resolves")
7367 .is_some()
7368 );
7369 }
7370
7371 #[test]
7372 fn public_import_rejects_incompatible_usage() {
7373 let resources = ResourcePolicy::default();
7375 let public = include_bytes!("../tests/fixtures/keys/rsa/rsa-2048-pubkey.pem");
7376 let mut inventory = KeyInventory::default();
7377 assert!(
7378 inventory
7379 .add_public_pem_with_usages("invalid".into(), public, KeyUsages::SIGN, &resources,)
7380 .is_err()
7381 );
7382 assert_eq!(inventory.entry_count(), 0);
7383 }
7384
7385 #[test]
7386 fn public_certificate_import_accepts_supported_ed25519_key() {
7387 let pair =
7390 rcgen::KeyPair::generate_for(&rcgen::PKCS_ED25519).expect("Ed25519 key generation");
7391 let params = rcgen::CertificateParams::new(vec!["example.test".into()])
7392 .expect("certificate parameters");
7393 let certificate = params.self_signed(&pair).expect("certificate generation");
7394 let mut inventory = KeyInventory::default();
7395 assert!(
7396 inventory
7397 .add_public_der(
7398 "ed25519".into(),
7399 certificate.der().to_vec(),
7400 &ResourcePolicy::default()
7401 )
7402 .is_ok()
7403 );
7404 assert!(inventory.trusted_certificates.is_empty());
7405 }
7406
7407 #[cfg(feature = "xmlenc")]
7408 #[test]
7409 fn imported_usage_restricts_operation_selection() {
7410 let resources = ResourcePolicy::default();
7413 let public = include_bytes!("../tests/fixtures/keys/rsa/rsa-2048-pubkey.pem");
7414 let bundle = include_bytes!("../tests/fixtures/xmlenc/01-phaos-xmlenc-3/rsa-priv-key.p12");
7415 let mut inventory = KeyInventory::default();
7416 inventory
7417 .add_public_pem_with_usages("verify".into(), public, KeyUsages::VERIFY, &resources)
7418 .expect("verification-only public key imports");
7419 assert!(
7420 inventory
7421 .rsa_encryption_key("verify", &crate::policy::EncryptionPolicy::default())
7422 .is_err()
7423 );
7424 inventory
7425 .add_pkcs12_with_usages(
7426 "decrypt".into(),
7427 bundle,
7428 "secret",
7429 KeyUsages::DECRYPT,
7430 &resources,
7431 )
7432 .expect("decryption-only private key imports");
7433 assert!(
7434 inventory
7435 .signing_key(
7436 "decrypt",
7437 SignatureAlgorithm::RsaSha256,
7438 &crate::policy::SigningPolicy::default(),
7439 )
7440 .is_err()
7441 );
7442 assert!(
7443 inventory
7444 .decryption_resolver("decrypt", &crate::policy::DecryptionPolicy::default())
7445 .is_ok()
7446 );
7447 }
7448
7449 #[cfg(feature = "xmlenc")]
7450 #[test]
7451 fn selected_weak_rsa_decryptor_obeys_default_policy() {
7452 let mut inventory = KeyInventory::default();
7454 let weak = RsaPrivateKey::new(&mut ChaCha8Rng::seed_from_u64(0xA11C_E502), 1024)
7455 .expect("legacy key generation")
7456 .to_pkcs8_der()
7457 .expect("legacy key encoding");
7458 inventory
7459 .add_private_der(
7460 "weak".into(),
7461 weak.as_bytes(),
7462 None,
7463 KeyUsages::DECRYPT,
7464 &ResourcePolicy::default(),
7465 )
7466 .expect("legacy import");
7467 let error = inventory
7468 .decryption_resolver("weak", &crate::policy::DecryptionPolicy::default())
7469 .err()
7470 .expect("weak key is rejected");
7471 assert!(
7472 matches!(
7473 error,
7474 KeyStoreError::Policy(crate::policy::PolicyViolation::KeySize {
7475 operation: "decryption",
7476 ..
7477 })
7478 ),
7479 "{error:?}"
7480 );
7481 let key = RsaPrivateKey::from_pkcs8_der(weak.as_bytes()).expect("legacy decode");
7484 let mut encryption = crate::policy::EncryptionPolicy::default();
7485 encryption.rsa_keys.minimum_modulus_bits = 1024;
7486 let encrypted = crate::xmlenc::EncryptedDataBuilder::new(
7487 crate::xmlenc::DataEncryptionAlgorithm::Aes128Gcm,
7488 )
7489 .policy(encryption)
7490 .recipient_rsa_oaep(key.to_public_key())
7491 .encrypt_binary(b"legacy consent")
7492 .expect("explicit legacy encryption");
7493 let mut decryption = crate::policy::DecryptionPolicy::default();
7494 decryption.rsa_keys.minimum_modulus_bits = 1024;
7495 let resolver = inventory
7496 .decryption_resolver("weak", &decryption)
7497 .expect("explicit legacy selection");
7498 assert_eq!(
7499 crate::xmlenc::DecryptContext::new(resolver.as_ref())
7500 .policy(decryption)
7501 .decrypt(&encrypted.encrypted_data_xml)
7502 .expect("explicit legacy recovery"),
7503 crate::xmlenc::DecryptedContent::Bytes(b"legacy consent".to_vec())
7504 );
7505 assert!(matches!(
7506 crate::xmlenc::DecryptContext::new(resolver.as_ref())
7507 .decrypt(&encrypted.encrypted_data_xml),
7508 Err(crate::xmlenc::XmlEncError::Policy(
7509 crate::policy::PolicyViolation::KeySize {
7510 actual_bits: 1024,
7511 ..
7512 }
7513 ))
7514 ));
7515 }
7516
7517 #[cfg(feature = "xmlenc")]
7518 #[test]
7519 fn selected_rsa_recipient_obeys_operation_modulus_policy() {
7520 let mut inventory = KeyInventory::default();
7522 inventory
7523 .add_public_pem(
7524 "rsa".into(),
7525 include_bytes!("../tests/fixtures/keys/rsa/rsa-2048-pubkey.pem"),
7526 &ResourcePolicy::default(),
7527 )
7528 .expect("RSA fixture imports");
7529 let mut policy = crate::policy::EncryptionPolicy::default();
7530 policy.rsa_keys.minimum_modulus_bits = 4096;
7531 assert!(matches!(
7532 inventory.rsa_encryption_key("rsa", &policy),
7533 Err(KeyStoreError::Policy(
7534 crate::policy::PolicyViolation::KeySize { .. }
7535 ))
7536 ));
7537 assert!(matches!(
7538 inventory.public_keys()[0].rsa_encryption_key(&policy),
7539 Err(KeyStoreError::Policy(
7540 crate::policy::PolicyViolation::KeySize { .. }
7541 ))
7542 ));
7543 }
7544
7545 #[test]
7546 fn inventory_merge_checks_names_and_budget_atomically() {
7547 let resources = ResourcePolicy::default();
7550 let mut first = KeyInventory::default();
7551 first
7552 .add_symmetric(
7553 "one".into(),
7554 SymmetricKeyKind::Hmac,
7555 b"first-secret".to_vec(),
7556 KeyUsages::SIGN,
7557 &resources,
7558 )
7559 .expect("first key imports");
7560 let mut duplicate = KeyInventory::default();
7561 duplicate
7562 .add_symmetric(
7563 "one".into(),
7564 SymmetricKeyKind::Hmac,
7565 b"second-secret".to_vec(),
7566 KeyUsages::VERIFY,
7567 &resources,
7568 )
7569 .expect("independent duplicate imports");
7570 assert!(matches!(
7571 first.extend(duplicate, &resources),
7572 Err(KeyStoreError::Selection("duplicate key name"))
7573 ));
7574 assert_eq!(first.entry_count(), 1);
7575 let mut second = KeyInventory::default();
7576 second
7577 .add_symmetric(
7578 "two".into(),
7579 SymmetricKeyKind::Hmac,
7580 b"second-secret".to_vec(),
7581 KeyUsages::VERIFY,
7582 &resources,
7583 )
7584 .expect("second key imports");
7585 let constrained = ResourcePolicy {
7586 max_external_resource_total_bytes: b"first-secret".len(),
7587 ..ResourcePolicy::default()
7588 };
7589 assert!(matches!(
7590 first.extend(second, &constrained),
7591 Err(KeyStoreError::Policy(
7592 crate::policy::PolicyViolation::ResourceLimitExceeded {
7593 resource: crate::policy::resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES,
7594 ..
7595 }
7596 ))
7597 ));
7598 assert_eq!(first.entry_count(), 1);
7599 }
7600
7601 #[test]
7602 fn public_dsa_store_entries_require_usable_parameters() {
7603 for fields in [
7606 "<Y>AQ==</Y>",
7607 "<P/><Q/><G/><Y>AQ==</Y>",
7608 "<P>AQ==</P><Q>AQ==</Q><G>AQ==</G><Y>AQ==</Y>",
7609 ] {
7610 let xml = format!(
7611 "<Keys xmlns=\"{XMLSEC_NS}\"><KeyInfo xmlns=\"{XMLDSIG_NS}\"><KeyName>dsa</KeyName><KeyValue><DSAKeyValue>{fields}</DSAKeyValue></KeyValue></KeyInfo></Keys>"
7612 );
7613 assert!(
7614 KeyInventory::from_xml_bytes(
7615 xml.as_bytes(),
7616 &xml_policy(ResourcePolicy::default()),
7617 XmlBackend::default()
7618 )
7619 .is_err(),
7620 "accepted unusable DSA: {fields}"
7621 );
7622 }
7623 }
7624
7625 #[test]
7626 fn oversized_private_dsa_component_stops_before_big_integer_work() {
7627 let oversized = base64::engine::general_purpose::STANDARD.encode(vec![1_u8; 513]);
7630 let xml = format!(
7631 "<Keys xmlns=\"{XMLSEC_NS}\"><KeyInfo xmlns=\"{XMLDSIG_NS}\"><KeyName>dsa</KeyName><KeyValue><DSAKeyValue><P>{oversized}</P><Q>AQ==</Q><G>AQ==</G><X xmlns=\"{XMLSEC_NS}\">AQ==</X><Y>AQ==</Y></DSAKeyValue></KeyValue></KeyInfo></Keys>"
7632 );
7633 assert!(matches!(
7634 KeyInventory::from_xml_bytes(
7635 xml.as_bytes(),
7636 &xml_policy(ResourcePolicy::default()),
7637 XmlBackend::default(),
7638 ),
7639 Err(KeyStoreError::Invalid(message)) if message.contains("safety limit")
7640 ));
7641 }
7642
7643 #[test]
7644 fn oversized_pkcs8_dsa_parameters_stop_before_key_derivation() {
7645 #[derive(der::Sequence)]
7647 struct DsaParameters<'a> {
7648 p: der::asn1::UintRef<'a>,
7649 q: der::asn1::UintRef<'a>,
7650 g: der::asn1::UintRef<'a>,
7651 }
7652 let oversized = vec![1_u8; crate::hard_limits::DSA_KEY_COMPONENT_BYTE_CEILING + 1];
7653 let one = [1_u8];
7654 let parameters = der::Encode::to_der(&DsaParameters {
7655 p: der::asn1::UintRef::new(&oversized).expect("positive P"),
7656 q: der::asn1::UintRef::new(&one).expect("positive Q"),
7657 g: der::asn1::UintRef::new(&one).expect("positive G"),
7658 })
7659 .expect("DSA parameters encode");
7660 let x = der::Encode::to_der(&der::asn1::UintRef::new(&one).expect("positive X"))
7661 .expect("DSA X encodes");
7662 let algorithm = pkcs8::AlgorithmIdentifierRef {
7663 oid: dsa::OID,
7664 parameters: Some(der::asn1::AnyRef::from_der(¶meters).expect("parameters")),
7665 };
7666 let private = der::asn1::OctetStringRef::new(&x).expect("private octets");
7667 let pkcs8 = der::Encode::to_der(&PrivateKeyInfoRef::new(algorithm, private))
7668 .expect("PKCS#8 encodes");
7669 let mut inventory = KeyInventory::default();
7670 let error = inventory
7671 .add_private_der(
7672 "oversized-dsa".into(),
7673 &pkcs8,
7674 None,
7675 KeyUsages::SIGN,
7676 &ResourcePolicy::default(),
7677 )
7678 .expect_err("oversized DSA parameter must fail preflight");
7679 assert!(
7680 matches!(
7681 error,
7682 KeyStoreError::Selection("invalid or oversized DSA private key")
7683 ),
7684 "{error}"
7685 );
7686 }
7687
7688 #[test]
7689 fn compressed_ec_spki_cannot_acquire_verify_usage() {
7690 for pem in [
7693 include_bytes!("../tests/fixtures/keys/ec/ec-prime256v1-pubkey.pem").as_slice(),
7694 include_bytes!("../tests/fixtures/keys/ec/ec-prime384v1-pubkey.pem").as_slice(),
7695 include_bytes!("../tests/fixtures/keys/ec/ec-prime521v1-pubkey.pem").as_slice(),
7696 ] {
7697 let original =
7698 single_pem_block(pem, ResourcePolicy::default().max_external_resource_bytes)
7699 .expect("EC fixture")
7700 .into_contents();
7701 let spki = pkcs8::SubjectPublicKeyInfoRef::from_der(&original).expect("SPKI");
7702 let point = spki
7703 .subject_public_key
7704 .as_bytes()
7705 .expect("octet-aligned point");
7706 let coordinate_len = (point.len() - 1) / 2;
7707 let mut compressed = vec![2 | (point.last().expect("Y coordinate") & 1)];
7708 compressed.extend_from_slice(&point[1..=coordinate_len]);
7709 let encoded = der::Encode::to_der(&pkcs8::SubjectPublicKeyInfoRef {
7710 algorithm: spki.algorithm,
7711 subject_public_key: der::asn1::BitStringRef::from_bytes(&compressed)
7712 .expect("point"),
7713 })
7714 .expect("compressed SPKI");
7715 let mut inventory = KeyInventory::default();
7716 assert!(
7717 inventory
7718 .add_public_der("compressed".into(), encoded, &ResourcePolicy::default())
7719 .is_err()
7720 );
7721 assert!(inventory.public_keys().is_empty());
7722 inventory
7723 .add_public_der("uncompressed".into(), original, &ResourcePolicy::default())
7724 .expect("supported uncompressed encoding remains usable");
7725 }
7726 struct CompressedPoint<'a>(&'a [u8], &'static rcgen::SignatureAlgorithm);
7728 impl rcgen::PublicKeyData for CompressedPoint<'_> {
7729 fn der_bytes(&self) -> &[u8] {
7730 self.0
7731 }
7732 fn algorithm(&self) -> &'static rcgen::SignatureAlgorithm {
7733 self.1
7734 }
7735 }
7736 let mut root_params = rcgen::CertificateParams::new(Vec::new()).expect("root params");
7737 root_params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
7738 let root = rcgen::CertifiedIssuer::self_signed(
7739 root_params,
7740 rcgen::KeyPair::generate().expect("root key"),
7741 )
7742 .expect("root certificate");
7743 for (pem, algorithm) in [
7744 (
7745 include_bytes!("../tests/fixtures/keys/ec/ec-prime256v1-cert.pem").as_slice(),
7746 &rcgen::PKCS_ECDSA_P256_SHA256,
7747 ),
7748 (
7749 include_bytes!("../tests/fixtures/keys/ec/ec-prime384v1-cert.pem").as_slice(),
7750 &rcgen::PKCS_ECDSA_P384_SHA384,
7751 ),
7752 ] {
7753 let original =
7754 single_pem_block(pem, ResourcePolicy::default().max_external_resource_bytes)
7755 .expect("certificate fixture")
7756 .into_contents();
7757 let (_, certificate) = X509Certificate::from_der(&original).expect("certificate");
7758 let point = certificate.public_key().subject_public_key.data.as_ref();
7759 let coordinate_len = (point.len() - 1) / 2;
7760 let mut compressed = vec![2 | (point.last().expect("Y coordinate") & 1)];
7761 compressed.extend_from_slice(&point[1..=coordinate_len]);
7762 let leaf = rcgen::CertificateParams::new(Vec::new())
7763 .expect("leaf params")
7764 .signed_by(&CompressedPoint(&compressed, algorithm), &root)
7765 .expect("signed compressed certificate");
7766 let encoded = leaf.der().to_vec();
7767 let mut inventory = KeyInventory::default();
7768 assert!(
7769 inventory
7770 .add_public_der(
7771 "compressed-cert".into(),
7772 encoded,
7773 &ResourcePolicy::default()
7774 )
7775 .is_err()
7776 );
7777 assert!(inventory.public_keys().is_empty());
7778 inventory
7779 .add_public_der(
7780 "uncompressed-cert".into(),
7781 original,
7782 &ResourcePolicy::default(),
7783 )
7784 .expect("uncompressed certificate imports");
7785 }
7786 }
7787
7788 #[test]
7789 fn malformed_ec_point_cannot_acquire_verify_usage() {
7790 let block = single_pem_block(
7792 include_bytes!("../tests/fixtures/keys/ec/ec-prime256v1-pubkey.pem"),
7793 ResourcePolicy::default().max_external_resource_bytes,
7794 )
7795 .expect("EC SPKI fixture");
7796 let mut der = block.into_contents();
7797 let last = der.last_mut().expect("point bytes");
7798 *last ^= 1;
7799 let mut inventory = KeyInventory::default();
7800 assert!(
7801 inventory
7802 .add_public_der("off-curve".into(), der, &ResourcePolicy::default())
7803 .is_err()
7804 );
7805 assert!(inventory.public_keys().is_empty());
7806 }
7807
7808 #[cfg(feature = "xmlenc")]
7809 #[test]
7810 fn decryption_policy_rejection_retains_its_type() {
7811 let mut policy = crate::policy::DecryptionPolicy::default();
7813 policy.resources.max_external_resource_bytes = usize::MAX;
7814 assert!(matches!(
7815 KeyInventory::default().decryption_resolver("missing", &policy),
7816 Err(KeyStoreError::Policy(_))
7817 ));
7818 }
7819
7820 #[cfg(feature = "xmlenc")]
7821 #[test]
7822 fn inventory_decryption_resolver_enforces_aes_usage_end_to_end() {
7823 use crate::xmlenc::{
7826 DataEncryptionAlgorithm, DecryptContext, DecryptedContent, EncryptedDataBuilder,
7827 };
7828
7829 let key = b"0123456789abcdef";
7830 let encrypted = EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
7831 .direct_key(*key)
7832 .encrypt_binary(b"inventory decrypt payload")
7833 .expect("AES fixture encrypts");
7834 let resources = ResourcePolicy::default();
7835 let mut keys = KeyInventory::default();
7836 keys.add_symmetric(
7837 "decrypt".into(),
7838 SymmetricKeyKind::Aes,
7839 key.to_vec(),
7840 KeyUsages::DECRYPT,
7841 &resources,
7842 )
7843 .expect("decrypt key imports");
7844 let resolver = keys
7845 .decryption_resolver("decrypt", &crate::policy::DecryptionPolicy::default())
7846 .expect("decrypt use is allowed");
7847 let content = DecryptContext::new(resolver.as_ref())
7848 .decrypt(&encrypted.encrypted_data_xml)
7849 .expect("inventory key decrypts");
7850 assert!(
7851 matches!(content, DecryptedContent::Bytes(bytes) if bytes == b"inventory decrypt payload")
7852 );
7853
7854 let mut restricted = KeyInventory::default();
7855 restricted
7856 .add_symmetric(
7857 "encrypt-only".into(),
7858 SymmetricKeyKind::Aes,
7859 key.to_vec(),
7860 KeyUsages::ENCRYPT,
7861 &resources,
7862 )
7863 .expect("encrypt-only key imports");
7864 assert!(
7865 restricted
7866 .decryption_resolver("encrypt-only", &crate::policy::DecryptionPolicy::default())
7867 .is_err()
7868 );
7869 }
7870
7871 #[cfg(feature = "xmlenc")]
7872 #[test]
7873 fn inventory_direct_aes_rechecks_execution_policy() {
7874 use crate::xmlenc::{DataEncryptionAlgorithm, KeyCandidateBudget, XmlEncError};
7877 let mut inventory = KeyInventory::default();
7878 inventory
7879 .add_symmetric(
7880 "aes".into(),
7881 SymmetricKeyKind::Aes,
7882 vec![1; 16],
7883 KeyUsages::DECRYPT,
7884 &ResourcePolicy::default(),
7885 )
7886 .expect("AES imports");
7887 let resolver = inventory
7888 .decryption_resolver("aes", &crate::policy::DecryptionPolicy::default())
7889 .expect("AES resolver");
7890 let mut policy = crate::policy::DecryptionPolicy::default();
7891 policy.resources.max_external_resource_bytes = 15;
7892 let mut budget = KeyCandidateBudget::with_limit(1);
7893 assert!(matches!(
7894 resolver.resolve_key_candidates_with_policy(
7895 &crate::provider::RustCryptoProvider,
7896 DataEncryptionAlgorithm::Aes128Gcm,
7897 None,
7898 &policy,
7899 &mut budget
7900 ),
7901 Err(XmlEncError::Policy(_))
7902 ));
7903 assert_eq!(budget.remaining(), 1);
7904 policy.resources.max_external_resource_bytes = 16;
7905 assert_eq!(
7906 resolver
7907 .resolve_key_candidates_with_policy(
7908 &crate::provider::RustCryptoProvider,
7909 DataEncryptionAlgorithm::Aes128Gcm,
7910 None,
7911 &policy,
7912 &mut budget
7913 )
7914 .expect("exact execution allowance"),
7915 vec![vec![1; 16]]
7916 );
7917 assert_eq!(budget.remaining(), 0);
7918 }
7919
7920 #[cfg(feature = "xmlenc")]
7921 #[test]
7922 fn inventory_direct_aes_does_not_consume_recipient_candidates() {
7923 use crate::xmlenc::{
7926 CipherData, DataEncryptionAlgorithm, EncryptedKey, EncryptionMethod,
7927 KeyCandidateBudget, KeyTransportAlgorithm, XmlEncError,
7928 };
7929 let mut keys = KeyInventory::default();
7930 keys.add_symmetric(
7931 "direct".into(),
7932 SymmetricKeyKind::Aes,
7933 vec![1; 16],
7934 KeyUsages::DECRYPT,
7935 &ResourcePolicy::default(),
7936 )
7937 .expect("AES imports");
7938 let resolver = keys
7939 .decryption_resolver("direct", &crate::policy::DecryptionPolicy::default())
7940 .expect("AES resolver");
7941 let recipient = EncryptedKey {
7942 sources: Default::default(),
7943 id: None,
7944 recipient: None,
7945 key_name: None,
7946 encryption_method: EncryptionMethod {
7947 algorithm: KeyTransportAlgorithm::RsaOaep11.uri().into(),
7948 key_size_bits: None,
7949 oaep_digest: None,
7950 mgf_algorithm: None,
7951 oaep_params: None,
7952 },
7953 cipher_data: CipherData::Value {
7954 value: String::new(),
7955 },
7956 reference_list: None,
7957 carried_key_name: None,
7958 };
7959 let mut budget = KeyCandidateBudget::with_limit(1);
7960 let provider = crate::provider::RustCryptoProvider;
7961 for _ in 0..64 {
7962 assert!(matches!(
7963 resolver.resolve_key_candidates(
7964 &provider,
7965 DataEncryptionAlgorithm::Aes128Gcm,
7966 Some(&recipient),
7967 &mut budget
7968 ),
7969 Err(XmlEncError::KeyNotFound)
7970 ));
7971 assert_eq!(budget.remaining(), 1);
7972 }
7973 assert_eq!(
7974 resolver
7975 .resolve_key_candidates(
7976 &provider,
7977 DataEncryptionAlgorithm::Aes128Gcm,
7978 None,
7979 &mut budget
7980 )
7981 .expect("one direct candidate"),
7982 vec![vec![1; 16]]
7983 );
7984 assert_eq!(budget.remaining(), 0);
7985 }
7986
7987 #[cfg(feature = "xmlenc")]
7988 #[test]
7989 fn decryption_selection_checks_operation_limits_before_material_use() {
7990 let resources = ResourcePolicy::default();
7993 let mut keys = KeyInventory::default();
7994 keys.add_symmetric(
7995 "aes".into(),
7996 SymmetricKeyKind::Aes,
7997 vec![0x31; 16],
7998 KeyUsages::DECRYPT,
7999 &resources,
8000 )
8001 .expect("AES key imports");
8002 keys.add_private_pem(
8003 "rsa".into(),
8004 include_bytes!("../tests/fixtures/keys/rsa/rsa-2048-key.pem"),
8005 None,
8006 KeyUsages::DECRYPT,
8007 &resources,
8008 )
8009 .expect("RSA key imports");
8010 let mut policy = crate::policy::DecryptionPolicy::default();
8011 policy.resources.max_external_resource_bytes = 8;
8012 policy.resources.max_external_resource_total_bytes = 8;
8013 assert!(keys.decryption_resolver("aes", &policy).is_err());
8014 assert!(keys.decryption_resolver("rsa", &policy).is_err());
8015 }
8016}