diff --git a/src/browser/js/bridge.zig b/src/browser/js/bridge.zig index 258e4396..53ca21b0 100644 --- a/src/browser/js/bridge.zig +++ b/src/browser/js/bridge.zig @@ -1261,4 +1261,5 @@ pub const JsApis = flattenTypes(&.{ @import("../webapi/navigation/NavigationActivation.zig"), @import("../webapi/canvas/CanvasRenderingContext2D.zig"), @import("../webapi/canvas/WebGLRenderingContext.zig"), + @import("../webapi/SubtleCrypto.zig"), }); diff --git a/src/browser/tests/crypto.html b/src/browser/tests/crypto.html index 74c30f02..14436491 100644 --- a/src/browser/tests/crypto.html +++ b/src/browser/tests/crypto.html @@ -54,3 +54,68 @@ const regex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; testing.expectEqual(true, regex.test(uuid)); --> + + + + + + diff --git a/src/browser/webapi/Crypto.zig b/src/browser/webapi/Crypto.zig index e8f987b5..cd482c2c 100644 --- a/src/browser/webapi/Crypto.zig +++ b/src/browser/webapi/Crypto.zig @@ -19,8 +19,12 @@ const std = @import("std"); const js = @import("../js/js.zig"); +const Page = @import("../Page.zig"); + +const SubtleCrypto = @import("SubtleCrypto.zig"); + const Crypto = @This(); -_pad: bool = false, +_subtle: SubtleCrypto = .{}, pub const init: Crypto = .{}; @@ -42,6 +46,10 @@ pub fn randomUUID(_: *const Crypto) ![36]u8 { return hex; } +pub fn getSubtle(self: *Crypto) *SubtleCrypto { + return &self._subtle; +} + const RandomValues = union(enum) { int8: []i8, uint8: []u8, @@ -78,6 +86,7 @@ pub const JsApi = struct { pub const getRandomValues = bridge.function(Crypto.getRandomValues, .{}); pub const randomUUID = bridge.function(Crypto.randomUUID, .{}); + pub const subtle = bridge.accessor(Crypto.getSubtle, null, .{}); }; const testing = @import("../../testing.zig"); diff --git a/src/browser/webapi/SubtleCrypto.zig b/src/browser/webapi/SubtleCrypto.zig new file mode 100644 index 00000000..8a8e733f --- /dev/null +++ b/src/browser/webapi/SubtleCrypto.zig @@ -0,0 +1,637 @@ +// Copyright (C) 2023-2025 Lightpanda (Selecy SAS) +// +// Francis Bouvier +// Pierre Tachoire +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +const std = @import("std"); +const log = @import("../../log.zig"); + +const crypto = @import("../../crypto.zig"); + +const Page = @import("../Page.zig"); +const js = @import("../js/js.zig"); + +pub fn registerTypes() []const type { + return &.{ SubtleCrypto, CryptoKey }; +} + +/// The SubtleCrypto interface of the Web Crypto API provides a number of low-level +/// cryptographic functions. +/// https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto +/// https://w3c.github.io/webcrypto/#subtlecrypto-interface +const SubtleCrypto = @This(); +/// Don't optimize away the type. +_pad: bool = false, + +const Algorithm = union(enum) { + /// For RSASSA-PKCS1-v1_5, RSA-PSS, or RSA-OAEP: pass an RsaHashedKeyGenParams object. + rsa_hashed_key_gen: RsaHashedKeyGen, + /// For HMAC: pass an HmacKeyGenParams object. + hmac_key_gen: HmacKeyGen, + /// Can be Ed25519 or X25519. + name: []const u8, + /// Can be Ed25519 or X25519. + object: struct { name: []const u8 }, + + /// https://developer.mozilla.org/en-US/docs/Web/API/RsaHashedKeyGenParams + const RsaHashedKeyGen = struct { + name: []const u8, + /// This should be at least 2048. + /// Some organizations are now recommending that it should be 4096. + modulusLength: u32, + publicExponent: js.TypedArray(u8), + hash: union(enum) { + string: []const u8, + object: struct { name: []const u8 }, + }, + }; + + /// https://developer.mozilla.org/en-US/docs/Web/API/HmacKeyGenParams + const HmacKeyGen = struct { + /// Always HMAC. + name: []const u8, + /// Its also possible to pass this in an object. + hash: union(enum) { + string: []const u8, + object: struct { name: []const u8 }, + }, + /// If omitted, default is the block size of the chosen hash function. + length: ?usize, + }; + /// Alias. + const HmacImport = HmacKeyGen; + + const EcdhKeyDeriveParams = struct { + /// Can be Ed25519 or X25519. + name: []const u8, + public: *const CryptoKey, + }; + + /// Algorithm for deriveBits() and deriveKey(). + const DeriveBits = union(enum) { + ecdh_or_x25519: EcdhKeyDeriveParams, + }; +}; + +/// Generate a new key (for symmetric algorithms) or key pair (for public-key algorithms). +pub fn generateKey( + _: *const SubtleCrypto, + algorithm: Algorithm, + extractable: bool, + key_usages: []const []const u8, + page: *Page, +) !js.Promise { + const key_or_pair = CryptoKey.init(algorithm, extractable, key_usages, page) catch |err| { + return page.js.rejectPromise(@errorName(err)); + }; + + return page.js.resolvePromise(key_or_pair); +} + +/// Exports a key: that is, it takes as input a CryptoKey object and gives you +/// the key in an external, portable format. +pub fn exportKey( + _: *const SubtleCrypto, + format: []const u8, + key: *CryptoKey, + page: *Page, +) !js.Promise { + if (!key.canExportKey()) { + return error.InvalidAccessError; + } + + if (std.mem.eql(u8, format, "raw")) { + return page.js.resolvePromise(js.ArrayBuffer{ .values = key._key }); + } + + const is_unsupported = std.mem.eql(u8, format, "pkcs8") or + std.mem.eql(u8, format, "spki") or std.mem.eql(u8, format, "jwk"); + + if (is_unsupported) { + log.warn(.not_implemented, "SubtleCrypto.exportKey", .{ .format = format }); + } + + return page.js.rejectPromise(@errorName(error.NotSupported)); +} + +/// Derive a secret key from a master key. +pub fn deriveBits( + _: *const SubtleCrypto, + algorithm: Algorithm.DeriveBits, + base_key: *const CryptoKey, // Private key. + length: usize, + page: *Page, +) !js.Promise { + return switch (algorithm) { + .ecdh_or_x25519 => |p| { + const name = p.name; + if (std.mem.eql(u8, name, "X25519")) { + return page.js.resolvePromise(base_key.deriveBitsX25519(p.public, length, page)); + } + + if (std.mem.eql(u8, name, "ECDH")) { + log.warn(.not_implemented, "SubtleCrypto.deriveBits", .{ .name = name }); + } + + return page.js.rejectPromise(@errorName(error.NotSupported)); + }, + }; +} + +const SignatureAlgorithm = union(enum) { + string: []const u8, + object: struct { name: []const u8 }, + + pub fn isHMAC(self: SignatureAlgorithm) bool { + const name = switch (self) { + .string => |string| string, + .object => |object| object.name, + }; + + if (name.len < 4) return false; + const hmac: u32 = @bitCast([4]u8{ 'H', 'M', 'A', 'C' }); + return @as(u32, @bitCast(name[0..4].*)) == hmac; + } +}; + +/// Generate a digital signature. +pub fn sign( + _: *const SubtleCrypto, + /// This can either be provided as string or object. + /// We can't use the `Algorithm` type defined before though since there + /// are couple of changes between the two. + /// https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/sign#algorithm + algorithm: SignatureAlgorithm, + key: *CryptoKey, + data: []const u8, // ArrayBuffer. + page: *Page, +) !js.Promise { + return switch (key._type) { + .hmac => { + // Verify algorithm. + if (!algorithm.isHMAC()) { + return page.js.rejectPromise(@errorName(error.InvalidAccessError)); + } + + // Call sign for HMAC. + const result = key.signHMAC(data, page) catch |err| { + return page.js.rejectPromise(@errorName(err)); + }; + + return page.js.resolvePromise(result); + }, + else => { + log.warn(.not_implemented, "SubtleCrypto.sign", .{ .key_type = key._type }); + return page.js.rejectPromise(@errorName(error.InvalidAccessError)); + }, + }; +} + +/// Verify a digital signature. +pub fn verify( + _: *const SubtleCrypto, + algorithm: SignatureAlgorithm, + key: *const CryptoKey, + signature: []const u8, // ArrayBuffer. + data: []const u8, // ArrayBuffer. + page: *Page, +) !js.Promise { + if (!algorithm.isHMAC()) return error.InvalidAccessError; + + return switch (key._type) { + .hmac => key.verifyHMAC(signature, data, page), + else => return error.InvalidAccessError, + }; +} + +/// Returns the desired digest by its name. +fn findDigest(name: []const u8) error{Invalid}!*const crypto.EVP_MD { + if (std.mem.eql(u8, "SHA-256", name)) { + return crypto.EVP_sha256(); + } + + if (std.mem.eql(u8, "SHA-384", name)) { + return crypto.EVP_sha384(); + } + + if (std.mem.eql(u8, "SHA-512", name)) { + return crypto.EVP_sha512(); + } + + if (std.mem.eql(u8, "SHA-1", name)) { + return crypto.EVP_sha1(); + } + + return error.Invalid; +} + +const KeyOrPair = union(enum) { key: *CryptoKey, pair: CryptoKeyPair }; + +/// https://developer.mozilla.org/en-US/docs/Web/API/CryptoKeyPair +const CryptoKeyPair = struct { + privateKey: *CryptoKey, + publicKey: *CryptoKey, +}; + +/// Represents a cryptographic key obtained from one of the SubtleCrypto methods +/// generateKey(), deriveKey(), importKey(), or unwrapKey(). +pub const CryptoKey = struct { + /// Algorithm being used. + _type: Type, + /// Whether the key is extractable. + _extractable: bool, + /// Bit flags of `usages`; see `Usages` type. + _usages: u8, + /// Raw bytes of key. + _key: []const u8, + /// Different algorithms may use different data structures; + /// this union can be used for such situations. Active field is understood + /// from `_type`. + _vary: extern union { + /// Used by HMAC. + digest: *const crypto.EVP_MD, + /// Used by asymmetric algorithms (X25519, Ed25519). + pkey: *crypto.EVP_PKEY, + }, + + pub const Type = enum(u8) { hmac, rsa, x25519 }; + + /// Changing the names of fields would affect bitmask creation. + pub const Usages = struct { + // zig fmt: off + pub const encrypt = 0x001; + pub const decrypt = 0x002; + pub const sign = 0x004; + pub const verify = 0x008; + pub const deriveKey = 0x010; + pub const deriveBits = 0x020; + pub const wrapKey = 0x040; + pub const unwrapKey = 0x080; + // zig fmt: on + }; + + pub fn init( + algorithm: Algorithm, + extractable: bool, + key_usages: []const []const u8, + page: *Page, + ) !KeyOrPair { + return switch (algorithm) { + .hmac_key_gen => |hmac| initHMAC(hmac, extractable, key_usages, page), + .name => |name| { + if (std.mem.eql(u8, "X25519", name)) { + return initX25519(extractable, key_usages, page); + } + log.warn(.not_implemented, "CryptoKey.init", .{ .name = name }); + return error.NotSupported; + }, + .object => |object| { + // Ditto. + const name = object.name; + if (std.mem.eql(u8, "X25519", name)) { + return initX25519(extractable, key_usages, page); + } + log.warn(.not_implemented, "CryptoKey.init", .{ .name = name }); + return error.NotSupported; + }, + else => { + log.warn(.not_implemented, "CryptoKey.init", .{ .algorithm = algorithm }); + return error.NotSupported; + }, + }; + } + + inline fn canSign(self: *const CryptoKey) bool { + return self._usages & Usages.sign != 0; + } + + inline fn canVerify(self: *const CryptoKey) bool { + return self._usages & Usages.verify != 0; + } + + inline fn canDeriveBits(self: *const CryptoKey) bool { + return self._usages & Usages.deriveBits != 0; + } + + inline fn canExportKey(self: *const CryptoKey) bool { + return self._extractable; + } + + /// Only valid for HMAC. + inline fn getDigest(self: *const CryptoKey) *const crypto.EVP_MD { + return self._vary.digest; + } + + /// Only valid for asymmetric algorithms (X25519, Ed25519). + inline fn getKeyObject(self: *const CryptoKey) *crypto.EVP_PKEY { + return self._vary.pkey; + } + + // HMAC. + + fn initHMAC( + algorithm: Algorithm.HmacKeyGen, + extractable: bool, + key_usages: []const []const u8, + page: *Page, + ) !KeyOrPair { + const hash = switch (algorithm.hash) { + .string => |str| str, + .object => |obj| obj.name, + }; + // Find digest. + const digest = try findDigest(hash); + + // We need at least a single usage. + if (key_usages.len == 0) { + return error.SyntaxError; + } + // Calculate usages mask. + const decls = @typeInfo(Usages).@"struct".decls; + var usages_mask: u8 = 0; + iter_usages: for (key_usages) |usage| { + inline for (decls) |decl| { + if (std.mem.eql(u8, decl.name, usage)) { + usages_mask |= @field(Usages, decl.name); + continue :iter_usages; + } + } + // Unknown usage if got here. + return error.SyntaxError; + } + + const block_size: usize = blk: { + // Caller provides this in bits, not bytes. + if (algorithm.length) |length| { + break :blk length / 8; + } + // Prefer block size of the hash function instead. + break :blk crypto.EVP_MD_block_size(digest); + }; + + const key = try page.arena.alloc(u8, block_size); + errdefer page.arena.free(key); + + // HMAC is simply CSPRNG. + const res = crypto.RAND_bytes(key.ptr, key.len); + std.debug.assert(res == 1); + + const crypto_key = try page._factory.create(CryptoKey{ + ._type = .hmac, + ._extractable = extractable, + ._usages = usages_mask, + ._key = key, + ._vary = .{ .digest = digest }, + }); + + return .{ .key = crypto_key }; + } + + fn signHMAC(self: *const CryptoKey, data: []const u8, page: *Page) !js.ArrayBuffer { + if (!self.canSign()) { + return error.InvalidAccessError; + } + + const buffer = try page.call_arena.alloc(u8, crypto.EVP_MD_size(self.getDigest())); + errdefer page.call_arena.free(buffer); + var out_len: u32 = 0; + // Try to sign. + const signed = crypto.HMAC( + self.getDigest(), + @ptrCast(self._key.ptr), + self._key.len, + data.ptr, + data.len, + buffer.ptr, + &out_len, + ); + + if (signed != null) { + return js.ArrayBuffer{ .values = buffer[0..out_len] }; + } + + // Not DOM exception, failed on our side. + return error.Invalid; + } + + fn verifyHMAC( + self: *const CryptoKey, + signature: []const u8, + data: []const u8, + page: *Page, + ) !js.Promise { + if (!self.canVerify()) { + return error.InvalidAccessError; + } + + var buffer: [crypto.EVP_MAX_MD_BLOCK_SIZE]u8 = undefined; + var out_len: u32 = 0; + // Try to sign. + const signed = crypto.HMAC( + self.getDigest(), + @ptrCast(self._key.ptr), + self._key.len, + data.ptr, + data.len, + &buffer, + &out_len, + ); + + if (signed != null) { + // CRYPTO_memcmp compare in constant time so prohibits time-based attacks. + const res = crypto.CRYPTO_memcmp(signed, @ptrCast(signature.ptr), signature.len); + return page.js.resolvePromise(res == 0); + } + + return page.js.resolvePromise(false); + } + + // X25519. + + /// Create a pair of X25519. + fn initX25519( + extractable: bool, + key_usages: []const []const u8, + page: *Page, + ) !KeyOrPair { + // This code has too many allocations here and there, might be nice to + // gather them together with a single alloc call. Not sure if factory + // pattern is suitable for it though. + + // Calculate usages; only matters for private key. + // Only deriveKey() and deriveBits() be used for X25519. + if (key_usages.len == 0) { + return error.SyntaxError; + } + var mask: u8 = 0; + iter_usages: for (key_usages) |usage| { + inline for ([_][]const u8{ "deriveKey", "deriveBits" }) |name| { + if (std.mem.eql(u8, name, usage)) { + mask |= @field(Usages, name); + continue :iter_usages; + } + } + // Unknown usage if got here. + return error.SyntaxError; + } + + const public_value = try page.arena.alloc(u8, crypto.X25519_PUBLIC_VALUE_LEN); + errdefer page.arena.free(public_value); + + const private_key = try page.arena.alloc(u8, crypto.X25519_PRIVATE_KEY_LEN); + errdefer page.arena.free(private_key); + + // There's no info about whether this can fail; so I assume it cannot. + crypto.X25519_keypair(@ptrCast(public_value), @ptrCast(private_key)); + + // Create EVP_PKEY for public key. + // Seems we can use `EVP_PKEY_from_raw_private_key` for this, Chrome + // prefer not to, yet BoringSSL added it and recommends instead of what + // we're doing currently. + const public_pkey = crypto.EVP_PKEY_new_raw_public_key( + crypto.EVP_PKEY_X25519, + null, + public_value.ptr, + public_value.len, + ); + if (public_pkey == null) { + return error.OutOfMemory; + } + + // Create EVP_PKEY for private key. + // Seems we can use `EVP_PKEY_from_raw_private_key` for this, Chrome + // prefer not to, yet BoringSSL added it and recommends instead of what + // we're doing currently. + const private_pkey = crypto.EVP_PKEY_new_raw_private_key( + crypto.EVP_PKEY_X25519, + null, + private_key.ptr, + private_key.len, + ); + if (private_pkey == null) { + return error.OutOfMemory; + } + + const private = try page._factory.create(CryptoKey{ + ._type = .x25519, + ._extractable = extractable, + ._usages = mask, + ._key = private_key, + ._vary = .{ .pkey = private_pkey.? }, + }); + errdefer page._factory.destroy(private); + + const public = try page._factory.create(CryptoKey{ + ._type = .x25519, + // Public keys are always extractable. + ._extractable = true, + // Always empty for public key. + ._usages = 0, + ._key = public_value, + ._vary = .{ .pkey = public_pkey.? }, + }); + errdefer page._factory.destroy(public); + + return .{ .pair = .{ .privateKey = private, .publicKey = public } }; + } + + fn deriveBitsX25519( + private: *const CryptoKey, + public: *const CryptoKey, + length_in_bits: usize, + page: *Page, + ) !js.ArrayBuffer { + if (!private.canDeriveBits()) { + return error.InvalidAccessError; + } + + const maybe_ctx = crypto.EVP_PKEY_CTX_new(private.getKeyObject(), null); + if (maybe_ctx) |ctx| { + // Context is valid, free it on failure. + errdefer crypto.EVP_PKEY_CTX_free(ctx); + + // Init derive operation and set public key as peer. + if (crypto.EVP_PKEY_derive_init(ctx) != 1 or + crypto.EVP_PKEY_derive_set_peer(ctx, public.getKeyObject()) != 1) + { + // Failed on our end. + return error.Internal; + } + + const derived_key = try page.call_arena.alloc(u8, 32); + errdefer page.call_arena.free(derived_key); + + var out_key_len: usize = derived_key.len; + const result = crypto.EVP_PKEY_derive(ctx, derived_key.ptr, &out_key_len); + if (result != 1) { + // Failed on our end. + return error.Internal; + } + // Sanity check. + std.debug.assert(derived_key.len == out_key_len); + + // Length is in bits, convert to byte length. + const length = (length_in_bits / 8) + (7 + (length_in_bits % 8)) / 8; + // Truncate the slice to specified length. + // Same as `derived_key`. + const tailored = blk: { + if (length > derived_key.len) { + return error.LengthTooLong; + } + break :blk derived_key[0..length]; + }; + + // Zero any "unused bits" in the final byte. + const remainder_bits: u3 = @intCast(length_in_bits % 8); + if (remainder_bits != 0) { + tailored[tailored.len - 1] &= ~(@as(u8, 0xFF) >> remainder_bits); + } + + return js.ArrayBuffer{ .values = tailored }; + } + + // Failed on our end. + return error.Internal; + } + + pub const JsApi = struct { + pub const bridge = js.Bridge(CryptoKey); + + pub const Meta = struct { + pub const name = "CryptoKey"; + + pub var class_id: bridge.ClassId = undefined; + pub const prototype_chain = bridge.prototypeChain(); + }; + }; +}; + +pub const JsApi = struct { + pub const bridge = js.Bridge(SubtleCrypto); + + pub const Meta = struct { + pub const name = "SubtleCrypto"; + + pub var class_id: bridge.ClassId = undefined; + pub const prototype_chain = bridge.prototypeChain(); + }; + + pub const generateKey = bridge.function(SubtleCrypto.generateKey, .{ .dom_exception = true }); + pub const exportKey = bridge.function(SubtleCrypto.exportKey, .{ .dom_exception = true }); + pub const sign = bridge.function(SubtleCrypto.sign, .{ .dom_exception = true }); + pub const verify = bridge.function(SubtleCrypto.verify, .{ .dom_exception = true }); + pub const deriveBits = bridge.function(SubtleCrypto.deriveBits, .{ .dom_exception = true }); +}; diff --git a/src/crypto.zig b/src/crypto.zig new file mode 100644 index 00000000..012ca96e --- /dev/null +++ b/src/crypto.zig @@ -0,0 +1,238 @@ +//! libcrypto utilities we use throughout browser. + +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 EVP_PKEY = struct_evp_pkey_st; +pub const struct_evp_pkey_ctx_st = opaque {}; +pub const EVP_PKEY_CTX = struct_evp_pkey_ctx_st; + +pub extern fn RAND_bytes(buf: [*]u8, len: usize) c_int; + +pub extern fn EVP_sha1() *const EVP_MD; +pub extern fn EVP_sha256() *const EVP_MD; +pub extern fn EVP_sha384() *const EVP_MD; +pub extern fn EVP_sha512() *const EVP_MD; + +pub const EVP_MAX_MD_BLOCK_SIZE = 128; + +pub extern fn EVP_MD_size(md: ?*const EVP_MD) usize; +pub extern fn EVP_MD_block_size(md: ?*const EVP_MD) usize; + +pub extern fn CRYPTO_memcmp(a: ?*const anyopaque, b: ?*const anyopaque, len: usize) c_int; + +pub extern fn HMAC( + evp_md: *const EVP_MD, + key: *const anyopaque, + key_len: usize, + data: [*]const u8, + data_len: usize, + out: [*]u8, + out_len: *c_uint, +) ?[*]u8; + +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; + +pub const NID_X25519 = @as(c_int, 948); +pub const EVP_PKEY_X25519 = NID_X25519; + +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_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;