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