mirror of
https://github.com/denoland/deno.git
synced 2025-01-03 12:58:54 -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.
|
// P-521 is not yet supported.
|
||||||
const supportedNamedCurves = ["P-256", "P-384"];
|
const supportedNamedCurves = ["P-256", "P-384"];
|
||||||
|
const recognisedUsages = [
|
||||||
|
"encrypt",
|
||||||
|
"decrypt",
|
||||||
|
"sign",
|
||||||
|
"verify",
|
||||||
|
"deriveKey",
|
||||||
|
"deriveBits",
|
||||||
|
"wrapKey",
|
||||||
|
"unwrapKey",
|
||||||
|
];
|
||||||
|
|
||||||
const simpleAlgorithmDictionaries = {
|
const simpleAlgorithmDictionaries = {
|
||||||
RsaHashedKeyGenParams: { hash: "HashAlgorithmIdentifier" },
|
RsaHashedKeyGenParams: { hash: "HashAlgorithmIdentifier" },
|
||||||
|
@ -45,6 +55,7 @@
|
||||||
HmacKeyGenParams: { hash: "HashAlgorithmIdentifier" },
|
HmacKeyGenParams: { hash: "HashAlgorithmIdentifier" },
|
||||||
RsaPssParams: {},
|
RsaPssParams: {},
|
||||||
EcdsaParams: { hash: "HashAlgorithmIdentifier" },
|
EcdsaParams: { hash: "HashAlgorithmIdentifier" },
|
||||||
|
HmacImportParams: { hash: "HashAlgorithmIdentifier" },
|
||||||
};
|
};
|
||||||
|
|
||||||
const supportedAlgorithms = {
|
const supportedAlgorithms = {
|
||||||
|
@ -70,6 +81,9 @@
|
||||||
"RSASSA-PKCS1-v1_5": null,
|
"RSASSA-PKCS1-v1_5": null,
|
||||||
"RSA-PSS": "RsaPssParams",
|
"RSA-PSS": "RsaPssParams",
|
||||||
},
|
},
|
||||||
|
"importKey": {
|
||||||
|
"HMAC": "HmacImportParams",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// See https://www.w3.org/TR/WebCryptoAPI/#dfn-normalize-an-algorithm
|
// See https://www.w3.org/TR/WebCryptoAPI/#dfn-normalize-an-algorithm
|
||||||
|
@ -225,14 +239,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://w3c.github.io/webcrypto/#concept-usage-intersection
|
// https://w3c.github.io/webcrypto/#concept-usage-intersection
|
||||||
// TODO(littledivy): When the need arises, make `b` a list.
|
|
||||||
/**
|
/**
|
||||||
* @param {string[]} a
|
* @param {string[]} a
|
||||||
* @param {string} b
|
* @param {string[]} b
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
function usageIntersection(a, b) {
|
function usageIntersection(a, b) {
|
||||||
return ArrayPrototypeIncludes(a, b) ? [b] : [];
|
return a.filter((i) => b.includes(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(lucacasonato): this should be moved to rust
|
// TODO(lucacasonato): this should be moved to rust
|
||||||
|
@ -415,6 +428,166 @@
|
||||||
throw new TypeError("unreachable");
|
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 {string} algorithm
|
||||||
* @param {CryptoKey} key
|
* @param {CryptoKey} key
|
||||||
|
@ -623,7 +796,7 @@
|
||||||
const publicKey = constructKey(
|
const publicKey = constructKey(
|
||||||
"public",
|
"public",
|
||||||
true,
|
true,
|
||||||
usageIntersection(usages, "verify"),
|
usageIntersection(usages, ["verify"]),
|
||||||
algorithm,
|
algorithm,
|
||||||
handle,
|
handle,
|
||||||
);
|
);
|
||||||
|
@ -632,7 +805,7 @@
|
||||||
const privateKey = constructKey(
|
const privateKey = constructKey(
|
||||||
"private",
|
"private",
|
||||||
extractable,
|
extractable,
|
||||||
usageIntersection(usages, "sign"),
|
usageIntersection(usages, ["sign"]),
|
||||||
algorithm,
|
algorithm,
|
||||||
handle,
|
handle,
|
||||||
);
|
);
|
||||||
|
@ -682,7 +855,7 @@
|
||||||
const publicKey = constructKey(
|
const publicKey = constructKey(
|
||||||
"public",
|
"public",
|
||||||
true,
|
true,
|
||||||
usageIntersection(usages, "verify"),
|
usageIntersection(usages, ["verify"]),
|
||||||
algorithm,
|
algorithm,
|
||||||
handle,
|
handle,
|
||||||
);
|
);
|
||||||
|
@ -691,7 +864,7 @@
|
||||||
const privateKey = constructKey(
|
const privateKey = constructKey(
|
||||||
"private",
|
"private",
|
||||||
extractable,
|
extractable,
|
||||||
usageIntersection(usages, "sign"),
|
usageIntersection(usages, ["sign"]),
|
||||||
algorithm,
|
algorithm,
|
||||||
handle,
|
handle,
|
||||||
);
|
);
|
||||||
|
|
|
@ -24,6 +24,13 @@
|
||||||
"secret",
|
"secret",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
webidl.converters.KeyFormat = webidl.createEnumConverter("KeyFormat", [
|
||||||
|
"raw",
|
||||||
|
"pkcs8",
|
||||||
|
"spki",
|
||||||
|
"jwk",
|
||||||
|
]);
|
||||||
|
|
||||||
webidl.converters.KeyUsage = webidl.createEnumConverter("KeyUsage", [
|
webidl.converters.KeyUsage = webidl.createEnumConverter("KeyUsage", [
|
||||||
"encrypt",
|
"encrypt",
|
||||||
"decrypt",
|
"decrypt",
|
||||||
|
@ -143,6 +150,23 @@
|
||||||
webidl.converters["EcdsaParams"] = webidl
|
webidl.converters["EcdsaParams"] = webidl
|
||||||
.createDictionaryConverter("EcdsaParams", dictEcdsaParams);
|
.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(
|
webidl.converters.CryptoKey = webidl.createInterfaceConverter(
|
||||||
"CryptoKey",
|
"CryptoKey",
|
||||||
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;
|
saltLength: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface HmacImportParams extends Algorithm {
|
||||||
|
hash: HashAlgorithmIdentifier;
|
||||||
|
length?: number;
|
||||||
|
}
|
||||||
|
|
||||||
/** The CryptoKey dictionary of the Web Crypto API represents a cryptographic key. */
|
/** The CryptoKey dictionary of the Web Crypto API represents a cryptographic key. */
|
||||||
interface CryptoKey {
|
interface CryptoKey {
|
||||||
readonly algorithm: KeyAlgorithm;
|
readonly algorithm: KeyAlgorithm;
|
||||||
|
@ -95,6 +100,13 @@ interface SubtleCrypto {
|
||||||
extractable: boolean,
|
extractable: boolean,
|
||||||
keyUsages: KeyUsage[],
|
keyUsages: KeyUsage[],
|
||||||
): Promise<CryptoKeyPair | CryptoKey>;
|
): Promise<CryptoKeyPair | CryptoKey>;
|
||||||
|
importKey(
|
||||||
|
format: "raw",
|
||||||
|
keyData: BufferSource,
|
||||||
|
algorithm: AlgorithmIdentifier | HmacImportParams,
|
||||||
|
extractable: boolean,
|
||||||
|
keyUsages: KeyUsage[],
|
||||||
|
): Promise<CryptoKey>;
|
||||||
sign(
|
sign(
|
||||||
algorithm: AlgorithmIdentifier | RsaPssParams | EcdsaParams,
|
algorithm: AlgorithmIdentifier | RsaPssParams | EcdsaParams,
|
||||||
key: CryptoKey,
|
key: CryptoKey,
|
||||||
|
|
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue