Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ext/crypto): implement deriveBits for ECDH (p256) #11873

Merged
merged 16 commits into from
Oct 8, 2021
102 changes: 102 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 25 additions & 0 deletions cli/tests/unit/webcrypto_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
53 changes: 53 additions & 0 deletions ext/crypto/00_crypto.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@
"deriveBits": {
"HKDF": "HkdfParams",
"PBKDF2": "Pbkdf2Params",
"ECDH": "EcdhKeyDeriveParams",
},
"encrypt": {
"RSA-OAEP": "RsaOaepParams",
Expand Down Expand Up @@ -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) {
Expand Down
12 changes: 12 additions & 0 deletions ext/crypto/01_webidl.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
2 changes: 2 additions & 0 deletions ext/crypto/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
Expand Down
10 changes: 9 additions & 1 deletion ext/crypto/lib.deno_crypto.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,10 @@ interface Pbkdf2Params extends Algorithm {
salt: BufferSource;
}

interface EcdhKeyDeriveParams extends Algorithm {
public: CryptoKey;
}

interface AesKeyGenParams extends Algorithm {
length: number;
}
Expand Down Expand Up @@ -219,7 +223,11 @@ interface SubtleCrypto {
data: BufferSource,
): Promise<ArrayBuffer>;
deriveBits(
algorithm: AlgorithmIdentifier | HkdfParams | Pbkdf2Params,
algorithm:
| AlgorithmIdentifier
| HkdfParams
| Pbkdf2Params
| EcdhKeyDeriveParams,
baseKey: CryptoKey,
length: number,
): Promise<ArrayBuffer>;
Expand Down
39 changes: 37 additions & 2 deletions ext/crypto/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -792,18 +793,23 @@ pub struct DeriveKeyArg {
hash: Option<CryptoHash>,
length: usize,
iterations: Option<u32>,
// ECDH
public_key: Option<KeyData>,
named_curve: Option<CryptoNamedCurve>,
// HKDF
info: Option<ZeroCopyBuf>,
}

pub async fn op_crypto_derive_bits(
_state: Rc<RefCell<OpState>>,
args: DeriveKeyArg,
zero_copy: ZeroCopyBuf,
zero_copy: Option<ZeroCopyBuf>,
) -> Result<ZeroCopyBuf, AnyError> {
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);
Expand All @@ -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,
Expand Down