Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

crypto: ECDH convertKey to convert public keys between different formats #19080

Closed
wants to merge 8 commits into from

Conversation

wuweiweiwu
Copy link
Contributor

@wuweiweiwu wuweiweiwu commented Mar 2, 2018

ECDH#convertKey is used to convert public keys between different formats, as per #18977

ECDH#convertKey(key, curve[, inputEncoding[, outputEncoding[, format]]])

  • key <string> | <Buffer> | <TypedArray> | <DataView>
  • curve <string>
  • inputEncoding <string>
  • outputEncoding <string>
  • format <string> can be 'uncompressed', 'compressed', 'hybrid'

Returns the input key converted to the specified encoding and format.

Checklist
  • make -j4 test (UNIX), or vcbuild test (Windows) passes
  • tests and/or benchmarks are included
  • documentation is changed or added
  • commit message follows commit guidelines
Affected core subsystem(s)

crypto, doc, test

@nodejs-github-bot nodejs-github-bot added c++ Issues and PRs that require attention from people who are familiar with C++. crypto Issues and PRs related to the crypto subsystem. labels Mar 2, 2018
@wuweiweiwu wuweiweiwu force-pushed the crypto-convert-key branch from 9060702 to 7869581 Compare March 2, 2018 01:27
@Trott Trott added the wip Issues and PRs that are still a work in progress. label Mar 2, 2018
if (outEnc && outEnc !== 'buffer')
convertedKey = convertedKey.toString(outEnc);
return convertedKey;
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't duplicate this logic, factor it out so it can be shared with ECDH#getPublicKey(). Likewise the C++ code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

return encode(convertedKey, outEnc);
};

function encode(buffer, encoding) {
Copy link
Contributor Author

@wuweiweiwu wuweiweiwu Mar 2, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bnoordhuis I also added the encode function since I noticed that the test for 'buffer' encoding happens a lot in this file. Should I also change that in this PR or should I submit another PR for that?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, doing it in this PR is fine.

@wuweiweiwu wuweiweiwu changed the title [WIP] crypto: ECDH convertKey to convert public keys between different formats crypto: ECDH convertKey to convert public keys between different formats Mar 3, 2018
@wuweiweiwu wuweiweiwu force-pushed the crypto-convert-key branch from 61fe99b to bd78e2c Compare March 3, 2018 00:50
Copy link
Member

@bnoordhuis bnoordhuis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly LGTM apart from some comments. Thanks for doing this.

return encode(convertedKey, outEnc);
};

function encode(buffer, encoding) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, doing it in this PR is fine.

return nullptr;
}

r = EC_POINT_oct2point(
group_,
group,
pub,
reinterpret_cast<unsigned char*>(data),
len,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you're making changes here, you might want to get rid of the goto by moving the fatal: block inside the if statement.

Copy link
Contributor Author

@wuweiweiwu wuweiweiwu Mar 4, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

@@ -5532,6 +5539,63 @@ void ExportChallenge(const FunctionCallbackInfo<Value>& args) {
args.GetReturnValue().Set(outString);
}


// convert public key to compressed, uncompressed, hybrid format
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you capitalize and punctuate the comments?

Buffer::Data(args[0]),
len);
if (pub == nullptr)
return env->ThrowError("Failed to convert Buffer to EC_POINT");
Copy link
Member

@bnoordhuis bnoordhuis Mar 3, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leaks group. group and pub are leaked on lines 5580 and 5587.

Suggestion: use a std::unique_ptr with a custom deleter or use the std::shared_ptr trick from 8ccd320.

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

size = EC_POINT_point2oct(group, pub, form, nullptr, 0, nullptr);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor thing but can you write this as int size = ...?

const cafebabeKey = 'cafebabe'.repeat(8);
// Associated compressed and uncompressed public keys (points).
const cafebabePubPtComp =
'03672a31bfc59d3f04548ec9b7daeeba2f61814e8ccc40448045007f5479f693a3';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you indent these by four spaces? Four because line continuation.

Buffer::Data(args[0]),
len);

std::shared_ptr<void> cleanup(nullptr, [group, pub] (...) {
Copy link
Contributor Author

@wuweiweiwu wuweiweiwu Mar 4, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bnoordhuis I am not sure if I am using shared_ptr correctly. I created it before the pub === nullptr check so that returns. the delete function will be called

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wuweiweiwu we also have OnScopeLeave in util.h, you could use that if you prefer? It might be a bit more self-documenting?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@addaleax I will look at that! Is there a preference using onScopeLeave v. shared_ptr?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wuweiweiwu The former is pretty new as an utility -- but basically, use whichever makes sense to you, or lets the code seem more readable. :)

(Also, probably doesn't matter much now that this has landed, unless you want to send another PR right behind this. ¯\_(ツ)_/¯)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good! I'll go over both so I am more familiar

Copy link
Member

@bnoordhuis bnoordhuis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, LGTM. CI: https://ci.nodejs.org/job/node-test-pull-request/13488/

This PR should probably get one or two more sign-offs because it adds a new API. Also, cc @nodejs/crypto.

@wuweiweiwu
Copy link
Contributor Author

node-test-commit failure looks unrelated

console output

not ok 1331 parallel/test-regress-GH-4948
  ---
  duration_ms: 0.169
  severity: fail
  stack: |-
  ...

Copy link
Member

@tniessen tniessen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well done!

@@ -656,6 +656,54 @@ assert.strictEqual(aliceSecret.toString('hex'), bobSecret.toString('hex'));
// OK
```

### ECDH.convertKey(key, curve[, inputEncoding][, outputEncoding][, format])
<!-- YAML
added: v10.0.0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be added: REPLACEME (it might be a different version on other release branches, not necessarily v10.0.0).

@@ -192,6 +181,28 @@ ECDH.prototype.generateKeys = function generateKeys(encoding, format) {
};

ECDH.prototype.getPublicKey = function getPublicKey(encoding, format) {
var f = getFormat(format);
var key = this._handle.getPublicKey(f);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason not to use const here? In general, let is preferred over var and const over let if possible.

inEnc = inEnc || encoding;
outEnc = outEnc || encoding;
var f = getFormat(format);
var convertedKey = _ECDHConvertKey(toBuf(key, inEnc), curve, f);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto.

const assert = require('assert');
const crypto = require('crypto');

const ECDH = crypto.ECDH;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I'd prefer destructuring here: const { ECDH } = crypto;, maybe even const { ECDH, getCurves } = require('crypto');.

@@ -656,6 +656,54 @@ assert.strictEqual(aliceSecret.toString('hex'), bobSecret.toString('hex'));
// OK
```

### ECDH.convertKey(key, curve[, inputEncoding][, outputEncoding][, format])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this signature correct? According to this signature, all of the following calls would be valid:

ECDH.convertKey(key, curve[, inputEncoding][, outputEncoding][, format])
ECDH.convertKey(key, curve[, inputEncoding][, outputEncoding])
ECDH.convertKey(key, curve[, inputEncoding])
ECDH.convertKey(key, curve)
ECDH.convertKey(key, curve[, inputEncoding][, format])
ECDH.convertKey(key, curve[, outputEncoding][, format])
ECDH.convertKey(key, curve[, format])

Copy link
Contributor Author

@wuweiweiwu wuweiweiwu Mar 5, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tniessen How would I structure the signature?

Function signature?

key and curve are required but the rest are optional. These are the available calls:

ECDH.convertKey(key, curve, inputEncoding, outputEncoding, format)
ECDH.convertKey(key, curve, inputEncoding, outputEncoding)
ECDH.convertKey(key, curve, inputEncoding)
ECDH.convertKey(key, curve)

Would it be: ECDH.convertKey(key, curve[, inputEncoding, outputEncoding, format])?

or just ECDH.convertKey(key, curve, inputEncoding, outputEncoding, format)

I figured it out!

ECDH.convertKey(key, curve[, inputEncoding[, outputEncoding[, format]]])

Example (uncompressing a key):

```js
const ECDH = require('crypto').ECDH;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Use destructuring: const { ECDH } = require('crypto');.


const availableCurves = new Set(getCurves());

if (availableCurves.has('secp256k1')) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tiny nit: No need for a set, just use if (getCurves().includes('secp256k1')) {.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed! :)

const assert = require('assert');
const crypto = require('crypto');

const { ECDH, getCurves } = crypto;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tiny nit: This can be combined with the call to require:

const assert = require('assert');
const { ECDH, getCurves } = require('crypto');

@jasnell jasnell added the semver-minor PRs that contain new features and should be released in the next minor version. label Mar 5, 2018

CHECK_EQ(args.Length(), 3);

THROW_AND_RETURN_IF_NOT_BUFFER(args[0], "Public key");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is adding a new API, please handle the input type checking in javascript and use the internal/errors mechanism.

Copy link
Contributor Author

@wuweiweiwu wuweiweiwu Mar 5, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done! :)

@wuweiweiwu
Copy link
Contributor Author

wuweiweiwu commented Mar 9, 2018

@jasnell @tniessen @Trott I had to resolve some merge conflicts in diffiehelman.js. Is there anything else that I should do on this PR?

@Trott Trott removed the wip Issues and PRs that are still a work in progress. label Mar 9, 2018
@tniessen
Copy link
Member

@wuweiweiwu Please rebase your changes on top of master instead of adding a merge commit if there are conflicts. In order to undo the merge commit and to properly rebase your changes, follow these steps.

# This is only necessary if you did not add the upstream remote before.
git remote add upstream https://github.com/nodejs/node.git

# Checkout your branch.
git checkout crypto-convert-key
# Undo the merge commit.
git reset --hard 7a921c56bd19cb2e7fa68862bcfa023fedc817b8
# Make sure your copy of our master branch is up-to-date.
git fetch upstream master
# Rebase on top of our changes.
git rebase upstream/master

git rebase will stop as soon as it encounters a conflict, at which point you will need to follow the instructions on the screen. It is usually a cycle of

  1. using git status to determine which files the conflict occurred in,
  2. manually resolving the conflicts,
  3. telling git that you are done with git add path/to/file/with/conflicts, and
  4. resuming the rebase with git rebase --continue.

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

Fixes: nodejs#18977
refactored ECDH::BufferToPoint and
key formatting
Adding tests to verify that convertkey
correctly converts ECDH keys to different
formats
Adding docs and usage examples.
Using std::shared_ptr to clean up pointers.
Formatting changes. Refactoring encode function.
Using const over var. Destructuring notation.
Updated function signature.
Handle input typechecking in JavaScript using
errors. Minor fixes and updated tests
Using the new error throwing mechanism.
@wuweiweiwu
Copy link
Contributor Author

wuweiweiwu commented Mar 10, 2018

@tniessen Thanks for the tips! I have rebased and also updated my error throwing using the new errors.

@tniessen
Copy link
Member

tniessen commented Mar 11, 2018

Thank you! CI: https://ci.nodejs.org/job/node-test-pull-request/13624/
(By the way, you don't need to follow the commit message guidelines for subsequent commits as they will usually be squashed into a single commit anyway.)

@wuweiweiwu
Copy link
Contributor Author

wuweiweiwu commented Mar 11, 2018

Not sure why test-process-env-deprecation fails for those test cases
09:16:20 not ok 1280 parallel/test-process-env-deprecation
09:16:20   ---
09:16:20   duration_ms: 0.297
09:16:20   severity: fail
09:16:20   stack: |-
09:16:20     Mismatched <anonymous> function calls. Expected exactly 1, actual 0.
09:16:20         at Object.exports.mustCall (/home/iojs/build/workspace/node-test-commit-linux-containered/nodes/ubuntu1604_sharedlibs_withoutintl_x64/test/common/index.js:427:10)
09:16:20         at expectWarning (/home/iojs/build/workspace/node-test-commit-linux-containered/nodes/ubuntu1604_sharedlibs_withoutintl_x64/test/common/index.js:615:18)
09:16:20         at expectWarningByName (/home/iojs/build/workspace/node-test-commit-linux-containered/nodes/ubuntu1604_sharedlibs_withoutintl_x64/test/common/index.js:629:25)
09:16:20         at Object.exports.expectWarning (/home/iojs/build/workspace/node-test-commit-linux-containered/nodes/ubuntu1604_sharedlibs_withoutintl_x64/test/common/index.js:649:5)
09:16:20         at Object.<anonymous> (/home/iojs/build/workspace/node-test-commit-linux-containered/nodes/ubuntu1604_sharedlibs_withoutintl_x64/test/parallel/test-process-env-deprecation.js:7:8)
09:16:20         at Module._compile (module.js:671:30)
09:16:20         at Object.Module._extensions..js (module.js:682:10)
09:16:20         at Module.load (module.js:582:32)
09:16:20         at tryModuleLoad (module.js:522:12)
09:16:20   ...

It passes locally.

@tniessen Is it something I need to fix?

@Trott
Copy link
Member

Trott commented Mar 13, 2018

I think the CI problems were on master and not in this PR and that they've been fixed. Let's find out...

CI: https://ci.nodejs.org/job/node-test-pull-request/13656/

@wuweiweiwu
Copy link
Contributor Author

wuweiweiwu commented Mar 13, 2018

Offending test: sequential/test-inspector-stop-profile-after-done
not ok 537 sequential/test-inspector-stop-profile-after-done
  ---
  duration_ms: 1.201
  severity: fail
  stack: |-
    [test] Connecting to a child Node process
    [test] Testing /json/list
    [err] Debugger listening on ws://127.0.0.1:63629/5d4b460e-4dec-4c63-a4f3-b9675efce5a3
    [err] For help see https://nodejs.org/en/docs/inspector
    [err] 
    [out] {}
    [out] 
    [out] {}
    [out] 
    [out] {}
    [out] 
    [out] {}
    [out] 
    [out] {}
    [out] 
    [out] {}
    [out] 
    [out] {}
    [out] 
    [out] {}
    [out] 
    [out] {}
    [out] 
    [out] {}
    [out] 
    [out] {}
    [out] 
    [err] Debugger attached.
    [err] Waiting for the debugger to disconnect...
    [err] 
    { AssertionError [ERR_ASSERTION]: 3221225477 strictEqual 0
        at runTests (c:\workspace\node-test-binary-windows\test\sequential\test-inspector-stop-profile-after-done.js:28:10)
        at process._tickCallback (internal/process/next_tick.js:114:7)
      generatedMessage: true,
      name: 'AssertionError [ERR_ASSERTION]',
      code: 'ERR_ASSERTION',
      actual: 3221225477,
      expected: 0,
      operator: 'strictEqual' }
    1
  ...

@Trott seems unrelated. But this test was passing for me locally. What should I do?

@Trott
Copy link
Member

Trott commented Mar 13, 2018

@wuweiweiwu That test failing on Windows in CI is a known issue: #19263

I think this can be treated as "CI has passed" at this point.

Also: "Node.js project needs to get CI back to green, this is ridiculous." But that's not your problem to solve (unless you want to). :-D

@Trott
Copy link
Member

Trott commented Mar 13, 2018

/ping @nodejs/crypto

@wuweiweiwu
Copy link
Contributor Author

@Trott just checking up on the status of this PR!

@tniessen
Copy link
Member

@wuweiweiwu I didn't have time to review this thoroughly, but this should be ready to land nevertheless. I will try to review and land on Saturday if no one beats me to it.

One more CI: https://ci.nodejs.org/job/node-test-pull-request/13826/

@Trott
Copy link
Member

Trott commented Mar 22, 2018

Re-running FreeBSD: https://ci.nodejs.org/job/node-test-commit-freebsd/16379/

Re-running arm-fanned: https://ci.nodejs.org/job/node-test-commit-arm-fanned/15339/

@Trott Trott added the author ready PRs that have at least one approval, no pending requests for changes, and a CI started. label Mar 22, 2018
@Trott
Copy link
Member

Trott commented Mar 23, 2018

Both re-runs of CI were green. This can land.

@tniessen
Copy link
Member

Landed in f2e0288, thank you @wuweiweiwu! 🎉

@tniessen tniessen closed this Mar 23, 2018
tniessen pushed a commit that referenced this pull request Mar 23, 2018
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>
@wuweiweiwu
Copy link
Contributor Author

Awesome! Can't wait to contribute more :)

@targos targos added backport-requested-v9.x and removed author ready PRs that have at least one approval, no pending requests for changes, and a CI started. labels Apr 2, 2018
targos pushed a commit to targos/node that referenced this pull request Apr 4, 2018
ECDH.convertKey is used to convert public keys between different
formats.

PR-URL: nodejs#19080
Fixes: nodejs#18977
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: James M Snell <jasnell@gmail.com>
targos pushed a commit that referenced this pull request Apr 12, 2018
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>

Backport-PR-URL: #19745
Reviewed-By: Tobias Nießen <tniessen@tnie.de>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c++ Issues and PRs that require attention from people who are familiar with C++. crypto Issues and PRs related to the crypto subsystem. semver-minor PRs that contain new features and should be released in the next minor version.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants