1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
// SPDX-FileCopyrightText: 2025 - 2026 Frederic Ruget <fred@atlant.is> (GitHub: @douzebis)
//
// SPDX-License-Identifier: MIT
//! Binary layout constants for PIV object storage.
//!
//! All multi-byte integers are little-endian unless noted.
//! Objects are written at the minimum size required for their content;
//! the PIV `GET DATA` response length tells the reader how large each
//! object is.
/// Magic marker at offset 0x00 (little-endian u32).
pub const YBLOB_MAGIC: u32 = 0xF2ED5F0B;
/// Minimum valid object size: the 9-byte empty-slot sentinel.
pub const OBJECT_MIN_SIZE: usize = 9;
/// Maximum PIV object payload in bytes.
///
/// The YubiKey firmware APDU buffer is 3,072 bytes; the TLV framing overhead
/// is 9 bytes (`5C 03 XX XX XX` + `53 82 HH LL`), leaving 3,063 bytes for
/// the payload. Empirically confirmed on firmware 5.4.3.
pub const OBJECT_MAX_SIZE: usize = 3_063;
/// Total NVM budget of the YubiKey 5 PIV application (bytes).
pub const YUBIKEY_NVM_BYTES: usize = 51_200;
/// Default number of PIV objects allocated per store.
pub const DEFAULT_OBJECT_COUNT: u8 = 32;
/// First PIV object data-object ID (object index 0).
pub const OBJECT_ID_ZERO: u32 = 0x5f_0000;
/// Default PIV slot used for the ECDH encryption key (slot 0x82).
pub const DEFAULT_KEY_SLOT: u8 = 0x82;
/// Default X.509 subject for the self-signed ECDH certificate.
pub const DEFAULT_SUBJECT: &str = "/CN=YBLOB ECCP256";
// ---------------------------------------------------------------------------
// Field offsets — present in every object (empty or occupied)
// ---------------------------------------------------------------------------
/// Offset of YBLOB_MAGIC (4 bytes, u32 LE).
pub const MAGIC_O: usize = 0x00;
/// Offset of object-count-in-store (1 byte).
pub const OBJECT_COUNT_O: usize = 0x04;
/// Offset of store encryption key slot (1 byte, PIV slot ID).
pub const STORE_KEY_SLOT_O: usize = 0x05;
/// Offset of object age (3 bytes, u24 LE; 0 = empty slot).
pub const OBJECT_AGE_O: usize = 0x06;
// ---------------------------------------------------------------------------
// Fields present only when age != 0
// ---------------------------------------------------------------------------
/// Offset of chunk position within blob (1 byte; 0 = head, 1+ = continuation).
pub const CHUNK_POS_O: usize = 0x09;
/// Offset of next-chunk object index (1 byte; equals own index for last chunk).
pub const NEXT_CHUNK_O: usize = 0x0A;
// ---------------------------------------------------------------------------
// Fields present only in head chunks (chunk_pos == 0)
// ---------------------------------------------------------------------------
/// Offset of blob modification time (4 bytes, u32 LE, Unix seconds).
pub const BLOB_MTIME_O: usize = 0x0B;
/// Offset of encrypted blob size (3 bytes, u24 LE).
pub const BLOB_SIZE_O: usize = 0x0F;
/// Offset of blob encryption key slot (1 byte; 0 = unencrypted).
pub const BLOB_KEY_SLOT_O: usize = 0x12;
/// Offset of unencrypted blob size (3 bytes, u24 LE).
pub const BLOB_PLAIN_SIZE_O: usize = 0x13;
/// Offset of blob name length (1 byte; 1–255).
pub const BLOB_NAME_LEN_O: usize = 0x16;
/// Offset of blob name UTF-8 bytes (variable length).
pub const BLOB_NAME_O: usize = 0x17;
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
/// First byte of chunk payload in a head object (after name).
/// First byte of chunk payload in a continuation object.
pub const CONTINUATION_PAYLOAD_O: usize = CHUNK_POS_O + 2; // = 0x0B
/// Maximum blob name length in bytes.
pub const MAX_NAME_LEN: usize = 255;