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