Skip to content

Commit ed94bc4

Browse files
authored
src: update crypto.getCipherInfo() to use DictionaryTemplate
Also, have it use a null prototype PR-URL: #60036 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Rafael Gonzaga <[email protected]> Reviewed-By: Filip Skokan <[email protected]>
1 parent 32851f3 commit ed94bc4

File tree

4 files changed

+94
-79
lines changed

4 files changed

+94
-79
lines changed

benchmark/crypto/getcipherinfo.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
'use strict';
2+
3+
const common = require('../common.js');
4+
const { getCiphers, getCipherInfo } = require('node:crypto');
5+
const bench = common.createBenchmark(main, {
6+
cipher: getCiphers(),
7+
n: 50,
8+
});
9+
10+
function main({ n, cipher }) {
11+
bench.start();
12+
for (let i = 0; i < n; i++) {
13+
getCipherInfo(cipher);
14+
}
15+
bench.end(n);
16+
}

lib/internal/crypto/cipher.js

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
'use strict';
22

33
const {
4+
NumberIsInteger,
45
ObjectSetPrototypeOf,
56
ReflectApply,
6-
StringPrototypeToLowerCase,
77
} = primordials;
88

99
const {
@@ -33,7 +33,7 @@ const {
3333

3434
const {
3535
validateEncoding,
36-
validateInt32,
36+
validateUint32,
3737
validateObject,
3838
validateString,
3939
} = require('internal/validators');
@@ -251,31 +251,34 @@ ObjectSetPrototypeOf(Decipheriv.prototype, LazyTransform.prototype);
251251
ObjectSetPrototypeOf(Decipheriv, LazyTransform);
252252
addCipherPrototypeFunctions(Decipheriv);
253253

254-
function getCipherInfo(nameOrNid, options) {
255-
if (typeof nameOrNid !== 'string' && typeof nameOrNid !== 'number') {
256-
throw new ERR_INVALID_ARG_TYPE(
257-
'nameOrNid',
258-
['string', 'number'],
259-
nameOrNid);
254+
const kMinNid = 1;
255+
const kMaxNid = 2_147_483_647;
256+
function getCipherInfo(nameOrNid, options = {}) {
257+
validateObject(options, 'options');
258+
const { keyLength, ivLength } = options;
259+
if (keyLength !== undefined) {
260+
validateUint32(keyLength, 'options.keyLength');
260261
}
261-
if (typeof nameOrNid === 'number')
262-
validateInt32(nameOrNid, 'nameOrNid');
263-
let keyLength, ivLength;
264-
if (options !== undefined) {
265-
validateObject(options, 'options');
266-
({ keyLength, ivLength } = options);
267-
if (keyLength !== undefined)
268-
validateInt32(keyLength, 'options.keyLength');
269-
if (ivLength !== undefined)
270-
validateInt32(ivLength, 'options.ivLength');
262+
if (ivLength !== undefined) {
263+
validateUint32(ivLength, 'options.ivLength');
271264
}
272265

273-
const ret = _getCipherInfo({}, nameOrNid, keyLength, ivLength);
274-
if (ret !== undefined) {
275-
ret.name &&= StringPrototypeToLowerCase(ret.name);
276-
ret.type &&= StringPrototypeToLowerCase(ret.type);
266+
const type = typeof nameOrNid;
267+
268+
if (type === 'string') {
269+
if (nameOrNid.length === 0) return undefined;
270+
return _getCipherInfo(nameOrNid, keyLength, ivLength);
271+
} else if (type === 'number') {
272+
if (!NumberIsInteger(nameOrNid) || nameOrNid < kMinNid || nameOrNid > kMaxNid) {
273+
return undefined;
274+
}
275+
return _getCipherInfo(nameOrNid, keyLength, ivLength);
277276
}
278-
return ret;
277+
278+
throw new ERR_INVALID_ARG_TYPE(
279+
'nameOrNid',
280+
['string', 'number'],
281+
nameOrNid);
279282
}
280283

281284
module.exports = {

src/crypto/crypto_cipher.cc

Lines changed: 51 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -24,51 +24,71 @@ using v8::ArrayBuffer;
2424
using v8::BackingStore;
2525
using v8::BackingStoreInitializationMode;
2626
using v8::Context;
27+
using v8::DictionaryTemplate;
2728
using v8::FunctionCallbackInfo;
2829
using v8::FunctionTemplate;
2930
using v8::HandleScope;
3031
using v8::Int32;
3132
using v8::Isolate;
3233
using v8::Local;
3334
using v8::LocalVector;
35+
using v8::MaybeLocal;
3436
using v8::Object;
3537
using v8::Uint32;
38+
using v8::Undefined;
3639
using v8::Value;
3740

3841
namespace crypto {
3942
namespace {
4043
// Collects and returns information on the given cipher
4144
void GetCipherInfo(const FunctionCallbackInfo<Value>& args) {
4245
Environment* env = Environment::GetCurrent(args);
43-
CHECK(args[0]->IsObject());
44-
Local<Object> info = args[0].As<Object>();
4546

46-
CHECK(args[1]->IsString() || args[1]->IsInt32());
47+
auto tmpl = env->cipherinfo_detail_template();
48+
if (tmpl.IsEmpty()) {
49+
static constexpr std::string_view names[] = {
50+
"mode",
51+
"name",
52+
"nid",
53+
"keyLength",
54+
"blockSize",
55+
"ivLength",
56+
};
57+
tmpl = DictionaryTemplate::New(env->isolate(), names);
58+
env->set_cipherinfo_detail_template(tmpl);
59+
}
60+
MaybeLocal<Value> values[] = {
61+
Undefined(env->isolate()), // mode
62+
Undefined(env->isolate()), // name
63+
Undefined(env->isolate()), // nid
64+
Undefined(env->isolate()), // keyLength
65+
Undefined(env->isolate()), // blockSize
66+
Undefined(env->isolate()), // ivLength
67+
};
68+
69+
CHECK(args[0]->IsString() || args[0]->IsInt32());
4770

4871
const auto cipher = ([&] {
49-
if (args[1]->IsString()) {
50-
Utf8Value name(env->isolate(), args[1]);
72+
if (args[0]->IsString()) {
73+
Utf8Value name(env->isolate(), args[0]);
5174
return Cipher::FromName(*name);
5275
} else {
53-
int nid = args[1].As<Int32>()->Value();
76+
int nid = args[0].As<Int32>()->Value();
5477
return Cipher::FromNid(nid);
5578
}
5679
})();
5780

5881
if (!cipher) return;
5982

60-
int iv_length = cipher.getIvLength();
61-
int key_length = cipher.getKeyLength();
62-
int block_length = cipher.getBlockSize();
63-
auto mode_label = cipher.getModeLabel();
64-
auto name = cipher.getName();
83+
size_t iv_length = cipher.getIvLength();
84+
size_t key_length = cipher.getKeyLength();
6585

6686
// If the testKeyLen and testIvLen arguments are specified,
6787
// then we will make an attempt to see if they are usable for
6888
// the cipher in question, returning undefined if they are not.
6989
// If they are, the info object will be returned with the values
7090
// given.
71-
if (args[2]->IsInt32() || args[3]->IsInt32()) {
91+
if (args[1]->IsUint32() || args[2]->IsUint32()) {
7292
// Test and input IV or key length to determine if it's acceptable.
7393
// If it is, then the getCipherInfo will succeed with the given
7494
// values.
@@ -77,16 +97,16 @@ void GetCipherInfo(const FunctionCallbackInfo<Value>& args) {
7797
return;
7898
}
7999

80-
if (args[2]->IsInt32()) {
81-
int check_len = args[2].As<Int32>()->Value();
100+
if (args[1]->IsUint32()) {
101+
size_t check_len = args[1].As<Uint32>()->Value();
82102
if (!ctx.setKeyLength(check_len)) {
83103
return;
84104
}
85105
key_length = check_len;
86106
}
87107

88-
if (args[3]->IsInt32()) {
89-
int check_len = args[3].As<Int32>()->Value();
108+
if (args[2]->IsUint32()) {
109+
size_t check_len = args[2].As<Uint32>()->Value();
90110
// For CCM modes, the IV may be between 7 and 13 bytes.
91111
// For GCM and OCB modes, we'll check by attempting to
92112
// set the value. For everything else, just check that
@@ -106,55 +126,30 @@ void GetCipherInfo(const FunctionCallbackInfo<Value>& args) {
106126
}
107127
}
108128

109-
if (mode_label.length() &&
110-
info->Set(env->context(),
111-
FIXED_ONE_BYTE_STRING(env->isolate(), "mode"),
112-
OneByteString(
113-
env->isolate(), mode_label.data(), mode_label.length()))
114-
.IsNothing()) {
115-
return;
116-
}
117-
118-
if (info->Set(env->context(),
119-
env->name_string(),
120-
OneByteString(env->isolate(), name))
121-
.IsNothing()) {
122-
return;
123-
}
129+
// Lowercase the name in place before we create the JS string from it.
130+
std::string name_str(cipher.getName());
131+
name_str = ToLower(name_str);
124132

125-
if (info->Set(env->context(),
126-
FIXED_ONE_BYTE_STRING(env->isolate(), "nid"),
127-
Int32::New(env->isolate(), cipher.getNid()))
128-
.IsNothing()) {
129-
return;
130-
}
133+
values[0] = ToV8Value(env->context(), cipher.getModeLabel(), env->isolate());
134+
values[1] = ToV8Value(env->context(), name_str, env->isolate());
135+
values[2] = Uint32::NewFromUnsigned(env->isolate(), cipher.getNid());
136+
values[3] = Uint32::NewFromUnsigned(env->isolate(), key_length);
131137

132138
// Stream ciphers do not have a meaningful block size
133-
if (!cipher.isStreamMode() &&
134-
info->Set(env->context(),
135-
FIXED_ONE_BYTE_STRING(env->isolate(), "blockSize"),
136-
Int32::New(env->isolate(), block_length))
137-
.IsNothing()) {
138-
return;
139+
if (!cipher.isStreamMode()) {
140+
values[4] = Uint32::NewFromUnsigned(env->isolate(), cipher.getBlockSize());
139141
}
140142

141143
// Ciphers that do not use an IV shouldn't report a length
142-
if (iv_length != 0 &&
143-
info->Set(
144-
env->context(),
145-
FIXED_ONE_BYTE_STRING(env->isolate(), "ivLength"),
146-
Int32::New(env->isolate(), iv_length)).IsNothing()) {
147-
return;
144+
if (iv_length != 0) {
145+
values[5] = Uint32::NewFromUnsigned(env->isolate(), iv_length);
148146
}
149147

150-
if (info->Set(
151-
env->context(),
152-
FIXED_ONE_BYTE_STRING(env->isolate(), "keyLength"),
153-
Int32::New(env->isolate(), key_length)).IsNothing()) {
154-
return;
148+
Local<Object> info;
149+
if (NewDictionaryInstanceNullProto(env->context(), tmpl, values)
150+
.ToLocal(&info)) {
151+
args.GetReturnValue().Set(info);
155152
}
156-
157-
args.GetReturnValue().Set(info);
158153
}
159154
} // namespace
160155

src/env_properties.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,7 @@
386386
V(blocklist_constructor_template, v8::FunctionTemplate) \
387387
V(caa_record_template, v8::DictionaryTemplate) \
388388
V(callsite_template, v8::DictionaryTemplate) \
389+
V(cipherinfo_detail_template, v8::DictionaryTemplate) \
389390
V(cipherinfo_template, v8::DictionaryTemplate) \
390391
V(cname_record_template, v8::DictionaryTemplate) \
391392
V(contextify_global_template, v8::ObjectTemplate) \

0 commit comments

Comments
 (0)