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};