diff --git a/src/Config.zig b/src/Config.zig index 2ec4b82a2..9609642b0 100644 --- a/src/Config.zig +++ b/src/Config.zig @@ -28,6 +28,7 @@ const Storage = @import("storage/Storage.zig"); const WebBotAuthConfig = @import("network/WebBotAuth.zig").Config; const log = lp.log; +const crypto = @import("sys/libcrypto.zig"); const Allocator = std.mem.Allocator; // TCP keepalive parameters applied to accepted CDP connections. @@ -73,18 +74,108 @@ fn logFilterScopesValidator(allocator: Allocator, args: *std.process.Args.Iterat } } -fn logLevelValidator(_: Allocator, args: *std.process.Args.Iterator) !?log.Level { +fn logLevelValidator(_: Allocator, args: *std.process.Args.Iterator, target: *?log.Level) !void { const str = args.next() orelse return error.MissingArgument; if (std.mem.eql(u8, str, "error")) { - return .err; + target.* = .err; + return; } - return std.meta.stringToEnum(log.Level, str) orelse { + target.* = std.meta.stringToEnum(log.Level, str) orelse { log.fatal(.app, "invalid option choice", .{ .arg = "--log-level", .value = str }); return error.InvalidArgument; }; } +const Cert = struct { + /// On successful CLI argument parsing phase, ownership of this transferred + /// to `Network`. Consider it as invalid. + store: ?*crypto.X509_STORE = null, + // Number of certificate sources loaded into `store`. + count: usize = 0, + + fn deinit(self: *Cert) void { + if (self.store) |store| { + crypto.X509_STORE_free(store); + } + self.* = .{}; + } + + /// Returns the store, creating it on first use. The store is shared by + /// every `--ca-cert`/`--ca-path` occurrence. + fn getOrCreate(self: *Cert) !*crypto.X509_STORE { + if (self.store) |store| { + return store; + } + const store = crypto.X509_STORE_new() orelse + return error.FailedToCreateCertStore; + self.store = store; + return store; + } +}; + +fn caCertValidator( + _: Allocator, + args: *std.process.Args.Iterator, + cert: *Cert, +) !void { + const file_name = args.next() orelse return error.MissingArgument; + const store = try cert.getOrCreate(); + errdefer cert.deinit(); + + if (crypto.X509_STORE_load_locations(store, file_name, null) != 1) { + log.fatal(.app, "Invalid CA cert", .{ .arg = "--ca-cert", .value = file_name }); + return error.InvalidArgument; + } + cert.count += 1; +} + +fn caPathValidator( + allocator: Allocator, + args: *std.process.Args.Iterator, + cert: *Cert, +) !void { + const dir_path = args.next() orelse return error.MissingArgument; + + var dir = std.Io.Dir.cwd().openDir(lp.io, dir_path, .{ .iterate = true }) catch { + log.fatal(.app, "Invalid CA path", .{ .arg = "--ca-path", .value = dir_path }); + return error.InvalidArgument; + }; + defer dir.close(lp.io); + + const store = try cert.getOrCreate(); + errdefer cert.deinit(); + + // Eagerly load every certificate in the directory rather than + // registering a lazy hashed lookup: the directory doesn't need to be + // c_rehash'ed, bad entries surface at startup and `count` reflects + // what was actually loaded. + const count_before = cert.count; + var it = dir.iterate(); + while (it.next(lp.io) catch { + log.fatal(.app, "Invalid CA path", .{ .arg = "--ca-path", .value = dir_path }); + return error.InvalidArgument; + }) |entry| { + if (entry.kind != .file and entry.kind != .sym_link) continue; + + const path = try std.fs.path.joinZ(allocator, &.{ dir_path, entry.name }); + defer allocator.free(path); + + if (crypto.X509_STORE_load_locations(store, path, null) != 1) { + log.warn(.app, "Skipping invalid CA cert", .{ .arg = "--ca-path", .value = path }); + continue; + } + cert.count += 1; + } + + // An empty directory (or one with no readable certificates) is + // indistinguishable from a typo; treat it as an error. + if (cert.count == count_before) { + log.fatal(.app, "No certificates loaded", .{ .arg = "--ca-path", .value = dir_path }); + return error.InvalidArgument; + } +} + /// Common CLI args. const CommonOptions = .{ .{ .name = "obey_robots", .type = bool }, @@ -118,24 +209,46 @@ const CommonOptions = .{ .{ .name = "v8_flags_unsafe", .type = ?[]const u8 }, .{ .name = "v8_max_heap_mb", .type = ?u32 }, .{ .name = "watchdog_ms", .type = ?u32 }, + .{ + .name = "ca_cert", + .field_name = "cert", + .type = .{ + .cli = [:0]const u8, + .memory = Cert, + }, + .default = Cert{}, + .validator = caCertValidator, + }, + .{ + .name = "ca_path", + .field_name = "cert", + .type = .{ + .cli = []const u8, + .memory = Cert, + }, + .default = Cert{}, + .validator = caPathValidator, + }, }; -fn dumpValidator(_: Allocator, args: *std.process.Args.Iterator) !?DumpFormat { +fn dumpValidator(_: Allocator, args: *std.process.Args.Iterator, target: *?DumpFormat) !void { // Peek next argument. var peek_args = args.*; if (peek_args.next()) |next_arg| { const mode = std.meta.stringToEnum(DumpFormat, next_arg) orelse { - return .html; + target.* = .html; + return; }; // Skip the argument we peek if successful. _ = args.next(); - return mode; + target.* = mode; + return; } // Means we couldn't get something like `--dump html` but we do have // `--dump`; which should fall to `html` by default. - return .html; + target.* = .html; } pub const AiProvider = std.meta.Tag(zenai.provider.Client); @@ -160,13 +273,13 @@ pub const AgentVerbosity = enum { } }; -fn waitScriptFileValidator(allocator: Allocator, args: *std.process.Args.Iterator) !?[:0]const u8 { +fn waitScriptFileValidator(allocator: Allocator, args: *std.process.Args.Iterator, target: *?[:0]const u8) !void { const path = args.next() orelse { log.fatal(.app, "missing argument value", .{ .arg = "--wait-script-file" }); return error.InvalidArgument; }; - return std.Io.Dir.cwd().readFileAllocOptions(lp.io, path, allocator, .limited(1024 * 1024), .of(u8), 0) catch |err| { + target.* = std.Io.Dir.cwd().readFileAllocOptions(lp.io, path, allocator, .limited(1024 * 1024), .of(u8), 0) catch |err| { log.fatal(.app, "failed to read file", .{ .arg = "--wait-script-file", .path = path, .err = err }); return error.InvalidArgument; }; @@ -635,6 +748,20 @@ pub fn storageSqlitePath(self: *const Config) ?[:0]const u8 { }; } +/// Returns the user-supplied certificate store (`--ca-cert`/`--ca-path`), +/// if any was loaded during argument parsing. The caller takes ownership. +pub fn customCertStore(self: *const Config) ?*crypto.X509_STORE { + return switch (self.mode) { + inline .serve, .fetch, .mcp, .agent => |opts| { + const store = opts.cert.store orelse return null; + // Validators guarantee a created store loaded something. + lp.assert(opts.cert.count > 0, "empty custom cert store", .{}); + return store; + }, + else => null, + }; +} + pub const DumpFormat = enum { html, markdown, diff --git a/src/Updater.zig b/src/Updater.zig index 97bb50a2a..1e044f432 100644 --- a/src/Updater.zig +++ b/src/Updater.zig @@ -38,7 +38,7 @@ config: *const Config, pub fn init(allocator: Allocator, config: *const Config) !Updater { Network.globalInit(allocator); errdefer Network.globalDeinit(); - const x509_store = try Network.createX509Store(allocator); + const x509_store = try Network.prepareX509Store(allocator, config); return .{ .x509_store = x509_store, diff --git a/src/cli.zig b/src/cli.zig index 08bbcb15e..75472eef6 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -48,7 +48,10 @@ const log = lp.log; /// - `options: tuple` — tuple of option descriptors (see below). Use `.{}` /// for none. /// - `shared_options: tuple` (optional) — extra options merged into this -/// command. Useful for common flags shared across commands. +/// command. Useful for common flags shared across commands. An option +/// that appears in both with the same field name and type is collapsed +/// into one field (the command's own option wins); reusing a name with +/// a different type is a compile error. /// - `positional: struct` (optional) — a positional argument with `.name` /// and `.type` that may appear anywhere in argv. By default it holds a /// single value: `.type` must be an optional pointer-to-u8 slice (e.g. @@ -61,7 +64,12 @@ const log = lp.log; /// /// - `name: []const u8` — snake_case field name. Both `--snake_case` and /// `--kebab-case` are accepted on the command line. +/// - `field_name: []const u8` (optional) — name of the struct field the +/// value is written to, when it should differ from the flag name. /// - `type` — the Zig type of the parsed value (see supported types below). +/// When the stored representation differs from the CLI-facing one, use +/// `.{ .cli = T, .memory = T }`; a `validator` must then produce the +/// memory type. /// - `default` (optional) — compile-time default when the flag is absent. /// Rules vary by type; see the defaults section below. /// - `multiple: bool` (optional) — when `true`, the field becomes a @@ -92,15 +100,20 @@ const log = lp.log; /// ## Validators /// /// A `validator` is a custom parse function that takes over argument -/// consumption for an option. Its signature depends on whether `multiple` +/// consumption for an option. It receives a pointer to the generated struct +/// field and writes through it; the pointee depends on whether `multiple` /// is set: /// -/// - Single: `fn (Allocator, *ArgIterator) !T` — returns the parsed value. +/// - Single: `fn (Allocator, *ArgIterator, *T) !void` — writes the parsed +/// value through the field pointer. /// - Multiple: `fn (Allocator, *ArgIterator, *std.ArrayList(T)) !void` — /// appends directly into the list. /// /// When a validator is present, the built-in type switch is skipped entirely. /// The validator owns advancing the iterator and is free to peek ahead. +/// Because the validator sees the field itself, a repeated flag can +/// accumulate state in place (e.g. add each certificate to a store the +/// field points at) instead of collecting values in a list. /// /// ## Variants /// @@ -196,7 +209,22 @@ pub fn Builder(comptime commands: anytype) type { // Whether option has a default value. const has_default = @hasField(@TypeOf(option), "default"); - const T = if (is_multiple) std.ArrayList(option.type) else option.type; + const Type = blk: { + if (@typeInfo(@TypeOf(option.type)) == .@"struct") { + break :blk option.type.memory; + } + break :blk option.type; + }; + + const T = if (is_multiple) std.ArrayList(Type) else Type; + + // Prefer `option.field_name` if memory representation will differ. + const name = blk: { + if (@hasField(@TypeOf(option), "field_name")) { + break :blk option.field_name; + } + break :blk option.name; + }; const default = blk: { if (is_multiple) { @@ -208,7 +236,7 @@ pub fn Builder(comptime commands: anytype) type { break :blk @as(*const anyopaque, @ptrCast(&@as(T, .empty))); } - switch (@typeInfo(option.type)) { + switch (@typeInfo(Type)) { .optional => |optional| { if (optional.child == bool) { @compileError("?bool is not supported, prefer enum"); @@ -228,7 +256,7 @@ pub fn Builder(comptime commands: anytype) type { }, inline else => { if (!has_default) { - @compileError("option `" ++ option.name ++ "` is not optional type and has no default value"); + @compileError("option `" ++ name ++ "` is not optional type and has no default value"); } break :blk @as(*const anyopaque, @ptrCast(&@as(T, option.default))); }, @@ -236,7 +264,7 @@ pub fn Builder(comptime commands: anytype) type { }; fields[j] = .{ - .name = option.name, + .name = name, .type = T, .default_value_ptr = default, .is_comptime = false, @@ -247,6 +275,34 @@ pub fn Builder(comptime commands: anytype) type { return fields; } + /// Drops duplicate fields, keeping the first occurrence. Only an exact + /// duplicate (same name and type) is deduplicated; a name that + /// reappears with a different type is a conflict. + fn dedupeStructFields(comptime fields: []const std.builtin.Type.StructField) []const std.builtin.Type.StructField { + // The pairwise name comparisons blow the default 1000-branch quota. + @setEvalBranchQuota(1000 + fields.len * fields.len * 100); + + var out: [fields.len]std.builtin.Type.StructField = undefined; + var len: usize = 0; + + outer: for (fields) |field| { + for (out[0..len]) |existing| { + if (!std.mem.eql(u8, existing.name, field.name)) continue; + + if (existing.type != field.type) { + @compileError("field `" ++ field.name ++ "` is duplicated with a different type"); + } + continue :outer; + } + + out[len] = field; + len += 1; + } + + const frozen = out; + return frozen[0..len]; + } + /// Union type for provided commands. pub const Union = blk: { const len = commands.len + 1; @@ -258,7 +314,7 @@ pub fn Builder(comptime commands: anytype) type { const Command = @TypeOf(command); const options = command.options; - const fields = optionsToStructFields(options) ++ + const all_fields = optionsToStructFields(options) ++ (if (@hasField(Command, "shared_options")) optionsToStructFields(command.shared_options) else @@ -268,7 +324,7 @@ pub fn Builder(comptime commands: anytype) type { else .{}); - const T = StructFromFields(&fields); + const T = StructFromFields(dedupeStructFields(&all_fields)); union_fields[i] = .{ .name = command.name, .type = T, .alignment = @alignOf(T) }; } @@ -435,7 +491,7 @@ pub fn Builder(comptime commands: anytype) type { return *const fn (Allocator, *std.process.Args.Iterator, *std.ArrayList(T)) anyerror!void; } - return *const fn (Allocator, *std.process.Args.Iterator) anyerror!T; + return *const fn (Allocator, *std.process.Args.Iterator, *T) anyerror!void; } /// Turns a snake_case string to kebab-case in comptime. @@ -456,7 +512,9 @@ pub fn Builder(comptime commands: anytype) type { /// ```zig /// Option{ /// .name = "option_name", - /// .type = T, + /// // If provided, names the struct field written to instead of `name`. + /// .field_name = "struct_field_name", + /// .type = T, // or .{ .cli = T, .memory = T } /// .multiple = ?bool, /// .validator = ?ValidatorFn(T, is_multiple), /// }; @@ -469,23 +527,19 @@ pub fn Builder(comptime commands: anytype) type { const is_multiple = @hasField(OptionType, "multiple") and option.multiple; const has_validator = @hasField(OptionType, "validator"); - // Prefer validator for parsing if provided. + // Prefer validator for parsing if provided. The validator writes + // through the field pointer (the list itself for multiples). if (has_validator) { - const validator = option.validator; - if (is_multiple) { - // Pass the list. - try @call(.auto, validator, .{ allocator, args, target }); - } else { - // Receive the value from return. - const v = try @call(.auto, validator, .{ allocator, args }); - target.* = v; - } - - return; + return @call(.auto, option.validator, .{ allocator, args, target }); } - // Extract type info. - const T = option.type; + // Extract type info. We need the type that's used in the CLI. + const T = blk: { + if (@typeInfo(@TypeOf(option.type)) == .@"struct") { + break :blk option.type.cli; + } + break :blk option.type; + }; const option_info = blk: { const info = @typeInfo(T); // If wrapped in optional, prefer the child type. @@ -639,15 +693,23 @@ pub fn Builder(comptime commands: anytype) type { }; iter_args: while (args.next()) |option_name| { inline for (options) |option| { + const name = option.name; + const field_name = blk: { + if (@hasField(@TypeOf(option), "field_name")) { + break :blk option.field_name; + } + break :blk option.name; + }; + // We allow both `--my-option` and `--my_option` variants; // assuming given `option` struct prefer snake_case for `name`. // Match an option. const matches_short = comptime @hasField(@TypeOf(option), "short"); - if (std.mem.eql(u8, option_name, "--" ++ option.name) or - std.mem.eql(u8, option_name, "--" ++ comptime toKebabCase(option.name)) or + if (std.mem.eql(u8, option_name, "--" ++ name) or + std.mem.eql(u8, option_name, "--" ++ comptime toKebabCase(name)) or (matches_short and std.mem.eql(u8, option_name, "-" ++ [_]u8{option.short}))) { - try parseValue(allocator, args, &@field(c, option.name), option); + try parseValue(allocator, args, &@field(c, field_name), option); continue :iter_args; } @@ -672,7 +734,7 @@ pub fn Builder(comptime commands: anytype) type { break :blk .{ .name = variant.name, .type = option.type, .multiple = is_multiple }; }; - try parseValue(allocator, args, &@field(c, option.name), opts); + try parseValue(allocator, args, &@field(c, field_name), opts); continue :iter_args; } } diff --git a/src/help.zon b/src/help.zon index 70809b9b7..1800a6026 100644 --- a/src/help.zon +++ b/src/help.zon @@ -305,6 +305,15 @@ \\ Block HTTP requests to private/internal IP addresses after DNS \\ resolution. \\ Defaults to false. + \\ --ca-cert + \\ Load TLS root certificates from a PEM file. Can be passed + \\ multiple times. When any --ca-cert or --ca-path is given, the + \\ system trust store is replaced by these certificates. + \\ --ca-path + \\ Load TLS root certificates from every file in a directory. Unlike + \\ openssl, the directory does not need to be c_rehash'ed. Can be + \\ passed multiple times. When any --ca-cert or --ca-path is given, + \\ the system trust store is replaced by these certificates. \\ --cookie \\ Path to a JSON file to load cookies from (read-only). \\ Defaults to no cookie loading. diff --git a/src/network/Network.zig b/src/network/Network.zig index 868e82680..fc7c827fd 100644 --- a/src/network/Network.zig +++ b/src/network/Network.zig @@ -183,7 +183,14 @@ pub fn init(allocator: Allocator, app: *App, config: *const Config) !Network { const x509_store = blk: { if (config.tlsVerifyHost()) { - break :blk try createX509Store(allocator); + break :blk try prepareX509Store(allocator, config); + } + // Verification is off, so the store is never consulted — but still + // take ownership of a user-supplied one so the flags compose and + // nothing leaks. + if (config.customCertStore()) |store| { + log.warn(.app, "custom CA ignored", .{ .arg = "--ca-cert, --ca-path", .reason = "TLS verification disabled" }); + break :blk store; } break :blk crypto.X509_STORE_new() orelse { return error.FailedToCreateX509Store; @@ -730,24 +737,38 @@ pub fn newConnection(self: *Network) ?*http.Connection { return conn; } -const CreateX509StoreError = std.crypto.Certificate.Bundle.RescanError || error{FailedToCreateX509Store}; - /// NEVER give full ownership of store to `SSL_CTX`, always rely on ref counting. /// Allocations made through passed `allocator` are freed before this function returns. -pub fn createX509Store(allocator: Allocator) CreateX509StoreError!*crypto.X509_STORE { +pub fn prepareX509Store(allocator: Allocator, config: *const Config) !*crypto.X509_STORE { + // A user-supplied store replaces system trust entirely. + if (config.customCertStore()) |store| { + return store; + } + return storeFromSystemCA(allocator); +} + +/// Creates an X509_STORE from system root CA. +fn storeFromSystemCA(allocator: Allocator) !*crypto.X509_STORE { const store = crypto.X509_STORE_new() orelse return error.FailedToCreateX509Store; errdefer crypto.X509_STORE_free(store); + var count: usize = 0; + defer { + if (count == 0) { + log.warn(.app, "No certificates loaded", .{}); + } + } + switch (comptime builtin.os.tag) { .linux, .openbsd, .netbsd, .freebsd => blk: { - // Iterate over known directories. - inline for ([_][:0]const u8{ + // Iterate over known directories; this may or may not succeed. + const cwd = std.Io.Dir.cwd(); + inline for ([_][]const u8{ "/etc/ssl/certs", // Debian/Ubuntu/Gentoo/Alpine, SUSE "/etc/pki/tls/certs", // Fedora/RHEL - }) |dir| { - if (loadHashedDirectory(store, dir)) { - break :blk; - } + }) |dir_path| { + count += try loadFromDirectory(allocator, store, cwd, dir_path); + if (count > 0) break :blk; } // Iterate over known files. @@ -760,11 +781,10 @@ pub fn createX509Store(allocator: Allocator) CreateX509StoreError!*crypto.X509_S "/etc/ssl/cert.pem", // Alpine, *BSD }) |file| { if (crypto.X509_STORE_load_locations(store, file, null) == 1) { + count += 1; break :blk; } } - - log.warn(.app, "No system certificates", .{}); }, else => { // Prefer stdlib's cert scanner. @@ -773,10 +793,6 @@ pub fn createX509Store(allocator: Allocator) CreateX509StoreError!*crypto.X509_S defer bundle.deinit(allocator); const bytes = bundle.bytes.items; - if (bytes.len == 0) { - log.warn(.app, "No system certificates", .{}); - return store; - } var it = bundle.map.valueIterator(); while (it.next()) |index| { // d2i_X509 reads the cert's own DER length header to find its end and @@ -792,6 +808,7 @@ pub fn createX509Store(allocator: Allocator) CreateX509StoreError!*crypto.X509_S if (result != 1) { log.warn(.app, "Failed to add X509 cert to store", .{}); } + count += 1; } }, } @@ -799,19 +816,27 @@ pub fn createX509Store(allocator: Allocator) CreateX509StoreError!*crypto.X509_S return store; } -fn loadHashedDirectory(store: *crypto.X509_STORE, dir: [:0]const u8) bool { - var handle = std.Io.Dir.openDirAbsolute(lp.io, dir, .{ .iterate = true }) catch return false; - defer handle.close(lp.io); +/// Loads certificates from given `path`; returning how many CA loaded. +fn loadFromDirectory( + allocator: Allocator, + store: *crypto.X509_STORE, + cwd: std.Io.Dir, + dir_path: []const u8, +) Allocator.Error!usize { + var count: usize = 0; + var dir = cwd.openDir(lp.io, dir_path, .{ .iterate = true }) catch return count; + defer dir.close(lp.io); - var hashed = false; - var it = handle.iterate(); - while (it.next(lp.io) catch return false) |entry| { - if (std.mem.endsWith(u8, entry.name, ".0")) { - hashed = true; - break; + var it = dir.iterate(); + while (it.next(lp.io) catch return count) |entry| { + if (entry.kind != .file and entry.kind != .sym_link) continue; + + const path = try std.fs.path.joinZ(allocator, &.{ dir_path, entry.name }); + defer allocator.free(path); + + if (crypto.X509_STORE_load_locations(store, path, null) == 1) { + count += 1; } } - if (!hashed) return false; - - return crypto.X509_STORE_load_locations(store, null, dir.ptr) == 1; + return count; } diff --git a/src/sys/libcrypto.zig b/src/sys/libcrypto.zig index 024b475ac..76cfb383f 100644 --- a/src/sys/libcrypto.zig +++ b/src/sys/libcrypto.zig @@ -20,196 +20,13 @@ const std = @import("std"); -const pthread_rwlock_t = std.c.pthread_rwlock_t; - pub const struct_env_md_st = opaque {}; pub const EVP_MD = struct_env_md_st; -pub const evp_pkey_alg_st = opaque {}; -pub const EVP_PKEY_ALG = evp_pkey_alg_st; pub const struct_engine_st = opaque {}; pub const ENGINE = struct_engine_st; -pub const CRYPTO_THREADID = c_int; -pub const struct_asn1_null_st = opaque {}; -pub const ASN1_NULL = struct_asn1_null_st; -pub const ASN1_BOOLEAN = c_int; -pub const struct_ASN1_ITEM_st = opaque {}; -pub const ASN1_ITEM = struct_ASN1_ITEM_st; -pub const struct_asn1_object_st = opaque {}; -pub const ASN1_OBJECT = struct_asn1_object_st; -pub const struct_asn1_pctx_st = opaque {}; -pub const ASN1_PCTX = struct_asn1_pctx_st; -pub const struct_asn1_string_st = extern struct { - length: c_int, - type: c_int, - data: [*c]u8, - flags: c_long, -}; -pub const ASN1_BIT_STRING = struct_asn1_string_st; -pub const ASN1_BMPSTRING = struct_asn1_string_st; -pub const ASN1_ENUMERATED = struct_asn1_string_st; -pub const ASN1_GENERALIZEDTIME = struct_asn1_string_st; -pub const ASN1_GENERALSTRING = struct_asn1_string_st; -pub const ASN1_IA5STRING = struct_asn1_string_st; -pub const ASN1_INTEGER = struct_asn1_string_st; -pub const ASN1_OCTET_STRING = struct_asn1_string_st; -pub const ASN1_PRINTABLESTRING = struct_asn1_string_st; -pub const ASN1_STRING = struct_asn1_string_st; -pub const ASN1_T61STRING = struct_asn1_string_st; -pub const ASN1_TIME = struct_asn1_string_st; -pub const ASN1_UNIVERSALSTRING = struct_asn1_string_st; -pub const ASN1_UTCTIME = struct_asn1_string_st; -pub const ASN1_UTF8STRING = struct_asn1_string_st; -pub const ASN1_VISIBLESTRING = struct_asn1_string_st; -pub const struct_ASN1_VALUE_st = opaque {}; -pub const ASN1_VALUE = struct_ASN1_VALUE_st; -const union_unnamed_1 = extern union { - ptr: [*c]u8, - boolean: ASN1_BOOLEAN, - asn1_string: [*c]ASN1_STRING, - object: ?*ASN1_OBJECT, - integer: [*c]ASN1_INTEGER, - enumerated: [*c]ASN1_ENUMERATED, - bit_string: [*c]ASN1_BIT_STRING, - octet_string: [*c]ASN1_OCTET_STRING, - printablestring: [*c]ASN1_PRINTABLESTRING, - t61string: [*c]ASN1_T61STRING, - ia5string: [*c]ASN1_IA5STRING, - generalstring: [*c]ASN1_GENERALSTRING, - bmpstring: [*c]ASN1_BMPSTRING, - universalstring: [*c]ASN1_UNIVERSALSTRING, - utctime: [*c]ASN1_UTCTIME, - generalizedtime: [*c]ASN1_GENERALIZEDTIME, - visiblestring: [*c]ASN1_VISIBLESTRING, - utf8string: [*c]ASN1_UTF8STRING, - set: [*c]ASN1_STRING, - sequence: [*c]ASN1_STRING, - asn1_value: ?*ASN1_VALUE, -}; -pub const struct_asn1_type_st = extern struct { - type: c_int, - value: union_unnamed_1, -}; -pub const ASN1_TYPE = struct_asn1_type_st; -pub const struct_AUTHORITY_KEYID_st = opaque {}; -pub const AUTHORITY_KEYID = struct_AUTHORITY_KEYID_st; -pub const struct_BASIC_CONSTRAINTS_st = opaque {}; -pub const BASIC_CONSTRAINTS = struct_BASIC_CONSTRAINTS_st; -pub const struct_DIST_POINT_st = opaque {}; -pub const DIST_POINT = struct_DIST_POINT_st; -pub const BN_ULONG = u64; -pub const struct_bignum_st = extern struct { - d: [*c]BN_ULONG, - width: c_int, - dmax: c_int, - neg: c_int, - flags: c_int, -}; -pub const BIGNUM = struct_bignum_st; -pub const struct_DSA_SIG_st = extern struct { - r: [*c]BIGNUM, - s: [*c]BIGNUM, -}; -pub const DSA_SIG = struct_DSA_SIG_st; -pub const struct_ISSUING_DIST_POINT_st = opaque {}; -pub const ISSUING_DIST_POINT = struct_ISSUING_DIST_POINT_st; -pub const struct_NAME_CONSTRAINTS_st = opaque {}; -pub const NAME_CONSTRAINTS = struct_NAME_CONSTRAINTS_st; -pub const struct_X509_pubkey_st = opaque {}; -pub const X509_PUBKEY = struct_X509_pubkey_st; -pub const struct_Netscape_spkac_st = extern struct { - pubkey: ?*X509_PUBKEY, - challenge: [*c]ASN1_IA5STRING, -}; -pub const NETSCAPE_SPKAC = struct_Netscape_spkac_st; -pub const struct_X509_algor_st = extern struct { - algorithm: ?*ASN1_OBJECT, - parameter: [*c]ASN1_TYPE, -}; -pub const X509_ALGOR = struct_X509_algor_st; -pub const struct_Netscape_spki_st = extern struct { - spkac: [*c]NETSCAPE_SPKAC, - sig_algor: [*c]X509_ALGOR, - signature: [*c]ASN1_BIT_STRING, -}; -pub const NETSCAPE_SPKI = struct_Netscape_spki_st; -pub const struct_RIPEMD160state_st = opaque {}; -pub const RIPEMD160_CTX = struct_RIPEMD160state_st; -pub const struct_X509_VERIFY_PARAM_st = opaque {}; -pub const X509_VERIFY_PARAM = struct_X509_VERIFY_PARAM_st; -pub const struct_X509_crl_st = opaque {}; -pub const X509_CRL = struct_X509_crl_st; -pub const struct_X509_extension_st = opaque {}; -pub const X509_EXTENSION = struct_X509_extension_st; -pub const struct_x509_st = opaque {}; -pub const X509 = struct_x509_st; -pub const CRYPTO_refcount_t = u32; -pub const struct_openssl_method_common_st = extern struct { - references: c_int, - is_static: u8, -}; -pub const struct_rsa_meth_st = extern struct { - common: struct_openssl_method_common_st, - app_data: ?*anyopaque, - init: ?*const fn (?*RSA) callconv(.c) c_int, - finish: ?*const fn (?*RSA) callconv(.c) c_int, - size: ?*const fn (?*const RSA) callconv(.c) usize, - sign: ?*const fn (c_int, [*c]const u8, c_uint, [*c]u8, [*c]c_uint, ?*const RSA) callconv(.c) c_int, - sign_raw: ?*const fn (?*RSA, [*c]usize, [*c]u8, usize, [*c]const u8, usize, c_int) callconv(.c) c_int, - decrypt: ?*const fn (?*RSA, [*c]usize, [*c]u8, usize, [*c]const u8, usize, c_int) callconv(.c) c_int, - private_transform: ?*const fn (?*RSA, [*c]u8, [*c]const u8, usize) callconv(.c) c_int, - flags: c_int, -}; -pub const RSA_METHOD = struct_rsa_meth_st; -pub const struct_stack_st_void = opaque {}; -pub const struct_crypto_ex_data_st = extern struct { - sk: ?*struct_stack_st_void, -}; -pub const CRYPTO_EX_DATA = struct_crypto_ex_data_st; -pub const CRYPTO_MUTEX = pthread_rwlock_t; -pub const struct_bn_mont_ctx_st = extern struct { - RR: BIGNUM, - N: BIGNUM, - n0: [2]BN_ULONG, -}; -pub const BN_MONT_CTX = struct_bn_mont_ctx_st; -pub const struct_bn_blinding_st = opaque {}; -pub const BN_BLINDING = struct_bn_blinding_st; // boringssl/include/openssl/rsa.h:788:12: warning: struct demoted to opaque type - has bitfield -pub const struct_rsa_st = opaque {}; -pub const RSA = struct_rsa_st; -pub const struct_dsa_st = extern struct { - version: c_long, - p: [*c]BIGNUM, - q: [*c]BIGNUM, - g: [*c]BIGNUM, - pub_key: [*c]BIGNUM, - priv_key: [*c]BIGNUM, - flags: c_int, - method_mont_lock: CRYPTO_MUTEX, - method_mont_p: [*c]BN_MONT_CTX, - method_mont_q: [*c]BN_MONT_CTX, - references: CRYPTO_refcount_t, - ex_data: CRYPTO_EX_DATA, -}; -pub const DSA = struct_dsa_st; -pub const struct_dh_st = opaque {}; -pub const DH = struct_dh_st; pub const struct_ec_key_st = opaque {}; pub const EC_KEY = struct_ec_key_st; -const union_unnamed_2 = extern union { - ptr: ?*anyopaque, - rsa: ?*RSA, - dsa: [*c]DSA, - dh: ?*DH, - ec: ?*EC_KEY, -}; -pub const struct_evp_pkey_asn1_method_st = opaque {}; -pub const EVP_PKEY_ASN1_METHOD = struct_evp_pkey_asn1_method_st; -pub const struct_evp_pkey_st = extern struct { - references: CRYPTO_refcount_t, - type: c_int, - pkey: union_unnamed_2, - ameth: ?*const EVP_PKEY_ASN1_METHOD, -}; +pub const struct_evp_pkey_st = opaque {}; pub const EVP_PKEY = struct_evp_pkey_st; pub const struct_evp_pkey_ctx_st = opaque {}; pub const EVP_PKEY_CTX = struct_evp_pkey_ctx_st; @@ -264,7 +81,6 @@ pub extern fn HKDF( pub const X25519_PRIVATE_KEY_LEN = 32; pub const X25519_PUBLIC_VALUE_LEN = 32; -pub const X25519_SHARED_KEY_LEN = 32; pub extern fn X25519_keypair(out_public_value: *[32]u8, out_private_key: *[32]u8) void; @@ -326,13 +142,13 @@ pub const EVP_PKEY_X25519 = NID_X25519; pub const NID_ED25519 = 949; pub const EVP_PKEY_ED25519 = NID_ED25519; -pub extern fn EVP_PKEY_new_raw_private_key(@"type": c_int, unused: ?*ENGINE, in: [*c]const u8, len: usize) [*c]EVP_PKEY; -pub extern fn EVP_PKEY_new_raw_public_key(@"type": c_int, unused: ?*ENGINE, in: [*c]const u8, len: usize) [*c]EVP_PKEY; -pub extern fn EVP_PKEY_CTX_new(pkey: [*c]EVP_PKEY, e: ?*ENGINE) ?*EVP_PKEY_CTX; +pub extern fn EVP_PKEY_new_raw_private_key(@"type": c_int, unused: ?*ENGINE, in: [*c]const u8, len: usize) ?*EVP_PKEY; +pub extern fn EVP_PKEY_new_raw_public_key(@"type": c_int, unused: ?*ENGINE, in: [*c]const u8, len: usize) ?*EVP_PKEY; +pub extern fn EVP_PKEY_CTX_new(pkey: ?*EVP_PKEY, e: ?*ENGINE) ?*EVP_PKEY_CTX; pub extern fn EVP_PKEY_CTX_free(ctx: ?*EVP_PKEY_CTX) void; pub extern fn EVP_PKEY_derive_init(ctx: ?*EVP_PKEY_CTX) c_int; pub extern fn EVP_PKEY_derive(ctx: ?*EVP_PKEY_CTX, key: [*c]u8, out_key_len: [*c]usize) c_int; -pub extern fn EVP_PKEY_derive_set_peer(ctx: ?*EVP_PKEY_CTX, peer: [*c]EVP_PKEY) c_int; +pub extern fn EVP_PKEY_derive_set_peer(ctx: ?*EVP_PKEY_CTX, peer: ?*EVP_PKEY) c_int; pub extern fn EVP_PKEY_free(pkey: ?*EVP_PKEY) void; pub extern fn EVP_DigestSignInit(ctx: ?*EVP_MD_CTX, pctx: ?*?*EVP_PKEY_CTX, typ: ?*const EVP_MD, e: ?*ENGINE, pkey: ?*EVP_PKEY) c_int; @@ -343,6 +159,9 @@ pub extern fn EVP_MD_CTX_free(ctx: ?*EVP_MD_CTX) void; pub const struct_evp_md_ctx_st = opaque {}; pub const EVP_MD_CTX = struct_evp_md_ctx_st; +pub const struct_x509_st = opaque {}; +pub const X509 = struct_x509_st; + pub extern fn X509_free(x509: ?*X509) void; pub extern fn d2i_X509(out: [*c]?*X509, inp: *[*]const u8, len: c_long) ?*X509;