| 1 | //! A sequence of certificates, where each certificate is authenticated by the next certificate. |
| 2 | const Chain = @This(); |
| 3 | |
| 4 | store: ?crypt32.HCERTSTORE, |
| 5 | primary: ?*const crypt32.CERT_CONTEXT, |
| 6 | |
| 7 | pub const empty: Chain = .{ .store = null, .primary = null }; |
| 8 | |
| 9 | pub fn deinit(chain: *Chain) void { |
| 10 | if (chain.primary) |primary| assert(crypt32.CertFreeCertificateContext(primary).toBool()); |
| 11 | if (chain.store) |store| if (!crypt32.CertCloseStore(store, .{ |
| 12 | .CHECK = std.debug.runtime_safety, |
| 13 | }).toBool()) std.os.windows.unexpectedError(std.os.windows.GetLastError()) catch unreachable; |
| 14 | chain.* = .empty; |
| 15 | } |
| 16 | |
| 17 | pub fn addCert(chain: *Chain, cert: []const u8) std.Io.UnexpectedError!void { |
| 18 | const store = chain.store orelse store: { |
| 19 | const store = crypt32.CertOpenStore( |
| 20 | .MEMORY, |
| 21 | .{}, |
| 22 | .NULL, |
| 23 | .{}, |
| 24 | null, |
| 25 | ) orelse return std.os.windows.unexpectedError(std.os.windows.GetLastError()); |
| 26 | chain.store = store; |
| 27 | break :store store; |
| 28 | }; |
| 29 | if (!crypt32.CertAddEncodedCertificateToStore( |
| 30 | store, |
| 31 | .{ .CERT = .ASN }, |
| 32 | cert.ptr, |
| 33 | @intCast(cert.len), |
| 34 | .ALWAYS, |
| 35 | if (chain.primary) |_| null else &chain.primary, |
| 36 | ).toBool()) return std.os.windows.unexpectedError(std.os.windows.GetLastError()); |
| 37 | } |
| 38 | |
| 39 | pub const VerifyError = error{ |
| 40 | TlsCertificateNotVerified, |
| 41 | } || std.Io.UnexpectedError; |
| 42 | |
| 43 | pub fn verify(chain: *const Chain, now: std.Io.Timestamp) VerifyError!void { |
| 44 | const now_win = @divFloor(now.nanoseconds - std.time.epoch.windows * std.time.ns_per_s, 100); |
| 45 | var cert_chain: *const crypt32.CERT_CHAIN.CONTEXT = undefined; |
| 46 | if (!crypt32.CertGetCertificateChain( |
| 47 | .CURRENT_USER, |
| 48 | chain.primary orelse return error.TlsCertificateNotVerified, |
| 49 | &.{ |
| 50 | .dwLowDateTime = @bitCast(@as(i32, @truncate(now_win >> 0))), |
| 51 | .dwHighDateTime = @bitCast(@as(i32, @intCast(now_win >> 32))), |
| 52 | }, |
| 53 | null, |
| 54 | &.{ .RequestedUsage = .{ .dwType = .AND, .Usage = .{ |
| 55 | .cUsageIdentifier = ALLOWED_EKUS.len, |
| 56 | .rgpszUsageIdentifier = &ALLOWED_EKUS, |
| 57 | } } }, |
| 58 | .{ .REVOCATION_CHECK_END_CERT = true, .REVOCATION_ACCUMULATIVE_TIMEOUT = true }, |
| 59 | null, |
| 60 | &cert_chain, |
| 61 | ).toBool()) return std.os.windows.unexpectedError(std.os.windows.GetLastError()); |
| 62 | defer crypt32.CertFreeCertificateChain(cert_chain); |
| 63 | var status: crypt32.CERT_CHAIN.POLICY.STATUS = .{ |
| 64 | .dwError = undefined, |
| 65 | .lChainIndex = undefined, |
| 66 | .lElementIndex = undefined, |
| 67 | .pvExtraPolicyStatus = undefined, |
| 68 | }; |
| 69 | if (!crypt32.CertVerifyCertificateChainPolicy( |
| 70 | .SSL, |
| 71 | cert_chain, |
| 72 | &.{ |
| 73 | .dwFlags = .{ |
| 74 | .IGNORE_END_REV_UNKNOWN = true, |
| 75 | .IGNORE_CTL_SIGNER_REV_UNKNOWN = true, |
| 76 | .IGNORE_CA_REV_UNKNOWN = true, |
| 77 | .IGNORE_ROOT_REV_UNKNOWN = true, |
| 78 | }, |
| 79 | .pvExtraPolicyPara = @constCast(&crypt32.HTTPSPolicyCallbackData{ .dwAuthType = .SERVER }), |
| 80 | }, |
| 81 | &status, |
| 82 | ).toBool()) return std.os.windows.unexpectedError(std.os.windows.GetLastError()); |
| 83 | switch (status.dwError) { |
| 84 | .SUCCESS => return, |
| 85 | .CERT_E_UNTRUSTEDROOT => return error.TlsCertificateNotVerified, |
| 86 | else => |err| return std.os.windows.unexpectedError(err), |
| 87 | } |
| 88 | } |
| 89 | |
| 90 | const ALLOWED_EKUS = [_][*:0]const u8{"1.3.6.1.5.5.7.3.1"}; |
| 91 | |
| 92 | const assert = std.debug.assert; |
| 93 | const builtin = @import("builtin"); |
| 94 | const std = @import("std"); |
| 95 | const crypt32 = std.os.windows.crypt32; |