diff options
author | Divy Srivastava <dj.srivastava23@gmail.com> | 2021-08-26 16:18:07 +0530 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-08-26 12:48:07 +0200 |
commit | 23a9bc099d21ef7d45fe0f76e2fc53740ca98f6a (patch) | |
tree | 1fffaf83c0b6582acf33b042275e1b792a42e3e1 /ext/crypto | |
parent | 5d7d9d64434bd0a9f1fcf391dabc51693e8cf1ae (diff) |
feat(ext/crypto): implement importKey and deriveBits for PBKDF2 (#11642)
Diffstat (limited to 'ext/crypto')
-rw-r--r-- | ext/crypto/00_crypto.js | 157 | ||||
-rw-r--r-- | ext/crypto/01_webidl.js | 23 | ||||
-rw-r--r-- | ext/crypto/key.rs | 2 | ||||
-rw-r--r-- | ext/crypto/lib.rs | 46 |
4 files changed, 219 insertions, 9 deletions
diff --git a/ext/crypto/00_crypto.js b/ext/crypto/00_crypto.js index fd7431b7c..5c80ac0ca 100644 --- a/ext/crypto/00_crypto.js +++ b/ext/crypto/00_crypto.js @@ -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"); } @@ -783,6 +834,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 * @param {BufferSource} signature @@ -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 { diff --git a/ext/crypto/01_webidl.js b/ext/crypto/01_webidl.js index 4216cb524..43bc5e822 100644 --- a/ext/crypto/01_webidl.js +++ b/ext/crypto/01_webidl.js @@ -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, diff --git a/ext/crypto/key.rs b/ext/crypto/key.rs index cb44812fd..d2420bfe9 100644 --- a/ext/crypto/key.rs +++ b/ext/crypto/key.rs @@ -114,4 +114,6 @@ pub enum Algorithm { AesKw, #[serde(rename = "HMAC")] Hmac, + #[serde(rename = "PBKDF2")] + Pbkdf2, } diff --git a/ext/crypto/lib.rs b/ext/crypto/lib.rs index 9224562f6..b68bd7887 100644 --- a/ext/crypto/lib.rs +++ b/ext/crypto/lib.rs @@ -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)), @@ -521,6 +524,49 @@ pub async fn op_crypto_verify_key( #[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 { key: KeyData, algorithm: Algorithm, |