Skip to content

Commit f4b4e57

Browse files
authored
Merge pull request #14229 from star-tek-mb/wincerts
windows root certificate scanning
2 parents d56a65a + d35d086 commit f4b4e57

File tree

5 files changed

+208
-34
lines changed

5 files changed

+208
-34
lines changed

lib/std/crypto/Certificate.zig

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ pub const Algorithm = enum {
1515
ecdsa_with_SHA256,
1616
ecdsa_with_SHA384,
1717
ecdsa_with_SHA512,
18+
md2WithRSAEncryption,
19+
md5WithRSAEncryption,
1820

1921
pub const map = std.ComptimeStringMap(Algorithm, .{
2022
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x05 }, .sha1WithRSAEncryption },
@@ -26,6 +28,8 @@ pub const Algorithm = enum {
2628
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x02 }, .ecdsa_with_SHA256 },
2729
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x03 }, .ecdsa_with_SHA384 },
2830
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x04 }, .ecdsa_with_SHA512 },
31+
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x02 }, .md2WithRSAEncryption },
32+
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x04 }, .md5WithRSAEncryption },
2933
});
3034

3135
pub fn Hash(comptime algorithm: Algorithm) type {
@@ -35,6 +39,8 @@ pub const Algorithm = enum {
3539
.ecdsa_with_SHA256, .sha256WithRSAEncryption => crypto.hash.sha2.Sha256,
3640
.ecdsa_with_SHA384, .sha384WithRSAEncryption => crypto.hash.sha2.Sha384,
3741
.ecdsa_with_SHA512, .sha512WithRSAEncryption => crypto.hash.sha2.Sha512,
42+
.md2WithRSAEncryption => @compileError("unimplemented"),
43+
.md5WithRSAEncryption => crypto.hash.Md5,
3844
};
3945
}
4046
};
@@ -55,30 +61,38 @@ pub const Attribute = enum {
5561
countryName,
5662
localityName,
5763
stateOrProvinceName,
64+
streetAddress,
5865
organizationName,
5966
organizationalUnitName,
67+
postalCode,
6068
organizationIdentifier,
6169
pkcs9_emailAddress,
70+
domainComponent,
6271

6372
pub const map = std.ComptimeStringMap(Attribute, .{
6473
.{ &[_]u8{ 0x55, 0x04, 0x03 }, .commonName },
6574
.{ &[_]u8{ 0x55, 0x04, 0x05 }, .serialNumber },
6675
.{ &[_]u8{ 0x55, 0x04, 0x06 }, .countryName },
6776
.{ &[_]u8{ 0x55, 0x04, 0x07 }, .localityName },
6877
.{ &[_]u8{ 0x55, 0x04, 0x08 }, .stateOrProvinceName },
78+
.{ &[_]u8{ 0x55, 0x04, 0x09 }, .streetAddress },
6979
.{ &[_]u8{ 0x55, 0x04, 0x0A }, .organizationName },
7080
.{ &[_]u8{ 0x55, 0x04, 0x0B }, .organizationalUnitName },
81+
.{ &[_]u8{ 0x55, 0x04, 0x11 }, .postalCode },
7182
.{ &[_]u8{ 0x55, 0x04, 0x61 }, .organizationIdentifier },
7283
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x09, 0x01 }, .pkcs9_emailAddress },
84+
.{ &[_]u8{ 0x09, 0x92, 0x26, 0x89, 0x93, 0xF2, 0x2C, 0x64, 0x01, 0x19 }, .domainComponent },
7385
});
7486
};
7587

7688
pub const NamedCurve = enum {
7789
secp384r1,
90+
secp521r1,
7891
X9_62_prime256v1,
7992

8093
pub const map = std.ComptimeStringMap(NamedCurve, .{
8194
.{ &[_]u8{ 0x2B, 0x81, 0x04, 0x00, 0x22 }, .secp384r1 },
95+
.{ &[_]u8{ 0x2B, 0x81, 0x04, 0x00, 0x23 }, .secp521r1 },
8296
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07 }, .X9_62_prime256v1 },
8397
});
8498
};
@@ -93,17 +107,40 @@ pub const ExtensionId = enum {
93107
crl_number,
94108
certificate_policies,
95109
authority_key_identifier,
110+
msCertsrvCAVersion,
111+
commonName,
112+
ext_key_usage,
113+
crl_distribution_points,
114+
info_access,
115+
entrustVersInfo,
116+
enroll_certtype,
117+
pe_logotype,
118+
netscape_cert_type,
119+
netscape_comment,
96120

97121
pub const map = std.ComptimeStringMap(ExtensionId, .{
122+
.{ &[_]u8{ 0x55, 0x04, 0x03 }, .commonName },
123+
.{ &[_]u8{ 0x55, 0x1D, 0x01 }, .authority_key_identifier },
124+
.{ &[_]u8{ 0x55, 0x1D, 0x07 }, .subject_alt_name },
98125
.{ &[_]u8{ 0x55, 0x1D, 0x0E }, .subject_key_identifier },
99126
.{ &[_]u8{ 0x55, 0x1D, 0x0F }, .key_usage },
127+
.{ &[_]u8{ 0x55, 0x1D, 0x0A }, .basic_constraints },
100128
.{ &[_]u8{ 0x55, 0x1D, 0x10 }, .private_key_usage_period },
101129
.{ &[_]u8{ 0x55, 0x1D, 0x11 }, .subject_alt_name },
102130
.{ &[_]u8{ 0x55, 0x1D, 0x12 }, .issuer_alt_name },
103131
.{ &[_]u8{ 0x55, 0x1D, 0x13 }, .basic_constraints },
104132
.{ &[_]u8{ 0x55, 0x1D, 0x14 }, .crl_number },
133+
.{ &[_]u8{ 0x55, 0x1D, 0x1F }, .crl_distribution_points },
105134
.{ &[_]u8{ 0x55, 0x1D, 0x20 }, .certificate_policies },
106135
.{ &[_]u8{ 0x55, 0x1D, 0x23 }, .authority_key_identifier },
136+
.{ &[_]u8{ 0x55, 0x1D, 0x25 }, .ext_key_usage },
137+
.{ &[_]u8{ 0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x15, 0x01 }, .msCertsrvCAVersion },
138+
.{ &[_]u8{ 0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01 }, .info_access },
139+
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF6, 0x7D, 0x07, 0x41, 0x00 }, .entrustVersInfo },
140+
.{ &[_]u8{ 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x14, 0x02 }, .enroll_certtype },
141+
.{ &[_]u8{ 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x0c }, .pe_logotype },
142+
.{ &[_]u8{ 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x01, 0x01 }, .netscape_cert_type },
143+
.{ &[_]u8{ 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x01, 0x0d }, .netscape_comment },
107144
});
108145
};
109146

@@ -238,6 +275,10 @@ pub const Parsed = struct {
238275
parsed_issuer.pub_key_algo,
239276
parsed_issuer.pubKey(),
240277
),
278+
279+
.md2WithRSAEncryption, .md5WithRSAEncryption => {
280+
return error.CertificateSignatureAlgorithmUnsupported;
281+
},
241282
}
242283
}
243284

@@ -354,13 +395,16 @@ pub fn parse(cert: Certificate) !Parsed {
354395
var atav_i = atav.slice.start;
355396
while (atav_i < atav.slice.end) {
356397
const ty_elem = try der.Element.parse(cert_bytes, atav_i);
357-
const ty = try parseAttribute(cert_bytes, ty_elem);
358398
const val = try der.Element.parse(cert_bytes, ty_elem.slice.end);
399+
atav_i = val.slice.end;
400+
const ty = parseAttribute(cert_bytes, ty_elem) catch |err| switch (err) {
401+
error.CertificateHasUnrecognizedObjectId => continue,
402+
else => |e| return e,
403+
};
359404
switch (ty) {
360405
.commonName => common_name = val.slice,
361406
else => {},
362407
}
363-
atav_i = val.slice.end;
364408
}
365409
rdn_i = atav.slice.end;
366410
}
@@ -712,6 +756,9 @@ fn verify_ecdsa(
712756
};
713757

714758
switch (sig_named_curve) {
759+
.secp521r1 => {
760+
return error.CertificateSignatureNamedCurveUnsupported;
761+
},
715762
.secp384r1 => {
716763
const P = crypto.ecc.P384;
717764
const Ecdsa = crypto.sign.ecdsa.Ecdsa(P, Hash);

lib/std/crypto/Certificate/Bundle.zig

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,8 @@ pub fn deinit(cb: *Bundle, gpa: Allocator) void {
5757
pub fn rescan(cb: *Bundle, gpa: Allocator) !void {
5858
switch (builtin.os.tag) {
5959
.linux => return rescanLinux(cb, gpa),
60-
.windows => {
61-
// TODO
62-
},
6360
.macos => return rescanMac(cb, gpa),
61+
.windows => return rescanWindows(cb, gpa),
6462
else => {},
6563
}
6664
}
@@ -109,6 +107,38 @@ pub fn rescanLinux(cb: *Bundle, gpa: Allocator) !void {
109107
cb.bytes.shrinkAndFree(gpa, cb.bytes.items.len);
110108
}
111109

110+
pub fn rescanWindows(cb: *Bundle, gpa: Allocator) !void {
111+
cb.bytes.clearRetainingCapacity();
112+
cb.map.clearRetainingCapacity();
113+
114+
const w = std.os.windows;
115+
const GetLastError = w.kernel32.GetLastError;
116+
const root = [4:0]u16{ 'R', 'O', 'O', 'T' };
117+
const store = w.crypt32.CertOpenSystemStoreW(null, &root) orelse switch (GetLastError()) {
118+
.FILE_NOT_FOUND => return error.FileNotFound,
119+
else => |err| return w.unexpectedError(err),
120+
};
121+
defer _ = w.crypt32.CertCloseStore(store, 0);
122+
123+
var ctx = w.crypt32.CertEnumCertificatesInStore(store, null);
124+
while (ctx) |context| : (ctx = w.crypt32.CertEnumCertificatesInStore(store, ctx)) {
125+
const decoded_start = @intCast(u32, cb.bytes.items.len);
126+
const encoded_cert = context.pbCertEncoded[0..context.cbCertEncoded];
127+
try cb.bytes.appendSlice(gpa, encoded_cert);
128+
const parsed_cert = try Certificate.parse(.{
129+
.buffer = cb.bytes.items,
130+
.index = decoded_start,
131+
});
132+
const gop = try cb.map.getOrPutContext(gpa, parsed_cert.subject_slice, .{ .cb = cb });
133+
if (gop.found_existing) {
134+
cb.bytes.items.len = decoded_start;
135+
} else {
136+
gop.value_ptr.* = decoded_start;
137+
}
138+
}
139+
cb.bytes.shrinkAndFree(gpa, cb.bytes.items.len);
140+
}
141+
112142
pub fn addCertsFromDirPath(
113143
cb: *Bundle,
114144
gpa: Allocator,

lib/std/net.zig

Lines changed: 75 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -746,7 +746,79 @@ pub fn getAddressList(allocator: mem.Allocator, name: []const u8, port: u16) !*A
746746
const arena = result.arena.allocator();
747747
errdefer result.deinit();
748748

749-
if (builtin.target.os.tag == .windows or builtin.link_libc) {
749+
if (builtin.target.os.tag == .windows) {
750+
const name_c = try std.cstr.addNullByte(allocator, name);
751+
defer allocator.free(name_c);
752+
753+
const port_c = try std.fmt.allocPrintZ(allocator, "{}", .{port});
754+
defer allocator.free(port_c);
755+
756+
const ws2_32 = os.windows.ws2_32;
757+
const hints = os.addrinfo{
758+
.flags = ws2_32.AI.NUMERICSERV,
759+
.family = os.AF.UNSPEC,
760+
.socktype = os.SOCK.STREAM,
761+
.protocol = os.IPPROTO.TCP,
762+
.canonname = null,
763+
.addr = null,
764+
.addrlen = 0,
765+
.next = null,
766+
};
767+
var res: *os.addrinfo = undefined;
768+
var first = true;
769+
while (true) {
770+
const rc = ws2_32.getaddrinfo(name_c.ptr, port_c.ptr, &hints, &res);
771+
switch (@intToEnum(os.windows.ws2_32.WinsockError, @intCast(u16, rc))) {
772+
@intToEnum(os.windows.ws2_32.WinsockError, 0) => break,
773+
.WSATRY_AGAIN => return error.TemporaryNameServerFailure,
774+
.WSANO_RECOVERY => return error.NameServerFailure,
775+
.WSAEAFNOSUPPORT => return error.AddressFamilyNotSupported,
776+
.WSA_NOT_ENOUGH_MEMORY => return error.OutOfMemory,
777+
.WSAHOST_NOT_FOUND => return error.UnknownHostName,
778+
.WSATYPE_NOT_FOUND => return error.ServiceUnavailable,
779+
.WSAEINVAL => unreachable,
780+
.WSAESOCKTNOSUPPORT => unreachable,
781+
.WSANOTINITIALISED => {
782+
if (!first) return error.Unexpected;
783+
first = false;
784+
try os.windows.callWSAStartup();
785+
continue;
786+
},
787+
else => |err| return os.windows.unexpectedWSAError(err),
788+
}
789+
}
790+
defer ws2_32.freeaddrinfo(res);
791+
792+
const addr_count = blk: {
793+
var count: usize = 0;
794+
var it: ?*os.addrinfo = res;
795+
while (it) |info| : (it = info.next) {
796+
if (info.addr != null) {
797+
count += 1;
798+
}
799+
}
800+
break :blk count;
801+
};
802+
result.addrs = try arena.alloc(Address, addr_count);
803+
804+
var it: ?*os.addrinfo = res;
805+
var i: usize = 0;
806+
while (it) |info| : (it = info.next) {
807+
const addr = info.addr orelse continue;
808+
result.addrs[i] = Address.initPosix(@alignCast(4, addr));
809+
810+
if (info.canonname) |n| {
811+
if (result.canon_name == null) {
812+
result.canon_name = try arena.dupe(u8, mem.sliceTo(n, 0));
813+
}
814+
}
815+
i += 1;
816+
}
817+
818+
return result;
819+
}
820+
821+
if (builtin.link_libc) {
750822
const name_c = try std.cstr.addNullByte(allocator, name);
751823
defer allocator.free(name_c);
752824

@@ -765,19 +837,7 @@ pub fn getAddressList(allocator: mem.Allocator, name: []const u8, port: u16) !*A
765837
.next = null,
766838
};
767839
var res: *os.addrinfo = undefined;
768-
const rc = sys.getaddrinfo(name_c.ptr, port_c.ptr, &hints, &res);
769-
if (builtin.target.os.tag == .windows) switch (@intToEnum(os.windows.ws2_32.WinsockError, @intCast(u16, rc))) {
770-
@intToEnum(os.windows.ws2_32.WinsockError, 0) => {},
771-
.WSATRY_AGAIN => return error.TemporaryNameServerFailure,
772-
.WSANO_RECOVERY => return error.NameServerFailure,
773-
.WSAEAFNOSUPPORT => return error.AddressFamilyNotSupported,
774-
.WSA_NOT_ENOUGH_MEMORY => return error.OutOfMemory,
775-
.WSAHOST_NOT_FOUND => return error.UnknownHostName,
776-
.WSATYPE_NOT_FOUND => return error.ServiceUnavailable,
777-
.WSAEINVAL => unreachable,
778-
.WSAESOCKTNOSUPPORT => unreachable,
779-
else => |err| return os.windows.unexpectedWSAError(err),
780-
} else switch (rc) {
840+
switch (sys.getaddrinfo(name_c.ptr, port_c.ptr, &hints, &res)) {
781841
@intToEnum(sys.EAI, 0) => {},
782842
.ADDRFAMILY => return error.HostLacksNetworkAddresses,
783843
.AGAIN => return error.TemporaryNameServerFailure,
@@ -824,6 +884,7 @@ pub fn getAddressList(allocator: mem.Allocator, name: []const u8, port: u16) !*A
824884

825885
return result;
826886
}
887+
827888
if (builtin.target.os.tag == .linux) {
828889
const flags = std.c.AI.NUMERICSERV;
829890
const family = os.AF.UNSPEC;

lib/std/os/windows.zig

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ pub const user32 = @import("windows/user32.zig");
2828
pub const ws2_32 = @import("windows/ws2_32.zig");
2929
pub const gdi32 = @import("windows/gdi32.zig");
3030
pub const winmm = @import("windows/winmm.zig");
31+
pub const crypt32 = @import("windows/crypt32.zig");
3132

3233
pub const self_process_handle = @intToPtr(HANDLE, maxInt(usize));
3334

@@ -1295,6 +1296,23 @@ pub fn WSACleanup() !void {
12951296

12961297
var wsa_startup_mutex: std.Thread.Mutex = .{};
12971298

1299+
pub fn callWSAStartup() !void {
1300+
wsa_startup_mutex.lock();
1301+
defer wsa_startup_mutex.unlock();
1302+
1303+
// Here we could use a flag to prevent multiple threads to prevent
1304+
// multiple calls to WSAStartup, but it doesn't matter. We're globally
1305+
// leaking the resource intentionally, and the mutex already prevents
1306+
// data races within the WSAStartup function.
1307+
_ = WSAStartup(2, 2) catch |err| switch (err) {
1308+
error.SystemNotAvailable => return error.SystemResources,
1309+
error.VersionNotSupported => return error.Unexpected,
1310+
error.BlockingOperationInProgress => return error.Unexpected,
1311+
error.ProcessFdQuotaExceeded => return error.ProcessFdQuotaExceeded,
1312+
error.Unexpected => return error.Unexpected,
1313+
};
1314+
}
1315+
12981316
/// Microsoft requires WSAStartup to be called to initialize, or else
12991317
/// WSASocketW will return WSANOTINITIALISED.
13001318
/// Since this is a standard library, we do not have the luxury of
@@ -1337,21 +1355,7 @@ pub fn WSASocketW(
13371355
.WSANOTINITIALISED => {
13381356
if (!first) return error.Unexpected;
13391357
first = false;
1340-
1341-
wsa_startup_mutex.lock();
1342-
defer wsa_startup_mutex.unlock();
1343-
1344-
// Here we could use a flag to prevent multiple threads to prevent
1345-
// multiple calls to WSAStartup, but it doesn't matter. We're globally
1346-
// leaking the resource intentionally, and the mutex already prevents
1347-
// data races within the WSAStartup function.
1348-
_ = WSAStartup(2, 2) catch |err| switch (err) {
1349-
error.SystemNotAvailable => return error.SystemResources,
1350-
error.VersionNotSupported => return error.Unexpected,
1351-
error.BlockingOperationInProgress => return error.Unexpected,
1352-
error.ProcessFdQuotaExceeded => return error.ProcessFdQuotaExceeded,
1353-
error.Unexpected => return error.Unexpected,
1354-
};
1358+
try callWSAStartup();
13551359
continue;
13561360
},
13571361
else => |err| return unexpectedWSAError(err),

lib/std/os/windows/crypt32.zig

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
const std = @import("../../std.zig");
2+
const windows = std.os.windows;
3+
const BOOL = windows.BOOL;
4+
const DWORD = windows.DWORD;
5+
const BYTE = windows.BYTE;
6+
const LPCWSTR = windows.LPCWSTR;
7+
const WINAPI = windows.WINAPI;
8+
9+
pub const CERT_INFO = *opaque {};
10+
pub const HCERTSTORE = *opaque {};
11+
pub const CERT_CONTEXT = extern struct {
12+
dwCertEncodingType: DWORD,
13+
pbCertEncoded: [*]BYTE,
14+
cbCertEncoded: DWORD,
15+
pCertInfo: CERT_INFO,
16+
hCertStore: HCERTSTORE,
17+
};
18+
19+
pub extern "crypt32" fn CertOpenSystemStoreW(
20+
_: ?*const anyopaque,
21+
szSubsystemProtocol: LPCWSTR,
22+
) callconv(WINAPI) ?HCERTSTORE;
23+
24+
pub extern "crypt32" fn CertCloseStore(
25+
hCertStore: HCERTSTORE,
26+
dwFlags: DWORD,
27+
) callconv(WINAPI) BOOL;
28+
29+
pub extern "crypt32" fn CertEnumCertificatesInStore(
30+
hCertStore: HCERTSTORE,
31+
pPrevCertContext: ?*CERT_CONTEXT,
32+
) callconv(WINAPI) ?*CERT_CONTEXT;

0 commit comments

Comments
 (0)