feat(ext/crypto): implement importKey and deriveBits for PBKDF2 (#11642)

This commit is contained in:
Divy Srivastava 2021-08-26 16:18:07 +05:30 committed by GitHub
parent 5d7d9d6443
commit 23a9bc099d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 220 additions and 720 deletions

View file

@ -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,18 +662,18 @@
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": {
if (
ArrayPrototypeFind(
keyUsages,
(u) => !ArrayPrototypeIncludes(["sign", "verify"], u),
) !== undefined
) {
throw new DOMException("Invalid key usages", "SyntaxError");
}
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 {

View file

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

View file

@ -114,4 +114,6 @@ pub enum Algorithm {
AesKw,
#[serde(rename = "HMAC")]
Hmac,
#[serde(rename = "PBKDF2")]
Pbkdf2,
}

View file

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