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;