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