authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-12-27 19:10:31-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-01-02 16:57:15-07:00
log5bbedb63cf43297bcb6ff1dca12affafebc9c09e
tree0ce1ebec9f16d6fdbda8a9a043507f38ed4e697a
parentb1cbfa0ec640c3a998e4a59352b93da37f359ff7

std.crypto.Certificate: support verifying secp384r1 pub keys


1 files changed, 158 insertions(+), 81 deletions(-)

lib/std/crypto/Certificate.zig+158-81
...@@ -71,6 +71,16 @@ pub const Attribute = enum {...@@ -71,6 +71,16 @@ pub const Attribute = enum {
71 });71 });
72};72};
7373
74pub const NamedCurve = enum {
75 secp384r1,
76 X9_62_prime256v1,
77
78 pub const map = std.ComptimeStringMap(NamedCurve, .{
79 .{ &[_]u8{ 0x2B, 0x81, 0x04, 0x00, 0x22 }, .secp384r1 },
80 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07 }, .X9_62_prime256v1 },
81 });
82};
83
74pub const Parsed = struct {84pub const Parsed = struct {
75 certificate: Certificate,85 certificate: Certificate,
76 issuer_slice: Slice,86 issuer_slice: Slice,
...@@ -78,11 +88,16 @@ pub const Parsed = struct {...@@ -78,11 +88,16 @@ pub const Parsed = struct {
78 common_name_slice: Slice,88 common_name_slice: Slice,
79 signature_slice: Slice,89 signature_slice: Slice,
80 signature_algorithm: Algorithm,90 signature_algorithm: Algorithm,
81 pub_key_algo: AlgorithmCategory,91 pub_key_algo: PubKeyAlgo,
82 pub_key_slice: Slice,92 pub_key_slice: Slice,
83 message_slice: Slice,93 message_slice: Slice,
84 validity: Validity,94 validity: Validity,
8595
96 pub const PubKeyAlgo = union(AlgorithmCategory) {
97 rsaEncryption: void,
98 X9_62_id_ecPublicKey: NamedCurve,
99 };
100
86 pub const Validity = struct {101 pub const Validity = struct {
87 not_before: u64,102 not_before: u64,
88 not_after: u64,103 not_after: u64,
...@@ -114,6 +129,10 @@ pub const Parsed = struct {...@@ -114,6 +129,10 @@ pub const Parsed = struct {
114 return p.slice(p.pub_key_slice);129 return p.slice(p.pub_key_slice);
115 }130 }
116131
132 pub fn pubKeySigAlgo(p: Parsed) []const u8 {
133 return p.slice(p.pub_key_signature_algorithm_slice);
134 }
135
117 pub fn message(p: Parsed) []const u8 {136 pub fn message(p: Parsed) []const u8 {
118 return p.slice(p.message_slice);137 return p.slice(p.message_slice);
119 }138 }
...@@ -130,6 +149,7 @@ pub const Parsed = struct {...@@ -130,6 +149,7 @@ pub const Parsed = struct {
130 CertificateSignatureInvalidLength,149 CertificateSignatureInvalidLength,
131 CertificateSignatureInvalid,150 CertificateSignatureInvalid,
132 CertificateSignatureUnsupportedBitCount,151 CertificateSignatureUnsupportedBitCount,
152 CertificateSignatureNamedCurveUnsupported,
133 };153 };
134154
135 /// This function checks the time validity for the subject only. Checking155 /// This function checks the time validity for the subject only. Checking
...@@ -160,56 +180,78 @@ pub const Parsed = struct {...@@ -160,56 +180,78 @@ pub const Parsed = struct {
160 parsed_issuer.pub_key_algo,180 parsed_issuer.pub_key_algo,
161 parsed_issuer.pubKey(),181 parsed_issuer.pubKey(),
162 ),182 ),
163 .ecdsa_with_SHA224,183
184 inline .ecdsa_with_SHA224,
164 .ecdsa_with_SHA256,185 .ecdsa_with_SHA256,
165 .ecdsa_with_SHA384,186 .ecdsa_with_SHA384,
166 .ecdsa_with_SHA512,187 .ecdsa_with_SHA512,
167 => {188 => |algorithm| return verify_ecdsa(
168 return error.CertificateSignatureAlgorithmUnsupported;189 algorithm.Hash(),
169 },190 parsed_subject.message(),
191 parsed_subject.signature(),
192 parsed_issuer.pub_key_algo,
193 parsed_issuer.pubKey(),
194 ),
170 }195 }
171 }196 }
172};197};
173198
174pub fn parse(cert: Certificate) !Parsed {199pub fn parse(cert: Certificate) !Parsed {
175 const cert_bytes = cert.buffer;200 const cert_bytes = cert.buffer;
176 const certificate = try der.parseElement(cert_bytes, cert.index);201 const certificate = try der.Element.parse(cert_bytes, cert.index);
177 const tbs_certificate = try der.parseElement(cert_bytes, certificate.slice.start);202 const tbs_certificate = try der.Element.parse(cert_bytes, certificate.slice.start);
178 const version = try der.parseElement(cert_bytes, tbs_certificate.slice.start);203 const version = try der.Element.parse(cert_bytes, tbs_certificate.slice.start);
179 try checkVersion(cert_bytes, version);204 try checkVersion(cert_bytes, version);
180 const serial_number = try der.parseElement(cert_bytes, version.slice.end);205 const serial_number = try der.Element.parse(cert_bytes, version.slice.end);
181 // RFC 5280, section 4.1.2.3:206 // RFC 5280, section 4.1.2.3:
182 // "This field MUST contain the same algorithm identifier as207 // "This field MUST contain the same algorithm identifier as
183 // the signatureAlgorithm field in the sequence Certificate."208 // the signatureAlgorithm field in the sequence Certificate."
184 const tbs_signature = try der.parseElement(cert_bytes, serial_number.slice.end);209 const tbs_signature = try der.Element.parse(cert_bytes, serial_number.slice.end);
185 const issuer = try der.parseElement(cert_bytes, tbs_signature.slice.end);210 const issuer = try der.Element.parse(cert_bytes, tbs_signature.slice.end);
186 const validity = try der.parseElement(cert_bytes, issuer.slice.end);211 const validity = try der.Element.parse(cert_bytes, issuer.slice.end);
187 const not_before = try der.parseElement(cert_bytes, validity.slice.start);212 const not_before = try der.Element.parse(cert_bytes, validity.slice.start);
188 const not_before_utc = try parseTime(cert, not_before);213 const not_before_utc = try parseTime(cert, not_before);
189 const not_after = try der.parseElement(cert_bytes, not_before.slice.end);214 const not_after = try der.Element.parse(cert_bytes, not_before.slice.end);
190 const not_after_utc = try parseTime(cert, not_after);215 const not_after_utc = try parseTime(cert, not_after);
191 const subject = try der.parseElement(cert_bytes, validity.slice.end);216 const subject = try der.Element.parse(cert_bytes, validity.slice.end);
192217
193 const pub_key_info = try der.parseElement(cert_bytes, subject.slice.end);218 const pub_key_info = try der.Element.parse(cert_bytes, subject.slice.end);
194 const pub_key_signature_algorithm = try der.parseElement(cert_bytes, pub_key_info.slice.start);219 const pub_key_signature_algorithm = try der.Element.parse(cert_bytes, pub_key_info.slice.start);
195 const pub_key_algo_elem = try der.parseElement(cert_bytes, pub_key_signature_algorithm.slice.start);220 const pub_key_algo_elem = try der.Element.parse(cert_bytes, pub_key_signature_algorithm.slice.start);
196 const pub_key_algo = try parseAlgorithmCategory(cert_bytes, pub_key_algo_elem);221 const pub_key_algo_tag = try parseAlgorithmCategory(cert_bytes, pub_key_algo_elem);
197 const pub_key_elem = try der.parseElement(cert_bytes, pub_key_signature_algorithm.slice.end);222 var pub_key_algo: Parsed.PubKeyAlgo = undefined;
223 switch (pub_key_algo_tag) {
224 .rsaEncryption => {
225 pub_key_algo = .{ .rsaEncryption = {} };
226 },
227 .X9_62_id_ecPublicKey => {
228 // RFC 5480 Section 2.1.1.1 Named Curve
229 // ECParameters ::= CHOICE {
230 // namedCurve OBJECT IDENTIFIER
231 // -- implicitCurve NULL
232 // -- specifiedCurve SpecifiedECDomain
233 // }
234 const params_elem = try der.Element.parse(cert_bytes, pub_key_algo_elem.slice.end);
235 const named_curve = try parseNamedCurve(cert_bytes, params_elem);
236 pub_key_algo = .{ .X9_62_id_ecPublicKey = named_curve };
237 },
238 }
239 const pub_key_elem = try der.Element.parse(cert_bytes, pub_key_signature_algorithm.slice.end);
198 const pub_key = try parseBitString(cert, pub_key_elem);240 const pub_key = try parseBitString(cert, pub_key_elem);
199241
200 var common_name = der.Element.Slice.empty;242 var common_name = der.Element.Slice.empty;
201 var name_i = subject.slice.start;243 var name_i = subject.slice.start;
202 //std.debug.print("subject name:\n", .{});244 //std.debug.print("subject name:\n", .{});
203 while (name_i < subject.slice.end) {245 while (name_i < subject.slice.end) {
204 const rdn = try der.parseElement(cert_bytes, name_i);246 const rdn = try der.Element.parse(cert_bytes, name_i);
205 var rdn_i = rdn.slice.start;247 var rdn_i = rdn.slice.start;
206 while (rdn_i < rdn.slice.end) {248 while (rdn_i < rdn.slice.end) {
207 const atav = try der.parseElement(cert_bytes, rdn_i);249 const atav = try der.Element.parse(cert_bytes, rdn_i);
208 var atav_i = atav.slice.start;250 var atav_i = atav.slice.start;
209 while (atav_i < atav.slice.end) {251 while (atav_i < atav.slice.end) {
210 const ty_elem = try der.parseElement(cert_bytes, atav_i);252 const ty_elem = try der.Element.parse(cert_bytes, atav_i);
211 const ty = try parseAttribute(cert_bytes, ty_elem);253 const ty = try parseAttribute(cert_bytes, ty_elem);
212 const val = try der.parseElement(cert_bytes, ty_elem.slice.end);254 const val = try der.Element.parse(cert_bytes, ty_elem.slice.end);
213 //std.debug.print(" {s}: '{s}'\n", .{255 //std.debug.print(" {s}: '{s}'\n", .{
214 // @tagName(ty), cert_bytes[val.slice.start..val.slice.end],256 // @tagName(ty), cert_bytes[val.slice.start..val.slice.end],
215 //});257 //});
...@@ -224,10 +266,10 @@ pub fn parse(cert: Certificate) !Parsed {...@@ -224,10 +266,10 @@ pub fn parse(cert: Certificate) !Parsed {
224 name_i = rdn.slice.end;266 name_i = rdn.slice.end;
225 }267 }
226268
227 const sig_algo = try der.parseElement(cert_bytes, tbs_certificate.slice.end);269 const sig_algo = try der.Element.parse(cert_bytes, tbs_certificate.slice.end);
228 const algo_elem = try der.parseElement(cert_bytes, sig_algo.slice.start);270 const algo_elem = try der.Element.parse(cert_bytes, sig_algo.slice.start);
229 const signature_algorithm = try parseAlgorithm(cert_bytes, algo_elem);271 const signature_algorithm = try parseAlgorithm(cert_bytes, algo_elem);
230 const sig_elem = try der.parseElement(cert_bytes, sig_algo.slice.end);272 const sig_elem = try der.Element.parse(cert_bytes, sig_algo.slice.end);
231 const signature = try parseBitString(cert, sig_elem);273 const signature = try parseBitString(cert, sig_elem);
232274
233 return .{275 return .{
...@@ -391,45 +433,52 @@ test parseYear4 {...@@ -391,45 +433,52 @@ test parseYear4 {
391}433}
392434
393pub fn parseAlgorithm(bytes: []const u8, element: der.Element) !Algorithm {435pub fn parseAlgorithm(bytes: []const u8, element: der.Element) !Algorithm {
394 if (element.identifier.tag != .object_identifier)436 return parseEnum(Algorithm, bytes, element);
395 return error.CertificateFieldHasWrongDataType;
396 const oid_bytes = bytes[element.slice.start..element.slice.end];
397 return Algorithm.map.get(oid_bytes) orelse {
398 //std.debug.print("oid bytes: {}\n", .{std.fmt.fmtSliceHexLower(oid_bytes)});
399 return error.CertificateHasUnrecognizedAlgorithm;
400 };
401}437}
402438
403pub fn parseAlgorithmCategory(bytes: []const u8, element: der.Element) !AlgorithmCategory {439pub fn parseAlgorithmCategory(bytes: []const u8, element: der.Element) !AlgorithmCategory {
404 if (element.identifier.tag != .object_identifier)440 return parseEnum(AlgorithmCategory, bytes, element);
405 return error.CertificateFieldHasWrongDataType;
406 return AlgorithmCategory.map.get(bytes[element.slice.start..element.slice.end]) orelse
407 return error.CertificateHasUnrecognizedAlgorithmCategory;
408}441}
409442
410pub fn parseAttribute(bytes: []const u8, element: der.Element) !Attribute {443pub fn parseAttribute(bytes: []const u8, element: der.Element) !Attribute {
444 return parseEnum(Attribute, bytes, element);
445}
446
447pub fn parseNamedCurve(bytes: []const u8, element: der.Element) !NamedCurve {
448 return parseEnum(NamedCurve, bytes, element);
449}
450
451fn parseEnum(comptime E: type, bytes: []const u8, element: der.Element) !E {
411 if (element.identifier.tag != .object_identifier)452 if (element.identifier.tag != .object_identifier)
412 return error.CertificateFieldHasWrongDataType;453 return error.CertificateFieldHasWrongDataType;
413 const oid_bytes = bytes[element.slice.start..element.slice.end];454 const oid_bytes = bytes[element.slice.start..element.slice.end];
414 return Attribute.map.get(oid_bytes) orelse {455 return E.map.get(oid_bytes) orelse {
415 //std.debug.print("attr: {}\n", .{std.fmt.fmtSliceHexLower(oid_bytes)});456 //std.debug.print("tag: {}\n", .{std.fmt.fmtSliceHexLower(oid_bytes)});
416 return error.CertificateHasUnrecognizedAttribute;457 return error.CertificateHasUnrecognizedObjectId;
417 };458 };
418}459}
419460
461pub fn checkVersion(bytes: []const u8, version: der.Element) !void {
462 if (@bitCast(u8, version.identifier) != 0xa0 or
463 !mem.eql(u8, bytes[version.slice.start..version.slice.end], "\x02\x01\x02"))
464 {
465 return error.UnsupportedCertificateVersion;
466 }
467}
468
420fn verifyRsa(469fn verifyRsa(
421 comptime Hash: type,470 comptime Hash: type,
422 message: []const u8,471 message: []const u8,
423 sig: []const u8,472 sig: []const u8,
424 pub_key_algo: AlgorithmCategory,473 pub_key_algo: Parsed.PubKeyAlgo,
425 pub_key: []const u8,474 pub_key: []const u8,
426) !void {475) !void {
427 if (pub_key_algo != .rsaEncryption) return error.CertificateSignatureAlgorithmMismatch;476 if (pub_key_algo != .rsaEncryption) return error.CertificateSignatureAlgorithmMismatch;
428 const pub_key_seq = try der.parseElement(pub_key, 0);477 const pub_key_seq = try der.Element.parse(pub_key, 0);
429 if (pub_key_seq.identifier.tag != .sequence) return error.CertificateFieldHasWrongDataType;478 if (pub_key_seq.identifier.tag != .sequence) return error.CertificateFieldHasWrongDataType;
430 const modulus_elem = try der.parseElement(pub_key, pub_key_seq.slice.start);479 const modulus_elem = try der.Element.parse(pub_key, pub_key_seq.slice.start);
431 if (modulus_elem.identifier.tag != .integer) return error.CertificateFieldHasWrongDataType;480 if (modulus_elem.identifier.tag != .integer) return error.CertificateFieldHasWrongDataType;
432 const exponent_elem = try der.parseElement(pub_key, modulus_elem.slice.end);481 const exponent_elem = try der.Element.parse(pub_key, modulus_elem.slice.end);
433 if (exponent_elem.identifier.tag != .integer) return error.CertificateFieldHasWrongDataType;482 if (exponent_elem.identifier.tag != .integer) return error.CertificateFieldHasWrongDataType;
434 // Skip over meaningless zeroes in the modulus.483 // Skip over meaningless zeroes in the modulus.
435 const modulus_raw = pub_key[modulus_elem.slice.start..modulus_elem.slice.end];484 const modulus_raw = pub_key[modulus_elem.slice.start..modulus_elem.slice.end];
...@@ -504,11 +553,39 @@ fn verifyRsa(...@@ -504,11 +553,39 @@ fn verifyRsa(
504 }553 }
505}554}
506555
507pub fn checkVersion(bytes: []const u8, version: der.Element) !void {556fn verify_ecdsa(
508 if (@bitCast(u8, version.identifier) != 0xa0 or557 comptime Hash: type,
509 !mem.eql(u8, bytes[version.slice.start..version.slice.end], "\x02\x01\x02"))558 message: []const u8,
510 {559 encoded_sig: []const u8,
511 return error.UnsupportedCertificateVersion;560 pub_key_algo: Parsed.PubKeyAlgo,
561 sec1_pub_key: []const u8,
562) !void {
563 const sig_named_curve = switch (pub_key_algo) {
564 .X9_62_id_ecPublicKey => |named_curve| named_curve,
565 else => return error.CertificateSignatureAlgorithmMismatch,
566 };
567
568 switch (sig_named_curve) {
569 .secp384r1 => {
570 const P = crypto.ecc.P384;
571 const Ecdsa = crypto.sign.ecdsa.Ecdsa(P, Hash);
572 const sig = Ecdsa.Signature.fromDer(encoded_sig) catch |err| switch (err) {
573 error.InvalidEncoding => return error.CertificateSignatureInvalid,
574 };
575 const pub_key = Ecdsa.PublicKey.fromSec1(sec1_pub_key) catch |err| switch (err) {
576 error.InvalidEncoding => return error.CertificateSignatureInvalid,
577 error.NonCanonical => return error.CertificateSignatureInvalid,
578 error.NotSquare => return error.CertificateSignatureInvalid,
579 };
580 sig.verify(message, pub_key) catch |err| switch (err) {
581 error.IdentityElement => return error.CertificateSignatureInvalid,
582 error.NonCanonical => return error.CertificateSignatureInvalid,
583 error.SignatureVerificationFailed => return error.CertificateSignatureInvalid,
584 };
585 },
586 .X9_62_prime256v1 => {
587 return error.CertificateSignatureNamedCurveUnsupported;
588 },
512 }589 }
513}590}
514591
...@@ -559,45 +636,45 @@ pub const der = struct {...@@ -559,45 +636,45 @@ pub const der = struct {
559636
560 pub const empty: Slice = .{ .start = 0, .end = 0 };637 pub const empty: Slice = .{ .start = 0, .end = 0 };
561 };638 };
562 };
563639
564 pub const ParseElementError = error{CertificateFieldHasInvalidLength};640 pub const ParseError = error{CertificateFieldHasInvalidLength};
641
642 pub fn parse(bytes: []const u8, index: u32) ParseError!Element {
643 var i = index;
644 const identifier = @bitCast(Identifier, bytes[i]);
645 i += 1;
646 const size_byte = bytes[i];
647 i += 1;
648 if ((size_byte >> 7) == 0) {
649 return .{
650 .identifier = identifier,
651 .slice = .{
652 .start = i,
653 .end = i + size_byte,
654 },
655 };
656 }
657
658 const len_size = @truncate(u7, size_byte);
659 if (len_size > @sizeOf(u32)) {
660 return error.CertificateFieldHasInvalidLength;
661 }
662
663 const end_i = i + len_size;
664 var long_form_size: u32 = 0;
665 while (i < end_i) : (i += 1) {
666 long_form_size = (long_form_size << 8) | bytes[i];
667 }
565668
566 pub fn parseElement(bytes: []const u8, index: u32) ParseElementError!Element {
567 var i = index;
568 const identifier = @bitCast(Identifier, bytes[i]);
569 i += 1;
570 const size_byte = bytes[i];
571 i += 1;
572 if ((size_byte >> 7) == 0) {
573 return .{669 return .{
574 .identifier = identifier,670 .identifier = identifier,
575 .slice = .{671 .slice = .{
576 .start = i,672 .start = i,
577 .end = i + size_byte,673 .end = i + long_form_size,
578 },674 },
579 };675 };
580 }676 }
581677 };
582 const len_size = @truncate(u7, size_byte);
583 if (len_size > @sizeOf(u32)) {
584 return error.CertificateFieldHasInvalidLength;
585 }
586
587 const end_i = i + len_size;
588 var long_form_size: u32 = 0;
589 while (i < end_i) : (i += 1) {
590 long_form_size = (long_form_size << 8) | bytes[i];
591 }
592
593 return .{
594 .identifier = identifier,
595 .slice = .{
596 .start = i,
597 .end = i + long_form_size,
598 },
599 };
600 }
601};678};
602679
603test {680test {