From d5e9ae23ef8147a9e59ea6d043794db4fe2721a8 Mon Sep 17 00:00:00 2001 From: Halil Durak Date: Sun, 11 Jan 2026 03:27:40 +0300 Subject: [PATCH 01/12] ground zero `SubtleCrypto` --- src/browser/js/bridge.zig | 1 + src/browser/webapi/Crypto.zig | 10 ++ src/browser/webapi/SubtleCrypto.zig | 207 ++++++++++++++++++++++++++++ src/crypto.zig | 23 ++++ 4 files changed, 241 insertions(+) create mode 100644 src/browser/webapi/SubtleCrypto.zig create mode 100644 src/crypto.zig 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/webapi/Crypto.zig b/src/browser/webapi/Crypto.zig index e8f987b5..a17ff2c3 100644 --- a/src/browser/webapi/Crypto.zig +++ b/src/browser/webapi/Crypto.zig @@ -19,6 +19,10 @@ 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, @@ -42,6 +46,11 @@ pub fn randomUUID(_: *const Crypto) ![36]u8 { return hex; } +pub fn getSubtle(self: *const Crypto, page: *Page) !*SubtleCrypto { + _ = self; + return page._factory.create(SubtleCrypto{}); +} + const RandomValues = union(enum) { int8: []i8, uint8: []u8, @@ -78,6 +87,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..7fa6f594 --- /dev/null +++ b/src/browser/webapi/SubtleCrypto.zig @@ -0,0 +1,207 @@ +// 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 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, + +/// NOTE: I think we can use extern union and cast this to intended algorithm +/// by `name` field. Not sure if it'd make difference memory/performance wise. +const Algorithm = union(enum) { + rsa_hashed_key_gen: RSA, + hmac_key_gen: HMAC, + + /// https://developer.mozilla.org/en-US/docs/Web/API/RsaHashedKeyGenParams + const RSA = 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: []const u8, + }; + + /// https://developer.mozilla.org/en-US/docs/Web/API/HmacKeyGenParams + const HMAC = struct { + name: []const u8, + /// Its also possible to pass this in an object. + hash: union(enum) { + str: []const u8, + obj: struct { name: []const u8 }, + }, + /// If omitted, default is the block size of the chosen hash function. + length: ?usize, + }; +}; + +/// Returns the desired digest by its name. +fn getDigest(name: []const u8) error{Invalid}!*const crypto.EVP_MD { + const digest = std.meta.stringToEnum(enum { + @"SHA-1", + @"SHA-256", + @"SHA-384", + @"SHA-512", + }, name) orelse return error.Invalid; + + return switch (digest) { + .@"SHA-1" => crypto.EVP_sha1(), + .@"SHA-256" => crypto.EVP_sha256(), + .@"SHA-384" => crypto.EVP_sha384(), + .@"SHA-512" => crypto.EVP_sha512(), + }; +} + +pub const CryptoKey = struct { + _algorithm: Algorithm, + /// Whether the key is extractable. + _extractable: bool, + /// Bit flags of `usages`. + _usages: u8, + /// Algorithm specific data. + _internal: union { + hmac: []u8, + }, + + 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, + ) !*CryptoKey { + // TODO. + _ = key_usages; + return switch (algorithm) { + .hmac_key_gen => |hmac| try initHMAC(hmac, extractable, page), + else => @panic("NYI"), + }; + } + + fn initHMAC(algorithm: Algorithm.HMAC, extractable: bool, page: *Page) !*CryptoKey { + const hash = switch (algorithm.hash) { + .str => |str| str, + .obj => |obj| obj.name, + }; + // Find digest. + const digest = try getDigest(hash); + + 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); + + return page._factory.create(CryptoKey{ + ._algorithm = .{ + .hmac_key_gen = .{ + .name = "HMAC", + .hash = .{ .obj = .{ .name = hash } }, + .length = block_size, + }, + }, + ._extractable = extractable, + ._usages = 0, + ._internal = .{ .hmac = key }, + }); + } + + 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(); + }; + }; +}; + +/// 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, +) !*CryptoKey { + return CryptoKey.init(algorithm, extractable, key_usages, page); +} + +/// Generate a digital signature. +pub fn sign( + _: *const SubtleCrypto, + algorithm: []const u8, + key: *const CryptoKey, + data: []const u8, +) void { + _ = algorithm; + _ = key; + _ = data; +} + +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, .{}); + pub const sign = bridge.function(SubtleCrypto.sign, .{}); +}; diff --git a/src/crypto.zig b/src/crypto.zig new file mode 100644 index 00000000..7b0e57ed --- /dev/null +++ b/src/crypto.zig @@ -0,0 +1,23 @@ +//! libcrypto utilities we use throughout browser. + +pub const struct_env_md_st = opaque {}; +pub const EVP_MD = struct_env_md_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 extern fn EVP_MD_block_size(md: ?*const EVP_MD) usize; + +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; From 9945a5f9ccb1cd5b2964bcbc4258664d6d73951c Mon Sep 17 00:00:00 2001 From: Halil Durak Date: Sun, 11 Jan 2026 23:43:12 +0300 Subject: [PATCH 02/12] implement `sign` and `verify` for HMAC --- src/browser/webapi/SubtleCrypto.zig | 167 ++++++++++++++++++++++------ src/crypto.zig | 5 + 2 files changed, 137 insertions(+), 35 deletions(-) diff --git a/src/browser/webapi/SubtleCrypto.zig b/src/browser/webapi/SubtleCrypto.zig index 7fa6f594..a38f83fc 100644 --- a/src/browser/webapi/SubtleCrypto.zig +++ b/src/browser/webapi/SubtleCrypto.zig @@ -35,35 +35,41 @@ const SubtleCrypto = @This(); /// Don't optimize away the type. _pad: bool = false, -/// NOTE: I think we can use extern union and cast this to intended algorithm -/// by `name` field. Not sure if it'd make difference memory/performance wise. -const Algorithm = union(enum) { - rsa_hashed_key_gen: RSA, - hmac_key_gen: HMAC, - +const Params = struct { /// https://developer.mozilla.org/en-US/docs/Web/API/RsaHashedKeyGenParams - const RSA = struct { + 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: []const u8, + hash: union(enum) { + string: []const u8, + object: struct { name: []const u8 }, + }, }; /// https://developer.mozilla.org/en-US/docs/Web/API/HmacKeyGenParams - const HMAC = struct { + const HmacKeyGen = struct { + /// Always HMAC. name: []const u8, /// Its also possible to pass this in an object. hash: union(enum) { - str: []const u8, - obj: struct { name: []const u8 }, + string: []const u8, + object: struct { name: []const u8 }, }, /// If omitted, default is the block size of the chosen hash function. length: ?usize, }; }; +/// NOTE: I think we can use extern union and cast this to intended algorithm +/// by `name` field. Not sure if it'd make difference memory/performance wise. +const Algorithm = union(enum) { + rsa_hashed_key_gen: Params.RsaHashedKeyGen, + hmac_key_gen: Params.HmacKeyGen, +}; + /// Returns the desired digest by its name. fn getDigest(name: []const u8) error{Invalid}!*const crypto.EVP_MD { const digest = std.meta.stringToEnum(enum { @@ -81,16 +87,19 @@ fn getDigest(name: []const u8) error{Invalid}!*const crypto.EVP_MD { }; } +/// Represents a cryptographic key obtained from one of the SubtleCrypto methods +/// generateKey(), deriveKey(), importKey(), or unwrapKey(). pub const CryptoKey = struct { - _algorithm: Algorithm, + /// Algorithm being used. + _type: Type, /// Whether the key is extractable. _extractable: bool, - /// Bit flags of `usages`. + /// Bit flags of `usages`; see `Usages` type. _usages: u8, - /// Algorithm specific data. - _internal: union { - hmac: []u8, - }, + _key: []const u8, + _digest: *const crypto.EVP_MD, + + pub const Type = enum(u8) { hmac, rsa }; pub const Usages = struct { // zig fmt: off @@ -119,10 +128,10 @@ pub const CryptoKey = struct { }; } - fn initHMAC(algorithm: Algorithm.HMAC, extractable: bool, page: *Page) !*CryptoKey { + fn initHMAC(algorithm: Params.HmacKeyGen, extractable: bool, page: *Page) !*CryptoKey { const hash = switch (algorithm.hash) { - .str => |str| str, - .obj => |obj| obj.name, + .string => |str| str, + .object => |obj| obj.name, }; // Find digest. const digest = try getDigest(hash); @@ -144,19 +153,64 @@ pub const CryptoKey = struct { std.debug.assert(res == 1); return page._factory.create(CryptoKey{ - ._algorithm = .{ - .hmac_key_gen = .{ - .name = "HMAC", - .hash = .{ .obj = .{ .name = hash } }, - .length = block_size, - }, - }, + ._type = .hmac, ._extractable = extractable, ._usages = 0, - ._internal = .{ .hmac = key }, + ._key = key, + ._digest = digest, }); } + fn signHMAC(self: *const CryptoKey, data: []const u8, page: *Page) !js.Promise { + const buffer = try page.arena.alloc(u8, crypto.EVP_MD_size(self._digest)); + errdefer page.arena.free(buffer); + var out_len: u32 = 0; + // Try to sign. + const signed = crypto.HMAC( + self._digest, + @ptrCast(self._key.ptr), + self._key.len, + data.ptr, + data.len, + buffer.ptr, + &out_len, + ); + + if (signed != null) { + return page.js.resolvePromise(js.ArrayBuffer{ .values = buffer[0..out_len] }); + } + + return error.Invalid; + } + + fn verifyHMAC( + self: *const CryptoKey, + signature: []const u8, + data: []const u8, + page: *Page, + ) !js.Promise { + var buffer: [crypto.EVP_MAX_MD_BLOCK_SIZE]u8 = undefined; + var out_len: u32 = 0; + // Try to sign. + const signed = crypto.HMAC( + self._digest, + @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); + } + pub const JsApi = struct { pub const bridge = js.Bridge(CryptoKey); @@ -180,16 +234,58 @@ pub fn generateKey( return CryptoKey.init(algorithm, extractable, key_usages, page); } +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, - algorithm: []const u8, + /// 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 { + // Verify algorithm. + if (!algorithm.isHMAC()) return error.InvalidAccess; + + return switch (key._type) { + .hmac => key.signHMAC(data, page), + else => return error.InvalidAccess, + }; +} + +/// Verify a digital signature. +pub fn verify( + _: *const SubtleCrypto, + algorithm: SignatureAlgorithm, key: *const CryptoKey, - data: []const u8, -) void { - _ = algorithm; - _ = key; - _ = data; + signature: []const u8, // ArrayBuffer. + data: []const u8, // ArrayBuffer. + page: *Page, +) !js.Promise { + if (!algorithm.isHMAC()) return error.InvalidAccess; + + return switch (key._type) { + .hmac => key.verifyHMAC(signature, data, page), + else => return error.InvalidAccess, + }; } pub const JsApi = struct { @@ -203,5 +299,6 @@ pub const JsApi = struct { }; pub const generateKey = bridge.function(SubtleCrypto.generateKey, .{}); - pub const sign = bridge.function(SubtleCrypto.sign, .{}); + pub const sign = bridge.function(SubtleCrypto.sign, .{ .dom_exception = true, .as_typed_array = false }); + pub const verify = bridge.function(SubtleCrypto.verify, .{}); }; diff --git a/src/crypto.zig b/src/crypto.zig index 7b0e57ed..99762a82 100644 --- a/src/crypto.zig +++ b/src/crypto.zig @@ -10,8 +10,13 @@ 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, From fd26ae4b5be25c084c510bd488d4d8309d88b6ed Mon Sep 17 00:00:00 2001 From: Halil Durak Date: Mon, 12 Jan 2026 17:16:36 +0300 Subject: [PATCH 03/12] parse `keyUsages` properly --- src/browser/webapi/SubtleCrypto.zig | 101 +++++++++++++++++++++++----- 1 file changed, 86 insertions(+), 15 deletions(-) diff --git a/src/browser/webapi/SubtleCrypto.zig b/src/browser/webapi/SubtleCrypto.zig index a38f83fc..f69ce927 100644 --- a/src/browser/webapi/SubtleCrypto.zig +++ b/src/browser/webapi/SubtleCrypto.zig @@ -101,6 +101,7 @@ pub const CryptoKey = struct { pub const Type = enum(u8) { hmac, rsa }; + /// Changing the names of fields would affect bitmask creation. pub const Usages = struct { // zig fmt: off pub const encrypt = 0x001; @@ -121,20 +122,52 @@ pub const CryptoKey = struct { page: *Page, ) !*CryptoKey { // TODO. - _ = key_usages; return switch (algorithm) { - .hmac_key_gen => |hmac| try initHMAC(hmac, extractable, page), + .hmac_key_gen => |hmac| initHMAC(hmac, extractable, key_usages, page), else => @panic("NYI"), }; } - fn initHMAC(algorithm: Params.HmacKeyGen, extractable: bool, page: *Page) !*CryptoKey { + /// Create a bitmask out of `key_usages`.- + fn createUsagesMask(usages: []const []const u8) !u8 { + const decls = @typeInfo(Usages).@"struct".decls; + var mask: u8 = 0; + iter_usages: for (usages) |usage| { + inline for (decls) |decl| { + if (std.mem.eql(u8, decl.name, usage)) { + mask |= @field(Usages, decl.name); + continue :iter_usages; + } + } + // Unknown usage if got here, report error. + return error.SyntaxError; + } + + return mask; + } + + 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; + } + + fn initHMAC( + algorithm: Params.HmacKeyGen, + extractable: bool, + key_usages: []const []const u8, + page: *Page, + ) !*CryptoKey { const hash = switch (algorithm.hash) { .string => |str| str, .object => |obj| obj.name, }; // Find digest. const digest = try getDigest(hash); + // Calculate usages mask and check if its correct. + const usages_mask = try createUsagesMask(key_usages); const block_size: usize = blk: { // Caller provides this in bits, not bytes. @@ -155,13 +188,17 @@ pub const CryptoKey = struct { return page._factory.create(CryptoKey{ ._type = .hmac, ._extractable = extractable, - ._usages = 0, + ._usages = usages_mask, ._key = key, ._digest = digest, }); } - fn signHMAC(self: *const CryptoKey, data: []const u8, page: *Page) !js.Promise { + fn signHMAC(self: *const CryptoKey, data: []const u8, page: *Page) !js.ArrayBuffer { + if (!self.canSign()) { + return error.InvalidAccessError; + } + const buffer = try page.arena.alloc(u8, crypto.EVP_MD_size(self._digest)); errdefer page.arena.free(buffer); var out_len: u32 = 0; @@ -177,9 +214,10 @@ pub const CryptoKey = struct { ); if (signed != null) { - return page.js.resolvePromise(js.ArrayBuffer{ .values = buffer[0..out_len] }); + return js.ArrayBuffer{ .values = buffer[0..out_len] }; } + // Not DOM exception, failed on our side. return error.Invalid; } @@ -189,6 +227,10 @@ pub const CryptoKey = struct { 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. @@ -230,8 +272,27 @@ pub fn generateKey( extractable: bool, key_usages: []const []const u8, page: *Page, -) !*CryptoKey { - return CryptoKey.init(algorithm, extractable, key_usages, page); +) !js.Promise { + const key = CryptoKey.init(algorithm, extractable, key_usages, page) catch |err| { + return page.js.rejectPromise(@errorName(err)); + }; + + return page.js.resolvePromise(key); +} + +/// 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 (std.mem.eql(u8, format, "raw")) { + return page.js.resolvePromise(js.ArrayBuffer{ .values = key._key }); + } + + return page.js.rejectPromise(@errorName(error.NotSupported)); } const SignatureAlgorithm = union(enum) { @@ -262,12 +323,21 @@ pub fn sign( data: []const u8, // ArrayBuffer. page: *Page, ) !js.Promise { - // Verify algorithm. - if (!algorithm.isHMAC()) return error.InvalidAccess; - return switch (key._type) { - .hmac => key.signHMAC(data, page), - else => return error.InvalidAccess, + .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 => return page.js.rejectPromise(@errorName(error.InvalidAccessError)), }; } @@ -298,7 +368,8 @@ pub const JsApi = struct { pub const prototype_chain = bridge.prototypeChain(); }; - pub const generateKey = bridge.function(SubtleCrypto.generateKey, .{}); + 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, .as_typed_array = false }); - pub const verify = bridge.function(SubtleCrypto.verify, .{}); + pub const verify = bridge.function(SubtleCrypto.verify, .{ .dom_exception = true }); }; From 7ae3e8cb47591c2767f9ecf2c659c2cd7f2ae94f Mon Sep 17 00:00:00 2001 From: Halil Durak Date: Tue, 13 Jan 2026 19:39:28 +0300 Subject: [PATCH 04/12] code cleanup, support keypairs, init support for `X25519` --- src/browser/tests/crypto.html | 48 +++++++++++++++++++++++++++++++++++ src/crypto.zig | 6 +++++ 2 files changed, 54 insertions(+) diff --git a/src/browser/tests/crypto.html b/src/browser/tests/crypto.html index 74c30f02..8f477535 100644 --- a/src/browser/tests/crypto.html +++ b/src/browser/tests/crypto.html @@ -47,6 +47,54 @@ // } + + + + + + + - - - From ea2fc76d3cf9ba9d13fe0016ef2328558da6326d Mon Sep 17 00:00:00 2001 From: Halil Durak Date: Thu, 15 Jan 2026 20:40:53 +0300 Subject: [PATCH 12/12] don't `@panic`! --- src/browser/webapi/SubtleCrypto.zig | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/browser/webapi/SubtleCrypto.zig b/src/browser/webapi/SubtleCrypto.zig index ea4209c5..8a8e733f 100644 --- a/src/browser/webapi/SubtleCrypto.zig +++ b/src/browser/webapi/SubtleCrypto.zig @@ -295,6 +295,7 @@ pub const CryptoKey = struct { 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| { @@ -303,9 +304,13 @@ pub const CryptoKey = struct { 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; }, - else => @panic("NYI"), }; }