1
0
Fork 0
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:
Divy Srivastava 2021-08-04 00:54:02 +05:30 committed by GitHub
parent d7d452efc1
commit 86f89f9222
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 13759 additions and 34 deletions

View file

@ -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,
); );

View file

@ -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,

View file

@ -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