-
Notifications
You must be signed in to change notification settings - Fork 29.6k
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: Add crypto.getEngines() #10865
Conversation
CI is https://ci.nodejs.org/job/node-test-pull-request/5921/ . Tests in Win and arm would be failed. |
doc/api/crypto.md
Outdated
console.log(crypto.getEngines()); | ||
// Prints: | ||
// [ { id: 'rdrand', name: 'Intel RDRAND engine' }, | ||
// { id: 'dynamic', name: 'Dynamic engine loading support' } ] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor nit: this line looks better indented two more spaces to the right.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will fix it, thanks.
lib/crypto.js
Outdated
@@ -633,6 +633,11 @@ exports.setEngine = function setEngine(id, flags) { | |||
return binding.setEngine(id, flags); | |||
}; | |||
|
|||
|
|||
exports.getEngines = function getEngines() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we get away with directly assigning to binding.getEngines
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mean we should use internalUtil.cachedResult
such as getCipher()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I meant if we could just do exports.getEngines = binding.getEngines;
and avoid the wrapper function?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's fine with me.
node.gyp
Outdated
@@ -947,6 +947,18 @@ | |||
], | |||
}], | |||
] | |||
}, | |||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to put this whole definition in a separate gyp file in test/ somewhere instead of node.gyp, since it's only used for a test?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think having target_name
and type
is good to know its purpose otherwise we need to write a comment of it nearly as the same description.
Perhaps it might be useful to also have a |
The test was unstable. Here is a new test result, https://ci.nodejs.org/job/node-test-commit/7335/ |
test/parallel/test-crypto-engine.js
Outdated
'use strict'; | ||
const common = require('../common'); | ||
|
||
if (!common.hasCrypto) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
missing test description, see https://github.com/nodejs/node/blob/master/doc/guides/writing-tests.md#test-structure
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
found++; | ||
}); | ||
|
||
assert.strictEqual(found, 1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
assert conflicts with comment about "at least one"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The description of comment was wrong. Fixed.
test/parallel/test-crypto-engine.js
Outdated
let found = 0; | ||
|
||
// check at least one builtin engine exists. | ||
crypto.getEngines().forEach((e) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same as found = array.filter((e) => e.id === 'dynamic').length
?
|
||
crypto.setEngine(test_engine); | ||
|
||
crypto.getEngines().forEach((e) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
found = crypto.getEngines.filter((e) => e.id === test_engine_id && e.name === test_engine_name)).length
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any reason to use filter rather than forEach? It seems to be equivalent for me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Its true that many of the Array methods can be implemented in terms of forEach(), so they are equivalent in that sense. Someone has to read this code, and understand that it is filtering. Better to just call filter()
. And if you don't, @Trott or one of the code-and-learn people may come through and modify this to use es6 and builtin library functions - better avoid the churn and do it now, I think.
fs.accessSync(test_engine); | ||
}, 'node test engine ' + test_engine + ' is not found.'); | ||
|
||
crypto.setEngine(test_engine); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe call twice to prove its possible?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is possible. Do we need a such kind of combination tests for setEngine?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tests should test invalid behaviours, and obscure corner conditions. If it is allowed to call multiple times, the test should do, and assert on the result, IMO. It would be possible that the second call always fails, for example, or someone might believe that both "RSA" engines get added (which isn't true). The docs don't actually say what happens when setEngine() is called multiple times with the same flag. The latest call will replace the previous default, but its not stated.
|
||
for (e = ENGINE_get_first(); e != nullptr; e = ENGINE_get_next(e)) { | ||
const char* id = ENGINE_get_id(e); | ||
const char* name = ENGINE_get_name(e); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
too bad there is no ENGINE_get_flags()
so we can know the flags
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch. SetEngine
did not have ENGINE_set_flag
so that flags was always 0 for dynamic engines. Fixed and add a new flags property.
I thought there was not one engine, but a set of engines available at a time, and which one is used depends on the operation, so a |
@sam-github I'm not sure I follow. If there is a |
I think @mscdex I'm just looking at the code, and going off of how I would expect ossl to work You can see that what it is doing is that for specific crypto algs, named by bits a field, an engine is then set as a default for that specific alg. So you can call setEngine() with X for RSA, and B for ECDH, and now you have two default engines. So, what would https://github.com/nodejs/node/blob/master/deps/openssl/openssl/crypto/engine/engine.h#L679-L685 Using |
At first, I put a name of |
Ping @sam-github @shigeki ... updates on this one? |
CI was fixed to include an engine module. I will make rebase this and resolve conflicts later today. @sam-github @mscdex We are still in a discussion of the name of this API. I do not have any strong opinion on it. I will take it if both of you can agree. |
This adds a new api to show the list of loaded engines of OpenSSL. It also includes the test of dynamic engine for `crypto.setEngine()`.
@shigeki looks like linuxone failed with:
Is this something you expect, or a linuxone specific issue? If the latter, we can get someone to take a look at it. |
@gibfahn Yes, I found that a build errors were occurred on some platforms(e.g. lineone, freebsd, 64bit smartos). They seems to be caused by build dependencies for the test engine was built before libopenssl.a. This did not have this error before, I will investigate it. |
@shigeki Looks like this has been sitting here for quite a while. Sorry. Can you rebase so we can ran the CI? @sam-github Have your comments been addressed? |
Its stalled, we are still not sure what to name the API, I think. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM modulo style nits, although the build issues probably still need to be worked out.
FWIW, crypto.getEngines()
seems like a fine name to me.
#include <openssl/engine.h> | ||
|
||
static const char *engine_id = "node_test_engine"; | ||
static const char *engine_name = "Node Test Engine for OpenSSL"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tiny style nit: star leans left (const char* engine_id = ...
.). You could also writes this as:
static const char engine_id[] = ...;
Saves a pointer indirection.
'sources': ['node_test_engine.c'], | ||
'conditions': [ | ||
['OS=="mac"', { | ||
'include_dirs': ['<(PRODUCT_DIR)/../../deps/openssl/openssl/include',], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Superfluous comma, also in the other include_dirs lists.
engine_lib = 'node_test_engine.dll'; | ||
} else if (common.isAix) { | ||
engine_lib = 'libnode_test_engine.a'; | ||
} else if (process.platform === 'darwin') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
common.isOSX
|
||
const test_engine_id = 'node_test_engine'; | ||
const test_engine_name = 'Node Test Engine for OpenSSL'; | ||
const test_engine = path.join(path.dirname(process.execPath), engine_lib); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tiny style nit: JS code uses camelCase, not snake_case (for the most part.)
|
||
assert.strictEqual(found, 1); | ||
|
||
// check set and get node test engine of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/check/Check/
doc/api/crypto.md
Outdated
|
||
Returns an array of objects with id, name and flags of loaded engines. | ||
The flags value represents an integer of one of or a mix of the crypto | ||
constants described above. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
docs can move around, better to link to crypto.setEngine() explicitly
|
||
if (id == nullptr || name == nullptr) { | ||
ENGINE_free(e); | ||
return env->ThrowError("Invalid Engine: id or name is not found."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if this is the right approach. If there is an engine with an id, but no name, then this will throw, but the user won't know what engine it is that has a name, it could be very frustrating. What about just using ""
for a missing id
or name
, as a replacment for NULL
? Or would that be terrible? I'm not sure how common it is that engines don't have this data, I would have hoped OpenSSL did not allow invalid engines!
fs.accessSync(test_engine); | ||
}, 'node test engine ' + test_engine + ' is not found.'); | ||
|
||
crypto.setEngine(test_engine); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tests should test invalid behaviours, and obscure corner conditions. If it is allowed to call multiple times, the test should do, and assert on the result, IMO. It would be possible that the second call always fails, for example, or someone might believe that both "RSA" engines get added (which isn't true). The docs don't actually say what happens when setEngine() is called multiple times with the same flag. The latest call will replace the previous default, but its not stated.
crypto.getEngines().forEach((e) => { | ||
if (e.id === 'dynamic') | ||
found++; | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
by reimplementing filter()
and ()=>
with forEach()
, this code is 5 lines instead of 1.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You mean
found += crypto.getEngines().filter(e => e.id === 'dynamic').length
?
|
||
crypto.setEngine(test_engine); | ||
|
||
crypto.getEngines().forEach((e) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Its true that many of the Array methods can be implemented in terms of forEach(), so they are equivalent in that sense. Someone has to read this code, and understand that it is filtering. Better to just call filter()
. And if you don't, @Trott or one of the code-and-learn people may come through and modify this to use es6 and builtin library functions - better avoid the churn and do it now, I think.
Should this be closed or is this something that is still worked on? |
Ping @shigeki |
Ping @shigeki again |
Or after looking at this again - I am actually going to close this. There was no response since March. @shigeki if you would like to pursue this further, please reopen. |
This adds a new api to show the list of loaded engines of OpenSSL.
It also includes the test of dynamic engine for
crypto.setEngine()
and fixes missed test coverages as discussed in #10786.A test engine is built from
test/fixtures/openssl_test_engine/
by adding a new target in node.gyp, but fipsld seems to have an error with linking the test engine withlibcrypto.a
so that a dynamic engine test is skipped in FIPS mode.I've already made several CI tests and found that tests on Windows and arm were failed because test engine files were lost in CI environment. They can be solved by including
Release\node_test_engine.dll
in Windows tests andout/Release/libnode_test_engine.so
in arm tests inbinary/binary.tar.gz
andbinary/binary.tar.xz
. I would like ask someone in @nodejs/build to help it.In my local environments, tests in Win and arm are fine.
CC @nodejs/crypto and @jasnell
Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passesAffected core subsystem(s)
crypto