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