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