milestone 10: doh and dot listeners, cert store with hot reload and cert reload api
This commit is contained in:
@@ -190,7 +190,7 @@ test "TlsStream.flush puts the record on the wire" {
|
||||
defer threaded.deinit();
|
||||
const io = threaded.io();
|
||||
|
||||
var ctx = try tls_server.ServerContext.init(gpa, fixtures.cert_pem, fixtures.key_pem);
|
||||
var ctx = try tls_server.ServerContext.init(gpa, fixtures.cert_pem, fixtures.key_pem, null);
|
||||
defer ctx.deinit(gpa);
|
||||
|
||||
const listen_address: net.IpAddress = .{ .ip4 = .loopback(0) };
|
||||
|
||||
@@ -62,7 +62,19 @@ pub const ServerContext = struct {
|
||||
const Config = Block(SslConfig);
|
||||
};
|
||||
|
||||
pub fn init(gpa: std.mem.Allocator, cert_pem: [:0]const u8, key_pem: [:0]const u8) InitError!ServerContext {
|
||||
/// `alpn`, when non-null, is a NULL-terminated list of protocol names in
|
||||
/// decreasing preference order (e.g. `&.{"http/1.1"}` as a
|
||||
/// comptime-constant `[_:null]?[*:0]const u8` array). Mbed TLS records the
|
||||
/// pointer, not a copy, so the array must outlive this `ServerContext` —
|
||||
/// pass a comptime-constant array, never stack or heap memory that can go
|
||||
/// away first. Clients that send no ALPN extension still connect; Mbed TLS
|
||||
/// only enforces the list against clients that offer one.
|
||||
pub fn init(
|
||||
gpa: std.mem.Allocator,
|
||||
cert_pem: [:0]const u8,
|
||||
key_pem: [:0]const u8,
|
||||
alpn: ?[*:null]const ?[*:0]const u8,
|
||||
) InitError!ServerContext {
|
||||
assert(nx_max_context_alignment() <= context_alignment);
|
||||
|
||||
// TLS 1.3 is on in the stock config; its key schedule runs through PSA.
|
||||
@@ -148,6 +160,12 @@ pub const ServerContext = struct {
|
||||
error.ConfigFailed,
|
||||
);
|
||||
|
||||
if (alpn) |protos| try check(
|
||||
mbedtls_ssl_conf_alpn_protocols(config.ptr, protos),
|
||||
"ssl_conf_alpn_protocols",
|
||||
error.ConfigFailed,
|
||||
);
|
||||
|
||||
return .{
|
||||
.entropy = entropy,
|
||||
.drbg = drbg,
|
||||
@@ -518,6 +536,9 @@ extern fn mbedtls_ssl_config_defaults(
|
||||
extern fn mbedtls_ssl_conf_rng(conf: *SslConfig, f_rng: *const RngFn, p_rng: ?*anyopaque) void;
|
||||
extern fn mbedtls_ssl_conf_authmode(conf: *SslConfig, authmode: c_int) void;
|
||||
extern fn mbedtls_ssl_conf_own_cert(conf: *SslConfig, own_cert: *X509Crt, pk_key: *PkContext) c_int;
|
||||
/// Records the pointer to `protos` (NULL-terminated, decreasing preference);
|
||||
/// the list must outlive the configuration.
|
||||
extern fn mbedtls_ssl_conf_alpn_protocols(conf: *SslConfig, protos: [*:null]const ?[*:0]const u8) c_int;
|
||||
|
||||
extern fn mbedtls_x509_crt_init(crt: *X509Crt) void;
|
||||
extern fn mbedtls_x509_crt_free(crt: *X509Crt) void;
|
||||
@@ -583,7 +604,7 @@ test "ServerContext.init accepts the fixture cert and key" {
|
||||
const fixtures = @import("test_fixtures");
|
||||
const gpa = std.testing.allocator;
|
||||
|
||||
var ctx = try ServerContext.init(gpa, fixtures.cert_pem, fixtures.key_pem);
|
||||
var ctx = try ServerContext.init(gpa, fixtures.cert_pem, fixtures.key_pem, null);
|
||||
defer ctx.deinit(gpa);
|
||||
}
|
||||
|
||||
@@ -596,7 +617,7 @@ test "ServerContext.init rejects a truncated certificate" {
|
||||
|
||||
try std.testing.expectError(
|
||||
error.CertParse,
|
||||
ServerContext.init(gpa, truncated, fixtures.key_pem),
|
||||
ServerContext.init(gpa, truncated, fixtures.key_pem, null),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -609,7 +630,7 @@ test "ServerContext.init rejects a truncated private key" {
|
||||
|
||||
try std.testing.expectError(
|
||||
error.KeyParse,
|
||||
ServerContext.init(gpa, fixtures.cert_pem, truncated),
|
||||
ServerContext.init(gpa, fixtures.cert_pem, truncated, null),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -619,10 +640,19 @@ test "ServerContext.init rejects a key that is not the certificate's" {
|
||||
|
||||
try std.testing.expectError(
|
||||
error.KeyMismatch,
|
||||
ServerContext.init(gpa, fixtures.cert_pem, fixtures.mismatched_key_pem),
|
||||
ServerContext.init(gpa, fixtures.cert_pem, fixtures.mismatched_key_pem, null),
|
||||
);
|
||||
}
|
||||
|
||||
test "ServerContext.init accepts a NULL-terminated ALPN protocol list" {
|
||||
const fixtures = @import("test_fixtures");
|
||||
const gpa = std.testing.allocator;
|
||||
|
||||
const protocols: [*:null]const ?[*:0]const u8 = &.{ "http/1.1", "dot" };
|
||||
var ctx = try ServerContext.init(gpa, fixtures.cert_pem, fixtures.key_pem, protocols);
|
||||
defer ctx.deinit(gpa);
|
||||
}
|
||||
|
||||
test "the shim agrees with the alignment contexts are allocated at" {
|
||||
try std.testing.expect(nx_max_context_alignment() <= context_alignment);
|
||||
try std.testing.expect(nx_sizeof_ssl_context() > 0);
|
||||
@@ -652,7 +682,7 @@ test "loopback echo between the mbedtls server and std.crypto.tls.Client" {
|
||||
defer threaded.deinit();
|
||||
const io = threaded.io();
|
||||
|
||||
var ctx = try ServerContext.init(gpa, fixtures.cert_pem, fixtures.key_pem);
|
||||
var ctx = try ServerContext.init(gpa, fixtures.cert_pem, fixtures.key_pem, null);
|
||||
defer ctx.deinit(gpa);
|
||||
|
||||
const listen_address: Io.net.IpAddress = .{ .ip4 = .loopback(0) };
|
||||
@@ -671,6 +701,40 @@ test "loopback echo between the mbedtls server and std.crypto.tls.Client" {
|
||||
try server_result;
|
||||
}
|
||||
|
||||
// std.crypto.tls.Client in Zig 0.16.0 cannot send the ALPN extension
|
||||
// (Client.Options has no such field), so negotiation itself is untestable
|
||||
// here; this proves the ruling that a client offering no ALPN still
|
||||
// completes the handshake against a context advertising a list.
|
||||
test "loopback echo succeeds against a context advertising ALPN" {
|
||||
const build_options = @import("build_options");
|
||||
if (!build_options.integration) return error.SkipZigTest;
|
||||
|
||||
const fixtures = @import("test_fixtures");
|
||||
const gpa = std.testing.allocator;
|
||||
|
||||
var threaded: Io.Threaded = .init(gpa, .{});
|
||||
defer threaded.deinit();
|
||||
const io = threaded.io();
|
||||
|
||||
const protocols: [*:null]const ?[*:0]const u8 = &.{"http/1.1"};
|
||||
var ctx = try ServerContext.init(gpa, fixtures.cert_pem, fixtures.key_pem, protocols);
|
||||
defer ctx.deinit(gpa);
|
||||
|
||||
const listen_address: Io.net.IpAddress = .{ .ip4 = .loopback(0) };
|
||||
var server = try listen_address.listen(io, .{ .reuse_address = true });
|
||||
defer server.deinit(io);
|
||||
|
||||
var server_task = try io.concurrent(echoOnce, .{ gpa, &ctx, io, &server });
|
||||
const client_result = runEchoClient(io, server.socket.address);
|
||||
const server_result = if (client_result) |_|
|
||||
server_task.await(io)
|
||||
else |_|
|
||||
server_task.cancel(io);
|
||||
|
||||
try client_result;
|
||||
try server_result;
|
||||
}
|
||||
|
||||
test "a transport EOF without close_notify reads as a truncated stream" {
|
||||
const build_options = @import("build_options");
|
||||
if (!build_options.integration) return error.SkipZigTest;
|
||||
@@ -682,7 +746,7 @@ test "a transport EOF without close_notify reads as a truncated stream" {
|
||||
defer threaded.deinit();
|
||||
const io = threaded.io();
|
||||
|
||||
var ctx = try ServerContext.init(gpa, fixtures.cert_pem, fixtures.key_pem);
|
||||
var ctx = try ServerContext.init(gpa, fixtures.cert_pem, fixtures.key_pem, null);
|
||||
defer ctx.deinit(gpa);
|
||||
|
||||
const listen_address: Io.net.IpAddress = .{ .ip4 = .loopback(0) };
|
||||
|
||||
Reference in New Issue
Block a user