mirror of
https://github.com/denoland/deno.git
synced 2024-11-21 15:04:11 -05:00
feat(ext/crypto): implement deriveKey (#12117)
This commit is contained in:
parent
f332d72f16
commit
58f04d8e46
5 changed files with 250 additions and 10917 deletions
|
@ -513,6 +513,42 @@ unitTest(async function testHkdfDeriveBits() {
|
|||
assertEquals(result.byteLength, 128 / 8);
|
||||
});
|
||||
|
||||
unitTest(async function testDeriveKey() {
|
||||
// Test deriveKey
|
||||
const rawKey = await crypto.getRandomValues(new Uint8Array(16));
|
||||
const key = await crypto.subtle.importKey(
|
||||
"raw",
|
||||
rawKey,
|
||||
"PBKDF2",
|
||||
false,
|
||||
["deriveKey", "deriveBits"],
|
||||
);
|
||||
|
||||
const salt = await crypto.getRandomValues(new Uint8Array(16));
|
||||
const derivedKey = await crypto.subtle.deriveKey(
|
||||
{
|
||||
name: "PBKDF2",
|
||||
salt,
|
||||
iterations: 1000,
|
||||
hash: "SHA-256",
|
||||
},
|
||||
key,
|
||||
{ name: "HMAC", hash: "SHA-256" },
|
||||
true,
|
||||
["sign"],
|
||||
);
|
||||
|
||||
assert(derivedKey instanceof CryptoKey);
|
||||
assertEquals(derivedKey.type, "secret");
|
||||
assertEquals(derivedKey.extractable, true);
|
||||
assertEquals(derivedKey.usages, ["sign"]);
|
||||
|
||||
const algorithm = derivedKey.algorithm as HmacKeyAlgorithm;
|
||||
assertEquals(algorithm.name, "HMAC");
|
||||
assertEquals(algorithm.hash.name, "SHA-256");
|
||||
assertEquals(algorithm.length, 256);
|
||||
});
|
||||
|
||||
unitTest(async function testAesCbcEncryptDecrypt() {
|
||||
const key = await crypto.subtle.generateKey(
|
||||
{ name: "AES-CBC", length: 128 },
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
StringFromCharCode,
|
||||
Symbol,
|
||||
SymbolFor,
|
||||
SyntaxError,
|
||||
WeakMap,
|
||||
WeakMapPrototypeGet,
|
||||
WeakMapPrototypeSet,
|
||||
|
@ -123,6 +124,14 @@
|
|||
"RSA-OAEP": "RsaOaepParams",
|
||||
"AES-CBC": "AesCbcParams",
|
||||
},
|
||||
"get key length": {
|
||||
"AES-CBC": "AesDerivedKeyParams",
|
||||
"AES-GCM": "AesDerivedKeyParams",
|
||||
"AES-KW": "AesDerivedKeyParams",
|
||||
"HMAC": "HmacImportParams",
|
||||
"HKDF": null,
|
||||
"PBKDF2": null,
|
||||
},
|
||||
"wrapKey": {
|
||||
// TODO(@littledivy): Enable this once implemented.
|
||||
// "AES-KW": "AesKeyWrapParams",
|
||||
|
@ -322,6 +331,67 @@
|
|||
/** @type {WeakMap<object, object>} */
|
||||
const KEY_STORE = new WeakMap();
|
||||
|
||||
function getKeyLength(algorithm) {
|
||||
switch (algorithm.name) {
|
||||
case "AES-CBC":
|
||||
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();
|
||||
|
@ -1574,19 +1644,119 @@
|
|||
const result = await deriveBits(normalizedAlgorithm, baseKey, length);
|
||||
// 7.
|
||||
if (normalizedAlgorithm.name !== baseKey[_algorithm].name) {
|
||||
throw new DOMException("InvalidAccessError", "Invalid algorithm name");
|
||||
throw new DOMException("Invalid algorithm name", "InvalidAccessError");
|
||||
}
|
||||
// 8.
|
||||
if (!ArrayPrototypeIncludes(baseKey[_usages], "deriveBits")) {
|
||||
throw new DOMException(
|
||||
"InvalidAccessError",
|
||||
"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, SubtleCrypto);
|
||||
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
|
||||
|
|
|
@ -367,6 +367,16 @@
|
|||
webidl.converters.Pbkdf2Params = webidl
|
||||
.createDictionaryConverter("Pbkdf2Params", dictPbkdf2Params);
|
||||
|
||||
const dictAesDerivedKeyParams = [
|
||||
...dictAlgorithm,
|
||||
{
|
||||
key: "length",
|
||||
converter: (V, opts) =>
|
||||
webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }),
|
||||
required: true,
|
||||
},
|
||||
];
|
||||
|
||||
const dictAesCbcParams = [
|
||||
...dictAlgorithm,
|
||||
{
|
||||
|
@ -376,6 +386,9 @@
|
|||
},
|
||||
];
|
||||
|
||||
webidl.converters.AesDerivedKeyParams = webidl
|
||||
.createDictionaryConverter("AesDerivedKeyParams", dictAesDerivedKeyParams);
|
||||
|
||||
webidl.converters.AesCbcParams = webidl
|
||||
.createDictionaryConverter("AesCbcParams", dictAesCbcParams);
|
||||
|
||||
|
|
16
ext/crypto/lib.deno_crypto.d.ts
vendored
16
ext/crypto/lib.deno_crypto.d.ts
vendored
|
@ -129,6 +129,10 @@ interface Pbkdf2Params extends Algorithm {
|
|||
salt: BufferSource;
|
||||
}
|
||||
|
||||
interface AesDerivedKeyParams extends Algorithm {
|
||||
length: number;
|
||||
}
|
||||
|
||||
interface EcdhKeyDeriveParams extends Algorithm {
|
||||
public: CryptoKey;
|
||||
}
|
||||
|
@ -235,6 +239,18 @@ interface SubtleCrypto {
|
|||
baseKey: CryptoKey,
|
||||
length: number,
|
||||
): Promise<ArrayBuffer>;
|
||||
deriveKey(
|
||||
algorithm: AlgorithmIdentifier | HkdfParams | Pbkdf2Params,
|
||||
baseKey: CryptoKey,
|
||||
derivedKeyType:
|
||||
| AlgorithmIdentifier
|
||||
| AesDerivedKeyParams
|
||||
| HmacImportParams
|
||||
| HkdfParams
|
||||
| Pbkdf2Params,
|
||||
extractable: boolean,
|
||||
keyUsages: KeyUsage[],
|
||||
): Promise<CryptoKey>;
|
||||
wrapKey(
|
||||
format: KeyFormat,
|
||||
key: CryptoKey,
|
||||
|
|
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue