1use crate::error::{Error, Result};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub struct Guid {
16 pub data1: u32,
17 pub data2: u16,
18 pub data3: u16,
19 pub data4: [u8; 8],
20}
21
22impl Guid {
23 pub const LEN: usize = 16;
24
25 pub const APPLE_CSP: Self = Self {
27 data1: 0x8719_1ca2,
28 data2: 0x0fc9,
29 data3: 0x11d4,
30 data4: [0x84, 0x9a, 0x00, 0x05, 0x02, 0xb5, 0x21, 0x22],
31 };
32
33 pub fn parse(data: &[u8]) -> Result<Self> {
34 if data.len() < Self::LEN {
35 return Err(Error::Crypto("GUID is truncated"));
36 }
37 Ok(Self {
38 data1: be32(data, 0),
39 data2: u16::from_be_bytes([data[4], data[5]]),
40 data3: u16::from_be_bytes([data[6], data[7]]),
41 data4: data[8..16].try_into().expect("8 bytes"),
42 })
43 }
44
45 pub fn write(&self, out: &mut Vec<u8>) {
46 out.extend_from_slice(&self.data1.to_be_bytes());
47 out.extend_from_slice(&self.data2.to_be_bytes());
48 out.extend_from_slice(&self.data3.to_be_bytes());
49 out.extend_from_slice(&self.data4);
50 }
51}
52
53impl std::fmt::Display for Guid {
54 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55 write!(
56 f,
57 "{:08x}-{:04x}-{:04x}-",
58 self.data1, self.data2, self.data3
59 )?;
60 for byte in &self.data4 {
61 write!(f, "{byte:02x}")?;
62 }
63 Ok(())
64 }
65}
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
69pub struct CssmDate(pub [u8; 8]);
70
71impl CssmDate {
72 pub const LEN: usize = 8;
73
74 pub fn is_unset(&self) -> bool {
75 self.0 == [0u8; 8]
76 }
77}
78
79#[derive(Debug, Clone, Copy, PartialEq, Eq)]
81pub enum KeyClass {
82 Public,
83 Private,
84 Session,
85 SecretPart,
86 Other(u32),
87}
88
89impl KeyClass {
90 pub fn from_u32(value: u32) -> Self {
91 match value {
92 0 => Self::Public,
93 1 => Self::Private,
94 2 => Self::Session,
95 3 => Self::SecretPart,
96 other => Self::Other(other),
97 }
98 }
99
100 pub fn as_u32(self) -> u32 {
101 match self {
102 Self::Public => 0,
103 Self::Private => 1,
104 Self::Session => 2,
105 Self::SecretPart => 3,
106 Self::Other(other) => other,
107 }
108 }
109}
110
111#[derive(Debug, Clone, Copy, PartialEq, Eq)]
113pub enum KeyBlobType {
114 Raw,
115 Reference,
116 Wrapped,
117 Other(u32),
118}
119
120impl KeyBlobType {
121 pub fn from_u32(value: u32) -> Self {
122 match value {
123 0 => Self::Raw,
124 1 => Self::Reference,
125 2 => Self::Wrapped,
126 other => Self::Other(other),
127 }
128 }
129
130 pub fn as_u32(self) -> u32 {
131 match self {
132 Self::Raw => 0,
133 Self::Reference => 1,
134 Self::Wrapped => 2,
135 Self::Other(other) => other,
136 }
137 }
138}
139
140pub mod algorithm {
142 pub const TRIPLE_DES_3KEY: u32 = 0x11;
144 pub const RSA: u32 = 42;
146 pub const NONE: u32 = 0;
147}
148
149pub mod encrypt_mode {
151 pub const NONE: u32 = 0;
152 pub const CBC_PAD_IV8: u32 = 6;
154}
155
156pub mod key_attr {
158 pub const PERMANENT: u32 = 0x0000_0001;
159 pub const PRIVATE: u32 = 0x0000_0002;
160 pub const MODIFIABLE: u32 = 0x0000_0004;
161 pub const SENSITIVE: u32 = 0x0000_0008;
162 pub const EXTRACTABLE: u32 = 0x0000_0010;
163 pub const ALWAYS_SENSITIVE: u32 = 0x0000_0020;
164 pub const NEVER_EXTRACTABLE: u32 = 0x0000_0040;
165}
166
167pub mod key_usage {
169 pub const ENCRYPT: u32 = 0x0000_0001;
170 pub const DECRYPT: u32 = 0x0000_0002;
171 pub const ANY: u32 = 0x8000_0000;
173}
174
175pub mod key_format {
177 pub const NONE: u32 = 0;
178 pub const APPLE_CUSTOM: u32 = 0x64;
180}
181
182#[derive(Debug, Clone, PartialEq, Eq)]
184pub struct KeyHeader {
185 pub header_version: u32,
186 pub csp_id: Guid,
187 pub blob_type: KeyBlobType,
188 pub format: u32,
189 pub algorithm_id: u32,
190 pub key_class: KeyClass,
191 pub logical_key_size_in_bits: u32,
192 pub key_attr: u32,
194 pub key_usage: u32,
196 pub start_date: CssmDate,
197 pub end_date: CssmDate,
198 pub wrap_algorithm_id: u32,
199 pub wrap_mode: u32,
200 pub reserved: u32,
201}
202
203impl KeyHeader {
204 pub const LEN: usize = 76;
205
206 pub fn parse(data: &[u8]) -> Result<Self> {
207 if data.len() < Self::LEN {
208 return Err(Error::Crypto("key header is truncated"));
209 }
210 Ok(Self {
211 header_version: be32(data, 0),
212 csp_id: Guid::parse(&data[4..20])?,
213 blob_type: KeyBlobType::from_u32(be32(data, 20)),
214 format: be32(data, 24),
215 algorithm_id: be32(data, 28),
216 key_class: KeyClass::from_u32(be32(data, 32)),
217 logical_key_size_in_bits: be32(data, 36),
218 key_attr: be32(data, 40),
219 key_usage: be32(data, 44),
220 start_date: CssmDate(data[48..56].try_into().expect("8 bytes")),
221 end_date: CssmDate(data[56..64].try_into().expect("8 bytes")),
222 wrap_algorithm_id: be32(data, 64),
223 wrap_mode: be32(data, 68),
224 reserved: be32(data, 72),
225 })
226 }
227
228 pub fn write(&self, out: &mut Vec<u8>) {
229 out.extend_from_slice(&self.header_version.to_be_bytes());
230 self.csp_id.write(out);
231 out.extend_from_slice(&self.blob_type.as_u32().to_be_bytes());
232 out.extend_from_slice(&self.format.to_be_bytes());
233 out.extend_from_slice(&self.algorithm_id.to_be_bytes());
234 out.extend_from_slice(&self.key_class.as_u32().to_be_bytes());
235 out.extend_from_slice(&self.logical_key_size_in_bits.to_be_bytes());
236 out.extend_from_slice(&self.key_attr.to_be_bytes());
237 out.extend_from_slice(&self.key_usage.to_be_bytes());
238 out.extend_from_slice(&self.start_date.0);
239 out.extend_from_slice(&self.end_date.0);
240 out.extend_from_slice(&self.wrap_algorithm_id.to_be_bytes());
241 out.extend_from_slice(&self.wrap_mode.to_be_bytes());
242 out.extend_from_slice(&self.reserved.to_be_bytes());
243 }
244
245 pub fn to_bytes(&self) -> Vec<u8> {
246 let mut out = Vec::with_capacity(Self::LEN);
247 self.write(&mut out);
248 out
249 }
250
251 pub fn item_key() -> Self {
259 Self {
260 header_version: 2,
261 csp_id: Guid::APPLE_CSP,
262 blob_type: KeyBlobType::Wrapped,
263 format: key_format::NONE,
264 algorithm_id: algorithm::TRIPLE_DES_3KEY,
265 key_class: KeyClass::Session,
266 logical_key_size_in_bits: 192,
267 key_attr: key_attr::PERMANENT
270 | key_attr::SENSITIVE
271 | key_attr::EXTRACTABLE
272 | key_attr::NEVER_EXTRACTABLE,
273 key_usage: key_usage::ENCRYPT | key_usage::DECRYPT,
274 start_date: CssmDate::default(),
275 end_date: CssmDate::default(),
276 wrap_algorithm_id: algorithm::NONE,
277 wrap_mode: encrypt_mode::NONE,
278 reserved: 0,
279 }
280 }
281}
282
283impl KeyHeader {
284 pub fn private_key(key_size_in_bits: u32) -> Self {
292 Self {
293 algorithm_id: algorithm::RSA,
294 key_class: KeyClass::Private,
295 logical_key_size_in_bits: key_size_in_bits,
296 key_attr: key_attr::PERMANENT
298 | key_attr::SENSITIVE
299 | key_attr::EXTRACTABLE
300 | key_attr::ALWAYS_SENSITIVE,
301 key_usage: key_usage::ANY,
302 ..Self::item_key()
303 }
304 }
305}
306
307#[derive(Debug, Clone, PartialEq, Eq)]
309pub struct WrappedKeyFields {
310 pub blob_type: u32,
311 pub blob_format: u32,
312 pub wrap_algorithm: u32,
313 pub wrap_mode: u32,
314}
315
316impl WrappedKeyFields {
317 pub const LEN: usize = 16;
318
319 pub fn parse(data: &[u8]) -> Result<Self> {
320 if data.len() < Self::LEN {
321 return Err(Error::Crypto("wrapped-key fields are truncated"));
322 }
323 Ok(Self {
324 blob_type: be32(data, 0),
325 blob_format: be32(data, 4),
326 wrap_algorithm: be32(data, 8),
327 wrap_mode: be32(data, 12),
328 })
329 }
330
331 pub fn write(&self, out: &mut Vec<u8>) {
332 out.extend_from_slice(&self.blob_type.to_be_bytes());
333 out.extend_from_slice(&self.blob_format.to_be_bytes());
334 out.extend_from_slice(&self.wrap_algorithm.to_be_bytes());
335 out.extend_from_slice(&self.wrap_mode.to_be_bytes());
336 }
337
338 pub fn item_key() -> Self {
340 Self {
341 blob_type: 3,
342 blob_format: key_format::APPLE_CUSTOM,
343 wrap_algorithm: algorithm::TRIPLE_DES_3KEY,
344 wrap_mode: encrypt_mode::CBC_PAD_IV8,
345 }
346 }
347}
348
349fn be32(data: &[u8], at: usize) -> u32 {
350 u32::from_be_bytes([data[at], data[at + 1], data[at + 2], data[at + 3]])
351}
352
353#[cfg(test)]
354mod tests {
355 use super::*;
356
357 #[test]
358 fn guid_round_trips_and_prints() {
359 let mut bytes = Vec::new();
360 Guid::APPLE_CSP.write(&mut bytes);
361 assert_eq!(bytes.len(), Guid::LEN);
362 assert_eq!(Guid::parse(&bytes).unwrap(), Guid::APPLE_CSP);
363 assert_eq!(
364 Guid::APPLE_CSP.to_string(),
365 "87191ca2-0fc9-11d4-849a000502b52122"
366 );
367 }
368
369 #[test]
370 fn key_header_round_trips() {
371 let header = KeyHeader::item_key();
372 let bytes = header.to_bytes();
373 assert_eq!(bytes.len(), KeyHeader::LEN);
374 assert_eq!(KeyHeader::parse(&bytes).unwrap(), header);
375 }
376
377 #[test]
380 fn item_key_header_matches_the_bytes_macos_writes() {
381 assert_eq!(
382 hex::encode(KeyHeader::item_key().to_bytes()),
383 concat!(
384 "00000002", "87191ca20fc911d4849a000502b52122", "00000002", "00000000", "00000011", "00000002", "000000c0", "00000059", "00000003", "0000000000000000", "0000000000000000", "00000000", "00000000", "00000000", )
399 );
400 }
401
402 #[test]
403 fn key_header_holds_the_values_a_keychain_item_key_needs() {
404 let header = KeyHeader::item_key();
405 assert_eq!(header.blob_type, KeyBlobType::Wrapped);
406 assert_eq!(header.key_class, KeyClass::Session);
407 assert_eq!(header.algorithm_id, algorithm::TRIPLE_DES_3KEY);
408 assert_eq!(header.logical_key_size_in_bits, 192);
409 assert!(header.start_date.is_unset() && header.end_date.is_unset());
410 }
411
412 #[test]
413 fn wrapped_fields_round_trip() {
414 let fields = WrappedKeyFields::item_key();
415 let mut bytes = Vec::new();
416 fields.write(&mut bytes);
417 assert_eq!(bytes.len(), WrappedKeyFields::LEN);
418 assert_eq!(WrappedKeyFields::parse(&bytes).unwrap(), fields);
419 assert_eq!(fields.blob_format, key_format::APPLE_CUSTOM);
420 }
421
422 #[test]
423 fn enums_round_trip_through_their_wire_values() {
424 for value in 0..=4 {
425 assert_eq!(KeyClass::from_u32(value).as_u32(), value);
426 assert_eq!(KeyBlobType::from_u32(value).as_u32(), value);
427 }
428 assert_eq!(KeyClass::from_u32(99), KeyClass::Other(99));
429 }
430
431 #[test]
432 fn truncated_input_is_an_error_not_a_panic() {
433 assert!(Guid::parse(&[0u8; 8]).is_err());
434 assert!(KeyHeader::parse(&[0u8; 40]).is_err());
435 assert!(WrappedKeyFields::parse(&[0u8; 4]).is_err());
436 }
437}