Merge pull request #2891 from lightpanda-io/nikneym/custom-cert-load

Support loading custom CAs (`--ca-cert`, `--ca-path`)
This commit is contained in:
Pierre Tachoire
2026-07-23 11:33:30 +02:00
committed by GitHub
6 changed files with 298 additions and 256 deletions

View File

@@ -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,

View File

@@ -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,

View File

@@ -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;
}
}

View File

@@ -305,6 +305,15 @@
\\ Block HTTP requests to private/internal IP addresses after DNS
\\ resolution.
\\ Defaults to false.
\\ --ca-cert <PATH>
\\ 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 <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>
\\ Path to a JSON file to load cookies from (read-only).
\\ Defaults to no cookie loading.

View File

@@ -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;
}

View File

@@ -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;