Current section
Files
Jump to
Current section
Files
src/ywt_ffi.mjs
import {
BitArray$BitArray,
BitArray$BitArray$data,
Result$isOk,
Result$Ok$0,
} from "./gleam.mjs";
import * as decode from "../gleam_stdlib/gleam/dynamic/decode.mjs";
import {
Option$isSome,
Option$Some$0,
} from "../gleam_stdlib/gleam/option.mjs";
import {
named_curve,
rsa_padding,
digest_type,
digest_size,
} from "../ywt_core/ywt/internal/core.mjs";
import * as sign_key from "../ywt_core/ywt/sign_key.mjs";
import * as verify_key from "../ywt_core/ywt/verify_key.mjs";
const importedKey = Symbol();
function optionValue(option) {
return Option$isSome(option) ? Option$Some$0(option) : undefined;
}
function rsaVerifyAlgorithm(digest, padding) {
const digestValue = optionValue(digest);
const paddingValue = optionValue(padding);
if (digestValue === undefined || paddingValue === undefined) {
return undefined;
}
return {
name: rsa_padding(paddingValue),
saltLength: digest_size(digestValue),
};
}
// there is no official API to do this without FFI internals right now.
function toBitArray(arrayBuffer) {
return BitArray$BitArray(
arrayBuffer instanceof Uint8Array
? arrayBuffer
: new Uint8Array(arrayBuffer),
);
}
function toUint8Array(bitArray) {
const data = BitArray$BitArray$data(bitArray);
return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
}
export async function verify(message, signature, key) {
const algorithm = verify_key.match(
key,
// PublicEcdsa(id:, curve:, digest_type:, public_key:) ->
(_id, _curve, digest, _public_key) => {
return { name: "ECDSA", hash: digest_type(digest) };
},
// PublicRsa(id:, digest_type:, exponent:, modulus:, padding:) ->
(_id, digest, _exponent, _modulus, padding) => {
return rsaVerifyAlgorithm(digest, padding);
},
// PublicHmac(id:, digest_type:, secret:) ->
(_id, _digest_type, _secret) => "HMAC",
);
if (algorithm === undefined) {
return false;
}
const cryptoKey = (key[importedKey] ??= await verify_key.match(
key,
// PublicEcdsa(id:, curve:, digest_type:, public_key:) ->
(_id, curve, _digest_type, public_key) =>
crypto.subtle.importKey(
"raw",
toUint8Array(public_key),
{ name: "ECDSA", namedCurve: named_curve(curve) },
false,
["verify"],
),
// PublicRsa(id:, digest_type:, exponent:, modulus:, padding:) ->
(id, digest, exponent, modulus, padding) =>
crypto.subtle.importKey(
"jwk",
verify_key.to_jwk(key),
{
name: rsa_padding(optionValue(padding)),
hash: digest_type(optionValue(digest)),
},
false,
["verify"],
),
// PublicHmac(id:, digest_type:, secret:) ->
async (id, digest, secret) =>
crypto.subtle.importKey(
"raw",
toUint8Array(secret),
{ name: "HMAC", hash: digest_type(digest) },
false,
["verify"],
),
));
return await crypto.subtle.verify(
algorithm,
cryptoKey,
toUint8Array(signature),
toUint8Array(message),
);
}
export async function sign(message, key) {
const algorithm = sign_key.match(
key,
// PrivateEcdsa(id:, curve:, digest_type:, public_key:, private_key:) ->
(_id, _curve, digest, _public_key, _private_key) => {
//
return { name: "ECDSA", hash: digest_type(digest) };
},
// PrivateRsaSimple(id:, digest_type:, public_exponent:, modulus:, private_exponent:, padding:) ->
(_id, digest, _e, _n, _d, padding) => {
return { name: rsa_padding(padding), saltLength: digest_size(digest) };
},
// PrivateRsaFull(id:, digest_type:, public_exponent:, modulus:, private_exponent:, first_prime_factor:, second_prime_factor:, first_factor_crt_exponent:, second_factor_crt_exponent:, first_crt_coefficient:, other_primes_info:, padding:) ->
(_id, digest, _e, _n, _d, _p, _q, _dp, _dq, _qi, _oth, padding) => {
return { name: rsa_padding(padding), saltLength: digest_size(digest) };
},
// PrivateHmac(id:, digest_type:, secret:) ->
(_id, _digest, _secret) => {
return "HMAC";
},
);
const cryptoKey = (key[importedKey] ??= await sign_key.match(
key,
// PrivateEcdsa(id:, curve:, digest_type:, public_key:, private_key:) ->
(_id, curve, _digest, public_key, private_key) =>
crypto.subtle.importKey(
"jwk",
sign_key.to_jwk(key),
{ name: "ECDSA", namedCurve: named_curve(curve) },
false,
["sign"],
),
// PrivateRsaSimple(id:, digest_type:, public_exponent:, modulus:, private_exponent:, padding:) ->
(_id, digest, e, n, d, padding) =>
crypto.subtle.importKey(
"jwk",
sign_key.to_jwk(key),
{ name: rsa_padding(padding), hash: digest_type(digest) },
false,
["sign"],
),
// PrivateRsaFull(id:, digest_type:, public_exponent:, modulus:, private_exponent:, first_prime_factor:, second_prime_factor:, first_factor_crt_exponent:, second_factor_crt_exponent:, first_crt_coefficient:, other_primes_info:, padding:) ->
(id, digest, e, n, d, p, q, dp, dq, qi, others, padding) =>
crypto.subtle.importKey(
"jwk",
sign_key.to_jwk(key),
{ name: rsa_padding(padding), hash: digest_type(digest) },
false,
["sign"],
),
// PrivateHmac(id:, digest_type:, secret:) ->
(_id, digest, secret) =>
crypto.subtle.importKey(
"raw",
toUint8Array(secret),
{ name: "HMAC", hash: digest_type(digest) },
false,
["sign"],
),
));
const signed = await crypto.subtle.sign(
algorithm,
cryptoKey,
toUint8Array(message),
);
return toBitArray(signed);
}
export async function generate_hmac(digest) {
return await generate_key({ name: "HMAC", hash: digest_type(digest) });
}
export async function generate_ecdsa(curve, _digest) {
return await generate_key({ name: "ECDSA", namedCurve: named_curve(curve) });
}
export async function generate_rsa(digest, key_length, padding) {
return await generate_key({
name: rsa_padding(padding),
modulusLength: key_length,
publicExponent: new Uint8Array([1, 0, 1]),
hash: digest_type(digest),
});
}
async function generate_key(algorithm) {
const keyOrKeyPair = await crypto.subtle.generateKey(algorithm, true, [
"sign",
"verify",
]);
const privateKey = keyOrKeyPair.privateKey ?? keyOrKeyPair;
const jwk = await crypto.subtle.exportKey("jwk", privateKey);
const decoded = decode.run(jwk, sign_key.decoder());
if (!Result$isOk(decoded)) {
throw new globalThis.Error();
}
return Result$Ok$0(decoded);
}