Skip to content

Commit f2e0288

Browse files
wuweiweiwutniessen
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 <info@bnoordhuis.nl> Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent a0adf56 commit f2e0288

File tree

5 files changed

+275
-45
lines changed

5 files changed

+275
-45
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} Defaults to `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: 50 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ const {
1414
const {
1515
DiffieHellman: _DiffieHellman,
1616
DiffieHellmanGroup: _DiffieHellmanGroup,
17-
ECDH: _ECDH
17+
ECDH: _ECDH,
18+
ECDHConvertKey: _ECDHConvertKey
1819
} = process.binding('crypto');
1920
const {
2021
POINT_CONVERSION_COMPRESSED,
@@ -84,11 +85,9 @@ DiffieHellmanGroup.prototype.generateKeys =
8485
dhGenerateKeys;
8586

8687
function dhGenerateKeys(encoding) {
87-
var keys = this._handle.generateKeys();
88+
const keys = this._handle.generateKeys();
8889
encoding = encoding || getDefaultEncoding();
89-
if (encoding && encoding !== 'buffer')
90-
keys = keys.toString(encoding);
91-
return keys;
90+
return encode(keys, encoding);
9291
}
9392

9493

@@ -100,12 +99,10 @@ function dhComputeSecret(key, inEnc, outEnc) {
10099
const encoding = getDefaultEncoding();
101100
inEnc = inEnc || encoding;
102101
outEnc = outEnc || encoding;
103-
var ret = this._handle.computeSecret(toBuf(key, inEnc));
102+
const ret = this._handle.computeSecret(toBuf(key, inEnc));
104103
if (typeof ret === 'string')
105104
throw new ERR_CRYPTO_ECDH_INVALID_PUBLIC_KEY();
106-
if (outEnc && outEnc !== 'buffer')
107-
ret = ret.toString(outEnc);
108-
return ret;
105+
return encode(ret, outEnc);
109106
}
110107

111108

@@ -114,11 +111,9 @@ DiffieHellmanGroup.prototype.getPrime =
114111
dhGetPrime;
115112

116113
function dhGetPrime(encoding) {
117-
var prime = this._handle.getPrime();
114+
const prime = this._handle.getPrime();
118115
encoding = encoding || getDefaultEncoding();
119-
if (encoding && encoding !== 'buffer')
120-
prime = prime.toString(encoding);
121-
return prime;
116+
return encode(prime, encoding);
122117
}
123118

124119

@@ -127,11 +122,9 @@ DiffieHellmanGroup.prototype.getGenerator =
127122
dhGetGenerator;
128123

129124
function dhGetGenerator(encoding) {
130-
var generator = this._handle.getGenerator();
125+
const generator = this._handle.getGenerator();
131126
encoding = encoding || getDefaultEncoding();
132-
if (encoding && encoding !== 'buffer')
133-
generator = generator.toString(encoding);
134-
return generator;
127+
return encode(generator, encoding);
135128
}
136129

137130

@@ -140,11 +133,9 @@ DiffieHellmanGroup.prototype.getPublicKey =
140133
dhGetPublicKey;
141134

142135
function dhGetPublicKey(encoding) {
143-
var key = this._handle.getPublicKey();
136+
const key = this._handle.getPublicKey();
144137
encoding = encoding || getDefaultEncoding();
145-
if (encoding && encoding !== 'buffer')
146-
key = key.toString(encoding);
147-
return key;
138+
return encode(key, encoding);
148139
}
149140

150141

@@ -153,11 +144,9 @@ DiffieHellmanGroup.prototype.getPrivateKey =
153144
dhGetPrivateKey;
154145

155146
function dhGetPrivateKey(encoding) {
156-
var key = this._handle.getPrivateKey();
147+
const key = this._handle.getPrivateKey();
157148
encoding = encoding || getDefaultEncoding();
158-
if (encoding && encoding !== 'buffer')
159-
key = key.toString(encoding);
160-
return key;
149+
return encode(key, encoding);
161150
}
162151

163152

@@ -197,7 +186,40 @@ ECDH.prototype.generateKeys = function generateKeys(encoding, format) {
197186
};
198187

199188
ECDH.prototype.getPublicKey = function getPublicKey(encoding, format) {
200-
var f;
189+
const f = getFormat(format);
190+
const key = this._handle.getPublicKey(f);
191+
encoding = encoding || getDefaultEncoding();
192+
return encode(key, encoding);
193+
};
194+
195+
ECDH.convertKey = function convertKey(key, curve, inEnc, outEnc, format) {
196+
if (typeof key !== 'string' && !isArrayBufferView(key)) {
197+
throw new ERR_INVALID_ARG_TYPE(
198+
'key',
199+
['string', 'Buffer', 'TypedArray', 'DataView']
200+
);
201+
}
202+
203+
if (typeof curve !== 'string') {
204+
throw new ERR_INVALID_ARG_TYPE('curve', 'string');
205+
}
206+
207+
const encoding = getDefaultEncoding();
208+
inEnc = inEnc || encoding;
209+
outEnc = outEnc || encoding;
210+
const f = getFormat(format);
211+
const convertedKey = _ECDHConvertKey(toBuf(key, inEnc), curve, f);
212+
return encode(convertedKey, outEnc);
213+
};
214+
215+
function encode(buffer, encoding) {
216+
if (encoding && encoding !== 'buffer')
217+
buffer = buffer.toString(encoding);
218+
return buffer;
219+
}
220+
221+
function getFormat(format) {
222+
let f;
201223
if (format) {
202224
if (format === 'compressed')
203225
f = POINT_CONVERSION_COMPRESSED;
@@ -211,12 +233,8 @@ ECDH.prototype.getPublicKey = function getPublicKey(encoding, format) {
211233
} else {
212234
f = POINT_CONVERSION_UNCOMPRESSED;
213235
}
214-
var key = this._handle.getPublicKey(f);
215-
encoding = encoding || getDefaultEncoding();
216-
if (encoding && encoding !== 'buffer')
217-
key = key.toString(encoding);
218-
return key;
219-
};
236+
return f;
237+
}
220238

221239
module.exports = {
222240
DiffieHellman,

src/node_crypto.cc

Lines changed: 72 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4731,31 +4731,31 @@ void ECDH::GenerateKeys(const FunctionCallbackInfo<Value>& args) {
47314731
}
47324732

47334733

4734-
EC_POINT* ECDH::BufferToPoint(char* data, size_t len) {
4734+
EC_POINT* ECDH::BufferToPoint(Environment* env,
4735+
const EC_GROUP* group,
4736+
char* data,
4737+
size_t len) {
47354738
EC_POINT* pub;
47364739
int r;
47374740

4738-
pub = EC_POINT_new(group_);
4741+
pub = EC_POINT_new(group);
47394742
if (pub == nullptr) {
4740-
env()->ThrowError("Failed to allocate EC_POINT for a public key");
4743+
env->ThrowError("Failed to allocate EC_POINT for a public key");
47414744
return nullptr;
47424745
}
47434746

47444747
r = EC_POINT_oct2point(
4745-
group_,
4748+
group,
47464749
pub,
47474750
reinterpret_cast<unsigned char*>(data),
47484751
len,
47494752
nullptr);
47504753
if (!r) {
4751-
goto fatal;
4754+
EC_POINT_free(pub);
4755+
return nullptr;
47524756
}
47534757

47544758
return pub;
4755-
4756-
fatal:
4757-
EC_POINT_free(pub);
4758-
return nullptr;
47594759
}
47604760

47614761

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

4775-
EC_POINT* pub = ecdh->BufferToPoint(Buffer::Data(args[0]),
4775+
EC_POINT* pub = ECDH::BufferToPoint(env,
4776+
ecdh->group_,
4777+
Buffer::Data(args[0]),
47764778
Buffer::Length(args[0]));
47774779
if (pub == nullptr) {
47784780
args.GetReturnValue().Set(
@@ -4921,7 +4923,9 @@ void ECDH::SetPublicKey(const FunctionCallbackInfo<Value>& args) {
49214923

49224924
MarkPopErrorOnReturn mark_pop_error_on_return;
49234925

4924-
EC_POINT* pub = ecdh->BufferToPoint(Buffer::Data(args[0].As<Object>()),
4926+
EC_POINT* pub = ECDH::BufferToPoint(env,
4927+
ecdh->group_,
4928+
Buffer::Data(args[0].As<Object>()),
49254929
Buffer::Length(args[0].As<Object>()));
49264930
if (pub == nullptr)
49274931
return env->ThrowError("Failed to convert Buffer to EC_POINT");
@@ -5597,6 +5601,61 @@ void ExportChallenge(const FunctionCallbackInfo<Value>& args) {
55975601
args.GetReturnValue().Set(outString);
55985602
}
55995603

5604+
5605+
// Convert the input public key to compressed, uncompressed, or hybrid formats.
5606+
void ConvertKey(const FunctionCallbackInfo<Value>& args) {
5607+
Environment* env = Environment::GetCurrent(args);
5608+
5609+
CHECK_EQ(args.Length(), 3);
5610+
5611+
size_t len = Buffer::Length(args[0]);
5612+
if (len == 0)
5613+
return args.GetReturnValue().SetEmptyString();
5614+
5615+
node::Utf8Value curve(env->isolate(), args[1]);
5616+
5617+
int nid = OBJ_sn2nid(*curve);
5618+
if (nid == NID_undef)
5619+
return env->ThrowTypeError("Invalid ECDH curve name");
5620+
5621+
EC_GROUP* group = EC_GROUP_new_by_curve_name(nid);
5622+
if (group == nullptr)
5623+
return env->ThrowError("Failed to get EC_GROUP");
5624+
5625+
EC_POINT* pub = ECDH::BufferToPoint(env,
5626+
group,
5627+
Buffer::Data(args[0]),
5628+
len);
5629+
5630+
std::shared_ptr<void> cleanup(nullptr, [group, pub] (...) {
5631+
EC_GROUP_free(group);
5632+
EC_POINT_free(pub);
5633+
});
5634+
5635+
if (pub == nullptr)
5636+
return env->ThrowError("Failed to convert Buffer to EC_POINT");
5637+
5638+
point_conversion_form_t form =
5639+
static_cast<point_conversion_form_t>(args[2]->Uint32Value());
5640+
5641+
int size = EC_POINT_point2oct(group, pub, form, nullptr, 0, nullptr);
5642+
if (size == 0)
5643+
return env->ThrowError("Failed to get public key length");
5644+
5645+
unsigned char* out = node::Malloc<unsigned char>(size);
5646+
5647+
int r = EC_POINT_point2oct(group, pub, form, out, size, nullptr);
5648+
if (r != size) {
5649+
free(out);
5650+
return env->ThrowError("Failed to get public key");
5651+
}
5652+
5653+
Local<Object> buf =
5654+
Buffer::New(env, reinterpret_cast<char*>(out), size).ToLocalChecked();
5655+
args.GetReturnValue().Set(buf);
5656+
}
5657+
5658+
56005659
void TimingSafeEqual(const FunctionCallbackInfo<Value>& args) {
56015660
CHECK(Buffer::HasInstance(args[0]));
56025661
CHECK(Buffer::HasInstance(args[1]));
@@ -5739,6 +5798,8 @@ void InitCrypto(Local<Object> target,
57395798
env->SetMethod(target, "certVerifySpkac", VerifySpkac);
57405799
env->SetMethod(target, "certExportPublicKey", ExportPublicKey);
57415800
env->SetMethod(target, "certExportChallenge", ExportChallenge);
5801+
5802+
env->SetMethod(target, "ECDHConvertKey", ConvertKey);
57425803
#ifndef OPENSSL_NO_ENGINE
57435804
env->SetMethod(target, "setEngine", SetEngine);
57445805
#endif // !OPENSSL_NO_ENGINE

src/node_crypto.h

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

629629
static void Initialize(Environment* env, v8::Local<v8::Object> target);
630+
static EC_POINT* BufferToPoint(Environment* env,
631+
const EC_GROUP* group,
632+
char* data,
633+
size_t len);
630634

631635
protected:
632636
ECDH(Environment* env, v8::Local<v8::Object> wrap, EC_KEY* key)
@@ -645,8 +649,6 @@ class ECDH : public BaseObject {
645649
static void GetPublicKey(const v8::FunctionCallbackInfo<v8::Value>& args);
646650
static void SetPublicKey(const v8::FunctionCallbackInfo<v8::Value>& args);
647651

648-
EC_POINT* BufferToPoint(char* data, size_t len);
649-
650652
bool IsKeyPairValid();
651653
bool IsKeyValidForCurve(const BIGNUM* private_key);
652654

0 commit comments

Comments
 (0)