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