1use cbc::cipher::block_padding::NoPadding;
25use cbc::cipher::{BlockDecryptMut, BlockEncryptMut, KeyIvInit};
26use des::TdesEde3;
27use hmac::Hmac;
28use sha1::Sha1;
29
30use crate::acl::AclBlob;
31use crate::cssm::{KeyHeader, WrappedKeyFields};
32use crate::error::{Error, Result};
33
34type Decryptor = cbc::Decryptor<TdesEde3>;
35type Encryptor = cbc::Encryptor<TdesEde3>;
36
37pub const BLOB_MAGIC: u32 = 0xfade_0711;
39
40pub const BLOB_VERSION_MACOS_10_0: u32 = 0x0000_0100;
43
44pub const BLOB_VERSION_MACOS_10_1: u32 = 0x0000_0101;
46
47pub const BLOB_VERSION_PARTITION: u32 = 0x0000_0200;
50
51pub const BLOB_VERSION: u32 = BLOB_VERSION_MACOS_10_0;
54
55pub const BLOCK_SIZE: usize = 8;
57
58pub const KEY_LEN: usize = 24;
60
61pub const SALT_LEN: usize = 20;
63
64pub const PBKDF2_ITERATIONS: u32 = 1000;
67
68pub const DB_BLOB_LEN: usize = 92;
70
71pub const KEY_BLOB_LEN: usize = 136;
73
74pub const MAGIC_CMS_IV: [u8; 8] = [0x4a, 0xdd, 0xa2, 0x2c, 0x79, 0xe8, 0x21, 0x05];
77
78pub const SSGP_MAGIC: &[u8; 4] = b"ssgp";
80
81pub use crate::secret::SecretBytes;
83
84pub fn master_key(password: &[u8], salt: &[u8]) -> SecretBytes {
86 let mut key = [0u8; KEY_LEN];
87 pbkdf2::pbkdf2::<Hmac<Sha1>>(password, salt, PBKDF2_ITERATIONS, &mut key)
88 .expect("PBKDF2 output length is valid");
89 SecretBytes::new(key.as_slice())
90}
91
92pub fn decrypt(key: &[u8], iv: &[u8], data: &[u8]) -> Result<Vec<u8>> {
98 if key.len() != KEY_LEN {
99 return Err(Error::Crypto("3DES key must be 24 bytes"));
100 }
101 if iv.len() != BLOCK_SIZE {
102 return Err(Error::Crypto("3DES IV must be 8 bytes"));
103 }
104 if data.is_empty() || !data.len().is_multiple_of(BLOCK_SIZE) {
105 return Err(Error::Crypto(
106 "ciphertext length is not a multiple of the block size",
107 ));
108 }
109
110 let mut buffer = data.to_vec();
111 Decryptor::new_from_slices(key, iv)
112 .map_err(|_| Error::Crypto("invalid 3DES key or IV"))?
113 .decrypt_padded_mut::<NoPadding>(&mut buffer)
114 .map_err(|_| Error::Crypto("3DES decryption failed"))?;
115
116 let pad = *buffer.last().expect("non-empty") as usize;
117 if pad == 0 || pad > BLOCK_SIZE || pad > buffer.len() {
118 return Err(Error::WrongPassword);
119 }
120 if buffer[buffer.len() - pad..]
121 .iter()
122 .any(|byte| *byte as usize != pad)
123 {
124 return Err(Error::WrongPassword);
125 }
126 buffer.truncate(buffer.len() - pad);
127 Ok(buffer)
128}
129
130pub fn encrypt(key: &[u8], iv: &[u8], data: &[u8]) -> Result<Vec<u8>> {
132 if key.len() != KEY_LEN {
133 return Err(Error::Crypto("3DES key must be 24 bytes"));
134 }
135 if iv.len() != BLOCK_SIZE {
136 return Err(Error::Crypto("3DES IV must be 8 bytes"));
137 }
138
139 let pad = BLOCK_SIZE - (data.len() % BLOCK_SIZE);
142 let mut buffer = Vec::with_capacity(data.len() + pad);
143 buffer.extend_from_slice(data);
144 buffer.extend(std::iter::repeat_n(pad as u8, pad));
145
146 let length = buffer.len();
147 Encryptor::new_from_slices(key, iv)
148 .map_err(|_| Error::Crypto("invalid 3DES key or IV"))?
149 .encrypt_padded_mut::<NoPadding>(&mut buffer, length)
150 .map_err(|_| Error::Crypto("3DES encryption failed"))?;
151 Ok(buffer)
152}
153
154pub fn sign_blob(version: u32, key: &[u8], chunks: &[&[u8]]) -> [u8; 20] {
162 if version == BLOB_VERSION_MACOS_10_0 {
163 return legacy_hmac_sha1(key, chunks);
164 }
165 let mut mac =
166 <Hmac<Sha1> as hmac::Mac>::new_from_slice(key).expect("HMAC takes any key length");
167 for chunk in chunks {
168 hmac::Mac::update(&mut mac, chunk);
169 }
170 hmac::Mac::finalize(mac).into_bytes().into()
171}
172
173pub fn legacy_hmac_sha1(key: &[u8], chunks: &[&[u8]]) -> [u8; 20] {
195 use sha1::Digest as _;
196
197 let mut k_ipad = [0x36u8; 64];
200 let mut k_opad = [0x5cu8; 64];
201 for (index, byte) in key.iter().take(64).enumerate() {
202 k_ipad[index] = byte ^ 0x36;
203 k_opad[index] = byte ^ 0x5c;
204 }
205
206 let mut context = Sha1::new();
207 for chunk in chunks {
208 context.update(k_ipad);
209 context.update(chunk);
210 let inner = context.finalize_reset();
211 context.update(k_opad);
214 context.update(inner);
215 }
216 context.finalize().into()
217}
218
219#[derive(Debug, Clone)]
221pub struct DbBlob {
222 pub version: u32,
223 pub start_crypto_blob: u32,
225 pub total_length: u32,
226 pub random_signature: [u8; 16],
227 pub sequence: u32,
228 pub idle_timeout: u32,
229 pub lock_on_sleep: bool,
230 pub salt: [u8; SALT_LEN],
231 pub iv: [u8; BLOCK_SIZE],
232 pub blob_signature: [u8; 20],
236 pub public_acl: Vec<u8>,
239 pub crypto_blob: Vec<u8>,
241}
242
243impl DbBlob {
244 pub fn parse(data: &[u8]) -> Result<Self> {
245 if data.len() < DB_BLOB_LEN {
246 return Err(Error::format(
247 "database blob is shorter than its fixed header",
248 ));
249 }
250 let magic = be32(data, 0);
251 if magic != BLOB_MAGIC {
252 return Err(Error::format(format!(
253 "database blob magic is 0x{magic:08x}, expected 0x{BLOB_MAGIC:08x}"
254 )));
255 }
256
257 let start_crypto_blob = be32(data, 8);
258 let total_length = be32(data, 12);
259 if (start_crypto_blob as usize) < DB_BLOB_LEN
260 || total_length < start_crypto_blob
261 || total_length as usize > data.len()
262 {
263 return Err(Error::format("database blob offsets are inconsistent"));
264 }
265
266 Ok(Self {
267 version: be32(data, 4),
268 start_crypto_blob,
269 total_length,
270 random_signature: data[16..32].try_into().expect("16 bytes"),
271 sequence: be32(data, 32),
272 idle_timeout: be32(data, 36),
273 lock_on_sleep: data[40] != 0,
274 salt: data[44..64].try_into().expect("20 bytes"),
275 iv: data[64..72].try_into().expect("8 bytes"),
276 blob_signature: data[72..92].try_into().expect("20 bytes"),
277 public_acl: data[DB_BLOB_LEN..start_crypto_blob as usize].to_vec(),
278 crypto_blob: data[start_crypto_blob as usize..total_length as usize].to_vec(),
279 })
280 }
281
282 pub fn to_bytes(&self) -> Vec<u8> {
283 let start_crypto_blob = (DB_BLOB_LEN + self.public_acl.len()) as u32;
284 let total_length = start_crypto_blob + self.crypto_blob.len() as u32;
285
286 let mut out = Vec::with_capacity(total_length as usize);
287 out.extend_from_slice(&BLOB_MAGIC.to_be_bytes());
288 out.extend_from_slice(&self.version.to_be_bytes());
289 out.extend_from_slice(&start_crypto_blob.to_be_bytes());
290 out.extend_from_slice(&total_length.to_be_bytes());
291 out.extend_from_slice(&self.random_signature);
292 out.extend_from_slice(&self.sequence.to_be_bytes());
293 out.extend_from_slice(&self.idle_timeout.to_be_bytes());
294 out.push(u8::from(self.lock_on_sleep));
297 out.extend_from_slice(&[0, 0, 0]);
298 out.extend_from_slice(&self.salt);
299 out.extend_from_slice(&self.iv);
300 out.extend_from_slice(&self.blob_signature);
301 out.extend_from_slice(&self.public_acl);
302 out.extend_from_slice(&self.crypto_blob);
303 out
304 }
305
306 pub fn unlock(&self, password: &[u8]) -> Result<DbKeys> {
308 let master = master_key(password, &self.salt);
309 let plain = decrypt(master.as_slice(), &self.iv, &self.crypto_blob)?;
310 DbKeys::parse(&plain)
311 }
312
313 pub fn seal(&mut self, password: &[u8], keys: &DbKeys) -> Result<()> {
315 let master = master_key(password, &self.salt);
316 self.crypto_blob = encrypt(master.as_slice(), &self.iv, &keys.to_bytes())?;
317 self.start_crypto_blob = (DB_BLOB_LEN + self.public_acl.len()) as u32;
318 self.total_length = self.start_crypto_blob + self.crypto_blob.len() as u32;
319 self.sign(keys.signing_key.as_slice());
320 Ok(())
321 }
322
323 const SIGNATURE_OFFSET: usize = 72;
326
327 fn signed_chunks(bytes: &[u8]) -> [&[u8]; 2] {
331 [&bytes[..Self::SIGNATURE_OFFSET], &bytes[DB_BLOB_LEN..]]
332 }
333
334 pub fn sign(&mut self, signing_key: &[u8]) {
336 self.blob_signature = [0u8; 20];
337 let bytes = self.to_bytes();
338 self.blob_signature = sign_blob(self.version, signing_key, &Self::signed_chunks(&bytes));
339 }
340
341 pub fn verify(&self, signing_key: &[u8]) -> bool {
343 let bytes = self.to_bytes();
344 sign_blob(self.version, signing_key, &Self::signed_chunks(&bytes)) == self.blob_signature
345 }
346}
347
348#[derive(Debug, Clone)]
350pub struct DbKeys {
351 pub encryption_key: SecretBytes,
353 pub signing_key: SecretBytes,
355 pub private_acl: Vec<u8>,
357}
358
359impl DbKeys {
360 pub fn parse(plain: &[u8]) -> Result<Self> {
361 if plain.len() < KEY_LEN + 20 {
362 return Err(Error::Crypto("database key blob is too short"));
363 }
364 Ok(Self {
365 encryption_key: SecretBytes::new(&plain[..KEY_LEN]),
366 signing_key: SecretBytes::new(&plain[KEY_LEN..KEY_LEN + 20]),
367 private_acl: plain[KEY_LEN + 20..].to_vec(),
368 })
369 }
370
371 pub fn to_bytes(&self) -> Vec<u8> {
372 let mut out = Vec::with_capacity(KEY_LEN + 20 + self.private_acl.len());
373 out.extend_from_slice(self.encryption_key.as_slice());
374 out.extend_from_slice(self.signing_key.as_slice());
375 out.extend_from_slice(&self.private_acl);
376 out
377 }
378}
379
380#[derive(Debug, Clone)]
382pub struct KeyBlob {
383 pub version: u32,
384 pub start_crypto_blob: u32,
385 pub total_length: u32,
386 pub iv: [u8; BLOCK_SIZE],
387 pub header: KeyHeader,
389 pub wrapped: WrappedKeyFields,
391 pub blob_signature: [u8; 20],
392 pub public_acl: PublicAcl,
395 pub crypto_blob: Vec<u8>,
396}
397
398#[derive(Debug, Clone, PartialEq, Eq)]
400pub enum PublicAcl {
401 Parsed(AclBlob),
402 Raw(Vec<u8>),
403}
404
405impl PublicAcl {
406 pub fn parse(data: &[u8]) -> Self {
407 match AclBlob::parse(data) {
408 Ok(blob) if blob.to_bytes() == data => Self::Parsed(blob),
409 _ => Self::Raw(data.to_vec()),
412 }
413 }
414
415 pub fn to_bytes(&self) -> Vec<u8> {
416 match self {
417 Self::Parsed(blob) => blob.to_bytes(),
418 Self::Raw(bytes) => bytes.clone(),
419 }
420 }
421
422 pub fn len(&self) -> usize {
423 match self {
424 Self::Parsed(blob) => blob.encoded_len(),
425 Self::Raw(bytes) => bytes.len(),
426 }
427 }
428
429 pub fn is_empty(&self) -> bool {
430 self.len() == 0
431 }
432
433 pub fn item_name(&self) -> Option<&str> {
435 match self {
436 Self::Parsed(blob) => blob.item_name(),
437 Self::Raw(_) => None,
438 }
439 }
440
441 pub fn trusted_paths(&self) -> Vec<&str> {
444 match self {
445 Self::Parsed(blob) => blob.trusted_paths(),
446 Self::Raw(_) => Vec::new(),
447 }
448 }
449}
450
451impl KeyBlob {
452 pub fn parse(data: &[u8]) -> Result<Self> {
453 if data.len() < KEY_BLOB_LEN {
454 return Err(Error::format("key blob is shorter than its fixed header"));
455 }
456 let magic = be32(data, 0);
457 if magic != BLOB_MAGIC {
458 return Err(Error::format(format!(
459 "key blob magic is 0x{magic:08x}, expected 0x{BLOB_MAGIC:08x}"
460 )));
461 }
462
463 let start_crypto_blob = be32(data, 8);
464 let total_length = be32(data, 12);
465 if (start_crypto_blob as usize) < KEY_BLOB_LEN
466 || total_length < start_crypto_blob
467 || total_length as usize > data.len()
468 {
469 return Err(Error::format("key blob offsets are inconsistent"));
470 }
471
472 Ok(Self {
473 version: be32(data, 4),
474 start_crypto_blob,
475 total_length,
476 iv: data[16..24].try_into().expect("8 bytes"),
477 header: KeyHeader::parse(&data[24..100])?,
478 wrapped: WrappedKeyFields::parse(&data[100..116])?,
479 blob_signature: data[116..136].try_into().expect("20 bytes"),
480 public_acl: PublicAcl::parse(&data[KEY_BLOB_LEN..start_crypto_blob as usize]),
481 crypto_blob: data[start_crypto_blob as usize..total_length as usize].to_vec(),
482 })
483 }
484
485 pub fn to_bytes(&self) -> Vec<u8> {
486 let acl = self.public_acl.to_bytes();
487 let start_crypto_blob = (KEY_BLOB_LEN + acl.len()) as u32;
488 let total_length = start_crypto_blob + self.crypto_blob.len() as u32;
489
490 let mut out = Vec::with_capacity(total_length as usize);
491 out.extend_from_slice(&BLOB_MAGIC.to_be_bytes());
492 out.extend_from_slice(&self.version.to_be_bytes());
493 out.extend_from_slice(&start_crypto_blob.to_be_bytes());
494 out.extend_from_slice(&total_length.to_be_bytes());
495 out.extend_from_slice(&self.iv);
496 self.header.write(&mut out);
497 self.wrapped.write(&mut out);
498 out.extend_from_slice(&self.blob_signature);
499 out.extend_from_slice(&acl);
500 out.extend_from_slice(&self.crypto_blob);
501 out
502 }
503
504 pub fn unwrap_key(&self, encryption_key: &[u8]) -> Result<SecretBytes> {
506 unwrap_key(encryption_key, &self.iv, &self.crypto_blob)
507 }
508
509 const SIGNATURE_OFFSET: usize = 116;
511
512 fn signed_chunks(bytes: &[u8]) -> [&[u8]; 2] {
513 [&bytes[..Self::SIGNATURE_OFFSET], &bytes[KEY_BLOB_LEN..]]
514 }
515
516 pub fn sign(&mut self, signing_key: &[u8]) {
519 self.blob_signature = [0u8; 20];
520 let bytes = self.to_bytes();
521 self.blob_signature = sign_blob(self.version, signing_key, &Self::signed_chunks(&bytes));
522 }
523
524 pub fn verify(&self, signing_key: &[u8]) -> bool {
525 let bytes = self.to_bytes();
526 sign_blob(self.version, signing_key, &Self::signed_chunks(&bytes)) == self.blob_signature
527 }
528}
529
530pub fn unwrap_key(encryption_key: &[u8], iv: &[u8], wrapped: &[u8]) -> Result<SecretBytes> {
551 let material = unwrap_blob(encryption_key, iv, wrapped)?;
552 if material.as_slice().len() != KEY_LEN {
553 return Err(Error::Crypto("unwrapped key is not 24 bytes"));
554 }
555 Ok(material)
556}
557
558pub fn unwrap_blob(encryption_key: &[u8], iv: &[u8], wrapped: &[u8]) -> Result<SecretBytes> {
561 let mut outer = decrypt(encryption_key, &MAGIC_CMS_IV, wrapped)?;
562 if outer.len() < BLOCK_SIZE + 2 * BLOCK_SIZE {
563 return Err(Error::Crypto("wrapped key is too short"));
564 }
565 outer.reverse();
566
567 let (embedded_iv, inner_ciphertext) = outer.split_at(BLOCK_SIZE);
568 let iv = if embedded_iv == iv { embedded_iv } else { iv };
570
571 let inner = decrypt(encryption_key, iv, inner_ciphertext)?;
572 if inner.len() < 4 {
573 return Err(Error::Crypto(
574 "unwrapped key has no descriptive-data length",
575 ));
576 }
577 let descriptive_len = u32::from_be_bytes([inner[0], inner[1], inner[2], inner[3]]) as usize;
578 let key_at = 4 + descriptive_len;
579 if inner.len() <= key_at {
580 return Err(Error::Crypto("unwrapped key is too short"));
581 }
582 Ok(SecretBytes::new(&inner[key_at..]))
585}
586
587pub fn wrap_key(encryption_key: &[u8], iv: &[u8], key: &[u8]) -> Result<Vec<u8>> {
589 if key.len() != KEY_LEN {
590 return Err(Error::Crypto("item key must be 24 bytes"));
591 }
592 wrap_blob(encryption_key, iv, key)
593}
594
595pub fn wrap_blob(encryption_key: &[u8], iv: &[u8], key: &[u8]) -> Result<Vec<u8>> {
597 if iv.len() != BLOCK_SIZE {
598 return Err(Error::Crypto("3DES IV must be 8 bytes"));
599 }
600
601 let mut inner = Vec::with_capacity(4 + key.len());
604 inner.extend_from_slice(&0u32.to_be_bytes());
605 inner.extend_from_slice(key);
606
607 let mut buffer = iv.to_vec();
608 buffer.extend_from_slice(&encrypt(encryption_key, iv, &inner)?);
609 buffer.reverse();
610 encrypt(encryption_key, &MAGIC_CMS_IV, &buffer)
611}
612
613#[derive(Debug, Clone)]
615pub struct Ssgp {
616 pub label: [u8; 20],
618 pub iv: [u8; BLOCK_SIZE],
619 pub ciphertext: Vec<u8>,
620}
621
622pub const SSGP_HEADER_LEN: usize = 28;
624
625impl Ssgp {
626 pub fn parse(data: &[u8]) -> Result<Self> {
627 if data.len() <= SSGP_HEADER_LEN {
628 return Err(Error::format("item has no secure-storage payload"));
629 }
630 if &data[..4] != SSGP_MAGIC {
631 return Err(Error::format("item payload is not a secure-storage group"));
632 }
633 Ok(Self {
634 label: data[..20].try_into().expect("20 bytes"),
635 iv: data[20..28].try_into().expect("8 bytes"),
636 ciphertext: data[SSGP_HEADER_LEN..].to_vec(),
637 })
638 }
639
640 pub fn to_bytes(&self) -> Vec<u8> {
641 let mut out = Vec::with_capacity(SSGP_HEADER_LEN + self.ciphertext.len());
642 out.extend_from_slice(&self.label);
643 out.extend_from_slice(&self.iv);
644 out.extend_from_slice(&self.ciphertext);
645 out
646 }
647
648 pub fn seal(
650 label: [u8; 20],
651 iv: [u8; BLOCK_SIZE],
652 item_key: &[u8],
653 secret: &[u8],
654 ) -> Result<Self> {
655 Ok(Self {
656 label,
657 iv,
658 ciphertext: encrypt(item_key, &iv, secret)?,
659 })
660 }
661
662 pub fn open(&self, item_key: &[u8]) -> Result<SecretBytes> {
663 Ok(SecretBytes::new(decrypt(
664 item_key,
665 &self.iv,
666 &self.ciphertext,
667 )?))
668 }
669
670 pub fn label_hex(&self) -> String {
672 hex::encode(&self.label[4..])
673 }
674}
675
676fn be32(data: &[u8], at: usize) -> u32 {
677 u32::from_be_bytes([data[at], data[at + 1], data[at + 2], data[at + 3]])
678}
679
680#[cfg(test)]
681mod tests {
682 use super::*;
683
684 #[test]
687 fn pbkdf2_matches_rfc_6070() {
688 let mut out = [0u8; 20];
689 pbkdf2::pbkdf2::<Hmac<Sha1>>(b"password", b"salt", 4096, &mut out).unwrap();
690 assert_eq!(hex::encode(out), "4b007901b765489abead49d926f721d065a429c1");
691 }
692
693 #[test]
696 fn master_key_uses_the_formats_parameters() {
697 let key = master_key(b"password", b"salt");
698 assert_eq!(key.as_slice().len(), KEY_LEN);
699 assert_eq!(
700 hex::encode(key.as_slice()),
701 "6e88be8bad7eae9d9e10aa061224034fed48d03fcbad968b"
702 );
703 }
704
705 #[test]
706 fn encrypt_then_decrypt_round_trips_with_cssm_padding() {
707 let key = [0x11u8; KEY_LEN];
708 let iv = [0x22u8; BLOCK_SIZE];
709 for plaintext in [
710 b"".to_vec(),
711 b"a".to_vec(),
712 b"12345678".to_vec(), b"123456789".to_vec(), vec![0xffu8; 64],
715 ] {
716 let sealed = encrypt(&key, &iv, &plaintext).unwrap();
717 assert_eq!(sealed.len() % BLOCK_SIZE, 0);
718 assert!(sealed.len() > plaintext.len(), "padding is always added");
719 assert_eq!(decrypt(&key, &iv, &sealed).unwrap(), plaintext);
720 }
721 }
722
723 #[test]
724 fn a_wrong_key_is_reported_as_a_wrong_password() {
725 let iv = [0x22u8; BLOCK_SIZE];
726 let sealed = encrypt(&[0x11u8; KEY_LEN], &iv, b"secret data here").unwrap();
727 assert!(matches!(
729 decrypt(&[0x33u8; KEY_LEN], &iv, &sealed),
730 Err(Error::WrongPassword) | Err(Error::Crypto(_))
731 ));
732 }
733
734 #[test]
735 fn decrypt_rejects_malformed_inputs() {
736 let key = [0x11u8; KEY_LEN];
737 let iv = [0x22u8; BLOCK_SIZE];
738 assert!(decrypt(&key, &iv, b"").is_err());
739 assert!(
740 decrypt(&key, &iv, b"1234567").is_err(),
741 "not a block multiple"
742 );
743 assert!(decrypt(&key[..8], &iv, b"12345678").is_err(), "short key");
744 assert!(decrypt(&key, &iv[..4], b"12345678").is_err(), "short IV");
745 }
746
747 #[test]
748 fn key_wrapping_round_trips() {
749 let encryption_key = [0x5au8; KEY_LEN];
750 let iv = [0x77u8; BLOCK_SIZE];
751 let item_key = [0xa5u8; KEY_LEN];
752
753 let wrapped = wrap_key(&encryption_key, &iv, &item_key).unwrap();
754 let unwrapped = unwrap_key(&encryption_key, &iv, &wrapped).unwrap();
755 assert_eq!(unwrapped.as_slice(), item_key);
756 }
757
758 #[test]
759 fn wrapped_key_has_the_layout_macos_writes() {
760 let encryption_key = [0x5au8; KEY_LEN];
761 let iv = [0x77u8; BLOCK_SIZE];
762 let wrapped = wrap_key(&encryption_key, &iv, &[0xa5u8; KEY_LEN]).unwrap();
763
764 assert_eq!(wrapped.len(), 48);
767
768 let mut outer = decrypt(&encryption_key, &MAGIC_CMS_IV, &wrapped).unwrap();
769 assert_eq!(outer.len(), 40);
770 outer.reverse();
771 assert_eq!(
772 &outer[..BLOCK_SIZE],
773 &iv,
774 "the IV is carried inside the blob"
775 );
776
777 let inner = decrypt(&encryption_key, &iv, &outer[BLOCK_SIZE..]).unwrap();
778 assert_eq!(&inner[..4], &[0, 0, 0, 0], "descriptive data is empty");
779 assert_eq!(&inner[4..], &[0xa5u8; KEY_LEN]);
780 }
781
782 #[test]
783 fn wrapping_rejects_a_wrong_length_key() {
784 assert!(wrap_key(&[0u8; KEY_LEN], &[0u8; BLOCK_SIZE], &[0u8; 16]).is_err());
785 }
786
787 #[test]
788 fn db_blob_round_trips_through_bytes() {
789 let mut blob = DbBlob {
790 version: BLOB_VERSION,
791 start_crypto_blob: 0,
792 total_length: 0,
793 random_signature: [7u8; 16],
794 sequence: 3,
795 idle_timeout: 300,
796 lock_on_sleep: true,
797 salt: [9u8; SALT_LEN],
798 iv: [1u8; BLOCK_SIZE],
799 blob_signature: [4u8; 20],
800 public_acl: vec![0xaa; 28],
801 crypto_blob: Vec::new(),
802 };
803 let keys = DbKeys {
804 encryption_key: SecretBytes::new(vec![0x13; KEY_LEN]),
805 signing_key: SecretBytes::new(vec![0x14; 20]),
806 private_acl: Vec::new(),
807 };
808 blob.seal(b"open sesame", &keys).unwrap();
809
810 let bytes = blob.to_bytes();
811 assert_eq!(bytes.len(), blob.total_length as usize);
812 let parsed = DbBlob::parse(&bytes).unwrap();
813 assert_eq!(parsed.start_crypto_blob as usize, DB_BLOB_LEN + 28);
814 assert_eq!(parsed.public_acl, blob.public_acl);
815 assert_eq!(parsed.salt, blob.salt);
816 assert!(parsed.lock_on_sleep);
817 assert_eq!(parsed.idle_timeout, 300);
818
819 let opened = parsed.unlock(b"open sesame").unwrap();
820 assert_eq!(
821 opened.encryption_key.as_slice(),
822 keys.encryption_key.as_slice()
823 );
824 assert_eq!(opened.signing_key.as_slice(), keys.signing_key.as_slice());
825
826 assert!(matches!(parsed.unlock(b"wrong"), Err(Error::WrongPassword)));
827 }
828
829 #[test]
833 fn a_partition_version_blob_is_signed_with_real_hmac() {
834 let bytes = hex::decode(concat!(
835 "fade07110000020000000078000000a81c5679b6a9edaa0a4753ce619fdd64b9",
836 "000000000000012c01000000ec9c4b45174e6421a2723869afb9eecc31155aa0",
837 "623dcb2aa7157013ffda409579631311d74c1b6ef9a5d1f5a79963e700000000",
838 "00000001000000010000000000000001000000000100000040692291de02d260",
839 "c740c2ba7fc7d6802b6a6b073de5ceb83da1d10afcecbd18315f365d7853670e",
840 "ab5456adbb219664",
841 ))
842 .unwrap();
843
844 let blob = DbBlob::parse(&bytes).unwrap();
845 assert_eq!(blob.version, BLOB_VERSION_PARTITION);
846
847 let keys = blob.unlock(b"probepw").expect("unlock");
848 assert!(
849 blob.verify(keys.signing_key.as_slice()),
850 "partition blob signature"
851 );
852
853 let chunks = [
856 &bytes[..72],
857 &bytes[DB_BLOB_LEN..blob.total_length as usize],
858 ];
859 assert_ne!(
860 legacy_hmac_sha1(keys.signing_key.as_slice(), &chunks),
861 blob.blob_signature
862 );
863 assert_eq!(
864 sign_blob(BLOB_VERSION_PARTITION, keys.signing_key.as_slice(), &chunks),
865 blob.blob_signature
866 );
867 }
868
869 #[test]
870 fn the_signature_algorithm_follows_the_blob_version() {
871 let key = [0x11u8; 20];
872 let chunks: [&[u8]; 2] = [b"first", b"second"];
873
874 assert_eq!(
876 sign_blob(BLOB_VERSION_MACOS_10_0, &key, &chunks),
877 legacy_hmac_sha1(&key, &chunks)
878 );
879 for version in [BLOB_VERSION_MACOS_10_1, BLOB_VERSION_PARTITION] {
880 assert_ne!(
881 sign_blob(version, &key, &chunks),
882 legacy_hmac_sha1(&key, &chunks)
883 );
884 }
885 let mut expected = <Hmac<Sha1> as hmac::Mac>::new_from_slice(&key).unwrap();
887 hmac::Mac::update(&mut expected, b"firstsecond");
888 assert_eq!(
889 sign_blob(BLOB_VERSION_PARTITION, &key, &chunks).to_vec(),
890 hmac::Mac::finalize(expected).into_bytes().to_vec()
891 );
892 }
893
894 #[test]
895 fn db_blob_rejects_bad_magic_and_offsets() {
896 let mut bytes = vec![0u8; DB_BLOB_LEN];
897 assert!(DbBlob::parse(&bytes).is_err(), "zero magic");
898
899 bytes[..4].copy_from_slice(&BLOB_MAGIC.to_be_bytes());
900 bytes[8..12].copy_from_slice(&4u32.to_be_bytes());
902 assert!(DbBlob::parse(&bytes).is_err());
903
904 assert!(DbBlob::parse(&bytes[..10]).is_err(), "truncated");
905 }
906
907 #[test]
908 fn ssgp_round_trips_and_carries_its_label() {
909 let mut label = [0u8; 20];
910 label[..4].copy_from_slice(SSGP_MAGIC);
911 label[4..].copy_from_slice(&[0xab; 16]);
912 let item_key = [0x3cu8; KEY_LEN];
913
914 let ssgp = Ssgp::seal(label, [0x0fu8; BLOCK_SIZE], &item_key, b"hunter2").unwrap();
915 let bytes = ssgp.to_bytes();
916 assert_eq!(bytes.len(), SSGP_HEADER_LEN + ssgp.ciphertext.len());
917
918 let parsed = Ssgp::parse(&bytes).unwrap();
919 assert_eq!(parsed.label, label);
920 assert_eq!(parsed.label_hex(), "ab".repeat(16));
921 assert_eq!(parsed.open(&item_key).unwrap().as_slice(), b"hunter2");
922 }
923
924 #[test]
925 fn ssgp_rejects_payloads_that_are_not_secure_storage() {
926 assert!(Ssgp::parse(b"short").is_err());
927 let mut data = vec![0u8; 40];
928 data[..4].copy_from_slice(b"nope");
929 assert!(Ssgp::parse(&data).is_err());
930 }
931}