Skip to content

Commit f0b4412

Browse files
panvaaduh95
authored andcommitted
crypto: add KeyObject.prototype.toCryptoKey
PR-URL: #55262 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: Tobias Nießen <tniessen@tnie.de>
1 parent c126543 commit f0b4412

10 files changed

+415
-59
lines changed

doc/api/crypto.md

+19
Original file line numberDiff line numberDiff line change
@@ -2134,6 +2134,24 @@ added: v11.6.0
21342134
For secret keys, this property represents the size of the key in bytes. This
21352135
property is `undefined` for asymmetric keys.
21362136

2137+
### `keyObject.toCryptoKey(algorithm, extractable, keyUsages)`
2138+
2139+
<!-- YAML
2140+
added: REPLACEME
2141+
-->
2142+
2143+
<!--lint disable maximum-line-length remark-lint-->
2144+
2145+
* `algorithm`: {AlgorithmIdentifier|RsaHashedImportParams|EcKeyImportParams|HmacImportParams}
2146+
2147+
<!--lint enable maximum-line-length remark-lint-->
2148+
2149+
* `extractable`: {boolean}
2150+
* `keyUsages`: {string\[]} See [Key usages][].
2151+
* Returns: {CryptoKey}
2152+
2153+
Converts a `KeyObject` instance to a `CryptoKey`.
2154+
21372155
### `keyObject.type`
21382156

21392157
<!-- YAML
@@ -6084,6 +6102,7 @@ See the [list of SSL OP Flags][] for details.
60846102
[FIPS provider from OpenSSL 3]: https://www.openssl.org/docs/man3.0/man7/crypto.html#FIPS-provider
60856103
[HTML 5.2]: https://www.w3.org/TR/html52/changes.html#features-removed
60866104
[JWK]: https://tools.ietf.org/html/rfc7517
6105+
[Key usages]: webcrypto.md#cryptokeyusages
60876106
[NIST SP 800-131A]: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-131Ar2.pdf
60886107
[NIST SP 800-132]: https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-132.pdf
60896108
[NIST SP 800-38D]: https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf

lib/internal/crypto/aes.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ async function aesGenerateKey(algorithm, extractable, keyUsages) {
245245
extractable);
246246
}
247247

248-
async function aesImportKey(
248+
function aesImportKey(
249249
algorithm,
250250
format,
251251
keyData,
@@ -266,6 +266,11 @@ async function aesImportKey(
266266
let keyObject;
267267
let length;
268268
switch (format) {
269+
case 'KeyObject': {
270+
validateKeyLength(keyData.symmetricKeySize * 8);
271+
keyObject = keyData;
272+
break;
273+
}
269274
case 'raw': {
270275
validateKeyLength(keyData.byteLength * 8);
271276
keyObject = createSecretKey(keyData);

lib/internal/crypto/cfrg.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ function cfrgExportKey(key, format) {
197197
key[kKeyObject][kHandle]));
198198
}
199199

200-
async function cfrgImportKey(
200+
function cfrgImportKey(
201201
format,
202202
keyData,
203203
algorithm,
@@ -208,6 +208,11 @@ async function cfrgImportKey(
208208
let keyObject;
209209
const usagesSet = new SafeSet(keyUsages);
210210
switch (format) {
211+
case 'KeyObject': {
212+
verifyAcceptableCfrgKeyUse(name, keyData.type === 'public', usagesSet);
213+
keyObject = keyData;
214+
break;
215+
}
211216
case 'spki': {
212217
verifyAcceptableCfrgKeyUse(name, true, usagesSet);
213218
try {

lib/internal/crypto/ec.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ function ecExportKey(key, format) {
149149
key[kKeyObject][kHandle]));
150150
}
151151

152-
async function ecImportKey(
152+
function ecImportKey(
153153
format,
154154
keyData,
155155
algorithm,
@@ -167,6 +167,11 @@ async function ecImportKey(
167167
let keyObject;
168168
const usagesSet = new SafeSet(keyUsages);
169169
switch (format) {
170+
case 'KeyObject': {
171+
verifyAcceptableEcKeyUse(name, keyData.type === 'public', usagesSet);
172+
keyObject = keyData;
173+
break;
174+
}
170175
case 'spki': {
171176
verifyAcceptableEcKeyUse(name, true, usagesSet);
172177
try {

lib/internal/crypto/keys.js

+159
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const {
66
ObjectDefineProperties,
77
ObjectDefineProperty,
88
ObjectSetPrototypeOf,
9+
SafeSet,
910
Symbol,
1011
SymbolToStringTag,
1112
Uint8Array,
@@ -49,6 +50,8 @@ const {
4950
kKeyObject,
5051
getArrayBufferOrView,
5152
bigIntArrayToUnsignedBigInt,
53+
normalizeAlgorithm,
54+
hasAnyNotIn,
5255
} = require('internal/crypto/util');
5356

5457
const {
@@ -65,6 +68,7 @@ const {
6568
const {
6669
customInspectSymbol: kInspect,
6770
kEnumerableProperty,
71+
lazyDOMException,
6872
} = require('internal/util');
6973

7074
const { inspect } = require('internal/util/inspect');
@@ -148,6 +152,8 @@ const {
148152
},
149153
});
150154

155+
let webidl;
156+
151157
class SecretKeyObject extends KeyObject {
152158
constructor(handle) {
153159
super('secret', handle);
@@ -168,6 +174,51 @@ const {
168174
}
169175
return this[kHandle].export();
170176
}
177+
178+
toCryptoKey(algorithm, extractable, keyUsages) {
179+
webidl ??= require('internal/crypto/webidl');
180+
algorithm = normalizeAlgorithm(webidl.converters.AlgorithmIdentifier(algorithm), 'importKey');
181+
extractable = webidl.converters.boolean(extractable);
182+
keyUsages = webidl.converters['sequence<KeyUsage>'](keyUsages);
183+
184+
let result;
185+
switch (algorithm.name) {
186+
case 'HMAC':
187+
result = require('internal/crypto/mac')
188+
.hmacImportKey('KeyObject', this, algorithm, extractable, keyUsages);
189+
break;
190+
case 'AES-CTR':
191+
// Fall through
192+
case 'AES-CBC':
193+
// Fall through
194+
case 'AES-GCM':
195+
// Fall through
196+
case 'AES-KW':
197+
result = require('internal/crypto/aes')
198+
.aesImportKey(algorithm, 'KeyObject', this, extractable, keyUsages);
199+
break;
200+
case 'HKDF':
201+
// Fall through
202+
case 'PBKDF2':
203+
result = importGenericSecretKey(
204+
algorithm,
205+
'KeyObject',
206+
this,
207+
extractable,
208+
keyUsages);
209+
break;
210+
default:
211+
throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError');
212+
}
213+
214+
if (result.usages.length === 0) {
215+
throw lazyDOMException(
216+
`Usages cannot be empty when importing a ${result.type} key.`,
217+
'SyntaxError');
218+
}
219+
220+
return result;
221+
}
171222
}
172223

173224
const kAsymmetricKeyType = Symbol('kAsymmetricKeyType');
@@ -209,6 +260,51 @@ const {
209260
return {};
210261
}
211262
}
263+
264+
toCryptoKey(algorithm, extractable, keyUsages) {
265+
webidl ??= require('internal/crypto/webidl');
266+
algorithm = normalizeAlgorithm(webidl.converters.AlgorithmIdentifier(algorithm), 'importKey');
267+
extractable = webidl.converters.boolean(extractable);
268+
keyUsages = webidl.converters['sequence<KeyUsage>'](keyUsages);
269+
270+
let result;
271+
switch (algorithm.name) {
272+
case 'RSASSA-PKCS1-v1_5':
273+
// Fall through
274+
case 'RSA-PSS':
275+
// Fall through
276+
case 'RSA-OAEP':
277+
result = require('internal/crypto/rsa')
278+
.rsaImportKey('KeyObject', this, algorithm, extractable, keyUsages);
279+
break;
280+
case 'ECDSA':
281+
// Fall through
282+
case 'ECDH':
283+
result = require('internal/crypto/ec')
284+
.ecImportKey('KeyObject', this, algorithm, extractable, keyUsages);
285+
break;
286+
case 'Ed25519':
287+
// Fall through
288+
case 'Ed448':
289+
// Fall through
290+
case 'X25519':
291+
// Fall through
292+
case 'X448':
293+
result = require('internal/crypto/cfrg')
294+
.cfrgImportKey('KeyObject', this, algorithm, extractable, keyUsages);
295+
break;
296+
default:
297+
throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError');
298+
}
299+
300+
if (result.type === 'private' && result.usages.length === 0) {
301+
throw lazyDOMException(
302+
`Usages cannot be empty when importing a ${result.type} key.`,
303+
'SyntaxError');
304+
}
305+
306+
return result;
307+
}
212308
}
213309

214310
class PublicKeyObject extends AsymmetricKeyObject {
@@ -801,6 +897,68 @@ function isCryptoKey(obj) {
801897
return obj != null && obj[kKeyObject] !== undefined;
802898
}
803899

900+
function importGenericSecretKey(
901+
{ name, length },
902+
format,
903+
keyData,
904+
extractable,
905+
keyUsages) {
906+
const usagesSet = new SafeSet(keyUsages);
907+
if (extractable)
908+
throw lazyDOMException(`${name} keys are not extractable`, 'SyntaxError');
909+
910+
if (hasAnyNotIn(usagesSet, ['deriveKey', 'deriveBits'])) {
911+
throw lazyDOMException(
912+
`Unsupported key usage for a ${name} key`,
913+
'SyntaxError');
914+
}
915+
916+
switch (format) {
917+
case 'KeyObject': {
918+
if (hasAnyNotIn(usagesSet, ['deriveKey', 'deriveBits'])) {
919+
throw lazyDOMException(
920+
`Unsupported key usage for a ${name} key`,
921+
'SyntaxError');
922+
}
923+
924+
const checkLength = keyData.symmetricKeySize * 8;
925+
926+
// The Web Crypto spec allows for key lengths that are not multiples of
927+
// 8. We don't. Our check here is stricter than that defined by the spec
928+
// in that we require that algorithm.length match keyData.length * 8 if
929+
// algorithm.length is specified.
930+
if (length !== undefined && length !== checkLength) {
931+
throw lazyDOMException('Invalid key length', 'DataError');
932+
}
933+
return new InternalCryptoKey(keyData, { name }, keyUsages, false);
934+
}
935+
case 'raw': {
936+
if (hasAnyNotIn(usagesSet, ['deriveKey', 'deriveBits'])) {
937+
throw lazyDOMException(
938+
`Unsupported key usage for a ${name} key`,
939+
'SyntaxError');
940+
}
941+
942+
const checkLength = keyData.byteLength * 8;
943+
944+
// The Web Crypto spec allows for key lengths that are not multiples of
945+
// 8. We don't. Our check here is stricter than that defined by the spec
946+
// in that we require that algorithm.length match keyData.length * 8 if
947+
// algorithm.length is specified.
948+
if (length !== undefined && length !== checkLength) {
949+
throw lazyDOMException('Invalid key length', 'DataError');
950+
}
951+
952+
const keyObject = createSecretKey(keyData);
953+
return new InternalCryptoKey(keyObject, { name }, keyUsages, false);
954+
}
955+
}
956+
957+
throw lazyDOMException(
958+
`Unable to import ${name} key with format ${format}`,
959+
'NotSupportedError');
960+
}
961+
804962
module.exports = {
805963
// Public API.
806964
createSecretKey,
@@ -822,4 +980,5 @@ module.exports = {
822980
PrivateKeyObject,
823981
isKeyObject,
824982
isCryptoKey,
983+
importGenericSecretKey,
825984
};

lib/internal/crypto/mac.js

+19-1
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ function getAlgorithmName(hash) {
8282
}
8383
}
8484

85-
async function hmacImportKey(
85+
function hmacImportKey(
8686
format,
8787
keyData,
8888
algorithm,
@@ -96,6 +96,24 @@ async function hmacImportKey(
9696
}
9797
let keyObject;
9898
switch (format) {
99+
case 'KeyObject': {
100+
const checkLength = keyData.symmetricKeySize * 8;
101+
102+
if (checkLength === 0 || algorithm.length === 0)
103+
throw lazyDOMException('Zero-length key is not supported', 'DataError');
104+
105+
// The Web Crypto spec allows for key lengths that are not multiples of
106+
// 8. We don't. Our check here is stricter than that defined by the spec
107+
// in that we require that algorithm.length match keyData.length * 8 if
108+
// algorithm.length is specified.
109+
if (algorithm.length !== undefined &&
110+
algorithm.length !== checkLength) {
111+
throw lazyDOMException('Invalid key length', 'DataError');
112+
}
113+
114+
keyObject = keyData;
115+
break;
116+
}
99117
case 'raw': {
100118
const checkLength = keyData.byteLength * 8;
101119

lib/internal/crypto/rsa.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ function rsaExportKey(key, format) {
200200
kRsaVariants[key.algorithm.name]));
201201
}
202202

203-
async function rsaImportKey(
203+
function rsaImportKey(
204204
format,
205205
keyData,
206206
algorithm,
@@ -209,6 +209,11 @@ async function rsaImportKey(
209209
const usagesSet = new SafeSet(keyUsages);
210210
let keyObject;
211211
switch (format) {
212+
case 'KeyObject': {
213+
verifyAcceptableRsaKeyUse(algorithm.name, keyData.type === 'public', usagesSet);
214+
keyObject = keyData;
215+
break;
216+
}
212217
case 'spki': {
213218
verifyAcceptableRsaKeyUse(algorithm.name, true, usagesSet);
214219
try {

0 commit comments

Comments
 (0)