mirror of
https://github.com/denoland/deno.git
synced 2024-12-01 16:51:13 -05:00
2164f6b1eb
Welcome to better optimised op calls! Currently opSync is called with parameters of every type and count. This most definitely makes the call megamorphic. Additionally, it seems that spread params leads to V8 not being able to optimise the calls quite as well (apparently Fast Calls cannot be used with spread params). Monomorphising op calls should lead to some improved performance. Now that unwrapping of sync ops results is done on Rust side, this is pretty simple: ``` opSync("op_foo", param1, param2); // -> turns to ops.op_foo(param1, param2); ``` This means sync op calls are now just directly calling the native binding function. When V8 Fast API Calls are enabled, this will enable those to be called on the optimised path. Monomorphising async ops likely requires using callbacks and is left as an exercise to the reader.
3955 lines
103 KiB
JavaScript
3955 lines
103 KiB
JavaScript
// Copyright 2018-2022 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" />
|
|
|
|
"use strict";
|
|
|
|
((window) => {
|
|
const core = window.Deno.core;
|
|
const ops = core.ops;
|
|
const webidl = window.__bootstrap.webidl;
|
|
const { DOMException } = window.__bootstrap.domException;
|
|
|
|
const {
|
|
ArrayBufferPrototype,
|
|
ArrayBufferIsView,
|
|
ArrayPrototypeEvery,
|
|
ArrayPrototypeFind,
|
|
ArrayPrototypeIncludes,
|
|
BigInt64ArrayPrototype,
|
|
BigUint64ArrayPrototype,
|
|
Int16ArrayPrototype,
|
|
Int32ArrayPrototype,
|
|
Int8ArrayPrototype,
|
|
JSONParse,
|
|
JSONStringify,
|
|
ObjectAssign,
|
|
ObjectPrototypeIsPrototypeOf,
|
|
StringPrototypeToLowerCase,
|
|
StringPrototypeToUpperCase,
|
|
StringPrototypeCharCodeAt,
|
|
StringFromCharCode,
|
|
Symbol,
|
|
SymbolFor,
|
|
SyntaxError,
|
|
TypedArrayPrototypeSlice,
|
|
TypeError,
|
|
Uint16ArrayPrototype,
|
|
Uint32ArrayPrototype,
|
|
Uint8Array,
|
|
Uint8ArrayPrototype,
|
|
Uint8ClampedArrayPrototype,
|
|
WeakMap,
|
|
WeakMapPrototypeGet,
|
|
WeakMapPrototypeSet,
|
|
} = window.__bootstrap.primordials;
|
|
|
|
// P-521 is not yet supported.
|
|
const supportedNamedCurves = ["P-256", "P-384"];
|
|
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",
|
|
},
|
|
"sign": {
|
|
"RSASSA-PKCS1-v1_5": null,
|
|
"RSA-PSS": "RsaPssParams",
|
|
"ECDSA": "EcdsaParams",
|
|
"HMAC": null,
|
|
},
|
|
"verify": {
|
|
"RSASSA-PKCS1-v1_5": null,
|
|
"RSA-PSS": "RsaPssParams",
|
|
"ECDSA": "EcdsaParams",
|
|
"HMAC": 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,
|
|
},
|
|
"deriveBits": {
|
|
"HKDF": "HkdfParams",
|
|
"PBKDF2": "Pbkdf2Params",
|
|
"ECDH": "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, {
|
|
prefix: "Failed to normalize algorithm",
|
|
context: "passed algorithm",
|
|
});
|
|
// 4.
|
|
let algName = initialAlg.name;
|
|
|
|
// 5.
|
|
let desiredType = undefined;
|
|
for (const key in registeredAlgorithms) {
|
|
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, {
|
|
prefix: "Failed to normalize algorithm",
|
|
context: "passed algorithm",
|
|
});
|
|
// 7.
|
|
normalizedAlgorithm.name = algName;
|
|
|
|
// 9.
|
|
const dict = simpleAlgorithmDictionaries[desiredType];
|
|
// 10.
|
|
for (const member in dict) {
|
|
const idlType = dict[member];
|
|
const idlValue = normalizedAlgorithm[member];
|
|
// 3.
|
|
if (idlType === "BufferSource" && idlValue) {
|
|
normalizedAlgorithm[member] = TypedArrayPrototypeSlice(
|
|
new Uint8Array(
|
|
ArrayBufferIsView(idlValue) ? idlValue.buffer : idlValue,
|
|
idlValue.byteOffset ?? 0,
|
|
idlValue.byteLength,
|
|
),
|
|
);
|
|
} 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) {
|
|
return TypedArrayPrototypeSlice(
|
|
ArrayBufferIsView(input)
|
|
? new Uint8Array(input.buffer, input.byteOffset, input.byteLength)
|
|
: new Uint8Array(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.customInspect")](inspect) {
|
|
return `${this.constructor.name} ${
|
|
inspect({
|
|
type: this.type,
|
|
extractable: this.extractable,
|
|
algorithm: this.algorithm,
|
|
usages: this.usages,
|
|
})
|
|
}`;
|
|
}
|
|
}
|
|
|
|
webidl.configurePrototype(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 a.filter((i) => b.includes(i));
|
|
}
|
|
|
|
// TODO(lucacasonato): this should be moved to rust
|
|
/** @type {WeakMap<object, object>} */
|
|
const KEY_STORE = new WeakMap();
|
|
|
|
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 = 160;
|
|
break;
|
|
case "SHA-256":
|
|
length = 256;
|
|
break;
|
|
case "SHA-384":
|
|
length = 384;
|
|
break;
|
|
case "SHA-512":
|
|
length = 512;
|
|
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<Uint8Array>}
|
|
*/
|
|
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,
|
|
context: "Argument 1",
|
|
});
|
|
data = webidl.converters.BufferSource(data, {
|
|
prefix,
|
|
context: "Argument 2",
|
|
});
|
|
|
|
data = copyBuffer(data);
|
|
|
|
algorithm = normalizeAlgorithm(algorithm, "digest");
|
|
|
|
const result = await core.opAsync(
|
|
"op_crypto_subtle_digest",
|
|
algorithm.name,
|
|
data,
|
|
);
|
|
|
|
return result.buffer;
|
|
}
|
|
|
|
/**
|
|
* @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,
|
|
context: "Argument 1",
|
|
});
|
|
key = webidl.converters.CryptoKey(key, {
|
|
prefix,
|
|
context: "Argument 2",
|
|
});
|
|
data = webidl.converters.BufferSource(data, {
|
|
prefix,
|
|
context: "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,
|
|
context: "Argument 1",
|
|
});
|
|
key = webidl.converters.CryptoKey(key, {
|
|
prefix,
|
|
context: "Argument 2",
|
|
});
|
|
data = webidl.converters.BufferSource(data, {
|
|
prefix,
|
|
context: "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 core.opAsync("op_crypto_decrypt", {
|
|
key: keyData,
|
|
algorithm: "RSA-OAEP",
|
|
hash: hashAlgorithm,
|
|
label: normalizedAlgorithm.label,
|
|
}, data);
|
|
|
|
// 6.
|
|
return plainText.buffer;
|
|
}
|
|
case "AES-CBC": {
|
|
normalizedAlgorithm.iv = copyBuffer(normalizedAlgorithm.iv);
|
|
|
|
// 1.
|
|
if (normalizedAlgorithm.iv.byteLength !== 16) {
|
|
throw new DOMException(
|
|
"Counter must be 16 bytes",
|
|
"OperationError",
|
|
);
|
|
}
|
|
|
|
const plainText = await core.opAsync("op_crypto_decrypt", {
|
|
key: keyData,
|
|
algorithm: "AES-CBC",
|
|
iv: normalizedAlgorithm.iv,
|
|
length: key[_algorithm].length,
|
|
}, data);
|
|
|
|
// 6.
|
|
return plainText.buffer;
|
|
}
|
|
case "AES-CTR": {
|
|
normalizedAlgorithm.counter = copyBuffer(normalizedAlgorithm.counter);
|
|
|
|
// 1.
|
|
if (normalizedAlgorithm.counter.byteLength !== 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 core.opAsync("op_crypto_decrypt", {
|
|
key: keyData,
|
|
algorithm: "AES-CTR",
|
|
keyLength: key[_algorithm].length,
|
|
counter: normalizedAlgorithm.counter,
|
|
ctrLength: normalizedAlgorithm.length,
|
|
}, data);
|
|
|
|
// 4.
|
|
return cipherText.buffer;
|
|
}
|
|
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 (data.byteLength < 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],
|
|
normalizedAlgorithm.iv.byteLength,
|
|
) === undefined
|
|
) {
|
|
throw new DOMException(
|
|
"Initialization vector length not supported",
|
|
"NotSupportedError",
|
|
);
|
|
}
|
|
|
|
// 4.
|
|
if (normalizedAlgorithm.additionalData !== undefined) {
|
|
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 core.opAsync("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 plaintext.buffer;
|
|
}
|
|
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,
|
|
context: "Argument 1",
|
|
});
|
|
key = webidl.converters.CryptoKey(key, {
|
|
prefix,
|
|
context: "Argument 2",
|
|
});
|
|
data = webidl.converters.BufferSource(data, {
|
|
prefix,
|
|
context: "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 core.opAsync("op_crypto_sign_key", {
|
|
key: keyData,
|
|
algorithm: "RSASSA-PKCS1-v1_5",
|
|
hash: hashAlgorithm,
|
|
}, data);
|
|
|
|
return signature.buffer;
|
|
}
|
|
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 core.opAsync("op_crypto_sign_key", {
|
|
key: keyData,
|
|
algorithm: "RSA-PSS",
|
|
hash: hashAlgorithm,
|
|
saltLength: normalizedAlgorithm.saltLength,
|
|
}, data);
|
|
|
|
return signature.buffer;
|
|
}
|
|
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");
|
|
}
|
|
|
|
const signature = await core.opAsync("op_crypto_sign_key", {
|
|
key: keyData,
|
|
algorithm: "ECDSA",
|
|
hash: hashAlgorithm,
|
|
namedCurve,
|
|
}, data);
|
|
|
|
return signature.buffer;
|
|
}
|
|
case "HMAC": {
|
|
const hashAlgorithm = key[_algorithm].hash.name;
|
|
|
|
const signature = await core.opAsync("op_crypto_sign_key", {
|
|
key: keyData,
|
|
algorithm: "HMAC",
|
|
hash: hashAlgorithm,
|
|
}, data);
|
|
|
|
return signature.buffer;
|
|
}
|
|
}
|
|
|
|
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,
|
|
context: "Argument 1",
|
|
});
|
|
keyData = webidl.converters["BufferSource or JsonWebKey"](keyData, {
|
|
prefix,
|
|
context: "Argument 2",
|
|
});
|
|
algorithm = webidl.converters.AlgorithmIdentifier(algorithm, {
|
|
prefix,
|
|
context: "Argument 3",
|
|
});
|
|
extractable = webidl.converters.boolean(extractable, {
|
|
prefix,
|
|
context: "Argument 4",
|
|
});
|
|
keyUsages = webidl.converters["sequence<KeyUsage>"](keyUsages, {
|
|
prefix,
|
|
context: "Argument 5",
|
|
});
|
|
|
|
// 2.
|
|
if (format !== "jwk") {
|
|
if (
|
|
ArrayBufferIsView(keyData) ||
|
|
ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, keyData)
|
|
) {
|
|
keyData = copyBuffer(keyData);
|
|
} else {
|
|
throw new TypeError("keyData is a JsonWebKey");
|
|
}
|
|
} else {
|
|
if (
|
|
ArrayBufferIsView(keyData) ||
|
|
ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, 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"],
|
|
);
|
|
}
|
|
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,
|
|
context: "Argument 1",
|
|
});
|
|
key = webidl.converters.CryptoKey(key, {
|
|
prefix,
|
|
context: "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 "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} 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,
|
|
context: "Argument 1",
|
|
});
|
|
baseKey = webidl.converters.CryptoKey(baseKey, {
|
|
prefix,
|
|
context: "Argument 2",
|
|
});
|
|
length = webidl.converters["unsigned long"](length, {
|
|
prefix,
|
|
context: "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,
|
|
context: "Argument 1",
|
|
});
|
|
baseKey = webidl.converters.CryptoKey(baseKey, {
|
|
prefix,
|
|
context: "Argument 2",
|
|
});
|
|
derivedKeyType = webidl.converters.AlgorithmIdentifier(derivedKeyType, {
|
|
prefix,
|
|
context: "Argument 3",
|
|
});
|
|
extractable = webidl.converters["boolean"](extractable, {
|
|
prefix,
|
|
context: "Argument 4",
|
|
});
|
|
keyUsages = webidl.converters["sequence<KeyUsage>"](keyUsages, {
|
|
prefix,
|
|
context: "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 this.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,
|
|
context: "Argument 1",
|
|
});
|
|
key = webidl.converters.CryptoKey(key, {
|
|
prefix,
|
|
context: "Argument 2",
|
|
});
|
|
signature = webidl.converters.BufferSource(signature, {
|
|
prefix,
|
|
context: "Argument 3",
|
|
});
|
|
data = webidl.converters.BufferSource(data, {
|
|
prefix,
|
|
context: "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 core.opAsync("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;
|
|
const saltLength = normalizedAlgorithm.saltLength;
|
|
return await core.opAsync("op_crypto_verify_key", {
|
|
key: keyData,
|
|
algorithm: "RSA-PSS",
|
|
hash: hashAlgorithm,
|
|
saltLength,
|
|
signature,
|
|
}, data);
|
|
}
|
|
case "HMAC": {
|
|
const hash = key[_algorithm].hash.name;
|
|
return await core.opAsync("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;
|
|
|
|
// 3-8.
|
|
return await core.opAsync("op_crypto_verify_key", {
|
|
key: keyData,
|
|
algorithm: "ECDSA",
|
|
hash,
|
|
signature,
|
|
namedCurve: key[_algorithm].namedCurve,
|
|
}, data);
|
|
}
|
|
}
|
|
|
|
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,
|
|
context: "Argument 1",
|
|
});
|
|
key = webidl.converters.CryptoKey(key, {
|
|
prefix,
|
|
context: "Argument 2",
|
|
});
|
|
wrappingKey = webidl.converters.CryptoKey(wrappingKey, {
|
|
prefix,
|
|
context: "Argument 3",
|
|
});
|
|
wrapAlgorithm = webidl.converters.AlgorithmIdentifier(wrapAlgorithm, {
|
|
prefix,
|
|
context: "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 cipherText.buffer;
|
|
}
|
|
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,
|
|
context: "Argument 1",
|
|
});
|
|
wrappedKey = webidl.converters.BufferSource(wrappedKey, {
|
|
prefix,
|
|
context: "Argument 2",
|
|
});
|
|
unwrappingKey = webidl.converters.CryptoKey(unwrappingKey, {
|
|
prefix,
|
|
context: "Argument 3",
|
|
});
|
|
unwrapAlgorithm = webidl.converters.AlgorithmIdentifier(unwrapAlgorithm, {
|
|
prefix,
|
|
context: "Argument 4",
|
|
});
|
|
unwrappedKeyAlgorithm = webidl.converters.AlgorithmIdentifier(
|
|
unwrappedKeyAlgorithm,
|
|
{
|
|
prefix,
|
|
context: "Argument 5",
|
|
},
|
|
);
|
|
extractable = webidl.converters.boolean(extractable, {
|
|
prefix,
|
|
context: "Argument 6",
|
|
});
|
|
keyUsages = webidl.converters["sequence<KeyUsage>"](keyUsages, {
|
|
prefix,
|
|
context: "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 = plainText.buffer;
|
|
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,
|
|
context: "Argument 1",
|
|
});
|
|
extractable = webidl.converters["boolean"](extractable, {
|
|
prefix,
|
|
context: "Argument 2",
|
|
});
|
|
keyUsages = webidl.converters["sequence<KeyUsage>"](keyUsages, {
|
|
prefix,
|
|
context: "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;
|
|
}
|
|
}
|
|
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 core.opAsync(
|
|
"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 core.opAsync(
|
|
"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 core.opAsync("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 core.opAsync("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 "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 core.opAsync("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: keyData.byteLength * 8,
|
|
};
|
|
|
|
// 5, 11-13.
|
|
const key = constructKey(
|
|
"secret",
|
|
extractable,
|
|
usages,
|
|
algorithm,
|
|
handle,
|
|
);
|
|
|
|
// 14.
|
|
return key;
|
|
}
|
|
}
|
|
}
|
|
|
|
function exportKeyAES(
|
|
format,
|
|
key,
|
|
innerKey,
|
|
) {
|
|
switch (format) {
|
|
// 2.
|
|
case "raw": {
|
|
// 1.
|
|
const data = innerKey.data;
|
|
// 2.
|
|
return data.buffer;
|
|
}
|
|
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], keyData.byteLength * 8)
|
|
) {
|
|
throw new DOMException("Invalid key length", "Datarror");
|
|
}
|
|
|
|
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 (data.byteLength * 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: data.byteLength * 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 !== "sign"
|
|
) {
|
|
throw new DOMException(
|
|
"'use' property of JsonWebKey must be 'sign'",
|
|
"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 = data.byteLength * 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;
|
|
for (let _i = 7 & (8 - bits.length % 8); _i > 0; _i--) {
|
|
bits.push(0);
|
|
}
|
|
// 4-5.
|
|
return bits.buffer;
|
|
}
|
|
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 data.buffer;
|
|
}
|
|
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 data.buffer;
|
|
}
|
|
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 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 data.buffer;
|
|
}
|
|
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 data.buffer;
|
|
}
|
|
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 data.buffer;
|
|
}
|
|
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 core.opAsync("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 core.opAsync("op_crypto_derive_bits", {
|
|
key: keyData,
|
|
algorithm: "PBKDF2",
|
|
hash: normalizedAlgorithm.hash.name,
|
|
iterations: normalizedAlgorithm.iterations,
|
|
length,
|
|
}, normalizedAlgorithm.salt);
|
|
|
|
return buf.buffer;
|
|
}
|
|
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 core.opAsync("op_crypto_derive_bits", {
|
|
key: baseKeyData,
|
|
publicKey: publicKeyData,
|
|
algorithm: "ECDH",
|
|
namedCurve: publicKey[_algorithm].namedCurve,
|
|
length,
|
|
});
|
|
|
|
return buf.buffer;
|
|
} 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 core.opAsync("op_crypto_derive_bits", {
|
|
key: keyDerivationKey,
|
|
algorithm: "HKDF",
|
|
hash: normalizedAlgorithm.hash.name,
|
|
info: normalizedAlgorithm.info,
|
|
length,
|
|
}, normalizedAlgorithm.salt);
|
|
|
|
return buf.buffer;
|
|
}
|
|
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 core.opAsync("op_crypto_encrypt", {
|
|
key: keyData,
|
|
algorithm: "RSA-OAEP",
|
|
hash: hashAlgorithm,
|
|
label: normalizedAlgorithm.label,
|
|
}, data);
|
|
|
|
// 6.
|
|
return cipherText.buffer;
|
|
}
|
|
case "AES-CBC": {
|
|
normalizedAlgorithm.iv = copyBuffer(normalizedAlgorithm.iv);
|
|
|
|
// 1.
|
|
if (normalizedAlgorithm.iv.byteLength !== 16) {
|
|
throw new DOMException(
|
|
"Initialization vector must be 16 bytes",
|
|
"OperationError",
|
|
);
|
|
}
|
|
|
|
// 2.
|
|
const cipherText = await core.opAsync("op_crypto_encrypt", {
|
|
key: keyData,
|
|
algorithm: "AES-CBC",
|
|
length: key[_algorithm].length,
|
|
iv: normalizedAlgorithm.iv,
|
|
}, data);
|
|
|
|
// 4.
|
|
return cipherText.buffer;
|
|
}
|
|
case "AES-CTR": {
|
|
normalizedAlgorithm.counter = copyBuffer(normalizedAlgorithm.counter);
|
|
|
|
// 1.
|
|
if (normalizedAlgorithm.counter.byteLength !== 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 core.opAsync("op_crypto_encrypt", {
|
|
key: keyData,
|
|
algorithm: "AES-CTR",
|
|
keyLength: key[_algorithm].length,
|
|
counter: normalizedAlgorithm.counter,
|
|
ctrLength: normalizedAlgorithm.length,
|
|
}, data);
|
|
|
|
// 4.
|
|
return cipherText.buffer;
|
|
}
|
|
case "AES-GCM": {
|
|
normalizedAlgorithm.iv = copyBuffer(normalizedAlgorithm.iv);
|
|
|
|
// 1.
|
|
if (data.byteLength > (2 ** 39) - 256) {
|
|
throw new DOMException(
|
|
"Plaintext too large",
|
|
"OperationError",
|
|
);
|
|
}
|
|
|
|
// 2.
|
|
// We only support 96-bit and 128-bit nonce.
|
|
if (
|
|
ArrayPrototypeIncludes(
|
|
[12, 16],
|
|
normalizedAlgorithm.iv.byteLength,
|
|
) === undefined
|
|
) {
|
|
throw new DOMException(
|
|
"Initialization vector length not supported",
|
|
"NotSupportedError",
|
|
);
|
|
}
|
|
|
|
// 3.
|
|
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 core.opAsync("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 cipherText.buffer;
|
|
}
|
|
default:
|
|
throw new DOMException("Not implemented", "NotSupportedError");
|
|
}
|
|
}
|
|
|
|
webidl.configurePrototype(SubtleCrypto);
|
|
const subtle = webidl.createBranded(SubtleCrypto);
|
|
|
|
class Crypto {
|
|
constructor() {
|
|
webidl.illegalConstructor();
|
|
}
|
|
|
|
getRandomValues(arrayBufferView) {
|
|
webidl.assertBranded(this, CryptoPrototype);
|
|
const prefix = "Failed to execute 'getRandomValues' on 'Crypto'";
|
|
webidl.requiredArguments(arguments.length, 1, { prefix });
|
|
arrayBufferView = webidl.converters.ArrayBufferView(arrayBufferView, {
|
|
prefix,
|
|
context: "Argument 1",
|
|
});
|
|
if (
|
|
!(
|
|
ObjectPrototypeIsPrototypeOf(Int8ArrayPrototype, arrayBufferView) ||
|
|
ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, arrayBufferView) ||
|
|
ObjectPrototypeIsPrototypeOf(
|
|
Uint8ClampedArrayPrototype,
|
|
arrayBufferView,
|
|
) ||
|
|
ObjectPrototypeIsPrototypeOf(Int16ArrayPrototype, arrayBufferView) ||
|
|
ObjectPrototypeIsPrototypeOf(Uint16ArrayPrototype, arrayBufferView) ||
|
|
ObjectPrototypeIsPrototypeOf(Int32ArrayPrototype, arrayBufferView) ||
|
|
ObjectPrototypeIsPrototypeOf(Uint32ArrayPrototype, arrayBufferView) ||
|
|
ObjectPrototypeIsPrototypeOf(
|
|
BigInt64ArrayPrototype,
|
|
arrayBufferView,
|
|
) ||
|
|
ObjectPrototypeIsPrototypeOf(BigUint64ArrayPrototype, arrayBufferView)
|
|
)
|
|
) {
|
|
throw new DOMException(
|
|
"The provided ArrayBufferView is not an integer array type",
|
|
"TypeMismatchError",
|
|
);
|
|
}
|
|
const ui8 = new Uint8Array(
|
|
arrayBufferView.buffer,
|
|
arrayBufferView.byteOffset,
|
|
arrayBufferView.byteLength,
|
|
);
|
|
ops.op_crypto_get_random_values(ui8);
|
|
return arrayBufferView;
|
|
}
|
|
|
|
randomUUID() {
|
|
webidl.assertBranded(this, CryptoPrototype);
|
|
return ops.op_crypto_random_uuid();
|
|
}
|
|
|
|
get subtle() {
|
|
webidl.assertBranded(this, CryptoPrototype);
|
|
return subtle;
|
|
}
|
|
|
|
[SymbolFor("Deno.customInspect")](inspect) {
|
|
return `${this.constructor.name} ${inspect({})}`;
|
|
}
|
|
}
|
|
|
|
webidl.configurePrototype(Crypto);
|
|
const CryptoPrototype = Crypto.prototype;
|
|
|
|
window.__bootstrap.crypto = {
|
|
SubtleCrypto,
|
|
crypto: webidl.createBranded(Crypto),
|
|
Crypto,
|
|
CryptoKey,
|
|
};
|
|
})(this);
|