mirror of
https://github.com/denoland/deno.git
synced 2025-03-03 17:34:47 -05:00
feat(extensions/crypto): implement importKey and exportKey for raw HMAC keys (#11367)
This commit introduces "SubtleCrypto.importKey()" and "SubtleCrypto.exportKey()" APIs.
This commit is contained in:
parent
d7d452efc1
commit
86f89f9222
4 changed files with 13759 additions and 34 deletions
|
@ -38,6 +38,16 @@
|
|||
|
||||
// P-521 is not yet supported.
|
||||
const supportedNamedCurves = ["P-256", "P-384"];
|
||||
const recognisedUsages = [
|
||||
"encrypt",
|
||||
"decrypt",
|
||||
"sign",
|
||||
"verify",
|
||||
"deriveKey",
|
||||
"deriveBits",
|
||||
"wrapKey",
|
||||
"unwrapKey",
|
||||
];
|
||||
|
||||
const simpleAlgorithmDictionaries = {
|
||||
RsaHashedKeyGenParams: { hash: "HashAlgorithmIdentifier" },
|
||||
|
@ -45,6 +55,7 @@
|
|||
HmacKeyGenParams: { hash: "HashAlgorithmIdentifier" },
|
||||
RsaPssParams: {},
|
||||
EcdsaParams: { hash: "HashAlgorithmIdentifier" },
|
||||
HmacImportParams: { hash: "HashAlgorithmIdentifier" },
|
||||
};
|
||||
|
||||
const supportedAlgorithms = {
|
||||
|
@ -70,6 +81,9 @@
|
|||
"RSASSA-PKCS1-v1_5": null,
|
||||
"RSA-PSS": "RsaPssParams",
|
||||
},
|
||||
"importKey": {
|
||||
"HMAC": "HmacImportParams",
|
||||
},
|
||||
};
|
||||
|
||||
// See https://www.w3.org/TR/WebCryptoAPI/#dfn-normalize-an-algorithm
|
||||
|
@ -225,14 +239,13 @@
|
|||
}
|
||||
|
||||
// https://w3c.github.io/webcrypto/#concept-usage-intersection
|
||||
// TODO(littledivy): When the need arises, make `b` a list.
|
||||
/**
|
||||
* @param {string[]} a
|
||||
* @param {string} b
|
||||
* @param {string[]} b
|
||||
* @returns
|
||||
*/
|
||||
function usageIntersection(a, b) {
|
||||
return ArrayPrototypeIncludes(a, b) ? [b] : [];
|
||||
return a.filter((i) => b.includes(i));
|
||||
}
|
||||
|
||||
// TODO(lucacasonato): this should be moved to rust
|
||||
|
@ -415,6 +428,166 @@
|
|||
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, SubtleCrypto);
|
||||
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(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",
|
||||
});
|
||||
|
||||
const normalizedAlgorithm = normalizeAlgorithm(algorithm, "importKey");
|
||||
|
||||
if (
|
||||
ArrayPrototypeFind(
|
||||
keyUsages,
|
||||
(u) => !ArrayPrototypeIncludes(["sign", "verify"], u),
|
||||
) !== undefined
|
||||
) {
|
||||
throw new DOMException("Invalid key usages", "SyntaxError");
|
||||
}
|
||||
|
||||
switch (normalizedAlgorithm.name) {
|
||||
// https://w3c.github.io/webcrypto/#hmac-operations
|
||||
case "HMAC": {
|
||||
switch (format) {
|
||||
case "raw": {
|
||||
const hash = normalizedAlgorithm.hash;
|
||||
// 5.
|
||||
let length = keyData.byteLength * 8;
|
||||
// 6.
|
||||
if (length === 0) {
|
||||
throw new DOMException("Key length is zero", "DataError");
|
||||
}
|
||||
if (normalizeAlgorithm.length) {
|
||||
// 7.
|
||||
if (
|
||||
normalizedAlgorithm.length > length ||
|
||||
normalizedAlgorithm.length <= (length - 8)
|
||||
) {
|
||||
throw new DOMException(
|
||||
"Key length is invalid",
|
||||
"DataError",
|
||||
);
|
||||
}
|
||||
length = normalizeAlgorithm.length;
|
||||
}
|
||||
|
||||
if (keyUsages.length == 0) {
|
||||
throw new DOMException("Key usage is empty", "SyntaxError");
|
||||
}
|
||||
|
||||
const handle = {};
|
||||
WeakMapPrototypeSet(KEY_STORE, handle, {
|
||||
type: "raw",
|
||||
data: keyData,
|
||||
});
|
||||
|
||||
const algorithm = {
|
||||
name: "HMAC",
|
||||
length,
|
||||
hash,
|
||||
};
|
||||
|
||||
const key = constructKey(
|
||||
"secret",
|
||||
true,
|
||||
usageIntersection(keyUsages, recognisedUsages),
|
||||
algorithm,
|
||||
handle,
|
||||
);
|
||||
|
||||
return key;
|
||||
}
|
||||
// TODO(@littledivy): jwk
|
||||
default:
|
||||
throw new DOMException("Not implemented", "NotSupportedError");
|
||||
}
|
||||
}
|
||||
// TODO(@littledivy): RSASSA-PKCS1-v1_5
|
||||
// TODO(@littledivy): RSA-PSS
|
||||
// TODO(@littledivy): ECDSA
|
||||
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, SubtleCrypto);
|
||||
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 bits = WeakMapPrototypeGet(KEY_STORE, handle);
|
||||
|
||||
switch (key[_algorithm].name) {
|
||||
case "HMAC": {
|
||||
if (bits == null) {
|
||||
throw new DOMException("Key is not available", "OperationError");
|
||||
}
|
||||
switch (format) {
|
||||
// 3.
|
||||
case "raw": {
|
||||
for (let _i = 7 & (8 - bits.length % 8); _i > 0; _i--) {
|
||||
bits.push(0);
|
||||
}
|
||||
// 4-5.
|
||||
return bits.buffer;
|
||||
}
|
||||
// TODO(@littledivy): jwk
|
||||
default:
|
||||
throw new DOMException("Not implemented", "NotSupportedError");
|
||||
}
|
||||
}
|
||||
// TODO(@littledivy): RSASSA-PKCS1-v1_5
|
||||
// TODO(@littledivy): RSA-PSS
|
||||
// TODO(@littledivy): ECDSA
|
||||
default:
|
||||
throw new DOMException("Not implemented", "NotSupportedError");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} algorithm
|
||||
* @param {CryptoKey} key
|
||||
|
@ -623,7 +796,7 @@
|
|||
const publicKey = constructKey(
|
||||
"public",
|
||||
true,
|
||||
usageIntersection(usages, "verify"),
|
||||
usageIntersection(usages, ["verify"]),
|
||||
algorithm,
|
||||
handle,
|
||||
);
|
||||
|
@ -632,7 +805,7 @@
|
|||
const privateKey = constructKey(
|
||||
"private",
|
||||
extractable,
|
||||
usageIntersection(usages, "sign"),
|
||||
usageIntersection(usages, ["sign"]),
|
||||
algorithm,
|
||||
handle,
|
||||
);
|
||||
|
@ -682,7 +855,7 @@
|
|||
const publicKey = constructKey(
|
||||
"public",
|
||||
true,
|
||||
usageIntersection(usages, "verify"),
|
||||
usageIntersection(usages, ["verify"]),
|
||||
algorithm,
|
||||
handle,
|
||||
);
|
||||
|
@ -691,7 +864,7 @@
|
|||
const privateKey = constructKey(
|
||||
"private",
|
||||
extractable,
|
||||
usageIntersection(usages, "sign"),
|
||||
usageIntersection(usages, ["sign"]),
|
||||
algorithm,
|
||||
handle,
|
||||
);
|
||||
|
|
|
@ -24,6 +24,13 @@
|
|||
"secret",
|
||||
]);
|
||||
|
||||
webidl.converters.KeyFormat = webidl.createEnumConverter("KeyFormat", [
|
||||
"raw",
|
||||
"pkcs8",
|
||||
"spki",
|
||||
"jwk",
|
||||
]);
|
||||
|
||||
webidl.converters.KeyUsage = webidl.createEnumConverter("KeyUsage", [
|
||||
"encrypt",
|
||||
"decrypt",
|
||||
|
@ -143,6 +150,23 @@
|
|||
webidl.converters["EcdsaParams"] = webidl
|
||||
.createDictionaryConverter("EcdsaParams", dictEcdsaParams);
|
||||
|
||||
const dictHmacImportParams = [
|
||||
...dictAlgorithm,
|
||||
{
|
||||
key: "hash",
|
||||
converter: webidl.converters.HashAlgorithmIdentifier,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
key: "length",
|
||||
converter: (V, opts) =>
|
||||
webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }),
|
||||
},
|
||||
];
|
||||
|
||||
webidl.converters.HmacImportParams = webidl
|
||||
.createDictionaryConverter("HmacImportParams", dictHmacImportParams);
|
||||
|
||||
webidl.converters.CryptoKey = webidl.createInterfaceConverter(
|
||||
"CryptoKey",
|
||||
CryptoKey,
|
||||
|
|
12
extensions/crypto/lib.deno_crypto.d.ts
vendored
12
extensions/crypto/lib.deno_crypto.d.ts
vendored
|
@ -54,6 +54,11 @@ interface RsaPssParams extends Algorithm {
|
|||
saltLength: number;
|
||||
}
|
||||
|
||||
interface HmacImportParams extends Algorithm {
|
||||
hash: HashAlgorithmIdentifier;
|
||||
length?: number;
|
||||
}
|
||||
|
||||
/** The CryptoKey dictionary of the Web Crypto API represents a cryptographic key. */
|
||||
interface CryptoKey {
|
||||
readonly algorithm: KeyAlgorithm;
|
||||
|
@ -95,6 +100,13 @@ interface SubtleCrypto {
|
|||
extractable: boolean,
|
||||
keyUsages: KeyUsage[],
|
||||
): Promise<CryptoKeyPair | CryptoKey>;
|
||||
importKey(
|
||||
format: "raw",
|
||||
keyData: BufferSource,
|
||||
algorithm: AlgorithmIdentifier | HmacImportParams,
|
||||
extractable: boolean,
|
||||
keyUsages: KeyUsage[],
|
||||
): Promise<CryptoKey>;
|
||||
sign(
|
||||
algorithm: AlgorithmIdentifier | RsaPssParams | EcdsaParams,
|
||||
key: CryptoKey,
|
||||
|
|
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue