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