Merge pull request #1554 from lightpanda-io/crypto_digest

Add SubtleCrypto.digest (used in nytimes)
This commit is contained in:
Karl Seguin
2026-02-16 22:56:34 +08:00
committed by GitHub
2 changed files with 73 additions and 29 deletions

View File

@@ -16,44 +16,44 @@
isRandom(ti8a) isRandom(ti8a)
} }
// { {
// let tu16a = new Uint16Array(100) let tu16a = new Uint16Array(100)
// testing.expectEqual(tu16a, crypto.getRandomValues(tu16a)) testing.expectEqual(tu16a, crypto.getRandomValues(tu16a))
// isRandom(tu16a) isRandom(tu16a)
// let ti16a = new Int16Array(100) let ti16a = new Int16Array(100)
// testing.expectEqual(ti16a, crypto.getRandomValues(ti16a)) testing.expectEqual(ti16a, crypto.getRandomValues(ti16a))
// isRandom(ti16a) isRandom(ti16a)
// } }
// { {
// let tu32a = new Uint32Array(100) let tu32a = new Uint32Array(100)
// testing.expectEqual(tu32a, crypto.getRandomValues(tu32a)) testing.expectEqual(tu32a, crypto.getRandomValues(tu32a))
// isRandom(tu32a) isRandom(tu32a)
// let ti32a = new Int32Array(100) let ti32a = new Int32Array(100)
// testing.expectEqual(ti32a, crypto.getRandomValues(ti32a)) testing.expectEqual(ti32a, crypto.getRandomValues(ti32a))
// isRandom(ti32a) isRandom(ti32a)
// } }
// { {
// let tu64a = new BigUint64Array(100) let tu64a = new BigUint64Array(100)
// testing.expectEqual(tu64a, crypto.getRandomValues(tu64a)) testing.expectEqual(tu64a, crypto.getRandomValues(tu64a))
// isRandom(tu64a) isRandom(tu64a)
// let ti64a = new BigInt64Array(100) let ti64a = new BigInt64Array(100)
// testing.expectEqual(ti64a, crypto.getRandomValues(ti64a)) testing.expectEqual(ti64a, crypto.getRandomValues(ti64a))
// isRandom(ti64a) isRandom(ti64a)
// } }
</script> </script>
<!-- <script id="randomUUID"> <script id="randomUUID">
const uuid = crypto.randomUUID(); const uuid = crypto.randomUUID();
testing.expectEqual('string', typeof uuid); testing.expectEqual('string', typeof uuid);
testing.expectEqual(36, uuid.length); testing.expectEqual(36, uuid.length);
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; 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)); testing.expectEqual(true, regex.test(uuid));
</script> --> </script>
<script id=SubtleCrypto> <script id=SubtleCrypto>
testing.expectEqual(true, crypto.subtle instanceof SubtleCrypto); testing.expectEqual(true, crypto.subtle instanceof SubtleCrypto);
@@ -119,3 +119,16 @@
testing.expectEqual(16, sharedKey.byteLength); testing.expectEqual(16, sharedKey.byteLength);
}); });
</script> </script>
<script id="digest">
testing.async(async () => {
async function hash(algo, data) {
const buffer = await window.crypto.subtle.digest(algo, new TextEncoder().encode(data));
return [...new Uint8Array(buffer)].map(x => x.toString(16).padStart(2, '0')).join('');
}
testing.expectEqual("a6a1e3375239f215f09a156df29c17c7d1ac6722", await hash('sha-1', 'over 9000'));
testing.expectEqual("1bc375bb92459685194dda18a4b835f4e2972ec1bde6d9ab3db53fcc584a6580", await hash('sha-256', 'over 9000'));
testing.expectEqual("a4260d64c2eea9fd30c1f895c5e48a26d817e19d3a700b61b3ce665864ff4b8e012bd357d345aa614c5f642dab865ea1", await hash('sha-384', 'over 9000'));
testing.expectEqual("6cad17e6f3f76680d6dd18ed043b75b4f6e1aa1d08b917294942e882fb6466c3510948c34af8b903ed0725b582b3b39c0e485ae2c1b7dfdb192ee38b79c782b6", await hash('sha-512', 'over 9000'));
});
</script>

View File

@@ -21,6 +21,7 @@ const lp = @import("lightpanda");
const log = @import("../../log.zig"); const log = @import("../../log.zig");
const crypto = @import("../../crypto.zig"); const crypto = @import("../../crypto.zig");
const DOMException = @import("DOMException.zig");
const Page = @import("../Page.zig"); const Page = @import("../Page.zig");
const js = @import("../js/js.zig"); const js = @import("../js/js.zig");
@@ -218,6 +219,35 @@ pub fn verify(
}; };
} }
pub fn digest(_: *const SubtleCrypto, algorithm: []const u8, data: js.TypedArray(u8), page: *Page) !js.Promise {
const local = page.js.local.?;
if (algorithm.len > 10) {
return local.rejectPromise(DOMException.fromError(error.NotSupported));
}
const normalized = std.ascii.lowerString(&page.buf, algorithm);
if (std.mem.eql(u8, normalized, "sha-1")) {
const Sha1 = std.crypto.hash.Sha1;
Sha1.hash(data.values, page.buf[0..Sha1.digest_length], .{});
return local.resolvePromise(js.ArrayBuffer{ .values = page.buf[0..Sha1.digest_length] });
}
if (std.mem.eql(u8, normalized, "sha-256")) {
const Sha256 = std.crypto.hash.sha2.Sha256;
Sha256.hash(data.values, page.buf[0..Sha256.digest_length], .{});
return local.resolvePromise(js.ArrayBuffer{ .values = page.buf[0..Sha256.digest_length] });
}
if (std.mem.eql(u8, normalized, "sha-384")) {
const Sha384 = std.crypto.hash.sha2.Sha384;
Sha384.hash(data.values, page.buf[0..Sha384.digest_length], .{});
return local.resolvePromise(js.ArrayBuffer{ .values = page.buf[0..Sha384.digest_length] });
}
if (std.mem.eql(u8, normalized, "sha-512")) {
const Sha512 = std.crypto.hash.sha2.Sha512;
Sha512.hash(data.values, page.buf[0..Sha512.digest_length], .{});
return local.resolvePromise(js.ArrayBuffer{ .values = page.buf[0..Sha512.digest_length] });
}
return local.rejectPromise(DOMException.fromError(error.NotSupported));
}
/// Returns the desired digest by its name. /// Returns the desired digest by its name.
fn findDigest(name: []const u8) error{Invalid}!*const crypto.EVP_MD { fn findDigest(name: []const u8) error{Invalid}!*const crypto.EVP_MD {
if (std.mem.eql(u8, "SHA-256", name)) { if (std.mem.eql(u8, "SHA-256", name)) {
@@ -354,7 +384,7 @@ pub const CryptoKey = struct {
.object => |obj| obj.name, .object => |obj| obj.name,
}; };
// Find digest. // Find digest.
const digest = try findDigest(hash); const d = try findDigest(hash);
// We need at least a single usage. // We need at least a single usage.
if (key_usages.len == 0) { if (key_usages.len == 0) {
@@ -380,7 +410,7 @@ pub const CryptoKey = struct {
break :blk length / 8; break :blk length / 8;
} }
// Prefer block size of the hash function instead. // Prefer block size of the hash function instead.
break :blk crypto.EVP_MD_block_size(digest); break :blk crypto.EVP_MD_block_size(d);
}; };
const key = try page.arena.alloc(u8, block_size); const key = try page.arena.alloc(u8, block_size);
@@ -395,7 +425,7 @@ pub const CryptoKey = struct {
._extractable = extractable, ._extractable = extractable,
._usages = usages_mask, ._usages = usages_mask,
._key = key, ._key = key,
._vary = .{ .digest = digest }, ._vary = .{ .digest = d },
}); });
return .{ .key = crypto_key }; return .{ .key = crypto_key };
@@ -635,4 +665,5 @@ pub const JsApi = struct {
pub const sign = bridge.function(SubtleCrypto.sign, .{ .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 verify = bridge.function(SubtleCrypto.verify, .{ .dom_exception = true });
pub const deriveBits = bridge.function(SubtleCrypto.deriveBits, .{ .dom_exception = true }); pub const deriveBits = bridge.function(SubtleCrypto.deriveBits, .{ .dom_exception = true });
pub const digest = bridge.function(SubtleCrypto.digest, .{ .dom_exception = true });
}; };