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: {},
|
RsaPssParams: {},
|
||||||
EcdsaParams: { hash: "HashAlgorithmIdentifier" },
|
EcdsaParams: { hash: "HashAlgorithmIdentifier" },
|
||||||
HmacImportParams: { hash: "HashAlgorithmIdentifier" },
|
HmacImportParams: { hash: "HashAlgorithmIdentifier" },
|
||||||
|
Pbkdf2Params: { hash: "HashAlgorithmIdentifier", salt: "BufferSource" },
|
||||||
RsaOaepParams: { label: "BufferSource" },
|
RsaOaepParams: { label: "BufferSource" },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -86,6 +87,10 @@
|
||||||
},
|
},
|
||||||
"importKey": {
|
"importKey": {
|
||||||
"HMAC": "HmacImportParams",
|
"HMAC": "HmacImportParams",
|
||||||
|
"PBKDF2": null,
|
||||||
|
},
|
||||||
|
"deriveBits": {
|
||||||
|
"PBKDF2": "Pbkdf2Params",
|
||||||
},
|
},
|
||||||
"encrypt": {
|
"encrypt": {
|
||||||
"RSA-OAEP": "RsaOaepParams",
|
"RSA-OAEP": "RsaOaepParams",
|
||||||
|
@ -657,18 +662,18 @@
|
||||||
|
|
||||||
const normalizedAlgorithm = normalizeAlgorithm(algorithm, "importKey");
|
const normalizedAlgorithm = normalizeAlgorithm(algorithm, "importKey");
|
||||||
|
|
||||||
if (
|
|
||||||
ArrayPrototypeFind(
|
|
||||||
keyUsages,
|
|
||||||
(u) => !ArrayPrototypeIncludes(["sign", "verify"], u),
|
|
||||||
) !== undefined
|
|
||||||
) {
|
|
||||||
throw new DOMException("Invalid key usages", "SyntaxError");
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (normalizedAlgorithm.name) {
|
switch (normalizedAlgorithm.name) {
|
||||||
// https://w3c.github.io/webcrypto/#hmac-operations
|
// https://w3c.github.io/webcrypto/#hmac-operations
|
||||||
case "HMAC": {
|
case "HMAC": {
|
||||||
|
if (
|
||||||
|
ArrayPrototypeFind(
|
||||||
|
keyUsages,
|
||||||
|
(u) => !ArrayPrototypeIncludes(["sign", "verify"], u),
|
||||||
|
) !== undefined
|
||||||
|
) {
|
||||||
|
throw new DOMException("Invalid key usages", "SyntaxError");
|
||||||
|
}
|
||||||
|
|
||||||
switch (format) {
|
switch (format) {
|
||||||
case "raw": {
|
case "raw": {
|
||||||
const hash = normalizedAlgorithm.hash;
|
const hash = normalizedAlgorithm.hash;
|
||||||
|
@ -726,6 +731,52 @@
|
||||||
// TODO(@littledivy): RSASSA-PKCS1-v1_5
|
// TODO(@littledivy): RSASSA-PKCS1-v1_5
|
||||||
// TODO(@littledivy): RSA-PSS
|
// TODO(@littledivy): RSA-PSS
|
||||||
// TODO(@littledivy): ECDSA
|
// 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:
|
default:
|
||||||
throw new DOMException("Not implemented", "NotSupportedError");
|
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 {string} algorithm
|
||||||
* @param {CryptoKey} key
|
* @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);
|
const subtle = webidl.createBranded(SubtleCrypto);
|
||||||
|
|
||||||
class Crypto {
|
class Crypto {
|
||||||
|
|
|
@ -178,6 +178,29 @@
|
||||||
webidl.converters.HmacImportParams = webidl
|
webidl.converters.HmacImportParams = webidl
|
||||||
.createDictionaryConverter("HmacImportParams", dictHmacImportParams);
|
.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(
|
webidl.converters.CryptoKey = webidl.createInterfaceConverter(
|
||||||
"CryptoKey",
|
"CryptoKey",
|
||||||
CryptoKey,
|
CryptoKey,
|
||||||
|
|
|
@ -114,4 +114,6 @@ pub enum Algorithm {
|
||||||
AesKw,
|
AesKw,
|
||||||
#[serde(rename = "HMAC")]
|
#[serde(rename = "HMAC")]
|
||||||
Hmac,
|
Hmac,
|
||||||
|
#[serde(rename = "PBKDF2")]
|
||||||
|
Pbkdf2,
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ use serde::Deserialize;
|
||||||
|
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
|
use std::num::NonZeroU32;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
|
@ -27,6 +28,7 @@ use rand::SeedableRng;
|
||||||
use ring::digest;
|
use ring::digest;
|
||||||
use ring::hmac::Algorithm as HmacAlgorithm;
|
use ring::hmac::Algorithm as HmacAlgorithm;
|
||||||
use ring::hmac::Key as HmacKey;
|
use ring::hmac::Key as HmacKey;
|
||||||
|
use ring::pbkdf2;
|
||||||
use ring::rand as RingRand;
|
use ring::rand as RingRand;
|
||||||
use ring::rand::SecureRandom;
|
use ring::rand::SecureRandom;
|
||||||
use ring::signature::EcdsaKeyPair;
|
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_generate_key", op_async(op_crypto_generate_key)),
|
||||||
("op_crypto_sign_key", op_async(op_crypto_sign_key)),
|
("op_crypto_sign_key", op_async(op_crypto_sign_key)),
|
||||||
("op_crypto_verify_key", op_async(op_crypto_verify_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_encrypt_key", op_async(op_crypto_encrypt_key)),
|
||||||
("op_crypto_decrypt_key", op_async(op_crypto_decrypt_key)),
|
("op_crypto_decrypt_key", op_async(op_crypto_decrypt_key)),
|
||||||
("op_crypto_subtle_digest", op_async(op_crypto_subtle_digest)),
|
("op_crypto_subtle_digest", op_async(op_crypto_subtle_digest)),
|
||||||
|
@ -519,6 +522,49 @@ pub async fn op_crypto_verify_key(
|
||||||
Ok(verification)
|
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)]
|
#[derive(Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct EncryptArg {
|
pub struct EncryptArg {
|
||||||
|
|
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue