mirror of
https://github.com/nodejs/node.git
synced 2024-11-21 10:59:27 +00:00
crypto: add getCipherInfo method
Simple method for retrieving basic information about a cipher (such as block length, expected or default iv length, key length, etc) Signed-off-by: James M Snell <jasnell@gmail.com> Fixes: https://github.com/nodejs/node/issues/22304 PR-URL: https://github.com/nodejs/node/pull/35368 Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
This commit is contained in:
parent
adf8f3d1fe
commit
095be6a01f
@ -2424,6 +2424,35 @@ const ciphers = crypto.getCiphers();
|
||||
console.log(ciphers); // ['aes-128-cbc', 'aes-128-ccm', ...]
|
||||
```
|
||||
|
||||
### `crypto.getCipherInfo(nameOrNid[, options])`
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* `nameOrNid`: {string|number} The name or nid of the cipher to query.
|
||||
* `options`: {Object}
|
||||
* `keyLength`: {number} A test key length.
|
||||
* `ivLength`: {number} A test IV length.
|
||||
* Returns: {Object}
|
||||
* `name` {string} The name of the cipher
|
||||
* `nid` {number} The nid of the cipher
|
||||
* `blockSize` {number} The block size of the cipher in bytes. This property
|
||||
is omitted when `mode` is `'stream'`.
|
||||
* `ivLength` {number} The expected or default initialization vector length in
|
||||
bytes. This property is omitted if the cipher does not use an initialization
|
||||
vector.
|
||||
* `keyLength` {number} The expected or default key length in bytes.
|
||||
* `mode` {string} The cipher mode. One of `'cbc'`, `'ccm'`, `'cfb'`, `'ctr'`,
|
||||
`'ecb'`, `'gcm'`, `'ocb'`, `'ofb'`, `'stream'`, `'wrap'`, `'xts'`.
|
||||
|
||||
Returns information about a given cipher.
|
||||
|
||||
Some ciphers accept variable length keys and initialization vectors. By default,
|
||||
the `crypto.getCipherInfo()` method will return the default values for these
|
||||
ciphers. To test if a given key length or iv length is acceptable for given
|
||||
cipher, use the `keyLenth` and `ivLenth` options. If the given values are
|
||||
unacceptable, `undefined` will be returned.
|
||||
|
||||
### `crypto.getCurves()`
|
||||
<!-- YAML
|
||||
added: v2.3.0
|
||||
|
@ -93,7 +93,8 @@ const {
|
||||
privateDecrypt,
|
||||
privateEncrypt,
|
||||
publicDecrypt,
|
||||
publicEncrypt
|
||||
publicEncrypt,
|
||||
getCipherInfo,
|
||||
} = require('internal/crypto/cipher');
|
||||
const {
|
||||
Sign,
|
||||
@ -178,6 +179,7 @@ module.exports = {
|
||||
createVerify,
|
||||
diffieHellman,
|
||||
getCiphers,
|
||||
getCipherInfo,
|
||||
getCurves,
|
||||
getDiffieHellman: createDiffieHellmanGroup,
|
||||
getHashes,
|
||||
|
@ -10,6 +10,7 @@ const {
|
||||
privateEncrypt: _privateEncrypt,
|
||||
publicDecrypt: _publicDecrypt,
|
||||
publicEncrypt: _publicEncrypt,
|
||||
getCipherInfo: _getCipherInfo,
|
||||
} = internalBinding('crypto');
|
||||
|
||||
const {
|
||||
@ -29,6 +30,8 @@ const {
|
||||
|
||||
const {
|
||||
validateEncoding,
|
||||
validateInt32,
|
||||
validateObject,
|
||||
validateString,
|
||||
} = require('internal/validators');
|
||||
|
||||
@ -291,6 +294,33 @@ ObjectSetPrototypeOf(Decipheriv.prototype, LazyTransform.prototype);
|
||||
ObjectSetPrototypeOf(Decipheriv, LazyTransform);
|
||||
addCipherPrototypeFunctions(Decipheriv);
|
||||
|
||||
function getCipherInfo(nameOrNid, options) {
|
||||
if (typeof nameOrNid !== 'string' && typeof nameOrNid !== 'number') {
|
||||
throw new ERR_INVALID_ARG_TYPE(
|
||||
'nameOrNid',
|
||||
['string', 'number'],
|
||||
nameOrNid);
|
||||
}
|
||||
if (typeof nameOrNid === 'number')
|
||||
validateInt32(nameOrNid, 'nameOrNid');
|
||||
let keyLength, ivLength;
|
||||
if (options !== undefined) {
|
||||
validateObject(options, 'options');
|
||||
({ keyLength, ivLength } = options);
|
||||
if (keyLength !== undefined)
|
||||
validateInt32(keyLength, 'options.keyLength');
|
||||
if (ivLength !== undefined)
|
||||
validateInt32(ivLength, 'options.ivLength');
|
||||
}
|
||||
|
||||
const ret = _getCipherInfo({}, nameOrNid, keyLength, ivLength);
|
||||
if (ret !== undefined) {
|
||||
if (ret.name) ret.name = ret.name.toLowerCase();
|
||||
if (ret.type) ret.type = ret.type.toLowerCase();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
Cipher,
|
||||
Cipheriv,
|
||||
@ -300,4 +330,5 @@ module.exports = {
|
||||
privateEncrypt,
|
||||
publicDecrypt,
|
||||
publicEncrypt,
|
||||
getCipherInfo,
|
||||
};
|
||||
|
@ -47,6 +47,145 @@ bool IsSupportedAuthenticatedMode(const EVP_CIPHER_CTX* ctx) {
|
||||
bool IsValidGCMTagLength(unsigned int tag_len) {
|
||||
return tag_len == 4 || tag_len == 8 || (tag_len >= 12 && tag_len <= 16);
|
||||
}
|
||||
|
||||
// Collects and returns information on the given cipher
|
||||
void GetCipherInfo(const FunctionCallbackInfo<Value>& args) {
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
CHECK(args[0]->IsObject());
|
||||
Local<Object> info = args[0].As<Object>();
|
||||
|
||||
CHECK(args[1]->IsString() || args[1]->IsInt32());
|
||||
|
||||
const EVP_CIPHER* cipher;
|
||||
if (args[1]->IsString()) {
|
||||
Utf8Value name(env->isolate(), args[1]);
|
||||
cipher = EVP_get_cipherbyname(*name);
|
||||
} else {
|
||||
int nid = args[1].As<Int32>()->Value();
|
||||
cipher = EVP_get_cipherbyname(OBJ_nid2sn(nid));
|
||||
}
|
||||
|
||||
if (cipher == nullptr)
|
||||
return;
|
||||
|
||||
int mode = EVP_CIPHER_mode(cipher);
|
||||
int iv_length = EVP_CIPHER_iv_length(cipher);
|
||||
int key_length = EVP_CIPHER_key_length(cipher);
|
||||
int block_length = EVP_CIPHER_block_size(cipher);
|
||||
const char* mode_label = nullptr;
|
||||
switch (mode) {
|
||||
case EVP_CIPH_CBC_MODE: mode_label = "cbc"; break;
|
||||
case EVP_CIPH_CCM_MODE: mode_label = "ccm"; break;
|
||||
case EVP_CIPH_CFB_MODE: mode_label = "cfb"; break;
|
||||
case EVP_CIPH_CTR_MODE: mode_label = "ctr"; break;
|
||||
case EVP_CIPH_ECB_MODE: mode_label = "ecb"; break;
|
||||
case EVP_CIPH_GCM_MODE: mode_label = "gcm"; break;
|
||||
case EVP_CIPH_OCB_MODE: mode_label = "ocb"; break;
|
||||
case EVP_CIPH_OFB_MODE: mode_label = "ofb"; break;
|
||||
case EVP_CIPH_WRAP_MODE: mode_label = "wrap"; break;
|
||||
case EVP_CIPH_XTS_MODE: mode_label = "xts"; break;
|
||||
case EVP_CIPH_STREAM_CIPHER: mode_label = "stream"; break;
|
||||
}
|
||||
|
||||
// If the testKeyLen and testIvLen arguments are specified,
|
||||
// then we will make an attempt to see if they are usable for
|
||||
// the cipher in question, returning undefined if they are not.
|
||||
// If they are, the info object will be returned with the values
|
||||
// given.
|
||||
if (args[2]->IsInt32() || args[3]->IsInt32()) {
|
||||
// Test and input IV or key length to determine if it's acceptable.
|
||||
// If it is, then the getCipherInfo will succeed with the given
|
||||
// values.
|
||||
CipherCtxPointer ctx(EVP_CIPHER_CTX_new());
|
||||
if (!EVP_CipherInit_ex(ctx.get(), cipher, nullptr, nullptr, nullptr, 1))
|
||||
return;
|
||||
|
||||
if (args[2]->IsInt32()) {
|
||||
int check_len = args[2].As<Int32>()->Value();
|
||||
if (!EVP_CIPHER_CTX_set_key_length(ctx.get(), check_len))
|
||||
return;
|
||||
key_length = check_len;
|
||||
}
|
||||
|
||||
if (args[3]->IsInt32()) {
|
||||
int check_len = args[3].As<Int32>()->Value();
|
||||
// For CCM modes, the IV may be between 7 and 13 bytes.
|
||||
// For GCM and OCB modes, we'll check by attempting to
|
||||
// set the value. For everything else, just check that
|
||||
// check_len == iv_length.
|
||||
switch (mode) {
|
||||
case EVP_CIPH_CCM_MODE:
|
||||
if (check_len < 7 || check_len > 13)
|
||||
return;
|
||||
break;
|
||||
case EVP_CIPH_GCM_MODE:
|
||||
// Fall through
|
||||
case EVP_CIPH_OCB_MODE:
|
||||
if (!EVP_CIPHER_CTX_ctrl(
|
||||
ctx.get(),
|
||||
EVP_CTRL_AEAD_SET_IVLEN,
|
||||
check_len,
|
||||
nullptr)) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (check_len != iv_length)
|
||||
return;
|
||||
}
|
||||
iv_length = check_len;
|
||||
}
|
||||
}
|
||||
|
||||
if (mode_label != nullptr &&
|
||||
info->Set(
|
||||
env->context(),
|
||||
FIXED_ONE_BYTE_STRING(env->isolate(), "mode"),
|
||||
OneByteString(env->isolate(), mode_label)).IsNothing()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (info->Set(
|
||||
env->context(),
|
||||
env->name_string(),
|
||||
OneByteString(env->isolate(), EVP_CIPHER_name(cipher))).IsNothing()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (info->Set(
|
||||
env->context(),
|
||||
FIXED_ONE_BYTE_STRING(env->isolate(), "nid"),
|
||||
Int32::New(env->isolate(), EVP_CIPHER_nid(cipher))).IsNothing()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Stream ciphers do not have a meaningful block size
|
||||
if (mode != EVP_CIPH_STREAM_CIPHER &&
|
||||
info->Set(
|
||||
env->context(),
|
||||
FIXED_ONE_BYTE_STRING(env->isolate(), "blockSize"),
|
||||
Int32::New(env->isolate(), block_length)).IsNothing()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ciphers that do not use an IV shouldn't report a length
|
||||
if (iv_length != 0 &&
|
||||
info->Set(
|
||||
env->context(),
|
||||
FIXED_ONE_BYTE_STRING(env->isolate(), "ivLength"),
|
||||
Int32::New(env->isolate(), iv_length)).IsNothing()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (info->Set(
|
||||
env->context(),
|
||||
FIXED_ONE_BYTE_STRING(env->isolate(), "keyLength"),
|
||||
Int32::New(env->isolate(), key_length)).IsNothing()) {
|
||||
return;
|
||||
}
|
||||
|
||||
args.GetReturnValue().Set(info);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void CipherBase::GetSSLCiphers(const FunctionCallbackInfo<Value>& args) {
|
||||
@ -151,6 +290,8 @@ void CipherBase::Initialize(Environment* env, Local<Object> target) {
|
||||
EVP_PKEY_verify_recover_init,
|
||||
EVP_PKEY_verify_recover>);
|
||||
|
||||
env->SetMethodNoSideEffect(target, "getCipherInfo", GetCipherInfo);
|
||||
|
||||
NODE_DEFINE_CONSTANT(target, kWebCryptoCipherEncrypt);
|
||||
NODE_DEFINE_CONSTANT(target, kWebCryptoCipherDecrypt);
|
||||
}
|
||||
|
70
test/parallel/test-crypto-getcipherinfo.js
Normal file
70
test/parallel/test-crypto-getcipherinfo.js
Normal file
@ -0,0 +1,70 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
if (!common.hasCrypto)
|
||||
common.skip('missing crypto');
|
||||
|
||||
const {
|
||||
getCiphers,
|
||||
getCipherInfo
|
||||
} = require('crypto');
|
||||
|
||||
const assert = require('assert');
|
||||
|
||||
const ciphers = getCiphers();
|
||||
|
||||
assert.strictEqual(getCipherInfo(-1), undefined);
|
||||
assert.strictEqual(getCipherInfo('cipher that does not exist'), undefined);
|
||||
|
||||
ciphers.forEach((cipher) => {
|
||||
const info = getCipherInfo(cipher);
|
||||
assert(info);
|
||||
const info2 = getCipherInfo(info.nid);
|
||||
assert.deepStrictEqual(info, info2);
|
||||
});
|
||||
|
||||
const info = getCipherInfo('aes-128-cbc');
|
||||
assert.strictEqual(info.name, 'aes-128-cbc');
|
||||
assert.strictEqual(info.nid, 419);
|
||||
assert.strictEqual(info.blockSize, 16);
|
||||
assert.strictEqual(info.ivLength, 16);
|
||||
assert.strictEqual(info.keyLength, 16);
|
||||
assert.strictEqual(info.mode, 'cbc');
|
||||
|
||||
[null, undefined, [], {}].forEach((arg) => {
|
||||
assert.throws(() => getCipherInfo(arg), {
|
||||
code: 'ERR_INVALID_ARG_TYPE'
|
||||
});
|
||||
});
|
||||
|
||||
[null, '', 1, true].forEach((options) => {
|
||||
assert.throws(
|
||||
() => getCipherInfo('aes-192-cbc', options), {
|
||||
code: 'ERR_INVALID_ARG_TYPE'
|
||||
});
|
||||
});
|
||||
|
||||
[null, '', {}, [], true].forEach((len) => {
|
||||
assert.throws(
|
||||
() => getCipherInfo('aes-192-cbc', { keyLength: len }), {
|
||||
code: 'ERR_INVALID_ARG_TYPE'
|
||||
});
|
||||
assert.throws(
|
||||
() => getCipherInfo('aes-192-cbc', { ivLength: len }), {
|
||||
code: 'ERR_INVALID_ARG_TYPE'
|
||||
});
|
||||
});
|
||||
|
||||
assert(!getCipherInfo('aes-128-cbc', { keyLength: 12 }));
|
||||
assert(getCipherInfo('aes-128-cbc', { keyLength: 16 }));
|
||||
assert(!getCipherInfo('aes-128-cbc', { ivLength: 12 }));
|
||||
assert(getCipherInfo('aes-128-cbc', { ivLength: 16 }));
|
||||
|
||||
assert(!getCipherInfo('aes-128-ccm', { ivLength: 1 }));
|
||||
assert(!getCipherInfo('aes-128-ccm', { ivLength: 14 }));
|
||||
for (let n = 7; n <= 13; n++)
|
||||
assert(getCipherInfo('aes-128-ccm', { ivLength: n }));
|
||||
|
||||
assert(!getCipherInfo('aes-128-ocb', { ivLength: 16 }));
|
||||
for (let n = 1; n < 16; n++)
|
||||
assert(getCipherInfo('aes-128-ocb', { ivLength: n }));
|
Loading…
Reference in New Issue
Block a user