From d56a65a8c4609a740eee43fd7073c2485c87c2c6 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 16 Jan 2023 15:49:58 -0700 Subject: [PATCH] std.http.Client: default to lazy root cert scanning After this change, the system will be inspected for root certificates only upon the first https request that actually occurs. This makes the compiler no longer do SSL certificate scanning when running `zig build` if no network requests are made. --- lib/std/http/Client.zig | 19 +++++++++++++++---- src/main.zig | 1 - 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/lib/std/http/Client.zig b/lib/std/http/Client.zig index e97b366370..7cf512d65f 100644 --- a/lib/std/http/Client.zig +++ b/lib/std/http/Client.zig @@ -1,5 +1,7 @@ //! TODO: send connection: keep-alive and LRU cache a configurable number of //! open connections to skip DNS and TLS handshake for subsequent requests. +//! +//! This API is *not* thread safe. const std = @import("../std.zig"); const mem = std.mem; @@ -15,6 +17,9 @@ const testing = std.testing; /// managed buffer is not provided. allocator: Allocator, ca_bundle: std.crypto.Certificate.Bundle = .{}, +/// When this is `true`, the next time this client performs an HTTPS request, +/// it will first rescan the system for root certificates. +next_https_rescan_certs: bool = true, pub const Connection = struct { stream: net.Stream, @@ -594,6 +599,7 @@ pub const Request = struct { CertificateTimeInvalid, CertificateHasUnrecognizedObjectId, CertificateHasInvalidBitString, + CertificateAuthorityBundleTooBig, // TODO: convert to higher level errors InvalidFormat, @@ -648,6 +654,10 @@ pub const Request = struct { NetworkSubsystemFailed, NotDir, ReadOnlyFileSystem, + Unseekable, + MissingEndCertificateMarker, + InvalidPadding, + EndOfStream, }; pub fn read(req: *Request, buffer: []u8) ReadError!usize { @@ -837,10 +847,6 @@ pub fn deinit(client: *Client) void { client.* = undefined; } -pub fn rescanRootCertificates(client: *Client) !void { - return client.ca_bundle.rescan(client.allocator); -} - pub fn connect(client: *Client, host: []const u8, port: u16, protocol: Connection.Protocol) !Connection { var conn: Connection = .{ .stream = try net.tcpConnectToHost(client.allocator, host, port), @@ -876,6 +882,11 @@ pub fn request(client: *Client, uri: Uri, headers: Request.Headers, options: Req const host = uri.host orelse return error.UriMissingHost; + if (client.next_https_rescan_certs and protocol == .tls) { + try client.ca_bundle.rescan(client.allocator); + client.next_https_rescan_certs = false; + } + var req: Request = .{ .client = client, .headers = headers, diff --git a/src/main.zig b/src/main.zig index 2e97cc0214..9455bc328a 100644 --- a/src/main.zig +++ b/src/main.zig @@ -4098,7 +4098,6 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi if (!build_options.omit_pkg_fetching_code) { var http_client: std.http.Client = .{ .allocator = gpa }; defer http_client.deinit(); - try http_client.rescanRootCertificates(); // Here we provide an import to the build runner that allows using reflection to find // all of the dependencies. Without this, there would be no way to use `@import` to