Permalink
Browse files

crypto: add ECDH.convertKey to convert public keys

ECDH.convertKey is used to convert public keys between different
formats.

PR-URL: #19080
Fixes: #18977
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: James M Snell <jasnell@gmail.com>
  • Loading branch information...
wuweiweiwu authored and tniessen committed Mar 2, 2018
1 parent a0adf56 commit f2e02883e7ef6ff58331e980f91bc31d8076a5c8
Showing with 275 additions and 45 deletions.
  1. +48 −0 doc/api/crypto.md
  2. +50 −32 lib/internal/crypto/diffiehellman.js
  3. +72 −11 src/node_crypto.cc
  4. +4 −2 src/node_crypto.h
  5. +101 −0 test/parallel/test-crypto-ecdh-convert-key.js
@@ -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} Defaults to `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
@@ -14,7 +14,8 @@ const {
const {
DiffieHellman: _DiffieHellman,
DiffieHellmanGroup: _DiffieHellmanGroup,
ECDH: _ECDH
ECDH: _ECDH,
ECDHConvertKey: _ECDHConvertKey
} = process.binding('crypto');
const {
POINT_CONVERSION_COMPRESSED,
@@ -84,11 +85,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);
}
@@ -100,12 +99,10 @@ function dhComputeSecret(key, inEnc, outEnc) {
const encoding = getDefaultEncoding();
inEnc = inEnc || encoding;
outEnc = outEnc || encoding;
var ret = this._handle.computeSecret(toBuf(key, inEnc));
const ret = this._handle.computeSecret(toBuf(key, inEnc));
if (typeof ret === 'string')
throw new ERR_CRYPTO_ECDH_INVALID_PUBLIC_KEY();
if (outEnc && outEnc !== 'buffer')
ret = ret.toString(outEnc);
return ret;
return encode(ret, outEnc);
}
@@ -114,11 +111,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);
}
@@ -127,11 +122,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);
}
@@ -140,11 +133,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);
}
@@ -153,11 +144,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);
}
@@ -197,7 +186,40 @@ 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 ERR_INVALID_ARG_TYPE(
'key',
['string', 'Buffer', 'TypedArray', 'DataView']
);
}
if (typeof curve !== 'string') {
throw new 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;
@@ -211,12 +233,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,
@@ -4731,31 +4731,31 @@ 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) {
goto fatal;
EC_POINT_free(pub);
return nullptr;
}
return pub;
fatal:
EC_POINT_free(pub);
return nullptr;
}
@@ -4772,7 +4772,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) {
args.GetReturnValue().Set(
@@ -4921,7 +4923,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");
@@ -5597,6 +5601,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]));
@@ -5739,6 +5798,8 @@ void InitCrypto(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
@@ -627,6 +627,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)
@@ -645,8 +649,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);
Oops, something went wrong.

0 comments on commit f2e0288

Please sign in to comment.