Skip to content

Commit 6c38ebf

Browse files
committed
crypto: unify asymmetric key import through KeyObjectHandle::Init
Consolidate all asymmetric key import paths (DER/PEM, JWK, raw) into a single KeyObjectHandle::Init() entry point with a uniform signature. Remove the per-type C++ init methods (InitECRaw, InitEDRaw, InitPqcRaw, InitJwk, InitECPrivateRaw) and their JS-side callers, replacing them with shared C++ and JS helpers. createPublicKey, createPrivateKey, sign, verify, and other operations that accept key material now handle JWK and raw formats directly in C++, removing redundant JS-to-C++ key handle round-trips. Signed-off-by: Filip Skokan <panva.ip@gmail.com> PR-URL: nodejs#62499 Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent 9ecc0f9 commit 6c38ebf

25 files changed

Lines changed: 1023 additions & 1121 deletions

lib/internal/crypto/aes.js

Lines changed: 8 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ const {
99
AESCipherJob,
1010
KeyObjectHandle,
1111
kCryptoJobAsync,
12+
kKeyFormatJWK,
13+
kKeyTypeSecret,
1214
kKeyVariantAES_CTR_128,
1315
kKeyVariantAES_CBC_128,
1416
kKeyVariantAES_GCM_128,
@@ -29,7 +31,6 @@ const {
2931
const {
3032
hasAnyNotIn,
3133
jobPromise,
32-
validateKeyOps,
3334
kHandle,
3435
kKeyObject,
3536
} = require('internal/crypto/util');
@@ -46,6 +47,10 @@ const {
4647
kAlgorithm,
4748
} = require('internal/crypto/keys');
4849

50+
const {
51+
validateJwk,
52+
} = require('internal/crypto/webcrypto_util');
53+
4954
const {
5055
generateKey: _generateKey,
5156
} = require('internal/crypto/keygen');
@@ -244,31 +249,11 @@ function aesImportKey(
244249
break;
245250
}
246251
case 'jwk': {
247-
if (!keyData.kty)
248-
throw lazyDOMException('Invalid keyData', 'DataError');
249-
250-
if (keyData.kty !== 'oct')
251-
throw lazyDOMException('Invalid JWK "kty" Parameter', 'DataError');
252-
253-
if (usagesSet.size > 0 &&
254-
keyData.use !== undefined &&
255-
keyData.use !== 'enc') {
256-
throw lazyDOMException('Invalid JWK "use" Parameter', 'DataError');
257-
}
258-
259-
validateKeyOps(keyData.key_ops, usagesSet);
260-
261-
if (keyData.ext !== undefined &&
262-
keyData.ext === false &&
263-
extractable === true) {
264-
throw lazyDOMException(
265-
'JWK "ext" Parameter and extractable mismatch',
266-
'DataError');
267-
}
252+
validateJwk(keyData, 'oct', extractable, usagesSet, 'enc');
268253

269254
const handle = new KeyObjectHandle();
270255
try {
271-
handle.initJwk(keyData);
256+
handle.init(kKeyTypeSecret, keyData, kKeyFormatJWK, null, null);
272257
} catch (err) {
273258
throw lazyDOMException(
274259
'Invalid keyData', { name: 'DataError', cause: err });

lib/internal/crypto/cfrg.js

Lines changed: 54 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -3,32 +3,27 @@
33
const {
44
SafeSet,
55
StringPrototypeToLowerCase,
6+
TypedArrayPrototypeGetBuffer,
67
} = primordials;
78

89
const { Buffer } = require('buffer');
910

1011
const {
11-
ECKeyExportJob,
12-
KeyObjectHandle,
1312
SignJob,
1413
kCryptoJobAsync,
15-
kKeyTypePrivate,
16-
kKeyTypePublic,
14+
kKeyFormatDER,
15+
kKeyFormatRawPublic,
1716
kSignJobModeSign,
1817
kSignJobModeVerify,
18+
kWebCryptoKeyFormatPKCS8,
19+
kWebCryptoKeyFormatRaw,
20+
kWebCryptoKeyFormatSPKI,
1921
} = internalBinding('crypto');
2022

21-
const {
22-
codes: {
23-
ERR_CRYPTO_INVALID_JWK,
24-
},
25-
} = require('internal/errors');
26-
2723
const {
2824
getUsagesUnion,
2925
hasAnyNotIn,
3026
jobPromise,
31-
validateKeyOps,
3227
kHandle,
3328
kKeyObject,
3429
} = require('internal/crypto/util');
@@ -44,13 +39,16 @@ const {
4439

4540
const {
4641
InternalCryptoKey,
47-
PrivateKeyObject,
48-
PublicKeyObject,
49-
createPrivateKey,
50-
createPublicKey,
5142
kKeyType,
5243
} = require('internal/crypto/keys');
5344

45+
const {
46+
importDerKey,
47+
importJwkKey,
48+
importRawKey,
49+
validateJwk,
50+
} = require('internal/crypto/webcrypto_util');
51+
5452
const generateKeyPair = promisify(_generateKeyPair);
5553

5654
function verifyAcceptableCfrgKeyUse(name, isPublic, usages) {
@@ -77,39 +75,6 @@ function verifyAcceptableCfrgKeyUse(name, isPublic, usages) {
7775
}
7876
}
7977

80-
function createCFRGRawKey(name, keyData, isPublic) {
81-
const handle = new KeyObjectHandle();
82-
83-
switch (name) {
84-
case 'Ed25519':
85-
case 'X25519':
86-
if (keyData.byteLength !== 32) {
87-
throw lazyDOMException(
88-
`${name} raw keys must be exactly 32-bytes`, 'DataError');
89-
}
90-
break;
91-
case 'Ed448':
92-
if (keyData.byteLength !== 57) {
93-
throw lazyDOMException(
94-
`${name} raw keys must be exactly 57-bytes`, 'DataError');
95-
}
96-
break;
97-
case 'X448':
98-
if (keyData.byteLength !== 56) {
99-
throw lazyDOMException(
100-
`${name} raw keys must be exactly 56-bytes`, 'DataError');
101-
}
102-
break;
103-
}
104-
105-
const keyType = isPublic ? kKeyTypePublic : kKeyTypePrivate;
106-
if (!handle.initEDRaw(name, keyData, keyType)) {
107-
throw lazyDOMException('Invalid keyData', 'DataError');
108-
}
109-
110-
return isPublic ? new PublicKeyObject(handle) : new PrivateKeyObject(handle);
111-
}
112-
11378
async function cfrgGenerateKey(algorithm, extractable, keyUsages) {
11479
const { name } = algorithm;
11580

@@ -171,7 +136,7 @@ async function cfrgGenerateKey(algorithm, extractable, keyUsages) {
171136
case 'X25519':
172137
// Fall through
173138
case 'X448':
174-
publicUsages = new SafeSet();
139+
publicUsages = [];
175140
privateUsages = getUsagesUnion(usageSet, 'deriveKey', 'deriveBits');
176141
break;
177142
}
@@ -196,10 +161,29 @@ async function cfrgGenerateKey(algorithm, extractable, keyUsages) {
196161
}
197162

198163
function cfrgExportKey(key, format) {
199-
return jobPromise(() => new ECKeyExportJob(
200-
kCryptoJobAsync,
201-
format,
202-
key[kKeyObject][kHandle]));
164+
try {
165+
switch (format) {
166+
case kWebCryptoKeyFormatRaw: {
167+
const handle = key[kKeyObject][kHandle];
168+
return TypedArrayPrototypeGetBuffer(
169+
key[kKeyType] === 'private' ? handle.rawPrivateKey() : handle.rawPublicKey());
170+
}
171+
case kWebCryptoKeyFormatSPKI: {
172+
return TypedArrayPrototypeGetBuffer(
173+
key[kKeyObject][kHandle].export(kKeyFormatDER, kWebCryptoKeyFormatSPKI));
174+
}
175+
case kWebCryptoKeyFormatPKCS8: {
176+
return TypedArrayPrototypeGetBuffer(
177+
key[kKeyObject][kHandle].export(kKeyFormatDER, kWebCryptoKeyFormatPKCS8, null, null));
178+
}
179+
default:
180+
return undefined;
181+
}
182+
} catch (err) {
183+
throw lazyDOMException(
184+
'The operation failed for an operation-specific reason',
185+
{ name: 'OperationError', cause: err });
186+
}
203187
}
204188

205189
function cfrgImportKey(
@@ -220,113 +204,42 @@ function cfrgImportKey(
220204
}
221205
case 'spki': {
222206
verifyAcceptableCfrgKeyUse(name, true, usagesSet);
223-
try {
224-
keyObject = createPublicKey({
225-
key: keyData,
226-
format: 'der',
227-
type: 'spki',
228-
});
229-
} catch (err) {
230-
throw lazyDOMException(
231-
'Invalid keyData', { name: 'DataError', cause: err });
232-
}
207+
keyObject = importDerKey(keyData, true);
233208
break;
234209
}
235210
case 'pkcs8': {
236211
verifyAcceptableCfrgKeyUse(name, false, usagesSet);
237-
try {
238-
keyObject = createPrivateKey({
239-
key: keyData,
240-
format: 'der',
241-
type: 'pkcs8',
242-
});
243-
} catch (err) {
244-
throw lazyDOMException(
245-
'Invalid keyData', { name: 'DataError', cause: err });
246-
}
212+
keyObject = importDerKey(keyData, false);
247213
break;
248214
}
249215
case 'jwk': {
250-
if (!keyData.kty)
251-
throw lazyDOMException('Invalid keyData', 'DataError');
252-
if (keyData.kty !== 'OKP')
253-
throw lazyDOMException('Invalid JWK "kty" Parameter', 'DataError');
216+
const expectedUse = (name === 'X25519' || name === 'X448') ? 'enc' : 'sig';
217+
validateJwk(keyData, 'OKP', extractable, usagesSet, expectedUse);
218+
254219
if (keyData.crv !== name)
255220
throw lazyDOMException(
256221
'JWK "crv" Parameter and algorithm name mismatch', 'DataError');
257-
const isPublic = keyData.d === undefined;
258-
259-
if (usagesSet.size > 0 && keyData.use !== undefined) {
260-
let checkUse;
261-
switch (name) {
262-
case 'Ed25519':
263-
// Fall through
264-
case 'Ed448':
265-
checkUse = 'sig';
266-
break;
267-
case 'X25519':
268-
// Fall through
269-
case 'X448':
270-
checkUse = 'enc';
271-
break;
272-
}
273-
if (keyData.use !== checkUse)
274-
throw lazyDOMException('Invalid JWK "use" Parameter', 'DataError');
275-
}
276-
277-
validateKeyOps(keyData.key_ops, usagesSet);
278-
279-
if (keyData.ext !== undefined &&
280-
keyData.ext === false &&
281-
extractable === true) {
282-
throw lazyDOMException(
283-
'JWK "ext" Parameter and extractable mismatch',
284-
'DataError');
285-
}
286222

287223
if (keyData.alg !== undefined && (name === 'Ed25519' || name === 'Ed448')) {
288-
if (keyData.alg !== name && keyData.alg !== 'EdDSA') {
224+
if (keyData.alg !== name && keyData.alg !== 'EdDSA')
289225
throw lazyDOMException(
290-
'JWK "alg" does not match the requested algorithm',
291-
'DataError');
292-
}
226+
'JWK "alg" does not match the requested algorithm', 'DataError');
293227
}
294228

295-
if (!isPublic && typeof keyData.x !== 'string') {
296-
throw lazyDOMException('Invalid JWK', 'DataError');
297-
}
298-
299-
verifyAcceptableCfrgKeyUse(
300-
name,
301-
isPublic,
302-
usagesSet);
303-
304-
try {
305-
const publicKeyObject = createCFRGRawKey(
306-
name,
307-
Buffer.from(keyData.x, 'base64'),
308-
true);
309-
310-
if (isPublic) {
311-
keyObject = publicKeyObject;
312-
} else {
313-
keyObject = createCFRGRawKey(
314-
name,
315-
Buffer.from(keyData.d, 'base64'),
316-
false);
229+
const isPublic = keyData.d === undefined;
230+
verifyAcceptableCfrgKeyUse(name, isPublic, usagesSet);
231+
keyObject = importJwkKey(isPublic, keyData);
317232

318-
if (!createPublicKey(keyObject).equals(publicKeyObject)) {
319-
throw new ERR_CRYPTO_INVALID_JWK();
320-
}
321-
}
322-
} catch (err) {
323-
throw lazyDOMException('Invalid keyData', { name: 'DataError', cause: err });
233+
if (!isPublic) {
234+
const publicKey = Buffer.from(keyData.x, 'base64url');
235+
if (!Buffer.from(keyObject[kHandle].rawPublicKey()).equals(publicKey))
236+
throw lazyDOMException('Invalid keyData', 'DataError');
324237
}
325238
break;
326239
}
327240
case 'raw': {
328241
verifyAcceptableCfrgKeyUse(name, true, usagesSet);
329-
keyObject = createCFRGRawKey(name, keyData, true);
242+
keyObject = importRawKey(true, keyData, kKeyFormatRawPublic, name);
330243
break;
331244
}
332245
default:
@@ -340,7 +253,7 @@ function cfrgImportKey(
340253
return new InternalCryptoKey(
341254
keyObject,
342255
{ name },
343-
usagesSet,
256+
keyUsages,
344257
extractable);
345258
}
346259

@@ -358,6 +271,7 @@ async function eddsaSignVerify(key, data, algorithm, signature) {
358271
undefined,
359272
undefined,
360273
undefined,
274+
undefined,
361275
data,
362276
undefined,
363277
undefined,

0 commit comments

Comments
 (0)