Skip to content

Commit f06b41e

Browse files
wuweiweiwutargos
authored andcommitted
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 <[email protected]> Reviewed-By: James M Snell <[email protected]> Backport-PR-URL: #19745 Reviewed-By: Tobias Nießen <[email protected]>
1 parent 5ecd82d commit f06b41e

File tree

5 files changed

+277
-46
lines changed

5 files changed

+277
-46
lines changed

doc/api/crypto.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,54 @@ assert.strictEqual(aliceSecret.toString('hex'), bobSecret.toString('hex'));
656656
// OK
657657
```
658658

659+
### ECDH.convertKey(key, curve[, inputEncoding[, outputEncoding[, format]]])
660+
<!-- YAML
661+
added: REPLACEME
662+
-->
663+
664+
- `key` {string | Buffer | TypedArray | DataView}
665+
- `curve` {string}
666+
- `inputEncoding` {string}
667+
- `outputEncoding` {string}
668+
- `format` {string} **Default:** `uncompressed`
669+
670+
Converts the EC Diffie-Hellman public key specified by `key` and `curve` to the
671+
format specified by `format`. The `format` argument specifies point encoding
672+
and can be `'compressed'`, `'uncompressed'` or `'hybrid'`. The supplied key is
673+
interpreted using the specified `inputEncoding`, and the returned key is encoded
674+
using the specified `outputEncoding`. Encodings can be `'latin1'`, `'hex'`,
675+
or `'base64'`.
676+
677+
Use [`crypto.getCurves()`][] to obtain a list of available curve names.
678+
On recent OpenSSL releases, `openssl ecparam -list_curves` will also display
679+
the name and description of each available elliptic curve.
680+
681+
If `format` is not specified the point will be returned in `'uncompressed'`
682+
format.
683+
684+
If the `inputEncoding` is not provided, `key` is expected to be a [`Buffer`][],
685+
`TypedArray`, or `DataView`.
686+
687+
Example (uncompressing a key):
688+
689+
```js
690+
const { ECDH } = require('crypto');
691+
692+
const ecdh = ECDH('secp256k1');
693+
ecdh.generateKeys();
694+
695+
const compressedKey = ecdh.getPublicKey('hex', 'compressed');
696+
697+
const uncompressedKey = ECDH.convertKey(compressedKey,
698+
'secp256k1',
699+
'hex',
700+
'hex',
701+
'uncompressed');
702+
703+
// the converted key and the uncompressed public key should be the same
704+
console.log(uncompressedKey === ecdh.getPublicKey('hex'));
705+
```
706+
659707
### ecdh.computeSecret(otherPublicKey[, inputEncoding][, outputEncoding])
660708
<!-- YAML
661709
added: v0.11.14

lib/internal/crypto/diffiehellman.js

Lines changed: 51 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ const {
1010
const {
1111
DiffieHellman: _DiffieHellman,
1212
DiffieHellmanGroup: _DiffieHellmanGroup,
13-
ECDH: _ECDH
13+
ECDH: _ECDH,
14+
ECDHConvertKey: _ECDHConvertKey
1415
} = process.binding('crypto');
1516
const {
1617
POINT_CONVERSION_COMPRESSED,
@@ -79,11 +80,9 @@ DiffieHellmanGroup.prototype.generateKeys =
7980
dhGenerateKeys;
8081

8182
function dhGenerateKeys(encoding) {
82-
var keys = this._handle.generateKeys();
83+
const keys = this._handle.generateKeys();
8384
encoding = encoding || getDefaultEncoding();
84-
if (encoding && encoding !== 'buffer')
85-
keys = keys.toString(encoding);
86-
return keys;
85+
return encode(keys, encoding);
8786
}
8887

8988

@@ -95,10 +94,8 @@ function dhComputeSecret(key, inEnc, outEnc) {
9594
const encoding = getDefaultEncoding();
9695
inEnc = inEnc || encoding;
9796
outEnc = outEnc || encoding;
98-
var ret = this._handle.computeSecret(toBuf(key, inEnc));
99-
if (outEnc && outEnc !== 'buffer')
100-
ret = ret.toString(outEnc);
101-
return ret;
97+
const ret = this._handle.computeSecret(toBuf(key, inEnc));
98+
return encode(ret, outEnc);
10299
}
103100

104101

@@ -107,11 +104,9 @@ DiffieHellmanGroup.prototype.getPrime =
107104
dhGetPrime;
108105

109106
function dhGetPrime(encoding) {
110-
var prime = this._handle.getPrime();
107+
const prime = this._handle.getPrime();
111108
encoding = encoding || getDefaultEncoding();
112-
if (encoding && encoding !== 'buffer')
113-
prime = prime.toString(encoding);
114-
return prime;
109+
return encode(prime, encoding);
115110
}
116111

117112

@@ -120,11 +115,9 @@ DiffieHellmanGroup.prototype.getGenerator =
120115
dhGetGenerator;
121116

122117
function dhGetGenerator(encoding) {
123-
var generator = this._handle.getGenerator();
118+
const generator = this._handle.getGenerator();
124119
encoding = encoding || getDefaultEncoding();
125-
if (encoding && encoding !== 'buffer')
126-
generator = generator.toString(encoding);
127-
return generator;
120+
return encode(generator, encoding);
128121
}
129122

130123

@@ -133,11 +126,9 @@ DiffieHellmanGroup.prototype.getPublicKey =
133126
dhGetPublicKey;
134127

135128
function dhGetPublicKey(encoding) {
136-
var key = this._handle.getPublicKey();
129+
const key = this._handle.getPublicKey();
137130
encoding = encoding || getDefaultEncoding();
138-
if (encoding && encoding !== 'buffer')
139-
key = key.toString(encoding);
140-
return key;
131+
return encode(key, encoding);
141132
}
142133

143134

@@ -146,11 +137,9 @@ DiffieHellmanGroup.prototype.getPrivateKey =
146137
dhGetPrivateKey;
147138

148139
function dhGetPrivateKey(encoding) {
149-
var key = this._handle.getPrivateKey();
140+
const key = this._handle.getPrivateKey();
150141
encoding = encoding || getDefaultEncoding();
151-
if (encoding && encoding !== 'buffer')
152-
key = key.toString(encoding);
153-
return key;
142+
return encode(key, encoding);
154143
}
155144

156145

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

192181
ECDH.prototype.getPublicKey = function getPublicKey(encoding, format) {
193-
var f;
182+
const f = getFormat(format);
183+
const key = this._handle.getPublicKey(f);
184+
encoding = encoding || getDefaultEncoding();
185+
return encode(key, encoding);
186+
};
187+
188+
ECDH.convertKey = function convertKey(key, curve, inEnc, outEnc, format) {
189+
if (typeof key !== 'string' && !isArrayBufferView(key)) {
190+
throw new errors.TypeError(
191+
'ERR_INVALID_ARG_TYPE',
192+
'key',
193+
['string', 'Buffer', 'TypedArray', 'DataView']
194+
);
195+
}
196+
197+
if (typeof curve !== 'string') {
198+
throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'curve', 'string');
199+
}
200+
201+
const encoding = getDefaultEncoding();
202+
inEnc = inEnc || encoding;
203+
outEnc = outEnc || encoding;
204+
const f = getFormat(format);
205+
const convertedKey = _ECDHConvertKey(toBuf(key, inEnc), curve, f);
206+
return encode(convertedKey, outEnc);
207+
};
208+
209+
function encode(buffer, encoding) {
210+
if (encoding && encoding !== 'buffer')
211+
buffer = buffer.toString(encoding);
212+
return buffer;
213+
}
214+
215+
function getFormat(format) {
216+
let f;
194217
if (format) {
195218
if (format === 'compressed')
196219
f = POINT_CONVERSION_COMPRESSED;
@@ -204,12 +227,8 @@ ECDH.prototype.getPublicKey = function getPublicKey(encoding, format) {
204227
} else {
205228
f = POINT_CONVERSION_UNCOMPRESSED;
206229
}
207-
var key = this._handle.getPublicKey(f);
208-
encoding = encoding || getDefaultEncoding();
209-
if (encoding && encoding !== 'buffer')
210-
key = key.toString(encoding);
211-
return key;
212-
};
230+
return f;
231+
}
213232

214233
module.exports = {
215234
DiffieHellman,

src/node_crypto.cc

Lines changed: 73 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5283,32 +5283,32 @@ void ECDH::GenerateKeys(const FunctionCallbackInfo<Value>& args) {
52835283
}
52845284

52855285

5286-
EC_POINT* ECDH::BufferToPoint(char* data, size_t len) {
5286+
EC_POINT* ECDH::BufferToPoint(Environment* env,
5287+
const EC_GROUP* group,
5288+
char* data,
5289+
size_t len) {
52875290
EC_POINT* pub;
52885291
int r;
52895292

5290-
pub = EC_POINT_new(group_);
5293+
pub = EC_POINT_new(group);
52915294
if (pub == nullptr) {
5292-
env()->ThrowError("Failed to allocate EC_POINT for a public key");
5295+
env->ThrowError("Failed to allocate EC_POINT for a public key");
52935296
return nullptr;
52945297
}
52955298

52965299
r = EC_POINT_oct2point(
5297-
group_,
5300+
group,
52985301
pub,
52995302
reinterpret_cast<unsigned char*>(data),
53005303
len,
53015304
nullptr);
53025305
if (!r) {
5303-
env()->ThrowError("Failed to translate Buffer to a EC_POINT");
5304-
goto fatal;
5306+
env->ThrowError("Failed to translate Buffer to a EC_POINT");
5307+
EC_POINT_free(pub);
5308+
return nullptr;
53055309
}
53065310

53075311
return pub;
5308-
5309-
fatal:
5310-
EC_POINT_free(pub);
5311-
return nullptr;
53125312
}
53135313

53145314

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

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

54715473
MarkPopErrorOnReturn mark_pop_error_on_return;
54725474

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

6153+
6154+
// Convert the input public key to compressed, uncompressed, or hybrid formats.
6155+
void ConvertKey(const FunctionCallbackInfo<Value>& args) {
6156+
Environment* env = Environment::GetCurrent(args);
6157+
6158+
CHECK_EQ(args.Length(), 3);
6159+
6160+
size_t len = Buffer::Length(args[0]);
6161+
if (len == 0)
6162+
return args.GetReturnValue().SetEmptyString();
6163+
6164+
node::Utf8Value curve(env->isolate(), args[1]);
6165+
6166+
int nid = OBJ_sn2nid(*curve);
6167+
if (nid == NID_undef)
6168+
return env->ThrowTypeError("Invalid ECDH curve name");
6169+
6170+
EC_GROUP* group = EC_GROUP_new_by_curve_name(nid);
6171+
if (group == nullptr)
6172+
return env->ThrowError("Failed to get EC_GROUP");
6173+
6174+
EC_POINT* pub = ECDH::BufferToPoint(env,
6175+
group,
6176+
Buffer::Data(args[0]),
6177+
len);
6178+
6179+
std::shared_ptr<void> cleanup(nullptr, [group, pub] (...) {
6180+
EC_GROUP_free(group);
6181+
EC_POINT_free(pub);
6182+
});
6183+
6184+
if (pub == nullptr)
6185+
return env->ThrowError("Failed to convert Buffer to EC_POINT");
6186+
6187+
point_conversion_form_t form =
6188+
static_cast<point_conversion_form_t>(args[2]->Uint32Value());
6189+
6190+
int size = EC_POINT_point2oct(group, pub, form, nullptr, 0, nullptr);
6191+
if (size == 0)
6192+
return env->ThrowError("Failed to get public key length");
6193+
6194+
unsigned char* out = node::Malloc<unsigned char>(size);
6195+
6196+
int r = EC_POINT_point2oct(group, pub, form, out, size, nullptr);
6197+
if (r != size) {
6198+
free(out);
6199+
return env->ThrowError("Failed to get public key");
6200+
}
6201+
6202+
Local<Object> buf =
6203+
Buffer::New(env, reinterpret_cast<char*>(out), size).ToLocalChecked();
6204+
args.GetReturnValue().Set(buf);
6205+
}
6206+
6207+
61496208
void TimingSafeEqual(const FunctionCallbackInfo<Value>& args) {
61506209
CHECK(Buffer::HasInstance(args[0]));
61516210
CHECK(Buffer::HasInstance(args[1]));
@@ -6289,6 +6348,8 @@ void Initialize(Local<Object> target,
62896348
env->SetMethod(target, "certVerifySpkac", VerifySpkac);
62906349
env->SetMethod(target, "certExportPublicKey", ExportPublicKey);
62916350
env->SetMethod(target, "certExportChallenge", ExportChallenge);
6351+
6352+
env->SetMethod(target, "ECDHConvertKey", ConvertKey);
62926353
#ifndef OPENSSL_NO_ENGINE
62936354
env->SetMethod(target, "setEngine", SetEngine);
62946355
#endif // !OPENSSL_NO_ENGINE

src/node_crypto.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -709,6 +709,10 @@ class ECDH : public BaseObject {
709709
}
710710

711711
static void Initialize(Environment* env, v8::Local<v8::Object> target);
712+
static EC_POINT* BufferToPoint(Environment* env,
713+
const EC_GROUP* group,
714+
char* data,
715+
size_t len);
712716

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

730-
EC_POINT* BufferToPoint(char* data, size_t len);
731-
732734
bool IsKeyPairValid();
733735
bool IsKeyValidForCurve(const BIGNUM* private_key);
734736

0 commit comments

Comments
 (0)