diff --git a/Cargo.lock b/Cargo.lock index 7bbe0d63727d31..4f6b82d02e7a32 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -482,6 +482,17 @@ dependencies = [ "generic-array", "rand_core 0.6.3", "subtle", + "zeroize", +] + +[[package]] +name = "crypto-mac" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" +dependencies = [ + "generic-array", + "subtle", ] [[package]] @@ -727,6 +738,8 @@ dependencies = [ "deno_web", "lazy_static", "num-traits", + "p256", + "p384", "rand 0.8.4", "ring", "rsa", @@ -1116,12 +1129,40 @@ dependencies = [ "text_lines", ] +[[package]] +name = "ecdsa" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43ee23aa5b4f68c7a092b5c3beb25f50c406adc75e2363634f242f28ab255372" +dependencies = [ + "der", + "elliptic-curve", + "hmac", + "signature", +] + [[package]] name = "either" version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +[[package]] +name = "elliptic-curve" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "beca177dcb8eb540133e7680baff45e7cc4d93bf22002676cec549f82343721b" +dependencies = [ + "crypto-bigint", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core 0.6.3", + "subtle", + "zeroize", +] + [[package]] name = "encoding_rs" version = "0.8.28" @@ -1228,6 +1269,16 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "ff" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0f40b2dcd8bc322217a5f6559ae5f9e9d1de202a2ecee2e9eafcbece7562a4f" +dependencies = [ + "rand_core 0.6.3", + "subtle", +] + [[package]] name = "filetime" version = "0.2.15" @@ -1544,6 +1595,17 @@ dependencies = [ "bitflags", ] +[[package]] +name = "group" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c363a5301b8f153d80747126a04b3c82073b9fe3130571a9d170cacdeaf7912" +dependencies = [ + "ff", + "rand_core 0.6.3", + "subtle", +] + [[package]] name = "h2" version = "0.3.4" @@ -1608,6 +1670,16 @@ dependencies = [ "libc", ] +[[package]] +name = "hmac" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" +dependencies = [ + "crypto-mac", + "digest", +] + [[package]] name = "hostname" version = "0.3.1" @@ -2336,6 +2408,26 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "p256" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d053368e1bae4c8a672953397bd1bd7183dde1c72b0b7612a15719173148d186" +dependencies = [ + "ecdsa", + "elliptic-curve", + "sha2", +] + +[[package]] +name = "p384" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f23bc88c404ccc881c8a1ad62ba5cd7d336a64ecbf46de4874f2ad955f67b157" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "parking_lot" version = "0.11.1" @@ -3221,6 +3313,16 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c19772be3c4dd2ceaacf03cb41d5885f2a02c4d8804884918e3a258480803335" +dependencies = [ + "digest", + "rand_core 0.6.3", +] + [[package]] name = "siphasher" version = "0.3.6" diff --git a/cli/tests/unit/webcrypto_test.ts b/cli/tests/unit/webcrypto_test.ts index b38a9934fcaad8..80275b0021da4b 100644 --- a/cli/tests/unit/webcrypto_test.ts +++ b/cli/tests/unit/webcrypto_test.ts @@ -513,6 +513,31 @@ unitTest(async function testHkdfDeriveBits() { assertEquals(result.byteLength, 128 / 8); }); +// TODO(@littledivy): Enable WPT when we have importKey support +unitTest(async function testECDH() { + const namedCurve = "P-256"; + const keyPair = await crypto.subtle.generateKey( + { + name: "ECDH", + namedCurve, + }, + true, + ["deriveBits"], + ); + + const derivedKey = await crypto.subtle.deriveBits( + { + name: "ECDH", + public: keyPair.publicKey, + }, + keyPair.privateKey, + 256, + ); + + assert(derivedKey instanceof ArrayBuffer); + assertEquals(derivedKey.byteLength, 256 / 8); +}); + unitTest(async function testWrapKey() { // Test wrapKey const key = await crypto.subtle.generateKey( diff --git a/ext/crypto/00_crypto.js b/ext/crypto/00_crypto.js index f0ba0b4bf0e3ad..4b4770e1303cbf 100644 --- a/ext/crypto/00_crypto.js +++ b/ext/crypto/00_crypto.js @@ -113,6 +113,7 @@ "deriveBits": { "HKDF": "HkdfParams", "PBKDF2": "Pbkdf2Params", + "ECDH": "EcdhKeyDeriveParams", }, "encrypt": { "RSA-OAEP": "RsaOaepParams", @@ -2138,6 +2139,58 @@ return buf.buffer; } + case "ECDH": { + // 1. + if (baseKey[_type] !== "private") { + throw new DOMException("Invalid key type", "InvalidAccessError"); + } + // 2. + const publicKey = normalizedAlgorithm.public; + // 3. + if (publicKey[_type] !== "public") { + throw new DOMException("Invalid key type", "InvalidAccessError"); + } + // 4. + if (publicKey[_algorithm].name !== baseKey[_algorithm].name) { + throw new DOMException( + "Algorithm mismatch", + "InvalidAccessError", + ); + } + // 5. + if ( + publicKey[_algorithm].namedCurve !== baseKey[_algorithm].namedCurve + ) { + throw new DOMException( + "namedCurve mismatch", + "InvalidAccessError", + ); + } + // 6. + if ( + ArrayPrototypeIncludes( + supportedNamedCurves, + publicKey[_algorithm].namedCurve, + ) + ) { + const baseKeyhandle = baseKey[_handle]; + const baseKeyData = WeakMapPrototypeGet(KEY_STORE, baseKeyhandle); + const publicKeyhandle = baseKey[_handle]; + const publicKeyData = WeakMapPrototypeGet(KEY_STORE, publicKeyhandle); + + const buf = await core.opAsync("op_crypto_derive_bits", { + key: baseKeyData, + publicKey: publicKeyData, + algorithm: "ECDH", + namedCurve: publicKey[_algorithm].namedCurve, + length, + }); + + return buf.buffer; + } else { + throw new DOMException("Not implemented", "NotSupportedError"); + } + } case "HKDF": { // 1. if (length === null || length === 0 || length % 8 !== 0) { diff --git a/ext/crypto/01_webidl.js b/ext/crypto/01_webidl.js index 8d9e061c62e81f..90bee464c1e55c 100644 --- a/ext/crypto/01_webidl.js +++ b/ext/crypto/01_webidl.js @@ -385,4 +385,16 @@ webidl.converters.CryptoKeyPair = webidl .createDictionaryConverter("CryptoKeyPair", dictCryptoKeyPair); + + const dictEcdhKeyDeriveParams = [ + ...dictAlgorithm, + { + key: "public", + converter: webidl.converters.CryptoKey, + required: true, + }, + ]; + + webidl.converters.EcdhKeyDeriveParams = webidl + .createDictionaryConverter("EcdhKeyDeriveParams", dictEcdhKeyDeriveParams); })(this); diff --git a/ext/crypto/Cargo.toml b/ext/crypto/Cargo.toml index d04f8dc3355708..060845b709b3fc 100644 --- a/ext/crypto/Cargo.toml +++ b/ext/crypto/Cargo.toml @@ -18,6 +18,8 @@ deno_core = { version = "0.102.0", path = "../../core" } deno_web = { version = "0.51.0", path = "../web" } lazy_static = "1.4.0" num-traits = "0.2.14" +p256 = { version = "0.9.0", features = ["ecdh"] } +p384 = "0.8.0" rand = "0.8.4" ring = { version = "0.16.20", features = ["std"] } rsa = { version = "0.5.0", default-features = false, features = ["std"] } diff --git a/ext/crypto/lib.deno_crypto.d.ts b/ext/crypto/lib.deno_crypto.d.ts index 673e8f9cb8163e..e5592d5bf91815 100644 --- a/ext/crypto/lib.deno_crypto.d.ts +++ b/ext/crypto/lib.deno_crypto.d.ts @@ -125,6 +125,10 @@ interface Pbkdf2Params extends Algorithm { salt: BufferSource; } +interface EcdhKeyDeriveParams extends Algorithm { + public: CryptoKey; +} + interface AesKeyGenParams extends Algorithm { length: number; } @@ -219,7 +223,11 @@ interface SubtleCrypto { data: BufferSource, ): Promise; deriveBits( - algorithm: AlgorithmIdentifier | HkdfParams | Pbkdf2Params, + algorithm: + | AlgorithmIdentifier + | HkdfParams + | Pbkdf2Params + | EcdhKeyDeriveParams, baseKey: CryptoKey, length: number, ): Promise; diff --git a/ext/crypto/lib.rs b/ext/crypto/lib.rs index 948ef53a11b99d..6376aedbb6a829 100644 --- a/ext/crypto/lib.rs +++ b/ext/crypto/lib.rs @@ -43,6 +43,7 @@ use rsa::pkcs1::der::Encodable; use rsa::pkcs1::FromRsaPrivateKey; use rsa::pkcs1::ToRsaPrivateKey; use rsa::pkcs8::der::asn1; +use rsa::pkcs8::FromPrivateKey; use rsa::BigUint; use rsa::PublicKey; use rsa::RsaPrivateKey; @@ -792,18 +793,23 @@ pub struct DeriveKeyArg { hash: Option, length: usize, iterations: Option, + // ECDH + public_key: Option, + named_curve: Option, + // HKDF info: Option, } pub async fn op_crypto_derive_bits( _state: Rc>, args: DeriveKeyArg, - zero_copy: ZeroCopyBuf, + zero_copy: Option, ) -> Result { - let salt = &*zero_copy; let algorithm = args.algorithm; match algorithm { Algorithm::Pbkdf2 => { + let zero_copy = zero_copy.ok_or_else(not_supported)?; + let salt = &*zero_copy; // The caller must validate these cases. assert!(args.length > 0); assert!(args.length % 8 == 0); @@ -823,7 +829,36 @@ pub async fn op_crypto_derive_bits( pbkdf2::derive(algorithm, iterations, salt, &secret, &mut out); Ok(out.into()) } + Algorithm::Ecdh => { + let named_curve = args + .named_curve + .ok_or_else(|| type_error("Missing argument namedCurve".to_string()))?; + + let public_key = args + .public_key + .ok_or_else(|| type_error("Missing argument publicKey".to_string()))?; + + match named_curve { + CryptoNamedCurve::P256 => { + let secret_key = p256::SecretKey::from_pkcs8_der(&args.key.data)?; + let public_key = + p256::SecretKey::from_pkcs8_der(&public_key.data)?.public_key(); + + let shared_secret = p256::elliptic_curve::ecdh::diffie_hellman( + secret_key.to_secret_scalar(), + public_key.as_affine(), + ); + + Ok(shared_secret.as_bytes().to_vec().into()) + } + // TODO(@littledivy): support for P384 + // https://github.com/RustCrypto/elliptic-curves/issues/240 + _ => Err(type_error("Unsupported namedCurve".to_string())), + } + } Algorithm::Hkdf => { + let zero_copy = zero_copy.ok_or_else(not_supported)?; + let salt = &*zero_copy; let algorithm = match args.hash.ok_or_else(not_supported)? { CryptoHash::Sha1 => hkdf::HKDF_SHA1_FOR_LEGACY_USE_ONLY, CryptoHash::Sha256 => hkdf::HKDF_SHA256,