mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-02-04 06:23:45 +00:00
implement sign and verify for HMAC
This commit is contained in:
@@ -35,35 +35,41 @@ const SubtleCrypto = @This();
|
|||||||
/// Don't optimize away the type.
|
/// Don't optimize away the type.
|
||||||
_pad: bool = false,
|
_pad: bool = false,
|
||||||
|
|
||||||
/// NOTE: I think we can use extern union and cast this to intended algorithm
|
const Params = struct {
|
||||||
/// 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
|
/// https://developer.mozilla.org/en-US/docs/Web/API/RsaHashedKeyGenParams
|
||||||
const RSA = struct {
|
const RsaHashedKeyGen = struct {
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
/// This should be at least 2048.
|
/// This should be at least 2048.
|
||||||
/// Some organizations are now recommending that it should be 4096.
|
/// Some organizations are now recommending that it should be 4096.
|
||||||
modulusLength: u32,
|
modulusLength: u32,
|
||||||
publicExponent: js.TypedArray(u8),
|
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
|
/// https://developer.mozilla.org/en-US/docs/Web/API/HmacKeyGenParams
|
||||||
const HMAC = struct {
|
const HmacKeyGen = struct {
|
||||||
|
/// Always HMAC.
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
/// Its also possible to pass this in an object.
|
/// Its also possible to pass this in an object.
|
||||||
hash: union(enum) {
|
hash: union(enum) {
|
||||||
str: []const u8,
|
string: []const u8,
|
||||||
obj: struct { name: []const u8 },
|
object: struct { name: []const u8 },
|
||||||
},
|
},
|
||||||
/// If omitted, default is the block size of the chosen hash function.
|
/// If omitted, default is the block size of the chosen hash function.
|
||||||
length: ?usize,
|
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.
|
/// Returns the desired digest by its name.
|
||||||
fn getDigest(name: []const u8) error{Invalid}!*const crypto.EVP_MD {
|
fn getDigest(name: []const u8) error{Invalid}!*const crypto.EVP_MD {
|
||||||
const digest = std.meta.stringToEnum(enum {
|
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 {
|
pub const CryptoKey = struct {
|
||||||
_algorithm: Algorithm,
|
/// Algorithm being used.
|
||||||
|
_type: Type,
|
||||||
/// Whether the key is extractable.
|
/// Whether the key is extractable.
|
||||||
_extractable: bool,
|
_extractable: bool,
|
||||||
/// Bit flags of `usages`.
|
/// Bit flags of `usages`; see `Usages` type.
|
||||||
_usages: u8,
|
_usages: u8,
|
||||||
/// Algorithm specific data.
|
_key: []const u8,
|
||||||
_internal: union {
|
_digest: *const crypto.EVP_MD,
|
||||||
hmac: []u8,
|
|
||||||
},
|
pub const Type = enum(u8) { hmac, rsa };
|
||||||
|
|
||||||
pub const Usages = struct {
|
pub const Usages = struct {
|
||||||
// zig fmt: off
|
// 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) {
|
const hash = switch (algorithm.hash) {
|
||||||
.str => |str| str,
|
.string => |str| str,
|
||||||
.obj => |obj| obj.name,
|
.object => |obj| obj.name,
|
||||||
};
|
};
|
||||||
// Find digest.
|
// Find digest.
|
||||||
const digest = try getDigest(hash);
|
const digest = try getDigest(hash);
|
||||||
@@ -144,19 +153,64 @@ pub const CryptoKey = struct {
|
|||||||
std.debug.assert(res == 1);
|
std.debug.assert(res == 1);
|
||||||
|
|
||||||
return page._factory.create(CryptoKey{
|
return page._factory.create(CryptoKey{
|
||||||
._algorithm = .{
|
._type = .hmac,
|
||||||
.hmac_key_gen = .{
|
|
||||||
.name = "HMAC",
|
|
||||||
.hash = .{ .obj = .{ .name = hash } },
|
|
||||||
.length = block_size,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
._extractable = extractable,
|
._extractable = extractable,
|
||||||
._usages = 0,
|
._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 JsApi = struct {
|
||||||
pub const bridge = js.Bridge(CryptoKey);
|
pub const bridge = js.Bridge(CryptoKey);
|
||||||
|
|
||||||
@@ -180,16 +234,58 @@ pub fn generateKey(
|
|||||||
return CryptoKey.init(algorithm, extractable, key_usages, page);
|
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.
|
/// Generate a digital signature.
|
||||||
pub fn sign(
|
pub fn sign(
|
||||||
_: *const SubtleCrypto,
|
_: *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,
|
key: *const CryptoKey,
|
||||||
data: []const u8,
|
signature: []const u8, // ArrayBuffer.
|
||||||
) void {
|
data: []const u8, // ArrayBuffer.
|
||||||
_ = algorithm;
|
page: *Page,
|
||||||
_ = key;
|
) !js.Promise {
|
||||||
_ = data;
|
if (!algorithm.isHMAC()) return error.InvalidAccess;
|
||||||
|
|
||||||
|
return switch (key._type) {
|
||||||
|
.hmac => key.verifyHMAC(signature, data, page),
|
||||||
|
else => return error.InvalidAccess,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const JsApi = struct {
|
pub const JsApi = struct {
|
||||||
@@ -203,5 +299,6 @@ pub const JsApi = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub const generateKey = bridge.function(SubtleCrypto.generateKey, .{});
|
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, .{});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,8 +10,13 @@ pub extern fn EVP_sha256() *const EVP_MD;
|
|||||||
pub extern fn EVP_sha384() *const EVP_MD;
|
pub extern fn EVP_sha384() *const EVP_MD;
|
||||||
pub extern fn EVP_sha512() *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 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(
|
pub extern fn HMAC(
|
||||||
evp_md: *const EVP_MD,
|
evp_md: *const EVP_MD,
|
||||||
key: *const anyopaque,
|
key: *const anyopaque,
|
||||||
|
|||||||
Reference in New Issue
Block a user