1//! A sequence of certificates, where each certificate is authenticated by the next certificate.
2const Chain = @This();
3
4store: ?crypt32.HCERTSTORE,
5primary: ?*const crypt32.CERT_CONTEXT,
6
7pub const empty: Chain = .{ .store = null, .primary = null };
8
9pub 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
17pub 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
39pub const VerifyError = error{
40 TlsCertificateNotVerified,
41} || std.Io.UnexpectedError;
42
43pub 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
90const ALLOWED_EKUS = [_][*:0]const u8{"1.3.6.1.5.5.7.3.1"};
91
92const assert = std.debug.assert;
93const builtin = @import("builtin");
94const std = @import("std");
95const crypt32 = std.os.windows.crypt32;