summaryrefslogtreecommitdiff
path: root/ext/crypto
diff options
context:
space:
mode:
authorDivy Srivastava <dj.srivastava23@gmail.com>2021-08-26 16:18:07 +0530
committerGitHub <noreply@github.com>2021-08-26 12:48:07 +0200
commit23a9bc099d21ef7d45fe0f76e2fc53740ca98f6a (patch)
tree1fffaf83c0b6582acf33b042275e1b792a42e3e1 /ext/crypto
parent5d7d9d64434bd0a9f1fcf391dabc51693e8cf1ae (diff)
feat(ext/crypto): implement importKey and deriveBits for PBKDF2 (#11642)
Diffstat (limited to 'ext/crypto')
-rw-r--r--ext/crypto/00_crypto.js157
-rw-r--r--ext/crypto/01_webidl.js23
-rw-r--r--ext/crypto/key.rs2
-rw-r--r--ext/crypto/lib.rs46
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,