1buffer: []const u8,
2index: u32,
3
4pub const Bundle = @import("Certificate/Bundle.zig");
5pub const Chain = switch (builtin.os.tag) {
6 else => void, // not a shim to also avoid expensive caller logic
7 .windows => @import("Certificate/Chain.zig"),
8};
9
10pub const Version = enum { v1, v2, v3 };
11
12pub const Algorithm = enum {
13 sha1WithRSAEncryption,
14 sha224WithRSAEncryption,
15 sha256WithRSAEncryption,
16 sha384WithRSAEncryption,
17 sha512WithRSAEncryption,
18 ecdsa_with_SHA224,
19 ecdsa_with_SHA256,
20 ecdsa_with_SHA384,
21 ecdsa_with_SHA512,
22 md2WithRSAEncryption,
23 md5WithRSAEncryption,
24 curveEd25519,
25
26 pub const map = std.StaticStringMap(Algorithm).initComptime(.{
27 .{ &.{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x05 }, .sha1WithRSAEncryption },
28 .{ &.{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0B }, .sha256WithRSAEncryption },
29 .{ &.{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0C }, .sha384WithRSAEncryption },
30 .{ &.{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0D }, .sha512WithRSAEncryption },
31 .{ &.{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0E }, .sha224WithRSAEncryption },
32 .{ &.{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x01 }, .ecdsa_with_SHA224 },
33 .{ &.{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x02 }, .ecdsa_with_SHA256 },
34 .{ &.{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x03 }, .ecdsa_with_SHA384 },
35 .{ &.{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x04 }, .ecdsa_with_SHA512 },
36 .{ &.{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x02 }, .md2WithRSAEncryption },
37 .{ &.{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x04 }, .md5WithRSAEncryption },
38 .{ &.{ 0x2B, 0x65, 0x70 }, .curveEd25519 },
39 });
40
41 pub fn Hash(comptime algorithm: Algorithm) type {
42 return switch (algorithm) {
43 .sha1WithRSAEncryption => crypto.hash.Sha1,
44 .ecdsa_with_SHA224, .sha224WithRSAEncryption => crypto.hash.sha2.Sha224,
45 .ecdsa_with_SHA256, .sha256WithRSAEncryption => crypto.hash.sha2.Sha256,
46 .ecdsa_with_SHA384, .sha384WithRSAEncryption => crypto.hash.sha2.Sha384,
47 .ecdsa_with_SHA512, .sha512WithRSAEncryption, .curveEd25519 => crypto.hash.sha2.Sha512,
48 .md2WithRSAEncryption => @compileError("unimplemented"),
49 .md5WithRSAEncryption => crypto.hash.Md5,
50 };
51 }
52};
53
54pub const AlgorithmCategory = enum {
55 rsaEncryption,
56 rsassa_pss,
57 X9_62_id_ecPublicKey,
58 curveEd25519,
59
60 pub const map = std.StaticStringMap(AlgorithmCategory).initComptime(.{
61 .{ &.{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01 }, .rsaEncryption },
62 .{ &.{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0A }, .rsassa_pss },
63 .{ &.{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01 }, .X9_62_id_ecPublicKey },
64 .{ &.{ 0x2B, 0x65, 0x70 }, .curveEd25519 },
65 });
66};
67
68pub const Attribute = enum {
69 commonName,
70 serialNumber,
71 countryName,
72 localityName,
73 stateOrProvinceName,
74 streetAddress,
75 organizationName,
76 organizationalUnitName,
77 postalCode,
78 organizationIdentifier,
79 pkcs9_emailAddress,
80 domainComponent,
81
82 pub const map = std.StaticStringMap(Attribute).initComptime(.{
83 .{ &.{ 0x55, 0x04, 0x03 }, .commonName },
84 .{ &.{ 0x55, 0x04, 0x05 }, .serialNumber },
85 .{ &.{ 0x55, 0x04, 0x06 }, .countryName },
86 .{ &.{ 0x55, 0x04, 0x07 }, .localityName },
87 .{ &.{ 0x55, 0x04, 0x08 }, .stateOrProvinceName },
88 .{ &.{ 0x55, 0x04, 0x09 }, .streetAddress },
89 .{ &.{ 0x55, 0x04, 0x0A }, .organizationName },
90 .{ &.{ 0x55, 0x04, 0x0B }, .organizationalUnitName },
91 .{ &.{ 0x55, 0x04, 0x11 }, .postalCode },
92 .{ &.{ 0x55, 0x04, 0x61 }, .organizationIdentifier },
93 .{ &.{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x09, 0x01 }, .pkcs9_emailAddress },
94 .{ &.{ 0x09, 0x92, 0x26, 0x89, 0x93, 0xF2, 0x2C, 0x64, 0x01, 0x19 }, .domainComponent },
95 });
96};
97
98pub const NamedCurve = enum {
99 secp384r1,
100 secp521r1,
101 X9_62_prime256v1,
102
103 pub const map = std.StaticStringMap(NamedCurve).initComptime(.{
104 .{ &.{ 0x2B, 0x81, 0x04, 0x00, 0x22 }, .secp384r1 },
105 .{ &.{ 0x2B, 0x81, 0x04, 0x00, 0x23 }, .secp521r1 },
106 .{ &.{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07 }, .X9_62_prime256v1 },
107 });
108
109 pub fn Curve(comptime curve: NamedCurve) type {
110 return switch (curve) {
111 .X9_62_prime256v1 => crypto.ecc.P256,
112 .secp384r1 => crypto.ecc.P384,
113 .secp521r1 => @compileError("unimplemented"),
114 };
115 }
116};
117
118pub const ExtensionId = enum {
119 subject_key_identifier,
120 key_usage,
121 private_key_usage_period,
122 subject_alt_name,
123 issuer_alt_name,
124 basic_constraints,
125 crl_number,
126 certificate_policies,
127 authority_key_identifier,
128 msCertsrvCAVersion,
129 commonName,
130 ext_key_usage,
131 crl_distribution_points,
132 info_access,
133 entrustVersInfo,
134 enroll_certtype,
135 pe_logotype,
136 netscape_cert_type,
137 netscape_comment,
138
139 pub const map = std.StaticStringMap(ExtensionId).initComptime(.{
140 .{ &.{ 0x55, 0x04, 0x03 }, .commonName },
141 .{ &.{ 0x55, 0x1D, 0x01 }, .authority_key_identifier },
142 .{ &.{ 0x55, 0x1D, 0x07 }, .subject_alt_name },
143 .{ &.{ 0x55, 0x1D, 0x0E }, .subject_key_identifier },
144 .{ &.{ 0x55, 0x1D, 0x0F }, .key_usage },
145 .{ &.{ 0x55, 0x1D, 0x0A }, .basic_constraints },
146 .{ &.{ 0x55, 0x1D, 0x10 }, .private_key_usage_period },
147 .{ &.{ 0x55, 0x1D, 0x11 }, .subject_alt_name },
148 .{ &.{ 0x55, 0x1D, 0x12 }, .issuer_alt_name },
149 .{ &.{ 0x55, 0x1D, 0x13 }, .basic_constraints },
150 .{ &.{ 0x55, 0x1D, 0x14 }, .crl_number },
151 .{ &.{ 0x55, 0x1D, 0x1F }, .crl_distribution_points },
152 .{ &.{ 0x55, 0x1D, 0x20 }, .certificate_policies },
153 .{ &.{ 0x55, 0x1D, 0x23 }, .authority_key_identifier },
154 .{ &.{ 0x55, 0x1D, 0x25 }, .ext_key_usage },
155 .{ &.{ 0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x15, 0x01 }, .msCertsrvCAVersion },
156 .{ &.{ 0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01 }, .info_access },
157 .{ &.{ 0x2A, 0x86, 0x48, 0x86, 0xF6, 0x7D, 0x07, 0x41, 0x00 }, .entrustVersInfo },
158 .{ &.{ 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x14, 0x02 }, .enroll_certtype },
159 .{ &.{ 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x0c }, .pe_logotype },
160 .{ &.{ 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x01, 0x01 }, .netscape_cert_type },
161 .{ &.{ 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x01, 0x0d }, .netscape_comment },
162 });
163};
164
165pub const GeneralNameTag = enum(u5) {
166 otherName = 0,
167 rfc822Name = 1,
168 dNSName = 2,
169 x400Address = 3,
170 directoryName = 4,
171 ediPartyName = 5,
172 uniformResourceIdentifier = 6,
173 iPAddress = 7,
174 registeredID = 8,
175 _,
176};
177
178const net = @import("../Io/net.zig");
179
180pub const Parsed = struct {
181 certificate: Certificate,
182 issuer_slice: Slice,
183 subject_slice: Slice,
184 common_name_slice: Slice,
185 signature_slice: Slice,
186 signature_algorithm: Algorithm,
187 pub_key_algo: PubKeyAlgo,
188 pub_key_slice: Slice,
189 message_slice: Slice,
190 subject_alt_name_slice: Slice,
191 validity: Validity,
192 version: Version,
193
194 pub const PubKeyAlgo = union(AlgorithmCategory) {
195 rsaEncryption: void,
196 rsassa_pss: void,
197 X9_62_id_ecPublicKey: NamedCurve,
198 curveEd25519: void,
199 };
200
201 pub const Validity = struct {
202 not_before: u64,
203 not_after: u64,
204 };
205
206 pub const Slice = der.Element.Slice;
207
208 pub fn slice(p: Parsed, s: Slice) []const u8 {
209 return p.certificate.buffer[s.start..s.end];
210 }
211
212 pub fn issuer(p: Parsed) []const u8 {
213 return p.slice(p.issuer_slice);
214 }
215
216 pub fn subject(p: Parsed) []const u8 {
217 return p.slice(p.subject_slice);
218 }
219
220 pub fn commonName(p: Parsed) []const u8 {
221 return p.slice(p.common_name_slice);
222 }
223
224 pub fn signature(p: Parsed) []const u8 {
225 return p.slice(p.signature_slice);
226 }
227
228 pub fn pubKey(p: Parsed) []const u8 {
229 return p.slice(p.pub_key_slice);
230 }
231
232 pub fn message(p: Parsed) []const u8 {
233 return p.slice(p.message_slice);
234 }
235
236 pub fn subjectAltName(p: Parsed) []const u8 {
237 return p.slice(p.subject_alt_name_slice);
238 }
239
240 pub const VerifyError = error{
241 CertificateIssuerMismatch,
242 CertificateNotYetValid,
243 CertificateExpired,
244 CertificateSignatureAlgorithmUnsupported,
245 CertificateSignatureAlgorithmMismatch,
246 CertificateFieldHasInvalidLength,
247 CertificateFieldHasWrongDataType,
248 CertificatePublicKeyInvalid,
249 CertificateSignatureInvalidLength,
250 CertificateSignatureInvalid,
251 CertificateSignatureUnsupportedBitCount,
252 CertificateSignatureNamedCurveUnsupported,
253 };
254
255 /// This function verifies:
256 /// * That the subject's issuer is indeed the provided issuer.
257 /// * The time validity of the subject.
258 /// * The signature.
259 pub fn verify(parsed_subject: Parsed, parsed_issuer: Parsed, now_sec: i64) VerifyError!void {
260 // Check that the subject's issuer name matches the issuer's
261 // subject name.
262 if (!mem.eql(u8, parsed_subject.issuer(), parsed_issuer.subject())) {
263 return error.CertificateIssuerMismatch;
264 }
265
266 if (now_sec < parsed_subject.validity.not_before)
267 return error.CertificateNotYetValid;
268 if (now_sec > parsed_subject.validity.not_after)
269 return error.CertificateExpired;
270
271 switch (parsed_subject.signature_algorithm) {
272 inline .sha1WithRSAEncryption,
273 .sha224WithRSAEncryption,
274 .sha256WithRSAEncryption,
275 .sha384WithRSAEncryption,
276 .sha512WithRSAEncryption,
277 => |algorithm| return verifyRsa(
278 algorithm.Hash(),
279 parsed_subject.message(),
280 parsed_subject.signature(),
281 parsed_issuer.pub_key_algo,
282 parsed_issuer.pubKey(),
283 ),
284
285 inline .ecdsa_with_SHA224,
286 .ecdsa_with_SHA256,
287 .ecdsa_with_SHA384,
288 .ecdsa_with_SHA512,
289 => |algorithm| return verify_ecdsa(
290 algorithm.Hash(),
291 parsed_subject.message(),
292 parsed_subject.signature(),
293 parsed_issuer.pub_key_algo,
294 parsed_issuer.pubKey(),
295 ),
296
297 .md2WithRSAEncryption, .md5WithRSAEncryption => {
298 return error.CertificateSignatureAlgorithmUnsupported;
299 },
300
301 .curveEd25519 => return verifyEd25519(
302 parsed_subject.message(),
303 parsed_subject.signature(),
304 parsed_issuer.pub_key_algo,
305 parsed_issuer.pubKey(),
306 ),
307 }
308 }
309
310 pub const VerifyHostNameError = error{
311 CertificateHostMismatch,
312 CertificateFieldHasInvalidLength,
313 };
314
315 pub fn verifyHostName(parsed_subject: Parsed, host_name: []const u8) VerifyHostNameError!void {
316 // If the Subject Alternative Names extension is present, this is
317 // what to check. Otherwise, only the common name is checked.
318 const subject_alt_name = parsed_subject.subjectAltName();
319 if (subject_alt_name.len == 0) {
320 // note: checkIpAddress is intentionally omitted, as it is not permitted in the common name field anyway.
321 if (checkHostName(host_name, parsed_subject.commonName())) {
322 return;
323 } else {
324 return error.CertificateHostMismatch;
325 }
326 }
327
328 const general_names = try der.Element.parse(subject_alt_name, 0);
329 var name_i = general_names.slice.start;
330 while (name_i < general_names.slice.end) {
331 const general_name = try der.Element.parse(subject_alt_name, name_i);
332 name_i = general_name.slice.end;
333 switch (@as(GeneralNameTag, @fromBackingInt(@intCast(@backingInt(general_name.identifier.tag))))) {
334 .dNSName => {
335 const dns_name = subject_alt_name[general_name.slice.start..general_name.slice.end];
336 if (checkHostName(host_name, dns_name)) return;
337 },
338 .iPAddress => {
339 const ip_address = subject_alt_name[general_name.slice.start..general_name.slice.end];
340 if (checkIpAddress(host_name, ip_address)) return;
341 },
342 else => {},
343 }
344 }
345
346 return error.CertificateHostMismatch;
347 }
348
349 // Check hostname according to RFC2818 specification:
350 //
351 // If more than one identity of a given type is present in
352 // the certificate (e.g., more than one DNSName name, a match in any one
353 // of the set is considered acceptable.) Names may contain the wildcard
354 // character * which is considered to match any single domain name
355 // component. E.g., *.a.com matches foo.a.com but not bar.foo.a.com.
356 // Partial wildcards like f*.com are not supported.
357 fn checkHostName(host_name: []const u8, dns_name: []const u8) bool {
358 // Empty strings should not match
359 if (host_name.len == 0 or dns_name.len == 0) return false;
360
361 // RFC 6125 Section 6.4.1: Exact match (case-insensitive)
362 if (std.ascii.eqlIgnoreCase(dns_name, host_name)) {
363 return true; // exact match
364 }
365
366 // RFC 6125 Section 6.4.3: Wildcard certificates
367 // Wildcard must be leftmost label and in the form "*.rest.of.domain"
368 if (dns_name.len >= 3 and mem.startsWith(u8, dns_name, "*.")) {
369 const wildcard_suffix = dns_name[2..];
370
371 // No additional wildcards allowed in the suffix
372 if (mem.find(u8, wildcard_suffix, "*") != null) return false;
373
374 // Find the first dot in hostname to split first label from rest
375 const dot_pos = mem.find(u8, host_name, ".") orelse return false;
376
377 // Wildcard matches exactly one label, so compare the rest
378 const host_suffix = host_name[dot_pos + 1 ..];
379
380 // Match suffixes (case-insensitive per RFC 6125)
381 return std.ascii.eqlIgnoreCase(wildcard_suffix, host_suffix);
382 }
383
384 return false;
385 }
386
387 // Check IP address according to RFC 5280 §4.2.1.6.
388 fn checkIpAddress(host_name: []const u8, ip_address: []const u8) bool {
389 switch (ip_address.len) {
390 4 => {
391 // port is irrelevant to SAN matching, so 0 is a harmless placeholder.
392 const address = net.Ip4Address.parse(host_name, 0) catch return false;
393 return mem.eql(u8, &address.bytes, ip_address);
394 },
395 16 => {
396 const address = net.Ip6Address.parse(host_name, 0) catch return false;
397 return mem.eql(u8, &address.bytes, ip_address);
398 },
399 else => return false, // a malformed certificate, neither 4 nor 16 octets
400 }
401 }
402};
403
404test "Parsed.checkHostName RFC 6125 compliance" {
405 const expectEqual = std.testing.expectEqual;
406
407 // Exact match tests
408 try expectEqual(true, Parsed.checkHostName("ziglang.org", "ziglang.org"));
409 try expectEqual(true, Parsed.checkHostName("ziglang.org", "Ziglang.org")); // case insensitive
410 try expectEqual(true, Parsed.checkHostName("ZIGLANG.ORG", "ziglang.org")); // case insensitive
411
412 // Valid wildcard matches
413 try expectEqual(true, Parsed.checkHostName("bar.ziglang.org", "*.ziglang.org"));
414 try expectEqual(true, Parsed.checkHostName("BAR.ziglang.org", "*.Ziglang.ORG")); // case insensitive
415
416 // RFC 6125: Wildcard matches exactly one label
417 try expectEqual(false, Parsed.checkHostName("foo.bar.ziglang.org", "*.ziglang.org"));
418 try expectEqual(false, Parsed.checkHostName("ziglang.org", "*.ziglang.org")); // no empty match
419
420 // RFC 6125: No partial wildcards allowed
421 try expectEqual(false, Parsed.checkHostName("ziglang.org", "zig*.org"));
422 try expectEqual(false, Parsed.checkHostName("ziglang.org", "*lang.org"));
423 try expectEqual(false, Parsed.checkHostName("ziglang.org", "zi*ng.org"));
424
425 // RFC 6125: No multiple wildcards
426 try expectEqual(false, Parsed.checkHostName("foo.bar.org", "*.*.org"));
427
428 // RFC 6125: Wildcard must be in leftmost label
429 try expectEqual(false, Parsed.checkHostName("foo.bar.org", "foo.*.org"));
430
431 // Single label hostnames should not match wildcards
432 try expectEqual(false, Parsed.checkHostName("localhost", "*.local"));
433 try expectEqual(false, Parsed.checkHostName("localhost", "*.localhost"));
434
435 // Edge cases
436 try expectEqual(false, Parsed.checkHostName("", ""));
437 try expectEqual(false, Parsed.checkHostName("example.com", ""));
438 try expectEqual(false, Parsed.checkHostName("", "*.example.com"));
439 try expectEqual(false, Parsed.checkHostName("example.com", "*"));
440 try expectEqual(false, Parsed.checkHostName("example.com", "*."));
441}
442
443test "Parsed.checkIpAddress RFC 5280 4.2.1.6 compliance" {
444 const expectEqual = std.testing.expectEqual;
445
446 // Exact match positive tests
447 try expectEqual(true, Parsed.checkIpAddress("127.0.0.1", &[4]u8{ 127, 0, 0, 1 }));
448 try expectEqual(true, Parsed.checkIpAddress("0:0:0:0:0:0:0:1", &[16]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }));
449
450 // Mismatches should not pass
451 try expectEqual(false, Parsed.checkIpAddress("1.2.3.4", &[4]u8{ 5, 6, 7, 8 }));
452 try expectEqual(false, Parsed.checkIpAddress("0:0:0:0:0:0:0:1", &[16]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 }));
453
454 // IPv6: the hostname may be in short-form and should match the exact 16 octets specified in the SAN
455 try expectEqual(true, Parsed.checkIpAddress("::1", &[16]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }));
456
457 // IPv6: do not match when using DNS64 / NAT64 (i.e. 64:ff9b::/96)
458 // the RFC requires exact octet matches, so this is likely surprising and wrong. The decision here is to fail-safe out of an abundance of caution.
459 // The test assertions are included not to harden on this behavior, but to show that this use-case was considered.
460 // This check may become more lenient in the future if a valid use-case is found.
461 try expectEqual(false, Parsed.checkIpAddress("64:ff9b::192.0.2.10", &[4]u8{ 192, 0, 2, 10 }));
462 try expectEqual(false, Parsed.checkIpAddress("::ffff:127.0.0.1", &[4]u8{ 127, 0, 0, 1 }));
463
464 // Malformed SAN lengths (not 4 or 16 octets) never match.
465 try expectEqual(false, Parsed.checkIpAddress("127.0.0", &[_]u8{ 127, 0, 0 }));
466 try expectEqual(false, Parsed.checkIpAddress("127.0.0.1.0", &[_]u8{ 127, 0, 0, 1, 0 }));
467
468 // A non-parseable host_name never matches.
469 try expectEqual(false, Parsed.checkIpAddress("not-an-ip", &[4]u8{ 127, 0, 0, 1 }));
470
471 // Edge cases - empty strings
472 try expectEqual(false, Parsed.checkIpAddress("", ""));
473 try expectEqual(false, Parsed.checkIpAddress("127.0.0.1", ""));
474}
475
476pub const ParseError = der.Element.ParseError || ParseVersionError || ParseTimeError || ParseEnumError || ParseBitStringError;
477
478pub fn parse(cert: Certificate) ParseError!Parsed {
479 const cert_bytes = cert.buffer;
480 const certificate = try der.Element.parse(cert_bytes, cert.index);
481 const tbs_certificate = try der.Element.parse(cert_bytes, certificate.slice.start);
482 const version_elem = try der.Element.parse(cert_bytes, tbs_certificate.slice.start);
483 const version = try parseVersion(cert_bytes, version_elem);
484 const serial_number = if (@as(u8, @bitCast(version_elem.identifier)) == 0xa0)
485 try der.Element.parse(cert_bytes, version_elem.slice.end)
486 else
487 version_elem;
488 // RFC 5280, section 4.1.2.3:
489 // "This field MUST contain the same algorithm identifier as
490 // the signatureAlgorithm field in the sequence Certificate."
491 const tbs_signature = try der.Element.parse(cert_bytes, serial_number.slice.end);
492 const issuer = try der.Element.parse(cert_bytes, tbs_signature.slice.end);
493 const validity = try der.Element.parse(cert_bytes, issuer.slice.end);
494 const not_before = try der.Element.parse(cert_bytes, validity.slice.start);
495 const not_before_utc = try parseTime(cert, not_before);
496 const not_after = try der.Element.parse(cert_bytes, not_before.slice.end);
497 const not_after_utc = try parseTime(cert, not_after);
498 const subject = try der.Element.parse(cert_bytes, validity.slice.end);
499
500 const pub_key_info = try der.Element.parse(cert_bytes, subject.slice.end);
501 const pub_key_signature_algorithm = try der.Element.parse(cert_bytes, pub_key_info.slice.start);
502 const pub_key_algo_elem = try der.Element.parse(cert_bytes, pub_key_signature_algorithm.slice.start);
503 const pub_key_algo: Parsed.PubKeyAlgo = switch (try parseAlgorithmCategory(cert_bytes, pub_key_algo_elem)) {
504 inline else => |tag| @unionInit(Parsed.PubKeyAlgo, @tagName(tag), {}),
505 .X9_62_id_ecPublicKey => pub_key_algo: {
506 // RFC 5480 Section 2.1.1.1 Named Curve
507 // ECParameters ::= CHOICE {
508 // namedCurve OBJECT IDENTIFIER
509 // -- implicitCurve NULL
510 // -- specifiedCurve SpecifiedECDomain
511 // }
512 const params_elem = try der.Element.parse(cert_bytes, pub_key_algo_elem.slice.end);
513 const named_curve = try parseNamedCurve(cert_bytes, params_elem);
514 break :pub_key_algo .{ .X9_62_id_ecPublicKey = named_curve };
515 },
516 };
517 const pub_key_elem = try der.Element.parse(cert_bytes, pub_key_signature_algorithm.slice.end);
518 const pub_key = try parseBitString(cert, pub_key_elem);
519
520 var common_name = der.Element.Slice.empty;
521 var name_i = subject.slice.start;
522 while (name_i < subject.slice.end) {
523 const rdn = try der.Element.parse(cert_bytes, name_i);
524 var rdn_i = rdn.slice.start;
525 while (rdn_i < rdn.slice.end) {
526 const atav = try der.Element.parse(cert_bytes, rdn_i);
527 var atav_i = atav.slice.start;
528 while (atav_i < atav.slice.end) {
529 const ty_elem = try der.Element.parse(cert_bytes, atav_i);
530 const val = try der.Element.parse(cert_bytes, ty_elem.slice.end);
531 atav_i = val.slice.end;
532 const ty = parseAttribute(cert_bytes, ty_elem) catch |err| switch (err) {
533 error.CertificateHasUnrecognizedObjectId => continue,
534 else => |e| return e,
535 };
536 switch (ty) {
537 .commonName => common_name = val.slice,
538 else => {},
539 }
540 }
541 rdn_i = atav.slice.end;
542 }
543 name_i = rdn.slice.end;
544 }
545
546 const sig_algo = try der.Element.parse(cert_bytes, tbs_certificate.slice.end);
547 const algo_elem = try der.Element.parse(cert_bytes, sig_algo.slice.start);
548 const signature_algorithm = try parseAlgorithm(cert_bytes, algo_elem);
549 const sig_elem = try der.Element.parse(cert_bytes, sig_algo.slice.end);
550 const signature = try parseBitString(cert, sig_elem);
551
552 // Extensions
553 var subject_alt_name_slice = der.Element.Slice.empty;
554 ext: {
555 if (version == .v1)
556 break :ext;
557
558 if (pub_key_info.slice.end >= tbs_certificate.slice.end)
559 break :ext;
560
561 const outer_extensions = try der.Element.parse(cert_bytes, pub_key_info.slice.end);
562 if (outer_extensions.identifier.tag != .bitstring)
563 break :ext;
564
565 const extensions = try der.Element.parse(cert_bytes, outer_extensions.slice.start);
566
567 var ext_i = extensions.slice.start;
568 while (ext_i < extensions.slice.end) {
569 const extension = try der.Element.parse(cert_bytes, ext_i);
570 ext_i = extension.slice.end;
571 const oid_elem = try der.Element.parse(cert_bytes, extension.slice.start);
572 const ext_id = parseExtensionId(cert_bytes, oid_elem) catch |err| switch (err) {
573 error.CertificateHasUnrecognizedObjectId => continue,
574 else => |e| return e,
575 };
576 const critical_elem = try der.Element.parse(cert_bytes, oid_elem.slice.end);
577 const ext_bytes_elem = if (critical_elem.identifier.tag != .boolean)
578 critical_elem
579 else
580 try der.Element.parse(cert_bytes, critical_elem.slice.end);
581 switch (ext_id) {
582 .subject_alt_name => subject_alt_name_slice = ext_bytes_elem.slice,
583 else => continue,
584 }
585 }
586 }
587
588 return .{
589 .certificate = cert,
590 .common_name_slice = common_name,
591 .issuer_slice = issuer.slice,
592 .subject_slice = subject.slice,
593 .signature_slice = signature,
594 .signature_algorithm = signature_algorithm,
595 .message_slice = .{ .start = certificate.slice.start, .end = tbs_certificate.slice.end },
596 .pub_key_algo = pub_key_algo,
597 .pub_key_slice = pub_key,
598 .validity = .{
599 .not_before = not_before_utc,
600 .not_after = not_after_utc,
601 },
602 .subject_alt_name_slice = subject_alt_name_slice,
603 .version = version,
604 };
605}
606
607pub fn verify(subject: Certificate, issuer: Certificate, now_sec: i64) !void {
608 const parsed_subject = try subject.parse();
609 const parsed_issuer = try issuer.parse();
610 return parsed_subject.verify(parsed_issuer, now_sec);
611}
612
613pub fn contents(cert: Certificate, elem: der.Element) []const u8 {
614 return cert.buffer[elem.slice.start..elem.slice.end];
615}
616
617pub const ParseBitStringError = error{ CertificateFieldHasWrongDataType, CertificateHasInvalidBitString };
618
619pub fn parseBitString(cert: Certificate, elem: der.Element) !der.Element.Slice {
620 if (elem.identifier.tag != .bitstring) return error.CertificateFieldHasWrongDataType;
621 if (cert.buffer[elem.slice.start] != 0) return error.CertificateHasInvalidBitString;
622 return .{ .start = elem.slice.start + 1, .end = elem.slice.end };
623}
624
625pub const ParseTimeError = error{ CertificateTimeInvalid, CertificateFieldHasWrongDataType };
626
627/// Returns number of seconds since epoch.
628pub fn parseTime(cert: Certificate, elem: der.Element) ParseTimeError!u64 {
629 const bytes = cert.contents(elem);
630 switch (elem.identifier.tag) {
631 .utc_time => {
632 // Example: "YYMMDD000000Z"
633 if (bytes.len != 13)
634 return error.CertificateTimeInvalid;
635 if (bytes[12] != 'Z')
636 return error.CertificateTimeInvalid;
637
638 return Date.toSeconds(.{
639 .year = blk: {
640 const year = try parseTimeDigits(bytes[0..2], 0, 99);
641 break :blk if (year < 50) @as(u16, 2000) + year else @as(u16, 1900) + year;
642 },
643 .month = try parseTimeDigits(bytes[2..4], 1, 12),
644 .day = try parseTimeDigits(bytes[4..6], 1, 31),
645 .hour = try parseTimeDigits(bytes[6..8], 0, 23),
646 .minute = try parseTimeDigits(bytes[8..10], 0, 59),
647 .second = try parseTimeDigits(bytes[10..12], 0, 59),
648 });
649 },
650 .generalized_time => {
651 // Examples:
652 // "19920521000000Z"
653 // "19920622123421Z"
654 // "19920722132100.3Z"
655 if (bytes.len < 15)
656 return error.CertificateTimeInvalid;
657 return Date.toSeconds(.{
658 .year = try parseYear4(bytes[0..4]),
659 .month = try parseTimeDigits(bytes[4..6], 1, 12),
660 .day = try parseTimeDigits(bytes[6..8], 1, 31),
661 .hour = try parseTimeDigits(bytes[8..10], 0, 23),
662 .minute = try parseTimeDigits(bytes[10..12], 0, 59),
663 .second = try parseTimeDigits(bytes[12..14], 0, 59),
664 });
665 },
666 else => return error.CertificateFieldHasWrongDataType,
667 }
668}
669
670const Date = struct {
671 /// example: 1999
672 year: u16,
673 /// range: 1 to 12
674 month: u8,
675 /// range: 1 to 31
676 day: u8,
677 /// range: 0 to 59
678 hour: u8,
679 /// range: 0 to 59
680 minute: u8,
681 /// range: 0 to 59
682 second: u8,
683
684 /// Convert to number of seconds since epoch.
685 pub fn toSeconds(date: Date) u64 {
686 var sec: u64 = 0;
687
688 {
689 var year: u16 = 1970;
690 while (year < date.year) : (year += 1) {
691 const days: u64 = std.time.epoch.getDaysInYear(year);
692 sec += days * std.time.epoch.secs_per_day;
693 }
694 }
695
696 {
697 var month: u4 = 1;
698 while (month < date.month) : (month += 1) {
699 const days: u64 = std.time.epoch.getDaysInMonth(
700 date.year,
701 @fromBackingInt(@intCast(month)),
702 );
703 sec += days * std.time.epoch.secs_per_day;
704 }
705 }
706
707 sec += (date.day - 1) * @as(u64, std.time.epoch.secs_per_day);
708 sec += date.hour * @as(u64, 60 * 60);
709 sec += date.minute * @as(u64, 60);
710 sec += date.second;
711
712 return sec;
713 }
714};
715
716pub fn parseTimeDigits(text: *const [2]u8, min: u8, max: u8) !u8 {
717 const V = @Vector(2, u16);
718 const bytes: V = text.*;
719 const zero: V = @splat('0');
720 const mm: V = .{ 10, 1 };
721 const d = bytes -% zero;
722 if (@reduce(.Or, d > @as(V, @splat(9)))) {
723 @branchHint(.unlikely);
724 return error.CertificateTimeInvalid;
725 }
726 const result = @reduce(.Add, d *% mm);
727 if (result < min) return error.CertificateTimeInvalid;
728 if (result > max) return error.CertificateTimeInvalid;
729 return @intCast(result);
730}
731
732test "parseTime UTCTime year mapping per RFC 5280" {
733 const utc_time_id: der.Identifier = .{ .tag = .utc_time, .pc = .primitive, .class = .universal };
734 const elem = der.Element{ .identifier = utc_time_id, .slice = .{ .start = 0, .end = 13 } };
735 const cert49 = Certificate{ .buffer = "490101000000Z", .index = 0 };
736 try std.testing.expectEqual(@as(u64, 2493072000), try cert49.parseTime(elem));
737 const cert99 = Certificate{ .buffer = "990101000000Z", .index = 0 };
738 try std.testing.expectEqual(@as(u64, 915148800), try cert99.parseTime(elem));
739}
740
741test parseTimeDigits {
742 const expectEqual = std.testing.expectEqual;
743 try expectEqual(@as(u8, 0), try parseTimeDigits("00", 0, 99));
744 try expectEqual(@as(u8, 99), try parseTimeDigits("99", 0, 99));
745 try expectEqual(@as(u8, 42), try parseTimeDigits("42", 0, 99));
746
747 const expectError = std.testing.expectError;
748 try expectError(error.CertificateTimeInvalid, parseTimeDigits("13", 1, 12));
749 try expectError(error.CertificateTimeInvalid, parseTimeDigits("00", 1, 12));
750 try expectError(error.CertificateTimeInvalid, parseTimeDigits("Di", 0, 99));
751 try expectError(error.CertificateTimeInvalid, parseTimeDigits("0:", 1, 31));
752}
753
754pub fn parseYear4(text: *const [4]u8) !u16 {
755 const V = @Vector(4, u32);
756 const bytes: V = text.*;
757 const zero: V = @splat('0');
758 const mmmm: V = .{ 1000, 100, 10, 1 };
759 const d = bytes -% zero;
760 if (@reduce(.Or, d > @as(V, @splat(9)))) {
761 @branchHint(.unlikely);
762 return error.CertificateTimeInvalid;
763 }
764 const result = @reduce(.Add, d *% mmmm);
765 return @intCast(result);
766}
767
768test parseYear4 {
769 const expectEqual = std.testing.expectEqual;
770 try expectEqual(@as(u16, 0), try parseYear4("0000"));
771 try expectEqual(@as(u16, 9999), try parseYear4("9999"));
772 try expectEqual(@as(u16, 1988), try parseYear4("1988"));
773
774 const expectError = std.testing.expectError;
775 try expectError(error.CertificateTimeInvalid, parseYear4("999b"));
776 try expectError(error.CertificateTimeInvalid, parseYear4("crap"));
777 try expectError(error.CertificateTimeInvalid, parseYear4("r:bQ"));
778 try expectError(error.CertificateTimeInvalid, parseYear4("000:"));
779 try expectError(error.CertificateTimeInvalid, parseYear4("0???"));
780 try expectError(error.CertificateTimeInvalid, parseYear4("*zig"));
781}
782
783pub fn parseAlgorithm(bytes: []const u8, element: der.Element) ParseEnumError!Algorithm {
784 return parseEnum(Algorithm, bytes, element);
785}
786
787pub fn parseAlgorithmCategory(bytes: []const u8, element: der.Element) ParseEnumError!AlgorithmCategory {
788 return parseEnum(AlgorithmCategory, bytes, element);
789}
790
791pub fn parseAttribute(bytes: []const u8, element: der.Element) ParseEnumError!Attribute {
792 return parseEnum(Attribute, bytes, element);
793}
794
795pub fn parseNamedCurve(bytes: []const u8, element: der.Element) ParseEnumError!NamedCurve {
796 return parseEnum(NamedCurve, bytes, element);
797}
798
799pub fn parseExtensionId(bytes: []const u8, element: der.Element) ParseEnumError!ExtensionId {
800 return parseEnum(ExtensionId, bytes, element);
801}
802
803pub const ParseEnumError = error{ CertificateFieldHasWrongDataType, CertificateHasUnrecognizedObjectId };
804
805fn parseEnum(comptime E: type, bytes: []const u8, element: der.Element) ParseEnumError!E {
806 if (element.identifier.tag != .object_identifier)
807 return error.CertificateFieldHasWrongDataType;
808 const oid_bytes = bytes[element.slice.start..element.slice.end];
809 return E.map.get(oid_bytes) orelse return error.CertificateHasUnrecognizedObjectId;
810}
811
812pub const ParseVersionError = error{ UnsupportedCertificateVersion, CertificateFieldHasInvalidLength };
813
814pub fn parseVersion(bytes: []const u8, version_elem: der.Element) ParseVersionError!Version {
815 if (@as(u8, @bitCast(version_elem.identifier)) != 0xa0)
816 return .v1;
817
818 if (version_elem.slice.end - version_elem.slice.start != 3)
819 return error.CertificateFieldHasInvalidLength;
820
821 const encoded_version = bytes[version_elem.slice.start..version_elem.slice.end];
822
823 if (mem.eql(u8, encoded_version, "\x02\x01\x02")) {
824 return .v3;
825 } else if (mem.eql(u8, encoded_version, "\x02\x01\x01")) {
826 return .v2;
827 } else if (mem.eql(u8, encoded_version, "\x02\x01\x00")) {
828 return .v1;
829 }
830
831 return error.UnsupportedCertificateVersion;
832}
833
834fn verifyRsa(
835 comptime Hash: type,
836 msg: []const u8,
837 sig: []const u8,
838 pub_key_algo: Parsed.PubKeyAlgo,
839 pub_key: []const u8,
840) !void {
841 if (pub_key_algo != .rsaEncryption) return error.CertificateSignatureAlgorithmMismatch;
842 const pk_components = try rsa.PublicKey.parseDer(pub_key);
843 const exponent = pk_components.exponent;
844 const modulus = pk_components.modulus;
845 if (exponent.len > modulus.len) return error.CertificatePublicKeyInvalid;
846 if (sig.len != modulus.len) return error.CertificateSignatureInvalidLength;
847
848 switch (modulus.len) {
849 inline 128, 256, 384, 512 => |modulus_len| {
850 const public_key = rsa.PublicKey.fromBytes(exponent, modulus) catch
851 return error.CertificateSignatureInvalid;
852 rsa.PKCS1v1_5Signature.verify(modulus_len, sig[0..modulus_len], msg, public_key, Hash) catch
853 return error.CertificateSignatureInvalid;
854 },
855 else => return error.CertificateSignatureUnsupportedBitCount,
856 }
857}
858
859fn verify_ecdsa(
860 comptime Hash: type,
861 message: []const u8,
862 encoded_sig: []const u8,
863 pub_key_algo: Parsed.PubKeyAlgo,
864 sec1_pub_key: []const u8,
865) !void {
866 const sig_named_curve = switch (pub_key_algo) {
867 .X9_62_id_ecPublicKey => |named_curve| named_curve,
868 else => return error.CertificateSignatureAlgorithmMismatch,
869 };
870
871 switch (sig_named_curve) {
872 .secp521r1 => {
873 return error.CertificateSignatureNamedCurveUnsupported;
874 },
875 inline .X9_62_prime256v1,
876 .secp384r1,
877 => |curve| {
878 const Ecdsa = crypto.sign.ecdsa.Ecdsa(curve.Curve(), Hash);
879 const sig = Ecdsa.Signature.fromDer(encoded_sig) catch |err| switch (err) {
880 error.InvalidEncoding => return error.CertificateSignatureInvalid,
881 };
882 const pub_key = Ecdsa.PublicKey.fromSec1(sec1_pub_key) catch |err| switch (err) {
883 error.InvalidEncoding => return error.CertificateSignatureInvalid,
884 error.NonCanonical => return error.CertificateSignatureInvalid,
885 error.NotSquare => return error.CertificateSignatureInvalid,
886 };
887 sig.verify(message, pub_key) catch |err| switch (err) {
888 error.IdentityElement => return error.CertificateSignatureInvalid,
889 error.NonCanonical => return error.CertificateSignatureInvalid,
890 error.SignatureVerificationFailed => return error.CertificateSignatureInvalid,
891 };
892 },
893 }
894}
895
896fn verifyEd25519(
897 message: []const u8,
898 encoded_sig: []const u8,
899 pub_key_algo: Parsed.PubKeyAlgo,
900 encoded_pub_key: []const u8,
901) !void {
902 if (pub_key_algo != .curveEd25519) return error.CertificateSignatureAlgorithmMismatch;
903 const Ed25519 = crypto.sign.Ed25519;
904 if (encoded_sig.len != Ed25519.Signature.encoded_length) return error.CertificateSignatureInvalid;
905 const sig = Ed25519.Signature.fromBytes(encoded_sig[0..Ed25519.Signature.encoded_length].*);
906 if (encoded_pub_key.len != Ed25519.PublicKey.encoded_length) return error.CertificateSignatureInvalid;
907 const pub_key = Ed25519.PublicKey.fromBytes(encoded_pub_key[0..Ed25519.PublicKey.encoded_length].*) catch |err| switch (err) {
908 error.NonCanonical => return error.CertificateSignatureInvalid,
909 };
910 sig.verify(message, pub_key) catch |err| switch (err) {
911 error.IdentityElement => return error.CertificateSignatureInvalid,
912 error.NonCanonical => return error.CertificateSignatureInvalid,
913 error.SignatureVerificationFailed => return error.CertificateSignatureInvalid,
914 error.InvalidEncoding => return error.CertificateSignatureInvalid,
915 error.WeakPublicKey => return error.CertificateSignatureInvalid,
916 };
917}
918
919const builtin = @import("builtin");
920const std = @import("../std.zig");
921const crypto = std.crypto;
922const mem = std.mem;
923const Certificate = @This();
924
925pub const der = struct {
926 pub const Class = enum(u2) {
927 universal,
928 application,
929 context_specific,
930 private,
931 };
932
933 pub const PC = enum(u1) {
934 primitive,
935 constructed,
936 };
937
938 pub const Identifier = packed struct(u8) {
939 tag: Tag,
940 pc: PC,
941 class: Class,
942 };
943
944 pub const Tag = enum(u5) {
945 boolean = 1,
946 integer = 2,
947 bitstring = 3,
948 octetstring = 4,
949 null = 5,
950 object_identifier = 6,
951 sequence = 16,
952 set = 17,
953 utc_time = 23,
954 generalized_time = 24,
955 _,
956 };
957
958 pub const Element = struct {
959 identifier: Identifier,
960 slice: Slice,
961
962 pub const Slice = struct {
963 start: u32,
964 end: u32,
965
966 pub const empty: Slice = .{ .start = 0, .end = 0 };
967 };
968
969 pub const ParseError = error{CertificateFieldHasInvalidLength};
970
971 pub fn parse(bytes: []const u8, index: u32) Element.ParseError!Element {
972 var i = index;
973 const identifier: Identifier = @bitCast(bytes[i]);
974 i += 1;
975 const size_byte = bytes[i];
976 i += 1;
977 if ((size_byte >> 7) == 0) {
978 return .{
979 .identifier = identifier,
980 .slice = .{
981 .start = i,
982 .end = i + size_byte,
983 },
984 };
985 }
986
987 const len_size: u7 = @truncate(size_byte);
988 if (len_size > @sizeOf(u32)) {
989 return error.CertificateFieldHasInvalidLength;
990 }
991
992 const end_i = i + len_size;
993 var long_form_size: u32 = 0;
994 while (i < end_i) : (i += 1) {
995 long_form_size = (long_form_size << 8) | bytes[i];
996 }
997
998 return .{
999 .identifier = identifier,
1000 .slice = .{
1001 .start = i,
1002 .end = i + long_form_size,
1003 },
1004 };
1005 }
1006 };
1007};
1008
1009test {
1010 _ = Bundle;
1011}
1012
1013pub const rsa = struct {
1014 const max_modulus_bits = 4096;
1015 const Uint = std.crypto.ff.Uint(max_modulus_bits);
1016 const Modulus = std.crypto.ff.Modulus(max_modulus_bits);
1017 const Fe = Modulus.Fe;
1018
1019 /// RFC 3447 8.1 RSASSA-PSS
1020 pub const PSSSignature = struct {
1021 pub fn fromBytes(comptime modulus_len: usize, msg: []const u8) [modulus_len]u8 {
1022 var result: [modulus_len]u8 = undefined;
1023 @memcpy(result[0..msg.len], msg);
1024 @memset(result[msg.len..], 0);
1025 return result;
1026 }
1027
1028 pub const VerifyError = EncryptError || error{InvalidSignature};
1029
1030 pub fn verify(
1031 comptime modulus_len: usize,
1032 sig: [modulus_len]u8,
1033 msg: []const u8,
1034 public_key: PublicKey,
1035 comptime Hash: type,
1036 ) VerifyError!void {
1037 try concatVerify(modulus_len, sig, &.{msg}, public_key, Hash);
1038 }
1039
1040 pub fn concatVerify(
1041 comptime modulus_len: usize,
1042 sig: *const [modulus_len]u8,
1043 msg: []const []const u8,
1044 public_key: PublicKey,
1045 comptime Hash: type,
1046 ) VerifyError!void {
1047 const mod_bits = public_key.n.bits();
1048 const em_dec = try encrypt(modulus_len, sig, public_key);
1049
1050 try EMSA_PSS_VERIFY(msg, &em_dec, mod_bits - 1, Hash.digest_length, Hash);
1051 }
1052
1053 fn EMSA_PSS_VERIFY(msg: []const []const u8, em: []const u8, emBit: usize, sLen: usize, comptime Hash: type) VerifyError!void {
1054 // 1. If the length of M is greater than the input limitation for
1055 // the hash function (2^61 - 1 octets for SHA-1), output
1056 // "inconsistent" and stop.
1057 // All the cryptographic hash functions in the standard library have a limit of >= 2^61 - 1.
1058 // Even then, this check is only there for paranoia. In the context of TLS certificates, emBit cannot exceed 4096.
1059 if (emBit >= 1 << 61) return error.InvalidSignature;
1060
1061 // emLen = \ceil(emBits/8)
1062 const emLen = ((emBit - 1) / 8) + 1;
1063 std.debug.assert(emLen == em.len);
1064
1065 // 2. Let mHash = Hash(M), an octet string of length hLen.
1066 var mHash: [Hash.digest_length]u8 = undefined;
1067 {
1068 var hasher: Hash = .init(.{});
1069 for (msg) |part| hasher.update(part);
1070 hasher.final(&mHash);
1071 }
1072
1073 // 3. If emLen < hLen + sLen + 2, output "inconsistent" and stop.
1074 if (emLen < Hash.digest_length + sLen + 2) {
1075 return error.InvalidSignature;
1076 }
1077
1078 // 4. If the rightmost octet of EM does not have hexadecimal value
1079 // 0xbc, output "inconsistent" and stop.
1080 if (em[em.len - 1] != 0xbc) {
1081 return error.InvalidSignature;
1082 }
1083
1084 // 5. Let maskedDB be the leftmost emLen - hLen - 1 octets of EM,
1085 // and let H be the next hLen octets.
1086 const maskedDB = em[0..(emLen - Hash.digest_length - 1)];
1087 const h = em[(emLen - Hash.digest_length - 1)..(emLen - 1)][0..Hash.digest_length];
1088
1089 // 6. If the leftmost 8emLen - emBits bits of the leftmost octet in
1090 // maskedDB are not all equal to zero, output "inconsistent" and
1091 // stop.
1092 const zero_bits = emLen * 8 - emBit;
1093 var mask: u8 = maskedDB[0];
1094 var i: usize = 0;
1095 while (i < 8 - zero_bits) : (i += 1) {
1096 mask = mask >> 1;
1097 }
1098 if (mask != 0) {
1099 return error.InvalidSignature;
1100 }
1101
1102 // 7. Let dbMask = MGF(H, emLen - hLen - 1).
1103 const mgf_len = emLen - Hash.digest_length - 1;
1104 var mgf_out_buf: [512]u8 = undefined;
1105 if (mgf_len > mgf_out_buf.len) { // Modulus > 4096 bits
1106 return error.InvalidSignature;
1107 }
1108 const mgf_out = mgf_out_buf[0 .. ((mgf_len - 1) / Hash.digest_length + 1) * Hash.digest_length];
1109 var dbMask = try MGF1(Hash, mgf_out, h, mgf_len);
1110
1111 // 8. Let DB = maskedDB \xor dbMask.
1112 i = 0;
1113 while (i < dbMask.len) : (i += 1) {
1114 dbMask[i] = maskedDB[i] ^ dbMask[i];
1115 }
1116
1117 // 9. Set the leftmost 8emLen - emBits bits of the leftmost octet
1118 // in DB to zero.
1119 i = 0;
1120 mask = 0;
1121 while (i < 8 - zero_bits) : (i += 1) {
1122 mask = mask << 1;
1123 mask += 1;
1124 }
1125 dbMask[0] = dbMask[0] & mask;
1126
1127 // 10. If the emLen - hLen - sLen - 2 leftmost octets of DB are not
1128 // zero or if the octet at position emLen - hLen - sLen - 1 (the
1129 // leftmost position is "position 1") does not have hexadecimal
1130 // value 0x01, output "inconsistent" and stop.
1131 if (dbMask[mgf_len - sLen - 2] != 0x00) {
1132 return error.InvalidSignature;
1133 }
1134
1135 if (dbMask[mgf_len - sLen - 1] != 0x01) {
1136 return error.InvalidSignature;
1137 }
1138
1139 // 11. Let salt be the last sLen octets of DB.
1140 const salt = dbMask[(mgf_len - sLen)..];
1141
1142 // 12. Let
1143 // M' = (0x)00 00 00 00 00 00 00 00 || mHash || salt ;
1144 // M' is an octet string of length 8 + hLen + sLen with eight
1145 // initial zero octets.
1146 if (sLen > Hash.digest_length) { // A seed larger than the hash length would be useless
1147 return error.InvalidSignature;
1148 }
1149 var m_p_buf: [8 + Hash.digest_length + Hash.digest_length]u8 = undefined;
1150 var m_p = m_p_buf[0 .. 8 + Hash.digest_length + sLen];
1151 @memmove(m_p[0..8], @as(*const [8]u8, &@splat(0)));
1152 @memmove(m_p[8..][0..Hash.digest_length], &mHash);
1153 @memmove(m_p[(8 + Hash.digest_length)..], salt);
1154
1155 // 13. Let H' = Hash(M'), an octet string of length hLen.
1156 var h_p: [Hash.digest_length]u8 = undefined;
1157 Hash.hash(m_p, &h_p, .{});
1158
1159 // 14. If H = H', output "consistent". Otherwise, output
1160 // "inconsistent".
1161 if (!std.mem.eql(u8, h, &h_p)) {
1162 return error.InvalidSignature;
1163 }
1164 }
1165
1166 fn MGF1(comptime Hash: type, out: []u8, seed: *const [Hash.digest_length]u8, len: usize) ![]u8 {
1167 var counter: u32 = 0;
1168 var idx: usize = 0;
1169 var hash = seed.* ++ @as([4]u8, undefined);
1170
1171 while (idx < len) {
1172 std.mem.writeInt(u32, hash[seed.len..][0..4], counter, .big);
1173 Hash.hash(&hash, out[idx..][0..Hash.digest_length], .{});
1174 idx += Hash.digest_length;
1175 counter += 1;
1176 }
1177
1178 return out[0..len];
1179 }
1180 };
1181
1182 /// RFC 3447 8.2 RSASSA-PKCS1-v1_5
1183 pub const PKCS1v1_5Signature = struct {
1184 pub fn fromBytes(comptime modulus_len: usize, msg: []const u8) [modulus_len]u8 {
1185 var result: [modulus_len]u8 = undefined;
1186 @memcpy(result[0..msg.len], msg);
1187 @memset(result[msg.len..], 0);
1188 return result;
1189 }
1190
1191 pub const VerifyError = EncryptError || error{InvalidSignature};
1192
1193 pub fn verify(
1194 comptime modulus_len: usize,
1195 sig: *const [modulus_len]u8,
1196 msg: []const u8,
1197 public_key: PublicKey,
1198 comptime Hash: type,
1199 ) VerifyError!void {
1200 try concatVerify(modulus_len, sig, &.{msg}, public_key, Hash);
1201 }
1202
1203 pub fn concatVerify(
1204 comptime modulus_len: usize,
1205 sig: *const [modulus_len]u8,
1206 msg: []const []const u8,
1207 public_key: PublicKey,
1208 comptime Hash: type,
1209 ) VerifyError!void {
1210 const em_dec = try encrypt(modulus_len, sig, public_key);
1211 const em = try EMSA_PKCS1_V1_5_ENCODE(msg, modulus_len, Hash);
1212 if (!std.mem.eql(u8, &em_dec, &em)) return error.InvalidSignature;
1213 }
1214
1215 fn EMSA_PKCS1_V1_5_ENCODE(msg: []const []const u8, comptime emLen: usize, comptime Hash: type) VerifyError![emLen]u8 {
1216 comptime var em_index = emLen;
1217 var em: [emLen]u8 = undefined;
1218
1219 // 1. Apply the hash function to the message M to produce a hash value
1220 // H:
1221 //
1222 // H = Hash(M).
1223 //
1224 // If the hash function outputs "message too long," output "message
1225 // too long" and stop.
1226 var hasher: Hash = .init(.{});
1227 for (msg) |part| hasher.update(part);
1228 em_index -= Hash.digest_length;
1229 hasher.final(em[em_index..]);
1230
1231 // 2. Encode the algorithm ID for the hash function and the hash value
1232 // into an ASN.1 value of type DigestInfo (see Appendix A.2.4) with
1233 // the Distinguished Encoding Rules (DER), where the type DigestInfo
1234 // has the syntax
1235 //
1236 // DigestInfo ::= SEQUENCE {
1237 // digestAlgorithm AlgorithmIdentifier,
1238 // digest OCTET STRING
1239 // }
1240 //
1241 // The first field identifies the hash function and the second
1242 // contains the hash value. Let T be the DER encoding of the
1243 // DigestInfo value (see the notes below) and let tLen be the length
1244 // in octets of T.
1245 const hash_der: []const u8 = &switch (Hash) {
1246 crypto.hash.Md5 => .{
1247 0x30, 0x20, 0x30, 0x0C, 0x06, 0x08, 0x2A, 0x86,
1248 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x05, 0x05, 0x00,
1249 0x04, 0x10,
1250 },
1251 crypto.hash.Sha1 => .{
1252 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e,
1253 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14,
1254 },
1255 crypto.hash.sha2.Sha224 => .{
1256 0x30, 0x2d, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
1257 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x05,
1258 0x00, 0x04, 0x1c,
1259 },
1260 crypto.hash.sha2.Sha256 => .{
1261 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
1262 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05,
1263 0x00, 0x04, 0x20,
1264 },
1265 crypto.hash.sha2.Sha384 => .{
1266 0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
1267 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05,
1268 0x00, 0x04, 0x30,
1269 },
1270 crypto.hash.sha2.Sha512 => .{
1271 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
1272 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05,
1273 0x00, 0x04, 0x40,
1274 },
1275 crypto.hash.sha3.Sha3_256 => .{
1276 0x30, 0x31, 0x30, 0x0D, 0x06, 0x09, 0x60, 0x86,
1277 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x08, 0x05,
1278 0x00, 0x04, 0x20,
1279 },
1280 crypto.hash.sha3.Sha3_512 => .{
1281 0x30, 0x51, 0x30, 0x0D, 0x06, 0x09, 0x60, 0x86,
1282 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x0a, 0x05,
1283 0x00, 0x04, 0x40,
1284 },
1285 else => comptime unreachable,
1286 };
1287 em_index -= hash_der.len;
1288 @memcpy(em[em_index..][0..hash_der.len], hash_der);
1289
1290 // 3. If emLen < tLen + 11, output "intended encoded message length too
1291 // short" and stop.
1292
1293 // 4. Generate an octet string PS consisting of emLen - tLen - 3 octets
1294 // with hexadecimal value 0xff. The length of PS will be at least 8
1295 // octets.
1296 em_index -= 1;
1297 @memset(em[2..em_index], 0xff);
1298
1299 // 5. Concatenate PS, the DER encoding T, and other padding to form the
1300 // encoded message EM as
1301 //
1302 // EM = 0x00 || 0x01 || PS || 0x00 || T.
1303 em[em_index] = 0x00;
1304 em[1] = 0x01;
1305 em[0] = 0x00;
1306
1307 // 6. Output EM.
1308 return em;
1309 }
1310 };
1311
1312 pub const PublicKey = struct {
1313 n: Modulus,
1314 e: Fe,
1315
1316 pub const FromBytesError = error{CertificatePublicKeyInvalid};
1317
1318 pub fn fromBytes(pub_bytes: []const u8, modulus_bytes: []const u8) FromBytesError!PublicKey {
1319 // Reject modulus below 512 bits.
1320 // 512-bit RSA was factored in 1999, so this limit barely means anything,
1321 // but establish some limit now to ratchet in what we can.
1322 const _n = Modulus.fromBytes(modulus_bytes, .big) catch return error.CertificatePublicKeyInvalid;
1323 if (_n.bits() < 512) return error.CertificatePublicKeyInvalid;
1324
1325 // Exponent must be odd and greater than 2.
1326 // Also, it must be less than 2^32 to mitigate DoS attacks.
1327 // Windows CryptoAPI doesn't support values larger than 32 bits [1], so it is
1328 // unlikely that exponents larger than 32 bits are being used for anything
1329 // Windows commonly does.
1330 // [1] https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/ns-wincrypt-rsapubkey
1331 if (pub_bytes.len > 4) return error.CertificatePublicKeyInvalid;
1332 const _e = Fe.fromBytes(_n, pub_bytes, .big) catch return error.CertificatePublicKeyInvalid;
1333 if (!_e.isOdd()) return error.CertificatePublicKeyInvalid;
1334 const e_v = _e.toPrimitive(u32) catch return error.CertificatePublicKeyInvalid;
1335 if (e_v < 2) return error.CertificatePublicKeyInvalid;
1336
1337 return .{
1338 .n = _n,
1339 .e = _e,
1340 };
1341 }
1342
1343 pub const ParseDerError = der.Element.ParseError || error{CertificateFieldHasWrongDataType};
1344
1345 pub fn parseDer(pub_key: []const u8) ParseDerError!struct { modulus: []const u8, exponent: []const u8 } {
1346 const pub_key_seq = try der.Element.parse(pub_key, 0);
1347 if (pub_key_seq.identifier.tag != .sequence) return error.CertificateFieldHasWrongDataType;
1348 const modulus_elem = try der.Element.parse(pub_key, pub_key_seq.slice.start);
1349 if (modulus_elem.identifier.tag != .integer) return error.CertificateFieldHasWrongDataType;
1350 const exponent_elem = try der.Element.parse(pub_key, modulus_elem.slice.end);
1351 if (exponent_elem.identifier.tag != .integer) return error.CertificateFieldHasWrongDataType;
1352 // Skip over meaningless zeroes in the modulus.
1353 const modulus_raw = pub_key[modulus_elem.slice.start..modulus_elem.slice.end];
1354 const modulus_offset = for (modulus_raw, 0..) |byte, i| {
1355 if (byte != 0) break i;
1356 } else modulus_raw.len;
1357 return .{
1358 .modulus = modulus_raw[modulus_offset..],
1359 .exponent = pub_key[exponent_elem.slice.start..exponent_elem.slice.end],
1360 };
1361 }
1362 };
1363
1364 const EncryptError = error{MessageTooLong};
1365
1366 fn encrypt(comptime modulus_len: usize, msg: *const [modulus_len]u8, public_key: PublicKey) EncryptError![modulus_len]u8 {
1367 const m = Fe.fromBytes(public_key.n, msg, .big) catch return error.MessageTooLong;
1368 const e = public_key.n.powPublic(m, public_key.e) catch unreachable;
1369 var res: [modulus_len]u8 = undefined;
1370 e.toBytes(&res, .big) catch unreachable;
1371 return res;
1372 }
1373};