authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-12-20 21:30:38-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-01-02 16:57:15-07:00
log7ed7bd247ed301b2904379570ea86abd04c65618
tree3f91ff3c5f97778b4bbf34a1e53669701228c605
parent244a97e8ada5349136ca642d89092dbaf6e52ae2

std.crypto.tls: verify the common name matches


2 files changed, 219 insertions(+), 101 deletions(-)

lib/std/crypto/CertificateBundle.zig+186-67
...@@ -13,6 +13,16 @@ pub const Key = struct {...@@ -13,6 +13,16 @@ pub const Key = struct {
13 subject_end: u32,13 subject_end: u32,
14};14};
1515
16pub fn verify(cb: CertificateBundle, subject: Certificate.Parsed) !void {
17 const bytes_index = cb.find(subject.issuer) orelse return error.IssuerNotFound;
18 const issuer_cert: Certificate = .{
19 .buffer = cb.bytes.items,
20 .index = bytes_index,
21 };
22 const issuer = try issuer_cert.parse();
23 try subject.verify(issuer);
24}
25
16/// The returned bytes become invalid after calling any of the rescan functions26/// The returned bytes become invalid after calling any of the rescan functions
17/// or add functions.27/// or add functions.
18pub fn find(cb: CertificateBundle, subject_name: []const u8) ?u32 {28pub fn find(cb: CertificateBundle, subject_name: []const u8) ?u32 {
...@@ -120,18 +130,11 @@ pub fn key(cb: CertificateBundle, bytes_index: u32) !Key {...@@ -120,18 +130,11 @@ pub fn key(cb: CertificateBundle, bytes_index: u32) !Key {
120 const tbs_certificate = try Der.parseElement(bytes, certificate.start);130 const tbs_certificate = try Der.parseElement(bytes, certificate.start);
121 const version = try Der.parseElement(bytes, tbs_certificate.start);131 const version = try Der.parseElement(bytes, tbs_certificate.start);
122 try checkVersion(bytes, version);132 try checkVersion(bytes, version);
123
124 const serial_number = try Der.parseElement(bytes, version.end);133 const serial_number = try Der.parseElement(bytes, version.end);
125
126 // RFC 5280, section 4.1.2.3:
127 // "This field MUST contain the same algorithm identifier as
128 // the signatureAlgorithm field in the sequence Certificate."
129 const signature = try Der.parseElement(bytes, serial_number.end);134 const signature = try Der.parseElement(bytes, serial_number.end);
130 const issuer = try Der.parseElement(bytes, signature.end);135 const issuer = try Der.parseElement(bytes, signature.end);
131 const validity = try Der.parseElement(bytes, issuer.end);136 const validity = try Der.parseElement(bytes, issuer.end);
132 const subject = try Der.parseElement(bytes, validity.end);137 const subject = try Der.parseElement(bytes, validity.end);
133 //const subject_pub_key = try Der.parseElement(bytes, subject.end);
134 //const extensions = try Der.parseElement(bytes, subject_pub_key.end);
135138
136 return .{139 return .{
137 .subject_start = subject.start,140 .subject_start = subject.start,
...@@ -143,70 +146,163 @@ pub const Certificate = struct {...@@ -143,70 +146,163 @@ pub const Certificate = struct {
143 buffer: []const u8,146 buffer: []const u8,
144 index: u32,147 index: u32,
145148
146 pub fn verify(subject: Certificate, issuer: Certificate) !void {149 pub const Algorithm = enum {
147 const subject_certificate = try Der.parseElement(subject.buffer, subject.index);150 sha1WithRSAEncryption,
148 const subject_tbs_certificate = try Der.parseElement(subject.buffer, subject_certificate.start);151 sha224WithRSAEncryption,
149 const subject_version = try Der.parseElement(subject.buffer, subject_tbs_certificate.start);152 sha256WithRSAEncryption,
150 try checkVersion(subject.buffer, subject_version);153 sha384WithRSAEncryption,
151 const subject_serial_number = try Der.parseElement(subject.buffer, subject_version.end);154 sha512WithRSAEncryption,
152 // RFC 5280, section 4.1.2.3:155
153 // "This field MUST contain the same algorithm identifier as156 pub const map = std.ComptimeStringMap(Algorithm, .{
154 // the signatureAlgorithm field in the sequence Certificate."157 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x05 }, .sha1WithRSAEncryption },
155 const subject_signature = try Der.parseElement(subject.buffer, subject_serial_number.end);158 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0B }, .sha256WithRSAEncryption },
156 const subject_issuer = try Der.parseElement(subject.buffer, subject_signature.end);159 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0C }, .sha384WithRSAEncryption },
157 const subject_validity = try Der.parseElement(subject.buffer, subject_issuer.end);160 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0D }, .sha512WithRSAEncryption },
158 //const subject_name = try Der.parseElement(subject.buffer, subject_validity.end);161 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0E }, .sha224WithRSAEncryption },
159162 });
160 const subject_sig_algo = try Der.parseElement(subject.buffer, subject_tbs_certificate.end);163
161 const subject_algo_elem = try Der.parseElement(subject.buffer, subject_sig_algo.start);164 pub fn Hash(comptime algorithm: Algorithm) type {
162 const subject_algo = try Der.parseObjectId(subject.buffer, subject_algo_elem);165 return switch (algorithm) {
163 const subject_sig_elem = try Der.parseElement(subject.buffer, subject_sig_algo.end);166 .sha1WithRSAEncryption => crypto.hash.Sha1,
164 const subject_sig = try parseBitString(subject, subject_sig_elem);167 .sha224WithRSAEncryption => crypto.hash.sha2.Sha224,
165168 .sha256WithRSAEncryption => crypto.hash.sha2.Sha256,
166 const issuer_certificate = try Der.parseElement(issuer.buffer, issuer.index);169 .sha384WithRSAEncryption => crypto.hash.sha2.Sha384,
167 const issuer_tbs_certificate = try Der.parseElement(issuer.buffer, issuer_certificate.start);170 .sha512WithRSAEncryption => crypto.hash.sha2.Sha512,
168 const issuer_version = try Der.parseElement(issuer.buffer, issuer_tbs_certificate.start);171 };
169 try checkVersion(issuer.buffer, issuer_version);172 }
170 const issuer_serial_number = try Der.parseElement(issuer.buffer, issuer_version.end);173 };
174
175 pub const AlgorithmCategory = enum {
176 rsaEncryption,
177 X9_62_id_ecPublicKey,
178
179 pub const map = std.ComptimeStringMap(AlgorithmCategory, .{
180 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01 }, .rsaEncryption },
181 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01 }, .X9_62_id_ecPublicKey },
182 });
183 };
184
185 pub const Attribute = enum {
186 commonName,
187 serialNumber,
188 countryName,
189 localityName,
190 stateOrProvinceName,
191 organizationName,
192 organizationalUnitName,
193 organizationIdentifier,
194
195 pub const map = std.ComptimeStringMap(Attribute, .{
196 .{ &[_]u8{ 0x55, 0x04, 0x03 }, .commonName },
197 .{ &[_]u8{ 0x55, 0x04, 0x05 }, .serialNumber },
198 .{ &[_]u8{ 0x55, 0x04, 0x06 }, .countryName },
199 .{ &[_]u8{ 0x55, 0x04, 0x07 }, .localityName },
200 .{ &[_]u8{ 0x55, 0x04, 0x08 }, .stateOrProvinceName },
201 .{ &[_]u8{ 0x55, 0x04, 0x0A }, .organizationName },
202 .{ &[_]u8{ 0x55, 0x04, 0x0B }, .organizationalUnitName },
203 .{ &[_]u8{ 0x55, 0x04, 0x61 }, .organizationIdentifier },
204 });
205 };
206
207 pub const Parsed = struct {
208 certificate: Certificate,
209 issuer: []const u8,
210 subject: []const u8,
211 common_name: []const u8,
212 signature: []const u8,
213 signature_algorithm: Algorithm,
214 message: []const u8,
215 pub_key_algo: AlgorithmCategory,
216 pub_key: []const u8,
217
218 pub fn verify(subject: Parsed, issuer: Parsed) !void {
219 // Check that the subject's issuer name matches the issuer's
220 // subject name.
221 if (!mem.eql(u8, subject.issuer, issuer.subject)) {
222 return error.CertificateIssuerMismatch;
223 }
224
225 // TODO check the time validity for the subject
226 // TODO check the time validity for the issuer
227
228 switch (subject.signature_algorithm) {
229 inline .sha1WithRSAEncryption,
230 .sha224WithRSAEncryption,
231 .sha256WithRSAEncryption,
232 .sha384WithRSAEncryption,
233 .sha512WithRSAEncryption,
234 => |algorithm| return verifyRsa(
235 algorithm.Hash(),
236 subject.message,
237 subject.signature,
238 issuer.pub_key_algo,
239 issuer.pub_key,
240 ),
241 }
242 }
243 };
244
245 pub fn parse(cert: Certificate) !Parsed {
246 const cert_bytes = cert.buffer;
247 const certificate = try Der.parseElement(cert_bytes, cert.index);
248 const tbs_certificate = try Der.parseElement(cert_bytes, certificate.start);
249 const version = try Der.parseElement(cert_bytes, tbs_certificate.start);
250 try checkVersion(cert_bytes, version);
251 const serial_number = try Der.parseElement(cert_bytes, version.end);
171 // RFC 5280, section 4.1.2.3:252 // RFC 5280, section 4.1.2.3:
172 // "This field MUST contain the same algorithm identifier as253 // "This field MUST contain the same algorithm identifier as
173 // the signatureAlgorithm field in the sequence Certificate."254 // the signatureAlgorithm field in the sequence Certificate."
174 const issuer_signature = try Der.parseElement(issuer.buffer, issuer_serial_number.end);255 const tbs_signature = try Der.parseElement(cert_bytes, serial_number.end);
175 const issuer_issuer = try Der.parseElement(issuer.buffer, issuer_signature.end);256 const issuer = try Der.parseElement(cert_bytes, tbs_signature.end);
176 const issuer_validity = try Der.parseElement(issuer.buffer, issuer_issuer.end);257 const validity = try Der.parseElement(cert_bytes, issuer.end);
177 const issuer_name = try Der.parseElement(issuer.buffer, issuer_validity.end);258 const subject = try Der.parseElement(cert_bytes, validity.end);
178 const issuer_pub_key_info = try Der.parseElement(issuer.buffer, issuer_name.end);259
179 const issuer_pub_key_signature_algorithm = try Der.parseElement(issuer.buffer, issuer_pub_key_info.start);260 const pub_key_info = try Der.parseElement(cert_bytes, subject.end);
180 const issuer_pub_key_algo_elem = try Der.parseElement(issuer.buffer, issuer_pub_key_signature_algorithm.start);261 const pub_key_signature_algorithm = try Der.parseElement(cert_bytes, pub_key_info.start);
181 const issuer_pub_key_algo = try Der.parseObjectId(issuer.buffer, issuer_pub_key_algo_elem);262 const pub_key_algo_elem = try Der.parseElement(cert_bytes, pub_key_signature_algorithm.start);
182 const issuer_pub_key_elem = try Der.parseElement(issuer.buffer, issuer_pub_key_signature_algorithm.end);263 const pub_key_algo = try parseAlgorithmCategory(cert_bytes, pub_key_algo_elem);
183 const issuer_pub_key = try parseBitString(issuer, issuer_pub_key_elem);264 const pub_key_elem = try Der.parseElement(cert_bytes, pub_key_signature_algorithm.end);
184265 const pub_key = try parseBitString(cert, pub_key_elem);
185 // Check that the subject's issuer name matches the issuer's subject266
186 // name.267 const rdn = try Der.parseElement(cert_bytes, subject.start);
187 if (!mem.eql(u8, subject.contents(subject_issuer), issuer.contents(issuer_name))) {268 const atav = try Der.parseElement(cert_bytes, rdn.start);
188 return error.CertificateIssuerMismatch;269
270 var common_name: []const u8 = &.{};
271 var atav_i = atav.start;
272 while (atav_i < atav.end) {
273 const ty_elem = try Der.parseElement(cert_bytes, atav_i);
274 const ty = try parseAttribute(cert_bytes, ty_elem);
275 const val = try Der.parseElement(cert_bytes, ty_elem.end);
276 switch (ty) {
277 .commonName => common_name = cert.contents(val),
278 else => {},
279 }
280 atav_i = val.end;
189 }281 }
190282
191 // TODO check the time validity for the subject283 const sig_algo = try Der.parseElement(cert_bytes, tbs_certificate.end);
192 _ = subject_validity;284 const algo_elem = try Der.parseElement(cert_bytes, sig_algo.start);
193 // TODO check the time validity for the issuer285 const signature_algorithm = try parseAlgorithm(cert_bytes, algo_elem);
194286 const sig_elem = try Der.parseElement(cert_bytes, sig_algo.end);
195 const message = subject.buffer[subject_certificate.start..subject_tbs_certificate.end];287 const signature = try parseBitString(cert, sig_elem);
196 //std.debug.print("issuer algo: {any} subject algo: {any}\n", .{ issuer_pub_key_algo, subject_algo });288
197 switch (subject_algo) {289 return .{
198 // zig fmt: off290 .certificate = cert,
199 .sha1WithRSAEncryption => return verifyRsa(crypto.hash.Sha1, message, subject_sig, issuer_pub_key_algo, issuer_pub_key),291 .common_name = common_name,
200 .sha224WithRSAEncryption => return verifyRsa(crypto.hash.sha2.Sha224, message, subject_sig, issuer_pub_key_algo, issuer_pub_key),292 .issuer = cert.contents(issuer),
201 .sha256WithRSAEncryption => return verifyRsa(crypto.hash.sha2.Sha256, message, subject_sig, issuer_pub_key_algo, issuer_pub_key),293 .subject = cert.contents(subject),
202 .sha384WithRSAEncryption => return verifyRsa(crypto.hash.sha2.Sha384, message, subject_sig, issuer_pub_key_algo, issuer_pub_key),294 .signature = signature,
203 .sha512WithRSAEncryption => return verifyRsa(crypto.hash.sha2.Sha512, message, subject_sig, issuer_pub_key_algo, issuer_pub_key),295 .signature_algorithm = signature_algorithm,
204 // zig fmt: on296 .message = cert_bytes[certificate.start..tbs_certificate.end],
205 else => {297 .pub_key_algo = pub_key_algo,
206 std.debug.print("unhandled algorithm: {any}\n", .{subject_algo});298 .pub_key = pub_key,
207 return error.UnsupportedCertificateSignatureAlgorithm;299 };
208 },300 }
209 }301
302 pub fn verify(subject: Certificate, issuer: Certificate) !void {
303 const parsed_subject = try subject.parse();
304 const parsed_issuer = try issuer.parse();
305 return parsed_subject.verify(parsed_issuer);
210 }306 }
211307
212 pub fn contents(cert: Certificate, elem: Der.Element) []const u8 {308 pub fn contents(cert: Certificate, elem: Der.Element) []const u8 {
...@@ -219,7 +315,30 @@ pub const Certificate = struct {...@@ -219,7 +315,30 @@ pub const Certificate = struct {
219 return cert.buffer[elem.start + 1 .. elem.end];315 return cert.buffer[elem.start + 1 .. elem.end];
220 }316 }
221317
222 fn verifyRsa(comptime Hash: type, message: []const u8, sig: []const u8, pub_key_algo: Der.Oid, pub_key: []const u8) !void {318 pub fn parseAlgorithm(bytes: []const u8, element: Der.Element) !Algorithm {
319 if (element.identifier.tag != .object_identifier)
320 return error.CertificateFieldHasWrongDataType;
321 return Algorithm.map.get(bytes[element.start..element.end]) orelse
322 return error.CertificateHasUnrecognizedAlgorithm;
323 }
324
325 pub fn parseAlgorithmCategory(bytes: []const u8, element: Der.Element) !AlgorithmCategory {
326 if (element.identifier.tag != .object_identifier)
327 return error.CertificateFieldHasWrongDataType;
328 return AlgorithmCategory.map.get(bytes[element.start..element.end]) orelse {
329 std.debug.print("unrecognized algorithm category: {}\n", .{std.fmt.fmtSliceHexLower(bytes[element.start..element.end])});
330 return error.CertificateHasUnrecognizedAlgorithmCategory;
331 };
332 }
333
334 pub fn parseAttribute(bytes: []const u8, element: Der.Element) !Attribute {
335 if (element.identifier.tag != .object_identifier)
336 return error.CertificateFieldHasWrongDataType;
337 return Attribute.map.get(bytes[element.start..element.end]) orelse
338 return error.CertificateHasUnrecognizedAlgorithm;
339 }
340
341 fn verifyRsa(comptime Hash: type, message: []const u8, sig: []const u8, pub_key_algo: AlgorithmCategory, pub_key: []const u8) !void {
223 if (pub_key_algo != .rsaEncryption) return error.CertificateSignatureAlgorithmMismatch;342 if (pub_key_algo != .rsaEncryption) return error.CertificateSignatureAlgorithmMismatch;
224 const pub_key_seq = try Der.parseElement(pub_key, 0);343 const pub_key_seq = try Der.parseElement(pub_key, 0);
225 if (pub_key_seq.identifier.tag != .sequence) return error.CertificateFieldHasWrongDataType;344 if (pub_key_seq.identifier.tag != .sequence) return error.CertificateFieldHasWrongDataType;
lib/std/crypto/tls/Client.zig+33-34
...@@ -18,6 +18,7 @@ const int2 = tls.int2;...@@ -18,6 +18,7 @@ const int2 = tls.int2;
18const int3 = tls.int3;18const int3 = tls.int3;
19const array = tls.array;19const array = tls.array;
20const enum_array = tls.enum_array;20const enum_array = tls.enum_array;
21const Certificate = crypto.CertificateBundle.Certificate;
2122
22application_cipher: ApplicationCipher,23application_cipher: ApplicationCipher,
23read_seq: u64,24read_seq: u64,
...@@ -298,6 +299,8 @@ pub fn init(stream: net.Stream, ca_bundle: crypto.CertificateBundle, host: []con...@@ -298,6 +299,8 @@ pub fn init(stream: net.Stream, ca_bundle: crypto.CertificateBundle, host: []con
298 };299 };
299300
300 var read_seq: u64 = 0;301 var read_seq: u64 = 0;
302 var validated_cert = false;
303 var is_subsequent_cert = false;
301304
302 while (true) {305 while (true) {
303 const end_hdr = i + 5;306 const end_hdr = i + 5;
...@@ -386,10 +389,11 @@ pub fn init(stream: net.Stream, ca_bundle: crypto.CertificateBundle, host: []con...@@ -386,10 +389,11 @@ pub fn init(stream: net.Stream, ca_bundle: crypto.CertificateBundle, host: []con
386 hs_i = next_ext_i;389 hs_i = next_ext_i;
387 }390 }
388 },391 },
389 @enumToInt(HandshakeType.certificate) => {392 @enumToInt(HandshakeType.certificate) => cert: {
390 switch (cipher_params) {393 switch (cipher_params) {
391 inline else => |*p| p.transcript_hash.update(wrapped_handshake),394 inline else => |*p| p.transcript_hash.update(wrapped_handshake),
392 }395 }
396 if (validated_cert) break :cert;
393 var hs_i: u32 = 0;397 var hs_i: u32 = 0;
394 const cert_req_ctx_len = handshake[hs_i];398 const cert_req_ctx_len = handshake[hs_i];
395 hs_i += 1;399 hs_i += 1;
...@@ -402,41 +406,36 @@ pub fn init(stream: net.Stream, ca_bundle: crypto.CertificateBundle, host: []con...@@ -402,41 +406,36 @@ pub fn init(stream: net.Stream, ca_bundle: crypto.CertificateBundle, host: []con
402 hs_i += 3;406 hs_i += 3;
403 const end_cert = hs_i + cert_size;407 const end_cert = hs_i + cert_size;
404408
405 const certificate = try Der.parseElement(handshake, hs_i);409 const subject_cert: Certificate = .{
406 const tbs_certificate = try Der.parseElement(handshake, certificate.start);410 .buffer = handshake,
407411 .index = hs_i,
408 const version = try Der.parseElement(handshake, tbs_certificate.start);412 };
409 if (@bitCast(u8, version.identifier) != 0xa0 or413 const subject = try subject_cert.parse();
410 !mem.eql(u8, handshake[version.start..version.end], "\x02\x01\x02"))414 if (!is_subsequent_cert) {
411 {415 is_subsequent_cert = true;
412 return error.UnsupportedCertificateVersion;416 if (mem.eql(u8, subject.common_name, host)) {
417 std.debug.print("exact host match\n", .{});
418 } else if (mem.startsWith(u8, subject.common_name, "*.") and
419 mem.eql(u8, subject.common_name[2..], host))
420 {
421 std.debug.print("wildcard host match\n", .{});
422 } else {
423 std.debug.print("host does not match\n", .{});
424 return error.TlsCertificateInvalidHost;
425 }
413 }426 }
414427
415 const serial_number = try Der.parseElement(handshake, version.end);428 if (ca_bundle.verify(subject)) |_| {
416 // RFC 5280, section 4.1.2.3:429 std.debug.print("found a root CA cert matching issuer. verification success!\n", .{});
417 // "This field MUST contain the same algorithm identifier as430 validated_cert = true;
418 // the signatureAlgorithm field in the sequence Certificate."431 break :cert;
419 const signature = try Der.parseElement(handshake, serial_number.end);432 } else |err| {
420 const issuer_elem = try Der.parseElement(handshake, signature.end);433 std.debug.print("unable to validate cert against system root CAs: {s}\n", .{
421434 @errorName(err),
422 const issuer_bytes = handshake[issuer_elem.start..issuer_elem.end];435 });
423 if (ca_bundle.find(issuer_bytes)) |ca_cert_i| {436 // TODO handle a certificate
424 const Certificate = crypto.CertificateBundle.Certificate;437 // signing chain that ends in a
425 const subject: Certificate = .{438 // root-validated one.
426 .buffer = handshake,
427 .index = hs_i,
428 };
429 const issuer: Certificate = .{
430 .buffer = ca_bundle.bytes.items,
431 .index = ca_cert_i,
432 };
433 if (subject.verify(issuer)) |_| {
434 std.debug.print("found a root CA cert matching issuer. verification success!\n", .{});
435 } else |err| {
436 std.debug.print("found a root CA cert matching issuer. verification failure: {s}\n", .{
437 @errorName(err),
438 });
439 }
440 }439 }
441440
442 hs_i = end_cert;441 hs_i = end_cert;