import struct
import sys
BUF = open(sys.argv[1], "rb").read()
BASE = 20
def tables():
_size, count = struct.unpack_from(">II", BUF, BASE)
for offset in struct.unpack_from(f">{count}I", BUF, BASE + 8):
yield BASE + offset, struct.unpack_from(">7I", BUF, BASE + offset)
def records(tab, hdr):
for record_offset in struct.unpack_from(f">{hdr[6]}I", BUF, tab + 28):
if record_offset:
yield tab + record_offset
def attr_offsets(rec, count):
return struct.unpack_from(f">{count}I", BUF, rec + 24)
def value(rec, offset, kind):
if offset == 0:
return None
at = rec + (offset & 0xFFFFFFFE)
if kind == "u32":
return struct.unpack_from(">I", BUF, at)[0]
length = struct.unpack_from(">I", BUF, at)[0]
return BUF[at + 4 : at + 4 + length]
def literal(raw):
if raw is None:
return "None"
body = "".join(
chr(c) if 32 <= c < 127 and c not in (34, 92) else "\\x%02x" % c for c in raw
)
return f'Some(b"{body}")'
by_type = {hdr[1]: (tab, hdr) for tab, hdr in tables()}
relations = []
tab, hdr = by_type[0]
for rec in records(tab, hdr):
off = attr_offsets(rec, 2)
relations.append((value(rec, off[0], "u32"), value(rec, off[1], "blob")))
attributes = []
tab, hdr = by_type[2]
for rec in records(tab, hdr):
off = attr_offsets(rec, 6)
attributes.append(tuple(
value(rec, off[i], "u32" if i in (0, 1, 2, 5) else "blob") for i in range(6)
))
indexes = []
tab, hdr = by_type[1]
for rec in records(tab, hdr):
off = attr_offsets(rec, 5)
indexes.append(tuple(value(rec, off[i], "u32") for i in range(5)))
print(f'''//! Apple's keychain schema, as data.
//!
//! GENERATED by `xtask/extract-schema.py` from a keychain written by
//! `security create-keychain`. Do not edit by hand; re-run the script against a
//! newer macOS if its schema ever changes.
//!
//! `kc create` replays these rows and index blobs so that a keychain it writes
//! carries the schema macOS expects. `tests/keychain_create.rs` checks the
//! result against a keychain `security` created.
/// One row of `CSSM_DL_DB_SCHEMA_INFO`.
pub struct RelationRow {{
pub relation_id: u32,
pub name: Option<&'static [u8]>,
}}
/// One row of `CSSM_DL_DB_SCHEMA_ATTRIBUTES`.
pub struct AttributeRow {{
pub relation_id: u32,
pub attribute_id: u32,
pub name_format: u32,
pub name: Option<&'static [u8]>,
pub name_id: Option<&'static [u8]>,
pub format: u32,
}}
/// One row of `CSSM_DL_DB_SCHEMA_INDEXES`.
pub struct IndexRow {{
pub relation_id: u32,
pub index_id: u32,
pub attribute_id: u32,
pub index_type: u32,
pub indexed_data_location: u32,
}}
/// A table as macOS first writes it: its relation, the free-list field it sets
/// while the table is empty, and the index blob that declares its indexes.
pub struct TableTemplate {{
pub relation_id: u32,
pub free_list: u32,
pub index_data: &'static [u8],
}}
/// Record header version field that macOS writes for every record.
pub const RECORD_VERSION: u32 = 1;
/// Relations, in the order macOS writes them.
pub const RELATIONS: [RelationRow; {len(relations)}] = [''')
for relation_id, name in relations:
print(f" RelationRow {{ relation_id: 0x{relation_id:08x}, name: {literal(name)} }},")
print("];\n")
print(f"pub const ATTRIBUTES: [AttributeRow; {len(attributes)}] = [")
for relation_id, attribute_id, name_format, name, name_id, fmt in attributes:
print(
f" AttributeRow {{ relation_id: 0x{relation_id:08x}, "
f"attribute_id: 0x{attribute_id:08x}, name_format: {name_format}, "
f"name: {literal(name)}, name_id: {literal(name_id)}, format: {fmt} }},"
)
print("];\n")
print(f"pub const INDEXES: [IndexRow; {len(indexes)}] = [")
for relation_id, index_id, attribute_id, index_type, location in indexes:
print(
f" IndexRow {{ relation_id: 0x{relation_id:08x}, index_id: {index_id}, "
f"attribute_id: 0x{attribute_id:08x}, index_type: {index_type}, "
f"indexed_data_location: {location} }},"
)
print("];\n")
print(f"pub const TABLES: [TableTemplate; {len(by_type)}] = [")
for tab, hdr in tables():
index_blob = BUF[tab + hdr[4] : tab + hdr[0]]
body = "".join("\\x%02x" % c for c in index_blob)
print(
f" TableTemplate {{ relation_id: 0x{hdr[1]:08x}, free_list: 0x{hdr[5]:08x}, "
f'index_data: b"{body}" }},'
)
print("];")