From 3237000d957617120e32e54498cac9afa23cbcd4 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 19 Dec 2022 01:07:52 -0700 Subject: [PATCH] std.crypto.tls: rudimentary certificate parsing --- lib/std/crypto/tls.zig | 109 ++++++++++++++++++++++++++++++++++ lib/std/crypto/tls/Client.zig | 66 +++++++++++++++++++- 2 files changed, 174 insertions(+), 1 deletion(-) diff --git a/lib/std/crypto/tls.zig b/lib/std/crypto/tls.zig index 09a22f9a231e2cfe8c017f2939ad51a046dec0b2..33d6bbe90672d7c48fc7a01d166ed88d957b5cd0 100644 --- a/lib/std/crypto/tls.zig +++ b/lib/std/crypto/tls.zig @@ -349,3 +349,112 @@ pub inline fn int3(x: u24) [3]u8 { @truncate(u8, x), }; } + +pub const Der = struct { + pub const Class = enum(u2) { + universal, + application, + context_specific, + private, + }; + + pub const PC = enum(u1) { + primitive, + constructed, + }; + + pub const Identifier = packed struct(u8) { + tag: Tag, + pc: PC, + class: Class, + }; + + pub const Tag = enum(u5) { + boolean = 1, + integer = 2, + bitstring = 3, + null = 5, + object_identifier = 6, + sequence = 16, + _, + }; + + pub const Oid = enum { + commonName, + countryName, + localityName, + stateOrProvinceName, + organizationName, + organizationalUnitName, + sha256WithRSAEncryption, + sha384WithRSAEncryption, + sha512WithRSAEncryption, + sha224WithRSAEncryption, + + pub const map = std.ComptimeStringMap(Oid, .{ + .{ &[_]u8{ 0x55, 0x04, 0x03 }, .commonName }, + .{ &[_]u8{ 0x55, 0x04, 0x06 }, .countryName }, + .{ &[_]u8{ 0x55, 0x04, 0x07 }, .localityName }, + .{ &[_]u8{ 0x55, 0x04, 0x08 }, .stateOrProvinceName }, + .{ &[_]u8{ 0x55, 0x04, 0x0A }, .organizationName }, + .{ &[_]u8{ 0x55, 0x04, 0x0B }, .organizationalUnitName }, + .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0B }, .sha256WithRSAEncryption }, + .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0C }, .sha384WithRSAEncryption }, + .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0D }, .sha512WithRSAEncryption }, + .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0E }, .sha224WithRSAEncryption }, + }); + }; + + pub const Element = struct { + identifier: Identifier, + contents: []const u8, + }; + + pub const ParseElementError = error{CertificateHasFieldWithInvalidLength}; + + pub fn parseElement(bytes: []const u8, index: *usize) ParseElementError!Der.Element { + var i = index.*; + const identifier = @bitCast(Identifier, bytes[i]); + i += 1; + const size_byte = bytes[i]; + i += 1; + if ((size_byte >> 7) == 0) { + const contents = bytes[i..][0..size_byte]; + index.* = i + contents.len; + return .{ + .identifier = identifier, + .contents = contents, + }; + } + + const len_size = @truncate(u7, size_byte); + if (len_size > @sizeOf(usize)) { + return error.CertificateHasFieldWithInvalidLength; + } + + const end = i + len_size; + var long_form_size: usize = 0; + while (i < end) : (i += 1) { + long_form_size = (long_form_size << 8) | bytes[i]; + } + + const contents = bytes[i..][0..long_form_size]; + index.* = i + contents.len; + + return .{ + .identifier = identifier, + .contents = contents, + }; + } + + pub const ParseObjectIdError = error{ + CertificateHasUnrecognizedObjectId, + CertificateFieldHasWrongDataType, + } || ParseElementError; + + pub fn parseObjectId(bytes: []const u8, index: *usize) ParseObjectIdError!Oid { + const oid_element = try parseElement(bytes, index); + if (oid_element.identifier.tag != .object_identifier) return error.CertificateFieldHasWrongDataType; + return Oid.map.get(oid_element.contents) orelse return error.CertificateHasUnrecognizedObjectId; + } +}; diff --git a/lib/std/crypto/tls/Client.zig b/lib/std/crypto/tls/Client.zig index 7fb96ff00c029c287055d3149b8a49abef8c9ce7..c167d85134aab9825357a9d17251c6e418b21ee4 100644 --- a/lib/std/crypto/tls/Client.zig +++ b/lib/std/crypto/tls/Client.zig @@ -402,7 +402,71 @@ pub fn init(stream: net.Stream, host: []const u8) !Client { while (hs_i < end_certs) { const cert_size = mem.readIntBig(u24, handshake[hs_i..][0..3]); hs_i += 3; - hs_i += cert_size; + const end_cert = hs_i + cert_size; + + const certificate = try tls.Der.parseElement(handshake, &hs_i); + { + var cert_i: usize = 0; + const tbs_certificate = try tls.Der.parseElement(certificate.contents, &cert_i); + { + var tbs_i: usize = 0; + const version = try tls.Der.parseElement(tbs_certificate.contents, &tbs_i); + const serial_number = try tls.Der.parseElement(tbs_certificate.contents, &tbs_i); + const signature = try tls.Der.parseElement(tbs_certificate.contents, &tbs_i); + const issuer = try tls.Der.parseElement(tbs_certificate.contents, &tbs_i); + const validity = try tls.Der.parseElement(tbs_certificate.contents, &tbs_i); + const subject = try tls.Der.parseElement(tbs_certificate.contents, &tbs_i); + const subject_pub_key = try tls.Der.parseElement(tbs_certificate.contents, &tbs_i); + const extensions = try tls.Der.parseElement(tbs_certificate.contents, &tbs_i); + + // RFC 5280, section 4.1.2.3: + // "This field MUST contain the same algorithm identifier as + // the signatureAlgorithm field in the sequence Certificate." + _ = signature; + + _ = issuer; + _ = validity; + + std.debug.print("version: {any} '{}'\n", .{ + version.identifier, std.fmt.fmtSliceHexLower(version.contents), + }); + + std.debug.print("serial_number: {any} {}\n", .{ + serial_number.identifier, + std.fmt.fmtSliceHexLower(serial_number.contents), + }); + + std.debug.print("subject: {any} {}\n", .{ + subject.identifier, + std.fmt.fmtSliceHexLower(subject.contents), + }); + + std.debug.print("subject pub key: {any} {}\n", .{ + subject_pub_key.identifier, + std.fmt.fmtSliceHexLower(subject_pub_key.contents), + }); + + std.debug.print("extensions: {any} {}\n", .{ + extensions.identifier, + std.fmt.fmtSliceHexLower(extensions.contents), + }); + } + const signature_algorithm = try tls.Der.parseElement(certificate.contents, &cert_i); + const signature_value = try tls.Der.parseElement(certificate.contents, &cert_i); + + { + var sa_i: usize = 0; + const algorithm = try tls.Der.parseObjectId(signature_algorithm.contents, &sa_i); + std.debug.print("cert has this signature algorithm: {any}\n", .{algorithm}); + //const parameters = try tls.Der.parseElement(signature_algorithm.contents, &sa_i); + } + + std.debug.print("signature_value: {any} {d} bytes\n", .{ + signature_value.identifier, signature_value.contents.len, + }); + } + + hs_i = end_cert; const total_ext_size = mem.readIntBig(u16, handshake[hs_i..][0..2]); hs_i += 2; hs_i += total_ext_size; -- 2.54.0