mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-07-31 09:46:05 -04:00
mem: Finalize more (non-dom) types
Adds finalizers to various types. The two most interesting are ImageData (which could have a large data field) and HTMLCollection which a page could create many. Various Crypto types are also finalized to make sure the key is freed. This is particularly important for freeing any keys created with EVP_PKEY_new.
This commit is contained in:
@@ -70,6 +70,19 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=structured-clone>
|
||||
{
|
||||
const img = new ImageData(2, 2);
|
||||
img.data[0] = 42;
|
||||
const clone = structuredClone(img);
|
||||
testing.expectEqual(2, clone.width);
|
||||
testing.expectEqual(2, clone.height);
|
||||
testing.expectEqual(42, clone.data[0]);
|
||||
clone.data[0] = 7;
|
||||
testing.expectEqual(42, img.data[0]);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=too-large>
|
||||
testing.expectError("IndexSizeError", () => new ImageData(2_147_483_648, 2_147_483_648));
|
||||
</script>
|
||||
|
||||
@@ -16,16 +16,22 @@
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
const crypto = @import("../../sys/libcrypto.zig");
|
||||
|
||||
const js = @import("../js/js.zig");
|
||||
const Page = @import("../Page.zig");
|
||||
|
||||
const Execution = js.Execution;
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
/// Represents a cryptographic key obtained from one of the SubtleCrypto methods
|
||||
/// generateKey(), deriveKey(), importKey(), or unwrapKey().
|
||||
const CryptoKey = @This();
|
||||
|
||||
_rc: lp.RC(u8) = .{},
|
||||
_arena: Allocator = undefined,
|
||||
/// Algorithm being used.
|
||||
_type: Type,
|
||||
/// Whether this is a secret (symmetric), public, or private key. Surfaced as
|
||||
@@ -37,8 +43,8 @@ _extractable: bool,
|
||||
_usages: u8,
|
||||
/// Raw bytes of key.
|
||||
_key: []const u8,
|
||||
/// Metadata needed to reconstruct the JS `.algorithm` dictionary. The strings
|
||||
/// are expected to outlive the key (arena-allocated alongside it).
|
||||
/// Metadata needed to reconstruct the JS `.algorithm` dictionary. `hash` is
|
||||
/// duped into the key's arena by init; `name`/`named_curve` are static.
|
||||
_algorithm: Algorithm,
|
||||
/// Different algorithms may use different data structures;
|
||||
/// this union can be used for such situations. Active field is understood
|
||||
@@ -94,6 +100,39 @@ pub const Usages = struct {
|
||||
// zig fmt: on
|
||||
};
|
||||
|
||||
/// Copies `key` into an owned pooled arena, duping the caller-provided
|
||||
/// slices (`_key`, `_algorithm.hash`). `_algorithm.name` and `_named_curve`
|
||||
/// are expected to be static strings. Takes ownership of `_vary.pkey`.
|
||||
pub fn init(exec: *const Execution, key: CryptoKey) !*CryptoKey {
|
||||
const arena = try exec.getArena(.tiny, "CryptoKey");
|
||||
errdefer exec.releaseArena(arena);
|
||||
|
||||
const self = try arena.create(CryptoKey);
|
||||
self.* = key;
|
||||
self._arena = arena;
|
||||
self._key = try arena.dupe(u8, key._key);
|
||||
if (key._algorithm.hash) |hash| {
|
||||
self._algorithm.hash = try arena.dupe(u8, hash);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *CryptoKey, page: *Page) void {
|
||||
switch (self._vary) {
|
||||
.pkey => |pkey| crypto.EVP_PKEY_free(pkey),
|
||||
.none, .digest => {},
|
||||
}
|
||||
page.releaseArena(self._arena);
|
||||
}
|
||||
|
||||
pub fn releaseRef(self: *CryptoKey, page: *Page) void {
|
||||
self._rc.release(self, page);
|
||||
}
|
||||
|
||||
pub fn acquireRef(self: *CryptoKey) void {
|
||||
self._rc.acquire();
|
||||
}
|
||||
|
||||
pub fn canEncrypt(self: *const CryptoKey) bool {
|
||||
return self._usages & Usages.encrypt != 0;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,10 @@
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
const lp = @import("lightpanda");
|
||||
|
||||
const js = @import("../js/js.zig");
|
||||
const Page = @import("../Page.zig");
|
||||
const Frame = @import("../Frame.zig");
|
||||
|
||||
const Node = @import("Node.zig");
|
||||
@@ -25,6 +28,7 @@ pub const FilterOpts = NodeFilter.FilterOpts;
|
||||
|
||||
const DOMNodeIterator = @This();
|
||||
|
||||
_rc: lp.RC(u8) = .{},
|
||||
_root: *Node,
|
||||
_what_to_show: u32,
|
||||
_filter: NodeFilter,
|
||||
@@ -43,6 +47,19 @@ pub fn init(root: *Node, what_to_show: u32, filter: ?FilterOpts, frame: *Frame)
|
||||
});
|
||||
}
|
||||
|
||||
pub fn deinit(self: *DOMNodeIterator, page: *Page) void {
|
||||
self._filter.deinit();
|
||||
page.factory.destroy(self);
|
||||
}
|
||||
|
||||
pub fn releaseRef(self: *DOMNodeIterator, page: *Page) void {
|
||||
self._rc.release(self, page);
|
||||
}
|
||||
|
||||
pub fn acquireRef(self: *DOMNodeIterator) void {
|
||||
self._rc.acquire();
|
||||
}
|
||||
|
||||
pub fn getRoot(self: *const DOMNodeIterator) *Node {
|
||||
return self._root;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,10 @@
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
const lp = @import("lightpanda");
|
||||
|
||||
const js = @import("../js/js.zig");
|
||||
const Page = @import("../Page.zig");
|
||||
const Frame = @import("../Frame.zig");
|
||||
|
||||
const Node = @import("Node.zig");
|
||||
@@ -25,6 +28,7 @@ pub const FilterOpts = NodeFilter.FilterOpts;
|
||||
|
||||
const DOMTreeWalker = @This();
|
||||
|
||||
_rc: lp.RC(u8) = .{},
|
||||
_root: *Node,
|
||||
_what_to_show: u32,
|
||||
_filter: NodeFilter,
|
||||
@@ -40,6 +44,19 @@ pub fn init(root: *Node, what_to_show: u32, filter: ?FilterOpts, frame: *Frame)
|
||||
});
|
||||
}
|
||||
|
||||
pub fn deinit(self: *DOMTreeWalker, page: *Page) void {
|
||||
self._filter.deinit();
|
||||
page.factory.destroy(self);
|
||||
}
|
||||
|
||||
pub fn releaseRef(self: *DOMTreeWalker, page: *Page) void {
|
||||
self._rc.release(self, page);
|
||||
}
|
||||
|
||||
pub fn acquireRef(self: *DOMTreeWalker) void {
|
||||
self._rc.acquire();
|
||||
}
|
||||
|
||||
pub fn getRoot(self: *const DOMTreeWalker) *Node {
|
||||
return self._root;
|
||||
}
|
||||
|
||||
@@ -188,8 +188,8 @@ pub fn getEmbeds(self: *HTMLDocument, frame: *Frame) !collections.NodeLive(.tag)
|
||||
return collections.NodeLive(.tag).init(self.asNode(), .embed, frame);
|
||||
}
|
||||
|
||||
pub fn getApplets(_: *const HTMLDocument) collections.HTMLCollection {
|
||||
return .{ ._data = .empty };
|
||||
pub fn getApplets(_: *const HTMLDocument, frame: *Frame) !*collections.HTMLCollection {
|
||||
return frame._factory.create(collections.HTMLCollection{ ._data = .empty });
|
||||
}
|
||||
|
||||
pub fn getCurrentScript(self: *const HTMLDocument) ?*Element.Html.Script {
|
||||
|
||||
@@ -27,6 +27,8 @@ const Execution = js.Execution;
|
||||
|
||||
/// https://developer.mozilla.org/en-US/docs/Web/API/ImageData/ImageData
|
||||
const ImageData = @This();
|
||||
|
||||
_rc: lp.RC(u8) = .{},
|
||||
_width: u32,
|
||||
_height: u32,
|
||||
_data: js.ArrayBufferRef(.uint8_clamped).Global,
|
||||
@@ -74,9 +76,14 @@ pub fn init(
|
||||
}
|
||||
|
||||
var size, var overflown = @mulWithOverflow(width, height);
|
||||
if (overflown == 1) return error.IndexSizeError;
|
||||
if (overflown == 1) {
|
||||
return error.IndexSizeError;
|
||||
}
|
||||
|
||||
size, overflown = @mulWithOverflow(size, 4);
|
||||
if (overflown == 1) return error.IndexSizeError;
|
||||
if (overflown == 1) {
|
||||
return error.IndexSizeError;
|
||||
}
|
||||
|
||||
return exec._factory.create(ImageData{
|
||||
._width = width,
|
||||
@@ -85,6 +92,19 @@ pub fn init(
|
||||
});
|
||||
}
|
||||
|
||||
pub fn deinit(self: *ImageData, page: *Page) void {
|
||||
self._data.release();
|
||||
page.factory.destroy(self);
|
||||
}
|
||||
|
||||
pub fn releaseRef(self: *ImageData, page: *Page) void {
|
||||
self._rc.release(self, page);
|
||||
}
|
||||
|
||||
pub fn acquireRef(self: *ImageData) void {
|
||||
self._rc.acquire();
|
||||
}
|
||||
|
||||
pub fn structuredSerialize(self: *const ImageData, writer: *js.StructuredWriter) !void {
|
||||
writer.writeUint32(self._width);
|
||||
writer.writeUint32(self._height);
|
||||
|
||||
@@ -44,6 +44,12 @@ pub fn init(opts_: ?FilterOpts) !NodeFilter {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *const NodeFilter) void {
|
||||
if (self._func) |func| {
|
||||
func.release();
|
||||
}
|
||||
}
|
||||
|
||||
// Constants
|
||||
pub const FILTER_ACCEPT: i32 = 1;
|
||||
pub const FILTER_REJECT: i32 = 2;
|
||||
|
||||
@@ -205,13 +205,12 @@ pub fn importKey(
|
||||
const mask = common.usageMask(&.{ "deriveKey", "deriveBits" }, key_usages) catch |err| {
|
||||
return local.rejectPromise(.{ .dom_exception = .{ .err = err } });
|
||||
};
|
||||
const key = try exec.arena.dupe(u8, raw);
|
||||
const crypto_key = try exec._factory.create(CryptoKey{
|
||||
const crypto_key = try CryptoKey.init(exec, .{
|
||||
._type = .derive,
|
||||
._kind = .secret,
|
||||
._extractable = extractable,
|
||||
._usages = mask,
|
||||
._key = key,
|
||||
._key = raw,
|
||||
._algorithm = .{ .name = derive_name },
|
||||
});
|
||||
return local.resolvePromise(crypto_key);
|
||||
|
||||
@@ -17,7 +17,10 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
|
||||
const js = @import("../../js/js.zig");
|
||||
const Page = @import("../../Page.zig");
|
||||
const Frame = @import("../../Frame.zig");
|
||||
const Element = @import("../Element.zig");
|
||||
const TreeWalker = @import("../TreeWalker.zig");
|
||||
@@ -55,6 +58,19 @@ _data: union(Mode) {
|
||||
form: NodeLive(.form),
|
||||
empty: void,
|
||||
},
|
||||
_rc: lp.RC(u8) = .{},
|
||||
|
||||
pub fn deinit(self: *HTMLCollection, page: *Page) void {
|
||||
page.factory.destroy(self);
|
||||
}
|
||||
|
||||
pub fn releaseRef(self: *HTMLCollection, page: *Page) void {
|
||||
self._rc.release(self, page);
|
||||
}
|
||||
|
||||
pub fn acquireRef(self: *HTMLCollection) void {
|
||||
self._rc.acquire();
|
||||
}
|
||||
|
||||
pub fn length(self: *HTMLCollection, frame: *const Frame) u32 {
|
||||
return switch (self._data) {
|
||||
@@ -119,6 +135,14 @@ pub const Iterator = GenericIterator(struct {
|
||||
empty: void,
|
||||
},
|
||||
|
||||
pub fn acquireRef(self: *@This()) void {
|
||||
self.list.acquireRef();
|
||||
}
|
||||
|
||||
pub fn releaseRef(self: *@This(), page: *Page) void {
|
||||
self.list.releaseRef(page);
|
||||
}
|
||||
|
||||
pub fn next(self: *@This(), _: *const Execution) ?*Element {
|
||||
return switch (self.list._data) {
|
||||
.tag => |*impl| impl.nextTw(&self.tw.tag),
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
const std = @import("std");
|
||||
const js = @import("../../js/js.zig");
|
||||
const Page = @import("../../Page.zig");
|
||||
const Frame = @import("../../Frame.zig");
|
||||
const Element = @import("../Element.zig");
|
||||
|
||||
@@ -31,10 +32,22 @@ const HTMLFormControlsCollection = @This();
|
||||
|
||||
_proto: *HTMLCollection,
|
||||
|
||||
pub const NamedItemResult = union(enum) {
|
||||
element: *Element,
|
||||
radio_node_list: *RadioNodeList,
|
||||
};
|
||||
// The refcount lives on the proto, but anchoring the finalizer here lets
|
||||
// deinit reclaim this struct's slot along with the proto's.
|
||||
pub fn deinit(self: *HTMLFormControlsCollection, page: *Page) void {
|
||||
self._proto.deinit(page);
|
||||
// Not destroy(): the proto is a separate slab allocation, not a
|
||||
// contiguous factory chain.
|
||||
page.factory.destroyStandalone(self);
|
||||
}
|
||||
|
||||
pub fn acquireRef(self: *HTMLFormControlsCollection) void {
|
||||
self._proto.acquireRef();
|
||||
}
|
||||
|
||||
pub fn releaseRef(self: *HTMLFormControlsCollection, page: *Page) void {
|
||||
self._proto._rc.release(self, page);
|
||||
}
|
||||
|
||||
pub fn length(self: *HTMLFormControlsCollection, frame: *Frame) u32 {
|
||||
return self._proto.length(frame);
|
||||
@@ -44,6 +57,11 @@ pub fn getAtIndex(self: *HTMLFormControlsCollection, index: usize, frame: *Frame
|
||||
return self._proto.getAtIndex(index, frame);
|
||||
}
|
||||
|
||||
pub const NamedItemResult = union(enum) {
|
||||
element: *Element,
|
||||
radio_node_list: *RadioNodeList,
|
||||
};
|
||||
|
||||
pub fn namedItem(self: *HTMLFormControlsCollection, name: []const u8, frame: *Frame) !?NamedItemResult {
|
||||
if (name.len == 0) {
|
||||
return null;
|
||||
@@ -87,6 +105,10 @@ pub fn namedItem(self: *HTMLFormControlsCollection, name: []const u8, frame: *Fr
|
||||
|
||||
radio_node_list._proto = try frame._factory.create(NodeList{ ._data = .{ .radio_node_list = radio_node_list } });
|
||||
|
||||
// The RadioNodeList outlives this call; its NodeList releases
|
||||
// the ref in deinit.
|
||||
self.acquireRef();
|
||||
|
||||
return .{ .radio_node_list = radio_node_list };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
const js = @import("../../js/js.zig");
|
||||
const Page = @import("../../Page.zig");
|
||||
const Frame = @import("../../Frame.zig");
|
||||
const Node = @import("../Node.zig");
|
||||
const Element = @import("../Element.zig");
|
||||
@@ -27,6 +28,23 @@ const HTMLOptionsCollection = @This();
|
||||
_proto: *HTMLCollection,
|
||||
_select: *@import("../element/html/Select.zig"),
|
||||
|
||||
// The refcount lives on the proto, but anchoring the finalizer here lets
|
||||
// deinit reclaim this struct's slot along with the proto's.
|
||||
pub fn deinit(self: *HTMLOptionsCollection, page: *Page) void {
|
||||
self._proto.deinit(page);
|
||||
// Not destroy(): the proto is a separate slab allocation, not a
|
||||
// contiguous factory chain.
|
||||
page.factory.destroyStandalone(self);
|
||||
}
|
||||
|
||||
pub fn acquireRef(self: *HTMLOptionsCollection) void {
|
||||
self._proto.acquireRef();
|
||||
}
|
||||
|
||||
pub fn releaseRef(self: *HTMLOptionsCollection, page: *Page) void {
|
||||
self._proto._rc.release(self, page);
|
||||
}
|
||||
|
||||
// Forward length to HTMLCollection
|
||||
pub fn length(self: *HTMLOptionsCollection, frame: *Frame) u32 {
|
||||
return self._proto.length(frame);
|
||||
|
||||
@@ -45,7 +45,8 @@ pub fn deinit(self: *NodeList, page: *Page) void {
|
||||
switch (self._data) {
|
||||
.child_nodes => |cn| cn.deinit(page),
|
||||
.selector_list => |list| list.deinit(page),
|
||||
else => {},
|
||||
.radio_node_list => |rnl| rnl._form_collection.releaseRef(page),
|
||||
.name => {},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -93,12 +93,12 @@ pub fn generate(
|
||||
const allowed = allowedUsages(params.name).?;
|
||||
const mask = common.usageMask(allowed, key_usages) catch unreachable;
|
||||
|
||||
const key = try exec.arena.alloc(u8, params.length / 8);
|
||||
const key = try exec.local_arena.alloc(u8, params.length / 8);
|
||||
|
||||
const res = crypto.RAND_bytes(key.ptr, key.len);
|
||||
lp.assert(res == 1, "AES.generate", .{ .res = res });
|
||||
|
||||
const crypto_key = try exec._factory.create(CryptoKey{
|
||||
const crypto_key = try CryptoKey.init(exec, .{
|
||||
._type = .aes,
|
||||
._kind = .secret,
|
||||
._extractable = extractable,
|
||||
@@ -133,13 +133,12 @@ pub fn import(
|
||||
return local.rejectPromise(.{ .dom_exception = .{ .err = error.DataError } });
|
||||
}
|
||||
|
||||
const key = try exec.arena.dupe(u8, raw);
|
||||
const crypto_key = try exec._factory.create(CryptoKey{
|
||||
const crypto_key = try CryptoKey.init(exec, .{
|
||||
._type = .aes,
|
||||
._kind = .secret,
|
||||
._extractable = extractable,
|
||||
._usages = mask,
|
||||
._key = key,
|
||||
._key = raw,
|
||||
._algorithm = .{ .name = canonical },
|
||||
});
|
||||
|
||||
|
||||
@@ -127,7 +127,7 @@ pub fn generate(
|
||||
errdefer crypto.EVP_PKEY_free(public_pkey);
|
||||
if (crypto.EVP_PKEY_set1_EC_KEY(public_pkey, pub_ec) != 1) return error.OutOfMemory;
|
||||
|
||||
const private = try exec._factory.create(CryptoKey{
|
||||
const private = try CryptoKey.init(exec, .{
|
||||
._type = .ec,
|
||||
._kind = .private,
|
||||
._extractable = extractable,
|
||||
@@ -136,9 +136,8 @@ pub fn generate(
|
||||
._algorithm = .{ .name = name, .named_curve = curve },
|
||||
._vary = .{ .pkey = private_pkey },
|
||||
});
|
||||
errdefer exec._factory.destroy(private);
|
||||
|
||||
const public = try exec._factory.create(CryptoKey{
|
||||
const public = try CryptoKey.init(exec, .{
|
||||
._type = .ec,
|
||||
._kind = .public,
|
||||
// Public keys are always extractable.
|
||||
@@ -192,7 +191,7 @@ pub fn import(
|
||||
return local.rejectPromise(.{ .dom_exception = .{ .err = error.DataError } });
|
||||
}
|
||||
|
||||
const crypto_key = try exec._factory.create(CryptoKey{
|
||||
const crypto_key = try CryptoKey.init(exec, .{
|
||||
._type = .ec,
|
||||
._kind = if (is_private) .private else .public,
|
||||
._extractable = extractable,
|
||||
|
||||
@@ -81,18 +81,18 @@ pub fn init(
|
||||
}
|
||||
|
||||
// Should we reject this in promise too?
|
||||
const key = try exec.arena.alloc(u8, block_size);
|
||||
const key = try exec.local_arena.alloc(u8, block_size);
|
||||
|
||||
// HMAC is simply CSPRNG.
|
||||
const res = crypto.RAND_bytes(key.ptr, key.len);
|
||||
lp.assert(res == 1, "HMAC.init", .{ .res = res });
|
||||
|
||||
const crypto_key = try exec._factory.create(CryptoKey{
|
||||
const crypto_key = try CryptoKey.init(exec, .{
|
||||
._type = .hmac,
|
||||
._extractable = extractable,
|
||||
._usages = mask,
|
||||
._key = key,
|
||||
._algorithm = .{ .name = "HMAC", .hash = try exec.arena.dupe(u8, hash_name) },
|
||||
._algorithm = .{ .name = "HMAC", .hash = hash_name },
|
||||
._vary = .{ .digest = digest },
|
||||
});
|
||||
|
||||
@@ -122,16 +122,13 @@ pub fn import(
|
||||
return local.rejectPromise(.{ .dom_exception = .{ .err = error.DataError } });
|
||||
}
|
||||
|
||||
const key = try exec.arena.dupe(u8, raw);
|
||||
errdefer exec.arena.free(key);
|
||||
|
||||
const crypto_key = try exec._factory.create(CryptoKey{
|
||||
const crypto_key = try CryptoKey.init(exec, .{
|
||||
._type = .hmac,
|
||||
._kind = .secret,
|
||||
._extractable = extractable,
|
||||
._usages = mask,
|
||||
._key = key,
|
||||
._algorithm = .{ .name = "HMAC", .hash = try exec.arena.dupe(u8, hash_name) },
|
||||
._key = raw,
|
||||
._algorithm = .{ .name = "HMAC", .hash = hash_name },
|
||||
._vary = .{ .digest = digest },
|
||||
});
|
||||
|
||||
|
||||
@@ -60,11 +60,8 @@ pub fn init(
|
||||
});
|
||||
}
|
||||
|
||||
const public_value = try exec.arena.alloc(u8, crypto.X25519_PUBLIC_VALUE_LEN);
|
||||
errdefer exec.arena.free(public_value);
|
||||
|
||||
const private_key = try exec.arena.alloc(u8, crypto.X25519_PRIVATE_KEY_LEN);
|
||||
errdefer exec.arena.free(private_key);
|
||||
const public_value = try exec.local_arena.alloc(u8, crypto.X25519_PUBLIC_VALUE_LEN);
|
||||
const private_key = try exec.local_arena.alloc(u8, crypto.X25519_PRIVATE_KEY_LEN);
|
||||
|
||||
// There's no info about whether this can fail; so I assume it cannot.
|
||||
crypto.X25519_keypair(@ptrCast(public_value), @ptrCast(private_key));
|
||||
@@ -91,7 +88,7 @@ pub fn init(
|
||||
private_key.len,
|
||||
) orelse return error.OutOfMemory;
|
||||
|
||||
const private = try exec._factory.create(CryptoKey{
|
||||
const private = try CryptoKey.init(exec, .{
|
||||
._type = .x25519,
|
||||
._kind = .private,
|
||||
._extractable = extractable,
|
||||
@@ -100,9 +97,8 @@ pub fn init(
|
||||
._algorithm = .{ .name = "X25519" },
|
||||
._vary = .{ .pkey = private_pkey },
|
||||
});
|
||||
errdefer exec._factory.destroy(private);
|
||||
|
||||
const public = try exec._factory.create(CryptoKey{
|
||||
const public = try CryptoKey.init(exec, .{
|
||||
._type = .x25519,
|
||||
._kind = .public,
|
||||
// Public keys are always extractable.
|
||||
|
||||
Reference in New Issue
Block a user