1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-03 17:08:35 -05:00
denoland-deno/ext/crypto/00_crypto.js
Divy Srivastava 6e017dbba7
feat(ext/crypto): initial support for p521 in generateKey and importKey (#21815)
Part 1 of a potential 3 part series. Ref #13449 

The current implementation passes key material back and forth RustCrypto
group of crates and ring. ring does not implement p521 yet.

This PR adds support for P521 named curve in `generateKey` and
`importKey` where we use RustCrypto. Other parts should be moved over to
the RustGroup group of crates for consistency.
2024-01-12 13:22:05 +01:00

5257 lines
128 KiB
JavaScript

// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
// @ts-check
/// <reference path="../../core/internal.d.ts" />
/// <reference path="../../core/lib.deno_core.d.ts" />
/// <reference path="../webidl/internal.d.ts" />
/// <reference path="../web/lib.deno_web.d.ts" />
import { core, primordials } from "ext:core/mod.js";
const ops = core.ops;
const {
op_crypto_decrypt,
op_crypto_derive_bits,
op_crypto_encrypt,
op_crypto_generate_key,
op_crypto_sign_key,
op_crypto_subtle_digest,
op_crypto_verify_key,
} = core.ensureFastOps();
import * as webidl from "ext:deno_webidl/00_webidl.js";
import { createFilteredInspectProxy } from "ext:deno_console/01_console.js";
import { DOMException } from "ext:deno_web/01_dom_exception.js";
const {
ArrayBufferIsView,
ArrayBufferPrototypeGetByteLength,
ArrayBufferPrototypeSlice,
ArrayPrototypeEvery,
ArrayPrototypeFilter,
ArrayPrototypeFind,
ArrayPrototypeIncludes,
DataViewPrototypeGetBuffer,
DataViewPrototypeGetByteLength,
DataViewPrototypeGetByteOffset,
JSONParse,
JSONStringify,
MathCeil,
ObjectAssign,
ObjectHasOwn,
ObjectPrototypeIsPrototypeOf,
SafeArrayIterator,
SafeWeakMap,
StringFromCharCode,
StringPrototypeCharCodeAt,
StringPrototypeToLowerCase,
StringPrototypeToUpperCase,
Symbol,
SymbolFor,
SyntaxError,
TypeError,
TypedArrayPrototypeGetBuffer,
TypedArrayPrototypeGetByteLength,
TypedArrayPrototypeGetByteOffset,
TypedArrayPrototypeGetSymbolToStringTag,
TypedArrayPrototypeSlice,
Uint8Array,
WeakMapPrototypeGet,
WeakMapPrototypeSet,
} = primordials;
const {
isArrayBuffer,
isTypedArray,
isDataView,
} = core;
const supportedNamedCurves = ["P-256", "P-384", "P-521"];
const recognisedUsages = [
"encrypt",
"decrypt",
"sign",
"verify",
"deriveKey",
"deriveBits",
"wrapKey",
"unwrapKey",
];
const simpleAlgorithmDictionaries = {
AesGcmParams: { iv: "BufferSource", additionalData: "BufferSource" },
RsaHashedKeyGenParams: { hash: "HashAlgorithmIdentifier" },
EcKeyGenParams: {},
HmacKeyGenParams: { hash: "HashAlgorithmIdentifier" },
RsaPssParams: {},
EcdsaParams: { hash: "HashAlgorithmIdentifier" },
HmacImportParams: { hash: "HashAlgorithmIdentifier" },
HkdfParams: {
hash: "HashAlgorithmIdentifier",
salt: "BufferSource",
info: "BufferSource",
},
Pbkdf2Params: { hash: "HashAlgorithmIdentifier", salt: "BufferSource" },
RsaOaepParams: { label: "BufferSource" },
RsaHashedImportParams: { hash: "HashAlgorithmIdentifier" },
EcKeyImportParams: {},
};
const supportedAlgorithms = {
"digest": {
"SHA-1": null,
"SHA-256": null,
"SHA-384": null,
"SHA-512": null,
},
"generateKey": {
"RSASSA-PKCS1-v1_5": "RsaHashedKeyGenParams",
"RSA-PSS": "RsaHashedKeyGenParams",
"RSA-OAEP": "RsaHashedKeyGenParams",
"ECDSA": "EcKeyGenParams",
"ECDH": "EcKeyGenParams",
"AES-CTR": "AesKeyGenParams",
"AES-CBC": "AesKeyGenParams",
"AES-GCM": "AesKeyGenParams",
"AES-KW": "AesKeyGenParams",
"HMAC": "HmacKeyGenParams",
"X25519": null,
"Ed25519": null,
},
"sign": {
"RSASSA-PKCS1-v1_5": null,
"RSA-PSS": "RsaPssParams",
"ECDSA": "EcdsaParams",
"HMAC": null,
"Ed25519": null,
},
"verify": {
"RSASSA-PKCS1-v1_5": null,
"RSA-PSS": "RsaPssParams",
"ECDSA": "EcdsaParams",
"HMAC": null,
"Ed25519": null,
},
"importKey": {
"RSASSA-PKCS1-v1_5": "RsaHashedImportParams",
"RSA-PSS": "RsaHashedImportParams",
"RSA-OAEP": "RsaHashedImportParams",
"ECDSA": "EcKeyImportParams",
"ECDH": "EcKeyImportParams",
"HMAC": "HmacImportParams",
"HKDF": null,
"PBKDF2": null,
"AES-CTR": null,
"AES-CBC": null,
"AES-GCM": null,
"AES-KW": null,
"Ed25519": null,
"X25519": null,
},
"deriveBits": {
"HKDF": "HkdfParams",
"PBKDF2": "Pbkdf2Params",
"ECDH": "EcdhKeyDeriveParams",
"X25519": "EcdhKeyDeriveParams",
},
"encrypt": {
"RSA-OAEP": "RsaOaepParams",
"AES-CBC": "AesCbcParams",
"AES-GCM": "AesGcmParams",
"AES-CTR": "AesCtrParams",
},
"decrypt": {
"RSA-OAEP": "RsaOaepParams",
"AES-CBC": "AesCbcParams",
"AES-GCM": "AesGcmParams",
"AES-CTR": "AesCtrParams",
},
"get key length": {
"AES-CBC": "AesDerivedKeyParams",
"AES-CTR": "AesDerivedKeyParams",
"AES-GCM": "AesDerivedKeyParams",
"AES-KW": "AesDerivedKeyParams",
"HMAC": "HmacImportParams",
"HKDF": null,
"PBKDF2": null,
},
"wrapKey": {
"AES-KW": null,
},
"unwrapKey": {
"AES-KW": null,
},
};
const aesJwkAlg = {
"AES-CTR": {
128: "A128CTR",
192: "A192CTR",
256: "A256CTR",
},
"AES-CBC": {
128: "A128CBC",
192: "A192CBC",
256: "A256CBC",
},
"AES-GCM": {
128: "A128GCM",
192: "A192GCM",
256: "A256GCM",
},
"AES-KW": {
128: "A128KW",
192: "A192KW",
256: "A256KW",
},
};
// See https://www.w3.org/TR/WebCryptoAPI/#dfn-normalize-an-algorithm
// 18.4.4
function normalizeAlgorithm(algorithm, op) {
if (typeof algorithm == "string") {
return normalizeAlgorithm({ name: algorithm }, op);
}
// 1.
const registeredAlgorithms = supportedAlgorithms[op];
// 2. 3.
const initialAlg = webidl.converters.Algorithm(
algorithm,
"Failed to normalize algorithm",
"passed algorithm",
);
// 4.
let algName = initialAlg.name;
// 5.
let desiredType = undefined;
for (const key in registeredAlgorithms) {
if (!ObjectHasOwn(registeredAlgorithms, key)) {
continue;
}
if (
StringPrototypeToUpperCase(key) === StringPrototypeToUpperCase(algName)
) {
algName = key;
desiredType = registeredAlgorithms[key];
}
}
if (desiredType === undefined) {
throw new DOMException(
"Unrecognized algorithm name",
"NotSupportedError",
);
}
// Fast path everything below if the registered dictionary is "None".
if (desiredType === null) {
return { name: algName };
}
// 6.
const normalizedAlgorithm = webidl.converters[desiredType](
algorithm,
"Failed to normalize algorithm",
"passed algorithm",
);
// 7.
normalizedAlgorithm.name = algName;
// 9.
const dict = simpleAlgorithmDictionaries[desiredType];
// 10.
for (const member in dict) {
if (!ObjectHasOwn(dict, member)) {
continue;
}
const idlType = dict[member];
const idlValue = normalizedAlgorithm[member];
// 3.
if (idlType === "BufferSource" && idlValue) {
normalizedAlgorithm[member] = copyBuffer(idlValue);
} else if (idlType === "HashAlgorithmIdentifier") {
normalizedAlgorithm[member] = normalizeAlgorithm(idlValue, "digest");
} else if (idlType === "AlgorithmIdentifier") {
// TODO(lucacasonato): implement
throw new TypeError("unimplemented");
}
}
return normalizedAlgorithm;
}
/**
* @param {ArrayBufferView | ArrayBuffer} input
* @returns {Uint8Array}
*/
function copyBuffer(input) {
if (isTypedArray(input)) {
return TypedArrayPrototypeSlice(
new Uint8Array(
TypedArrayPrototypeGetBuffer(/** @type {Uint8Array} */ (input)),
TypedArrayPrototypeGetByteOffset(/** @type {Uint8Array} */ (input)),
TypedArrayPrototypeGetByteLength(/** @type {Uint8Array} */ (input)),
),
);
} else if (isDataView(input)) {
return TypedArrayPrototypeSlice(
new Uint8Array(
DataViewPrototypeGetBuffer(/** @type {DataView} */ (input)),
DataViewPrototypeGetByteOffset(/** @type {DataView} */ (input)),
DataViewPrototypeGetByteLength(/** @type {DataView} */ (input)),
),
);
}
// ArrayBuffer
return TypedArrayPrototypeSlice(
new Uint8Array(
input,
0,
ArrayBufferPrototypeGetByteLength(input),
),
);
}
const _handle = Symbol("[[handle]]");
const _algorithm = Symbol("[[algorithm]]");
const _extractable = Symbol("[[extractable]]");
const _usages = Symbol("[[usages]]");
const _type = Symbol("[[type]]");
class CryptoKey {
/** @type {string} */
[_type];
/** @type {boolean} */
[_extractable];
/** @type {object} */
[_algorithm];
/** @type {string[]} */
[_usages];
/** @type {object} */
[_handle];
constructor() {
webidl.illegalConstructor();
}
/** @returns {string} */
get type() {
webidl.assertBranded(this, CryptoKeyPrototype);
return this[_type];
}
/** @returns {boolean} */
get extractable() {
webidl.assertBranded(this, CryptoKeyPrototype);
return this[_extractable];
}
/** @returns {string[]} */
get usages() {
webidl.assertBranded(this, CryptoKeyPrototype);
// TODO(lucacasonato): return a SameObject copy
return this[_usages];
}
/** @returns {object} */
get algorithm() {
webidl.assertBranded(this, CryptoKeyPrototype);
// TODO(lucacasonato): return a SameObject copy
return this[_algorithm];
}
[SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) {
return inspect(
createFilteredInspectProxy({
object: this,
evaluate: ObjectPrototypeIsPrototypeOf(CryptoKeyPrototype, this),
keys: [
"type",
"extractable",
"algorithm",
"usages",
],
}),
inspectOptions,
);
}
}
webidl.configureInterface(CryptoKey);
const CryptoKeyPrototype = CryptoKey.prototype;
/**
* @param {string} type
* @param {boolean} extractable
* @param {string[]} usages
* @param {object} algorithm
* @param {object} handle
* @returns
*/
function constructKey(type, extractable, usages, algorithm, handle) {
const key = webidl.createBranded(CryptoKey);
key[_type] = type;
key[_extractable] = extractable;
key[_usages] = usages;
key[_algorithm] = algorithm;
key[_handle] = handle;
return key;
}
// https://w3c.github.io/webcrypto/#concept-usage-intersection
/**
* @param {string[]} a
* @param {string[]} b
* @returns
*/
function usageIntersection(a, b) {
return ArrayPrototypeFilter(
a,
(i) => ArrayPrototypeIncludes(b, i),
);
}
// TODO(lucacasonato): this should be moved to rust
/** @type {WeakMap<object, object>} */
const KEY_STORE = new SafeWeakMap();
function getKeyLength(algorithm) {
switch (algorithm.name) {
case "AES-CBC":
case "AES-CTR":
case "AES-GCM":
case "AES-KW": {
// 1.
if (!ArrayPrototypeIncludes([128, 192, 256], algorithm.length)) {
throw new DOMException(
"length must be 128, 192, or 256",
"OperationError",
);
}
// 2.
return algorithm.length;
}
case "HMAC": {
// 1.
let length;
if (algorithm.length === undefined) {
switch (algorithm.hash.name) {
case "SHA-1":
length = 512;
break;
case "SHA-256":
length = 512;
break;
case "SHA-384":
length = 1024;
break;
case "SHA-512":
length = 1024;
break;
default:
throw new DOMException(
"Unrecognized hash algorithm",
"NotSupportedError",
);
}
} else if (algorithm.length !== 0) {
length = algorithm.length;
} else {
throw new TypeError("Invalid length.");
}
// 2.
return length;
}
case "HKDF": {
// 1.
return null;
}
case "PBKDF2": {
// 1.
return null;
}
default:
throw new TypeError("unreachable");
}
}
class SubtleCrypto {
constructor() {
webidl.illegalConstructor();
}
/**
* @param {string} algorithm
* @param {BufferSource} data
* @returns {Promise<ArrayBuffer>}
*/
async digest(algorithm, data) {
webidl.assertBranded(this, SubtleCryptoPrototype);
const prefix = "Failed to execute 'digest' on 'SubtleCrypto'";
webidl.requiredArguments(arguments.length, 2, prefix);
algorithm = webidl.converters.AlgorithmIdentifier(
algorithm,
prefix,
"Argument 1",
);
data = webidl.converters.BufferSource(data, prefix, "Argument 2");
data = copyBuffer(data);
algorithm = normalizeAlgorithm(algorithm, "digest");
const result = await op_crypto_subtle_digest(
algorithm.name,
data,
);
return TypedArrayPrototypeGetBuffer(result);
}
/**
* @param {string} algorithm
* @param {CryptoKey} key
* @param {BufferSource} data
* @returns {Promise<any>}
*/
async encrypt(algorithm, key, data) {
webidl.assertBranded(this, SubtleCryptoPrototype);
const prefix = "Failed to execute 'encrypt' on 'SubtleCrypto'";
webidl.requiredArguments(arguments.length, 3, prefix);
algorithm = webidl.converters.AlgorithmIdentifier(
algorithm,
prefix,
"Argument 1",
);
key = webidl.converters.CryptoKey(key, prefix, "Argument 2");
data = webidl.converters.BufferSource(data, prefix, "Argument 3");
// 2.
data = copyBuffer(data);
// 3.
const normalizedAlgorithm = normalizeAlgorithm(algorithm, "encrypt");
// 8.
if (normalizedAlgorithm.name !== key[_algorithm].name) {
throw new DOMException(
"Encryption algorithm doesn't match key algorithm.",
"InvalidAccessError",
);
}
// 9.
if (!ArrayPrototypeIncludes(key[_usages], "encrypt")) {
throw new DOMException(
"Key does not support the 'encrypt' operation.",
"InvalidAccessError",
);
}
return await encrypt(normalizedAlgorithm, key, data);
}
/**
* @param {string} algorithm
* @param {CryptoKey} key
* @param {BufferSource} data
* @returns {Promise<any>}
*/
async decrypt(algorithm, key, data) {
webidl.assertBranded(this, SubtleCryptoPrototype);
const prefix = "Failed to execute 'decrypt' on 'SubtleCrypto'";
webidl.requiredArguments(arguments.length, 3, prefix);
algorithm = webidl.converters.AlgorithmIdentifier(
algorithm,
prefix,
"Argument 1",
);
key = webidl.converters.CryptoKey(key, prefix, "Argument 2");
data = webidl.converters.BufferSource(data, prefix, "Argument 3");
// 2.
data = copyBuffer(data);
// 3.
const normalizedAlgorithm = normalizeAlgorithm(algorithm, "decrypt");
// 8.
if (normalizedAlgorithm.name !== key[_algorithm].name) {
throw new DOMException(
"Decryption algorithm doesn't match key algorithm.",
"OperationError",
);
}
// 9.
if (!ArrayPrototypeIncludes(key[_usages], "decrypt")) {
throw new DOMException(
"Key does not support the 'decrypt' operation.",
"InvalidAccessError",
);
}
const handle = key[_handle];
const keyData = WeakMapPrototypeGet(KEY_STORE, handle);
switch (normalizedAlgorithm.name) {
case "RSA-OAEP": {
// 1.
if (key[_type] !== "private") {
throw new DOMException(
"Key type not supported",
"InvalidAccessError",
);
}
// 2.
if (normalizedAlgorithm.label) {
normalizedAlgorithm.label = copyBuffer(normalizedAlgorithm.label);
} else {
normalizedAlgorithm.label = new Uint8Array();
}
// 3-5.
const hashAlgorithm = key[_algorithm].hash.name;
const plainText = await op_crypto_decrypt({
key: keyData,
algorithm: "RSA-OAEP",
hash: hashAlgorithm,
label: normalizedAlgorithm.label,
}, data);
// 6.
return TypedArrayPrototypeGetBuffer(plainText);
}
case "AES-CBC": {
normalizedAlgorithm.iv = copyBuffer(normalizedAlgorithm.iv);
// 1.
if (TypedArrayPrototypeGetByteLength(normalizedAlgorithm.iv) !== 16) {
throw new DOMException(
"Counter must be 16 bytes",
"OperationError",
);
}
const plainText = await op_crypto_decrypt({
key: keyData,
algorithm: "AES-CBC",
iv: normalizedAlgorithm.iv,
length: key[_algorithm].length,
}, data);
// 6.
return TypedArrayPrototypeGetBuffer(plainText);
}
case "AES-CTR": {
normalizedAlgorithm.counter = copyBuffer(normalizedAlgorithm.counter);
// 1.
if (
TypedArrayPrototypeGetByteLength(normalizedAlgorithm.counter) !== 16
) {
throw new DOMException(
"Counter vector must be 16 bytes",
"OperationError",
);
}
// 2.
if (
normalizedAlgorithm.length === 0 || normalizedAlgorithm.length > 128
) {
throw new DOMException(
"Counter length must not be 0 or greater than 128",
"OperationError",
);
}
// 3.
const cipherText = await op_crypto_decrypt({
key: keyData,
algorithm: "AES-CTR",
keyLength: key[_algorithm].length,
counter: normalizedAlgorithm.counter,
ctrLength: normalizedAlgorithm.length,
}, data);
// 4.
return TypedArrayPrototypeGetBuffer(cipherText);
}
case "AES-GCM": {
normalizedAlgorithm.iv = copyBuffer(normalizedAlgorithm.iv);
// 1.
if (normalizedAlgorithm.tagLength === undefined) {
normalizedAlgorithm.tagLength = 128;
} else if (
!ArrayPrototypeIncludes(
[32, 64, 96, 104, 112, 120, 128],
normalizedAlgorithm.tagLength,
)
) {
throw new DOMException(
"Invalid tag length",
"OperationError",
);
}
// 2.
if (
TypedArrayPrototypeGetByteLength(data) <
normalizedAlgorithm.tagLength / 8
) {
throw new DOMException(
"Tag length overflows ciphertext",
"OperationError",
);
}
// 3. We only support 96-bit and 128-bit nonce.
if (
ArrayPrototypeIncludes(
[12, 16],
TypedArrayPrototypeGetByteLength(normalizedAlgorithm.iv),
) === undefined
) {
throw new DOMException(
"Initialization vector length not supported",
"NotSupportedError",
);
}
// 4.
if (normalizedAlgorithm.additionalData !== undefined) {
// NOTE: over the size of Number.MAX_SAFE_INTEGER is not available in V8
// if (normalizedAlgorithm.additionalData.byteLength > (2 ** 64) - 1) {
// throw new DOMException(
// "Additional data too large",
// "OperationError",
// );
// }
normalizedAlgorithm.additionalData = copyBuffer(
normalizedAlgorithm.additionalData,
);
}
// 5-8.
const plaintext = await op_crypto_decrypt({
key: keyData,
algorithm: "AES-GCM",
length: key[_algorithm].length,
iv: normalizedAlgorithm.iv,
additionalData: normalizedAlgorithm.additionalData ||
null,
tagLength: normalizedAlgorithm.tagLength,
}, data);
// 9.
return TypedArrayPrototypeGetBuffer(plaintext);
}
default:
throw new DOMException("Not implemented", "NotSupportedError");
}
}
/**
* @param {string} algorithm
* @param {CryptoKey} key
* @param {BufferSource} data
* @returns {Promise<any>}
*/
async sign(algorithm, key, data) {
webidl.assertBranded(this, SubtleCryptoPrototype);
const prefix = "Failed to execute 'sign' on 'SubtleCrypto'";
webidl.requiredArguments(arguments.length, 3, prefix);
algorithm = webidl.converters.AlgorithmIdentifier(
algorithm,
prefix,
"Argument 1",
);
key = webidl.converters.CryptoKey(key, prefix, "Argument 2");
data = webidl.converters.BufferSource(data, prefix, "Argument 3");
// 1.
data = copyBuffer(data);
// 2.
const normalizedAlgorithm = normalizeAlgorithm(algorithm, "sign");
const handle = key[_handle];
const keyData = WeakMapPrototypeGet(KEY_STORE, handle);
// 8.
if (normalizedAlgorithm.name !== key[_algorithm].name) {
throw new DOMException(
"Signing algorithm doesn't match key algorithm.",
"InvalidAccessError",
);
}
// 9.
if (!ArrayPrototypeIncludes(key[_usages], "sign")) {
throw new DOMException(
"Key does not support the 'sign' operation.",
"InvalidAccessError",
);
}
switch (normalizedAlgorithm.name) {
case "RSASSA-PKCS1-v1_5": {
// 1.
if (key[_type] !== "private") {
throw new DOMException(
"Key type not supported",
"InvalidAccessError",
);
}
// 2.
const hashAlgorithm = key[_algorithm].hash.name;
const signature = await op_crypto_sign_key({
key: keyData,
algorithm: "RSASSA-PKCS1-v1_5",
hash: hashAlgorithm,
}, data);
return TypedArrayPrototypeGetBuffer(signature);
}
case "RSA-PSS": {
// 1.
if (key[_type] !== "private") {
throw new DOMException(
"Key type not supported",
"InvalidAccessError",
);
}
// 2.
const hashAlgorithm = key[_algorithm].hash.name;
const signature = await op_crypto_sign_key({
key: keyData,
algorithm: "RSA-PSS",
hash: hashAlgorithm,
saltLength: normalizedAlgorithm.saltLength,
}, data);
return TypedArrayPrototypeGetBuffer(signature);
}
case "ECDSA": {
// 1.
if (key[_type] !== "private") {
throw new DOMException(
"Key type not supported",
"InvalidAccessError",
);
}
// 2.
const hashAlgorithm = normalizedAlgorithm.hash.name;
const namedCurve = key[_algorithm].namedCurve;
if (!ArrayPrototypeIncludes(supportedNamedCurves, namedCurve)) {
throw new DOMException("Curve not supported", "NotSupportedError");
}
if (
(key[_algorithm].namedCurve === "P-256" &&
hashAlgorithm !== "SHA-256") ||
(key[_algorithm].namedCurve === "P-384" &&
hashAlgorithm !== "SHA-384")
) {
throw new DOMException(
"Not implemented",
"NotSupportedError",
);
}
const signature = await op_crypto_sign_key({
key: keyData,
algorithm: "ECDSA",
hash: hashAlgorithm,
namedCurve,
}, data);
return TypedArrayPrototypeGetBuffer(signature);
}
case "HMAC": {
const hashAlgorithm = key[_algorithm].hash.name;
const signature = await op_crypto_sign_key({
key: keyData,
algorithm: "HMAC",
hash: hashAlgorithm,
}, data);
return TypedArrayPrototypeGetBuffer(signature);
}
case "Ed25519": {
// 1.
if (key[_type] !== "private") {
throw new DOMException(
"Key type not supported",
"InvalidAccessError",
);
}
// https://briansmith.org/rustdoc/src/ring/ec/curve25519/ed25519/signing.rs.html#260
const SIGNATURE_LEN = 32 * 2; // ELEM_LEN + SCALAR_LEN
const signature = new Uint8Array(SIGNATURE_LEN);
if (!ops.op_crypto_sign_ed25519(keyData, data, signature)) {
throw new DOMException(
"Failed to sign",
"OperationError",
);
}
return TypedArrayPrototypeGetBuffer(signature);
}
}
throw new TypeError("unreachable");
}
/**
* @param {string} format
* @param {BufferSource} keyData
* @param {string} algorithm
* @param {boolean} extractable
* @param {KeyUsages[]} keyUsages
* @returns {Promise<any>}
*/
// deno-lint-ignore require-await
async importKey(format, keyData, algorithm, extractable, keyUsages) {
webidl.assertBranded(this, SubtleCryptoPrototype);
const prefix = "Failed to execute 'importKey' on 'SubtleCrypto'";
webidl.requiredArguments(arguments.length, 4, prefix);
format = webidl.converters.KeyFormat(format, prefix, "Argument 1");
keyData = webidl.converters["BufferSource or JsonWebKey"](
keyData,
prefix,
"Argument 2",
);
algorithm = webidl.converters.AlgorithmIdentifier(
algorithm,
prefix,
"Argument 3",
);
extractable = webidl.converters.boolean(extractable, prefix, "Argument 4");
keyUsages = webidl.converters["sequence<KeyUsage>"](
keyUsages,
prefix,
"Argument 5",
);
// 2.
if (format !== "jwk") {
if (ArrayBufferIsView(keyData) || isArrayBuffer(keyData)) {
keyData = copyBuffer(keyData);
} else {
throw new TypeError("keyData is a JsonWebKey");
}
} else {
if (ArrayBufferIsView(keyData) || isArrayBuffer(keyData)) {
throw new TypeError("keyData is not a JsonWebKey");
}
}
const normalizedAlgorithm = normalizeAlgorithm(algorithm, "importKey");
const algorithmName = normalizedAlgorithm.name;
switch (algorithmName) {
case "HMAC": {
return importKeyHMAC(
format,
normalizedAlgorithm,
keyData,
extractable,
keyUsages,
);
}
case "ECDH":
case "ECDSA": {
return importKeyEC(
format,
normalizedAlgorithm,
keyData,
extractable,
keyUsages,
);
}
case "RSASSA-PKCS1-v1_5":
case "RSA-PSS":
case "RSA-OAEP": {
return importKeyRSA(
format,
normalizedAlgorithm,
keyData,
extractable,
keyUsages,
);
}
case "HKDF": {
return importKeyHKDF(format, keyData, extractable, keyUsages);
}
case "PBKDF2": {
return importKeyPBKDF2(format, keyData, extractable, keyUsages);
}
case "AES-CTR":
case "AES-CBC":
case "AES-GCM": {
return importKeyAES(
format,
normalizedAlgorithm,
keyData,
extractable,
keyUsages,
["encrypt", "decrypt", "wrapKey", "unwrapKey"],
);
}
case "AES-KW": {
return importKeyAES(
format,
normalizedAlgorithm,
keyData,
extractable,
keyUsages,
["wrapKey", "unwrapKey"],
);
}
case "X25519": {
return importKeyX25519(
format,
keyData,
extractable,
keyUsages,
);
}
case "Ed25519": {
return importKeyEd25519(
format,
keyData,
extractable,
keyUsages,
);
}
default:
throw new DOMException("Not implemented", "NotSupportedError");
}
}
/**
* @param {string} format
* @param {CryptoKey} key
* @returns {Promise<any>}
*/
// deno-lint-ignore require-await
async exportKey(format, key) {
webidl.assertBranded(this, SubtleCryptoPrototype);
const prefix = "Failed to execute 'exportKey' on 'SubtleCrypto'";
webidl.requiredArguments(arguments.length, 2, prefix);
format = webidl.converters.KeyFormat(format, prefix, "Argument 1");
key = webidl.converters.CryptoKey(key, prefix, "Argument 2");
const handle = key[_handle];
// 2.
const innerKey = WeakMapPrototypeGet(KEY_STORE, handle);
const algorithmName = key[_algorithm].name;
let result;
switch (algorithmName) {
case "HMAC": {
result = exportKeyHMAC(format, key, innerKey);
break;
}
case "RSASSA-PKCS1-v1_5":
case "RSA-PSS":
case "RSA-OAEP": {
result = exportKeyRSA(format, key, innerKey);
break;
}
case "ECDH":
case "ECDSA": {
result = exportKeyEC(format, key, innerKey);
break;
}
case "Ed25519": {
result = exportKeyEd25519(format, key, innerKey);
break;
}
case "X25519": {
result = exportKeyX25519(format, key, innerKey);
break;
}
case "AES-CTR":
case "AES-CBC":
case "AES-GCM":
case "AES-KW": {
result = exportKeyAES(format, key, innerKey);
break;
}
default:
throw new DOMException("Not implemented", "NotSupportedError");
}
if (key.extractable === false) {
throw new DOMException(
"Key is not extractable",
"InvalidAccessError",
);
}
return result;
}
/**
* @param {AlgorithmIdentifier} algorithm
* @param {CryptoKey} baseKey
* @param {number | null} length
* @returns {Promise<ArrayBuffer>}
*/
async deriveBits(algorithm, baseKey, length) {
webidl.assertBranded(this, SubtleCryptoPrototype);
const prefix = "Failed to execute 'deriveBits' on 'SubtleCrypto'";
webidl.requiredArguments(arguments.length, 3, prefix);
algorithm = webidl.converters.AlgorithmIdentifier(
algorithm,
prefix,
"Argument 1",
);
baseKey = webidl.converters.CryptoKey(baseKey, prefix, "Argument 2");
if (length !== null) {
length = webidl.converters["unsigned long"](length, prefix, "Argument 3");
}
// 2.
const normalizedAlgorithm = normalizeAlgorithm(algorithm, "deriveBits");
// 4-6.
const result = await deriveBits(normalizedAlgorithm, baseKey, length);
// 7.
if (normalizedAlgorithm.name !== baseKey[_algorithm].name) {
throw new DOMException("Invalid algorithm name", "InvalidAccessError");
}
// 8.
if (!ArrayPrototypeIncludes(baseKey[_usages], "deriveBits")) {
throw new DOMException(
"baseKey usages does not contain `deriveBits`",
"InvalidAccessError",
);
}
// 9-10.
return result;
}
/**
* @param {AlgorithmIdentifier} algorithm
* @param {CryptoKey} baseKey
* @param {number} length
* @returns {Promise<ArrayBuffer>}
*/
async deriveKey(
algorithm,
baseKey,
derivedKeyType,
extractable,
keyUsages,
) {
webidl.assertBranded(this, SubtleCryptoPrototype);
const prefix = "Failed to execute 'deriveKey' on 'SubtleCrypto'";
webidl.requiredArguments(arguments.length, 5, prefix);
algorithm = webidl.converters.AlgorithmIdentifier(
algorithm,
prefix,
"Argument 1",
);
baseKey = webidl.converters.CryptoKey(baseKey, prefix, "Argument 2");
derivedKeyType = webidl.converters.AlgorithmIdentifier(
derivedKeyType,
prefix,
"Argument 3",
);
extractable = webidl.converters["boolean"](
extractable,
prefix,
"Argument 4",
);
keyUsages = webidl.converters["sequence<KeyUsage>"](
keyUsages,
prefix,
"Argument 5",
);
// 2-3.
const normalizedAlgorithm = normalizeAlgorithm(algorithm, "deriveBits");
// 4-5.
const normalizedDerivedKeyAlgorithmImport = normalizeAlgorithm(
derivedKeyType,
"importKey",
);
// 6-7.
const normalizedDerivedKeyAlgorithmLength = normalizeAlgorithm(
derivedKeyType,
"get key length",
);
// 8-10.
// 11.
if (normalizedAlgorithm.name !== baseKey[_algorithm].name) {
throw new DOMException(
"Invalid algorithm name",
"InvalidAccessError",
);
}
// 12.
if (!ArrayPrototypeIncludes(baseKey[_usages], "deriveKey")) {
throw new DOMException(
"baseKey usages does not contain `deriveKey`",
"InvalidAccessError",
);
}
// 13.
const length = getKeyLength(normalizedDerivedKeyAlgorithmLength);
// 14.
const secret = await deriveBits(
normalizedAlgorithm,
baseKey,
length,
);
// 15.
const result = await this.importKey(
"raw",
secret,
normalizedDerivedKeyAlgorithmImport,
extractable,
keyUsages,
);
// 16.
if (
ArrayPrototypeIncludes(["private", "secret"], result[_type]) &&
keyUsages.length == 0
) {
throw new SyntaxError("Invalid key usages");
}
// 17.
return result;
}
/**
* @param {string} algorithm
* @param {CryptoKey} key
* @param {BufferSource} signature
* @param {BufferSource} data
* @returns {Promise<boolean>}
*/
async verify(algorithm, key, signature, data) {
webidl.assertBranded(this, SubtleCryptoPrototype);
const prefix = "Failed to execute 'verify' on 'SubtleCrypto'";
webidl.requiredArguments(arguments.length, 4, prefix);
algorithm = webidl.converters.AlgorithmIdentifier(
algorithm,
prefix,
"Argument 1",
);
key = webidl.converters.CryptoKey(key, prefix, "Argument 2");
signature = webidl.converters.BufferSource(signature, prefix, "Argument 3");
data = webidl.converters.BufferSource(data, prefix, "Argument 4");
// 2.
signature = copyBuffer(signature);
// 3.
data = copyBuffer(data);
const normalizedAlgorithm = normalizeAlgorithm(algorithm, "verify");
const handle = key[_handle];
const keyData = WeakMapPrototypeGet(KEY_STORE, handle);
if (normalizedAlgorithm.name !== key[_algorithm].name) {
throw new DOMException(
"Verifying algorithm doesn't match key algorithm.",
"InvalidAccessError",
);
}
if (!ArrayPrototypeIncludes(key[_usages], "verify")) {
throw new DOMException(
"Key does not support the 'verify' operation.",
"InvalidAccessError",
);
}
switch (normalizedAlgorithm.name) {
case "RSASSA-PKCS1-v1_5": {
if (key[_type] !== "public") {
throw new DOMException(
"Key type not supported",
"InvalidAccessError",
);
}
const hashAlgorithm = key[_algorithm].hash.name;
return await op_crypto_verify_key({
key: keyData,
algorithm: "RSASSA-PKCS1-v1_5",
hash: hashAlgorithm,
signature,
}, data);
}
case "RSA-PSS": {
if (key[_type] !== "public") {
throw new DOMException(
"Key type not supported",
"InvalidAccessError",
);
}
const hashAlgorithm = key[_algorithm].hash.name;
return await op_crypto_verify_key({
key: keyData,
algorithm: "RSA-PSS",
hash: hashAlgorithm,
signature,
saltLength: normalizedAlgorithm.saltLength,
}, data);
}
case "HMAC": {
const hash = key[_algorithm].hash.name;
return await op_crypto_verify_key({
key: keyData,
algorithm: "HMAC",
hash,
signature,
}, data);
}
case "ECDSA": {
// 1.
if (key[_type] !== "public") {
throw new DOMException(
"Key type not supported",
"InvalidAccessError",
);
}
// 2.
const hash = normalizedAlgorithm.hash.name;
if (
(key[_algorithm].namedCurve === "P-256" && hash !== "SHA-256") ||
(key[_algorithm].namedCurve === "P-384" && hash !== "SHA-384")
) {
throw new DOMException(
"Not implemented",
"NotSupportedError",
);
}
// 3-8.
return await op_crypto_verify_key({
key: keyData,
algorithm: "ECDSA",
hash,
signature,
namedCurve: key[_algorithm].namedCurve,
}, data);
}
case "Ed25519": {
// 1.
if (key[_type] !== "public") {
throw new DOMException(
"Key type not supported",
"InvalidAccessError",
);
}
return ops.op_crypto_verify_ed25519(keyData, data, signature);
}
}
throw new TypeError("unreachable");
}
/**
* @param {string} algorithm
* @param {boolean} extractable
* @param {KeyUsage[]} keyUsages
* @returns {Promise<any>}
*/
async wrapKey(format, key, wrappingKey, wrapAlgorithm) {
webidl.assertBranded(this, SubtleCryptoPrototype);
const prefix = "Failed to execute 'wrapKey' on 'SubtleCrypto'";
webidl.requiredArguments(arguments.length, 4, prefix);
format = webidl.converters.KeyFormat(format, prefix, "Argument 1");
key = webidl.converters.CryptoKey(key, prefix, "Argument 2");
wrappingKey = webidl.converters.CryptoKey(
wrappingKey,
prefix,
"Argument 3",
);
wrapAlgorithm = webidl.converters.AlgorithmIdentifier(
wrapAlgorithm,
prefix,
"Argument 4",
);
let normalizedAlgorithm;
try {
// 2.
normalizedAlgorithm = normalizeAlgorithm(wrapAlgorithm, "wrapKey");
} catch (_) {
// 3.
normalizedAlgorithm = normalizeAlgorithm(wrapAlgorithm, "encrypt");
}
// 8.
if (normalizedAlgorithm.name !== wrappingKey[_algorithm].name) {
throw new DOMException(
"Wrapping algorithm doesn't match key algorithm.",
"InvalidAccessError",
);
}
// 9.
if (!ArrayPrototypeIncludes(wrappingKey[_usages], "wrapKey")) {
throw new DOMException(
"Key does not support the 'wrapKey' operation.",
"InvalidAccessError",
);
}
// 10. NotSupportedError will be thrown in step 12.
// 11.
if (key[_extractable] === false) {
throw new DOMException(
"Key is not extractable",
"InvalidAccessError",
);
}
// 12.
const exportedKey = await this.exportKey(format, key);
let bytes;
// 13.
if (format !== "jwk") {
bytes = new Uint8Array(exportedKey);
} else {
const jwk = JSONStringify(exportedKey);
const ret = new Uint8Array(jwk.length);
for (let i = 0; i < jwk.length; i++) {
ret[i] = StringPrototypeCharCodeAt(jwk, i);
}
bytes = ret;
}
// 14-15.
if (
supportedAlgorithms["wrapKey"][normalizedAlgorithm.name] !== undefined
) {
const handle = wrappingKey[_handle];
const keyData = WeakMapPrototypeGet(KEY_STORE, handle);
switch (normalizedAlgorithm.name) {
case "AES-KW": {
const cipherText = await ops.op_crypto_wrap_key({
key: keyData,
algorithm: normalizedAlgorithm.name,
}, bytes);
// 4.
return TypedArrayPrototypeGetBuffer(cipherText);
}
default: {
throw new DOMException(
"Not implemented",
"NotSupportedError",
);
}
}
} else if (
supportedAlgorithms["encrypt"][normalizedAlgorithm.name] !== undefined
) {
// must construct a new key, since keyUsages is ["wrapKey"] and not ["encrypt"]
return await encrypt(
normalizedAlgorithm,
constructKey(
wrappingKey[_type],
wrappingKey[_extractable],
["encrypt"],
wrappingKey[_algorithm],
wrappingKey[_handle],
),
bytes,
);
} else {
throw new DOMException(
"Algorithm not supported",
"NotSupportedError",
);
}
}
/**
* @param {string} format
* @param {BufferSource} wrappedKey
* @param {CryptoKey} unwrappingKey
* @param {AlgorithmIdentifier} unwrapAlgorithm
* @param {AlgorithmIdentifier} unwrappedKeyAlgorithm
* @param {boolean} extractable
* @param {KeyUsage[]} keyUsages
* @returns {Promise<CryptoKey>}
*/
async unwrapKey(
format,
wrappedKey,
unwrappingKey,
unwrapAlgorithm,
unwrappedKeyAlgorithm,
extractable,
keyUsages,
) {
webidl.assertBranded(this, SubtleCryptoPrototype);
const prefix = "Failed to execute 'unwrapKey' on 'SubtleCrypto'";
webidl.requiredArguments(arguments.length, 7, prefix);
format = webidl.converters.KeyFormat(format, prefix, "Argument 1");
wrappedKey = webidl.converters.BufferSource(
wrappedKey,
prefix,
"Argument 2",
);
unwrappingKey = webidl.converters.CryptoKey(
unwrappingKey,
prefix,
"Argument 3",
);
unwrapAlgorithm = webidl.converters.AlgorithmIdentifier(
unwrapAlgorithm,
prefix,
"Argument 4",
);
unwrappedKeyAlgorithm = webidl.converters.AlgorithmIdentifier(
unwrappedKeyAlgorithm,
prefix,
"Argument 5",
);
extractable = webidl.converters.boolean(extractable, prefix, "Argument 6");
keyUsages = webidl.converters["sequence<KeyUsage>"](
keyUsages,
prefix,
"Argument 7",
);
// 2.
wrappedKey = copyBuffer(wrappedKey);
let normalizedAlgorithm;
try {
// 3.
normalizedAlgorithm = normalizeAlgorithm(unwrapAlgorithm, "unwrapKey");
} catch (_) {
// 4.
normalizedAlgorithm = normalizeAlgorithm(unwrapAlgorithm, "decrypt");
}
// 6.
const normalizedKeyAlgorithm = normalizeAlgorithm(
unwrappedKeyAlgorithm,
"importKey",
);
// 11.
if (normalizedAlgorithm.name !== unwrappingKey[_algorithm].name) {
throw new DOMException(
"Unwrapping algorithm doesn't match key algorithm.",
"InvalidAccessError",
);
}
// 12.
if (!ArrayPrototypeIncludes(unwrappingKey[_usages], "unwrapKey")) {
throw new DOMException(
"Key does not support the 'unwrapKey' operation.",
"InvalidAccessError",
);
}
// 13.
let key;
if (
supportedAlgorithms["unwrapKey"][normalizedAlgorithm.name] !== undefined
) {
const handle = unwrappingKey[_handle];
const keyData = WeakMapPrototypeGet(KEY_STORE, handle);
switch (normalizedAlgorithm.name) {
case "AES-KW": {
const plainText = await ops.op_crypto_unwrap_key({
key: keyData,
algorithm: normalizedAlgorithm.name,
}, wrappedKey);
// 4.
key = TypedArrayPrototypeGetBuffer(plainText);
break;
}
default: {
throw new DOMException(
"Not implemented",
"NotSupportedError",
);
}
}
} else if (
supportedAlgorithms["decrypt"][normalizedAlgorithm.name] !== undefined
) {
// must construct a new key, since keyUsages is ["unwrapKey"] and not ["decrypt"]
key = await this.decrypt(
normalizedAlgorithm,
constructKey(
unwrappingKey[_type],
unwrappingKey[_extractable],
["decrypt"],
unwrappingKey[_algorithm],
unwrappingKey[_handle],
),
wrappedKey,
);
} else {
throw new DOMException(
"Algorithm not supported",
"NotSupportedError",
);
}
let bytes;
// 14.
if (format !== "jwk") {
bytes = key;
} else {
const k = new Uint8Array(key);
let str = "";
for (let i = 0; i < k.length; i++) {
str += StringFromCharCode(k[i]);
}
bytes = JSONParse(str);
}
// 15.
const result = await this.importKey(
format,
bytes,
normalizedKeyAlgorithm,
extractable,
keyUsages,
);
// 16.
if (
(result[_type] == "secret" || result[_type] == "private") &&
keyUsages.length == 0
) {
throw new SyntaxError("Invalid key type.");
}
// 17.
result[_extractable] = extractable;
// 18.
result[_usages] = usageIntersection(keyUsages, recognisedUsages);
// 19.
return result;
}
/**
* @param {string} algorithm
* @param {boolean} extractable
* @param {KeyUsage[]} keyUsages
* @returns {Promise<any>}
*/
async generateKey(algorithm, extractable, keyUsages) {
webidl.assertBranded(this, SubtleCryptoPrototype);
const prefix = "Failed to execute 'generateKey' on 'SubtleCrypto'";
webidl.requiredArguments(arguments.length, 3, prefix);
algorithm = webidl.converters.AlgorithmIdentifier(
algorithm,
prefix,
"Argument 1",
);
extractable = webidl.converters["boolean"](
extractable,
prefix,
"Argument 2",
);
keyUsages = webidl.converters["sequence<KeyUsage>"](
keyUsages,
prefix,
"Argument 3",
);
const usages = keyUsages;
const normalizedAlgorithm = normalizeAlgorithm(algorithm, "generateKey");
const result = await generateKey(
normalizedAlgorithm,
extractable,
usages,
);
if (ObjectPrototypeIsPrototypeOf(CryptoKeyPrototype, result)) {
const type = result[_type];
if ((type === "secret" || type === "private") && usages.length === 0) {
throw new DOMException("Invalid key usages", "SyntaxError");
}
} else if (
ObjectPrototypeIsPrototypeOf(CryptoKeyPrototype, result.privateKey)
) {
if (result.privateKey[_usages].length === 0) {
throw new DOMException("Invalid key usages", "SyntaxError");
}
}
return result;
}
[SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) {
return `${this.constructor.name} ${inspect({}, inspectOptions)}`;
}
}
const SubtleCryptoPrototype = SubtleCrypto.prototype;
async function generateKey(normalizedAlgorithm, extractable, usages) {
const algorithmName = normalizedAlgorithm.name;
switch (algorithmName) {
case "RSASSA-PKCS1-v1_5":
case "RSA-PSS": {
// 1.
if (
ArrayPrototypeFind(
usages,
(u) => !ArrayPrototypeIncludes(["sign", "verify"], u),
) !== undefined
) {
throw new DOMException("Invalid key usages", "SyntaxError");
}
// 2.
const keyData = await op_crypto_generate_key(
{
algorithm: "RSA",
modulusLength: normalizedAlgorithm.modulusLength,
publicExponent: normalizedAlgorithm.publicExponent,
},
);
const handle = {};
WeakMapPrototypeSet(KEY_STORE, handle, {
type: "private",
data: keyData,
});
// 4-8.
const algorithm = {
name: algorithmName,
modulusLength: normalizedAlgorithm.modulusLength,
publicExponent: normalizedAlgorithm.publicExponent,
hash: normalizedAlgorithm.hash,
};
// 9-13.
const publicKey = constructKey(
"public",
true,
usageIntersection(usages, ["verify"]),
algorithm,
handle,
);
// 14-18.
const privateKey = constructKey(
"private",
extractable,
usageIntersection(usages, ["sign"]),
algorithm,
handle,
);
// 19-22.
return { publicKey, privateKey };
}
case "RSA-OAEP": {
if (
ArrayPrototypeFind(
usages,
(u) =>
!ArrayPrototypeIncludes([
"encrypt",
"decrypt",
"wrapKey",
"unwrapKey",
], u),
) !== undefined
) {
throw new DOMException("Invalid key usages", "SyntaxError");
}
// 2.
const keyData = await op_crypto_generate_key(
{
algorithm: "RSA",
modulusLength: normalizedAlgorithm.modulusLength,
publicExponent: normalizedAlgorithm.publicExponent,
},
);
const handle = {};
WeakMapPrototypeSet(KEY_STORE, handle, {
type: "private",
data: keyData,
});
// 4-8.
const algorithm = {
name: algorithmName,
modulusLength: normalizedAlgorithm.modulusLength,
publicExponent: normalizedAlgorithm.publicExponent,
hash: normalizedAlgorithm.hash,
};
// 9-13.
const publicKey = constructKey(
"public",
true,
usageIntersection(usages, ["encrypt", "wrapKey"]),
algorithm,
handle,
);
// 14-18.
const privateKey = constructKey(
"private",
extractable,
usageIntersection(usages, ["decrypt", "unwrapKey"]),
algorithm,
handle,
);
// 19-22.
return { publicKey, privateKey };
}
case "ECDSA": {
const namedCurve = normalizedAlgorithm.namedCurve;
// 1.
if (
ArrayPrototypeFind(
usages,
(u) => !ArrayPrototypeIncludes(["sign", "verify"], u),
) !== undefined
) {
throw new DOMException("Invalid key usages", "SyntaxError");
}
// 2-3.
const handle = {};
if (
ArrayPrototypeIncludes(
supportedNamedCurves,
namedCurve,
)
) {
const keyData = await op_crypto_generate_key({
algorithm: "EC",
namedCurve,
});
WeakMapPrototypeSet(KEY_STORE, handle, {
type: "private",
data: keyData,
});
} else {
throw new DOMException("Curve not supported", "NotSupportedError");
}
// 4-6.
const algorithm = {
name: algorithmName,
namedCurve,
};
// 7-11.
const publicKey = constructKey(
"public",
true,
usageIntersection(usages, ["verify"]),
algorithm,
handle,
);
// 12-16.
const privateKey = constructKey(
"private",
extractable,
usageIntersection(usages, ["sign"]),
algorithm,
handle,
);
// 17-20.
return { publicKey, privateKey };
}
case "ECDH": {
const namedCurve = normalizedAlgorithm.namedCurve;
// 1.
if (
ArrayPrototypeFind(
usages,
(u) => !ArrayPrototypeIncludes(["deriveKey", "deriveBits"], u),
) !== undefined
) {
throw new DOMException("Invalid key usages", "SyntaxError");
}
// 2-3.
const handle = {};
if (
ArrayPrototypeIncludes(
supportedNamedCurves,
namedCurve,
)
) {
const keyData = await op_crypto_generate_key({
algorithm: "EC",
namedCurve,
});
WeakMapPrototypeSet(KEY_STORE, handle, {
type: "private",
data: keyData,
});
} else {
throw new DOMException("Curve not supported", "NotSupportedError");
}
// 4-6.
const algorithm = {
name: algorithmName,
namedCurve,
};
// 7-11.
const publicKey = constructKey(
"public",
true,
usageIntersection(usages, []),
algorithm,
handle,
);
// 12-16.
const privateKey = constructKey(
"private",
extractable,
usageIntersection(usages, ["deriveKey", "deriveBits"]),
algorithm,
handle,
);
// 17-20.
return { publicKey, privateKey };
}
case "AES-CTR":
case "AES-CBC":
case "AES-GCM": {
// 1.
if (
ArrayPrototypeFind(
usages,
(u) =>
!ArrayPrototypeIncludes([
"encrypt",
"decrypt",
"wrapKey",
"unwrapKey",
], u),
) !== undefined
) {
throw new DOMException("Invalid key usages", "SyntaxError");
}
return generateKeyAES(normalizedAlgorithm, extractable, usages);
}
case "AES-KW": {
// 1.
if (
ArrayPrototypeFind(
usages,
(u) => !ArrayPrototypeIncludes(["wrapKey", "unwrapKey"], u),
) !== undefined
) {
throw new DOMException("Invalid key usages", "SyntaxError");
}
return generateKeyAES(normalizedAlgorithm, extractable, usages);
}
case "X25519": {
if (
ArrayPrototypeFind(
usages,
(u) => !ArrayPrototypeIncludes(["deriveKey", "deriveBits"], u),
) !== undefined
) {
throw new DOMException("Invalid key usages", "SyntaxError");
}
const privateKeyData = new Uint8Array(32);
const publicKeyData = new Uint8Array(32);
ops.op_crypto_generate_x25519_keypair(privateKeyData, publicKeyData);
const handle = {};
WeakMapPrototypeSet(KEY_STORE, handle, privateKeyData);
const publicHandle = {};
WeakMapPrototypeSet(KEY_STORE, publicHandle, publicKeyData);
const algorithm = {
name: algorithmName,
};
const publicKey = constructKey(
"public",
true,
usageIntersection(usages, []),
algorithm,
publicHandle,
);
const privateKey = constructKey(
"private",
extractable,
usageIntersection(usages, ["deriveKey", "deriveBits"]),
algorithm,
handle,
);
return { publicKey, privateKey };
}
case "Ed25519": {
if (
ArrayPrototypeFind(
usages,
(u) => !ArrayPrototypeIncludes(["sign", "verify"], u),
) !== undefined
) {
throw new DOMException("Invalid key usages", "SyntaxError");
}
const ED25519_SEED_LEN = 32;
const ED25519_PUBLIC_KEY_LEN = 32;
const privateKeyData = new Uint8Array(ED25519_SEED_LEN);
const publicKeyData = new Uint8Array(ED25519_PUBLIC_KEY_LEN);
if (
!ops.op_crypto_generate_ed25519_keypair(privateKeyData, publicKeyData)
) {
throw new DOMException("Failed to generate key", "OperationError");
}
const handle = {};
WeakMapPrototypeSet(KEY_STORE, handle, privateKeyData);
const publicHandle = {};
WeakMapPrototypeSet(KEY_STORE, publicHandle, publicKeyData);
const algorithm = {
name: algorithmName,
};
const publicKey = constructKey(
"public",
true,
usageIntersection(usages, ["verify"]),
algorithm,
publicHandle,
);
const privateKey = constructKey(
"private",
extractable,
usageIntersection(usages, ["sign"]),
algorithm,
handle,
);
return { publicKey, privateKey };
}
case "HMAC": {
// 1.
if (
ArrayPrototypeFind(
usages,
(u) => !ArrayPrototypeIncludes(["sign", "verify"], u),
) !== undefined
) {
throw new DOMException("Invalid key usages", "SyntaxError");
}
// 2.
let length;
if (normalizedAlgorithm.length === undefined) {
length = null;
} else if (normalizedAlgorithm.length !== 0) {
length = normalizedAlgorithm.length;
} else {
throw new DOMException("Invalid length", "OperationError");
}
// 3-4.
const keyData = await op_crypto_generate_key({
algorithm: "HMAC",
hash: normalizedAlgorithm.hash.name,
length,
});
const handle = {};
WeakMapPrototypeSet(KEY_STORE, handle, {
type: "secret",
data: keyData,
});
// 6-10.
const algorithm = {
name: algorithmName,
hash: {
name: normalizedAlgorithm.hash.name,
},
length: TypedArrayPrototypeGetByteLength(keyData) * 8,
};
// 5, 11-13.
const key = constructKey(
"secret",
extractable,
usages,
algorithm,
handle,
);
// 14.
return key;
}
}
}
function importKeyEd25519(
format,
keyData,
extractable,
keyUsages,
) {
switch (format) {
case "raw": {
// 1.
if (
ArrayPrototypeFind(
keyUsages,
(u) => !ArrayPrototypeIncludes(["verify"], u),
) !== undefined
) {
throw new DOMException("Invalid key usages", "SyntaxError");
}
const handle = {};
WeakMapPrototypeSet(KEY_STORE, handle, keyData);
// 2-3.
const algorithm = {
name: "Ed25519",
};
// 4-6.
return constructKey(
"public",
extractable,
usageIntersection(keyUsages, recognisedUsages),
algorithm,
handle,
);
}
case "spki": {
// 1.
if (
ArrayPrototypeFind(
keyUsages,
(u) => !ArrayPrototypeIncludes(["verify"], u),
) !== undefined
) {
throw new DOMException("Invalid key usages", "SyntaxError");
}
const publicKeyData = new Uint8Array(32);
if (!ops.op_crypto_import_spki_ed25519(keyData, publicKeyData)) {
throw new DOMException("Invalid key data", "DataError");
}
const handle = {};
WeakMapPrototypeSet(KEY_STORE, handle, publicKeyData);
const algorithm = {
name: "Ed25519",
};
return constructKey(
"public",
extractable,
usageIntersection(keyUsages, recognisedUsages),
algorithm,
handle,
);
}
case "pkcs8": {
// 1.
if (
ArrayPrototypeFind(
keyUsages,
(u) => !ArrayPrototypeIncludes(["sign"], u),
) !== undefined
) {
throw new DOMException("Invalid key usages", "SyntaxError");
}
const privateKeyData = new Uint8Array(32);
if (!ops.op_crypto_import_pkcs8_ed25519(keyData, privateKeyData)) {
throw new DOMException("Invalid key data", "DataError");
}
const handle = {};
WeakMapPrototypeSet(KEY_STORE, handle, privateKeyData);
const algorithm = {
name: "Ed25519",
};
return constructKey(
"private",
extractable,
usageIntersection(keyUsages, recognisedUsages),
algorithm,
handle,
);
}
case "jwk": {
// 1.
const jwk = keyData;
// 2.
if (jwk.d !== undefined) {
if (
ArrayPrototypeFind(
keyUsages,
(u) =>
!ArrayPrototypeIncludes(
["sign"],
u,
),
) !== undefined
) {
throw new DOMException("Invalid key usages", "SyntaxError");
}
} else {
if (
ArrayPrototypeFind(
keyUsages,
(u) =>
!ArrayPrototypeIncludes(
["verify"],
u,
),
) !== undefined
) {
throw new DOMException("Invalid key usages", "SyntaxError");
}
}
// 3.
if (jwk.kty !== "OKP") {
throw new DOMException("Invalid key type", "DataError");
}
// 4.
if (jwk.crv !== "Ed25519") {
throw new DOMException("Invalid curve", "DataError");
}
// 5.
if (
keyUsages.length > 0 && jwk.use !== undefined && jwk.use !== "sig"
) {
throw new DOMException("Invalid key usage", "DataError");
}
// 6.
if (jwk.key_ops !== undefined) {
if (
ArrayPrototypeFind(
jwk.key_ops,
(u) => !ArrayPrototypeIncludes(recognisedUsages, u),
) !== undefined
) {
throw new DOMException(
"'key_ops' property of JsonWebKey is invalid",
"DataError",
);
}
if (
!ArrayPrototypeEvery(
jwk.key_ops,
(u) => ArrayPrototypeIncludes(keyUsages, u),
)
) {
throw new DOMException(
"'key_ops' property of JsonWebKey is invalid",
"DataError",
);
}
}
// 7.
if (jwk.ext !== undefined && jwk.ext === false && extractable) {
throw new DOMException("Invalid key extractability", "DataError");
}
// 8.
if (jwk.d !== undefined) {
// https://www.rfc-editor.org/rfc/rfc8037#section-2
let privateKeyData;
try {
privateKeyData = ops.op_crypto_base64url_decode(jwk.d);
} catch (_) {
throw new DOMException("invalid private key data", "DataError");
}
const handle = {};
WeakMapPrototypeSet(KEY_STORE, handle, privateKeyData);
const algorithm = {
name: "Ed25519",
};
return constructKey(
"private",
extractable,
usageIntersection(keyUsages, recognisedUsages),
algorithm,
handle,
);
} else {
// https://www.rfc-editor.org/rfc/rfc8037#section-2
let publicKeyData;
try {
publicKeyData = ops.op_crypto_base64url_decode(jwk.x);
} catch (_) {
throw new DOMException("invalid public key data", "DataError");
}
const handle = {};
WeakMapPrototypeSet(KEY_STORE, handle, publicKeyData);
const algorithm = {
name: "Ed25519",
};
return constructKey(
"public",
extractable,
usageIntersection(keyUsages, recognisedUsages),
algorithm,
handle,
);
}
}
default:
throw new DOMException("Not implemented", "NotSupportedError");
}
}
function importKeyX25519(
format,
keyData,
extractable,
keyUsages,
) {
switch (format) {
case "raw": {
// 1.
if (keyUsages.length > 0) {
throw new DOMException("Invalid key usages", "SyntaxError");
}
const handle = {};
WeakMapPrototypeSet(KEY_STORE, handle, keyData);
// 2-3.
const algorithm = {
name: "X25519",
};
// 4-6.
return constructKey(
"public",
extractable,
[],
algorithm,
handle,
);
}
case "spki": {
// 1.
if (keyUsages.length > 0) {
throw new DOMException("Invalid key usages", "SyntaxError");
}
const publicKeyData = new Uint8Array(32);
if (!ops.op_crypto_import_spki_x25519(keyData, publicKeyData)) {
throw new DOMException("Invalid key data", "DataError");
}
const handle = {};
WeakMapPrototypeSet(KEY_STORE, handle, publicKeyData);
const algorithm = {
name: "X25519",
};
return constructKey(
"public",
extractable,
[],
algorithm,
handle,
);
}
case "pkcs8": {
// 1.
if (
ArrayPrototypeFind(
keyUsages,
(u) => !ArrayPrototypeIncludes(["deriveKey", "deriveBits"], u),
) !== undefined
) {
throw new DOMException("Invalid key usages", "SyntaxError");
}
const privateKeyData = new Uint8Array(32);
if (!ops.op_crypto_import_pkcs8_x25519(keyData, privateKeyData)) {
throw new DOMException("Invalid key data", "DataError");
}
const handle = {};
WeakMapPrototypeSet(KEY_STORE, handle, privateKeyData);
const algorithm = {
name: "X25519",
};
return constructKey(
"private",
extractable,
usageIntersection(keyUsages, recognisedUsages),
algorithm,
handle,
);
}
case "jwk": {
// 1.
const jwk = keyData;
// 2.
if (jwk.d !== undefined) {
if (
ArrayPrototypeFind(
keyUsages,
(u) =>
!ArrayPrototypeIncludes(
["deriveKey", "deriveBits"],
u,
),
) !== undefined
) {
throw new DOMException("Invalid key usages", "SyntaxError");
}
}
// 3.
if (jwk.d === undefined && keyUsages.length > 0) {
throw new DOMException("Invalid key usages", "SyntaxError");
}
// 4.
if (jwk.kty !== "OKP") {
throw new DOMException("Invalid key type", "DataError");
}
// 5.
if (jwk.crv !== "X25519") {
throw new DOMException("Invalid curve", "DataError");
}
// 6.
if (keyUsages.length > 0 && jwk.use !== undefined) {
if (jwk.use !== "enc") {
throw new DOMException("Invalid key use", "DataError");
}
}
// 7.
if (jwk.key_ops !== undefined) {
if (
ArrayPrototypeFind(
jwk.key_ops,
(u) => !ArrayPrototypeIncludes(recognisedUsages, u),
) !== undefined
) {
throw new DOMException(
"'key_ops' property of JsonWebKey is invalid",
"DataError",
);
}
if (
!ArrayPrototypeEvery(
jwk.key_ops,
(u) => ArrayPrototypeIncludes(keyUsages, u),
)
) {
throw new DOMException(
"'key_ops' property of JsonWebKey is invalid",
"DataError",
);
}
}
// 8.
if (jwk.ext !== undefined && jwk.ext === false && extractable) {
throw new DOMException("Invalid key extractability", "DataError");
}
// 9.
if (jwk.d !== undefined) {
// https://www.rfc-editor.org/rfc/rfc8037#section-2
const privateKeyData = ops.op_crypto_base64url_decode(jwk.d);
const handle = {};
WeakMapPrototypeSet(KEY_STORE, handle, privateKeyData);
const algorithm = {
name: "X25519",
};
return constructKey(
"private",
extractable,
usageIntersection(keyUsages, ["deriveKey", "deriveBits"]),
algorithm,
handle,
);
} else {
// https://www.rfc-editor.org/rfc/rfc8037#section-2
const publicKeyData = ops.op_crypto_base64url_decode(jwk.x);
const handle = {};
WeakMapPrototypeSet(KEY_STORE, handle, publicKeyData);
const algorithm = {
name: "X25519",
};
return constructKey(
"public",
extractable,
[],
algorithm,
handle,
);
}
}
default:
throw new DOMException("Not implemented", "NotSupportedError");
}
}
function exportKeyAES(
format,
key,
innerKey,
) {
switch (format) {
// 2.
case "raw": {
// 1.
const data = innerKey.data;
// 2.
return TypedArrayPrototypeGetBuffer(data);
}
case "jwk": {
// 1-2.
const jwk = {
kty: "oct",
};
// 3.
const data = ops.op_crypto_export_key({
format: "jwksecret",
algorithm: "AES",
}, innerKey);
ObjectAssign(jwk, data);
// 4.
const algorithm = key[_algorithm];
switch (algorithm.length) {
case 128:
jwk.alg = aesJwkAlg[algorithm.name][128];
break;
case 192:
jwk.alg = aesJwkAlg[algorithm.name][192];
break;
case 256:
jwk.alg = aesJwkAlg[algorithm.name][256];
break;
default:
throw new DOMException(
"Invalid key length",
"NotSupportedError",
);
}
// 5.
jwk.key_ops = key.usages;
// 6.
jwk.ext = key[_extractable];
// 7.
return jwk;
}
default:
throw new DOMException("Not implemented", "NotSupportedError");
}
}
function importKeyAES(
format,
normalizedAlgorithm,
keyData,
extractable,
keyUsages,
supportedKeyUsages,
) {
// 1.
if (
ArrayPrototypeFind(
keyUsages,
(u) => !ArrayPrototypeIncludes(supportedKeyUsages, u),
) !== undefined
) {
throw new DOMException("Invalid key usages", "SyntaxError");
}
const algorithmName = normalizedAlgorithm.name;
// 2.
let data = keyData;
switch (format) {
case "raw": {
// 2.
if (
!ArrayPrototypeIncludes(
[128, 192, 256],
TypedArrayPrototypeGetByteLength(keyData) * 8,
)
) {
throw new DOMException("Invalid key length", "DataError");
}
break;
}
case "jwk": {
// 1.
const jwk = keyData;
// 2.
if (jwk.kty !== "oct") {
throw new DOMException(
"'kty' property of JsonWebKey must be 'oct'",
"DataError",
);
}
// Section 6.4.1 of RFC7518
if (jwk.k === undefined) {
throw new DOMException(
"'k' property of JsonWebKey must be present",
"DataError",
);
}
// 4.
const { rawData } = ops.op_crypto_import_key(
{ algorithm: "AES" },
{ jwkSecret: jwk },
);
data = rawData.data;
// 5.
switch (TypedArrayPrototypeGetByteLength(data) * 8) {
case 128:
if (
jwk.alg !== undefined &&
jwk.alg !== aesJwkAlg[algorithmName][128]
) {
throw new DOMException("Invalid algorithm", "DataError");
}
break;
case 192:
if (
jwk.alg !== undefined &&
jwk.alg !== aesJwkAlg[algorithmName][192]
) {
throw new DOMException("Invalid algorithm", "DataError");
}
break;
case 256:
if (
jwk.alg !== undefined &&
jwk.alg !== aesJwkAlg[algorithmName][256]
) {
throw new DOMException("Invalid algorithm", "DataError");
}
break;
default:
throw new DOMException(
"Invalid key length",
"DataError",
);
}
// 6.
if (
keyUsages.length > 0 && jwk.use !== undefined && jwk.use !== "enc"
) {
throw new DOMException("Invalid key usages", "DataError");
}
// 7.
// Section 4.3 of RFC7517
if (jwk.key_ops !== undefined) {
if (
ArrayPrototypeFind(
jwk.key_ops,
(u) => !ArrayPrototypeIncludes(recognisedUsages, u),
) !== undefined
) {
throw new DOMException(
"'key_ops' property of JsonWebKey is invalid",
"DataError",
);
}
if (
!ArrayPrototypeEvery(
jwk.key_ops,
(u) => ArrayPrototypeIncludes(keyUsages, u),
)
) {
throw new DOMException(
"'key_ops' property of JsonWebKey is invalid",
"DataError",
);
}
}
// 8.
if (jwk.ext === false && extractable === true) {
throw new DOMException(
"'ext' property of JsonWebKey must not be false if extractable is true",
"DataError",
);
}
break;
}
default:
throw new DOMException("Not implemented", "NotSupportedError");
}
const handle = {};
WeakMapPrototypeSet(KEY_STORE, handle, {
type: "secret",
data,
});
// 4-7.
const algorithm = {
name: algorithmName,
length: TypedArrayPrototypeGetByteLength(data) * 8,
};
const key = constructKey(
"secret",
extractable,
usageIntersection(keyUsages, recognisedUsages),
algorithm,
handle,
);
// 8.
return key;
}
function importKeyHMAC(
format,
normalizedAlgorithm,
keyData,
extractable,
keyUsages,
) {
// 2.
if (
ArrayPrototypeFind(
keyUsages,
(u) => !ArrayPrototypeIncludes(["sign", "verify"], u),
) !== undefined
) {
throw new DOMException("Invalid key usages", "SyntaxError");
}
// 3.
let hash;
let data;
// 4. https://w3c.github.io/webcrypto/#hmac-operations
switch (format) {
case "raw": {
data = keyData;
hash = normalizedAlgorithm.hash;
break;
}
case "jwk": {
const jwk = keyData;
// 2.
if (jwk.kty !== "oct") {
throw new DOMException(
"'kty' property of JsonWebKey must be 'oct'",
"DataError",
);
}
// Section 6.4.1 of RFC7518
if (jwk.k === undefined) {
throw new DOMException(
"'k' property of JsonWebKey must be present",
"DataError",
);
}
// 4.
const { rawData } = ops.op_crypto_import_key(
{ algorithm: "HMAC" },
{ jwkSecret: jwk },
);
data = rawData.data;
// 5.
hash = normalizedAlgorithm.hash;
// 6.
switch (hash.name) {
case "SHA-1": {
if (jwk.alg !== undefined && jwk.alg !== "HS1") {
throw new DOMException(
"'alg' property of JsonWebKey must be 'HS1'",
"DataError",
);
}
break;
}
case "SHA-256": {
if (jwk.alg !== undefined && jwk.alg !== "HS256") {
throw new DOMException(
"'alg' property of JsonWebKey must be 'HS256'",
"DataError",
);
}
break;
}
case "SHA-384": {
if (jwk.alg !== undefined && jwk.alg !== "HS384") {
throw new DOMException(
"'alg' property of JsonWebKey must be 'HS384'",
"DataError",
);
}
break;
}
case "SHA-512": {
if (jwk.alg !== undefined && jwk.alg !== "HS512") {
throw new DOMException(
"'alg' property of JsonWebKey must be 'HS512'",
"DataError",
);
}
break;
}
default:
throw new TypeError("unreachable");
}
// 7.
if (
keyUsages.length > 0 && jwk.use !== undefined && jwk.use !== "sig"
) {
throw new DOMException(
"'use' property of JsonWebKey must be 'sig'",
"DataError",
);
}
// 8.
// Section 4.3 of RFC7517
if (jwk.key_ops !== undefined) {
if (
ArrayPrototypeFind(
jwk.key_ops,
(u) => !ArrayPrototypeIncludes(recognisedUsages, u),
) !== undefined
) {
throw new DOMException(
"'key_ops' property of JsonWebKey is invalid",
"DataError",
);
}
if (
!ArrayPrototypeEvery(
jwk.key_ops,
(u) => ArrayPrototypeIncludes(keyUsages, u),
)
) {
throw new DOMException(
"'key_ops' property of JsonWebKey is invalid",
"DataError",
);
}
}
// 9.
if (jwk.ext === false && extractable === true) {
throw new DOMException(
"'ext' property of JsonWebKey must not be false if extractable is true",
"DataError",
);
}
break;
}
default:
throw new DOMException("Not implemented", "NotSupportedError");
}
// 5.
let length = TypedArrayPrototypeGetByteLength(data) * 8;
// 6.
if (length === 0) {
throw new DOMException("Key length is zero", "DataError");
}
// 7.
if (normalizedAlgorithm.length !== undefined) {
if (
normalizedAlgorithm.length > length ||
normalizedAlgorithm.length <= (length - 8)
) {
throw new DOMException(
"Key length is invalid",
"DataError",
);
}
length = normalizedAlgorithm.length;
}
const handle = {};
WeakMapPrototypeSet(KEY_STORE, handle, {
type: "secret",
data,
});
const algorithm = {
name: "HMAC",
length,
hash,
};
const key = constructKey(
"secret",
extractable,
usageIntersection(keyUsages, recognisedUsages),
algorithm,
handle,
);
return key;
}
function importKeyEC(
format,
normalizedAlgorithm,
keyData,
extractable,
keyUsages,
) {
const supportedUsages = SUPPORTED_KEY_USAGES[normalizedAlgorithm.name];
switch (format) {
case "raw": {
// 1.
if (
!ArrayPrototypeIncludes(
supportedNamedCurves,
normalizedAlgorithm.namedCurve,
)
) {
throw new DOMException(
"Invalid namedCurve",
"DataError",
);
}
// 2.
if (
ArrayPrototypeFind(
keyUsages,
(u) =>
!ArrayPrototypeIncludes(
SUPPORTED_KEY_USAGES[normalizedAlgorithm.name].public,
u,
),
) !== undefined
) {
throw new DOMException("Invalid key usages", "SyntaxError");
}
// 3.
const { rawData } = ops.op_crypto_import_key({
algorithm: normalizedAlgorithm.name,
namedCurve: normalizedAlgorithm.namedCurve,
}, { raw: keyData });
const handle = {};
WeakMapPrototypeSet(KEY_STORE, handle, rawData);
// 4-5.
const algorithm = {
name: normalizedAlgorithm.name,
namedCurve: normalizedAlgorithm.namedCurve,
};
// 6-8.
const key = constructKey(
"public",
extractable,
usageIntersection(keyUsages, recognisedUsages),
algorithm,
handle,
);
return key;
}
case "pkcs8": {
// 1.
if (
ArrayPrototypeFind(
keyUsages,
(u) =>
!ArrayPrototypeIncludes(
SUPPORTED_KEY_USAGES[normalizedAlgorithm.name].private,
u,
),
) !== undefined
) {
throw new DOMException("Invalid key usages", "SyntaxError");
}
// 2-9.
const { rawData } = ops.op_crypto_import_key({
algorithm: normalizedAlgorithm.name,
namedCurve: normalizedAlgorithm.namedCurve,
}, { pkcs8: keyData });
const handle = {};
WeakMapPrototypeSet(KEY_STORE, handle, rawData);
const algorithm = {
name: normalizedAlgorithm.name,
namedCurve: normalizedAlgorithm.namedCurve,
};
const key = constructKey(
"private",
extractable,
usageIntersection(keyUsages, recognisedUsages),
algorithm,
handle,
);
return key;
}
case "spki": {
// 1.
if (normalizedAlgorithm.name == "ECDSA") {
if (
ArrayPrototypeFind(
keyUsages,
(u) =>
!ArrayPrototypeIncludes(
SUPPORTED_KEY_USAGES[normalizedAlgorithm.name].public,
u,
),
) !== undefined
) {
throw new DOMException("Invalid key usages", "SyntaxError");
}
} else if (keyUsages.length != 0) {
throw new DOMException("Key usage must be empty", "SyntaxError");
}
// 2-12
const { rawData } = ops.op_crypto_import_key({
algorithm: normalizedAlgorithm.name,
namedCurve: normalizedAlgorithm.namedCurve,
}, { spki: keyData });
const handle = {};
WeakMapPrototypeSet(KEY_STORE, handle, rawData);
const algorithm = {
name: normalizedAlgorithm.name,
namedCurve: normalizedAlgorithm.namedCurve,
};
// 6-8.
const key = constructKey(
"public",
extractable,
usageIntersection(keyUsages, recognisedUsages),
algorithm,
handle,
);
return key;
}
case "jwk": {
const jwk = keyData;
const keyType = (jwk.d !== undefined) ? "private" : "public";
// 2.
if (
ArrayPrototypeFind(
keyUsages,
(u) => !ArrayPrototypeIncludes(supportedUsages[keyType], u),
) !== undefined
) {
throw new DOMException("Invalid key usages", "SyntaxError");
}
// 3.
if (jwk.kty !== "EC") {
throw new DOMException(
"'kty' property of JsonWebKey must be 'EC'",
"DataError",
);
}
// 4.
if (
keyUsages.length > 0 && jwk.use !== undefined &&
jwk.use !== supportedUsages.jwkUse
) {
throw new DOMException(
`'use' property of JsonWebKey must be '${supportedUsages.jwkUse}'`,
"DataError",
);
}
// 5.
// Section 4.3 of RFC7517
if (jwk.key_ops !== undefined) {
if (
ArrayPrototypeFind(
jwk.key_ops,
(u) => !ArrayPrototypeIncludes(recognisedUsages, u),
) !== undefined
) {
throw new DOMException(
"'key_ops' member of JsonWebKey is invalid",
"DataError",
);
}
if (
!ArrayPrototypeEvery(
jwk.key_ops,
(u) => ArrayPrototypeIncludes(keyUsages, u),
)
) {
throw new DOMException(
"'key_ops' member of JsonWebKey is invalid",
"DataError",
);
}
}
// 6.
if (jwk.ext === false && extractable === true) {
throw new DOMException(
"'ext' property of JsonWebKey must not be false if extractable is true",
"DataError",
);
}
// 9.
if (jwk.alg !== undefined && normalizedAlgorithm.name == "ECDSA") {
let algNamedCurve;
switch (jwk.alg) {
case "ES256": {
algNamedCurve = "P-256";
break;
}
case "ES384": {
algNamedCurve = "P-384";
break;
}
case "ES512": {
algNamedCurve = "P-521";
break;
}
default:
throw new DOMException(
"Curve algorithm not supported",
"DataError",
);
}
if (algNamedCurve) {
if (algNamedCurve !== normalizedAlgorithm.namedCurve) {
throw new DOMException(
"Mismatched curve algorithm",
"DataError",
);
}
}
}
// Validate that this is a valid public key.
if (jwk.x === undefined) {
throw new DOMException(
"'x' property of JsonWebKey is required for EC keys",
"DataError",
);
}
if (jwk.y === undefined) {
throw new DOMException(
"'y' property of JsonWebKey is required for EC keys",
"DataError",
);
}
if (jwk.d !== undefined) {
// it's also a Private key
const { rawData } = ops.op_crypto_import_key({
algorithm: normalizedAlgorithm.name,
namedCurve: normalizedAlgorithm.namedCurve,
}, { jwkPrivateEc: jwk });
const handle = {};
WeakMapPrototypeSet(KEY_STORE, handle, rawData);
const algorithm = {
name: normalizedAlgorithm.name,
namedCurve: normalizedAlgorithm.namedCurve,
};
const key = constructKey(
"private",
extractable,
usageIntersection(keyUsages, recognisedUsages),
algorithm,
handle,
);
return key;
} else {
const { rawData } = ops.op_crypto_import_key({
algorithm: normalizedAlgorithm.name,
namedCurve: normalizedAlgorithm.namedCurve,
}, { jwkPublicEc: jwk });
const handle = {};
WeakMapPrototypeSet(KEY_STORE, handle, rawData);
const algorithm = {
name: normalizedAlgorithm.name,
namedCurve: normalizedAlgorithm.namedCurve,
};
const key = constructKey(
"public",
extractable,
usageIntersection(keyUsages, recognisedUsages),
algorithm,
handle,
);
return key;
}
}
default:
throw new DOMException("Not implemented", "NotSupportedError");
}
}
const SUPPORTED_KEY_USAGES = {
"RSASSA-PKCS1-v1_5": {
public: ["verify"],
private: ["sign"],
jwkUse: "sig",
},
"RSA-PSS": {
public: ["verify"],
private: ["sign"],
jwkUse: "sig",
},
"RSA-OAEP": {
public: ["encrypt", "wrapKey"],
private: ["decrypt", "unwrapKey"],
jwkUse: "enc",
},
"ECDSA": {
public: ["verify"],
private: ["sign"],
jwkUse: "sig",
},
"ECDH": {
public: [],
private: ["deriveKey", "deriveBits"],
jwkUse: "enc",
},
};
function importKeyRSA(
format,
normalizedAlgorithm,
keyData,
extractable,
keyUsages,
) {
switch (format) {
case "pkcs8": {
// 1.
if (
ArrayPrototypeFind(
keyUsages,
(u) =>
!ArrayPrototypeIncludes(
SUPPORTED_KEY_USAGES[normalizedAlgorithm.name].private,
u,
),
) !== undefined
) {
throw new DOMException("Invalid key usages", "SyntaxError");
}
// 2-9.
const { modulusLength, publicExponent, rawData } = ops
.op_crypto_import_key(
{
algorithm: normalizedAlgorithm.name,
// Needed to perform step 7 without normalization.
hash: normalizedAlgorithm.hash.name,
},
{ pkcs8: keyData },
);
const handle = {};
WeakMapPrototypeSet(KEY_STORE, handle, rawData);
const algorithm = {
name: normalizedAlgorithm.name,
modulusLength,
publicExponent,
hash: normalizedAlgorithm.hash,
};
const key = constructKey(
"private",
extractable,
usageIntersection(keyUsages, recognisedUsages),
algorithm,
handle,
);
return key;
}
case "spki": {
// 1.
if (
ArrayPrototypeFind(
keyUsages,
(u) =>
!ArrayPrototypeIncludes(
SUPPORTED_KEY_USAGES[normalizedAlgorithm.name].public,
u,
),
) !== undefined
) {
throw new DOMException("Invalid key usages", "SyntaxError");
}
// 2-9.
const { modulusLength, publicExponent, rawData } = ops
.op_crypto_import_key(
{
algorithm: normalizedAlgorithm.name,
// Needed to perform step 7 without normalization.
hash: normalizedAlgorithm.hash.name,
},
{ spki: keyData },
);
const handle = {};
WeakMapPrototypeSet(KEY_STORE, handle, rawData);
const algorithm = {
name: normalizedAlgorithm.name,
modulusLength,
publicExponent,
hash: normalizedAlgorithm.hash,
};
const key = constructKey(
"public",
extractable,
usageIntersection(keyUsages, recognisedUsages),
algorithm,
handle,
);
return key;
}
case "jwk": {
// 1.
const jwk = keyData;
// 2.
if (jwk.d !== undefined) {
if (
ArrayPrototypeFind(
keyUsages,
(u) =>
!ArrayPrototypeIncludes(
SUPPORTED_KEY_USAGES[normalizedAlgorithm.name].private,
u,
),
) !== undefined
) {
throw new DOMException("Invalid key usages", "SyntaxError");
}
} else if (
ArrayPrototypeFind(
keyUsages,
(u) =>
!ArrayPrototypeIncludes(
SUPPORTED_KEY_USAGES[normalizedAlgorithm.name].public,
u,
),
) !== undefined
) {
throw new DOMException("Invalid key usages", "SyntaxError");
}
// 3.
if (StringPrototypeToUpperCase(jwk.kty) !== "RSA") {
throw new DOMException(
"'kty' property of JsonWebKey must be 'RSA'",
"DataError",
);
}
// 4.
if (
keyUsages.length > 0 && jwk.use !== undefined &&
StringPrototypeToLowerCase(jwk.use) !==
SUPPORTED_KEY_USAGES[normalizedAlgorithm.name].jwkUse
) {
throw new DOMException(
`'use' property of JsonWebKey must be '${
SUPPORTED_KEY_USAGES[normalizedAlgorithm.name].jwkUse
}'`,
"DataError",
);
}
// 5.
if (jwk.key_ops !== undefined) {
if (
ArrayPrototypeFind(
jwk.key_ops,
(u) => !ArrayPrototypeIncludes(recognisedUsages, u),
) !== undefined
) {
throw new DOMException(
"'key_ops' property of JsonWebKey is invalid",
"DataError",
);
}
if (
!ArrayPrototypeEvery(
jwk.key_ops,
(u) => ArrayPrototypeIncludes(keyUsages, u),
)
) {
throw new DOMException(
"'key_ops' property of JsonWebKey is invalid",
"DataError",
);
}
}
if (jwk.ext === false && extractable === true) {
throw new DOMException(
"'ext' property of JsonWebKey must not be false if extractable is true",
"DataError",
);
}
// 7.
let hash;
// 8.
if (normalizedAlgorithm.name === "RSASSA-PKCS1-v1_5") {
switch (jwk.alg) {
case undefined:
hash = undefined;
break;
case "RS1":
hash = "SHA-1";
break;
case "RS256":
hash = "SHA-256";
break;
case "RS384":
hash = "SHA-384";
break;
case "RS512":
hash = "SHA-512";
break;
default:
throw new DOMException(
`'alg' property of JsonWebKey must be one of 'RS1', 'RS256', 'RS384', 'RS512'`,
"DataError",
);
}
} else if (normalizedAlgorithm.name === "RSA-PSS") {
switch (jwk.alg) {
case undefined:
hash = undefined;
break;
case "PS1":
hash = "SHA-1";
break;
case "PS256":
hash = "SHA-256";
break;
case "PS384":
hash = "SHA-384";
break;
case "PS512":
hash = "SHA-512";
break;
default:
throw new DOMException(
`'alg' property of JsonWebKey must be one of 'PS1', 'PS256', 'PS384', 'PS512'`,
"DataError",
);
}
} else {
switch (jwk.alg) {
case undefined:
hash = undefined;
break;
case "RSA-OAEP":
hash = "SHA-1";
break;
case "RSA-OAEP-256":
hash = "SHA-256";
break;
case "RSA-OAEP-384":
hash = "SHA-384";
break;
case "RSA-OAEP-512":
hash = "SHA-512";
break;
default:
throw new DOMException(
`'alg' property of JsonWebKey must be one of 'RSA-OAEP', 'RSA-OAEP-256', 'RSA-OAEP-384', or 'RSA-OAEP-512'`,
"DataError",
);
}
}
// 9.
if (hash !== undefined) {
// 9.1.
const normalizedHash = normalizeAlgorithm(hash, "digest");
// 9.2.
if (normalizedHash.name !== normalizedAlgorithm.hash.name) {
throw new DOMException(
`'alg' property of JsonWebKey must be '${normalizedAlgorithm.name}'`,
"DataError",
);
}
}
// 10.
if (jwk.d !== undefined) {
// Private key
const optimizationsPresent = jwk.p !== undefined ||
jwk.q !== undefined || jwk.dp !== undefined ||
jwk.dq !== undefined || jwk.qi !== undefined;
if (optimizationsPresent) {
if (jwk.q === undefined) {
throw new DOMException(
"'q' property of JsonWebKey is required for private keys",
"DataError",
);
}
if (jwk.dp === undefined) {
throw new DOMException(
"'dp' property of JsonWebKey is required for private keys",
"DataError",
);
}
if (jwk.dq === undefined) {
throw new DOMException(
"'dq' property of JsonWebKey is required for private keys",
"DataError",
);
}
if (jwk.qi === undefined) {
throw new DOMException(
"'qi' property of JsonWebKey is required for private keys",
"DataError",
);
}
if (jwk.oth !== undefined) {
throw new DOMException(
"'oth' property of JsonWebKey is not supported",
"NotSupportedError",
);
}
} else {
throw new DOMException(
"only optimized private keys are supported",
"NotSupportedError",
);
}
const { modulusLength, publicExponent, rawData } = ops
.op_crypto_import_key(
{
algorithm: normalizedAlgorithm.name,
hash: normalizedAlgorithm.hash.name,
},
{ jwkPrivateRsa: jwk },
);
const handle = {};
WeakMapPrototypeSet(KEY_STORE, handle, rawData);
const algorithm = {
name: normalizedAlgorithm.name,
modulusLength,
publicExponent,
hash: normalizedAlgorithm.hash,
};
const key = constructKey(
"private",
extractable,
usageIntersection(keyUsages, recognisedUsages),
algorithm,
handle,
);
return key;
} else {
// Validate that this is a valid public key.
if (jwk.n === undefined) {
throw new DOMException(
"'n' property of JsonWebKey is required for public keys",
"DataError",
);
}
if (jwk.e === undefined) {
throw new DOMException(
"'e' property of JsonWebKey is required for public keys",
"DataError",
);
}
const { modulusLength, publicExponent, rawData } = ops
.op_crypto_import_key(
{
algorithm: normalizedAlgorithm.name,
hash: normalizedAlgorithm.hash.name,
},
{ jwkPublicRsa: jwk },
);
const handle = {};
WeakMapPrototypeSet(KEY_STORE, handle, rawData);
const algorithm = {
name: normalizedAlgorithm.name,
modulusLength,
publicExponent,
hash: normalizedAlgorithm.hash,
};
const key = constructKey(
"public",
extractable,
usageIntersection(keyUsages, recognisedUsages),
algorithm,
handle,
);
return key;
}
}
default:
throw new DOMException("Not implemented", "NotSupportedError");
}
}
function importKeyHKDF(
format,
keyData,
extractable,
keyUsages,
) {
if (format !== "raw") {
throw new DOMException("Format not supported", "NotSupportedError");
}
// 1.
if (
ArrayPrototypeFind(
keyUsages,
(u) => !ArrayPrototypeIncludes(["deriveKey", "deriveBits"], u),
) !== undefined
) {
throw new DOMException("Invalid key usages", "SyntaxError");
}
// 2.
if (extractable !== false) {
throw new DOMException(
"Key must not be extractable",
"SyntaxError",
);
}
// 3.
const handle = {};
WeakMapPrototypeSet(KEY_STORE, handle, {
type: "secret",
data: keyData,
});
// 4-8.
const algorithm = {
name: "HKDF",
};
const key = constructKey(
"secret",
false,
usageIntersection(keyUsages, recognisedUsages),
algorithm,
handle,
);
// 9.
return key;
}
function importKeyPBKDF2(
format,
keyData,
extractable,
keyUsages,
) {
// 1.
if (format !== "raw") {
throw new DOMException("Format not supported", "NotSupportedError");
}
// 2.
if (
ArrayPrototypeFind(
keyUsages,
(u) => !ArrayPrototypeIncludes(["deriveKey", "deriveBits"], u),
) !== undefined
) {
throw new DOMException("Invalid key usages", "SyntaxError");
}
// 3.
if (extractable !== false) {
throw new DOMException(
"Key must not be extractable",
"SyntaxError",
);
}
// 4.
const handle = {};
WeakMapPrototypeSet(KEY_STORE, handle, {
type: "secret",
data: keyData,
});
// 5-9.
const algorithm = {
name: "PBKDF2",
};
const key = constructKey(
"secret",
false,
usageIntersection(keyUsages, recognisedUsages),
algorithm,
handle,
);
// 10.
return key;
}
function exportKeyHMAC(format, key, innerKey) {
// 1.
if (innerKey == null) {
throw new DOMException("Key is not available", "OperationError");
}
switch (format) {
// 3.
case "raw": {
const bits = innerKey.data;
// TODO(petamoriken): Uint8Array doesn't have push method
// for (let _i = 7 & (8 - bits.length % 8); _i > 0; _i--) {
// bits.push(0);
// }
// 4-5.
return TypedArrayPrototypeGetBuffer(bits);
}
case "jwk": {
// 1-2.
const jwk = {
kty: "oct",
};
// 3.
const data = ops.op_crypto_export_key({
format: "jwksecret",
algorithm: key[_algorithm].name,
}, innerKey);
jwk.k = data.k;
// 4.
const algorithm = key[_algorithm];
// 5.
const hash = algorithm.hash;
// 6.
switch (hash.name) {
case "SHA-1":
jwk.alg = "HS1";
break;
case "SHA-256":
jwk.alg = "HS256";
break;
case "SHA-384":
jwk.alg = "HS384";
break;
case "SHA-512":
jwk.alg = "HS512";
break;
default:
throw new DOMException(
"Hash algorithm not supported",
"NotSupportedError",
);
}
// 7.
jwk.key_ops = key.usages;
// 8.
jwk.ext = key[_extractable];
// 9.
return jwk;
}
default:
throw new DOMException("Not implemented", "NotSupportedError");
}
}
function exportKeyRSA(format, key, innerKey) {
switch (format) {
case "pkcs8": {
// 1.
if (key[_type] !== "private") {
throw new DOMException(
"Key is not a private key",
"InvalidAccessError",
);
}
// 2.
const data = ops.op_crypto_export_key({
algorithm: key[_algorithm].name,
format: "pkcs8",
}, innerKey);
// 3.
return TypedArrayPrototypeGetBuffer(data);
}
case "spki": {
// 1.
if (key[_type] !== "public") {
throw new DOMException(
"Key is not a public key",
"InvalidAccessError",
);
}
// 2.
const data = ops.op_crypto_export_key({
algorithm: key[_algorithm].name,
format: "spki",
}, innerKey);
// 3.
return TypedArrayPrototypeGetBuffer(data);
}
case "jwk": {
// 1-2.
const jwk = {
kty: "RSA",
};
// 3.
const hash = key[_algorithm].hash.name;
// 4.
if (key[_algorithm].name === "RSASSA-PKCS1-v1_5") {
switch (hash) {
case "SHA-1":
jwk.alg = "RS1";
break;
case "SHA-256":
jwk.alg = "RS256";
break;
case "SHA-384":
jwk.alg = "RS384";
break;
case "SHA-512":
jwk.alg = "RS512";
break;
default:
throw new DOMException(
"Hash algorithm not supported",
"NotSupportedError",
);
}
} else if (key[_algorithm].name === "RSA-PSS") {
switch (hash) {
case "SHA-1":
jwk.alg = "PS1";
break;
case "SHA-256":
jwk.alg = "PS256";
break;
case "SHA-384":
jwk.alg = "PS384";
break;
case "SHA-512":
jwk.alg = "PS512";
break;
default:
throw new DOMException(
"Hash algorithm not supported",
"NotSupportedError",
);
}
} else {
switch (hash) {
case "SHA-1":
jwk.alg = "RSA-OAEP";
break;
case "SHA-256":
jwk.alg = "RSA-OAEP-256";
break;
case "SHA-384":
jwk.alg = "RSA-OAEP-384";
break;
case "SHA-512":
jwk.alg = "RSA-OAEP-512";
break;
default:
throw new DOMException(
"Hash algorithm not supported",
"NotSupportedError",
);
}
}
// 5-6.
const data = ops.op_crypto_export_key({
format: key[_type] === "private" ? "jwkprivate" : "jwkpublic",
algorithm: key[_algorithm].name,
}, innerKey);
ObjectAssign(jwk, data);
// 7.
jwk.key_ops = key.usages;
// 8.
jwk.ext = key[_extractable];
return jwk;
}
default:
throw new DOMException("Not implemented", "NotSupportedError");
}
}
function exportKeyEd25519(format, key, innerKey) {
switch (format) {
case "raw": {
// 1.
if (key[_type] !== "public") {
throw new DOMException(
"Key is not a public key",
"InvalidAccessError",
);
}
// 2-3.
return TypedArrayPrototypeGetBuffer(innerKey);
}
case "spki": {
// 1.
if (key[_type] !== "public") {
throw new DOMException(
"Key is not a public key",
"InvalidAccessError",
);
}
const spkiDer = ops.op_crypto_export_spki_ed25519(innerKey);
return TypedArrayPrototypeGetBuffer(spkiDer);
}
case "pkcs8": {
// 1.
if (key[_type] !== "private") {
throw new DOMException(
"Key is not a public key",
"InvalidAccessError",
);
}
const pkcs8Der = ops.op_crypto_export_pkcs8_ed25519(
new Uint8Array([0x04, 0x22, ...new SafeArrayIterator(innerKey)]),
);
pkcs8Der[15] = 0x20;
return TypedArrayPrototypeGetBuffer(pkcs8Der);
}
case "jwk": {
const x = key[_type] === "private"
? ops.op_crypto_jwk_x_ed25519(innerKey)
: ops.op_crypto_base64url_encode(innerKey);
const jwk = {
kty: "OKP",
crv: "Ed25519",
x,
"key_ops": key.usages,
ext: key[_extractable],
};
if (key[_type] === "private") {
jwk.d = ops.op_crypto_base64url_encode(innerKey);
}
return jwk;
}
default:
throw new DOMException("Not implemented", "NotSupportedError");
}
}
function exportKeyX25519(format, key, innerKey) {
switch (format) {
case "raw": {
// 1.
if (key[_type] !== "public") {
throw new DOMException(
"Key is not a public key",
"InvalidAccessError",
);
}
// 2-3.
return TypedArrayPrototypeGetBuffer(innerKey);
}
case "spki": {
// 1.
if (key[_type] !== "public") {
throw new DOMException(
"Key is not a public key",
"InvalidAccessError",
);
}
const spkiDer = ops.op_crypto_export_spki_x25519(innerKey);
return TypedArrayPrototypeGetBuffer(spkiDer);
}
case "pkcs8": {
// 1.
if (key[_type] !== "private") {
throw new DOMException(
"Key is not a public key",
"InvalidAccessError",
);
}
const pkcs8Der = ops.op_crypto_export_pkcs8_x25519(
new Uint8Array([0x04, 0x22, ...new SafeArrayIterator(innerKey)]),
);
pkcs8Der[15] = 0x20;
return TypedArrayPrototypeGetBuffer(pkcs8Der);
}
case "jwk": {
if (key[_type] === "private") {
throw new DOMException("Not implemented", "NotSupportedError");
}
const x = ops.op_crypto_base64url_encode(innerKey);
const jwk = {
kty: "OKP",
crv: "X25519",
x,
"key_ops": key.usages,
ext: key[_extractable],
};
return jwk;
}
default:
throw new DOMException("Not implemented", "NotSupportedError");
}
}
function exportKeyEC(format, key, innerKey) {
switch (format) {
case "raw": {
// 1.
if (key[_type] !== "public") {
throw new DOMException(
"Key is not a public key",
"InvalidAccessError",
);
}
// 2.
const data = ops.op_crypto_export_key({
algorithm: key[_algorithm].name,
namedCurve: key[_algorithm].namedCurve,
format: "raw",
}, innerKey);
return TypedArrayPrototypeGetBuffer(data);
}
case "pkcs8": {
// 1.
if (key[_type] !== "private") {
throw new DOMException(
"Key is not a private key",
"InvalidAccessError",
);
}
// 2.
const data = ops.op_crypto_export_key({
algorithm: key[_algorithm].name,
namedCurve: key[_algorithm].namedCurve,
format: "pkcs8",
}, innerKey);
return TypedArrayPrototypeGetBuffer(data);
}
case "spki": {
// 1.
if (key[_type] !== "public") {
throw new DOMException(
"Key is not a public key",
"InvalidAccessError",
);
}
// 2.
const data = ops.op_crypto_export_key({
algorithm: key[_algorithm].name,
namedCurve: key[_algorithm].namedCurve,
format: "spki",
}, innerKey);
return TypedArrayPrototypeGetBuffer(data);
}
case "jwk": {
if (key[_algorithm].name == "ECDSA") {
// 1-2.
const jwk = {
kty: "EC",
};
// 3.1
jwk.crv = key[_algorithm].namedCurve;
// Missing from spec
let algNamedCurve;
switch (key[_algorithm].namedCurve) {
case "P-256": {
algNamedCurve = "ES256";
break;
}
case "P-384": {
algNamedCurve = "ES384";
break;
}
case "P-521": {
algNamedCurve = "ES512";
break;
}
default:
throw new DOMException(
"Curve algorithm not supported",
"DataError",
);
}
jwk.alg = algNamedCurve;
// 3.2 - 3.4.
const data = ops.op_crypto_export_key({
format: key[_type] === "private" ? "jwkprivate" : "jwkpublic",
algorithm: key[_algorithm].name,
namedCurve: key[_algorithm].namedCurve,
}, innerKey);
ObjectAssign(jwk, data);
// 4.
jwk.key_ops = key.usages;
// 5.
jwk.ext = key[_extractable];
return jwk;
} else { // ECDH
// 1-2.
const jwk = {
kty: "EC",
};
// missing step from spec
jwk.alg = "ECDH";
// 3.1
jwk.crv = key[_algorithm].namedCurve;
// 3.2 - 3.4
const data = ops.op_crypto_export_key({
format: key[_type] === "private" ? "jwkprivate" : "jwkpublic",
algorithm: key[_algorithm].name,
namedCurve: key[_algorithm].namedCurve,
}, innerKey);
ObjectAssign(jwk, data);
// 4.
jwk.key_ops = key.usages;
// 5.
jwk.ext = key[_extractable];
return jwk;
}
}
default:
throw new DOMException("Not implemented", "NotSupportedError");
}
}
async function generateKeyAES(normalizedAlgorithm, extractable, usages) {
const algorithmName = normalizedAlgorithm.name;
// 2.
if (!ArrayPrototypeIncludes([128, 192, 256], normalizedAlgorithm.length)) {
throw new DOMException("Invalid key length", "OperationError");
}
// 3.
const keyData = await op_crypto_generate_key({
algorithm: "AES",
length: normalizedAlgorithm.length,
});
const handle = {};
WeakMapPrototypeSet(KEY_STORE, handle, {
type: "secret",
data: keyData,
});
// 6-8.
const algorithm = {
name: algorithmName,
length: normalizedAlgorithm.length,
};
// 9-11.
const key = constructKey(
"secret",
extractable,
usages,
algorithm,
handle,
);
// 12.
return key;
}
async function deriveBits(normalizedAlgorithm, baseKey, length) {
switch (normalizedAlgorithm.name) {
case "PBKDF2": {
// 1.
if (length == null || length == 0 || length % 8 !== 0) {
throw new DOMException("Invalid length", "OperationError");
}
if (normalizedAlgorithm.iterations == 0) {
throw new DOMException(
"iterations must not be zero",
"OperationError",
);
}
const handle = baseKey[_handle];
const keyData = WeakMapPrototypeGet(KEY_STORE, handle);
normalizedAlgorithm.salt = copyBuffer(normalizedAlgorithm.salt);
const buf = await op_crypto_derive_bits({
key: keyData,
algorithm: "PBKDF2",
hash: normalizedAlgorithm.hash.name,
iterations: normalizedAlgorithm.iterations,
length,
}, normalizedAlgorithm.salt);
return TypedArrayPrototypeGetBuffer(buf);
}
case "ECDH": {
// 1.
if (baseKey[_type] !== "private") {
throw new DOMException("Invalid key type", "InvalidAccessError");
}
// 2.
const publicKey = normalizedAlgorithm.public;
// 3.
if (publicKey[_type] !== "public") {
throw new DOMException("Invalid key type", "InvalidAccessError");
}
// 4.
if (publicKey[_algorithm].name !== baseKey[_algorithm].name) {
throw new DOMException(
"Algorithm mismatch",
"InvalidAccessError",
);
}
// 5.
if (
publicKey[_algorithm].namedCurve !== baseKey[_algorithm].namedCurve
) {
throw new DOMException(
"namedCurve mismatch",
"InvalidAccessError",
);
}
// 6.
if (
ArrayPrototypeIncludes(
supportedNamedCurves,
publicKey[_algorithm].namedCurve,
)
) {
const baseKeyhandle = baseKey[_handle];
const baseKeyData = WeakMapPrototypeGet(KEY_STORE, baseKeyhandle);
const publicKeyhandle = publicKey[_handle];
const publicKeyData = WeakMapPrototypeGet(KEY_STORE, publicKeyhandle);
const buf = await op_crypto_derive_bits({
key: baseKeyData,
publicKey: publicKeyData,
algorithm: "ECDH",
namedCurve: publicKey[_algorithm].namedCurve,
length: length ?? 0,
});
// 8.
if (length === null) {
return TypedArrayPrototypeGetBuffer(buf);
} else if (TypedArrayPrototypeGetByteLength(buf) * 8 < length) {
throw new DOMException("Invalid length", "OperationError");
} else {
return ArrayBufferPrototypeSlice(
TypedArrayPrototypeGetBuffer(buf),
0,
MathCeil(length / 8),
);
}
} else {
throw new DOMException("Not implemented", "NotSupportedError");
}
}
case "HKDF": {
// 1.
if (length === null || length === 0 || length % 8 !== 0) {
throw new DOMException("Invalid length", "OperationError");
}
const handle = baseKey[_handle];
const keyDerivationKey = WeakMapPrototypeGet(KEY_STORE, handle);
normalizedAlgorithm.salt = copyBuffer(normalizedAlgorithm.salt);
normalizedAlgorithm.info = copyBuffer(normalizedAlgorithm.info);
const buf = await op_crypto_derive_bits({
key: keyDerivationKey,
algorithm: "HKDF",
hash: normalizedAlgorithm.hash.name,
info: normalizedAlgorithm.info,
length,
}, normalizedAlgorithm.salt);
return TypedArrayPrototypeGetBuffer(buf);
}
case "X25519": {
// 1.
if (baseKey[_type] !== "private") {
throw new DOMException("Invalid key type", "InvalidAccessError");
}
// 2.
const publicKey = normalizedAlgorithm.public;
// 3.
if (publicKey[_type] !== "public") {
throw new DOMException("Invalid key type", "InvalidAccessError");
}
// 4.
if (publicKey[_algorithm].name !== baseKey[_algorithm].name) {
throw new DOMException(
"Algorithm mismatch",
"InvalidAccessError",
);
}
// 5.
const kHandle = baseKey[_handle];
const k = WeakMapPrototypeGet(KEY_STORE, kHandle);
const uHandle = publicKey[_handle];
const u = WeakMapPrototypeGet(KEY_STORE, uHandle);
const secret = new Uint8Array(32);
const isIdentity = ops.op_crypto_derive_bits_x25519(k, u, secret);
// 6.
if (isIdentity) {
throw new DOMException("Invalid key", "OperationError");
}
// 7.
if (length === null) {
return TypedArrayPrototypeGetBuffer(secret);
} else if (
TypedArrayPrototypeGetByteLength(secret) * 8 < length
) {
throw new DOMException("Invalid length", "OperationError");
} else {
return ArrayBufferPrototypeSlice(
TypedArrayPrototypeGetBuffer(secret),
0,
MathCeil(length / 8),
);
}
}
default:
throw new DOMException("Not implemented", "NotSupportedError");
}
}
async function encrypt(normalizedAlgorithm, key, data) {
const handle = key[_handle];
const keyData = WeakMapPrototypeGet(KEY_STORE, handle);
switch (normalizedAlgorithm.name) {
case "RSA-OAEP": {
// 1.
if (key[_type] !== "public") {
throw new DOMException(
"Key type not supported",
"InvalidAccessError",
);
}
// 2.
if (normalizedAlgorithm.label) {
normalizedAlgorithm.label = copyBuffer(normalizedAlgorithm.label);
} else {
normalizedAlgorithm.label = new Uint8Array();
}
// 3-5.
const hashAlgorithm = key[_algorithm].hash.name;
const cipherText = await op_crypto_encrypt({
key: keyData,
algorithm: "RSA-OAEP",
hash: hashAlgorithm,
label: normalizedAlgorithm.label,
}, data);
// 6.
return TypedArrayPrototypeGetBuffer(cipherText);
}
case "AES-CBC": {
normalizedAlgorithm.iv = copyBuffer(normalizedAlgorithm.iv);
// 1.
if (TypedArrayPrototypeGetByteLength(normalizedAlgorithm.iv) !== 16) {
throw new DOMException(
"Initialization vector must be 16 bytes",
"OperationError",
);
}
// 2.
const cipherText = await op_crypto_encrypt({
key: keyData,
algorithm: "AES-CBC",
length: key[_algorithm].length,
iv: normalizedAlgorithm.iv,
}, data);
// 4.
return TypedArrayPrototypeGetBuffer(cipherText);
}
case "AES-CTR": {
normalizedAlgorithm.counter = copyBuffer(normalizedAlgorithm.counter);
// 1.
if (
TypedArrayPrototypeGetByteLength(normalizedAlgorithm.counter) !== 16
) {
throw new DOMException(
"Counter vector must be 16 bytes",
"OperationError",
);
}
// 2.
if (
normalizedAlgorithm.length == 0 || normalizedAlgorithm.length > 128
) {
throw new DOMException(
"Counter length must not be 0 or greater than 128",
"OperationError",
);
}
// 3.
const cipherText = await op_crypto_encrypt({
key: keyData,
algorithm: "AES-CTR",
keyLength: key[_algorithm].length,
counter: normalizedAlgorithm.counter,
ctrLength: normalizedAlgorithm.length,
}, data);
// 4.
return TypedArrayPrototypeGetBuffer(cipherText);
}
case "AES-GCM": {
normalizedAlgorithm.iv = copyBuffer(normalizedAlgorithm.iv);
// 1.
if (TypedArrayPrototypeGetByteLength(data) > (2 ** 39) - 256) {
throw new DOMException(
"Plaintext too large",
"OperationError",
);
}
// 2.
// We only support 96-bit and 128-bit nonce.
if (
ArrayPrototypeIncludes(
[12, 16],
TypedArrayPrototypeGetByteLength(normalizedAlgorithm.iv),
) === undefined
) {
throw new DOMException(
"Initialization vector length not supported",
"NotSupportedError",
);
}
// 3.
// NOTE: over the size of Number.MAX_SAFE_INTEGER is not available in V8
// if (normalizedAlgorithm.additionalData !== undefined) {
// if (normalizedAlgorithm.additionalData.byteLength > (2 ** 64) - 1) {
// throw new DOMException(
// "Additional data too large",
// "OperationError",
// );
// }
// }
// 4.
if (normalizedAlgorithm.tagLength == undefined) {
normalizedAlgorithm.tagLength = 128;
} else if (
!ArrayPrototypeIncludes(
[32, 64, 96, 104, 112, 120, 128],
normalizedAlgorithm.tagLength,
)
) {
throw new DOMException(
"Invalid tag length",
"OperationError",
);
}
// 5.
if (normalizedAlgorithm.additionalData) {
normalizedAlgorithm.additionalData = copyBuffer(
normalizedAlgorithm.additionalData,
);
}
// 6-7.
const cipherText = await op_crypto_encrypt({
key: keyData,
algorithm: "AES-GCM",
length: key[_algorithm].length,
iv: normalizedAlgorithm.iv,
additionalData: normalizedAlgorithm.additionalData || null,
tagLength: normalizedAlgorithm.tagLength,
}, data);
// 8.
return TypedArrayPrototypeGetBuffer(cipherText);
}
default:
throw new DOMException("Not implemented", "NotSupportedError");
}
}
webidl.configureInterface(SubtleCrypto);
const subtle = webidl.createBranded(SubtleCrypto);
class Crypto {
constructor() {
webidl.illegalConstructor();
}
getRandomValues(typedArray) {
webidl.assertBranded(this, CryptoPrototype);
const prefix = "Failed to execute 'getRandomValues' on 'Crypto'";
webidl.requiredArguments(arguments.length, 1, prefix);
// Fast path for Uint8Array
const tag = TypedArrayPrototypeGetSymbolToStringTag(typedArray);
if (tag === "Uint8Array") {
ops.op_crypto_get_random_values(typedArray);
return typedArray;
}
typedArray = webidl.converters.ArrayBufferView(
typedArray,
prefix,
"Argument 1",
);
switch (tag) {
case "Int8Array":
case "Uint8ClampedArray":
case "Int16Array":
case "Uint16Array":
case "Int32Array":
case "Uint32Array":
case "BigInt64Array":
case "BigUint64Array":
break;
default:
throw new DOMException(
"The provided ArrayBufferView is not an integer array type",
"TypeMismatchError",
);
}
const ui8 = new Uint8Array(
TypedArrayPrototypeGetBuffer(typedArray),
TypedArrayPrototypeGetByteOffset(typedArray),
TypedArrayPrototypeGetByteLength(typedArray),
);
ops.op_crypto_get_random_values(ui8);
return typedArray;
}
randomUUID() {
webidl.assertBranded(this, CryptoPrototype);
return ops.op_crypto_random_uuid();
}
get subtle() {
webidl.assertBranded(this, CryptoPrototype);
return subtle;
}
[SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) {
return inspect(
createFilteredInspectProxy({
object: this,
evaluate: ObjectPrototypeIsPrototypeOf(CryptoPrototype, this),
keys: ["subtle"],
}),
inspectOptions,
);
}
}
webidl.configureInterface(Crypto);
const CryptoPrototype = Crypto.prototype;
const crypto = webidl.createBranded(Crypto);
webidl.converters.AlgorithmIdentifier = (V, prefix, context, opts) => {
// Union for (object or DOMString)
if (webidl.type(V) == "Object") {
return webidl.converters.object(V, prefix, context, opts);
}
return webidl.converters.DOMString(V, prefix, context, opts);
};
webidl.converters["BufferSource or JsonWebKey"] = (
V,
prefix,
context,
opts,
) => {
// Union for (BufferSource or JsonWebKey)
if (ArrayBufferIsView(V) || isArrayBuffer(V)) {
return webidl.converters.BufferSource(V, prefix, context, opts);
}
return webidl.converters.JsonWebKey(V, prefix, context, opts);
};
webidl.converters.KeyType = webidl.createEnumConverter("KeyType", [
"public",
"private",
"secret",
]);
webidl.converters.KeyFormat = webidl.createEnumConverter("KeyFormat", [
"raw",
"pkcs8",
"spki",
"jwk",
]);
webidl.converters.KeyUsage = webidl.createEnumConverter("KeyUsage", [
"encrypt",
"decrypt",
"sign",
"verify",
"deriveKey",
"deriveBits",
"wrapKey",
"unwrapKey",
]);
webidl.converters["sequence<KeyUsage>"] = webidl.createSequenceConverter(
webidl.converters.KeyUsage,
);
webidl.converters.HashAlgorithmIdentifier =
webidl.converters.AlgorithmIdentifier;
/** @type {webidl.Dictionary} */
const dictAlgorithm = [{
key: "name",
converter: webidl.converters.DOMString,
required: true,
}];
webidl.converters.Algorithm = webidl
.createDictionaryConverter("Algorithm", dictAlgorithm);
webidl.converters.BigInteger = webidl.converters.Uint8Array;
/** @type {webidl.Dictionary} */
const dictRsaKeyGenParams = [
...new SafeArrayIterator(dictAlgorithm),
{
key: "modulusLength",
converter: (V, prefix, context, opts) =>
webidl.converters["unsigned long"](V, prefix, context, {
...opts,
enforceRange: true,
}),
required: true,
},
{
key: "publicExponent",
converter: webidl.converters.BigInteger,
required: true,
},
];
webidl.converters.RsaKeyGenParams = webidl
.createDictionaryConverter("RsaKeyGenParams", dictRsaKeyGenParams);
const dictRsaHashedKeyGenParams = [
...new SafeArrayIterator(dictRsaKeyGenParams),
{
key: "hash",
converter: webidl.converters.HashAlgorithmIdentifier,
required: true,
},
];
webidl.converters.RsaHashedKeyGenParams = webidl.createDictionaryConverter(
"RsaHashedKeyGenParams",
dictRsaHashedKeyGenParams,
);
const dictRsaHashedImportParams = [
...new SafeArrayIterator(dictAlgorithm),
{
key: "hash",
converter: webidl.converters.HashAlgorithmIdentifier,
required: true,
},
];
webidl.converters.RsaHashedImportParams = webidl.createDictionaryConverter(
"RsaHashedImportParams",
dictRsaHashedImportParams,
);
webidl.converters.NamedCurve = webidl.converters.DOMString;
const dictEcKeyImportParams = [
...new SafeArrayIterator(dictAlgorithm),
{
key: "namedCurve",
converter: webidl.converters.NamedCurve,
required: true,
},
];
webidl.converters.EcKeyImportParams = webidl.createDictionaryConverter(
"EcKeyImportParams",
dictEcKeyImportParams,
);
const dictEcKeyGenParams = [
...new SafeArrayIterator(dictAlgorithm),
{
key: "namedCurve",
converter: webidl.converters.NamedCurve,
required: true,
},
];
webidl.converters.EcKeyGenParams = webidl
.createDictionaryConverter("EcKeyGenParams", dictEcKeyGenParams);
const dictAesKeyGenParams = [
...new SafeArrayIterator(dictAlgorithm),
{
key: "length",
converter: (V, prefix, context, opts) =>
webidl.converters["unsigned short"](V, prefix, context, {
...opts,
enforceRange: true,
}),
required: true,
},
];
webidl.converters.AesKeyGenParams = webidl
.createDictionaryConverter("AesKeyGenParams", dictAesKeyGenParams);
const dictHmacKeyGenParams = [
...new SafeArrayIterator(dictAlgorithm),
{
key: "hash",
converter: webidl.converters.HashAlgorithmIdentifier,
required: true,
},
{
key: "length",
converter: (V, prefix, context, opts) =>
webidl.converters["unsigned long"](V, prefix, context, {
...opts,
enforceRange: true,
}),
},
];
webidl.converters.HmacKeyGenParams = webidl
.createDictionaryConverter("HmacKeyGenParams", dictHmacKeyGenParams);
const dictRsaPssParams = [
...new SafeArrayIterator(dictAlgorithm),
{
key: "saltLength",
converter: (V, prefix, context, opts) =>
webidl.converters["unsigned long"](V, prefix, context, {
...opts,
enforceRange: true,
}),
required: true,
},
];
webidl.converters.RsaPssParams = webidl
.createDictionaryConverter("RsaPssParams", dictRsaPssParams);
const dictRsaOaepParams = [
...new SafeArrayIterator(dictAlgorithm),
{
key: "label",
converter: webidl.converters["BufferSource"],
},
];
webidl.converters.RsaOaepParams = webidl
.createDictionaryConverter("RsaOaepParams", dictRsaOaepParams);
const dictEcdsaParams = [
...new SafeArrayIterator(dictAlgorithm),
{
key: "hash",
converter: webidl.converters.HashAlgorithmIdentifier,
required: true,
},
];
webidl.converters["EcdsaParams"] = webidl
.createDictionaryConverter("EcdsaParams", dictEcdsaParams);
const dictHmacImportParams = [
...new SafeArrayIterator(dictAlgorithm),
{
key: "hash",
converter: webidl.converters.HashAlgorithmIdentifier,
required: true,
},
{
key: "length",
converter: (V, prefix, context, opts) =>
webidl.converters["unsigned long"](V, prefix, context, {
...opts,
enforceRange: true,
}),
},
];
webidl.converters.HmacImportParams = webidl
.createDictionaryConverter("HmacImportParams", dictHmacImportParams);
const dictRsaOtherPrimesInfo = [
{
key: "r",
converter: webidl.converters["DOMString"],
},
{
key: "d",
converter: webidl.converters["DOMString"],
},
{
key: "t",
converter: webidl.converters["DOMString"],
},
];
webidl.converters.RsaOtherPrimesInfo = webidl.createDictionaryConverter(
"RsaOtherPrimesInfo",
dictRsaOtherPrimesInfo,
);
webidl.converters["sequence<RsaOtherPrimesInfo>"] = webidl
.createSequenceConverter(
webidl.converters.RsaOtherPrimesInfo,
);
const dictJsonWebKey = [
// Sections 4.2 and 4.3 of RFC7517.
// https://datatracker.ietf.org/doc/html/rfc7517#section-4
{
key: "kty",
converter: webidl.converters["DOMString"],
},
{
key: "use",
converter: webidl.converters["DOMString"],
},
{
key: "key_ops",
converter: webidl.converters["sequence<DOMString>"],
},
{
key: "alg",
converter: webidl.converters["DOMString"],
},
// JSON Web Key Parameters Registration
{
key: "ext",
converter: webidl.converters["boolean"],
},
// Section 6 of RFC7518 JSON Web Algorithms
// https://datatracker.ietf.org/doc/html/rfc7518#section-6
{
key: "crv",
converter: webidl.converters["DOMString"],
},
{
key: "x",
converter: webidl.converters["DOMString"],
},
{
key: "y",
converter: webidl.converters["DOMString"],
},
{
key: "d",
converter: webidl.converters["DOMString"],
},
{
key: "n",
converter: webidl.converters["DOMString"],
},
{
key: "e",
converter: webidl.converters["DOMString"],
},
{
key: "p",
converter: webidl.converters["DOMString"],
},
{
key: "q",
converter: webidl.converters["DOMString"],
},
{
key: "dp",
converter: webidl.converters["DOMString"],
},
{
key: "dq",
converter: webidl.converters["DOMString"],
},
{
key: "qi",
converter: webidl.converters["DOMString"],
},
{
key: "oth",
converter: webidl.converters["sequence<RsaOtherPrimesInfo>"],
},
{
key: "k",
converter: webidl.converters["DOMString"],
},
];
webidl.converters.JsonWebKey = webidl.createDictionaryConverter(
"JsonWebKey",
dictJsonWebKey,
);
const dictHkdfParams = [
...new SafeArrayIterator(dictAlgorithm),
{
key: "hash",
converter: webidl.converters.HashAlgorithmIdentifier,
required: true,
},
{
key: "salt",
converter: webidl.converters["BufferSource"],
required: true,
},
{
key: "info",
converter: webidl.converters["BufferSource"],
required: true,
},
];
webidl.converters.HkdfParams = webidl
.createDictionaryConverter("HkdfParams", dictHkdfParams);
const dictPbkdf2Params = [
...new SafeArrayIterator(dictAlgorithm),
{
key: "hash",
converter: webidl.converters.HashAlgorithmIdentifier,
required: true,
},
{
key: "iterations",
converter: (V, prefix, context, opts) =>
webidl.converters["unsigned long"](V, prefix, context, {
...opts,
enforceRange: true,
}),
required: true,
},
{
key: "salt",
converter: webidl.converters["BufferSource"],
required: true,
},
];
webidl.converters.Pbkdf2Params = webidl
.createDictionaryConverter("Pbkdf2Params", dictPbkdf2Params);
const dictAesDerivedKeyParams = [
...new SafeArrayIterator(dictAlgorithm),
{
key: "length",
converter: (V, prefix, context, opts) =>
webidl.converters["unsigned long"](V, prefix, context, {
...opts,
enforceRange: true,
}),
required: true,
},
];
const dictAesCbcParams = [
...new SafeArrayIterator(dictAlgorithm),
{
key: "iv",
converter: webidl.converters["BufferSource"],
required: true,
},
];
const dictAesGcmParams = [
...new SafeArrayIterator(dictAlgorithm),
{
key: "iv",
converter: webidl.converters["BufferSource"],
required: true,
},
{
key: "tagLength",
converter: (V, prefix, context, opts) =>
webidl.converters["unsigned long"](V, prefix, context, {
...opts,
enforceRange: true,
}),
},
{
key: "additionalData",
converter: webidl.converters["BufferSource"],
},
];
const dictAesCtrParams = [
...new SafeArrayIterator(dictAlgorithm),
{
key: "counter",
converter: webidl.converters["BufferSource"],
required: true,
},
{
key: "length",
converter: (V, prefix, context, opts) =>
webidl.converters["unsigned short"](V, prefix, context, {
...opts,
enforceRange: true,
}),
required: true,
},
];
webidl.converters.AesDerivedKeyParams = webidl
.createDictionaryConverter("AesDerivedKeyParams", dictAesDerivedKeyParams);
webidl.converters.AesCbcParams = webidl
.createDictionaryConverter("AesCbcParams", dictAesCbcParams);
webidl.converters.AesGcmParams = webidl
.createDictionaryConverter("AesGcmParams", dictAesGcmParams);
webidl.converters.AesCtrParams = webidl
.createDictionaryConverter("AesCtrParams", dictAesCtrParams);
webidl.converters.CryptoKey = webidl.createInterfaceConverter(
"CryptoKey",
CryptoKey.prototype,
);
const dictCryptoKeyPair = [
{
key: "publicKey",
converter: webidl.converters.CryptoKey,
},
{
key: "privateKey",
converter: webidl.converters.CryptoKey,
},
];
webidl.converters.CryptoKeyPair = webidl
.createDictionaryConverter("CryptoKeyPair", dictCryptoKeyPair);
const dictEcdhKeyDeriveParams = [
...new SafeArrayIterator(dictAlgorithm),
{
key: "public",
converter: webidl.converters.CryptoKey,
required: true,
},
];
webidl.converters.EcdhKeyDeriveParams = webidl
.createDictionaryConverter("EcdhKeyDeriveParams", dictEcdhKeyDeriveParams);
export { Crypto, crypto, CryptoKey, SubtleCrypto };