mirror of
https://github.com/ziglang/zig.git
synced 2026-01-20 22:35:24 +00:00
introduce std.crypto.CertificateBundle
for reading root certificate authority bundles from standard installation locations on the file system. So far only Linux logic is added.
This commit is contained in:
parent
3237000d95
commit
bbc074252c
@ -177,6 +177,8 @@ const std = @import("std.zig");
|
||||
pub const errors = @import("crypto/errors.zig");
|
||||
|
||||
pub const tls = @import("crypto/tls.zig");
|
||||
pub const Der = @import("crypto/Der.zig");
|
||||
pub const CertificateBundle = @import("crypto/CertificateBundle.zig");
|
||||
|
||||
test {
|
||||
_ = aead.aegis.Aegis128L;
|
||||
@ -266,6 +268,9 @@ test {
|
||||
_ = utils;
|
||||
_ = random;
|
||||
_ = errors;
|
||||
_ = tls;
|
||||
_ = Der;
|
||||
_ = CertificateBundle;
|
||||
}
|
||||
|
||||
test "CSPRNG" {
|
||||
|
||||
173
lib/std/crypto/CertificateBundle.zig
Normal file
173
lib/std/crypto/CertificateBundle.zig
Normal file
@ -0,0 +1,173 @@
|
||||
//! A set of certificates. Typically pre-installed on every operating system,
|
||||
//! these are "Certificate Authorities" used to validate SSL certificates.
|
||||
//! This data structure stores certificates in DER-encoded form, all of them
|
||||
//! concatenated together in the `bytes` array. The `map` field contains an
|
||||
//! index from the DER-encoded subject name to the index within `bytes`.
|
||||
|
||||
map: std.HashMapUnmanaged(Key, u32, MapContext, std.hash_map.default_max_load_percentage) = .{},
|
||||
bytes: std.ArrayListUnmanaged(u8) = .{},
|
||||
|
||||
pub const Key = struct {
|
||||
subject_start: u32,
|
||||
subject_end: u32,
|
||||
};
|
||||
|
||||
/// The returned bytes become invalid after calling any of the rescan functions
|
||||
/// or add functions.
|
||||
pub fn find(cb: CertificateBundle, subject_name: []const u8) ?[]const u8 {
|
||||
const Adapter = struct {
|
||||
cb: CertificateBundle,
|
||||
|
||||
pub fn hash(ctx: @This(), k: []const u8) u64 {
|
||||
_ = ctx;
|
||||
return std.hash_map.hashString(k);
|
||||
}
|
||||
|
||||
pub fn eql(ctx: @This(), a: []const u8, b_key: Key) bool {
|
||||
const b = ctx.cb.bytes.items[b_key.subject_start..b_key.subject_end];
|
||||
return mem.eql(u8, a, b);
|
||||
}
|
||||
};
|
||||
const index = cb.map.getAdapted(subject_name, Adapter{ .cb = cb }) orelse return null;
|
||||
return cb.bytes.items[index..];
|
||||
}
|
||||
|
||||
pub fn deinit(cb: *CertificateBundle, gpa: Allocator) void {
|
||||
cb.map.deinit(gpa);
|
||||
cb.bytes.deinit(gpa);
|
||||
cb.* = undefined;
|
||||
}
|
||||
|
||||
/// Empties the set of certificates and then scans the host operating system
|
||||
/// file system standard locations for certificates.
|
||||
pub fn rescan(cb: *CertificateBundle, gpa: Allocator) !void {
|
||||
switch (builtin.os.tag) {
|
||||
.linux => return rescanLinux(cb, gpa),
|
||||
else => @compileError("it is unknown where the root CA certificates live on this OS"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rescanLinux(cb: *CertificateBundle, gpa: Allocator) !void {
|
||||
var dir = fs.openIterableDirAbsolute("/etc/ssl/certs", .{}) catch |err| switch (err) {
|
||||
error.FileNotFound => return,
|
||||
else => |e| return e,
|
||||
};
|
||||
defer dir.close();
|
||||
|
||||
cb.bytes.clearRetainingCapacity();
|
||||
cb.map.clearRetainingCapacity();
|
||||
|
||||
var it = dir.iterate();
|
||||
while (try it.next()) |entry| {
|
||||
switch (entry.kind) {
|
||||
.File, .SymLink => {},
|
||||
else => continue,
|
||||
}
|
||||
|
||||
try addCertsFromFile(cb, gpa, dir.dir, entry.name);
|
||||
}
|
||||
|
||||
cb.bytes.shrinkAndFree(gpa, cb.bytes.items.len);
|
||||
}
|
||||
|
||||
pub fn addCertsFromFile(
|
||||
cb: *CertificateBundle,
|
||||
gpa: Allocator,
|
||||
dir: fs.Dir,
|
||||
sub_file_path: []const u8,
|
||||
) !void {
|
||||
var file = try dir.openFile(sub_file_path, .{});
|
||||
defer file.close();
|
||||
|
||||
const size = try file.getEndPos();
|
||||
|
||||
// We borrow `bytes` as a temporary buffer for the base64-encoded data.
|
||||
// This is possible by computing the decoded length and reserving the space
|
||||
// for the decoded bytes first.
|
||||
const decoded_size_upper_bound = size / 4 * 3;
|
||||
try cb.bytes.ensureUnusedCapacity(gpa, decoded_size_upper_bound + size);
|
||||
const end_reserved = cb.bytes.items.len + decoded_size_upper_bound;
|
||||
const buffer = cb.bytes.allocatedSlice()[end_reserved..];
|
||||
const end_index = try file.readAll(buffer);
|
||||
const encoded_bytes = buffer[0..end_index];
|
||||
|
||||
const begin_marker = "-----BEGIN CERTIFICATE-----";
|
||||
const end_marker = "-----END CERTIFICATE-----";
|
||||
|
||||
var start_index: usize = 0;
|
||||
while (mem.indexOfPos(u8, encoded_bytes, start_index, begin_marker)) |begin_marker_start| {
|
||||
const cert_start = begin_marker_start + begin_marker.len;
|
||||
const cert_end = mem.indexOfPos(u8, encoded_bytes, cert_start, end_marker) orelse
|
||||
return error.MissingEndCertificateMarker;
|
||||
start_index = cert_end + end_marker.len;
|
||||
const encoded_cert = mem.trim(u8, encoded_bytes[cert_start..cert_end], " \t\r\n");
|
||||
const decoded_start = @intCast(u32, cb.bytes.items.len);
|
||||
const dest_buf = cb.bytes.allocatedSlice()[decoded_start..];
|
||||
cb.bytes.items.len += try base64.decode(dest_buf, encoded_cert);
|
||||
const k = try key(cb, decoded_start);
|
||||
try cb.map.putContext(gpa, k, decoded_start, .{ .cb = cb });
|
||||
}
|
||||
}
|
||||
|
||||
pub fn key(cb: *CertificateBundle, bytes_index: u32) !Key {
|
||||
const bytes = cb.bytes.items;
|
||||
const certificate = try Der.parseElement(bytes, bytes_index);
|
||||
const tbs_certificate = try Der.parseElement(bytes, certificate.start);
|
||||
const version = try Der.parseElement(bytes, tbs_certificate.start);
|
||||
if (@bitCast(u8, version.identifier) != 0xa0 or
|
||||
!mem.eql(u8, bytes[version.start..version.end], "\x02\x01\x02"))
|
||||
{
|
||||
return error.UnsupportedCertificateVersion;
|
||||
}
|
||||
|
||||
const serial_number = try Der.parseElement(bytes, version.end);
|
||||
|
||||
// RFC 5280, section 4.1.2.3:
|
||||
// "This field MUST contain the same algorithm identifier as
|
||||
// the signatureAlgorithm field in the sequence Certificate."
|
||||
const signature = try Der.parseElement(bytes, serial_number.end);
|
||||
const issuer = try Der.parseElement(bytes, signature.end);
|
||||
const validity = try Der.parseElement(bytes, issuer.end);
|
||||
const subject = try Der.parseElement(bytes, validity.end);
|
||||
//const subject_pub_key = try Der.parseElement(bytes, subject.end);
|
||||
//const extensions = try Der.parseElement(bytes, subject_pub_key.end);
|
||||
|
||||
return .{
|
||||
.subject_start = subject.start,
|
||||
.subject_end = subject.end,
|
||||
};
|
||||
}
|
||||
|
||||
const builtin = @import("builtin");
|
||||
const std = @import("../std.zig");
|
||||
const fs = std.fs;
|
||||
const mem = std.mem;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const Der = std.crypto.Der;
|
||||
const CertificateBundle = @This();
|
||||
|
||||
const base64 = std.base64.standard.decoderWithIgnore(" \t\r\n");
|
||||
|
||||
const MapContext = struct {
|
||||
cb: *const CertificateBundle,
|
||||
|
||||
pub fn hash(ctx: MapContext, k: Key) u64 {
|
||||
return std.hash_map.hashString(ctx.cb.bytes.items[k.subject_start..k.subject_end]);
|
||||
}
|
||||
|
||||
pub fn eql(ctx: MapContext, a: Key, b: Key) bool {
|
||||
const bytes = ctx.cb.bytes.items;
|
||||
return mem.eql(
|
||||
u8,
|
||||
bytes[a.subject_start..a.subject_end],
|
||||
bytes[b.subject_start..b.subject_end],
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
test {
|
||||
var bundle: CertificateBundle = .{};
|
||||
defer bundle.deinit(std.testing.allocator);
|
||||
|
||||
try bundle.rescan(std.testing.allocator);
|
||||
}
|
||||
153
lib/std/crypto/Der.zig
Normal file
153
lib/std/crypto/Der.zig
Normal file
@ -0,0 +1,153 @@
|
||||
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,
|
||||
sequence_of = 17,
|
||||
_,
|
||||
};
|
||||
|
||||
pub const Oid = enum {
|
||||
rsadsi,
|
||||
pkcs,
|
||||
rsaEncryption,
|
||||
md2WithRSAEncryption,
|
||||
md5WithRSAEncryption,
|
||||
sha1WithRSAEncryption,
|
||||
sha256WithRSAEncryption,
|
||||
sha384WithRSAEncryption,
|
||||
sha512WithRSAEncryption,
|
||||
sha224WithRSAEncryption,
|
||||
pbeWithMD2AndDES_CBC,
|
||||
pbeWithMD5AndDES_CBC,
|
||||
pkcs9_emailAddress,
|
||||
md2,
|
||||
md5,
|
||||
rc4,
|
||||
ecdsa_with_Recommended,
|
||||
ecdsa_with_Specified,
|
||||
ecdsa_with_SHA224,
|
||||
ecdsa_with_SHA256,
|
||||
ecdsa_with_SHA384,
|
||||
ecdsa_with_SHA512,
|
||||
X500,
|
||||
X509,
|
||||
commonName,
|
||||
serialNumber,
|
||||
countryName,
|
||||
localityName,
|
||||
stateOrProvinceName,
|
||||
organizationName,
|
||||
organizationalUnitName,
|
||||
organizationIdentifier,
|
||||
|
||||
pub const map = std.ComptimeStringMap(Oid, .{
|
||||
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D }, .rsadsi },
|
||||
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01 }, .pkcs },
|
||||
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01 }, .rsaEncryption },
|
||||
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x02 }, .md2WithRSAEncryption },
|
||||
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x04 }, .md5WithRSAEncryption },
|
||||
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x05 }, .sha1WithRSAEncryption },
|
||||
.{ &[_]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 },
|
||||
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x05, 0x01 }, .pbeWithMD2AndDES_CBC },
|
||||
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x05, 0x03 }, .pbeWithMD5AndDES_CBC },
|
||||
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x09, 0x01 }, .pkcs9_emailAddress },
|
||||
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x02 }, .md2 },
|
||||
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x05 }, .md5 },
|
||||
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x03, 0x04 }, .rc4 },
|
||||
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x02 }, .ecdsa_with_Recommended },
|
||||
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03 }, .ecdsa_with_Specified },
|
||||
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x01 }, .ecdsa_with_SHA224 },
|
||||
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x02 }, .ecdsa_with_SHA256 },
|
||||
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x03 }, .ecdsa_with_SHA384 },
|
||||
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x04 }, .ecdsa_with_SHA512 },
|
||||
.{ &[_]u8{0x55}, .X500 },
|
||||
.{ &[_]u8{ 0x55, 0x04 }, .X509 },
|
||||
.{ &[_]u8{ 0x55, 0x04, 0x03 }, .commonName },
|
||||
.{ &[_]u8{ 0x55, 0x04, 0x05 }, .serialNumber },
|
||||
.{ &[_]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{ 0x55, 0x04, 0x61 }, .organizationIdentifier },
|
||||
});
|
||||
};
|
||||
|
||||
pub const Element = struct {
|
||||
identifier: Identifier,
|
||||
start: u32,
|
||||
end: u32,
|
||||
};
|
||||
|
||||
pub const ParseElementError = error{CertificateHasFieldWithInvalidLength};
|
||||
|
||||
pub fn parseElement(bytes: []const u8, index: u32) ParseElementError!Element {
|
||||
var i = index;
|
||||
const identifier = @bitCast(Identifier, bytes[i]);
|
||||
i += 1;
|
||||
const size_byte = bytes[i];
|
||||
i += 1;
|
||||
if ((size_byte >> 7) == 0) {
|
||||
return .{
|
||||
.identifier = identifier,
|
||||
.start = i,
|
||||
.end = i + size_byte,
|
||||
};
|
||||
}
|
||||
|
||||
const len_size = @truncate(u7, size_byte);
|
||||
if (len_size > @sizeOf(u32)) {
|
||||
return error.CertificateHasFieldWithInvalidLength;
|
||||
}
|
||||
|
||||
const end_i = i + len_size;
|
||||
var long_form_size: u32 = 0;
|
||||
while (i < end_i) : (i += 1) {
|
||||
long_form_size = (long_form_size << 8) | bytes[i];
|
||||
}
|
||||
|
||||
return .{
|
||||
.identifier = identifier,
|
||||
.start = i,
|
||||
.end = i + long_form_size,
|
||||
};
|
||||
}
|
||||
|
||||
pub const ParseObjectIdError = error{
|
||||
CertificateHasUnrecognizedObjectId,
|
||||
CertificateFieldHasWrongDataType,
|
||||
} || ParseElementError;
|
||||
|
||||
pub fn parseObjectId(bytes: []const u8, element: Element) ParseObjectIdError!Oid {
|
||||
if (element.identifier.tag != .object_identifier)
|
||||
return error.CertificateFieldHasWrongDataType;
|
||||
return Oid.map.get(bytes[element.start..element.end]) orelse
|
||||
return error.CertificateHasUnrecognizedObjectId;
|
||||
}
|
||||
|
||||
const std = @import("../std.zig");
|
||||
const Der = @This();
|
||||
@ -349,112 +349,3 @@ 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;
|
||||
}
|
||||
};
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
const std = @import("../../std.zig");
|
||||
const tls = std.crypto.tls;
|
||||
const Der = std.crypto.Der;
|
||||
const Client = @This();
|
||||
const net = std.net;
|
||||
const mem = std.mem;
|
||||
@ -28,7 +29,7 @@ partially_read_len: u15,
|
||||
eof: bool,
|
||||
|
||||
/// `host` is only borrowed during this function call.
|
||||
pub fn init(stream: net.Stream, host: []const u8) !Client {
|
||||
pub fn init(stream: net.Stream, ca_bundle: crypto.CertificateBundle, host: []const u8) !Client {
|
||||
const host_len = @intCast(u16, host.len);
|
||||
|
||||
var random_buffer: [128]u8 = undefined;
|
||||
@ -392,7 +393,7 @@ pub fn init(stream: net.Stream, host: []const u8) !Client {
|
||||
switch (cipher_params) {
|
||||
inline else => |*p| p.transcript_hash.update(wrapped_handshake),
|
||||
}
|
||||
var hs_i: usize = 0;
|
||||
var hs_i: u32 = 0;
|
||||
const cert_req_ctx_len = handshake[hs_i];
|
||||
hs_i += 1;
|
||||
if (cert_req_ctx_len != 0) return error.TlsIllegalParameter;
|
||||
@ -404,75 +405,47 @@ pub fn init(stream: net.Stream, host: []const u8) !Client {
|
||||
hs_i += 3;
|
||||
const end_cert = hs_i + cert_size;
|
||||
|
||||
const certificate = try tls.Der.parseElement(handshake, &hs_i);
|
||||
const certificate = try Der.parseElement(handshake, hs_i);
|
||||
const tbs_certificate = try Der.parseElement(handshake, certificate.start);
|
||||
|
||||
const version = try Der.parseElement(handshake, tbs_certificate.start);
|
||||
if (@bitCast(u8, version.identifier) != 0xa0 or
|
||||
!mem.eql(u8, handshake[version.start..version.end], "\x02\x01\x02"))
|
||||
{
|
||||
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,
|
||||
});
|
||||
return error.UnsupportedCertificateVersion;
|
||||
}
|
||||
|
||||
const serial_number = try Der.parseElement(handshake, version.end);
|
||||
// RFC 5280, section 4.1.2.3:
|
||||
// "This field MUST contain the same algorithm identifier as
|
||||
// the signatureAlgorithm field in the sequence Certificate."
|
||||
const signature = try Der.parseElement(handshake, serial_number.end);
|
||||
const issuer = try Der.parseElement(handshake, signature.end);
|
||||
const validity = try Der.parseElement(handshake, issuer.end);
|
||||
const subject = try Der.parseElement(handshake, validity.end);
|
||||
const subject_pub_key = try Der.parseElement(handshake, subject.end);
|
||||
const extensions = try Der.parseElement(handshake, subject_pub_key.end);
|
||||
_ = extensions;
|
||||
|
||||
const signature_algorithm = try Der.parseElement(handshake, tbs_certificate.end);
|
||||
const signature_value = try Der.parseElement(handshake, signature_algorithm.end);
|
||||
_ = signature_value;
|
||||
|
||||
const algorithm_elem = try Der.parseElement(handshake, signature_algorithm.start);
|
||||
const algorithm = try Der.parseObjectId(handshake, algorithm_elem);
|
||||
std.debug.print("cert has this signature algorithm: {any}\n", .{algorithm});
|
||||
//const parameters = try Der.parseElement(signature_algorithm.contents, &sa_i);
|
||||
|
||||
hs_i = end_cert;
|
||||
const total_ext_size = mem.readIntBig(u16, handshake[hs_i..][0..2]);
|
||||
hs_i += 2;
|
||||
hs_i += total_ext_size;
|
||||
|
||||
std.debug.print("received certificate of size {d} bytes with {d} bytes of extensions\n", .{
|
||||
cert_size, total_ext_size,
|
||||
const issuer_bytes = handshake[issuer.start..issuer.end];
|
||||
const ca_cert = ca_bundle.find(issuer_bytes);
|
||||
|
||||
std.debug.print("received certificate of size {d} bytes with {d} bytes of extensions. ca_found={any}\n", .{
|
||||
cert_size, total_ext_size, ca_cert != null,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@ -7,6 +7,7 @@ const Client = @This();
|
||||
allocator: std.mem.Allocator,
|
||||
headers: std.ArrayListUnmanaged(u8) = .{},
|
||||
active_requests: usize = 0,
|
||||
ca_bundle: std.crypto.CertificateBundle = .{},
|
||||
|
||||
pub const Request = struct {
|
||||
client: *Client,
|
||||
@ -102,7 +103,7 @@ pub fn request(client: *Client, options: Request.Options) !Request {
|
||||
switch (options.protocol) {
|
||||
.http => {},
|
||||
.https => {
|
||||
req.tls_client = try std.crypto.tls.Client.init(req.stream, options.host);
|
||||
req.tls_client = try std.crypto.tls.Client.init(req.stream, client.ca_bundle, options.host);
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user