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
//! **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: = ;
/// 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 last `CN` attribute value in a `Name`, decoded as UTF-8. Every string
/// type a CN is 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.