// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
// @ts-check
///
///
///
///
import { core, primordials } from "ext:core/mod.js";
const {
isArrayBuffer,
isTypedArray,
isDataView,
} = core;
import {
op_crypto_base64url_decode,
op_crypto_base64url_encode,
op_crypto_decrypt,
op_crypto_derive_bits,
op_crypto_derive_bits_x25519,
op_crypto_derive_bits_x448,
op_crypto_encrypt,
op_crypto_export_key,
op_crypto_export_pkcs8_ed25519,
op_crypto_export_pkcs8_x25519,
op_crypto_export_pkcs8_x448,
op_crypto_export_spki_ed25519,
op_crypto_export_spki_x25519,
op_crypto_export_spki_x448,
op_crypto_generate_ed25519_keypair,
op_crypto_generate_key,
op_crypto_generate_x25519_keypair,
op_crypto_generate_x448_keypair,
op_crypto_get_random_values,
op_crypto_import_key,
op_crypto_import_pkcs8_ed25519,
op_crypto_import_pkcs8_x25519,
op_crypto_import_pkcs8_x448,
op_crypto_import_spki_ed25519,
op_crypto_import_spki_x25519,
op_crypto_import_spki_x448,
op_crypto_jwk_x_ed25519,
op_crypto_random_uuid,
op_crypto_sign_ed25519,
op_crypto_sign_key,
op_crypto_subtle_digest,
op_crypto_unwrap_key,
op_crypto_verify_ed25519,
op_crypto_verify_key,
op_crypto_wrap_key,
} from "ext:core/ops";
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;
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 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,
"X448": 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,
"X448": null,
},
"deriveBits": {
"HKDF": "HkdfParams",
"PBKDF2": "Pbkdf2Params",
"ECDH": "EcdhKeyDeriveParams",
"X25519": "EcdhKeyDeriveParams",
"X448": "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