29
29
import { Buffer } from 'node-internal:internal_buffer' ;
30
30
31
31
import {
32
- KeyData ,
33
- KeyObjectType ,
34
- KeyExportResult ,
35
- SecretKeyType ,
36
- SecretKeyExportOptions ,
37
- PublicKeyExportOptions ,
38
- PrivateKeyExportOptions ,
39
- ExportOptions ,
40
- AsymmetricKeyDetails ,
41
- AsymmetricKeyType ,
42
- CreateAsymmetricKeyOptions ,
43
- GenerateKeyOptions ,
44
- GenerateKeyPairOptions ,
45
- InnerExportOptions ,
46
- // TODO(soon): Uncomment these once createPrivateKey/createPublicKey are implemented.
47
- // JsonWebKey,
48
- // InnerCreateAsymmetricKeyOptions,
32
+ type KeyData ,
33
+ type KeyObjectType ,
34
+ type KeyExportResult ,
35
+ type SecretKeyType ,
36
+ type SecretKeyExportOptions ,
37
+ type PublicKeyExportOptions ,
38
+ type PrivateKeyExportOptions ,
39
+ type ExportOptions ,
40
+ type AsymmetricKeyDetails ,
41
+ type AsymmetricKeyType ,
42
+ type CreateAsymmetricKeyOptions ,
43
+ type GenerateKeyOptions ,
44
+ type GenerateKeyPairOptions ,
45
+ type InnerExportOptions ,
46
+ type InnerCreateAsymmetricKeyOptions ,
47
+ // type AsymmetricKeyFormat,
48
+ // type PrivateKeyEncoding,
49
+ // type PublicKeyEncoding,
50
+ type JsonWebKey ,
49
51
default as cryptoImpl ,
50
52
} from 'node-internal:crypto' ;
51
53
52
54
import {
53
55
arrayBufferToUnsignedBigInt ,
56
+ getArrayBufferOrView ,
54
57
kHandle ,
55
58
} from 'node-internal:crypto_util' ;
56
59
@@ -64,13 +67,26 @@ import {
64
67
} from 'node-internal:internal_types' ;
65
68
66
69
import {
70
+ // ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS,
71
+ // ERR_CRYPTO_INVALID_JWK,
67
72
ERR_INVALID_ARG_TYPE ,
68
- ERR_METHOD_NOT_IMPLEMENTED ,
69
- // TODO(soon): Uncomment these once createPrivateKey/createPublicKey are implemented.
70
73
// ERR_INVALID_ARG_VALUE,
74
+ ERR_METHOD_NOT_IMPLEMENTED ,
71
75
} from 'node-internal:internal_errors' ;
72
76
73
- import { validateObject , validateString } from 'node-internal:validators' ;
77
+ import {
78
+ validateObject ,
79
+ validateOneOf ,
80
+ validateString ,
81
+ } from 'node-internal:validators' ;
82
+
83
+ // Key input contexts.
84
+ enum KeyContext {
85
+ kConsumePublic ,
86
+ kConsumePrivate ,
87
+ kCreatePublic ,
88
+ kCreatePrivate ,
89
+ }
74
90
75
91
// In Node.js, the definition of KeyObject is a bit complicated because
76
92
// KeyObject instances in Node.js can be transferred via postMessage() and
@@ -81,6 +97,14 @@ import { validateObject, validateString } from 'node-internal:validators';
81
97
// existed first. We're, however, going to layer our KeyObject on top of
82
98
// CryptoKey with a few augmentations.
83
99
100
+ let isKeyObject : ( obj : any ) => boolean ;
101
+
102
+ function isStringOrBuffer ( val : any ) {
103
+ return (
104
+ typeof val === 'string' || isArrayBufferView ( val ) || isAnyArrayBuffer ( val )
105
+ ) ;
106
+ }
107
+
84
108
export abstract class KeyObject {
85
109
[ kHandle ] : CryptoKey ;
86
110
@@ -191,6 +215,12 @@ export abstract class KeyObject {
191
215
get [ Symbol . toStringTag ] ( ) {
192
216
return 'KeyObject' ;
193
217
}
218
+
219
+ static {
220
+ isKeyObject = function ( obj : any ) : obj is KeyObject {
221
+ return obj [ kHandle ] !== undefined ;
222
+ } ;
223
+ }
194
224
}
195
225
196
226
abstract class AsymmetricKeyObject extends KeyObject {
@@ -300,97 +330,91 @@ export function createSecretKey(
300
330
return KeyObject . from ( cryptoImpl . createSecretKey ( key ) ) as SecretKeyObject ;
301
331
}
302
332
303
- // TODO(soon): Fully implement createPrivateKey/createPublicKey. These are the
304
- // equivalent of the WebCrypto API's importKey() method but operate synchronously
305
- // and support a range of options not currently supported by WebCrypto. Implementing
306
- // these will require either duplicating or significantly refactoring the current
307
- // import key logic that supports Web Crypto now as the import logic is spread out
308
- // over several locations and makes a number of assumptions that Web Crypto is being
309
- // used.
310
- //
311
- // For now, users can use Web Crypto to import a CryptoKey then convert that into
312
- // a KeyObject using KeyObject.from().
313
- //
314
- // const kPrivateKey = Symbol('privateKey');
315
- // const kPublicKey = Symbol('publicKey');
316
-
317
- // function validateAsymmetricKeyOptions(
318
- // key: CreateAsymmetricKeyOptions | KeyData | CryptoKey | KeyObject,
319
- // type: Symbol) {
320
- // validateKeyData(key, 'key', { allowObject: true });
321
- // let inner : InnerCreateAsymmetricKeyOptions = {};
322
- // inner.format = 'pem';
323
- // if (typeof key === 'string') {
324
- // inner.key = Buffer.from(key as string);
325
- // } else if (isArrayBufferView(key)) {
326
- // inner.key = key as ArrayBufferView;
327
- // } else if (isArrayBuffer(key)) {
328
- // inner.key = key as ArrayBuffer;
329
- // } else if (isSharedArrayBuffer(key)) {
330
- // inner.key = key as SharedArrayBuffer;
331
- // } else if (type === kPublicKey && key instanceof KeyObject) {
332
- // // Covers deriving public key from a private key.
333
- // if (key.type !== 'private') {
334
- // throw new ERR_INVALID_ARG_VALUE('key', key, 'must be a private key');
335
- // }
336
- // inner.key = (key as KeyObject)[kHandle];
337
- // } else if (type === kPublicKey && key instanceof CryptoKey) {
338
- // // Covers deriving public key from a private key.
339
- // if ((key as CryptoKey).type !== 'private') {
340
- // throw new ERR_INVALID_ARG_VALUE('key', key, 'must be a private key');
341
- // }
342
- // inner.key = key as CryptoKey;
343
- // } else {
344
- // const options = key as CreateAsymmetricKeyOptions;
345
- // if (typeof options.key === 'string') {
346
- // inner.key = Buffer.from(options.key as string, options.encoding);
347
- // } else if (isArrayBufferView(options.key)) {
348
- // inner.key = options.key as ArrayBufferView;
349
- // } else if (isArrayBuffer(options.key)) {
350
- // inner.key = options.key as ArrayBuffer;
351
- // } else if (isSharedArrayBuffer(options.key)) {
352
- // inner.key = options.key as SharedArrayBuffer;
353
- // } else if (type === kPublicKey && key instanceof KeyObject) {
354
- // if ((options.key as KeyObject).type !== 'private') {
355
- // throw new ERR_INVALID_ARG_VALUE('options.key', options.key, 'must be a private key');
356
- // }
357
- // inner.key = (options.key as KeyObject)[kHandle];
358
- // } else if (type === kPublicKey && key instanceof CryptoKey) {
359
- // if ((options.key as CryptoKey).type !== 'private') {
360
- // throw new ERR_INVALID_ARG_VALUE('options.key', options.key, 'must be a private key');
361
- // }
362
- // inner.key = options.key as CryptoKey;
363
- // } else {
364
- // inner.key = key as JsonWebKey;
365
- // }
366
- // validateKeyData(inner.key, 'options.key', { allowObject: true });
367
-
368
- // if (options.format !== undefined) {
369
- // validateString(options.format, 'options.format');
370
- // inner.format = options.format;
371
- // }
372
- // if (options.type !== undefined) {
373
- // validateString(options.type, 'options.type');
374
- // inner.type = options.type;
375
- // }
376
- // if (options.passphrase !== undefined) {
377
- // if (typeof options.passphrase === 'string') {
378
- // inner.passphrase = Buffer.from(options.passphrase, options.encoding);
379
- // } else {
380
- // if (!isUint8Array(options.passphrase)) {
381
- // throw new ERR_INVALID_ARG_TYPE('options.passphrase', [
382
- // 'string', 'Uint8Array'
383
- // ], options.passphrase);
384
- // }
385
- // inner.passphrase = options.passphrase;
386
- // }
387
- // if (inner.passphrase.byteLength > 1024) {
388
- // throw new ERR_INVALID_ARG_VALUE('options.passphrase', options.passphrase.length, '<= 1024');
389
- // }
390
- // }
391
- // }
392
- // return inner;
393
- // }
333
+ function prepareAsymmetricKey (
334
+ key : CreateAsymmetricKeyOptions | KeyData ,
335
+ ctx : KeyContext
336
+ ) : InnerCreateAsymmetricKeyOptions {
337
+ // Safety check... key should not be undefined or null here.
338
+ if ( ( key as any ) == null ) {
339
+ throw new ERR_INVALID_ARG_TYPE (
340
+ 'key' ,
341
+ [ 'ArrayBuffer' , 'Buffer' , 'TypedArray' , 'DataView' , 'string' , 'object' ] ,
342
+ key
343
+ ) ;
344
+ }
345
+
346
+ let normalized : CreateAsymmetricKeyOptions ;
347
+ if (
348
+ isStringOrBuffer ( key ) ||
349
+ isAnyArrayBuffer ( key ) ||
350
+ isArrayBufferView ( key )
351
+ ) {
352
+ normalized = { key, format : 'pem' } as CreateAsymmetricKeyOptions ;
353
+ } else {
354
+ normalized = key as CreateAsymmetricKeyOptions ;
355
+ }
356
+
357
+ const {
358
+ key : data ,
359
+ encoding = 'utf8' ,
360
+ format = 'pem' ,
361
+ type,
362
+ passphrase,
363
+ } = normalized ;
364
+
365
+ // The key data must be specified. The value has to be one of either a
366
+ // string, an ArrayBuffer, an ArrayBufferView, or a JWK object.
367
+ if ( ( data as any ) == null || isKeyObject ( data ) || data instanceof CryptoKey ) {
368
+ throw new ERR_INVALID_ARG_TYPE (
369
+ 'options.key' ,
370
+ [ 'ArrayBuffer' , 'Buffer' , 'TypedArray' , 'DataView' , 'string' , 'object' ] ,
371
+ data
372
+ ) ;
373
+ }
374
+
375
+ if ( isStringOrBuffer ( data ) ) {
376
+ // When the key data is a string or buffer, the format must be
377
+ // one of either pem or der.
378
+ validateOneOf ( format , 'format' , [ 'pem' , 'der' ] ) ;
379
+ if ( type !== undefined ) {
380
+ if ( ctx == KeyContext . kCreatePrivate ) {
381
+ // When the key data is a string or buffer, the type must be
382
+ // one of either pkcs1, pkcs8, or sec1.
383
+ validateOneOf ( type , 'type' , [ 'pkcs1' , 'pkcs8' , 'sec1' ] ) ;
384
+ } else if ( ctx == KeyContext . kCreatePublic ) {
385
+ validateOneOf ( type , 'type' , [ 'pkcs1' , 'spki' ] ) ;
386
+ }
387
+ }
388
+ return {
389
+ key : getArrayBufferOrView ( data , 'key' , encoding ) ,
390
+ format,
391
+ type,
392
+ passphrase :
393
+ passphrase != null
394
+ ? getArrayBufferOrView ( passphrase , 'passphrase' , encoding )
395
+ : undefined ,
396
+ } ;
397
+ }
398
+
399
+ // Final type check. The key data at this point has to be an object that
400
+ // we will interpret as a JWK.
401
+ if ( typeof data !== 'object' ) {
402
+ throw new ERR_INVALID_ARG_TYPE (
403
+ 'key' ,
404
+ [ 'ArrayBuffer' , 'Buffer' , 'TypedArray' , 'DataView' , 'string' , 'object' ] ,
405
+ key
406
+ ) ;
407
+ }
408
+
409
+ // At this point we ignore all remaining options and assume the key is a
410
+ // JSON Web Key.
411
+ return {
412
+ key : data as JsonWebKey ,
413
+ format : 'jwk' ,
414
+ type : undefined ,
415
+ passphrase : undefined ,
416
+ } ;
417
+ }
394
418
395
419
export function createPrivateKey ( key : string ) : PrivateKeyObject ;
396
420
export function createPrivateKey (
@@ -400,16 +424,12 @@ export function createPrivateKey(
400
424
key : CreateAsymmetricKeyOptions
401
425
) : PrivateKeyObject ;
402
426
export function createPrivateKey (
403
- _key : CreateAsymmetricKeyOptions | KeyData
427
+ key : CreateAsymmetricKeyOptions | KeyData
404
428
) : PrivateKeyObject {
405
- // The options here are fairly complex. The key data can be a string,
406
- // ArrayBuffer, or ArrayBufferView. The first argument can be one of
407
- // these or an object with a key property that is one of these. If the
408
- // key data is a string, then it will be decoded using an encoding
409
- // (defaults to UTF8).
410
- throw new ERR_METHOD_NOT_IMPLEMENTED ( 'crypto.createPrivateKey' ) ;
411
- // return KeyObject.from(cryptoImpl.createPrivateKey(
412
- // validateAsymmetricKeyOptions(key, kPrivateKey))) as PrivateKeyObject;
429
+ const cryptoKey = cryptoImpl . createPrivateKey (
430
+ prepareAsymmetricKey ( key , KeyContext . kCreatePrivate )
431
+ ) ;
432
+ return KeyObject . from ( cryptoKey ) as PrivateKeyObject ;
413
433
}
414
434
415
435
export function createPublicKey ( key : string ) : PublicKeyObject ;
@@ -424,14 +444,6 @@ export function createPublicKey(
424
444
export function createPublicKey (
425
445
_key : CreateAsymmetricKeyOptions | KeyData | CryptoKey | KeyObject
426
446
) : PublicKeyObject {
427
- // The options here are a bit complicated. The key material itself can
428
- // either be a string, ArrayBuffer, or ArrayBufferView. It is also
429
- // possible to pass a private key in the form of either a CryptoKey
430
- // or KeyObject. The first argument can be one of these, or an object
431
- // whose key value is one of these. If the key data is a string, then
432
- // it will be decoded using an encoding (defaults to UTF8). If a
433
- // CryptoKey or KeyObject is passed, it will be used to derived the
434
- // public key.
435
447
throw new ERR_METHOD_NOT_IMPLEMENTED ( 'crypto.createPublicKey' ) ;
436
448
// return KeyObject.from(cryptoImpl.createPublicKey(
437
449
// validateAsymmetricKeyOptions(key, kPublicKey))) as PublicKeyObject;
0 commit comments