Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions deps/rust/Cargo.lock

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

1 change: 1 addition & 0 deletions deps/rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ syn = { version = "2", features = ["full"] }
ada-url = "3"
anyhow = "1"
async-trait = { version = "0", default-features = false }
aws-lc-rs = { version = "1", features = ["prebuilt-nasm"] }
capnp = "0"
capnpc = "0"
capnp-rpc = "0"
Expand Down
11 changes: 11 additions & 0 deletions src/rust/aws-lc/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
load("//:build/wd_rust_crate.bzl", "wd_rust_crate")

wd_rust_crate(
name = "aws-lc",
cxx_bridge_src = "lib.rs",
visibility = ["//visibility:public"],
deps = [
"//src/rust/cxx-integration",
"@crates_vendor//:aws-lc-rs",
],
)
74 changes: 74 additions & 0 deletions src/rust/aws-lc/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
use aws_lc_rs::encoding::AsBigEndian;
use aws_lc_rs::rand::SystemRandom;
use aws_lc_rs::signature::ECDSA_P256K1_SHA256_FIXED;
use aws_lc_rs::signature::ECDSA_P256K1_SHA256_FIXED_SIGNING;
use aws_lc_rs::signature::EcdsaKeyPair;
use aws_lc_rs::signature::KeyPair;
use aws_lc_rs::signature::ParsedPublicKey;

#[cxx::bridge(namespace = "workerd::rust::aws_lc")]
mod ffi {
extern "Rust" {
fn validate_public(input: &[u8]) -> bool;
fn validate_keypair(seckey: &[u8], pubkey: &[u8]) -> bool;
fn generate_keypair() -> Vec<u8>;
fn sign(seckey: &[u8], pubkey: &[u8], message: &[u8]) -> Vec<u8>;
fn verify(pubkey: &[u8], message: &[u8], signature: &[u8]) -> bool;
}
}

#[must_use]
pub fn validate_public(input: &[u8]) -> bool {
ParsedPublicKey::new(&ECDSA_P256K1_SHA256_FIXED, input).is_ok()
}

#[must_use]
pub fn validate_keypair(seckey: &[u8], pubkey: &[u8]) -> bool {
EcdsaKeyPair::from_private_key_and_public_key(
&ECDSA_P256K1_SHA256_FIXED_SIGNING,
seckey,
pubkey,
)
.is_ok()
}

#[must_use]
pub fn generate_keypair() -> Vec<u8> {
let Ok(keypair) = EcdsaKeyPair::generate(&ECDSA_P256K1_SHA256_FIXED_SIGNING) else {
return Vec::new();
};
let Ok(seckey_bytes) = keypair.private_key().as_be_bytes() else {
return Vec::new();
};
let pubkey_bytes = keypair.public_key().as_ref();
let mut out = Vec::with_capacity(32 + 65);
out.extend_from_slice(seckey_bytes.as_ref());
out.extend_from_slice(pubkey_bytes);
out
}

#[must_use]
pub fn sign(seckey: &[u8], pubkey: &[u8], message: &[u8]) -> Vec<u8> {
let Ok(keypair) = EcdsaKeyPair::from_private_key_and_public_key(
&ECDSA_P256K1_SHA256_FIXED_SIGNING,
seckey,
pubkey,
) else {
return Vec::new();
};
// aws-lc-rs ignores the SecureRandom parameter (kept for ring compatibility); nonces come
// from AWS-LC's internal CSPRNG.
let rng = SystemRandom::new();
let Ok(signature) = keypair.sign(&rng, message) else {
return Vec::new();
};
signature.as_ref().to_vec()
}

#[must_use]
pub fn verify(pubkey: &[u8], message: &[u8], signature: &[u8]) -> bool {
let Ok(parsed) = ParsedPublicKey::new(&ECDSA_P256K1_SHA256_FIXED, pubkey) else {
return false;
};
parsed.verify_sig(message, signature).is_ok()
}
42 changes: 37 additions & 5 deletions src/workerd/api/crypto/ec.c++
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@

#include "impl.h"
#include "keys.h"
#include "secp256k1-key.h"

#include <workerd/api/util.h>
#include <workerd/io/features.h>
#include <workerd/util/autogate.h>

#include <openssl/bn.h>
#include <openssl/crypto.h>
Expand Down Expand Up @@ -429,10 +431,14 @@ class EllipticKey final: public AsymmetricKeyCryptoKeyImpl {

struct EllipticCurveInfo {
kj::StringPtr normalizedName;
int opensslCurveId;
uint rsSize; // size of "r" and "s" in the signature
int opensslCurveId; // 0 for curves BoringSSL doesn't support (e.g. secp256k1).
uint rsSize; // size of "r" and "s" in the signature
};

bool isSecp256k1(kj::StringPtr name) {
return strcasecmp(name.cStr(), "secp256k1") == 0;
}

EllipticCurveInfo lookupEllipticCurve(kj::StringPtr curveName) {
static const std::map<kj::StringPtr, EllipticCurveInfo, CiLess> registeredCurves{
{"P-256", {"P-256", NID_X9_62_prime256v1, 32}},
Expand All @@ -446,6 +452,14 @@ EllipticCurveInfo lookupEllipticCurve(kj::StringPtr curveName) {
return iter->second;
}

// Overload that adds secp256k1 to the table when the autogate is enabled.
EllipticCurveInfo lookupEllipticCurve(jsg::Lock& js, kj::StringPtr curveName) {
if (util::Autogate::isEnabled(util::AutogateKey::SECP256K1_ECDSA) && isSecp256k1(curveName)) {
return {"secp256k1", 0, 32};
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we add this, it likely should be with an autogate and not a compat flag. Once we're sure it's ready, it should just be there, rather than made conditional on an autogate. That said, we might want to consider using a namespaced name for it just in case the WebCrypto standard adds the algorithm later with the same name but potentially conflicting details.

return lookupEllipticCurve(curveName);
}

kj::OneOf<jsg::Ref<CryptoKey>, CryptoKeyPair> EllipticKey::generateElliptic(jsg::Lock& js,
kj::StringPtr normalizedName,
SubtleCrypto::GenerateKeyAlgorithm&& algorithm,
Expand All @@ -455,7 +469,16 @@ kj::OneOf<jsg::Ref<CryptoKey>, CryptoKeyPair> EllipticKey::generateElliptic(jsg:
kj::StringPtr namedCurve = JSG_REQUIRE_NONNULL(
algorithm.namedCurve, TypeError, "Missing field \"namedCurve\" in \"algorithm\".");

auto [normalizedNamedCurve, curveId, rsSize] = lookupEllipticCurve(namedCurve);
auto [normalizedNamedCurve, curveId, rsSize] = lookupEllipticCurve(js, namedCurve);

// The compat flag scopes secp256k1 support to ECDSA; ECDH is rejected.
if (isSecp256k1(normalizedNamedCurve)) {
JSG_REQUIRE(normalizedName == "ECDSA", DOMNotSupportedError, "\"", normalizedName,
"\" is not supported for curve \"secp256k1\".");
return Secp256k1Key::generatePair(js,
CryptoKey::EllipticKeyAlgorithm{normalizedName, normalizedNamedCurve}, extractable,
privateKeyUsages, publicKeyUsages);
}

auto keyAlgorithm = CryptoKey::EllipticKeyAlgorithm{
normalizedName,
Expand Down Expand Up @@ -700,7 +723,13 @@ kj::Own<CryptoKey::Impl> CryptoKey::Impl::importEcdsa(jsg::Lock& js,
kj::StringPtr namedCurve = JSG_REQUIRE_NONNULL(
algorithm.namedCurve, TypeError, "Missing field \"namedCurve\" in \"algorithm\".");

auto [normalizedNamedCurve, curveId, rsSize] = lookupEllipticCurve(namedCurve);
auto [normalizedNamedCurve, curveId, rsSize] = lookupEllipticCurve(js, namedCurve);

if (isSecp256k1(normalizedNamedCurve)) {
return Secp256k1Key::import(js, normalizedName, format, kj::mv(keyData),
CryptoKey::EllipticKeyAlgorithm{normalizedName, normalizedNamedCurve}, extractable,
keyUsages);
}

auto importedKey = [&, curveId = curveId] {
if (format != "raw") {
Expand Down Expand Up @@ -758,7 +787,10 @@ kj::Own<CryptoKey::Impl> CryptoKey::Impl::importEcdh(jsg::Lock& js,
kj::StringPtr namedCurve = JSG_REQUIRE_NONNULL(
algorithm.namedCurve, TypeError, "Missing field \"namedCurve\" in \"algorithm\".");

auto [normalizedNamedCurve, curveId, rsSize] = lookupEllipticCurve(namedCurve);
auto [normalizedNamedCurve, curveId, rsSize] = lookupEllipticCurve(js, namedCurve);

JSG_REQUIRE(!isSecp256k1(normalizedNamedCurve), DOMNotSupportedError,
"ECDH is not supported for curve \"secp256k1\".");

auto importedKey = [&, curveId = curveId] {
auto strictCrypto = FeatureFlags::get(js).getStrictCrypto();
Expand Down
Loading
Loading