Commit 3237000d95

Andrew Kelley <andrew@ziglang.org>
2022-12-19 09:07:52
std.crypto.tls: rudimentary certificate parsing
1 parent 5d7eca6
Changed files (2)
lib
std
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;
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;
+    }
+};