Skip to content
Open
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
19 changes: 19 additions & 0 deletions benchmark/crypto/ec-jwk-export.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
'use strict';

const common = require('../common.js');
const { generateKeyPairSync } = require('crypto');

const bench = common.createBenchmark(main, {
namedCurve: ['prime256v1', 'secp384r1', 'secp521r1', 'secp256k1'],
type: ['public', 'private'],
n: [10000],
});

function main({ namedCurve, type, n }) {
const key = generateKeyPairSync('ec', { namedCurve })[`${type}Key`];
const options = { format: 'jwk' };
bench.start();
for (let index = 0; index < n; index++)
key.export(options);
bench.end(n);
}
23 changes: 23 additions & 0 deletions benchmark/crypto/ec-key-details.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
'use strict';

const common = require('../common.js');
const { KeyObject } = require('crypto');

const bench = common.createBenchmark(main, {
namedCurve: ['P-256', 'P-384', 'P-521'],
type: ['public', 'private'],
n: [10000],
});

async function main({ namedCurve, type, n }) {
const pair = await crypto.subtle.generateKey({
name: 'ECDSA', namedCurve,
}, true, ['sign', 'verify']);
const cryptoKey = pair[`${type}Key`];
bench.start();
for (let index = 0; index < n; index++) {
if (!KeyObject.from(cryptoKey).asymmetricKeyDetails.namedCurve)
throw new Error('Missing named curve');
}
bench.end(n);
}
23 changes: 23 additions & 0 deletions benchmark/crypto/ec-raw-export.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
'use strict';

const common = require('../common.js');
const { generateKeyPairSync } = require('crypto');

const bench = common.createBenchmark(main, {
namedCurve: ['prime256v1', 'secp384r1', 'secp521r1'],
format: ['raw-private', 'raw-public'],
type: ['uncompressed', 'compressed'],
n: [10000],
}, {
combinationFilter: ({ format, type }) => format === 'raw-public' || type === 'uncompressed',
});

function main({ namedCurve, format, type, n }) {
const pair = generateKeyPairSync('ec', { namedCurve });
const key = format === 'raw-private' ? pair.privateKey : pair.publicKey;
const options = format === 'raw-public' ? { format, type } : { format };
bench.start();
for (let index = 0; index < n; index++)
key.export(options);
bench.end(n);
}
146 changes: 141 additions & 5 deletions deps/ncrypto/ncrypto.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4130,11 +4130,7 @@ std::optional<uint32_t> EVPKeyPointer::getBytesOfRS() const {
#endif
} else if (id == EVP_PKEY_EC) {
#if NCRYPTO_USE_OPENSSL3_PROVIDER
Ec ec(get());
if (!ec) return std::nullopt;
const EC_GROUP* group = ec.getGroup();
if (group == nullptr) return std::nullopt;
bits = EC_GROUP_order_bits(group);
bits = EVP_PKEY_bits(get());
#else
const EC_KEY* ec_key = EVP_PKEY_get0_EC_KEY(get());
if (ec_key == nullptr) return std::nullopt;
Expand Down Expand Up @@ -5348,6 +5344,24 @@ bool ECPointPointer::mul(const EC_GROUP* group, const BIGNUM* priv_key) {

// ============================================================================

bool ECKeyPointer::checkPrivateKey() const {
const auto group = getGroup();
const auto priv = getPrivateKey();
const auto pub = getPublicKey();
if (group == nullptr || priv == nullptr || pub == nullptr) return false;

auto order = BignumPointer::New();
if (!order || !EC_GROUP_get_order(group, order.get(), nullptr) ||
BN_is_zero(priv) || BN_is_negative(priv) ||
BN_cmp(priv, order.get()) >= 0) {
return false;
}

auto expected = ECPointPointer::New(group);
return expected && expected.mul(group, priv) &&
EC_POINT_cmp(group, expected.get(), pub, nullptr) == 0;
}

#if NCRYPTO_USE_LEGACY_KEY_TYPES
ECKeyPointer::ECKeyPointer() : key_(nullptr) {}

Expand Down Expand Up @@ -6915,6 +6929,128 @@ int Ec::getCurve() const {
return EC_GROUP_get_curve_name(getGroup());
}

DataPointer Ec::TryExportPublic(const EVPKeyPointer& key,
point_conversion_form_t form) {
if (form != POINT_CONVERSION_UNCOMPRESSED) return {};
#if NCRYPTO_USE_OPENSSL3_PROVIDER
{
MarkPopErrorOnReturn pop_errors;
size_t length = 0;
if (EVP_PKEY_get_octet_string_param(
key.get(), OSSL_PKEY_PARAM_PUB_KEY, nullptr, 0, &length) == 1) {
auto bytes = DataPointer::Alloc(length);
if (bytes && length != 0 &&
EVP_PKEY_get_octet_string_param(key.get(),
OSSL_PKEY_PARAM_PUB_KEY,
bytes.get<unsigned char>(),
length,
&length) == 1 &&
(bytes.get<unsigned char>()[0] & ~1) == form) {
return bytes.resize(length);
}
}
}
#endif
return {};
}

DataPointer Ec::ExportPrivate(const EVPKeyPointer& key) {
#if NCRYPTO_USE_OPENSSL3_PROVIDER
{
MarkPopErrorOnReturn pop_errors;
BignumPointer priv;
BignumPointer order;
if (GetPKeyBnParam(key.get(), OSSL_PKEY_PARAM_PRIV_KEY, &priv) &&
GetPKeyBnParam(key.get(), OSSL_PKEY_PARAM_EC_ORDER, &order)) {
return priv.encodePadded(order.byteLength());
}
}
#endif
ECKeyPointer ec(key);
if (!ec || ec.getPrivateKey() == nullptr) return {};
auto order = BignumPointer::New();
if (!order || !EC_GROUP_get_order(ec.getGroup(), order.get(), nullptr))
return {};
return BignumPointer::EncodePadded(ec.getPrivateKey(), order.byteLength());
}

bool Ec::GetKeyComponents(const EVPKeyPointer& key,
BignumPointer* x,
BignumPointer* y,
BignumPointer* priv,
int* degree) {
#if NCRYPTO_USE_OPENSSL3_PROVIDER
const int nid = GetCurveId(key);
switch (nid) {
case NID_X9_62_prime256v1:
case NID_secp256k1:
*degree = 256;
break;
case NID_secp384r1:
*degree = 384;
break;
case NID_secp521r1:
*degree = 521;
break;
default:
*degree = 0;
}
if (*degree != 0) {
MarkPopErrorOnReturn pop_errors;
unsigned char x_bytes[66]{};
unsigned char y_bytes[66]{};
const size_t width = (*degree + 7) / 8;
OSSL_PARAM params[] = {
OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_EC_PUB_X, x_bytes, width),
OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_EC_PUB_Y, y_bytes, width),
OSSL_PARAM_construct_end(),
};
if (EVP_PKEY_get_params(key.get(), params) == 1 &&
OSSL_PARAM_modified(&params[0]) && OSSL_PARAM_modified(&params[1])) {
x->reset(BN_native2bn(x_bytes, width, nullptr));
y->reset(BN_native2bn(y_bytes, width, nullptr));
return *x && *y &&
(priv == nullptr ||
GetPKeyBnParam(key.get(), OSSL_PKEY_PARAM_PRIV_KEY, priv));
}
}
#endif
ECKeyPointer ec(key);
if (!ec || ec.getPublicKey() == nullptr) return false;
*degree = EC_GROUP_get_degree(ec.getGroup());
x->reset(BN_new());
y->reset(BN_new());
if (!*x || !*y ||
EC_POINT_get_affine_coordinates(
ec.getGroup(), ec.getPublicKey(), x->get(), y->get(), nullptr) != 1) {
return false;
}
if (priv != nullptr) {
if (ec.getPrivateKey() == nullptr) return false;
priv->reset(BN_dup(ec.getPrivateKey()));
if (!*priv) return false;
}
return true;
}

int Ec::GetCurveId(const EVPKeyPointer& key) {
#if NCRYPTO_USE_OPENSSL3_PROVIDER
char name[80];
size_t length = 0;
if (EVP_PKEY_get_utf8_string_param(
key.get(), OSSL_PKEY_PARAM_GROUP_NAME, name, sizeof(name), &length) !=
1) {
return NID_undef;
}
return GetCurveIdFromName(name);
#else
const EC_KEY* ec = key;
if (ec == nullptr) return NID_undef;
const EC_GROUP* group = EC_KEY_get0_group(ec);
return group == nullptr ? NID_undef : EC_GROUP_get_curve_name(group);
#endif
}

int Ec::GetCurveIdFromName(const char* name) {
int nid = EC_curve_nist2nid(name);
if (nid == NID_undef) {
Expand Down
10 changes: 10 additions & 0 deletions deps/ncrypto/ncrypto.h
Original file line number Diff line number Diff line change
Expand Up @@ -812,6 +812,15 @@ class Ec final {
#endif

static int GetCurveIdFromName(const char* name);
static int GetCurveId(const EVPKeyPointer& key);
static DataPointer TryExportPublic(const EVPKeyPointer& key,
point_conversion_form_t form);
static DataPointer ExportPrivate(const EVPKeyPointer& key);
static bool GetKeyComponents(const EVPKeyPointer& key,
BignumPointer* x,
BignumPointer* y,
BignumPointer* priv,
int* degree);

using GetCurveCallback = std::function<bool(const char*)>;
static bool GetCurves(GetCurveCallback callback);
Expand Down Expand Up @@ -1764,6 +1773,7 @@ class ECKeyPointer final {
bool setPublicKeyRaw(const BignumPointer& x, const BignumPointer& y);
bool generate();
bool checkKey() const;
bool checkPrivateKey() const;
DataPointer computeSecret(const ECPointPointer& peer) const;

const EC_GROUP* getGroup() const;
Expand Down
5 changes: 1 addition & 4 deletions src/crypto/crypto_common.cc
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
namespace node {

using ncrypto::ClearErrorOnReturn;
using ncrypto::ECKeyPointer;
using ncrypto::EVPKeyPointer;
using ncrypto::SSLPointer;
using ncrypto::SSLSessionPointer;
Expand Down Expand Up @@ -231,9 +230,7 @@ MaybeLocal<Object> GetEphemeralKey(Environment* env, const SSLPointer& ssl) {
case EVP_PKEY_X448: {
const char* curve_name;
if (kid == EVP_PKEY_EC) {
ECKeyPointer ec(key);
if (!ec) break;
int nid = EC_GROUP_get_curve_name(ec.getGroup());
int nid = ncrypto::Ec::GetCurveId(key);
if (nid == NID_undef) break;
curve_name = OBJ_nid2sn(nid);
} else {
Expand Down
49 changes: 19 additions & 30 deletions src/crypto/crypto_ec.cc
Original file line number Diff line number Diff line change
Expand Up @@ -488,30 +488,21 @@ bool ExportJWKEcKey(Environment* env,
const auto& m_pkey = key.GetAsymmetricKey();
CHECK_EQ(m_pkey.id(), EVP_PKEY_EC);

ECKeyPointer ec(m_pkey);
if (!ec) {
THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK EC key");
BignumPointer x;
BignumPointer y;
BignumPointer priv;
int degree_bits;
if (!Ec::GetKeyComponents(
m_pkey,
&x,
&y,
key.GetKeyType() == kKeyTypePrivate ? &priv : nullptr,
&degree_bits)) {
return false;
}
// A provider-backed key need not expose its public point.
if (ec.getPublicKey() == nullptr) return false;

const auto pub = ec.getPublicKey();
const auto group = ec.getGroup();

int degree_bits = EC_GROUP_get_degree(group);
int degree_bytes =
(degree_bits / CHAR_BIT) + (7 + (degree_bits % CHAR_BIT)) / 8;

auto x = BignumPointer::New();
auto y = BignumPointer::New();

if (!EC_POINT_get_affine_coordinates(group, pub, x.get(), y.get(), nullptr)) {
ThrowCryptoError(env, ERR_get_error(),
"Failed to get elliptic-curve point coordinates");
return false;
}

if (!target
->DefineOwnProperty(
env->context(), env->jwk_kty_string(), env->jwk_ec_string())
Expand All @@ -535,7 +526,7 @@ bool ExportJWKEcKey(Environment* env,
}

Local<String> crv_name;
const int nid = EC_GROUP_get_curve_name(group);
const int nid = Ec::GetCurveId(m_pkey);
switch (nid) {
case NID_X9_62_prime256v1:
crv_name = env->p256_string();
Expand All @@ -562,9 +553,8 @@ bool ExportJWKEcKey(Environment* env,
}

if (key.GetKeyType() == kKeyTypePrivate) {
auto pvt = ec.getPrivateKey();
if (pvt == nullptr) return false;
return SetEncodedValue(env, target, env->jwk_d_string(), pvt, degree_bytes)
return SetEncodedValue(
env, target, env->jwk_d_string(), priv.get(), degree_bytes)
.IsJust();
}

Expand Down Expand Up @@ -756,15 +746,18 @@ KeyObjectData ImportJWKEcKey(Environment* env, Local<Object> jwk) {
return {};
}
// Verify that the public point matches the private scalar (d*G == (x,y)).
if (!ec.checkKey()) {
if (!ec.checkPrivateKey()) {
THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK EC key");
return {};
}
}

auto pkey = EVPKeyPointer::New();
if (!pkey) return {};
CHECK(pkey.set(ec));
if (!pkey.set(ec)) {
THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK EC key");
return {};
}

return KeyObjectData::CreateAsymmetric(type, std::move(pkey));
}
Expand All @@ -776,11 +769,7 @@ bool GetEcKeyDetail(Environment* env,
const auto& m_pkey = key.GetAsymmetricKey();
CHECK_EQ(m_pkey.id(), EVP_PKEY_EC);

ECKeyPointer ec(m_pkey);
if (!ec) return true;

const auto group = ec.getGroup();
int nid = EC_GROUP_get_curve_name(group);
int nid = Ec::GetCurveId(m_pkey);
if (nid == NID_undef) return true;

return target
Expand Down
Loading
Loading