| ... | ... | @@ -71,6 +71,16 @@ pub const Attribute = enum { |
| 71 | 71 | }); |
| 72 | 72 | }; |
| 73 | 73 | |
| 74 | pub 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 | |
| 74 | 84 | pub const Parsed = struct { |
| 75 | 85 | certificate: Certificate, |
| 76 | 86 | issuer_slice: Slice, |
| ... | ... | @@ -78,11 +88,16 @@ pub const Parsed = struct { |
| 78 | 88 | common_name_slice: Slice, |
| 79 | 89 | signature_slice: Slice, |
| 80 | 90 | signature_algorithm: Algorithm, |
| 81 | | pub_key_algo: AlgorithmCategory, |
| 91 | pub_key_algo: PubKeyAlgo, |
| 82 | 92 | pub_key_slice: Slice, |
| 83 | 93 | message_slice: Slice, |
| 84 | 94 | validity: Validity, |
| 85 | 95 | |
| 96 | pub const PubKeyAlgo = union(AlgorithmCategory) { |
| 97 | rsaEncryption: void, |
| 98 | X9_62_id_ecPublicKey: NamedCurve, |
| 99 | }; |
| 100 | |
| 86 | 101 | pub const Validity = struct { |
| 87 | 102 | not_before: u64, |
| 88 | 103 | not_after: u64, |
| ... | ... | @@ -114,6 +129,10 @@ pub const Parsed = struct { |
| 114 | 129 | return p.slice(p.pub_key_slice); |
| 115 | 130 | } |
| 116 | 131 | |
| 132 | pub fn pubKeySigAlgo(p: Parsed) []const u8 { |
| 133 | return p.slice(p.pub_key_signature_algorithm_slice); |
| 134 | } |
| 135 | |
| 117 | 136 | pub fn message(p: Parsed) []const u8 { |
| 118 | 137 | return p.slice(p.message_slice); |
| 119 | 138 | } |
| ... | ... | @@ -130,6 +149,7 @@ pub const Parsed = struct { |
| 130 | 149 | CertificateSignatureInvalidLength, |
| 131 | 150 | CertificateSignatureInvalid, |
| 132 | 151 | CertificateSignatureUnsupportedBitCount, |
| 152 | CertificateSignatureNamedCurveUnsupported, |
| 133 | 153 | }; |
| 134 | 154 | |
| 135 | 155 | /// This function checks the time validity for the subject only. Checking |
| ... | ... | @@ -160,56 +180,78 @@ pub const Parsed = struct { |
| 160 | 180 | parsed_issuer.pub_key_algo, |
| 161 | 181 | parsed_issuer.pubKey(), |
| 162 | 182 | ), |
| 163 | | .ecdsa_with_SHA224, |
| 183 | |
| 184 | inline .ecdsa_with_SHA224, |
| 164 | 185 | .ecdsa_with_SHA256, |
| 165 | 186 | .ecdsa_with_SHA384, |
| 166 | 187 | .ecdsa_with_SHA512, |
| 167 | | => { |
| 168 | | return error.CertificateSignatureAlgorithmUnsupported; |
| 169 | | }, |
| 188 | => |algorithm| return verify_ecdsa( |
| 189 | algorithm.Hash(), |
| 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 | }; |
| 173 | 198 | |
| 174 | 199 | pub fn parse(cert: Certificate) !Parsed { |
| 175 | 200 | const cert_bytes = cert.buffer; |
| 176 | | const certificate = try der.parseElement(cert_bytes, cert.index); |
| 177 | | const tbs_certificate = try der.parseElement(cert_bytes, certificate.slice.start); |
| 178 | | const version = try der.parseElement(cert_bytes, tbs_certificate.slice.start); |
| 201 | const certificate = try der.Element.parse(cert_bytes, cert.index); |
| 202 | const tbs_certificate = try der.Element.parse(cert_bytes, certificate.slice.start); |
| 203 | const version = try der.Element.parse(cert_bytes, tbs_certificate.slice.start); |
| 179 | 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 | 206 | // RFC 5280, section 4.1.2.3: |
| 182 | 207 | // "This field MUST contain the same algorithm identifier as |
| 183 | 208 | // the signatureAlgorithm field in the sequence Certificate." |
| 184 | | const tbs_signature = try der.parseElement(cert_bytes, serial_number.slice.end); |
| 185 | | const issuer = try der.parseElement(cert_bytes, tbs_signature.slice.end); |
| 186 | | const validity = try der.parseElement(cert_bytes, issuer.slice.end); |
| 187 | | const not_before = try der.parseElement(cert_bytes, validity.slice.start); |
| 209 | const tbs_signature = try der.Element.parse(cert_bytes, serial_number.slice.end); |
| 210 | const issuer = try der.Element.parse(cert_bytes, tbs_signature.slice.end); |
| 211 | const validity = try der.Element.parse(cert_bytes, issuer.slice.end); |
| 212 | const not_before = try der.Element.parse(cert_bytes, validity.slice.start); |
| 188 | 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 | 215 | const not_after_utc = try parseTime(cert, not_after); |
| 191 | | const subject = try der.parseElement(cert_bytes, validity.slice.end); |
| 192 | | |
| 193 | | const pub_key_info = try der.parseElement(cert_bytes, subject.slice.end); |
| 194 | | const pub_key_signature_algorithm = try der.parseElement(cert_bytes, pub_key_info.slice.start); |
| 195 | | const pub_key_algo_elem = try der.parseElement(cert_bytes, pub_key_signature_algorithm.slice.start); |
| 196 | | const pub_key_algo = try parseAlgorithmCategory(cert_bytes, pub_key_algo_elem); |
| 197 | | const pub_key_elem = try der.parseElement(cert_bytes, pub_key_signature_algorithm.slice.end); |
| 216 | const subject = try der.Element.parse(cert_bytes, validity.slice.end); |
| 217 | |
| 218 | const pub_key_info = try der.Element.parse(cert_bytes, subject.slice.end); |
| 219 | const pub_key_signature_algorithm = try der.Element.parse(cert_bytes, pub_key_info.slice.start); |
| 220 | const pub_key_algo_elem = try der.Element.parse(cert_bytes, pub_key_signature_algorithm.slice.start); |
| 221 | const pub_key_algo_tag = try parseAlgorithmCategory(cert_bytes, pub_key_algo_elem); |
| 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 | 240 | const pub_key = try parseBitString(cert, pub_key_elem); |
| 199 | 241 | |
| 200 | 242 | var common_name = der.Element.Slice.empty; |
| 201 | 243 | var name_i = subject.slice.start; |
| 202 | 244 | //std.debug.print("subject name:\n", .{}); |
| 203 | 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 | 247 | var rdn_i = rdn.slice.start; |
| 206 | 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 | 250 | var atav_i = atav.slice.start; |
| 209 | 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 | 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 | 255 | //std.debug.print(" {s}: '{s}'\n", .{ |
| 214 | 256 | // @tagName(ty), cert_bytes[val.slice.start..val.slice.end], |
| 215 | 257 | //}); |
| ... | ... | @@ -224,10 +266,10 @@ pub fn parse(cert: Certificate) !Parsed { |
| 224 | 266 | name_i = rdn.slice.end; |
| 225 | 267 | } |
| 226 | 268 | |
| 227 | | const sig_algo = try der.parseElement(cert_bytes, tbs_certificate.slice.end); |
| 228 | | const algo_elem = try der.parseElement(cert_bytes, sig_algo.slice.start); |
| 269 | const sig_algo = try der.Element.parse(cert_bytes, tbs_certificate.slice.end); |
| 270 | const algo_elem = try der.Element.parse(cert_bytes, sig_algo.slice.start); |
| 229 | 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 | 273 | const signature = try parseBitString(cert, sig_elem); |
| 232 | 274 | |
| 233 | 275 | return .{ |
| ... | ... | @@ -391,45 +433,52 @@ test parseYear4 { |
| 391 | 433 | } |
| 392 | 434 | |
| 393 | 435 | pub fn parseAlgorithm(bytes: []const u8, element: der.Element) !Algorithm { |
| 394 | | if (element.identifier.tag != .object_identifier) |
| 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 | | }; |
| 436 | return parseEnum(Algorithm, bytes, element); |
| 401 | 437 | } |
| 402 | 438 | |
| 403 | 439 | pub fn parseAlgorithmCategory(bytes: []const u8, element: der.Element) !AlgorithmCategory { |
| 404 | | if (element.identifier.tag != .object_identifier) |
| 405 | | return error.CertificateFieldHasWrongDataType; |
| 406 | | return AlgorithmCategory.map.get(bytes[element.slice.start..element.slice.end]) orelse |
| 407 | | return error.CertificateHasUnrecognizedAlgorithmCategory; |
| 440 | return parseEnum(AlgorithmCategory, bytes, element); |
| 408 | 441 | } |
| 409 | 442 | |
| 410 | 443 | pub fn parseAttribute(bytes: []const u8, element: der.Element) !Attribute { |
| 444 | return parseEnum(Attribute, bytes, element); |
| 445 | } |
| 446 | |
| 447 | pub fn parseNamedCurve(bytes: []const u8, element: der.Element) !NamedCurve { |
| 448 | return parseEnum(NamedCurve, bytes, element); |
| 449 | } |
| 450 | |
| 451 | fn parseEnum(comptime E: type, bytes: []const u8, element: der.Element) !E { |
| 411 | 452 | if (element.identifier.tag != .object_identifier) |
| 412 | 453 | return error.CertificateFieldHasWrongDataType; |
| 413 | 454 | const oid_bytes = bytes[element.slice.start..element.slice.end]; |
| 414 | | return Attribute.map.get(oid_bytes) orelse { |
| 415 | | //std.debug.print("attr: {}\n", .{std.fmt.fmtSliceHexLower(oid_bytes)}); |
| 416 | | return error.CertificateHasUnrecognizedAttribute; |
| 455 | return E.map.get(oid_bytes) orelse { |
| 456 | //std.debug.print("tag: {}\n", .{std.fmt.fmtSliceHexLower(oid_bytes)}); |
| 457 | return error.CertificateHasUnrecognizedObjectId; |
| 417 | 458 | }; |
| 418 | 459 | } |
| 419 | 460 | |
| 461 | pub 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 | |
| 420 | 469 | fn verifyRsa( |
| 421 | 470 | comptime Hash: type, |
| 422 | 471 | message: []const u8, |
| 423 | 472 | sig: []const u8, |
| 424 | | pub_key_algo: AlgorithmCategory, |
| 473 | pub_key_algo: Parsed.PubKeyAlgo, |
| 425 | 474 | pub_key: []const u8, |
| 426 | 475 | ) !void { |
| 427 | 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 | 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 | 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 | 482 | if (exponent_elem.identifier.tag != .integer) return error.CertificateFieldHasWrongDataType; |
| 434 | 483 | // Skip over meaningless zeroes in the modulus. |
| 435 | 484 | const modulus_raw = pub_key[modulus_elem.slice.start..modulus_elem.slice.end]; |
| ... | ... | @@ -504,11 +553,39 @@ fn verifyRsa( |
| 504 | 553 | } |
| 505 | 554 | } |
| 506 | 555 | |
| 507 | | pub fn checkVersion(bytes: []const u8, version: der.Element) !void { |
| 508 | | if (@bitCast(u8, version.identifier) != 0xa0 or |
| 509 | | !mem.eql(u8, bytes[version.slice.start..version.slice.end], "\x02\x01\x02")) |
| 510 | | { |
| 511 | | return error.UnsupportedCertificateVersion; |
| 556 | fn verify_ecdsa( |
| 557 | comptime Hash: type, |
| 558 | message: []const u8, |
| 559 | encoded_sig: []const u8, |
| 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 | } |
| 514 | 591 | |
| ... | ... | @@ -559,45 +636,45 @@ pub const der = struct { |
| 559 | 636 | |
| 560 | 637 | pub const empty: Slice = .{ .start = 0, .end = 0 }; |
| 561 | 638 | }; |
| 562 | | }; |
| 563 | 639 | |
| 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 | } |
| 565 | 668 | |
| 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 | 669 | return .{ |
| 574 | 670 | .identifier = identifier, |
| 575 | 671 | .slice = .{ |
| 576 | 672 | .start = i, |
| 577 | | .end = i + size_byte, |
| 673 | .end = i + long_form_size, |
| 578 | 674 | }, |
| 579 | 675 | }; |
| 580 | 676 | } |
| 581 | | |
| 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 | | } |
| 677 | }; |
| 601 | 678 | }; |
| 602 | 679 | |
| 603 | 680 | test { |