1pub fn base64url(data: &[u8]) -> String {
10 const ALPHA: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
11 let mut out = Vec::with_capacity((data.len() + 2) / 3 * 4);
12 for chunk in data.chunks(3) {
13 let b0 = chunk[0] as usize;
14 let b1 = if chunk.len() > 1 { chunk[1] as usize } else { 0 };
15 let b2 = if chunk.len() > 2 { chunk[2] as usize } else { 0 };
16 out.push(ALPHA[b0 >> 2]);
17 out.push(ALPHA[((b0 & 3) << 4) | (b1 >> 4)]);
18 if chunk.len() > 1 { out.push(ALPHA[((b1 & 0xf) << 2) | (b2 >> 6)]); }
19 if chunk.len() > 2 { out.push(ALPHA[b2 & 0x3f]); }
20 }
21 String::from_utf8(out).unwrap()
22}
23
24#[derive(Clone, Debug, PartialEq, Eq)]
30pub struct CertInfo {
31 pub not_before: i64,
32 pub not_after: i64,
33 pub cert_id: Option<String>,
36}
37
38pub fn cert_info_from_pem(pem: &[u8]) -> Option<CertInfo> {
42 cert_info_from_der(&pem_to_first_cert_der(std::str::from_utf8(pem).ok()?)?)
43}
44
45pub fn read_cert_info(cert_path: &str) -> Option<CertInfo> {
46 let pem = std::fs::read_to_string(cert_path).ok()?;
47 cert_info_from_der(&pem_to_first_cert_der(&pem)?)
48}
49
50pub fn cert_info_from_der(cert_der: &[u8]) -> Option<CertInfo> {
53 let (_, cert_body, _) = read_tlv(cert_der, 0)?;
54 let (_, tbs, _) = read_tlv(cert_body, 0)?;
55 let mut pos = 0;
56 if tbs.get(pos) == Some(&0xa0) {
57 pos = read_tlv(tbs, pos)?.2;
58 }
59 let (serial_tag, serial, next) = read_tlv(tbs, pos)?;
60 if serial_tag != 0x02 || serial.is_empty() {
61 return None;
62 }
63 pos = skip_tlvs(tbs, next, 2)?; let (_, validity, next) = read_tlv(tbs, pos)?;
65 let (nb_tag, nb_val, nb_end) = read_tlv(validity, 0)?;
66 let (na_tag, na_val, _) = read_tlv(validity, nb_end)?;
67 let is_time = |tag: u8| tag == 0x17 || tag == 0x18;
68 if !is_time(nb_tag) || !is_time(na_tag) {
69 return None;
70 }
71 let not_before = parse_asn1_time(nb_val)?;
72 let not_after = parse_asn1_time(na_val)?;
73 pos = skip_tlvs(tbs, next, 2)?; let mut aki: Option<&[u8]> = None;
77 while pos < tbs.len() {
78 let (tag, value, next) = read_tlv(tbs, pos)?;
79 if tag == 0xa3 {
80 aki = authority_key_identifier(value);
81 }
82 pos = next;
83 }
84 let cert_id = aki.map(|key_id| format!("{}.{}", base64url(key_id), base64url(serial)));
85 Some(CertInfo { not_before, not_after, cert_id })
86}
87
88fn authority_key_identifier(explicit: &[u8]) -> Option<&[u8]> {
91 const AKI_OID: &[u8] = &[0x55, 0x1d, 0x23];
92 let (_, extensions, _) = read_tlv(explicit, 0)?;
93 let mut pos = 0;
94 while pos < extensions.len() {
95 let (_, extension, next) = read_tlv(extensions, pos)?;
96 pos = next;
97 let (oid_tag, oid, mut inner) = read_tlv(extension, 0)?;
98 if oid_tag != 0x06 || oid != AKI_OID {
99 continue;
100 }
101 let (mut tag, mut value, mut after) = read_tlv(extension, inner)?;
102 if tag == 0x01 {
103 inner = after;
105 (tag, value, after) = read_tlv(extension, inner)?;
106 }
107 let _ = after;
108 if tag != 0x04 {
109 return None;
110 }
111 let (_, aki, _) = read_tlv(value, 0)?;
113 let (key_tag, key_id, _) = read_tlv(aki, 0)?;
114 return (key_tag == 0x80 && !key_id.is_empty()).then_some(key_id);
115 }
116 None
117}
118
119#[derive(Clone, Debug, PartialEq, Eq)]
121pub enum SubjectAltName {
122 Dns(String),
123 Uri(String),
124}
125
126pub fn subject_alt_names(cert_der: &[u8]) -> Option<Vec<SubjectAltName>> {
130 const SAN_OID: &[u8] = &[0x55, 0x1d, 0x11];
131 let (_, cert_body, _) = read_tlv(cert_der, 0)?;
132 let (_, tbs, _) = read_tlv(cert_body, 0)?;
133 let mut pos = 0;
134 let mut names = Vec::new();
135 while pos < tbs.len() {
136 let (tag, value, next) = read_tlv(tbs, pos)?;
137 pos = next;
138 if tag != 0xa3 {
139 continue;
140 }
141 let (_, extensions, _) = read_tlv(value, 0)?;
142 let mut at = 0;
143 while at < extensions.len() {
144 let (_, extension, next) = read_tlv(extensions, at)?;
145 at = next;
146 let (oid_tag, oid, mut inner) = read_tlv(extension, 0)?;
147 if oid_tag != 0x06 || oid != SAN_OID {
148 continue;
149 }
150 let (mut tag, mut value, after) = read_tlv(extension, inner)?;
151 if tag == 0x01 {
152 inner = after; (tag, value, _) = read_tlv(extension, inner)?;
154 }
155 if tag != 0x04 {
156 return None;
157 }
158 let (_, general_names, _) = read_tlv(value, 0)?;
160 let mut n = 0;
161 while n < general_names.len() {
162 let (name_tag, name, next) = read_tlv(general_names, n)?;
163 n = next;
164 let text = || std::str::from_utf8(name).ok().map(str::to_string);
165 match name_tag {
166 0x82 => names.extend(text().map(SubjectAltName::Dns)),
167 0x86 => names.extend(text().map(SubjectAltName::Uri)),
168 _ => {}
169 }
170 }
171 }
172 }
173 Some(names)
174}
175
176fn pem_to_first_cert_der(pem: &str) -> Option<Vec<u8>> {
177 let start = pem.find("-----BEGIN CERTIFICATE-----")?;
178 let after = &pem[start + 27..];
179 let end = after.find("-----END CERTIFICATE-----")?;
180 let b64: String = after[..end].chars().filter(|c| !c.is_whitespace()).collect();
181 base64_decode_std(&b64)
182}
183
184fn read_tlv(data: &[u8], pos: usize) -> Option<(u8, &[u8], usize)> {
187 if pos >= data.len() { return None; }
188 let tag = data[pos];
189 let mut p = pos + 1;
190 let len = {
191 let b = *data.get(p)?; p += 1;
192 if b < 0x80 { b as usize } else {
193 let n = (b & 0x7f) as usize;
196 if n == 0 || n > 4 { return None; }
197 let mut l = 0usize;
198 for _ in 0..n { l = (l << 8) | *data.get(p)? as usize; p += 1; }
199 l
200 }
201 };
202 let end = p.checked_add(len)?;
203 if end > data.len() { return None; }
204 Some((tag, &data[p..end], end))
205}
206
207fn skip_tlvs(data: &[u8], mut pos: usize, count: usize) -> Option<usize> {
208 for _ in 0..count {
209 let (_, _, next) = read_tlv(data, pos)?;
210 pos = next;
211 }
212 Some(pos)
213}
214
215fn parse_asn1_time(t: &[u8]) -> Option<i64> {
216 if t.len() < 2 || !t[..t.len() - 1].iter().all(u8::is_ascii_digit) {
219 return None;
220 }
221 let s = std::str::from_utf8(t).ok()?;
222 if !s.ends_with('Z') { return None; }
223 let s = &s[..s.len() - 1]; let (year, rest) = if s.len() == 12 {
225 let yy: i64 = s[..2].parse().ok()?;
226 let year = if yy < 50 { 2000 + yy } else { 1900 + yy };
227 (year, &s[2..])
228 } else if s.len() == 14 {
229 (s[..4].parse().ok()?, &s[4..])
230 } else {
231 return None;
232 };
233 let mo: i64 = rest[0..2].parse().ok()?;
234 let da: i64 = rest[2..4].parse().ok()?;
235 let hr: i64 = rest[4..6].parse().ok()?;
236 let mi: i64 = rest[6..8].parse().ok()?;
237 let se: i64 = rest[8..10].parse().ok()?;
238 let days = days_since_epoch(year, mo, da)?;
239 Some(days * 86400 + hr * 3600 + mi * 60 + se)
240}
241
242pub fn days_since_epoch(year: i64, month: i64, day: i64) -> Option<i64> {
243 let y = if month <= 2 { year - 1 } else { year };
245 let m = if month <= 2 { month + 9 } else { month - 3 };
246 let era = y.div_euclid(400);
247 let yoe = y - era * 400;
248 let doy = (153 * m + 2) / 5 + (day - 1);
249 let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
250 Some(era * 146097 + doe - 719468)
251}
252
253pub fn base64_decode_std(s: &str) -> Option<Vec<u8>> {
256 let digits: Vec<u8> = s.bytes().filter_map(|b| match b {
257 b'A'..=b'Z' => Some(b - b'A'),
258 b'a'..=b'z' => Some(b - b'a' + 26),
259 b'0'..=b'9' => Some(b - b'0' + 52),
260 b'+' | b'-' => Some(62),
261 b'/' | b'_' => Some(63),
262 b'=' => None,
263 _ => None,
264 }).collect();
265 let mut out = Vec::with_capacity(digits.len() * 3 / 4);
266 for chunk in digits.chunks(4) {
267 let v = chunk.iter().enumerate()
268 .fold(0u32, |acc, (i, &d)| acc | ((d as u32) << (18 - i * 6)));
269 if chunk.len() >= 2 { out.push((v >> 16) as u8); }
270 if chunk.len() >= 3 { out.push((v >> 8) as u8); }
271 if chunk.len() >= 4 { out.push(v as u8); }
272 }
273 Some(out)
274}
275
276#[cfg(test)]
277mod subject_alt_name_tests {
278 use super::*;
279
280 #[test]
281 fn dns_and_uri_names_are_read_others_skipped() {
282 let mut params = rcgen::CertificateParams::new(vec!["abc.example.com".to_string()]).unwrap();
283 params.subject_alt_names.push(rcgen::SanType::URI("spiffe://cluster.local/ns/x/sa/y".try_into().unwrap()));
284 params.subject_alt_names.push(rcgen::SanType::IpAddress("10.0.0.1".parse().unwrap()));
285 let cert = params.self_signed(&rcgen::KeyPair::generate().unwrap()).unwrap();
286 let der: &[u8] = cert.der();
287 assert_eq!(
288 Some(vec![SubjectAltName::Dns("abc.example.com".to_string()), SubjectAltName::Uri("spiffe://cluster.local/ns/x/sa/y".to_string())]),
289 subject_alt_names(der),
290 "the IP name is skipped"
291 );
292 for cut in [0, 1, 10, der.len() / 2, der.len() - 1] {
293 assert!(subject_alt_names(&der[..cut]).is_none_or(|n| n.len() <= 2), "a truncated certificate never panics");
294 }
295 assert_eq!(None, subject_alt_names(b"not DER"));
296 }
297}