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