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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
//! **A certificate leaf name** (REMOTE §2, bl-8bbc): the client identity read
//! off the certificate the peer presented, and nothing else.
//!
//! §2 is exact about what a client is — *"One certificate = one client identity
//! (its leaf name)"* — so the identity is the leaf's subject **common name**,
//! which is what `make wire-certs` mints (`/CN=yog-client`) and what an
//! operator types when they seat a registration by hand. A fingerprint would
//! have been cheaper to compute and wrong on both counts: it is unreadable in a
//! `clients/` listing, and it changes on renewal, silently de-scoping every
//! registration the operator wrote.
//!
//! **yog links no certificate library, so this is a DER walk** (AGENTS.md rule
//! 6: zero new dependencies). It is ~60 lines of structural ASN.1 rather than a
//! byte search, and the structure is the point: the **issuer** carries a common
//! name too, and it comes FIRST — a scan for the CN object identifier would
//! return the operator CA's name for every client on the box.
//!
//! What it reads, per RFC 5280:
//!
//! ```text
//! Certificate ::= SEQUENCE { tbsCertificate, signatureAlgorithm, signature }
//! TBSCertificate ::= SEQUENCE { [0] version OPTIONAL, serialNumber INTEGER,
//! signature, issuer, validity, subject, … }
//! Name ::= SEQUENCE OF SET OF SEQUENCE { type OID, value ANY }
//! ```
//!
//! The optional `[0] version` is why `subject` is located **relative to the
//! serial number** rather than at a fixed index: the serial is the first field
//! certainly present, and `subject` is four constructed values past it. A
//! version-1 certificate and a version-3 one then take one path, not two.
/// DER tags this walk names.
const INTEGER: u8 = 0x02;
const OID: u8 = 0x06;
/// `id-at-commonName` — ASN.1 `{joint-iso-itu-t(2) ds(5) attributeType(4)
/// commonName(3)}`, in its DER encoding. Spelled as bytes rather than as the
/// dotted arc string because the dotted form of four small arcs is
/// indistinguishable from an IPv4 address, to a reader and to `make leak-scan`.
const COMMON_NAME: = ;
/// `id-at-organizationalUnitName` — the same arc one attribute over, and the
/// home REMOTE §4.2 gives the grade. Spelled as bytes for [`COMMON_NAME`]'s
/// reason.
const ORG_UNIT: = ;
/// How many constructed fields separate `serialNumber` from `subject`:
/// signature, issuer, validity, subject.
const SERIAL_TO_SUBJECT: usize = 4;
/// The subject common name of a DER-encoded certificate, or `None` when the
/// bytes are not a certificate or carry no readable one.
///
/// The **last** common name wins. A distinguished name is written most-general
/// first in DER and most-specific last (RFC 4514 renders it reversed), so the
/// final `CN` is the leaf's own; a certificate minted by `make wire-certs` has
/// exactly one and the question does not arise.
/// **The grade the same subject carries** (REMOTE §4.2, bl-1dd3): an
/// `OU=foot` anywhere in it is a foot, and everything else — a subject with
/// other organizational units, a subject with none, bytes that are not a
/// certificate at all — is operator grade.
///
/// **Default-operator, not default-foot**, which is why this answers a
/// [`Grade`] rather than an `Option`: a leaf minted before the grade existed
/// must keep working exactly as it did. A silent demotion would be an outage
/// with no sentence attached; a silent promotion cannot happen, because the
/// word is written by the operator's own CA or not at all.
///
/// It is the organizational unit rather than a custom extension or an EKU OID
/// because those need an `openssl` config stanza in the mint *and* an extension
/// parser yog does not have, where the OU is one more attribute in a subject
/// this walk opens anyway.
/// The `Name` bytes of the certificate's **subject** — located relative to the
/// serial number, for the reason the module doc gives.
/// Every value of attribute `oid` in a `Name`, in DER order, decoded as UTF-8.
/// Every string type these attributes are minted in — `UTF8String`,
/// `PrintableString`, `IA5String` — is UTF-8 or a subset of it, and one that is
/// not (`BMPString` is UTF-16) fails the decode and is skipped rather than
/// mis-read.
/// One DER type-length-value off the front of `bytes`: its tag, its contents,
/// and what follows it. `None` for a truncated header, a truncated value, or a
/// length DER does not permit — the indefinite form (`0x80`), which BER allows
/// and DER forbids, and a length wider than this walk will serve.
/// Every element of a constructed value, in order. A trailing byte run that is
/// not a whole TLV ends the walk — a malformed tail yields the elements read
/// before it, which is what makes every read above total.