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_45};
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-v45\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_45,
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_45
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_45
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 hpke_seal_master_key_with_aad_domain(
316 recipient_certificate_der,
317 archive_identity,
318 metadata,
319 identity,
320 master_key,
321 suite,
322 HPKE_AAD_DOMAIN,
323 )
324}
325
326fn hpke_seal_master_key_with_aad_domain(
327 recipient_certificate_der: &[u8],
328 archive_identity: &ArchiveIdentity,
329 metadata: &RecipientRecordMetadata,
330 identity: &ParsedRecipientIdentity,
331 master_key: &[u8; 32],
332 suite: HpkeSuite,
333 aad_domain: &[u8],
334) -> Result<Vec<u8>, KeyWrapOutcome> {
335 let key_wrap_context_digest =
336 compute_key_wrap_context_digest(archive_identity, metadata, identity, suite);
337 let info = hpke_info(&key_wrap_context_digest);
338 let aad = hpke_aad_with_domain(aad_domain, &key_wrap_context_digest);
339 let (enc, ciphertext) = match (suite.kem_id, suite.aead_id) {
340 (X25519_KEM_ID, CHACHA20POLY1305_AEAD_ID) => {
341 let public_key = x25519_public_key_from_certificate(recipient_certificate_der)
342 .map_err(|_| KeyWrapOutcome::UnsupportedSuite)?;
343 let mut rng = UnwrapErr(OsRng);
344 let (enc, ciphertext) =
345 hpke::single_shot_seal::<ChaCha20Poly1305, HkdfSha256, X25519HkdfSha256, _>(
346 &OpModeS::Base,
347 &public_key,
348 &info,
349 master_key,
350 &aad,
351 &mut rng,
352 )
353 .map_err(|_| KeyWrapOutcome::InvalidRecord)?;
354 (enc.to_bytes().to_vec(), ciphertext)
355 }
356 (P256_KEM_ID, AES256GCM_AEAD_ID) => {
357 let public_key = p256_public_key_from_certificate(recipient_certificate_der)
358 .map_err(|_| KeyWrapOutcome::UnsupportedSuite)?;
359 let mut rng = UnwrapErr(OsRng);
360 let (enc, ciphertext) =
361 hpke::single_shot_seal::<AesGcm256, HkdfSha256, DhP256HkdfSha256, _>(
362 &OpModeS::Base,
363 &public_key,
364 &info,
365 master_key,
366 &aad,
367 &mut rng,
368 )
369 .map_err(|_| KeyWrapOutcome::InvalidRecord)?;
370 (enc.to_bytes().to_vec(), ciphertext)
371 }
372 _ => return Err(KeyWrapOutcome::UnsupportedSuite),
373 };
374
375 if enc.len() != suite.enc_length || ciphertext.len() != suite.ciphertext_length {
376 return Err(KeyWrapOutcome::InvalidRecord);
377 }
378
379 Ok(build_profile_payload(
380 suite,
381 &key_wrap_context_digest,
382 &enc,
383 &ciphertext,
384 ))
385}
386
387fn hpke_open_candidate_master_key(
388 payload: &ParsedProfilePayload,
389 private_key_bytes: &[u8],
390) -> Result<[u8; 32], KeyWrapOutcome> {
391 let info = hpke_info(&payload.key_wrap_context_digest);
392 let aad = hpke_aad(&payload.key_wrap_context_digest);
393 let plaintext = match (payload.suite.kem_id, payload.suite.aead_id) {
394 (X25519_KEM_ID, CHACHA20POLY1305_AEAD_ID) => {
395 let private_key = x25519_private_key_from_bytes(private_key_bytes)?;
396 let encapped_key = <X25519HkdfSha256 as HpkeKem>::EncappedKey::from_bytes(&payload.enc)
397 .map_err(|_| KeyWrapOutcome::InvalidRecord)?;
398 hpke::single_shot_open::<ChaCha20Poly1305, HkdfSha256, X25519HkdfSha256>(
399 &OpModeR::Base,
400 &private_key,
401 &encapped_key,
402 &info,
403 &payload.ciphertext,
404 &aad,
405 )
406 .map_err(|_| KeyWrapOutcome::InvalidRecord)?
407 }
408 (P256_KEM_ID, AES256GCM_AEAD_ID) => {
409 let private_key = p256_private_key_from_bytes(private_key_bytes)?;
410 let encapped_key = <DhP256HkdfSha256 as HpkeKem>::EncappedKey::from_bytes(&payload.enc)
411 .map_err(|_| KeyWrapOutcome::InvalidRecord)?;
412 hpke::single_shot_open::<AesGcm256, HkdfSha256, DhP256HkdfSha256>(
413 &OpModeR::Base,
414 &private_key,
415 &encapped_key,
416 &info,
417 &payload.ciphertext,
418 &aad,
419 )
420 .map_err(|_| KeyWrapOutcome::InvalidRecord)?
421 }
422 _ => return Err(KeyWrapOutcome::UnsupportedSuite),
423 };
424
425 if plaintext.len() != 32 {
426 return Err(KeyWrapOutcome::InvalidRecord);
427 }
428 let mut master_key = [0u8; 32];
429 master_key.copy_from_slice(&plaintext);
430 Ok(master_key)
431}
432
433fn build_profile_payload(
434 suite: HpkeSuite,
435 key_wrap_context_digest: &[u8; 32],
436 enc: &[u8],
437 ciphertext: &[u8],
438) -> Vec<u8> {
439 let mut payload = vec![0u8; KEYWRAP_PROFILE_PAYLOAD_HEADER_LEN + enc.len() + ciphertext.len()];
440 payload[0..2].copy_from_slice(&KEYWRAP_PAYLOAD_VERSION.to_le_bytes());
441 payload[2..4].copy_from_slice(&suite.kem_id.to_le_bytes());
442 payload[4..6].copy_from_slice(&suite.kdf_id.to_le_bytes());
443 payload[6..8].copy_from_slice(&suite.aead_id.to_le_bytes());
444 payload[8..10].copy_from_slice(&(enc.len() as u16).to_le_bytes());
445 payload[10..12].copy_from_slice(&(ciphertext.len() as u16).to_le_bytes());
446 payload[16..48].copy_from_slice(key_wrap_context_digest);
447 payload[KEYWRAP_PROFILE_PAYLOAD_HEADER_LEN..KEYWRAP_PROFILE_PAYLOAD_HEADER_LEN + enc.len()]
448 .copy_from_slice(enc);
449 payload[KEYWRAP_PROFILE_PAYLOAD_HEADER_LEN + enc.len()..].copy_from_slice(ciphertext);
450 payload
451}
452
453fn parse_x509_recipient_identity(
454 recipient_identity_bytes: &[u8],
455) -> Result<ParsedRecipientIdentity, KeyWrapOutcome> {
456 let (remaining, parsed_cert) = parse_x509_certificate(recipient_identity_bytes)
457 .map_err(|_error| KeyWrapOutcome::InvalidRecord)?;
458
459 if !remaining.is_empty() {
460 return Err(KeyWrapOutcome::InvalidRecord);
461 }
462
463 match parsed_cert.key_usage() {
464 Ok(Some(key_usage)) => {
465 if !key_usage.value.key_agreement() {
466 return Err(KeyWrapOutcome::InvalidRecord);
467 }
468 }
469 Ok(None) => {}
470 Err(_) => {
471 return Err(KeyWrapOutcome::InvalidRecord);
472 }
473 }
474
475 let openssl_cert =
476 X509::from_der(recipient_identity_bytes).map_err(|_| KeyWrapOutcome::InvalidRecord)?;
477 let openssl_public_key: PKey<_> = openssl_cert
478 .public_key()
479 .map_err(|_| KeyWrapOutcome::InvalidRecord)?;
480 let spki_der = openssl_public_key
481 .public_key_to_der()
482 .map_err(|_| KeyWrapOutcome::InvalidRecord)?;
483 let recipient_identity_digest = sha256_digest(recipient_identity_bytes);
484 let recipient_spki_digest = sha256_digest(&spki_der);
485
486 Ok(ParsedRecipientIdentity {
487 recipient_identity_digest,
488 recipient_spki_digest,
489 })
490}
491
492fn parse_profile_payload(
493 profile_payload_bytes: &[u8],
494) -> Result<ParsedProfilePayload, KeyWrapOutcome> {
495 if profile_payload_bytes.len() < KEYWRAP_PROFILE_PAYLOAD_HEADER_LEN {
496 return Err(KeyWrapOutcome::InvalidRecord);
497 }
498
499 let payload_version = read_u16(profile_payload_bytes, 0)?;
500 let hpke_kem_id = read_u16(profile_payload_bytes, 2)?;
501 let hpke_kdf_id = read_u16(profile_payload_bytes, 4)?;
502 let hpke_aead_id = read_u16(profile_payload_bytes, 6)?;
503 let enc_length = usize::from(read_u16(profile_payload_bytes, 8)?);
504 let ciphertext_length = usize::from(read_u16(profile_payload_bytes, 10)?);
505 let flags = read_u32(profile_payload_bytes, 12)?;
506 let key_wrap_context_digest = read_array_32(profile_payload_bytes, 16)?;
507
508 if flags != 0 {
509 return Err(KeyWrapOutcome::InvalidRecord);
510 }
511
512 if !profile_payload_bytes[48..KEYWRAP_PROFILE_PAYLOAD_HEADER_LEN]
513 .iter()
514 .all(|value| *value == 0)
515 {
516 return Err(KeyWrapOutcome::InvalidRecord);
517 }
518
519 if payload_version != KEYWRAP_PAYLOAD_VERSION {
520 return Err(KeyWrapOutcome::InvalidRecord);
521 }
522
523 let suite = HpkeSuite::for_profile(hpke_kem_id, hpke_kdf_id, hpke_aead_id)
524 .ok_or(KeyWrapOutcome::UnsupportedSuite)?;
525
526 if enc_length != suite.enc_length || ciphertext_length != suite.ciphertext_length {
527 return Err(KeyWrapOutcome::InvalidRecord);
528 }
529
530 let expected_length = KEYWRAP_PROFILE_PAYLOAD_HEADER_LEN
531 .checked_add(enc_length)
532 .and_then(|value| value.checked_add(ciphertext_length))
533 .ok_or(KeyWrapOutcome::InvalidRecord)?;
534
535 if profile_payload_bytes.len() != expected_length {
536 return Err(KeyWrapOutcome::InvalidRecord);
537 }
538
539 let enc_start = KEYWRAP_PROFILE_PAYLOAD_HEADER_LEN;
540 let ciphertext_start = enc_start + enc_length;
541 let enc = profile_payload_bytes[enc_start..ciphertext_start].to_vec();
542 let ciphertext =
543 profile_payload_bytes[ciphertext_start..ciphertext_start + ciphertext_length].to_vec();
544
545 Ok(ParsedProfilePayload {
546 suite,
547 enc,
548 ciphertext,
549 key_wrap_context_digest,
550 })
551}
552
553fn validate_recipient_public_key_matches_suite(
554 recipient_certificate_der: &[u8],
555 suite: HpkeSuite,
556) -> Result<(), KeyWrapOutcome> {
557 match suite.kem_id {
558 X25519_KEM_ID => x25519_public_key_from_certificate(recipient_certificate_der)
559 .map(|_| ())
560 .map_err(|_| KeyWrapOutcome::UnsupportedSuite),
561 P256_KEM_ID => p256_public_key_from_certificate(recipient_certificate_der)
562 .map(|_| ())
563 .map_err(|_| KeyWrapOutcome::UnsupportedSuite),
564 _ => Err(KeyWrapOutcome::UnsupportedSuite),
565 }
566}
567
568fn compute_key_wrap_context_digest(
569 archive_identity: &ArchiveIdentity,
570 metadata: &RecipientRecordMetadata,
571 identity: &ParsedRecipientIdentity,
572 suite: HpkeSuite,
573) -> [u8; 32] {
574 let mut hasher = Sha256::new();
575 hasher.update(KEY_WRAP_CONTEXT_DOMAIN);
576 hasher.update(archive_identity.archive_uuid);
577 hasher.update(archive_identity.session_id);
578 hasher.update(archive_identity.format_version.to_le_bytes());
579 hasher.update(archive_identity.volume_format_rev.to_le_bytes());
580 hasher.update(metadata.profile_id.to_le_bytes());
581 hasher.update(metadata.recipient_identity_type.to_le_bytes());
582 hasher.update(identity.recipient_identity_digest);
583 hasher.update(identity.recipient_spki_digest);
584 hasher.update(suite.kem_id.to_le_bytes());
585 hasher.update(suite.kdf_id.to_le_bytes());
586 hasher.update(suite.aead_id.to_le_bytes());
587
588 let digest = hasher.finalize();
589 let mut out = [0u8; 32];
590 out.copy_from_slice(&digest);
591 out
592}
593
594fn hpke_info(key_wrap_context_digest: &[u8; 32]) -> Vec<u8> {
595 let mut info = Vec::with_capacity(HPKE_INFO_DOMAIN.len() + key_wrap_context_digest.len());
596 info.extend_from_slice(HPKE_INFO_DOMAIN);
597 info.extend_from_slice(key_wrap_context_digest);
598 info
599}
600
601fn hpke_aad(key_wrap_context_digest: &[u8; 32]) -> Vec<u8> {
602 hpke_aad_with_domain(HPKE_AAD_DOMAIN, key_wrap_context_digest)
603}
604
605fn hpke_aad_with_domain(domain: &[u8], key_wrap_context_digest: &[u8; 32]) -> Vec<u8> {
606 let mut aad = Vec::with_capacity(domain.len() + key_wrap_context_digest.len());
607 aad.extend_from_slice(domain);
608 aad.extend_from_slice(key_wrap_context_digest);
609 aad
610}
611
612fn x25519_public_key_from_certificate(
613 recipient_certificate_der: &[u8],
614) -> Result<<X25519HkdfSha256 as HpkeKem>::PublicKey, KeyWrapOutcome> {
615 let certificate =
616 X509::from_der(recipient_certificate_der).map_err(|_| KeyWrapOutcome::InvalidRecord)?;
617 let public_key = certificate
618 .public_key()
619 .map_err(|_| KeyWrapOutcome::InvalidRecord)?;
620 let raw = public_key
621 .raw_public_key()
622 .map_err(|_| KeyWrapOutcome::InvalidRecord)?;
623 if raw.len() != 32 {
624 return Err(KeyWrapOutcome::InvalidRecord);
625 }
626 <X25519HkdfSha256 as HpkeKem>::PublicKey::from_bytes(&raw)
627 .map_err(|_| KeyWrapOutcome::InvalidRecord)
628}
629
630fn p256_public_key_from_certificate(
631 recipient_certificate_der: &[u8],
632) -> Result<<DhP256HkdfSha256 as HpkeKem>::PublicKey, KeyWrapOutcome> {
633 let certificate =
634 X509::from_der(recipient_certificate_der).map_err(|_| KeyWrapOutcome::InvalidRecord)?;
635 let public_key = certificate
636 .public_key()
637 .map_err(|_| KeyWrapOutcome::InvalidRecord)?;
638 let ec_key = public_key
639 .ec_key()
640 .map_err(|_| KeyWrapOutcome::InvalidRecord)?;
641 if ec_key.group().curve_name() != Some(Nid::X9_62_PRIME256V1) {
642 return Err(KeyWrapOutcome::InvalidRecord);
643 }
644 let mut context = BigNumContext::new().map_err(|_| KeyWrapOutcome::InvalidRecord)?;
645 let encoded = ec_key
646 .public_key()
647 .to_bytes(
648 ec_key.group(),
649 PointConversionForm::UNCOMPRESSED,
650 &mut context,
651 )
652 .map_err(|_| KeyWrapOutcome::InvalidRecord)?;
653 if encoded.len() != 65 {
654 return Err(KeyWrapOutcome::InvalidRecord);
655 }
656 <DhP256HkdfSha256 as HpkeKem>::PublicKey::from_bytes(&encoded)
657 .map_err(|_| KeyWrapOutcome::InvalidRecord)
658}
659
660fn x25519_private_key_from_bytes(
661 private_key_bytes: &[u8],
662) -> Result<<X25519HkdfSha256 as HpkeKem>::PrivateKey, KeyWrapOutcome> {
663 let raw = if private_key_bytes.len() == 32 {
664 private_key_bytes.to_vec()
665 } else {
666 let private_key = PKey::private_key_from_der(private_key_bytes)
667 .map_err(|_| KeyWrapOutcome::InvalidRecord)?;
668 private_key
669 .raw_private_key()
670 .map_err(|_| KeyWrapOutcome::InvalidRecord)?
671 };
672 if raw.len() != 32 {
673 return Err(KeyWrapOutcome::InvalidRecord);
674 }
675 <X25519HkdfSha256 as HpkeKem>::PrivateKey::from_bytes(&raw)
676 .map_err(|_| KeyWrapOutcome::InvalidRecord)
677}
678
679fn p256_private_key_from_bytes(
680 private_key_bytes: &[u8],
681) -> Result<<DhP256HkdfSha256 as HpkeKem>::PrivateKey, KeyWrapOutcome> {
682 let raw = if private_key_bytes.len() == 32 {
683 private_key_bytes.to_vec()
684 } else {
685 let private_key = PKey::private_key_from_der(private_key_bytes)
686 .map_err(|_| KeyWrapOutcome::InvalidRecord)?;
687 let ec_key = private_key
688 .ec_key()
689 .map_err(|_| KeyWrapOutcome::InvalidRecord)?;
690 ec_key
691 .private_key()
692 .to_vec_padded(32)
693 .map_err(|_| KeyWrapOutcome::InvalidRecord)?
694 };
695 if raw.len() != 32 {
696 return Err(KeyWrapOutcome::InvalidRecord);
697 }
698 <DhP256HkdfSha256 as HpkeKem>::PrivateKey::from_bytes(&raw)
699 .map_err(|_| KeyWrapOutcome::InvalidRecord)
700}
701
702fn sha256_digest(bytes: &[u8]) -> [u8; 32] {
703 let digest = Sha256::digest(bytes);
704 let mut out = [0u8; 32];
705 out.copy_from_slice(&digest);
706 out
707}
708
709fn read_u16(bytes: &[u8], offset: usize) -> Result<u16, KeyWrapOutcome> {
710 let value = bytes
711 .get(offset..offset + 2)
712 .ok_or(KeyWrapOutcome::InvalidRecord)?;
713 Ok(u16::from_le_bytes([value[0], value[1]]))
714}
715
716fn read_u32(bytes: &[u8], offset: usize) -> Result<u32, KeyWrapOutcome> {
717 let value = bytes
718 .get(offset..offset + 4)
719 .ok_or(KeyWrapOutcome::InvalidRecord)?;
720 Ok(u32::from_le_bytes([value[0], value[1], value[2], value[3]]))
721}
722
723fn read_array_32(bytes: &[u8], offset: usize) -> Result<[u8; 32], KeyWrapOutcome> {
724 let value = bytes
725 .get(offset..offset + 32)
726 .ok_or(KeyWrapOutcome::InvalidRecord)?;
727 let mut out = [0u8; 32];
728 out.copy_from_slice(value);
729 Ok(out)
730}
731
732#[cfg(test)]
733mod tests {
734 use super::*;
735
736 use openssl::{
737 asn1::{Asn1Object, Asn1OctetString, Asn1Time},
738 bn::{BigNum, MsbOption},
739 ec::{EcGroup, EcKey},
740 hash::MessageDigest,
741 nid::Nid,
742 pkey::{PKey, Private},
743 rand::rand_bytes,
744 rsa::Rsa,
745 x509::{
746 extension::BasicConstraints, extension::KeyUsage, X509Extension, X509NameBuilder, X509,
747 },
748 };
749
750 #[derive(Debug)]
751 struct NoMatchLookup;
752
753 impl PrivateKeyLookup for NoMatchLookup {
754 fn lookup_private_key(
755 &self,
756 _: &ArchiveIdentity,
757 _: &RecipientRecordMetadata,
758 _: &[u8],
759 ) -> Option<Vec<u8>> {
760 None
761 }
762 }
763
764 #[derive(Debug)]
765 struct StaticLookup {
766 private_key: Vec<u8>,
767 }
768
769 impl PrivateKeyLookup for StaticLookup {
770 fn lookup_private_key(
771 &self,
772 _: &ArchiveIdentity,
773 _: &RecipientRecordMetadata,
774 _: &[u8],
775 ) -> Option<Vec<u8>> {
776 Some(self.private_key.clone())
777 }
778 }
779
780 #[derive(Debug)]
781 struct RejectLookup;
782
783 impl PrivateKeyLookup for RejectLookup {
784 fn lookup_private_key(
785 &self,
786 _: &ArchiveIdentity,
787 _: &RecipientRecordMetadata,
788 _: &[u8],
789 ) -> Option<Vec<u8>> {
790 Some(vec![0xAA; 32])
791 }
792
793 fn is_recipient_certificate_accepted(
794 &self,
795 _: &ArchiveIdentity,
796 _: &RecipientRecordMetadata,
797 _: &[u8],
798 _: &[u8; 32],
799 ) -> bool {
800 false
801 }
802 }
803
804 #[derive(Debug)]
805 struct MatchingLookup {
806 matches: Vec<(Vec<u8>, Vec<u8>)>,
807 }
808
809 impl PrivateKeyLookup for MatchingLookup {
810 fn lookup_private_key(
811 &self,
812 _: &ArchiveIdentity,
813 _: &RecipientRecordMetadata,
814 recipient_identity_bytes: &[u8],
815 ) -> Option<Vec<u8>> {
816 self.matches
817 .iter()
818 .find(|(identity, _)| identity.as_slice() == recipient_identity_bytes)
819 .map(|(_, private_key)| private_key.clone())
820 }
821 }
822
823 fn archive_identity() -> ArchiveIdentity {
824 let mut archive_uuid = [0u8; 16];
825 let mut session_id = [0u8; 16];
826 rand_bytes(&mut archive_uuid).unwrap();
827 rand_bytes(&mut session_id).unwrap();
828
829 ArchiveIdentity {
830 archive_uuid,
831 session_id,
832 format_version: FORMAT_VERSION,
833 volume_format_rev: VOLUME_FORMAT_REV_45,
834 }
835 }
836
837 fn test_certificate_der() -> Vec<u8> {
838 make_self_signed_certificate(None)
839 }
840
841 fn test_certificate_der_with_bad_key_usage() -> Vec<u8> {
842 let key_usage = KeyUsage::new()
843 .critical()
844 .digital_signature()
845 .build()
846 .unwrap();
847 make_self_signed_certificate(Some(key_usage)).to_vec()
848 }
849
850 fn test_certificate_der_with_malformed_key_usage() -> Vec<u8> {
851 let recipient_key = PKey::generate_x25519().unwrap();
852 let key_usage = X509Extension::new_from_der(
853 &Asn1Object::from_str("2.5.29.15").unwrap(),
854 true,
855 &Asn1OctetString::new_from_bytes(b"\x05\x00").unwrap(),
856 )
857 .unwrap();
858 make_certificate_for_subject(&recipient_key, Some(key_usage))
859 }
860
861 fn x25519_recipient_material() -> (Vec<u8>, Vec<u8>) {
862 let recipient_key = PKey::generate_x25519().unwrap();
863 let key_usage = KeyUsage::new().critical().key_agreement().build().unwrap();
864 (
865 make_certificate_for_subject(&recipient_key, Some(key_usage)),
866 recipient_key.raw_private_key().unwrap(),
867 )
868 }
869
870 fn p256_recipient_material() -> (Vec<u8>, Vec<u8>) {
871 let group = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1).unwrap();
872 let recipient_key = PKey::from_ec_key(EcKey::generate(&group).unwrap()).unwrap();
873 let key_usage = KeyUsage::new().critical().key_agreement().build().unwrap();
874 (
875 make_certificate_for_subject(&recipient_key, Some(key_usage)),
876 recipient_key.private_key_to_der().unwrap(),
877 )
878 }
879
880 fn p384_recipient_certificate() -> Vec<u8> {
881 let group = EcGroup::from_curve_name(Nid::SECP384R1).unwrap();
882 let recipient_key = PKey::from_ec_key(EcKey::generate(&group).unwrap()).unwrap();
883 let key_usage = KeyUsage::new().critical().key_agreement().build().unwrap();
884 make_certificate_for_subject(&recipient_key, Some(key_usage))
885 }
886
887 fn make_self_signed_certificate(
888 key_usage_ext: Option<openssl::x509::X509Extension>,
889 ) -> Vec<u8> {
890 let key = PKey::from_rsa(Rsa::generate(2048).unwrap()).unwrap();
891 let mut name = X509NameBuilder::new().unwrap();
892 name.append_entry_by_text("CN", "tzap-keywrap-test")
893 .unwrap();
894 let name = name.build();
895
896 let mut certificate = X509::builder().unwrap();
897 certificate.set_version(2).unwrap();
898 certificate
899 .set_serial_number(&random_serial_number())
900 .unwrap();
901 certificate.set_subject_name(&name).unwrap();
902 certificate.set_issuer_name(&name).unwrap();
903 certificate.set_pubkey(&key).unwrap();
904 certificate
905 .set_not_before(&Asn1Time::days_from_now(0).unwrap())
906 .unwrap();
907 certificate
908 .set_not_after(&Asn1Time::days_from_now(365).unwrap())
909 .unwrap();
910 certificate
911 .append_extension(BasicConstraints::new().build().unwrap())
912 .unwrap();
913 if let Some(key_usage_ext) = key_usage_ext {
914 certificate.append_extension(key_usage_ext).unwrap();
915 }
916 certificate.sign(&key, MessageDigest::sha256()).unwrap();
917
918 certificate.build().to_der().unwrap()
919 }
920
921 fn make_certificate_for_subject(
922 subject_key: &PKey<Private>,
923 key_usage_ext: Option<openssl::x509::X509Extension>,
924 ) -> Vec<u8> {
925 let signer_key = PKey::from_rsa(Rsa::generate(2048).unwrap()).unwrap();
926 let mut name = X509NameBuilder::new().unwrap();
927 name.append_entry_by_text("CN", "tzap-keywrap-recipient")
928 .unwrap();
929 let name = name.build();
930
931 let mut certificate = X509::builder().unwrap();
932 certificate.set_version(2).unwrap();
933 certificate
934 .set_serial_number(&random_serial_number())
935 .unwrap();
936 certificate.set_subject_name(&name).unwrap();
937 certificate.set_issuer_name(&name).unwrap();
938 certificate.set_pubkey(subject_key).unwrap();
939 certificate
940 .set_not_before(&Asn1Time::days_from_now(0).unwrap())
941 .unwrap();
942 certificate
943 .set_not_after(&Asn1Time::days_from_now(365).unwrap())
944 .unwrap();
945 certificate
946 .append_extension(BasicConstraints::new().build().unwrap())
947 .unwrap();
948 if let Some(key_usage_ext) = key_usage_ext {
949 certificate.append_extension(key_usage_ext).unwrap();
950 }
951 certificate
952 .sign(&signer_key, MessageDigest::sha256())
953 .unwrap();
954
955 certificate.build().to_der().unwrap()
956 }
957
958 fn random_serial_number() -> openssl::asn1::Asn1Integer {
959 let mut serial = BigNum::new().unwrap();
960 serial.rand(159, MsbOption::MAYBE_ZERO, false).unwrap();
961 serial.to_asn1_integer().unwrap()
962 }
963
964 fn make_payload(
965 archive_identity: &ArchiveIdentity,
966 metadata: &RecipientRecordMetadata,
967 identity: &ParsedRecipientIdentity,
968 suite: HpkeSuite,
969 ) -> Vec<u8> {
970 make_payload_with_lengths(
971 archive_identity,
972 metadata,
973 identity,
974 suite,
975 suite.enc_length,
976 suite.ciphertext_length,
977 )
978 }
979
980 fn make_payload_with_lengths(
981 archive_identity: &ArchiveIdentity,
982 metadata: &RecipientRecordMetadata,
983 identity: &ParsedRecipientIdentity,
984 suite: HpkeSuite,
985 enc_len: usize,
986 ciphertext_len: usize,
987 ) -> Vec<u8> {
988 let mut payload = vec![0u8; KEYWRAP_PROFILE_PAYLOAD_HEADER_LEN + enc_len + ciphertext_len];
989 payload[0..2].copy_from_slice(&KEYWRAP_PAYLOAD_VERSION.to_le_bytes());
990 payload[2..4].copy_from_slice(&suite.kem_id.to_le_bytes());
991 payload[4..6].copy_from_slice(&suite.kdf_id.to_le_bytes());
992 payload[6..8].copy_from_slice(&suite.aead_id.to_le_bytes());
993 payload[8..10].copy_from_slice(&u16::try_from(enc_len).unwrap().to_le_bytes());
994 payload[10..12].copy_from_slice(&u16::try_from(ciphertext_len).unwrap().to_le_bytes());
995 payload[16..48].copy_from_slice(&compute_key_wrap_context_digest(
996 archive_identity,
997 metadata,
998 identity,
999 suite,
1000 ));
1001
1002 let enc_start = KEYWRAP_PROFILE_PAYLOAD_HEADER_LEN;
1003 let enc_end = enc_start + enc_len;
1004 let ct_end = enc_end + ciphertext_len;
1005 for value in payload[enc_start..enc_end].iter_mut() {
1006 *value = 0xAA;
1007 }
1008 for value in payload[enc_end..ct_end].iter_mut() {
1009 *value = 0x55;
1010 }
1011
1012 payload
1013 }
1014
1015 fn recipient_record_input_with_payload(
1016 profile_payload: Vec<u8>,
1017 cert_der: &[u8],
1018 ) -> RecipientRecordInput {
1019 let identity_digest = sha256_digest(cert_der);
1020 RecipientRecordInput {
1021 archive_identity: archive_identity(),
1022 metadata: RecipientRecordMetadata {
1023 profile_id: KEYWRAP_PROFILE_ID,
1024 recipient_identity_type: KEYWRAP_RECIPIENT_IDENTITY_TYPE_BYTES,
1025 recipient_identity_digest: identity_digest,
1026 },
1027 recipient_identity_bytes: cert_der.to_vec(),
1028 profile_payload_bytes: profile_payload,
1029 }
1030 }
1031
1032 fn recipient_record_input_for_suite(cert_der: &[u8], suite: HpkeSuite) -> RecipientRecordInput {
1033 let identity = parse_x509_recipient_identity(cert_der).unwrap();
1034 let archive_identity = archive_identity();
1035 let metadata = RecipientRecordMetadata {
1036 profile_id: KEYWRAP_PROFILE_ID,
1037 recipient_identity_type: KEYWRAP_RECIPIENT_IDENTITY_TYPE_BYTES,
1038 recipient_identity_digest: identity.recipient_identity_digest,
1039 };
1040 let payload = make_payload(&archive_identity, &metadata, &identity, suite);
1041 RecipientRecordInput {
1042 archive_identity,
1043 metadata,
1044 recipient_identity_bytes: cert_der.to_vec(),
1045 profile_payload_bytes: payload,
1046 }
1047 }
1048
1049 fn recipient_record_input_from_record(
1050 archive_identity: ArchiveIdentity,
1051 record: RecipientRecordV1,
1052 ) -> RecipientRecordInput {
1053 RecipientRecordInput {
1054 archive_identity,
1055 metadata: RecipientRecordMetadata {
1056 profile_id: record.profile_id,
1057 recipient_identity_type: record.recipient_identity_type,
1058 recipient_identity_digest: record.recipient_identity_digest,
1059 },
1060 recipient_identity_bytes: record.recipient_identity_bytes,
1061 profile_payload_bytes: record.profile_payload_bytes,
1062 }
1063 }
1064
1065 #[test]
1066 fn malformed_recipient_identity_is_invalid() {
1067 let cert_der = b"not-a-certificate".to_vec();
1068 let profile_payload = vec![0u8; KEYWRAP_PROFILE_PAYLOAD_HEADER_LEN];
1069
1070 let input = recipient_record_input_with_payload(profile_payload, &cert_der);
1071 let result = dispatch_key_wrap_record(input, &NoMatchLookup);
1072
1073 assert!(matches!(result, KeyWrapOutcome::InvalidRecord));
1074 }
1075
1076 #[test]
1077 fn unsupported_identity_type_is_returned() {
1078 let cert_der = test_certificate_der();
1079 let identity = parse_x509_recipient_identity(&cert_der).unwrap();
1080
1081 let archive_identity = archive_identity();
1082 let suite =
1083 HpkeSuite::for_profile(X25519_KEM_ID, HKDF_SHA256_KDF_ID, CHACHA20POLY1305_AEAD_ID)
1084 .unwrap();
1085 let payload = make_payload(
1086 &archive_identity,
1087 &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 &identity,
1093 suite,
1094 );
1095 let mut input = recipient_record_input_with_payload(payload, &cert_der);
1096 input.metadata.recipient_identity_type = 0;
1097
1098 let result = dispatch_key_wrap_record(input, &NoMatchLookup);
1099
1100 assert!(matches!(
1101 result,
1102 KeyWrapOutcome::UnsupportedRecipientIdentity
1103 ));
1104 }
1105
1106 #[test]
1107 fn unsupported_suite_is_invalid() {
1108 let cert_der = test_certificate_der();
1109 let identity = parse_x509_recipient_identity(&cert_der).unwrap();
1110 let archive_identity = archive_identity();
1111 let metadata = RecipientRecordMetadata {
1112 profile_id: KEYWRAP_PROFILE_ID,
1113 recipient_identity_type: KEYWRAP_RECIPIENT_IDENTITY_TYPE_BYTES,
1114 recipient_identity_digest: identity.recipient_identity_digest,
1115 };
1116
1117 let unsupported_suite = HpkeSuite {
1118 kem_id: 0x00ff,
1119 kdf_id: 0x00ff,
1120 aead_id: 0x00ff,
1121 enc_length: 0,
1122 ciphertext_length: 0,
1123 };
1124
1125 let payload = make_payload_with_lengths(
1126 &archive_identity,
1127 &metadata,
1128 &identity,
1129 unsupported_suite,
1130 0,
1131 0,
1132 );
1133
1134 let input = recipient_record_input_with_payload(payload, &cert_der);
1135 let result = dispatch_key_wrap_record(input, &NoMatchLookup);
1136
1137 assert!(matches!(result, KeyWrapOutcome::UnsupportedSuite));
1138 }
1139
1140 #[test]
1141 fn wrong_lengths_rejected_as_invalid_record() {
1142 let cert_der = test_certificate_der();
1143 let identity = parse_x509_recipient_identity(&cert_der).unwrap();
1144 let archive_identity = archive_identity();
1145 let metadata = RecipientRecordMetadata {
1146 profile_id: KEYWRAP_PROFILE_ID,
1147 recipient_identity_type: KEYWRAP_RECIPIENT_IDENTITY_TYPE_BYTES,
1148 recipient_identity_digest: identity.recipient_identity_digest,
1149 };
1150 let suite =
1151 HpkeSuite::for_profile(X25519_KEM_ID, HKDF_SHA256_KDF_ID, CHACHA20POLY1305_AEAD_ID)
1152 .unwrap();
1153
1154 let payload = make_payload_with_lengths(
1155 &archive_identity,
1156 &metadata,
1157 &identity,
1158 suite,
1159 suite.enc_length - 1,
1160 suite.ciphertext_length,
1161 );
1162 let input = recipient_record_input_with_payload(payload, &cert_der);
1163
1164 let result = dispatch_key_wrap_record(input, &NoMatchLookup);
1165
1166 assert!(matches!(result, KeyWrapOutcome::InvalidRecord));
1167 }
1168
1169 #[test]
1170 fn header_field_mutations_rejected_as_invalid_record() {
1171 let cert_der = test_certificate_der();
1174 let identity = parse_x509_recipient_identity(&cert_der).unwrap();
1175 let archive_identity = archive_identity();
1176 let metadata = RecipientRecordMetadata {
1177 profile_id: KEYWRAP_PROFILE_ID,
1178 recipient_identity_type: KEYWRAP_RECIPIENT_IDENTITY_TYPE_BYTES,
1179 recipient_identity_digest: identity.recipient_identity_digest,
1180 };
1181 let suite =
1182 HpkeSuite::for_profile(X25519_KEM_ID, HKDF_SHA256_KDF_ID, CHACHA20POLY1305_AEAD_ID)
1183 .unwrap();
1184
1185 let mut payload = make_payload(&archive_identity, &metadata, &identity, suite);
1187 payload[0..2].copy_from_slice(&2u16.to_le_bytes());
1188 assert!(matches!(
1189 dispatch_key_wrap_record(
1190 recipient_record_input_with_payload(payload, &cert_der),
1191 &NoMatchLookup
1192 ),
1193 KeyWrapOutcome::InvalidRecord
1194 ));
1195
1196 let mut payload = make_payload(&archive_identity, &metadata, &identity, suite);
1198 payload[12..16].copy_from_slice(&1u32.to_le_bytes());
1199 assert!(matches!(
1200 dispatch_key_wrap_record(
1201 recipient_record_input_with_payload(payload, &cert_der),
1202 &NoMatchLookup
1203 ),
1204 KeyWrapOutcome::InvalidRecord
1205 ));
1206
1207 let mut payload = make_payload(&archive_identity, &metadata, &identity, suite);
1209 payload[48] = 0x01;
1210 assert!(matches!(
1211 dispatch_key_wrap_record(
1212 recipient_record_input_with_payload(payload, &cert_der),
1213 &NoMatchLookup
1214 ),
1215 KeyWrapOutcome::InvalidRecord
1216 ));
1217 }
1218
1219 #[test]
1220 fn concatenated_certificate_bytes_rejected_as_invalid_record() {
1221 let cert_der = test_certificate_der();
1224 let mut concatenated = cert_der.clone();
1225 concatenated.extend_from_slice(&test_certificate_der());
1226 let identity_digest = sha256_digest(&concatenated);
1227 let archive_identity = archive_identity();
1228 let metadata = RecipientRecordMetadata {
1229 profile_id: KEYWRAP_PROFILE_ID,
1230 recipient_identity_type: KEYWRAP_RECIPIENT_IDENTITY_TYPE_BYTES,
1231 recipient_identity_digest: identity_digest,
1232 };
1233 let identity = parse_x509_recipient_identity(&cert_der).unwrap();
1234 let suite =
1235 HpkeSuite::for_profile(X25519_KEM_ID, HKDF_SHA256_KDF_ID, CHACHA20POLY1305_AEAD_ID)
1236 .unwrap();
1237 let payload = make_payload(&archive_identity, &metadata, &identity, suite);
1238 let input = RecipientRecordInput {
1239 archive_identity,
1240 metadata,
1241 recipient_identity_bytes: concatenated,
1242 profile_payload_bytes: payload,
1243 };
1244
1245 let result = dispatch_key_wrap_record(input, &NoMatchLookup);
1246
1247 assert!(matches!(result, KeyWrapOutcome::InvalidRecord));
1248 }
1249
1250 #[test]
1251 fn wrong_context_digest_is_invalid_record() {
1252 let (cert_der, private_key) = x25519_recipient_material();
1253 let identity = parse_x509_recipient_identity(&cert_der).unwrap();
1254 let archive_identity = archive_identity();
1255 let metadata = RecipientRecordMetadata {
1256 profile_id: KEYWRAP_PROFILE_ID,
1257 recipient_identity_type: KEYWRAP_RECIPIENT_IDENTITY_TYPE_BYTES,
1258 recipient_identity_digest: identity.recipient_identity_digest,
1259 };
1260 let suite =
1261 HpkeSuite::for_profile(X25519_KEM_ID, HKDF_SHA256_KDF_ID, CHACHA20POLY1305_AEAD_ID)
1262 .unwrap();
1263 let mut payload = make_payload(&archive_identity, &metadata, &identity, suite);
1264 payload[16] ^= 0xFF;
1265
1266 let input = recipient_record_input_with_payload(payload, &cert_der);
1267 let result = dispatch_key_wrap_record(input, &StaticLookup { private_key });
1268
1269 assert!(matches!(result, KeyWrapOutcome::InvalidRecord));
1270 }
1271
1272 #[test]
1273 fn revision_45_never_retries_revision_44_hpke_aad() {
1274 let (cert_der, private_key) = x25519_recipient_material();
1275 let identity = parse_x509_recipient_identity(&cert_der).unwrap();
1276 let archive_identity = archive_identity();
1277 let metadata = RecipientRecordMetadata {
1278 profile_id: KEYWRAP_PROFILE_ID,
1279 recipient_identity_type: KEYWRAP_RECIPIENT_IDENTITY_TYPE_BYTES,
1280 recipient_identity_digest: identity.recipient_identity_digest,
1281 };
1282 let suite =
1283 HpkeSuite::for_profile(X25519_KEM_ID, HKDF_SHA256_KDF_ID, CHACHA20POLY1305_AEAD_ID)
1284 .unwrap();
1285 let payload = hpke_seal_master_key_with_aad_domain(
1286 &cert_der,
1287 &archive_identity,
1288 &metadata,
1289 &identity,
1290 &[0x42; 32],
1291 suite,
1292 b"tzap-keywrap-master-key-v44\0",
1293 )
1294 .unwrap();
1295 let input = RecipientRecordInput {
1296 archive_identity,
1297 metadata,
1298 recipient_identity_bytes: cert_der,
1299 profile_payload_bytes: payload,
1300 };
1301
1302 assert!(matches!(
1303 dispatch_key_wrap_record(input, &StaticLookup { private_key }),
1304 KeyWrapOutcome::InvalidRecord
1305 ));
1306 }
1307
1308 #[test]
1309 fn revision_44_archive_identity_is_rejected_without_compatibility_dispatch() {
1310 let (cert_der, _) = x25519_recipient_material();
1311 let mut identity = archive_identity();
1312 identity.volume_format_rev = 44;
1313 assert!(matches!(
1314 wrap_master_key_for_recipient(
1315 identity,
1316 &cert_der,
1317 &[0x42; 32],
1318 KeyWrapSuite::X25519HkdfSha256ChaCha20Poly1305,
1319 ),
1320 Err(KeyWrapOutcome::UnsupportedArchiveIdentity)
1321 ));
1322 }
1323
1324 #[test]
1325 fn no_matching_private_key_precedes_context_digest_check() {
1326 let (cert_der, _) = x25519_recipient_material();
1327 let identity = parse_x509_recipient_identity(&cert_der).unwrap();
1328 let archive_identity = archive_identity();
1329 let metadata = RecipientRecordMetadata {
1330 profile_id: KEYWRAP_PROFILE_ID,
1331 recipient_identity_type: KEYWRAP_RECIPIENT_IDENTITY_TYPE_BYTES,
1332 recipient_identity_digest: identity.recipient_identity_digest,
1333 };
1334 let suite =
1335 HpkeSuite::for_profile(X25519_KEM_ID, HKDF_SHA256_KDF_ID, CHACHA20POLY1305_AEAD_ID)
1336 .unwrap();
1337 let mut payload = make_payload(&archive_identity, &metadata, &identity, suite);
1338 payload[16] ^= 0xFF;
1339
1340 let input = recipient_record_input_with_payload(payload, &cert_der);
1341 let result = dispatch_key_wrap_record(input, &NoMatchLookup);
1342
1343 assert!(matches!(result, KeyWrapOutcome::NoMatchingPrivateKey));
1344 }
1345
1346 #[test]
1347 fn certificate_policy_rejection_precedes_context_digest_check() {
1348 let (cert_der, _) = x25519_recipient_material();
1349 let identity = parse_x509_recipient_identity(&cert_der).unwrap();
1350 let archive_identity = archive_identity();
1351 let metadata = RecipientRecordMetadata {
1352 profile_id: KEYWRAP_PROFILE_ID,
1353 recipient_identity_type: KEYWRAP_RECIPIENT_IDENTITY_TYPE_BYTES,
1354 recipient_identity_digest: identity.recipient_identity_digest,
1355 };
1356 let suite =
1357 HpkeSuite::for_profile(X25519_KEM_ID, HKDF_SHA256_KDF_ID, CHACHA20POLY1305_AEAD_ID)
1358 .unwrap();
1359 let mut payload = make_payload(&archive_identity, &metadata, &identity, suite);
1360 payload[16] ^= 0xFF;
1361
1362 let input = recipient_record_input_with_payload(payload, &cert_der);
1363 let result = dispatch_key_wrap_record(input, &RejectLookup);
1364
1365 assert!(matches!(result, KeyWrapOutcome::CertificatePolicyRejected));
1366 }
1367
1368 #[test]
1369 fn valid_structure_with_no_matching_private_key_is_no_matching() {
1370 let archive_identity = archive_identity();
1371 let (cert_der, _) = x25519_recipient_material();
1372 let master_key = [0x42u8; 32];
1373 let record = wrap_master_key_for_recipient(
1374 archive_identity.clone(),
1375 &cert_der,
1376 &master_key,
1377 KeyWrapSuite::X25519HkdfSha256ChaCha20Poly1305,
1378 )
1379 .unwrap();
1380 let input = recipient_record_input_from_record(archive_identity, record);
1381
1382 let result = dispatch_key_wrap_record(input, &NoMatchLookup);
1383
1384 assert!(matches!(result, KeyWrapOutcome::NoMatchingPrivateKey));
1385 }
1386
1387 #[test]
1388 fn key_usage_without_key_agreement_is_invalid_record() {
1389 let cert_der = test_certificate_der_with_bad_key_usage();
1390 let payload = vec![0u8; KEYWRAP_PROFILE_PAYLOAD_HEADER_LEN];
1391 let input = recipient_record_input_with_payload(payload, &cert_der);
1392
1393 let result = dispatch_key_wrap_record(input, &NoMatchLookup);
1394
1395 assert!(matches!(result, KeyWrapOutcome::InvalidRecord));
1396 }
1397
1398 #[test]
1399 fn malformed_key_usage_extension_is_invalid_record() {
1400 let cert_der = test_certificate_der_with_malformed_key_usage();
1401 let payload = vec![0u8; KEYWRAP_PROFILE_PAYLOAD_HEADER_LEN];
1402 let input = recipient_record_input_with_payload(payload, &cert_der);
1403
1404 let result = dispatch_key_wrap_record(input, &NoMatchLookup);
1405
1406 assert!(matches!(result, KeyWrapOutcome::InvalidRecord));
1407 }
1408
1409 #[test]
1410 fn certificate_policy_rejection_is_reported() {
1411 let archive_identity = archive_identity();
1412 let (cert_der, _) = x25519_recipient_material();
1413 let master_key = [0x42u8; 32];
1414 let record = wrap_master_key_for_recipient(
1415 archive_identity.clone(),
1416 &cert_der,
1417 &master_key,
1418 KeyWrapSuite::X25519HkdfSha256ChaCha20Poly1305,
1419 )
1420 .unwrap();
1421 let input = recipient_record_input_from_record(archive_identity, record);
1422
1423 let result = dispatch_key_wrap_record(input, &RejectLookup);
1424
1425 assert!(matches!(result, KeyWrapOutcome::CertificatePolicyRejected));
1426 }
1427
1428 #[test]
1429 fn suite_certificate_mismatch_is_unsupported_before_private_key_lookup() {
1430 let (p256_cert_der, _) = p256_recipient_material();
1431 let x25519_suite =
1432 HpkeSuite::for_profile(X25519_KEM_ID, HKDF_SHA256_KDF_ID, CHACHA20POLY1305_AEAD_ID)
1433 .unwrap();
1434 let result = dispatch_key_wrap_record(
1435 recipient_record_input_for_suite(&p256_cert_der, x25519_suite),
1436 &NoMatchLookup,
1437 );
1438 assert!(matches!(result, KeyWrapOutcome::UnsupportedSuite));
1439
1440 let (x25519_cert_der, _) = x25519_recipient_material();
1441 let p256_suite =
1442 HpkeSuite::for_profile(P256_KEM_ID, HKDF_SHA256_KDF_ID, AES256GCM_AEAD_ID).unwrap();
1443 let result = dispatch_key_wrap_record(
1444 recipient_record_input_for_suite(&x25519_cert_der, p256_suite),
1445 &NoMatchLookup,
1446 );
1447 assert!(matches!(result, KeyWrapOutcome::UnsupportedSuite));
1448
1449 let p384_cert_der = p384_recipient_certificate();
1450 let result = dispatch_key_wrap_record(
1451 recipient_record_input_for_suite(&p384_cert_der, p256_suite),
1452 &NoMatchLookup,
1453 );
1454 assert!(matches!(result, KeyWrapOutcome::UnsupportedSuite));
1455 }
1456
1457 #[test]
1458 fn writer_reports_unsupported_suite_for_certificate_mismatch() {
1459 let master_key = [0x42u8; 32];
1460 let (p256_cert_der, _) = p256_recipient_material();
1461 let result = wrap_master_key_for_recipient(
1462 archive_identity(),
1463 &p256_cert_der,
1464 &master_key,
1465 KeyWrapSuite::X25519HkdfSha256ChaCha20Poly1305,
1466 );
1467 assert!(matches!(result, Err(KeyWrapOutcome::UnsupportedSuite)));
1468
1469 let (x25519_cert_der, _) = x25519_recipient_material();
1470 let result = wrap_master_key_for_recipient(
1471 archive_identity(),
1472 &x25519_cert_der,
1473 &master_key,
1474 KeyWrapSuite::P256HkdfSha256Aes256Gcm,
1475 );
1476 assert!(matches!(result, Err(KeyWrapOutcome::UnsupportedSuite)));
1477 }
1478
1479 #[test]
1480 fn x25519_wrap_unwrap_returns_candidate_master_key() {
1481 let (cert_der, private_key) = x25519_recipient_material();
1482 let identity = parse_x509_recipient_identity(&cert_der).unwrap();
1483 let archive_identity = archive_identity();
1484 let master_key = [0x42u8; 32];
1485 let record = wrap_master_key_for_recipient(
1486 archive_identity.clone(),
1487 &cert_der,
1488 &master_key,
1489 KeyWrapSuite::X25519HkdfSha256ChaCha20Poly1305,
1490 )
1491 .unwrap();
1492 let input = recipient_record_input_from_record(archive_identity, record);
1493
1494 let result = dispatch_key_wrap_record(input, &StaticLookup { private_key });
1495
1496 assert!(matches!(
1497 result,
1498 KeyWrapOutcome::UnwrappedCandidateMasterKey {
1499 master_key: actual_master_key,
1500 recipient_spki_digest: actual_digest
1501 } if actual_master_key == master_key && actual_digest == identity.recipient_spki_digest
1502 ));
1503 }
1504
1505 #[test]
1506 fn p256_wrap_unwrap_returns_candidate_master_key() {
1507 let (cert_der, private_key) = p256_recipient_material();
1508 let identity = parse_x509_recipient_identity(&cert_der).unwrap();
1509 let archive_identity = archive_identity();
1510 let master_key = [0x24u8; 32];
1511 let record = wrap_master_key_for_recipient(
1512 archive_identity.clone(),
1513 &cert_der,
1514 &master_key,
1515 KeyWrapSuite::P256HkdfSha256Aes256Gcm,
1516 )
1517 .unwrap();
1518 let input = recipient_record_input_from_record(archive_identity, record);
1519
1520 let result = dispatch_key_wrap_record(input, &StaticLookup { private_key });
1521
1522 assert!(matches!(
1523 result,
1524 KeyWrapOutcome::UnwrappedCandidateMasterKey {
1525 master_key: actual_master_key,
1526 recipient_spki_digest: actual_digest
1527 } if actual_master_key == master_key && actual_digest == identity.recipient_spki_digest
1528 ));
1529 }
1530
1531 #[test]
1532 fn multi_recipient_records_open_independently_with_matching_private_key() {
1533 let archive_identity = archive_identity();
1534 let master_key = [0x33u8; 32];
1535 let (bob_cert, bob_key) = x25519_recipient_material();
1536 let (carol_cert, carol_key) = p256_recipient_material();
1537 let (ops_cert, ops_key) = x25519_recipient_material();
1538 let recipients = [
1539 (
1540 bob_cert,
1541 bob_key,
1542 KeyWrapSuite::X25519HkdfSha256ChaCha20Poly1305,
1543 ),
1544 (carol_cert, carol_key, KeyWrapSuite::P256HkdfSha256Aes256Gcm),
1545 (
1546 ops_cert,
1547 ops_key,
1548 KeyWrapSuite::X25519HkdfSha256ChaCha20Poly1305,
1549 ),
1550 ];
1551 let records = recipients
1552 .iter()
1553 .map(|(cert_der, _, suite)| {
1554 wrap_master_key_for_recipient(
1555 archive_identity.clone(),
1556 cert_der,
1557 &master_key,
1558 *suite,
1559 )
1560 .unwrap()
1561 })
1562 .collect::<Vec<_>>();
1563
1564 for (cert_der, private_key, _) in recipients {
1565 let lookup = MatchingLookup {
1566 matches: vec![(cert_der.clone(), private_key)],
1567 };
1568 let unwrapped = records
1569 .clone()
1570 .into_iter()
1571 .filter_map(|record| {
1572 let input =
1573 recipient_record_input_from_record(archive_identity.clone(), record);
1574 match dispatch_key_wrap_record(input, &lookup) {
1575 KeyWrapOutcome::UnwrappedCandidateMasterKey {
1576 master_key: candidate,
1577 ..
1578 } => Some(candidate),
1579 KeyWrapOutcome::NoMatchingPrivateKey => None,
1580 other => panic!("unexpected key-wrap outcome: {other:?}"),
1581 }
1582 })
1583 .collect::<Vec<_>>();
1584
1585 assert_eq!(unwrapped, vec![master_key]);
1586 }
1587 }
1588
1589 #[test]
1590 fn wrong_private_key_is_invalid_record() {
1591 let (cert_der, _private_key) = x25519_recipient_material();
1592 let (_wrong_cert_der, wrong_private_key) = x25519_recipient_material();
1593 let archive_identity = archive_identity();
1594 let master_key = [0x5au8; 32];
1595 let record = wrap_master_key_for_recipient(
1596 archive_identity.clone(),
1597 &cert_der,
1598 &master_key,
1599 KeyWrapSuite::X25519HkdfSha256ChaCha20Poly1305,
1600 )
1601 .unwrap();
1602 let input = recipient_record_input_from_record(archive_identity, record);
1603
1604 let result = dispatch_key_wrap_record(
1605 input,
1606 &StaticLookup {
1607 private_key: wrong_private_key,
1608 },
1609 );
1610
1611 assert!(matches!(result, KeyWrapOutcome::InvalidRecord));
1612 }
1613
1614 #[test]
1615 fn signing_certificate_key_usage_does_not_satisfy_recipient_wrap() {
1616 let cert_der = test_certificate_der_with_bad_key_usage();
1617 let master_key = [0x42u8; 32];
1618
1619 let result = wrap_master_key_for_recipient(
1620 archive_identity(),
1621 &cert_der,
1622 &master_key,
1623 KeyWrapSuite::X25519HkdfSha256ChaCha20Poly1305,
1624 );
1625
1626 assert!(matches!(result, Err(KeyWrapOutcome::InvalidRecord)));
1627 }
1628}