1use crate::cssm::Guid;
15use crate::format::Value;
16use crate::schema::Relation;
17
18struct AttributeWriter<'r> {
20 relation: &'r Relation,
21 values: Vec<Option<Value>>,
22}
23
24impl<'r> AttributeWriter<'r> {
25 fn new(relation: &'r Relation) -> Self {
26 Self {
27 relation,
28 values: vec![None; relation.attributes.len()],
29 }
30 }
31
32 fn set(&mut self, name: &str, value: Value) -> &mut Self {
36 if let Some(index) = self.relation.index_of(name) {
37 self.values[index] = Some(value);
38 }
39 self
40 }
41
42 fn flag(&mut self, name: &str, value: bool) -> &mut Self {
43 self.set(name, Value::Uint32(u32::from(value)))
44 }
45
46 fn finish(self) -> Vec<Option<Value>> {
47 self.values
48 }
49}
50
51#[derive(Debug, Clone)]
60pub struct ItemKeyRecord {
61 pub key_class: u32,
62 pub print_name: Vec<u8>,
63 pub alias: Vec<u8>,
64 pub permanent: bool,
65 pub private: bool,
66 pub modifiable: bool,
67 pub label: [u8; 20],
69 pub application_tag: Vec<u8>,
70 pub key_creator: Vec<u8>,
72 pub key_type: u32,
74 pub key_size_in_bits: u32,
75 pub effective_key_size: u32,
76 pub start_date: Vec<u8>,
78 pub end_date: Vec<u8>,
79 pub sensitive: bool,
80 pub always_sensitive: bool,
81 pub extractable: bool,
82 pub never_extractable: bool,
83 pub encrypt: bool,
84 pub decrypt: bool,
85 pub derive: bool,
86 pub sign: bool,
87 pub verify: bool,
88 pub sign_recover: bool,
89 pub verify_recover: bool,
90 pub wrap: bool,
91 pub unwrap: bool,
92}
93
94pub const KEY_CLASS_SYMMETRIC: u32 = 0x11;
96
97pub const KEY_TYPE_3DES_3KEY: u32 = 0x11;
99
100impl ItemKeyRecord {
101 pub fn for_item_key(label: [u8; 20]) -> Self {
103 Self {
104 key_class: KEY_CLASS_SYMMETRIC,
105 print_name: label.to_vec(),
106 alias: Vec::new(),
107 permanent: true,
108 private: false,
109 modifiable: false,
110 label,
111 application_tag: Vec::new(),
112 key_creator: format!("{{{}}}\0", Self::creator_guid()).into_bytes(),
113 key_type: KEY_TYPE_3DES_3KEY,
114 key_size_in_bits: 192,
115 effective_key_size: 192,
116 start_date: vec![0u8; 8],
117 end_date: vec![0u8; 8],
118 sensitive: true,
121 always_sensitive: true,
122 extractable: false,
123 never_extractable: true,
124 encrypt: true,
125 decrypt: true,
126 derive: false,
127 sign: false,
128 verify: false,
129 sign_recover: false,
130 verify_recover: false,
131 wrap: false,
132 unwrap: false,
133 }
134 }
135
136 fn creator_guid() -> String {
138 let guid = Guid::APPLE_CSP;
139 format!(
140 "{:08x}-{:04x}-{:04x}-{:02x}{:02x}-{}",
141 guid.data1,
142 guid.data2,
143 guid.data3,
144 guid.data4[0],
145 guid.data4[1],
146 guid.data4[2..]
147 .iter()
148 .map(|byte| format!("{byte:02x}"))
149 .collect::<String>()
150 )
151 }
152
153 pub fn to_attributes(&self, relation: &Relation) -> Vec<Option<Value>> {
155 let mut writer = AttributeWriter::new(relation);
156 writer
157 .set("KeyClass", Value::Uint32(self.key_class))
158 .set("PrintName", Value::Blob(self.print_name.clone()))
159 .set("Alias", Value::Blob(self.alias.clone()))
160 .flag("Permanent", self.permanent)
161 .flag("Private", self.private)
162 .flag("Modifiable", self.modifiable)
163 .set("Label", Value::Blob(self.label.to_vec()))
164 .set("ApplicationTag", Value::Blob(self.application_tag.clone()))
165 .set("KeyCreator", Value::Blob(self.key_creator.clone()))
166 .set("KeyType", Value::Uint32(self.key_type))
167 .set("KeySizeInBits", Value::Uint32(self.key_size_in_bits))
168 .set("EffectiveKeySize", Value::Uint32(self.effective_key_size))
169 .set("StartDate", Value::Blob(self.start_date.clone()))
170 .set("EndDate", Value::Blob(self.end_date.clone()))
171 .flag("Sensitive", self.sensitive)
172 .flag("AlwaysSensitive", self.always_sensitive)
173 .flag("Extractable", self.extractable)
174 .flag("NeverExtractable", self.never_extractable)
175 .flag("Encrypt", self.encrypt)
176 .flag("Decrypt", self.decrypt)
177 .flag("Derive", self.derive)
178 .flag("Sign", self.sign)
179 .flag("Verify", self.verify)
180 .flag("SignRecover", self.sign_recover)
181 .flag("VerifyRecover", self.verify_recover)
182 .flag("Wrap", self.wrap)
183 .flag("Unwrap", self.unwrap);
184 writer.finish()
185 }
186}
187
188#[derive(Debug, Clone, Default)]
190pub struct PasswordRecord {
191 pub created: String,
193 pub modified: String,
194 pub print_name: String,
196 pub description: Option<String>,
198 pub comment: Option<String>,
200 pub account: Option<String>,
202 pub service: Option<String>,
204 pub generic: Option<Vec<u8>>,
206 pub server: Option<String>,
208 pub security_domain: Option<String>,
210 pub path: Option<String>,
212 pub port: Option<u32>,
214 pub protocol: Option<[u8; 4]>,
216 pub auth_type: Option<[u8; 4]>,
218 pub volume: Option<String>,
220 pub address: Option<String>,
222 pub signature: Option<String>,
224}
225
226const ALWAYS_PRESENT: [&str; 4] = ["desc", "icmt", "sdmn", "gena"];
234
235impl PasswordRecord {
236 pub fn to_attributes(&self, relation: &Relation) -> Vec<Option<Value>> {
237 let mut writer = AttributeWriter::new(relation);
238 writer
239 .set("cdat", Value::Date(date_field(&self.created)))
240 .set("mdat", Value::Date(date_field(&self.modified)))
241 .set(
242 "PrintName",
243 Value::Blob(self.print_name.as_bytes().to_vec()),
244 );
245
246 let mut text = |name: &str, value: &Option<String>| match value {
247 Some(value) => {
248 writer.set(name, Value::Blob(value.as_bytes().to_vec()));
249 }
250 None if ALWAYS_PRESENT.contains(&name) => {
251 writer.set(name, Value::Blob(Vec::new()));
252 }
253 None => {}
254 };
255 text("desc", &self.description);
256 text("icmt", &self.comment);
257 text("acct", &self.account);
258 text("svce", &self.service);
259 text("srvr", &self.server);
260 text("sdmn", &self.security_domain);
261 text("path", &self.path);
262 text("vlme", &self.volume);
263 text("addr", &self.address);
264 text("ssig", &self.signature);
265
266 writer.set(
267 "gena",
268 Value::Blob(self.generic.clone().unwrap_or_default()),
269 );
270 if let Some(port) = self.port {
271 writer.set("port", Value::Uint32(port));
272 }
273 if let Some(protocol) = self.protocol {
274 writer.set("ptcl", Value::Uint32(u32::from_be_bytes(protocol)));
275 }
276 if let Some(auth_type) = self.auth_type {
277 writer.set("atyp", Value::Blob(auth_type.to_vec()));
278 }
279 writer.finish()
280 }
281}
282
283pub const CERT_TYPE_X509_V3: u32 = 1;
285
286pub const CERT_ENCODING_DER: u32 = 3;
288
289pub const KEY_CLASS_PRIVATE: u32 = 0x10;
292
293pub const KEY_TYPE_RSA: u32 = 42;
295
296#[derive(Debug, Clone)]
302pub struct CertificateRecord {
303 pub cert_type: u32,
304 pub cert_encoding: u32,
305 pub print_name: String,
307 pub alias: Vec<u8>,
308 pub subject: Vec<u8>,
309 pub issuer: Vec<u8>,
310 pub serial_number: Vec<u8>,
311 pub subject_key_identifier: Option<Vec<u8>>,
313 pub public_key_hash: [u8; 20],
316}
317
318impl CertificateRecord {
319 pub fn for_certificate(label: &str, certificate: &crate::der::Certificate<'_>) -> Self {
321 Self {
322 cert_type: CERT_TYPE_X509_V3,
323 cert_encoding: CERT_ENCODING_DER,
324 print_name: label.to_string(),
325 alias: label.as_bytes().to_vec(),
326 subject: certificate.subject.to_vec(),
327 issuer: certificate.issuer.to_vec(),
328 serial_number: certificate.serial_number.to_vec(),
329 subject_key_identifier: certificate.subject_key_identifier.map(<[u8]>::to_vec),
330 public_key_hash: certificate.public_key_hash(),
331 }
332 }
333
334 pub fn to_attributes(&self, relation: &Relation) -> Vec<Option<Value>> {
335 let mut writer = AttributeWriter::new(relation);
336 writer
337 .set("CertType", Value::Uint32(self.cert_type))
338 .set("CertEncoding", Value::Uint32(self.cert_encoding))
339 .set(
340 "PrintName",
341 Value::Blob(self.print_name.as_bytes().to_vec()),
342 )
343 .set("Alias", Value::Blob(self.alias.clone()))
344 .set("Subject", Value::Blob(self.subject.clone()))
345 .set("Issuer", Value::Blob(self.issuer.clone()))
346 .set("SerialNumber", Value::Blob(self.serial_number.clone()))
347 .set("PublicKeyHash", Value::Blob(self.public_key_hash.to_vec()));
348 if let Some(identifier) = &self.subject_key_identifier {
349 writer.set("SubjectKeyIdentifier", Value::Blob(identifier.clone()));
350 }
351 writer.finish()
352 }
353}
354
355#[derive(Debug, Clone)]
360pub struct PrivateKeyRecord {
361 pub key_class: u32,
362 pub print_name: String,
363 pub alias: Vec<u8>,
364 pub permanent: bool,
365 pub private: bool,
366 pub modifiable: bool,
367 pub label: [u8; 20],
369 pub application_tag: Vec<u8>,
370 pub key_creator: Vec<u8>,
371 pub key_type: u32,
372 pub key_size_in_bits: u32,
373 pub effective_key_size: u32,
374 pub start_date: Vec<u8>,
375 pub end_date: Vec<u8>,
376 pub sensitive: bool,
377 pub always_sensitive: bool,
378 pub extractable: bool,
379 pub never_extractable: bool,
380 pub encrypt: bool,
381 pub decrypt: bool,
382 pub derive: bool,
383 pub sign: bool,
384 pub verify: bool,
385 pub sign_recover: bool,
386 pub verify_recover: bool,
387 pub wrap: bool,
388 pub unwrap: bool,
389}
390
391impl PrivateKeyRecord {
392 pub fn for_private_key(label: &str, public_key_hash: [u8; 20], key_size_in_bits: u32) -> Self {
395 Self {
396 key_class: KEY_CLASS_PRIVATE,
397 print_name: label.to_string(),
398 alias: Vec::new(),
399 permanent: true,
400 private: false,
401 modifiable: false,
402 label: public_key_hash,
403 application_tag: Vec::new(),
404 key_creator: ItemKeyRecord::for_item_key([0u8; 20]).key_creator,
405 key_type: KEY_TYPE_RSA,
406 key_size_in_bits,
407 effective_key_size: key_size_in_bits,
408 start_date: vec![0u8; 8],
409 end_date: vec![0u8; 8],
410 sensitive: true,
411 always_sensitive: true,
412 extractable: true,
415 never_extractable: false,
416 encrypt: true,
417 decrypt: true,
418 derive: true,
419 sign: true,
420 verify: true,
421 sign_recover: true,
422 verify_recover: true,
423 wrap: true,
424 unwrap: true,
425 }
426 }
427
428 pub fn to_attributes(&self, relation: &Relation) -> Vec<Option<Value>> {
429 let mut writer = AttributeWriter::new(relation);
430 writer
431 .set("KeyClass", Value::Uint32(self.key_class))
432 .set(
433 "PrintName",
434 Value::Blob(self.print_name.as_bytes().to_vec()),
435 )
436 .set("Alias", Value::Blob(self.alias.clone()))
437 .flag("Permanent", self.permanent)
438 .flag("Private", self.private)
439 .flag("Modifiable", self.modifiable)
440 .set("Label", Value::Blob(self.label.to_vec()))
441 .set("ApplicationTag", Value::Blob(self.application_tag.clone()))
442 .set("KeyCreator", Value::Blob(self.key_creator.clone()))
443 .set("KeyType", Value::Uint32(self.key_type))
444 .set("KeySizeInBits", Value::Uint32(self.key_size_in_bits))
445 .set("EffectiveKeySize", Value::Uint32(self.effective_key_size))
446 .set("StartDate", Value::Blob(self.start_date.clone()))
447 .set("EndDate", Value::Blob(self.end_date.clone()))
448 .flag("Sensitive", self.sensitive)
449 .flag("AlwaysSensitive", self.always_sensitive)
450 .flag("Extractable", self.extractable)
451 .flag("NeverExtractable", self.never_extractable)
452 .flag("Encrypt", self.encrypt)
453 .flag("Decrypt", self.decrypt)
454 .flag("Derive", self.derive)
455 .flag("Sign", self.sign)
456 .flag("Verify", self.verify)
457 .flag("SignRecover", self.sign_recover)
458 .flag("VerifyRecover", self.verify_recover)
459 .flag("Wrap", self.wrap)
460 .flag("Unwrap", self.unwrap);
461 writer.finish()
462 }
463}
464
465fn date_field(timestamp: &str) -> Vec<u8> {
467 format!("{timestamp}\0").into_bytes()
468}
469
470#[cfg(test)]
471mod tests {
472 use super::*;
473 use crate::schema::{AttributeDef, AttributeFormat, RecordType};
474
475 fn relation(names: &[&str]) -> Relation {
476 Relation {
477 record_type: RecordType::SYMMETRIC_KEY,
478 name: None,
479 attributes: names
480 .iter()
481 .enumerate()
482 .map(|(index, name)| AttributeDef {
483 id: index as u32,
484 name: (*name).to_string(),
485 format: AttributeFormat::Blob,
486 })
487 .collect(),
488 }
489 }
490
491 #[test]
492 fn key_creator_is_a_braced_guid_string() {
493 let record = ItemKeyRecord::for_item_key([0u8; 20]);
494 assert_eq!(
495 record.key_creator,
496 b"{87191ca2-0fc9-11d4-849a-000502b52122}\0".to_vec(),
497 "must match the string macOS stores"
498 );
499 assert_eq!(record.key_creator.len(), 39);
500 }
501
502 #[test]
503 fn key_record_lowers_every_attribute_the_relation_declares() {
504 let names = [
505 "KeyClass",
506 "PrintName",
507 "Alias",
508 "Permanent",
509 "Private",
510 "Modifiable",
511 "Label",
512 "ApplicationTag",
513 "KeyCreator",
514 "KeyType",
515 "KeySizeInBits",
516 "EffectiveKeySize",
517 "StartDate",
518 "EndDate",
519 "Sensitive",
520 "AlwaysSensitive",
521 "Extractable",
522 "NeverExtractable",
523 "Encrypt",
524 "Decrypt",
525 "Derive",
526 "Sign",
527 "Verify",
528 "SignRecover",
529 "VerifyRecover",
530 "Wrap",
531 "Unwrap",
532 ];
533 let relation = relation(&names);
534 let mut label = [0u8; 20];
535 label[..4].copy_from_slice(b"ssgp");
536 let values = ItemKeyRecord::for_item_key(label).to_attributes(&relation);
537
538 assert_eq!(values.len(), names.len());
539 for (index, name) in names.iter().enumerate() {
540 assert!(
541 values[index].is_some(),
542 "{name} must have a value: securityd asks for it"
543 );
544 }
545 assert_eq!(values[0], Some(Value::Uint32(KEY_CLASS_SYMMETRIC)));
546 assert_eq!(values[1], Some(Value::Blob(label.to_vec())));
547 assert_eq!(
548 values[2],
549 Some(Value::Blob(Vec::new())),
550 "Alias is present but empty"
551 );
552 assert_eq!(
553 values[12],
554 Some(Value::Blob(vec![0u8; 8])),
555 "unset dates are eight zeroes"
556 );
557 }
558
559 #[test]
560 fn unknown_attribute_names_are_skipped_not_fatal() {
561 let relation = relation(&["KeyClass", "Label"]);
562 let values = ItemKeyRecord::for_item_key([1u8; 20]).to_attributes(&relation);
563 assert_eq!(values.len(), 2);
564 assert_eq!(values[0], Some(Value::Uint32(KEY_CLASS_SYMMETRIC)));
565 }
566
567 #[test]
568 fn attributes_macos_always_writes_are_present_even_when_empty() {
569 let relation = relation(&[
570 "cdat",
571 "mdat",
572 "desc",
573 "icmt",
574 "PrintName",
575 "acct",
576 "svce",
577 "gena",
578 "port",
579 ]);
580 let record = PasswordRecord {
581 created: "20260725123456Z".into(),
582 modified: "20260725123456Z".into(),
583 print_name: "myservice".into(),
584 account: Some("alice".into()),
585 service: Some("myservice".into()),
586 ..PasswordRecord::default()
587 };
588 let values = record.to_attributes(&relation);
589
590 assert_eq!(values[0], Some(Value::Date(b"20260725123456Z\0".to_vec())));
591 assert_eq!(values[2], Some(Value::Blob(Vec::new())));
594 assert_eq!(values[3], Some(Value::Blob(Vec::new())));
595 assert_eq!(values[7], Some(Value::Blob(Vec::new())));
596 assert_eq!(values[4], Some(Value::Blob(b"myservice".to_vec())));
597 assert_eq!(values[5], Some(Value::Blob(b"alice".to_vec())));
598 assert_eq!(values[8], None);
600 }
601
602 #[test]
603 fn class_specific_always_present_attributes_follow_the_relation() {
604 let internet = relation(&["cdat", "mdat", "PrintName", "sdmn", "srvr"]);
606 let values = PasswordRecord {
607 server: Some("h".into()),
608 ..PasswordRecord::default()
609 }
610 .to_attributes(&internet);
611 assert_eq!(
612 values[3],
613 Some(Value::Blob(Vec::new())),
614 "sdmn present but empty"
615 );
616
617 let generic = relation(&["cdat", "mdat", "PrintName", "gena", "svce"]);
618 let values = PasswordRecord {
619 service: Some("s".into()),
620 ..PasswordRecord::default()
621 }
622 .to_attributes(&generic);
623 assert_eq!(
624 values[3],
625 Some(Value::Blob(Vec::new())),
626 "gena present but empty"
627 );
628 }
629
630 #[test]
631 fn internet_attributes_are_encoded_in_their_formats() {
632 let relation = relation(&[
633 "cdat",
634 "mdat",
635 "PrintName",
636 "srvr",
637 "path",
638 "port",
639 "ptcl",
640 "atyp",
641 ]);
642 let record = PasswordRecord {
643 created: "20260725123456Z".into(),
644 modified: "20260725123456Z".into(),
645 print_name: "example.com".into(),
646 server: Some("example.com".into()),
647 path: Some("/login".into()),
648 port: Some(8080),
649 protocol: Some(*b"http"),
650 auth_type: Some(*b"dflt"),
651 ..PasswordRecord::default()
652 };
653 let values = record.to_attributes(&relation);
654
655 assert_eq!(values[5], Some(Value::Uint32(8080)));
656 assert_eq!(values[6], Some(Value::Uint32(u32::from_be_bytes(*b"http"))));
659 assert_eq!(values[7], Some(Value::Blob(b"dflt".to_vec())));
660 }
661}