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