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
48 changes: 48 additions & 0 deletions doc/api/crypto.md
Original file line number Diff line number Diff line change
Expand Up @@ -656,6 +656,54 @@ assert.strictEqual(aliceSecret.toString('hex'), bobSecret.toString('hex'));
// OK
```

### ECDH.convertKey(key, curve[, inputEncoding[, outputEncoding[, format]]])
<!-- YAML
added: REPLACEME
-->

- `key` {string | Buffer | TypedArray | DataView}
- `curve` {string}
- `inputEncoding` {string}
- `outputEncoding` {string}
- `format` {string} **Default:** `uncompressed`

Converts the EC Diffie-Hellman public key specified by `key` and `curve` to the
format specified by `format`. The `format` argument specifies point encoding
and can be `'compressed'`, `'uncompressed'` or `'hybrid'`. The supplied key is
interpreted using the specified `inputEncoding`, and the returned key is encoded
using the specified `outputEncoding`. Encodings can be `'latin1'`, `'hex'`,
or `'base64'`.

Use [`crypto.getCurves()`][] to obtain a list of available curve names.
On recent OpenSSL releases, `openssl ecparam -list_curves` will also display
the name and description of each available elliptic curve.

If `format` is not specified the point will be returned in `'uncompressed'`
format.

If the `inputEncoding` is not provided, `key` is expected to be a [`Buffer`][],
`TypedArray`, or `DataView`.

Example (uncompressing a key):

```js
const { ECDH } = require('crypto');

const ecdh = ECDH('secp256k1');
ecdh.generateKeys();

const compressedKey = ecdh.getPublicKey('hex', 'compressed');

const uncompressedKey = ECDH.convertKey(compressedKey,
'secp256k1',
'hex',
'hex',
'uncompressed');

// the converted key and the uncompressed public key should be the same
console.log(uncompressedKey === ecdh.getPublicKey('hex'));
```

### ecdh.computeSecret(otherPublicKey[, inputEncoding][, outputEncoding])
<!-- YAML
added: v0.11.14
Expand Down
83 changes: 51 additions & 32 deletions lib/internal/crypto/diffiehellman.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ const {
const {
DiffieHellman: _DiffieHellman,
DiffieHellmanGroup: _DiffieHellmanGroup,
ECDH: _ECDH
ECDH: _ECDH,
ECDHConvertKey: _ECDHConvertKey
} = process.binding('crypto');
const {
POINT_CONVERSION_COMPRESSED,
Expand Down Expand Up @@ -79,11 +80,9 @@ DiffieHellmanGroup.prototype.generateKeys =
dhGenerateKeys;

function dhGenerateKeys(encoding) {
var keys = this._handle.generateKeys();
const keys = this._handle.generateKeys();
encoding = encoding || getDefaultEncoding();
if (encoding && encoding !== 'buffer')
keys = keys.toString(encoding);
return keys;
return encode(keys, encoding);
}


Expand All @@ -95,10 +94,8 @@ function dhComputeSecret(key, inEnc, outEnc) {
const encoding = getDefaultEncoding();
inEnc = inEnc || encoding;
outEnc = outEnc || encoding;
var ret = this._handle.computeSecret(toBuf(key, inEnc));
if (outEnc && outEnc !== 'buffer')
ret = ret.toString(outEnc);
return ret;
const ret = this._handle.computeSecret(toBuf(key, inEnc));
return encode(ret, outEnc);
}


Expand All @@ -107,11 +104,9 @@ DiffieHellmanGroup.prototype.getPrime =
dhGetPrime;

function dhGetPrime(encoding) {
var prime = this._handle.getPrime();
const prime = this._handle.getPrime();
encoding = encoding || getDefaultEncoding();
if (encoding && encoding !== 'buffer')
prime = prime.toString(encoding);
return prime;
return encode(prime, encoding);
}


Expand All @@ -120,11 +115,9 @@ DiffieHellmanGroup.prototype.getGenerator =
dhGetGenerator;

function dhGetGenerator(encoding) {
var generator = this._handle.getGenerator();
const generator = this._handle.getGenerator();
encoding = encoding || getDefaultEncoding();
if (encoding && encoding !== 'buffer')
generator = generator.toString(encoding);
return generator;
return encode(generator, encoding);
}


Expand All @@ -133,11 +126,9 @@ DiffieHellmanGroup.prototype.getPublicKey =
dhGetPublicKey;

function dhGetPublicKey(encoding) {
var key = this._handle.getPublicKey();
const key = this._handle.getPublicKey();
encoding = encoding || getDefaultEncoding();
if (encoding && encoding !== 'buffer')
key = key.toString(encoding);
return key;
return encode(key, encoding);
}


Expand All @@ -146,11 +137,9 @@ DiffieHellmanGroup.prototype.getPrivateKey =
dhGetPrivateKey;

function dhGetPrivateKey(encoding) {
var key = this._handle.getPrivateKey();
const key = this._handle.getPrivateKey();
encoding = encoding || getDefaultEncoding();
if (encoding && encoding !== 'buffer')
key = key.toString(encoding);
return key;
return encode(key, encoding);
}


Expand Down Expand Up @@ -190,7 +179,41 @@ ECDH.prototype.generateKeys = function generateKeys(encoding, format) {
};

ECDH.prototype.getPublicKey = function getPublicKey(encoding, format) {
var f;
const f = getFormat(format);
const key = this._handle.getPublicKey(f);
encoding = encoding || getDefaultEncoding();
return encode(key, encoding);
};

ECDH.convertKey = function convertKey(key, curve, inEnc, outEnc, format) {
if (typeof key !== 'string' && !isArrayBufferView(key)) {
throw new errors.TypeError(
'ERR_INVALID_ARG_TYPE',
'key',
['string', 'Buffer', 'TypedArray', 'DataView']
);
}

if (typeof curve !== 'string') {
throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'curve', 'string');
}

const encoding = getDefaultEncoding();
inEnc = inEnc || encoding;
outEnc = outEnc || encoding;
const f = getFormat(format);
const convertedKey = _ECDHConvertKey(toBuf(key, inEnc), curve, f);
return encode(convertedKey, outEnc);
};

function encode(buffer, encoding) {
if (encoding && encoding !== 'buffer')
buffer = buffer.toString(encoding);
return buffer;
}

function getFormat(format) {
let f;
if (format) {
if (format === 'compressed')
f = POINT_CONVERSION_COMPRESSED;
Expand All @@ -204,12 +227,8 @@ ECDH.prototype.getPublicKey = function getPublicKey(encoding, format) {
} else {
f = POINT_CONVERSION_UNCOMPRESSED;
}
var key = this._handle.getPublicKey(f);
encoding = encoding || getDefaultEncoding();
if (encoding && encoding !== 'buffer')
key = key.toString(encoding);
return key;
};
return f;
}

module.exports = {
DiffieHellman,
Expand Down
85 changes: 73 additions & 12 deletions src/node_crypto.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5283,32 +5283,32 @@ void ECDH::GenerateKeys(const FunctionCallbackInfo<Value>& args) {
}


EC_POINT* ECDH::BufferToPoint(char* data, size_t len) {
EC_POINT* ECDH::BufferToPoint(Environment* env,
const EC_GROUP* group,
char* data,
size_t len) {
EC_POINT* pub;
int r;

pub = EC_POINT_new(group_);
pub = EC_POINT_new(group);
if (pub == nullptr) {
env()->ThrowError("Failed to allocate EC_POINT for a public key");
env->ThrowError("Failed to allocate EC_POINT for a public key");
return nullptr;
}

r = EC_POINT_oct2point(
group_,
group,
pub,
reinterpret_cast<unsigned char*>(data),
len,
nullptr);
if (!r) {
env()->ThrowError("Failed to translate Buffer to a EC_POINT");
goto fatal;
env->ThrowError("Failed to translate Buffer to a EC_POINT");
EC_POINT_free(pub);
return nullptr;
}

return pub;

fatal:
EC_POINT_free(pub);
return nullptr;
}


Expand All @@ -5325,7 +5325,9 @@ void ECDH::ComputeSecret(const FunctionCallbackInfo<Value>& args) {
if (!ecdh->IsKeyPairValid())
return env->ThrowError("Invalid key pair");

EC_POINT* pub = ecdh->BufferToPoint(Buffer::Data(args[0]),
EC_POINT* pub = ECDH::BufferToPoint(env,
ecdh->group_,
Buffer::Data(args[0]),
Buffer::Length(args[0]));
if (pub == nullptr)
return;
Expand Down Expand Up @@ -5470,7 +5472,9 @@ void ECDH::SetPublicKey(const FunctionCallbackInfo<Value>& args) {

MarkPopErrorOnReturn mark_pop_error_on_return;

EC_POINT* pub = ecdh->BufferToPoint(Buffer::Data(args[0].As<Object>()),
EC_POINT* pub = ECDH::BufferToPoint(env,
ecdh->group_,
Buffer::Data(args[0].As<Object>()),
Buffer::Length(args[0].As<Object>()));
if (pub == nullptr)
return env->ThrowError("Failed to convert Buffer to EC_POINT");
Expand Down Expand Up @@ -6146,6 +6150,61 @@ void ExportChallenge(const FunctionCallbackInfo<Value>& args) {
args.GetReturnValue().Set(outString);
}


// Convert the input public key to compressed, uncompressed, or hybrid formats.
void ConvertKey(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);

CHECK_EQ(args.Length(), 3);

size_t len = Buffer::Length(args[0]);
if (len == 0)
return args.GetReturnValue().SetEmptyString();

node::Utf8Value curve(env->isolate(), args[1]);

int nid = OBJ_sn2nid(*curve);
if (nid == NID_undef)
return env->ThrowTypeError("Invalid ECDH curve name");

EC_GROUP* group = EC_GROUP_new_by_curve_name(nid);
if (group == nullptr)
return env->ThrowError("Failed to get EC_GROUP");

EC_POINT* pub = ECDH::BufferToPoint(env,
group,
Buffer::Data(args[0]),
len);

std::shared_ptr<void> cleanup(nullptr, [group, pub] (...) {
EC_GROUP_free(group);
EC_POINT_free(pub);
});

if (pub == nullptr)
return env->ThrowError("Failed to convert Buffer to EC_POINT");

point_conversion_form_t form =
static_cast<point_conversion_form_t>(args[2]->Uint32Value());

int size = EC_POINT_point2oct(group, pub, form, nullptr, 0, nullptr);
if (size == 0)
return env->ThrowError("Failed to get public key length");

unsigned char* out = node::Malloc<unsigned char>(size);

int r = EC_POINT_point2oct(group, pub, form, out, size, nullptr);
if (r != size) {
free(out);
return env->ThrowError("Failed to get public key");
}

Local<Object> buf =
Buffer::New(env, reinterpret_cast<char*>(out), size).ToLocalChecked();
args.GetReturnValue().Set(buf);
}


void TimingSafeEqual(const FunctionCallbackInfo<Value>& args) {
CHECK(Buffer::HasInstance(args[0]));
CHECK(Buffer::HasInstance(args[1]));
Expand Down Expand Up @@ -6289,6 +6348,8 @@ void Initialize(Local<Object> target,
env->SetMethod(target, "certVerifySpkac", VerifySpkac);
env->SetMethod(target, "certExportPublicKey", ExportPublicKey);
env->SetMethod(target, "certExportChallenge", ExportChallenge);

env->SetMethod(target, "ECDHConvertKey", ConvertKey);
#ifndef OPENSSL_NO_ENGINE
env->SetMethod(target, "setEngine", SetEngine);
#endif // !OPENSSL_NO_ENGINE
Expand Down
6 changes: 4 additions & 2 deletions src/node_crypto.h
Original file line number Diff line number Diff line change
Expand Up @@ -709,6 +709,10 @@ class ECDH : public BaseObject {
}

static void Initialize(Environment* env, v8::Local<v8::Object> target);
static EC_POINT* BufferToPoint(Environment* env,
const EC_GROUP* group,
char* data,
size_t len);

protected:
ECDH(Environment* env, v8::Local<v8::Object> wrap, EC_KEY* key)
Expand All @@ -727,8 +731,6 @@ class ECDH : public BaseObject {
static void GetPublicKey(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetPublicKey(const v8::FunctionCallbackInfo<v8::Value>& args);

EC_POINT* BufferToPoint(char* data, size_t len);

bool IsKeyPairValid();
bool IsKeyValidForCurve(const BIGNUM* private_key);

Expand Down
Loading