1#![forbid(unsafe_code)]
2
3use hpke::{
4 aead::{AesGcm256, ChaCha20Poly1305},
5 kdf::HkdfSha256,
6 kem::{DhP256HkdfSha256, X25519HkdfSha256},
7 Deserializable, Kem as HpkeKem, OpModeR, OpModeS, Serializable,
8};
9use openssl::{bn::BigNumContext, ec::PointConversionForm, nid::Nid, pkey::PKey, x509::X509};
10use rand_core::{OsRng, UnwrapErr};
11use sha2::{Digest, Sha256};
12use tzap_core::format::{FORMAT_VERSION, VOLUME_FORMAT_REV_44};
13use tzap_core::wire::RecipientRecordV1;
14use x509_parser::parse_x509_certificate;
15
16pub const KEYWRAP_PROFILE_ID: u16 = 0x0001;
18
19pub const KEYWRAP_RECIPIENT_IDENTITY_TYPE_BYTES: u16 = 2;
21
22const KEYWRAP_PROFILE_PAYLOAD_HEADER_LEN: usize = 64;
23pub const KEYWRAP_PAYLOAD_VERSION: u16 = 1;
24const KEY_WRAP_CONTEXT_DOMAIN: &[u8] = b"tzap-keywrap-x509-hpke-v1-context\0";
25const HPKE_INFO_DOMAIN: &[u8] = b"tzap-x509-hpke-recipient-v1\0";
26const HPKE_AAD_DOMAIN: &[u8] = b"tzap-keywrap-master-key-v44\0";
27
28const X25519_KEM_ID: u16 = 0x0020;
29const P256_KEM_ID: u16 = 0x0010;
30const HKDF_SHA256_KDF_ID: u16 = 0x0001;
31const CHACHA20POLY1305_AEAD_ID: u16 = 0x0003;
32const AES256GCM_AEAD_ID: u16 = 0x0002;
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35struct HpkeSuite {
36 kem_id: u16,
37 kdf_id: u16,
38 aead_id: u16,
39 enc_length: usize,
40 ciphertext_length: usize,
41}
42
43impl HpkeSuite {
44 fn for_profile(hpke_kem_id: u16, hpke_kdf_id: u16, hpke_aead_id: u16) -> Option<Self> {
45 let suites = [
46 Self {
47 kem_id: X25519_KEM_ID,
48 kdf_id: HKDF_SHA256_KDF_ID,
49 aead_id: CHACHA20POLY1305_AEAD_ID,
50 enc_length: 32,
51 ciphertext_length: 48,
52 },
53 Self {
54 kem_id: P256_KEM_ID,
55 kdf_id: HKDF_SHA256_KDF_ID,
56 aead_id: AES256GCM_AEAD_ID,
57 enc_length: 65,
58 ciphertext_length: 48,
59 },
60 ];
61
62 suites.into_iter().find(|suite| {
63 suite.kem_id == hpke_kem_id
64 && suite.kdf_id == hpke_kdf_id
65 && suite.aead_id == hpke_aead_id
66 })
67 }
68}
69
70#[derive(Debug, Clone, Copy, PartialEq, Eq)]
71pub enum KeyWrapSuite {
72 X25519HkdfSha256ChaCha20Poly1305,
73 P256HkdfSha256Aes256Gcm,
74}
75
76impl KeyWrapSuite {
77 fn hpke_suite(self) -> HpkeSuite {
78 match self {
79 Self::X25519HkdfSha256ChaCha20Poly1305 => HpkeSuite {
80 kem_id: X25519_KEM_ID,
81 kdf_id: HKDF_SHA256_KDF_ID,
82 aead_id: CHACHA20POLY1305_AEAD_ID,
83 enc_length: 32,
84 ciphertext_length: 48,
85 },
86 Self::P256HkdfSha256Aes256Gcm => HpkeSuite {
87 kem_id: P256_KEM_ID,
88 kdf_id: HKDF_SHA256_KDF_ID,
89 aead_id: AES256GCM_AEAD_ID,
90 enc_length: 65,
91 ciphertext_length: 48,
92 },
93 }
94 }
95}
96
97#[derive(Debug, Clone, PartialEq, Eq)]
98pub struct ArchiveIdentity {
99 pub archive_uuid: [u8; 16],
100 pub session_id: [u8; 16],
101 pub format_version: u16,
102 pub volume_format_rev: u16,
103}
104
105impl Default for ArchiveIdentity {
106 fn default() -> Self {
107 Self {
108 archive_uuid: [0u8; 16],
109 session_id: [0u8; 16],
110 format_version: FORMAT_VERSION,
111 volume_format_rev: VOLUME_FORMAT_REV_44,
112 }
113 }
114}
115
116#[derive(Debug, Clone, PartialEq, Eq)]
117pub struct RecipientRecordMetadata {
118 pub profile_id: u16,
119 pub recipient_identity_type: u16,
120 pub recipient_identity_digest: [u8; 32],
121}
122
123#[derive(Debug, Clone, PartialEq, Eq)]
124pub struct RecipientRecordInput {
125 pub archive_identity: ArchiveIdentity,
126 pub metadata: RecipientRecordMetadata,
127 pub recipient_identity_bytes: Vec<u8>,
128 pub profile_payload_bytes: Vec<u8>,
129}
130
131pub trait PrivateKeyLookup {
133 fn lookup_private_key(
134 &self,
135 archive_identity: &ArchiveIdentity,
136 metadata: &RecipientRecordMetadata,
137 recipient_identity_bytes: &[u8],
138 ) -> Option<Vec<u8>>;
139
140 fn is_recipient_certificate_accepted(
141 &self,
142 _archive_identity: &ArchiveIdentity,
143 _metadata: &RecipientRecordMetadata,
144 _recipient_identity_bytes: &[u8],
145 _recipient_spki_digest: &[u8; 32],
146 ) -> bool {
147 true
148 }
149}
150
151#[derive(Debug, Clone, PartialEq, Eq)]
152pub enum KeyWrapOutcome {
153 UnsupportedProfileId,
154 UnsupportedArchiveIdentity,
155 UnsupportedRecipientIdentity,
156 UnsupportedSuite,
157 CertificatePolicyRejected,
158 InvalidRecord,
159 NoMatchingPrivateKey,
160 UnwrappedCandidateMasterKey {
161 master_key: [u8; 32],
162 recipient_spki_digest: [u8; 32],
163 },
164}
165
166#[derive(Debug, Clone)]
167struct ParsedRecipientIdentity {
168 recipient_identity_digest: [u8; 32],
169 recipient_spki_digest: [u8; 32],
170}
171
172#[derive(Debug, Clone)]
173struct ParsedProfilePayload {
174 suite: HpkeSuite,
175 enc: Vec<u8>,
176 ciphertext: Vec<u8>,
177 key_wrap_context_digest: [u8; 32],
178}
179
180pub fn dispatch_key_wrap_record<L>(
190 input: RecipientRecordInput,
191 private_key_lookup: &L,
192) -> KeyWrapOutcome
193where
194 L: PrivateKeyLookup + ?Sized,
195{
196 if input.archive_identity.format_version != FORMAT_VERSION
197 || input.archive_identity.volume_format_rev != VOLUME_FORMAT_REV_44
198 {
199 return KeyWrapOutcome::UnsupportedArchiveIdentity;
200 }
201
202 if input.metadata.profile_id != KEYWRAP_PROFILE_ID {
203 return KeyWrapOutcome::UnsupportedProfileId;
204 }
205
206 if input.metadata.recipient_identity_type != KEYWRAP_RECIPIENT_IDENTITY_TYPE_BYTES {
207 return KeyWrapOutcome::UnsupportedRecipientIdentity;
208 }
209
210 let parsed_identity = match parse_x509_recipient_identity(&input.recipient_identity_bytes) {
211 Ok(identity) => identity,
212 Err(outcome) => return outcome,
213 };
214
215 if parsed_identity.recipient_identity_digest != input.metadata.recipient_identity_digest {
216 return KeyWrapOutcome::InvalidRecord;
217 }
218
219 let payload = match parse_profile_payload(&input.profile_payload_bytes) {
220 Ok(payload) => payload,
221 Err(outcome) => return outcome,
222 };
223
224 if let Err(outcome) =
225 validate_recipient_public_key_matches_suite(&input.recipient_identity_bytes, payload.suite)
226 {
227 return outcome;
228 }
229
230 let private_key = match private_key_lookup.lookup_private_key(
231 &input.archive_identity,
232 &input.metadata,
233 &input.recipient_identity_bytes,
234 ) {
235 Some(private_key) => private_key,
236 None => return KeyWrapOutcome::NoMatchingPrivateKey,
237 };
238
239 if !private_key_lookup.is_recipient_certificate_accepted(
240 &input.archive_identity,
241 &input.metadata,
242 &input.recipient_identity_bytes,
243 &parsed_identity.recipient_spki_digest,
244 ) {
245 return KeyWrapOutcome::CertificatePolicyRejected;
246 }
247
248 let expected_context_digest = compute_key_wrap_context_digest(
249 &input.archive_identity,
250 &input.metadata,
251 &parsed_identity,
252 payload.suite,
253 );
254 if payload.key_wrap_context_digest != expected_context_digest {
255 return KeyWrapOutcome::InvalidRecord;
256 }
257
258 match hpke_open_candidate_master_key(&payload, &private_key) {
259 Ok(master_key) => KeyWrapOutcome::UnwrappedCandidateMasterKey {
260 master_key,
261 recipient_spki_digest: parsed_identity.recipient_spki_digest,
262 },
263 Err(outcome) => outcome,
264 }
265}
266
267pub fn wrap_master_key_for_recipient(
268 archive_identity: ArchiveIdentity,
269 recipient_certificate_der: &[u8],
270 master_key: &[u8; 32],
271 suite: KeyWrapSuite,
272) -> Result<RecipientRecordV1, KeyWrapOutcome> {
273 if archive_identity.format_version != FORMAT_VERSION
274 || archive_identity.volume_format_rev != VOLUME_FORMAT_REV_44
275 {
276 return Err(KeyWrapOutcome::UnsupportedArchiveIdentity);
277 }
278
279 let identity = parse_x509_recipient_identity(recipient_certificate_der)?;
280 let metadata = RecipientRecordMetadata {
281 profile_id: KEYWRAP_PROFILE_ID,
282 recipient_identity_type: KEYWRAP_RECIPIENT_IDENTITY_TYPE_BYTES,
283 recipient_identity_digest: identity.recipient_identity_digest,
284 };
285 let profile_payload_bytes = hpke_seal_master_key(
286 recipient_certificate_der,
287 &archive_identity,
288 &metadata,
289 &identity,
290 master_key,
291 suite.hpke_suite(),
292 )?;
293
294 Ok(RecipientRecordV1 {
295 record_length: 0,
296 profile_id: KEYWRAP_PROFILE_ID,
297 recipient_identity_type: KEYWRAP_RECIPIENT_IDENTITY_TYPE_BYTES,
298 flags: 0,
299 recipient_identity_length: recipient_certificate_der.len() as u32,
300 profile_payload_length: profile_payload_bytes.len() as u32,
301 recipient_identity_digest: identity.recipient_identity_digest,
302 recipient_identity_bytes: recipient_certificate_der.to_vec(),
303 profile_payload_bytes,
304 })
305}
306
307fn hpke_seal_master_key(
308 recipient_certificate_der: &[u8],
309 archive_identity: &ArchiveIdentity,
310 metadata: &RecipientRecordMetadata,
311 identity: &ParsedRecipientIdentity,
312 master_key: &[u8; 32],
313 suite: HpkeSuite,
314) -> Result<Vec<u8>, KeyWrapOutcome> {
315 let key_wrap_context_digest =
316 compute_key_wrap_context_digest(archive_identity, metadata, identity, suite);
317 let info = hpke_info(&key_wrap_context_digest);
318 let aad = hpke_aad(&key_wrap_context_digest);
319 let (enc, ciphertext) = match (suite.kem_id, suite.aead_id) {
320 (X25519_KEM_ID, CHACHA20POLY1305_AEAD_ID) => {
321 let public_key = x25519_public_key_from_certificate(recipient_certificate_der)
322 .map_err(|_| KeyWrapOutcome::UnsupportedSuite)?;
323 let mut rng = UnwrapErr(OsRng);
324 let (enc, ciphertext) =
325 hpke::single_shot_seal::<ChaCha20Poly1305, HkdfSha256, X25519HkdfSha256, _>(
326 &OpModeS::Base,
327 &public_key,
328 &info,
329 master_key,
330 &aad,
331 &mut rng,
332 )
333 .map_err(|_| KeyWrapOutcome::InvalidRecord)?;
334 (enc.to_bytes().to_vec(), ciphertext)
335 }
336 (P256_KEM_ID, AES256GCM_AEAD_ID) => {
337 let public_key = p256_public_key_from_certificate(recipient_certificate_der)
338 .map_err(|_| KeyWrapOutcome::UnsupportedSuite)?;
339 let mut rng = UnwrapErr(OsRng);
340 let (enc, ciphertext) =
341 hpke::single_shot_seal::<AesGcm256, HkdfSha256, DhP256HkdfSha256, _>(
342 &OpModeS::Base,
343 &public_key,
344 &info,
345 master_key,
346 &aad,
347 &mut rng,
348 )
349 .map_err(|_| KeyWrapOutcome::InvalidRecord)?;
350 (enc.to_bytes().to_vec(), ciphertext)
351 }
352 _ => return Err(KeyWrapOutcome::UnsupportedSuite),
353 };
354
355 if enc.len() != suite.enc_length || ciphertext.len() != suite.ciphertext_length {
356 return Err(KeyWrapOutcome::InvalidRecord);
357 }
358
359 Ok(build_profile_payload(
360 suite,
361 &key_wrap_context_digest,
362 &enc,
363 &ciphertext,
364 ))
365}
366
367fn hpke_open_candidate_master_key(
368 payload: &ParsedProfilePayload,
369 private_key_bytes: &[u8],
370) -> Result<[u8; 32], KeyWrapOutcome> {
371 let info = hpke_info(&payload.key_wrap_context_digest);
372 let aad = hpke_aad(&payload.key_wrap_context_digest);
373 let plaintext = match (payload.suite.kem_id, payload.suite.aead_id) {
374 (X25519_KEM_ID, CHACHA20POLY1305_AEAD_ID) => {
375 let private_key = x25519_private_key_from_bytes(private_key_bytes)?;
376 let encapped_key = <X25519HkdfSha256 as HpkeKem>::EncappedKey::from_bytes(&payload.enc)
377 .map_err(|_| KeyWrapOutcome::InvalidRecord)?;
378 hpke::single_shot_open::<ChaCha20Poly1305, HkdfSha256, X25519HkdfSha256>(
379 &OpModeR::Base,
380 &private_key,
381 &encapped_key,
382 &info,
383 &payload.ciphertext,
384 &aad,
385 )
386 .map_err(|_| KeyWrapOutcome::InvalidRecord)?
387 }
388 (P256_KEM_ID, AES256GCM_AEAD_ID) => {
389 let private_key = p256_private_key_from_bytes(private_key_bytes)?;
390 let encapped_key = <DhP256HkdfSha256 as HpkeKem>::EncappedKey::from_bytes(&payload.enc)
391 .map_err(|_| KeyWrapOutcome::InvalidRecord)?;
392 hpke::single_shot_open::<AesGcm256, HkdfSha256, DhP256HkdfSha256>(
393 &OpModeR::Base,
394 &private_key,
395 &encapped_key,
396 &info,
397 &payload.ciphertext,
398 &aad,
399 )
400 .map_err(|_| KeyWrapOutcome::InvalidRecord)?
401 }
402 _ => return Err(KeyWrapOutcome::UnsupportedSuite),
403 };
404
405 if plaintext.len() != 32 {
406 return Err(KeyWrapOutcome::InvalidRecord);
407 }
408 let mut master_key = [0u8; 32];
409 master_key.copy_from_slice(&plaintext);
410 Ok(master_key)
411}
412
413fn build_profile_payload(
414 suite: HpkeSuite,
415 key_wrap_context_digest: &[u8; 32],
416 enc: &[u8],
417 ciphertext: &[u8],
418) -> Vec<u8> {
419 let mut payload = vec![0u8; KEYWRAP_PROFILE_PAYLOAD_HEADER_LEN + enc.len() + ciphertext.len()];
420 payload[0..2].copy_from_slice(&KEYWRAP_PAYLOAD_VERSION.to_le_bytes());
421 payload[2..4].copy_from_slice(&suite.kem_id.to_le_bytes());
422 payload[4..6].copy_from_slice(&suite.kdf_id.to_le_bytes());
423 payload[6..8].copy_from_slice(&suite.aead_id.to_le_bytes());
424 payload[8..10].copy_from_slice(&(enc.len() as u16).to_le_bytes());
425 payload[10..12].copy_from_slice(&(ciphertext.len() as u16).to_le_bytes());
426 payload[16..48].copy_from_slice(key_wrap_context_digest);
427 payload[KEYWRAP_PROFILE_PAYLOAD_HEADER_LEN..KEYWRAP_PROFILE_PAYLOAD_HEADER_LEN + enc.len()]
428 .copy_from_slice(enc);
429 payload[KEYWRAP_PROFILE_PAYLOAD_HEADER_LEN + enc.len()..].copy_from_slice(ciphertext);
430 payload
431}
432
433fn parse_x509_recipient_identity(
434 recipient_identity_bytes: &[u8],
435) -> Result<ParsedRecipientIdentity, KeyWrapOutcome> {
436 let (remaining, parsed_cert) = parse_x509_certificate(recipient_identity_bytes)
437 .map_err(|_error| KeyWrapOutcome::InvalidRecord)?;
438
439 if !remaining.is_empty() {
440 return Err(KeyWrapOutcome::InvalidRecord);
441 }
442
443 match parsed_cert.key_usage() {
444 Ok(Some(key_usage)) => {
445 if !key_usage.value.key_agreement() {
446 return Err(KeyWrapOutcome::InvalidRecord);
447 }
448 }
449 Ok(None) => {}
450 Err(_) => {
451 return Err(KeyWrapOutcome::InvalidRecord);
452 }
453 }
454
455 let openssl_cert =
456 X509::from_der(recipient_identity_bytes).map_err(|_| KeyWrapOutcome::InvalidRecord)?;
457 let openssl_public_key: PKey<_> = openssl_cert
458 .public_key()
459 .map_err(|_| KeyWrapOutcome::InvalidRecord)?;
460 let spki_der = openssl_public_key
461 .public_key_to_der()
462 .map_err(|_| KeyWrapOutcome::InvalidRecord)?;
463 let recipient_identity_digest = sha256_digest(recipient_identity_bytes);
464 let recipient_spki_digest = sha256_digest(&spki_der);
465
466 Ok(ParsedRecipientIdentity {
467 recipient_identity_digest,
468 recipient_spki_digest,
469 })
470}
471
472fn parse_profile_payload(
473 profile_payload_bytes: &[u8],
474) -> Result<ParsedProfilePayload, KeyWrapOutcome> {
475 if profile_payload_bytes.len() < KEYWRAP_PROFILE_PAYLOAD_HEADER_LEN {
476 return Err(KeyWrapOutcome::InvalidRecord);
477 }
478
479 let payload_version = read_u16(profile_payload_bytes, 0)?;
480 let hpke_kem_id = read_u16(profile_payload_bytes, 2)?;
481 let hpke_kdf_id = read_u16(profile_payload_bytes, 4)?;
482 let hpke_aead_id = read_u16(profile_payload_bytes, 6)?;
483 let enc_length = usize::from(read_u16(profile_payload_bytes, 8)?);
484 let ciphertext_length = usize::from(read_u16(profile_payload_bytes, 10)?);
485 let flags = read_u32(profile_payload_bytes, 12)?;
486 let key_wrap_context_digest = read_array_32(profile_payload_bytes, 16)?;
487
488 if flags != 0 {
489 return Err(KeyWrapOutcome::InvalidRecord);
490 }
491
492 if !profile_payload_bytes[48..KEYWRAP_PROFILE_PAYLOAD_HEADER_LEN]
493 .iter()
494 .all(|value| *value == 0)
495 {
496 return Err(KeyWrapOutcome::InvalidRecord);
497 }
498
499 if payload_version != KEYWRAP_PAYLOAD_VERSION {
500 return Err(KeyWrapOutcome::InvalidRecord);
501 }
502
503 let suite = HpkeSuite::for_profile(hpke_kem_id, hpke_kdf_id, hpke_aead_id)
504 .ok_or(KeyWrapOutcome::UnsupportedSuite)?;
505
506 if enc_length != suite.enc_length || ciphertext_length != suite.ciphertext_length {
507 return Err(KeyWrapOutcome::InvalidRecord);
508 }
509
510 let expected_length = KEYWRAP_PROFILE_PAYLOAD_HEADER_LEN
511 .checked_add(enc_length)
512 .and_then(|value| value.checked_add(ciphertext_length))
513 .ok_or(KeyWrapOutcome::InvalidRecord)?;
514
515 if profile_payload_bytes.len() != expected_length {
516 return Err(KeyWrapOutcome::InvalidRecord);
517 }
518
519 let enc_start = KEYWRAP_PROFILE_PAYLOAD_HEADER_LEN;
520 let ciphertext_start = enc_start + enc_length;
521 let enc = profile_payload_bytes[enc_start..ciphertext_start].to_vec();
522 let ciphertext =
523 profile_payload_bytes[ciphertext_start..ciphertext_start + ciphertext_length].to_vec();
524
525 Ok(ParsedProfilePayload {
526 suite,
527 enc,
528 ciphertext,
529 key_wrap_context_digest,
530 })
531}
532
533fn validate_recipient_public_key_matches_suite(
534 recipient_certificate_der: &[u8],
535 suite: HpkeSuite,
536) -> Result<(), KeyWrapOutcome> {
537 match suite.kem_id {
538 X25519_KEM_ID => x25519_public_key_from_certificate(recipient_certificate_der)
539 .map(|_| ())
540 .map_err(|_| KeyWrapOutcome::UnsupportedSuite),
541 P256_KEM_ID => p256_public_key_from_certificate(recipient_certificate_der)
542 .map(|_| ())
543 .map_err(|_| KeyWrapOutcome::UnsupportedSuite),
544 _ => Err(KeyWrapOutcome::UnsupportedSuite),
545 }
546}
547
548fn compute_key_wrap_context_digest(
549 archive_identity: &ArchiveIdentity,
550 metadata: &RecipientRecordMetadata,
551 identity: &ParsedRecipientIdentity,
552 suite: HpkeSuite,
553) -> [u8; 32] {
554 let mut hasher = Sha256::new();
555 hasher.update(KEY_WRAP_CONTEXT_DOMAIN);
556 hasher.update(archive_identity.archive_uuid);
557 hasher.update(archive_identity.session_id);
558 hasher.update(archive_identity.format_version.to_le_bytes());
559 hasher.update(archive_identity.volume_format_rev.to_le_bytes());
560 hasher.update(metadata.profile_id.to_le_bytes());
561 hasher.update(metadata.recipient_identity_type.to_le_bytes());
562 hasher.update(identity.recipient_identity_digest);
563 hasher.update(identity.recipient_spki_digest);
564 hasher.update(suite.kem_id.to_le_bytes());
565 hasher.update(suite.kdf_id.to_le_bytes());
566 hasher.update(suite.aead_id.to_le_bytes());
567
568 let digest = hasher.finalize();
569 let mut out = [0u8; 32];
570 out.copy_from_slice(&digest);
571 out
572}
573
574fn hpke_info(key_wrap_context_digest: &[u8; 32]) -> Vec<u8> {
575 let mut info = Vec::with_capacity(HPKE_INFO_DOMAIN.len() + key_wrap_context_digest.len());
576 info.extend_from_slice(HPKE_INFO_DOMAIN);
577 info.extend_from_slice(key_wrap_context_digest);
578 info
579}
580
581fn hpke_aad(key_wrap_context_digest: &[u8; 32]) -> Vec<u8> {
582 let mut aad = Vec::with_capacity(HPKE_AAD_DOMAIN.len() + key_wrap_context_digest.len());
583 aad.extend_from_slice(HPKE_AAD_DOMAIN);
584 aad.extend_from_slice(key_wrap_context_digest);
585 aad
586}
587
588fn x25519_public_key_from_certificate(
589 recipient_certificate_der: &[u8],
590) -> Result<<X25519HkdfSha256 as HpkeKem>::PublicKey, KeyWrapOutcome> {
591 let certificate =
592 X509::from_der(recipient_certificate_der).map_err(|_| KeyWrapOutcome::InvalidRecord)?;
593 let public_key = certificate
594 .public_key()
595 .map_err(|_| KeyWrapOutcome::InvalidRecord)?;
596 let raw = public_key
597 .raw_public_key()
598 .map_err(|_| KeyWrapOutcome::InvalidRecord)?;
599 if raw.len() != 32 {
600 return Err(KeyWrapOutcome::InvalidRecord);
601 }
602 <X25519HkdfSha256 as HpkeKem>::PublicKey::from_bytes(&raw)
603 .map_err(|_| KeyWrapOutcome::InvalidRecord)
604}
605
606fn p256_public_key_from_certificate(
607 recipient_certificate_der: &[u8],
608) -> Result<<DhP256HkdfSha256 as HpkeKem>::PublicKey, KeyWrapOutcome> {
609 let certificate =
610 X509::from_der(recipient_certificate_der).map_err(|_| KeyWrapOutcome::InvalidRecord)?;
611 let public_key = certificate
612 .public_key()
613 .map_err(|_| KeyWrapOutcome::InvalidRecord)?;
614 let ec_key = public_key
615 .ec_key()
616 .map_err(|_| KeyWrapOutcome::InvalidRecord)?;
617 if ec_key.group().curve_name() != Some(Nid::X9_62_PRIME256V1) {
618 return Err(KeyWrapOutcome::InvalidRecord);
619 }
620 let mut context = BigNumContext::new().map_err(|_| KeyWrapOutcome::InvalidRecord)?;
621 let encoded = ec_key
622 .public_key()
623 .to_bytes(
624 ec_key.group(),
625 PointConversionForm::UNCOMPRESSED,
626 &mut context,
627 )
628 .map_err(|_| KeyWrapOutcome::InvalidRecord)?;
629 if encoded.len() != 65 {
630 return Err(KeyWrapOutcome::InvalidRecord);
631 }
632 <DhP256HkdfSha256 as HpkeKem>::PublicKey::from_bytes(&encoded)
633 .map_err(|_| KeyWrapOutcome::InvalidRecord)
634}
635
636fn x25519_private_key_from_bytes(
637 private_key_bytes: &[u8],
638) -> Result<<X25519HkdfSha256 as HpkeKem>::PrivateKey, KeyWrapOutcome> {
639 let raw = if private_key_bytes.len() == 32 {
640 private_key_bytes.to_vec()
641 } else {
642 let private_key = PKey::private_key_from_der(private_key_bytes)
643 .map_err(|_| KeyWrapOutcome::InvalidRecord)?;
644 private_key
645 .raw_private_key()
646 .map_err(|_| KeyWrapOutcome::InvalidRecord)?
647 };
648 if raw.len() != 32 {
649 return Err(KeyWrapOutcome::InvalidRecord);
650 }
651 <X25519HkdfSha256 as HpkeKem>::PrivateKey::from_bytes(&raw)
652 .map_err(|_| KeyWrapOutcome::InvalidRecord)
653}
654
655fn p256_private_key_from_bytes(
656 private_key_bytes: &[u8],
657) -> Result<<DhP256HkdfSha256 as HpkeKem>::PrivateKey, KeyWrapOutcome> {
658 let raw = if private_key_bytes.len() == 32 {
659 private_key_bytes.to_vec()
660 } else {
661 let private_key = PKey::private_key_from_der(private_key_bytes)
662 .map_err(|_| KeyWrapOutcome::InvalidRecord)?;
663 let ec_key = private_key
664 .ec_key()
665 .map_err(|_| KeyWrapOutcome::InvalidRecord)?;
666 ec_key
667 .private_key()
668 .to_vec_padded(32)
669 .map_err(|_| KeyWrapOutcome::InvalidRecord)?
670 };
671 if raw.len() != 32 {
672 return Err(KeyWrapOutcome::InvalidRecord);
673 }
674 <DhP256HkdfSha256 as HpkeKem>::PrivateKey::from_bytes(&raw)
675 .map_err(|_| KeyWrapOutcome::InvalidRecord)
676}
677
678fn sha256_digest(bytes: &[u8]) -> [u8; 32] {
679 let digest = Sha256::digest(bytes);
680 let mut out = [0u8; 32];
681 out.copy_from_slice(&digest);
682 out
683}
684
685fn read_u16(bytes: &[u8], offset: usize) -> Result<u16, KeyWrapOutcome> {
686 let value = bytes
687 .get(offset..offset + 2)
688 .ok_or(KeyWrapOutcome::InvalidRecord)?;
689 Ok(u16::from_le_bytes([value[0], value[1]]))
690}
691
692fn read_u32(bytes: &[u8], offset: usize) -> Result<u32, KeyWrapOutcome> {
693 let value = bytes
694 .get(offset..offset + 4)
695 .ok_or(KeyWrapOutcome::InvalidRecord)?;
696 Ok(u32::from_le_bytes([value[0], value[1], value[2], value[3]]))
697}
698
699fn read_array_32(bytes: &[u8], offset: usize) -> Result<[u8; 32], KeyWrapOutcome> {
700 let value = bytes
701 .get(offset..offset + 32)
702 .ok_or(KeyWrapOutcome::InvalidRecord)?;
703 let mut out = [0u8; 32];
704 out.copy_from_slice(value);
705 Ok(out)
706}
707
708#[cfg(test)]
709mod tests {
710 use super::*;
711
712 use openssl::{
713 asn1::{Asn1Object, Asn1OctetString, Asn1Time},
714 bn::{BigNum, MsbOption},
715 ec::{EcGroup, EcKey},
716 hash::MessageDigest,
717 nid::Nid,
718 pkey::{PKey, Private},
719 rand::rand_bytes,
720 rsa::Rsa,
721 x509::{
722 extension::BasicConstraints, extension::KeyUsage, X509Extension, X509NameBuilder, X509,
723 },
724 };
725
726 #[derive(Debug)]
727 struct NoMatchLookup;
728
729 impl PrivateKeyLookup for NoMatchLookup {
730 fn lookup_private_key(
731 &self,
732 _: &ArchiveIdentity,
733 _: &RecipientRecordMetadata,
734 _: &[u8],
735 ) -> Option<Vec<u8>> {
736 None
737 }
738 }
739
740 #[derive(Debug)]
741 struct StaticLookup {
742 private_key: Vec<u8>,
743 }
744
745 impl PrivateKeyLookup for StaticLookup {
746 fn lookup_private_key(
747 &self,
748 _: &ArchiveIdentity,
749 _: &RecipientRecordMetadata,
750 _: &[u8],
751 ) -> Option<Vec<u8>> {
752 Some(self.private_key.clone())
753 }
754 }
755
756 #[derive(Debug)]
757 struct RejectLookup;
758
759 impl PrivateKeyLookup for RejectLookup {
760 fn lookup_private_key(
761 &self,
762 _: &ArchiveIdentity,
763 _: &RecipientRecordMetadata,
764 _: &[u8],
765 ) -> Option<Vec<u8>> {
766 Some(vec![0xAA; 32])
767 }
768
769 fn is_recipient_certificate_accepted(
770 &self,
771 _: &ArchiveIdentity,
772 _: &RecipientRecordMetadata,
773 _: &[u8],
774 _: &[u8; 32],
775 ) -> bool {
776 false
777 }
778 }
779
780 #[derive(Debug)]
781 struct MatchingLookup {
782 matches: Vec<(Vec<u8>, Vec<u8>)>,
783 }
784
785 impl PrivateKeyLookup for MatchingLookup {
786 fn lookup_private_key(
787 &self,
788 _: &ArchiveIdentity,
789 _: &RecipientRecordMetadata,
790 recipient_identity_bytes: &[u8],
791 ) -> Option<Vec<u8>> {
792 self.matches
793 .iter()
794 .find(|(identity, _)| identity.as_slice() == recipient_identity_bytes)
795 .map(|(_, private_key)| private_key.clone())
796 }
797 }
798
799 fn archive_identity() -> ArchiveIdentity {
800 let mut archive_uuid = [0u8; 16];
801 let mut session_id = [0u8; 16];
802 rand_bytes(&mut archive_uuid).unwrap();
803 rand_bytes(&mut session_id).unwrap();
804
805 ArchiveIdentity {
806 archive_uuid,
807 session_id,
808 format_version: FORMAT_VERSION,
809 volume_format_rev: VOLUME_FORMAT_REV_44,
810 }
811 }
812
813 fn test_certificate_der() -> Vec<u8> {
814 make_self_signed_certificate(None)
815 }
816
817 fn test_certificate_der_with_bad_key_usage() -> Vec<u8> {
818 let key_usage = KeyUsage::new()
819 .critical()
820 .digital_signature()
821 .build()
822 .unwrap();
823 make_self_signed_certificate(Some(key_usage)).to_vec()
824 }
825
826 fn test_certificate_der_with_malformed_key_usage() -> Vec<u8> {
827 let recipient_key = PKey::generate_x25519().unwrap();
828 let key_usage = X509Extension::new_from_der(
829 &Asn1Object::from_str("2.5.29.15").unwrap(),
830 true,
831 &Asn1OctetString::new_from_bytes(b"\x05\x00").unwrap(),
832 )
833 .unwrap();
834 make_certificate_for_subject(&recipient_key, Some(key_usage))
835 }
836
837 fn x25519_recipient_material() -> (Vec<u8>, Vec<u8>) {
838 let recipient_key = PKey::generate_x25519().unwrap();
839 let key_usage = KeyUsage::new().critical().key_agreement().build().unwrap();
840 (
841 make_certificate_for_subject(&recipient_key, Some(key_usage)),
842 recipient_key.raw_private_key().unwrap(),
843 )
844 }
845
846 fn p256_recipient_material() -> (Vec<u8>, Vec<u8>) {
847 let group = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1).unwrap();
848 let recipient_key = PKey::from_ec_key(EcKey::generate(&group).unwrap()).unwrap();
849 let key_usage = KeyUsage::new().critical().key_agreement().build().unwrap();
850 (
851 make_certificate_for_subject(&recipient_key, Some(key_usage)),
852 recipient_key.private_key_to_der().unwrap(),
853 )
854 }
855
856 fn p384_recipient_certificate() -> Vec<u8> {
857 let group = EcGroup::from_curve_name(Nid::SECP384R1).unwrap();
858 let recipient_key = PKey::from_ec_key(EcKey::generate(&group).unwrap()).unwrap();
859 let key_usage = KeyUsage::new().critical().key_agreement().build().unwrap();
860 make_certificate_for_subject(&recipient_key, Some(key_usage))
861 }
862
863 fn make_self_signed_certificate(
864 key_usage_ext: Option<openssl::x509::X509Extension>,
865 ) -> Vec<u8> {
866 let key = PKey::from_rsa(Rsa::generate(2048).unwrap()).unwrap();
867 let mut name = X509NameBuilder::new().unwrap();
868 name.append_entry_by_text("CN", "tzap-keywrap-test")
869 .unwrap();
870 let name = name.build();
871
872 let mut certificate = X509::builder().unwrap();
873 certificate.set_version(2).unwrap();
874 certificate
875 .set_serial_number(&random_serial_number())
876 .unwrap();
877 certificate.set_subject_name(&name).unwrap();
878 certificate.set_issuer_name(&name).unwrap();
879 certificate.set_pubkey(&key).unwrap();
880 certificate
881 .set_not_before(&Asn1Time::days_from_now(0).unwrap())
882 .unwrap();
883 certificate
884 .set_not_after(&Asn1Time::days_from_now(365).unwrap())
885 .unwrap();
886 certificate
887 .append_extension(BasicConstraints::new().build().unwrap())
888 .unwrap();
889 if let Some(key_usage_ext) = key_usage_ext {
890 certificate.append_extension(key_usage_ext).unwrap();
891 }
892 certificate.sign(&key, MessageDigest::sha256()).unwrap();
893
894 certificate.build().to_der().unwrap()
895 }
896
897 fn make_certificate_for_subject(
898 subject_key: &PKey<Private>,
899 key_usage_ext: Option<openssl::x509::X509Extension>,
900 ) -> Vec<u8> {
901 let signer_key = PKey::from_rsa(Rsa::generate(2048).unwrap()).unwrap();
902 let mut name = X509NameBuilder::new().unwrap();
903 name.append_entry_by_text("CN", "tzap-keywrap-recipient")
904 .unwrap();
905 let name = name.build();
906
907 let mut certificate = X509::builder().unwrap();
908 certificate.set_version(2).unwrap();
909 certificate
910 .set_serial_number(&random_serial_number())
911 .unwrap();
912 certificate.set_subject_name(&name).unwrap();
913 certificate.set_issuer_name(&name).unwrap();
914 certificate.set_pubkey(subject_key).unwrap();
915 certificate
916 .set_not_before(&Asn1Time::days_from_now(0).unwrap())
917 .unwrap();
918 certificate
919 .set_not_after(&Asn1Time::days_from_now(365).unwrap())
920 .unwrap();
921 certificate
922 .append_extension(BasicConstraints::new().build().unwrap())
923 .unwrap();
924 if let Some(key_usage_ext) = key_usage_ext {
925 certificate.append_extension(key_usage_ext).unwrap();
926 }
927 certificate
928 .sign(&signer_key, MessageDigest::sha256())
929 .unwrap();
930
931 certificate.build().to_der().unwrap()
932 }
933
934 fn random_serial_number() -> openssl::asn1::Asn1Integer {
935 let mut serial = BigNum::new().unwrap();
936 serial.rand(159, MsbOption::MAYBE_ZERO, false).unwrap();
937 serial.to_asn1_integer().unwrap()
938 }
939
940 fn make_payload(
941 archive_identity: &ArchiveIdentity,
942 metadata: &RecipientRecordMetadata,
943 identity: &ParsedRecipientIdentity,
944 suite: HpkeSuite,
945 ) -> Vec<u8> {
946 make_payload_with_lengths(
947 archive_identity,
948 metadata,
949 identity,
950 suite,
951 suite.enc_length,
952 suite.ciphertext_length,
953 )
954 }
955
956 fn make_payload_with_lengths(
957 archive_identity: &ArchiveIdentity,
958 metadata: &RecipientRecordMetadata,
959 identity: &ParsedRecipientIdentity,
960 suite: HpkeSuite,
961 enc_len: usize,
962 ciphertext_len: usize,
963 ) -> Vec<u8> {
964 let mut payload = vec![0u8; KEYWRAP_PROFILE_PAYLOAD_HEADER_LEN + enc_len + ciphertext_len];
965 payload[0..2].copy_from_slice(&KEYWRAP_PAYLOAD_VERSION.to_le_bytes());
966 payload[2..4].copy_from_slice(&suite.kem_id.to_le_bytes());
967 payload[4..6].copy_from_slice(&suite.kdf_id.to_le_bytes());
968 payload[6..8].copy_from_slice(&suite.aead_id.to_le_bytes());
969 payload[8..10].copy_from_slice(&u16::try_from(enc_len).unwrap().to_le_bytes());
970 payload[10..12].copy_from_slice(&u16::try_from(ciphertext_len).unwrap().to_le_bytes());
971 payload[16..48].copy_from_slice(&compute_key_wrap_context_digest(
972 archive_identity,
973 metadata,
974 identity,
975 suite,
976 ));
977
978 let enc_start = KEYWRAP_PROFILE_PAYLOAD_HEADER_LEN;
979 let enc_end = enc_start + enc_len;
980 let ct_end = enc_end + ciphertext_len;
981 for value in payload[enc_start..enc_end].iter_mut() {
982 *value = 0xAA;
983 }
984 for value in payload[enc_end..ct_end].iter_mut() {
985 *value = 0x55;
986 }
987
988 payload
989 }
990
991 fn recipient_record_input_with_payload(
992 profile_payload: Vec<u8>,
993 cert_der: &[u8],
994 ) -> RecipientRecordInput {
995 let identity_digest = sha256_digest(cert_der);
996 RecipientRecordInput {
997 archive_identity: archive_identity(),
998 metadata: RecipientRecordMetadata {
999 profile_id: KEYWRAP_PROFILE_ID,
1000 recipient_identity_type: KEYWRAP_RECIPIENT_IDENTITY_TYPE_BYTES,
1001 recipient_identity_digest: identity_digest,
1002 },
1003 recipient_identity_bytes: cert_der.to_vec(),
1004 profile_payload_bytes: profile_payload,
1005 }
1006 }
1007
1008 fn recipient_record_input_for_suite(cert_der: &[u8], suite: HpkeSuite) -> RecipientRecordInput {
1009 let identity = parse_x509_recipient_identity(cert_der).unwrap();
1010 let archive_identity = archive_identity();
1011 let metadata = RecipientRecordMetadata {
1012 profile_id: KEYWRAP_PROFILE_ID,
1013 recipient_identity_type: KEYWRAP_RECIPIENT_IDENTITY_TYPE_BYTES,
1014 recipient_identity_digest: identity.recipient_identity_digest,
1015 };
1016 let payload = make_payload(&archive_identity, &metadata, &identity, suite);
1017 RecipientRecordInput {
1018 archive_identity,
1019 metadata,
1020 recipient_identity_bytes: cert_der.to_vec(),
1021 profile_payload_bytes: payload,
1022 }
1023 }
1024
1025 fn recipient_record_input_from_record(
1026 archive_identity: ArchiveIdentity,
1027 record: RecipientRecordV1,
1028 ) -> RecipientRecordInput {
1029 RecipientRecordInput {
1030 archive_identity,
1031 metadata: RecipientRecordMetadata {
1032 profile_id: record.profile_id,
1033 recipient_identity_type: record.recipient_identity_type,
1034 recipient_identity_digest: record.recipient_identity_digest,
1035 },
1036 recipient_identity_bytes: record.recipient_identity_bytes,
1037 profile_payload_bytes: record.profile_payload_bytes,
1038 }
1039 }
1040
1041 #[test]
1042 fn malformed_recipient_identity_is_invalid() {
1043 let cert_der = b"not-a-certificate".to_vec();
1044 let profile_payload = vec![0u8; KEYWRAP_PROFILE_PAYLOAD_HEADER_LEN];
1045
1046 let input = recipient_record_input_with_payload(profile_payload, &cert_der);
1047 let result = dispatch_key_wrap_record(input, &NoMatchLookup);
1048
1049 assert!(matches!(result, KeyWrapOutcome::InvalidRecord));
1050 }
1051
1052 #[test]
1053 fn unsupported_identity_type_is_returned() {
1054 let cert_der = test_certificate_der();
1055 let identity = parse_x509_recipient_identity(&cert_der).unwrap();
1056
1057 let archive_identity = archive_identity();
1058 let suite =
1059 HpkeSuite::for_profile(X25519_KEM_ID, HKDF_SHA256_KDF_ID, CHACHA20POLY1305_AEAD_ID)
1060 .unwrap();
1061 let payload = make_payload(
1062 &archive_identity,
1063 &RecipientRecordMetadata {
1064 profile_id: KEYWRAP_PROFILE_ID,
1065 recipient_identity_type: KEYWRAP_RECIPIENT_IDENTITY_TYPE_BYTES,
1066 recipient_identity_digest: identity.recipient_identity_digest,
1067 },
1068 &identity,
1069 suite,
1070 );
1071 let mut input = recipient_record_input_with_payload(payload, &cert_der);
1072 input.metadata.recipient_identity_type = 0;
1073
1074 let result = dispatch_key_wrap_record(input, &NoMatchLookup);
1075
1076 assert!(matches!(
1077 result,
1078 KeyWrapOutcome::UnsupportedRecipientIdentity
1079 ));
1080 }
1081
1082 #[test]
1083 fn unsupported_suite_is_invalid() {
1084 let cert_der = test_certificate_der();
1085 let identity = parse_x509_recipient_identity(&cert_der).unwrap();
1086 let archive_identity = archive_identity();
1087 let metadata = RecipientRecordMetadata {
1088 profile_id: KEYWRAP_PROFILE_ID,
1089 recipient_identity_type: KEYWRAP_RECIPIENT_IDENTITY_TYPE_BYTES,
1090 recipient_identity_digest: identity.recipient_identity_digest,
1091 };
1092
1093 let unsupported_suite = HpkeSuite {
1094 kem_id: 0x00ff,
1095 kdf_id: 0x00ff,
1096 aead_id: 0x00ff,
1097 enc_length: 0,
1098 ciphertext_length: 0,
1099 };
1100
1101 let payload = make_payload_with_lengths(
1102 &archive_identity,
1103 &metadata,
1104 &identity,
1105 unsupported_suite,
1106 0,
1107 0,
1108 );
1109
1110 let input = recipient_record_input_with_payload(payload, &cert_der);
1111 let result = dispatch_key_wrap_record(input, &NoMatchLookup);
1112
1113 assert!(matches!(result, KeyWrapOutcome::UnsupportedSuite));
1114 }
1115
1116 #[test]
1117 fn wrong_lengths_rejected_as_invalid_record() {
1118 let cert_der = test_certificate_der();
1119 let identity = parse_x509_recipient_identity(&cert_der).unwrap();
1120 let archive_identity = archive_identity();
1121 let metadata = RecipientRecordMetadata {
1122 profile_id: KEYWRAP_PROFILE_ID,
1123 recipient_identity_type: KEYWRAP_RECIPIENT_IDENTITY_TYPE_BYTES,
1124 recipient_identity_digest: identity.recipient_identity_digest,
1125 };
1126 let suite =
1127 HpkeSuite::for_profile(X25519_KEM_ID, HKDF_SHA256_KDF_ID, CHACHA20POLY1305_AEAD_ID)
1128 .unwrap();
1129
1130 let payload = make_payload_with_lengths(
1131 &archive_identity,
1132 &metadata,
1133 &identity,
1134 suite,
1135 suite.enc_length - 1,
1136 suite.ciphertext_length,
1137 );
1138 let input = recipient_record_input_with_payload(payload, &cert_der);
1139
1140 let result = dispatch_key_wrap_record(input, &NoMatchLookup);
1141
1142 assert!(matches!(result, KeyWrapOutcome::InvalidRecord));
1143 }
1144
1145 #[test]
1146 fn wrong_context_digest_is_invalid_record() {
1147 let (cert_der, private_key) = x25519_recipient_material();
1148 let identity = parse_x509_recipient_identity(&cert_der).unwrap();
1149 let archive_identity = archive_identity();
1150 let metadata = RecipientRecordMetadata {
1151 profile_id: KEYWRAP_PROFILE_ID,
1152 recipient_identity_type: KEYWRAP_RECIPIENT_IDENTITY_TYPE_BYTES,
1153 recipient_identity_digest: identity.recipient_identity_digest,
1154 };
1155 let suite =
1156 HpkeSuite::for_profile(X25519_KEM_ID, HKDF_SHA256_KDF_ID, CHACHA20POLY1305_AEAD_ID)
1157 .unwrap();
1158 let mut payload = make_payload(&archive_identity, &metadata, &identity, suite);
1159 payload[16] ^= 0xFF;
1160
1161 let input = recipient_record_input_with_payload(payload, &cert_der);
1162 let result = dispatch_key_wrap_record(input, &StaticLookup { private_key });
1163
1164 assert!(matches!(result, KeyWrapOutcome::InvalidRecord));
1165 }
1166
1167 #[test]
1168 fn no_matching_private_key_precedes_context_digest_check() {
1169 let (cert_der, _) = x25519_recipient_material();
1170 let identity = parse_x509_recipient_identity(&cert_der).unwrap();
1171 let archive_identity = archive_identity();
1172 let metadata = RecipientRecordMetadata {
1173 profile_id: KEYWRAP_PROFILE_ID,
1174 recipient_identity_type: KEYWRAP_RECIPIENT_IDENTITY_TYPE_BYTES,
1175 recipient_identity_digest: identity.recipient_identity_digest,
1176 };
1177 let suite =
1178 HpkeSuite::for_profile(X25519_KEM_ID, HKDF_SHA256_KDF_ID, CHACHA20POLY1305_AEAD_ID)
1179 .unwrap();
1180 let mut payload = make_payload(&archive_identity, &metadata, &identity, suite);
1181 payload[16] ^= 0xFF;
1182
1183 let input = recipient_record_input_with_payload(payload, &cert_der);
1184 let result = dispatch_key_wrap_record(input, &NoMatchLookup);
1185
1186 assert!(matches!(result, KeyWrapOutcome::NoMatchingPrivateKey));
1187 }
1188
1189 #[test]
1190 fn certificate_policy_rejection_precedes_context_digest_check() {
1191 let (cert_der, _) = x25519_recipient_material();
1192 let identity = parse_x509_recipient_identity(&cert_der).unwrap();
1193 let archive_identity = archive_identity();
1194 let metadata = RecipientRecordMetadata {
1195 profile_id: KEYWRAP_PROFILE_ID,
1196 recipient_identity_type: KEYWRAP_RECIPIENT_IDENTITY_TYPE_BYTES,
1197 recipient_identity_digest: identity.recipient_identity_digest,
1198 };
1199 let suite =
1200 HpkeSuite::for_profile(X25519_KEM_ID, HKDF_SHA256_KDF_ID, CHACHA20POLY1305_AEAD_ID)
1201 .unwrap();
1202 let mut payload = make_payload(&archive_identity, &metadata, &identity, suite);
1203 payload[16] ^= 0xFF;
1204
1205 let input = recipient_record_input_with_payload(payload, &cert_der);
1206 let result = dispatch_key_wrap_record(input, &RejectLookup);
1207
1208 assert!(matches!(result, KeyWrapOutcome::CertificatePolicyRejected));
1209 }
1210
1211 #[test]
1212 fn valid_structure_with_no_matching_private_key_is_no_matching() {
1213 let archive_identity = archive_identity();
1214 let (cert_der, _) = x25519_recipient_material();
1215 let master_key = [0x42u8; 32];
1216 let record = wrap_master_key_for_recipient(
1217 archive_identity.clone(),
1218 &cert_der,
1219 &master_key,
1220 KeyWrapSuite::X25519HkdfSha256ChaCha20Poly1305,
1221 )
1222 .unwrap();
1223 let input = recipient_record_input_from_record(archive_identity, record);
1224
1225 let result = dispatch_key_wrap_record(input, &NoMatchLookup);
1226
1227 assert!(matches!(result, KeyWrapOutcome::NoMatchingPrivateKey));
1228 }
1229
1230 #[test]
1231 fn key_usage_without_key_agreement_is_invalid_record() {
1232 let cert_der = test_certificate_der_with_bad_key_usage();
1233 let payload = vec![0u8; KEYWRAP_PROFILE_PAYLOAD_HEADER_LEN];
1234 let input = recipient_record_input_with_payload(payload, &cert_der);
1235
1236 let result = dispatch_key_wrap_record(input, &NoMatchLookup);
1237
1238 assert!(matches!(result, KeyWrapOutcome::InvalidRecord));
1239 }
1240
1241 #[test]
1242 fn malformed_key_usage_extension_is_invalid_record() {
1243 let cert_der = test_certificate_der_with_malformed_key_usage();
1244 let payload = vec![0u8; KEYWRAP_PROFILE_PAYLOAD_HEADER_LEN];
1245 let input = recipient_record_input_with_payload(payload, &cert_der);
1246
1247 let result = dispatch_key_wrap_record(input, &NoMatchLookup);
1248
1249 assert!(matches!(result, KeyWrapOutcome::InvalidRecord));
1250 }
1251
1252 #[test]
1253 fn certificate_policy_rejection_is_reported() {
1254 let archive_identity = archive_identity();
1255 let (cert_der, _) = x25519_recipient_material();
1256 let master_key = [0x42u8; 32];
1257 let record = wrap_master_key_for_recipient(
1258 archive_identity.clone(),
1259 &cert_der,
1260 &master_key,
1261 KeyWrapSuite::X25519HkdfSha256ChaCha20Poly1305,
1262 )
1263 .unwrap();
1264 let input = recipient_record_input_from_record(archive_identity, record);
1265
1266 let result = dispatch_key_wrap_record(input, &RejectLookup);
1267
1268 assert!(matches!(result, KeyWrapOutcome::CertificatePolicyRejected));
1269 }
1270
1271 #[test]
1272 fn suite_certificate_mismatch_is_unsupported_before_private_key_lookup() {
1273 let (p256_cert_der, _) = p256_recipient_material();
1274 let x25519_suite =
1275 HpkeSuite::for_profile(X25519_KEM_ID, HKDF_SHA256_KDF_ID, CHACHA20POLY1305_AEAD_ID)
1276 .unwrap();
1277 let result = dispatch_key_wrap_record(
1278 recipient_record_input_for_suite(&p256_cert_der, x25519_suite),
1279 &NoMatchLookup,
1280 );
1281 assert!(matches!(result, KeyWrapOutcome::UnsupportedSuite));
1282
1283 let (x25519_cert_der, _) = x25519_recipient_material();
1284 let p256_suite =
1285 HpkeSuite::for_profile(P256_KEM_ID, HKDF_SHA256_KDF_ID, AES256GCM_AEAD_ID).unwrap();
1286 let result = dispatch_key_wrap_record(
1287 recipient_record_input_for_suite(&x25519_cert_der, p256_suite),
1288 &NoMatchLookup,
1289 );
1290 assert!(matches!(result, KeyWrapOutcome::UnsupportedSuite));
1291
1292 let p384_cert_der = p384_recipient_certificate();
1293 let result = dispatch_key_wrap_record(
1294 recipient_record_input_for_suite(&p384_cert_der, p256_suite),
1295 &NoMatchLookup,
1296 );
1297 assert!(matches!(result, KeyWrapOutcome::UnsupportedSuite));
1298 }
1299
1300 #[test]
1301 fn writer_reports_unsupported_suite_for_certificate_mismatch() {
1302 let master_key = [0x42u8; 32];
1303 let (p256_cert_der, _) = p256_recipient_material();
1304 let result = wrap_master_key_for_recipient(
1305 archive_identity(),
1306 &p256_cert_der,
1307 &master_key,
1308 KeyWrapSuite::X25519HkdfSha256ChaCha20Poly1305,
1309 );
1310 assert!(matches!(result, Err(KeyWrapOutcome::UnsupportedSuite)));
1311
1312 let (x25519_cert_der, _) = x25519_recipient_material();
1313 let result = wrap_master_key_for_recipient(
1314 archive_identity(),
1315 &x25519_cert_der,
1316 &master_key,
1317 KeyWrapSuite::P256HkdfSha256Aes256Gcm,
1318 );
1319 assert!(matches!(result, Err(KeyWrapOutcome::UnsupportedSuite)));
1320 }
1321
1322 #[test]
1323 fn x25519_wrap_unwrap_returns_candidate_master_key() {
1324 let (cert_der, private_key) = x25519_recipient_material();
1325 let identity = parse_x509_recipient_identity(&cert_der).unwrap();
1326 let archive_identity = archive_identity();
1327 let master_key = [0x42u8; 32];
1328 let record = wrap_master_key_for_recipient(
1329 archive_identity.clone(),
1330 &cert_der,
1331 &master_key,
1332 KeyWrapSuite::X25519HkdfSha256ChaCha20Poly1305,
1333 )
1334 .unwrap();
1335 let input = recipient_record_input_from_record(archive_identity, record);
1336
1337 let result = dispatch_key_wrap_record(input, &StaticLookup { private_key });
1338
1339 assert!(matches!(
1340 result,
1341 KeyWrapOutcome::UnwrappedCandidateMasterKey {
1342 master_key: actual_master_key,
1343 recipient_spki_digest: actual_digest
1344 } if actual_master_key == master_key && actual_digest == identity.recipient_spki_digest
1345 ));
1346 }
1347
1348 #[test]
1349 fn p256_wrap_unwrap_returns_candidate_master_key() {
1350 let (cert_der, private_key) = p256_recipient_material();
1351 let identity = parse_x509_recipient_identity(&cert_der).unwrap();
1352 let archive_identity = archive_identity();
1353 let master_key = [0x24u8; 32];
1354 let record = wrap_master_key_for_recipient(
1355 archive_identity.clone(),
1356 &cert_der,
1357 &master_key,
1358 KeyWrapSuite::P256HkdfSha256Aes256Gcm,
1359 )
1360 .unwrap();
1361 let input = recipient_record_input_from_record(archive_identity, record);
1362
1363 let result = dispatch_key_wrap_record(input, &StaticLookup { private_key });
1364
1365 assert!(matches!(
1366 result,
1367 KeyWrapOutcome::UnwrappedCandidateMasterKey {
1368 master_key: actual_master_key,
1369 recipient_spki_digest: actual_digest
1370 } if actual_master_key == master_key && actual_digest == identity.recipient_spki_digest
1371 ));
1372 }
1373
1374 #[test]
1375 fn multi_recipient_records_open_independently_with_matching_private_key() {
1376 let archive_identity = archive_identity();
1377 let master_key = [0x33u8; 32];
1378 let (bob_cert, bob_key) = x25519_recipient_material();
1379 let (carol_cert, carol_key) = p256_recipient_material();
1380 let (ops_cert, ops_key) = x25519_recipient_material();
1381 let recipients = [
1382 (
1383 bob_cert,
1384 bob_key,
1385 KeyWrapSuite::X25519HkdfSha256ChaCha20Poly1305,
1386 ),
1387 (carol_cert, carol_key, KeyWrapSuite::P256HkdfSha256Aes256Gcm),
1388 (
1389 ops_cert,
1390 ops_key,
1391 KeyWrapSuite::X25519HkdfSha256ChaCha20Poly1305,
1392 ),
1393 ];
1394 let records = recipients
1395 .iter()
1396 .map(|(cert_der, _, suite)| {
1397 wrap_master_key_for_recipient(
1398 archive_identity.clone(),
1399 cert_der,
1400 &master_key,
1401 *suite,
1402 )
1403 .unwrap()
1404 })
1405 .collect::<Vec<_>>();
1406
1407 for (cert_der, private_key, _) in recipients {
1408 let lookup = MatchingLookup {
1409 matches: vec![(cert_der.clone(), private_key)],
1410 };
1411 let unwrapped = records
1412 .clone()
1413 .into_iter()
1414 .filter_map(|record| {
1415 let input =
1416 recipient_record_input_from_record(archive_identity.clone(), record);
1417 match dispatch_key_wrap_record(input, &lookup) {
1418 KeyWrapOutcome::UnwrappedCandidateMasterKey {
1419 master_key: candidate,
1420 ..
1421 } => Some(candidate),
1422 KeyWrapOutcome::NoMatchingPrivateKey => None,
1423 other => panic!("unexpected key-wrap outcome: {other:?}"),
1424 }
1425 })
1426 .collect::<Vec<_>>();
1427
1428 assert_eq!(unwrapped, vec![master_key]);
1429 }
1430 }
1431
1432 #[test]
1433 fn wrong_private_key_is_invalid_record() {
1434 let (cert_der, _private_key) = x25519_recipient_material();
1435 let (_wrong_cert_der, wrong_private_key) = x25519_recipient_material();
1436 let archive_identity = archive_identity();
1437 let master_key = [0x5au8; 32];
1438 let record = wrap_master_key_for_recipient(
1439 archive_identity.clone(),
1440 &cert_der,
1441 &master_key,
1442 KeyWrapSuite::X25519HkdfSha256ChaCha20Poly1305,
1443 )
1444 .unwrap();
1445 let input = recipient_record_input_from_record(archive_identity, record);
1446
1447 let result = dispatch_key_wrap_record(
1448 input,
1449 &StaticLookup {
1450 private_key: wrong_private_key,
1451 },
1452 );
1453
1454 assert!(matches!(result, KeyWrapOutcome::InvalidRecord));
1455 }
1456
1457 #[test]
1458 fn signing_certificate_key_usage_does_not_satisfy_recipient_wrap() {
1459 let cert_der = test_certificate_der_with_bad_key_usage();
1460 let master_key = [0x42u8; 32];
1461
1462 let result = wrap_master_key_for_recipient(
1463 archive_identity(),
1464 &cert_der,
1465 &master_key,
1466 KeyWrapSuite::X25519HkdfSha256ChaCha20Poly1305,
1467 );
1468
1469 assert!(matches!(result, Err(KeyWrapOutcome::InvalidRecord)));
1470 }
1471}