mirror of
https://github.com/denoland/deno.git
synced 2024-12-24 08:09:08 -05:00
feat(ext/crypto): implement importKey and deriveBits for PBKDF2 (#11642)
This commit is contained in:
parent
5d7d9d6443
commit
23a9bc099d
5 changed files with 220 additions and 720 deletions
|
@ -56,6 +56,7 @@
|
|||
RsaPssParams: {},
|
||||
EcdsaParams: { hash: "HashAlgorithmIdentifier" },
|
||||
HmacImportParams: { hash: "HashAlgorithmIdentifier" },
|
||||
Pbkdf2Params: { hash: "HashAlgorithmIdentifier", salt: "BufferSource" },
|
||||
RsaOaepParams: { label: "BufferSource" },
|
||||
};
|
||||
|
||||
|
@ -86,6 +87,10 @@
|
|||
},
|
||||
"importKey": {
|
||||
"HMAC": "HmacImportParams",
|
||||
"PBKDF2": null,
|
||||
},
|
||||
"deriveBits": {
|
||||
"PBKDF2": "Pbkdf2Params",
|
||||
},
|
||||
"encrypt": {
|
||||
"RSA-OAEP": "RsaOaepParams",
|
||||
|
@ -657,6 +662,9 @@
|
|||
|
||||
const normalizedAlgorithm = normalizeAlgorithm(algorithm, "importKey");
|
||||
|
||||
switch (normalizedAlgorithm.name) {
|
||||
// https://w3c.github.io/webcrypto/#hmac-operations
|
||||
case "HMAC": {
|
||||
if (
|
||||
ArrayPrototypeFind(
|
||||
keyUsages,
|
||||
|
@ -666,9 +674,6 @@
|
|||
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;
|
||||
|
@ -726,6 +731,52 @@
|
|||
// TODO(@littledivy): RSASSA-PKCS1-v1_5
|
||||
// TODO(@littledivy): RSA-PSS
|
||||
// TODO(@littledivy): ECDSA
|
||||
case "PBKDF2": {
|
||||
// 1.
|
||||
if (format !== "raw") {
|
||||
throw new DOMException("Format not supported", "NotSupportedError");
|
||||
}
|
||||
|
||||
// 2.
|
||||
if (
|
||||
ArrayPrototypeFind(
|
||||
keyUsages,
|
||||
(u) => !ArrayPrototypeIncludes(["deriveKey", "deriveBits"], u),
|
||||
) !== undefined
|
||||
) {
|
||||
throw new DOMException("Invalid key usages", "SyntaxError");
|
||||
}
|
||||
|
||||
// 3.
|
||||
if (extractable !== false) {
|
||||
throw new DOMException(
|
||||
"Key must not be extractable",
|
||||
"SyntaxError",
|
||||
);
|
||||
}
|
||||
|
||||
// 4.
|
||||
const handle = {};
|
||||
WeakMapPrototypeSet(KEY_STORE, handle, {
|
||||
type: "raw",
|
||||
data: keyData,
|
||||
});
|
||||
|
||||
// 5-9.
|
||||
const algorithm = {
|
||||
name: "PBKDF2",
|
||||
};
|
||||
const key = constructKey(
|
||||
"secret",
|
||||
false,
|
||||
usageIntersection(keyUsages, recognisedUsages),
|
||||
algorithm,
|
||||
handle,
|
||||
);
|
||||
|
||||
// 10.
|
||||
return key;
|
||||
}
|
||||
default:
|
||||
throw new DOMException("Not implemented", "NotSupportedError");
|
||||
}
|
||||
|
@ -782,6 +833,48 @@
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {AlgorithmIdentifier} algorithm
|
||||
* @param {CryptoKey} baseKey
|
||||
* @param {number} length
|
||||
* @returns {Promise<ArrayBuffer>}
|
||||
*/
|
||||
async deriveBits(algorithm, baseKey, length) {
|
||||
webidl.assertBranded(this, SubtleCrypto);
|
||||
const prefix = "Failed to execute 'deriveBits' on 'SubtleCrypto'";
|
||||
webidl.requiredArguments(arguments.length, 3, { prefix });
|
||||
algorithm = webidl.converters.AlgorithmIdentifier(algorithm, {
|
||||
prefix,
|
||||
context: "Argument 1",
|
||||
});
|
||||
baseKey = webidl.converters.CryptoKey(baseKey, {
|
||||
prefix,
|
||||
context: "Argument 2",
|
||||
});
|
||||
length = webidl.converters["unsigned long"](length, {
|
||||
prefix,
|
||||
context: "Argument 3",
|
||||
});
|
||||
|
||||
// 2.
|
||||
const normalizedAlgorithm = normalizeAlgorithm(algorithm, "deriveBits");
|
||||
// 4-6.
|
||||
const result = await deriveBits(normalizedAlgorithm, baseKey, length);
|
||||
// 7.
|
||||
if (normalizedAlgorithm.name !== baseKey[_algorithm].name) {
|
||||
throw new DOMException("InvalidAccessError", "Invalid algorithm name");
|
||||
}
|
||||
// 8.
|
||||
if (!ArrayPrototypeIncludes(baseKey[_usages], "deriveBits")) {
|
||||
throw new DOMException(
|
||||
"InvalidAccessError",
|
||||
"baseKey usages does not contain `deriveBits`",
|
||||
);
|
||||
}
|
||||
// 9-10.
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} algorithm
|
||||
* @param {CryptoKey} key
|
||||
|
@ -1185,6 +1278,52 @@
|
|||
}
|
||||
}
|
||||
|
||||
async function deriveBits(normalizedAlgorithm, baseKey, length) {
|
||||
switch (normalizedAlgorithm.name) {
|
||||
case "PBKDF2": {
|
||||
// 1.
|
||||
if (length == null || length == 0 || length % 8 !== 0) {
|
||||
throw new DOMException("Invalid length", "OperationError");
|
||||
}
|
||||
|
||||
if (normalizedAlgorithm.iterations == 0) {
|
||||
throw new DOMException(
|
||||
"iterations must not be zero",
|
||||
"OperationError",
|
||||
);
|
||||
}
|
||||
|
||||
const handle = baseKey[_handle];
|
||||
const keyData = WeakMapPrototypeGet(KEY_STORE, handle);
|
||||
|
||||
if (ArrayBufferIsView(normalizedAlgorithm.salt)) {
|
||||
normalizedAlgorithm.salt = new Uint8Array(
|
||||
normalizedAlgorithm.salt.buffer,
|
||||
normalizedAlgorithm.salt.byteOffset,
|
||||
normalizedAlgorithm.salt.byteLength,
|
||||
);
|
||||
} else {
|
||||
normalizedAlgorithm.salt = new Uint8Array(normalizedAlgorithm.salt);
|
||||
}
|
||||
normalizedAlgorithm.salt = TypedArrayPrototypeSlice(
|
||||
normalizedAlgorithm.salt,
|
||||
);
|
||||
|
||||
const buf = await core.opAsync("op_crypto_derive_bits", {
|
||||
key: keyData,
|
||||
algorithm: "PBKDF2",
|
||||
hash: normalizedAlgorithm.hash.name,
|
||||
iterations: normalizedAlgorithm.iterations,
|
||||
length,
|
||||
}, normalizedAlgorithm.salt);
|
||||
|
||||
return buf.buffer;
|
||||
}
|
||||
default:
|
||||
throw new DOMException("Not implemented", "NotSupportedError");
|
||||
}
|
||||
}
|
||||
|
||||
const subtle = webidl.createBranded(SubtleCrypto);
|
||||
|
||||
class Crypto {
|
||||
|
|
|
@ -178,6 +178,29 @@
|
|||
webidl.converters.HmacImportParams = webidl
|
||||
.createDictionaryConverter("HmacImportParams", dictHmacImportParams);
|
||||
|
||||
const dictPbkdf2Params = [
|
||||
...dictAlgorithm,
|
||||
{
|
||||
key: "hash",
|
||||
converter: webidl.converters.HashAlgorithmIdentifier,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
key: "iterations",
|
||||
converter: (V, opts) =>
|
||||
webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }),
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
key: "salt",
|
||||
converter: webidl.converters["BufferSource"],
|
||||
required: true,
|
||||
},
|
||||
];
|
||||
|
||||
webidl.converters.Pbkdf2Params = webidl
|
||||
.createDictionaryConverter("Pbkdf2Params", dictPbkdf2Params);
|
||||
|
||||
webidl.converters.CryptoKey = webidl.createInterfaceConverter(
|
||||
"CryptoKey",
|
||||
CryptoKey,
|
||||
|
|
|
@ -114,4 +114,6 @@ pub enum Algorithm {
|
|||
AesKw,
|
||||
#[serde(rename = "HMAC")]
|
||||
Hmac,
|
||||
#[serde(rename = "PBKDF2")]
|
||||
Pbkdf2,
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ use serde::Deserialize;
|
|||
|
||||
use std::cell::RefCell;
|
||||
use std::convert::TryInto;
|
||||
use std::num::NonZeroU32;
|
||||
use std::rc::Rc;
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
|
@ -27,6 +28,7 @@ use rand::SeedableRng;
|
|||
use ring::digest;
|
||||
use ring::hmac::Algorithm as HmacAlgorithm;
|
||||
use ring::hmac::Key as HmacKey;
|
||||
use ring::pbkdf2;
|
||||
use ring::rand as RingRand;
|
||||
use ring::rand::SecureRandom;
|
||||
use ring::signature::EcdsaKeyPair;
|
||||
|
@ -74,6 +76,7 @@ pub fn init(maybe_seed: Option<u64>) -> Extension {
|
|||
("op_crypto_generate_key", op_async(op_crypto_generate_key)),
|
||||
("op_crypto_sign_key", op_async(op_crypto_sign_key)),
|
||||
("op_crypto_verify_key", op_async(op_crypto_verify_key)),
|
||||
("op_crypto_derive_bits", op_async(op_crypto_derive_bits)),
|
||||
("op_crypto_encrypt_key", op_async(op_crypto_encrypt_key)),
|
||||
("op_crypto_decrypt_key", op_async(op_crypto_decrypt_key)),
|
||||
("op_crypto_subtle_digest", op_async(op_crypto_subtle_digest)),
|
||||
|
@ -519,6 +522,49 @@ pub async fn op_crypto_verify_key(
|
|||
Ok(verification)
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DeriveKeyArg {
|
||||
key: KeyData,
|
||||
algorithm: Algorithm,
|
||||
hash: Option<CryptoHash>,
|
||||
length: usize,
|
||||
iterations: Option<u32>,
|
||||
}
|
||||
|
||||
pub async fn op_crypto_derive_bits(
|
||||
_state: Rc<RefCell<OpState>>,
|
||||
args: DeriveKeyArg,
|
||||
zero_copy: Option<ZeroCopyBuf>,
|
||||
) -> Result<ZeroCopyBuf, AnyError> {
|
||||
let zero_copy = zero_copy.ok_or_else(null_opbuf)?;
|
||||
let salt = &*zero_copy;
|
||||
let algorithm = args.algorithm;
|
||||
match algorithm {
|
||||
Algorithm::Pbkdf2 => {
|
||||
// The caller must validate these cases.
|
||||
assert!(args.length > 0);
|
||||
assert!(args.length % 8 == 0);
|
||||
|
||||
let algorithm = match args.hash.ok_or_else(not_supported)? {
|
||||
CryptoHash::Sha1 => pbkdf2::PBKDF2_HMAC_SHA1,
|
||||
CryptoHash::Sha256 => pbkdf2::PBKDF2_HMAC_SHA256,
|
||||
CryptoHash::Sha384 => pbkdf2::PBKDF2_HMAC_SHA384,
|
||||
CryptoHash::Sha512 => pbkdf2::PBKDF2_HMAC_SHA512,
|
||||
};
|
||||
|
||||
// This will never panic. We have already checked length earlier.
|
||||
let iterations =
|
||||
NonZeroU32::new(args.iterations.ok_or_else(not_supported)?).unwrap();
|
||||
let secret = args.key.data;
|
||||
let mut out = vec![0; args.length / 8];
|
||||
pbkdf2::derive(algorithm, iterations, salt, &secret, &mut out);
|
||||
Ok(out.into())
|
||||
}
|
||||
_ => Err(type_error("Unsupported algorithm".to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EncryptArg {
|
||||
|
|
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue