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
18 changes: 18 additions & 0 deletions deps/ncrypto/ncrypto.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5348,6 +5348,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
1 change: 1 addition & 0 deletions deps/ncrypto/ncrypto.h
Original file line number Diff line number Diff line change
Expand Up @@ -1764,6 +1764,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
7 changes: 5 additions & 2 deletions src/crypto/crypto_ec.cc
Original file line number Diff line number Diff line change
Expand Up @@ -756,15 +756,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 Down
79 changes: 79 additions & 0 deletions test/parallel/test-crypto-key-objects-ec-jwk-private.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
'use strict';

const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');

const assert = require('assert');
const {
createECDH,
createPrivateKey,
createPublicKey,
getCurves,
getFips,
sign,
verify,
} = require('crypto');

const curves = [
['prime256v1', 'P-256', 32,
'ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551'],
['secp384r1', 'P-384', 48,
'ffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf' +
'581a0db248b0a77aecec196accc52973'],
['secp521r1', 'P-521', 66,
'01ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' +
'fa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386409'],
];
if (!getFips() && getCurves().includes('secp256k1')) {
curves.push(['secp256k1', 'secp256k1', 32,
'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141']);
}

for (const [namedCurve, crv, width, orderHex] of curves) {
const order = BigInt(`0x${orderHex}`);
const encode = (scalar) => Buffer.from(
scalar.toString(16).padStart(width * 2, '0'), 'hex');
const makeJwk = (scalar) => {
const ecdh = createECDH(namedCurve);
ecdh.setPrivateKey(encode(scalar));
const point = ecdh.getPublicKey();
return {
kty: 'EC',
crv,
x: point.subarray(1, 1 + width).toString('base64url'),
y: point.subarray(1 + width).toString('base64url'),
d: encode(scalar).toString('base64url'),
};
};
const generator = makeJwk(1n);
const other = makeJwk(2n);
const message = Buffer.from('EC JWK private key consistency');

for (const jwk of [generator, other, makeJwk(order - 1n)]) {
const key = createPrivateKey({ format: 'jwk', key: jwk });
assert.deepStrictEqual(key.export({ format: 'jwk' }), jwk);
const publicJwk = { kty: jwk.kty, crv, x: jwk.x, y: jwk.y };
const publicKey = createPublicKey({ format: 'jwk', key: publicJwk });
assert(verify('sha256', message, publicKey, sign('sha256', message, key)));
}

const invalid = [
{ ...generator, d: other.d },
{ ...generator, x: other.x, y: other.y },
...[0n, order, order + 1n].map((scalar) => ({
...generator, d: encode(scalar).toString('base64url'),
})),
{ ...generator, d: '' },
{
...generator,
x: Buffer.alloc(width).toString('base64url'),
y: Buffer.alloc(width).toString('base64url'),
},
];
for (const jwk of invalid) {
assert.throws(() => createPrivateKey({ format: 'jwk', key: jwk }), {
code: 'ERR_CRYPTO_INVALID_JWK',
});
}
}
Loading