1
1
const timespan = require ( './lib/timespan' ) ;
2
- const PS_SUPPORTED = require ( './lib/psSupported' ) ;
3
2
const validateAsymmetricKey = require ( './lib/validateAsymmetricKey' ) ;
4
- const jws = require ( 'jws' ) ;
5
3
const includes = require ( 'lodash.includes' ) ;
6
4
const isBoolean = require ( 'lodash.isboolean' ) ;
7
5
const isInteger = require ( 'lodash.isinteger' ) ;
8
6
const isNumber = require ( 'lodash.isnumber' ) ;
9
7
const isPlainObject = require ( 'lodash.isplainobject' ) ;
10
8
const isString = require ( 'lodash.isstring' ) ;
11
- const once = require ( 'lodash.once' ) ;
12
- const { KeyObject, createSecretKey, createPrivateKey } = require ( 'crypto' )
9
+ const crypto = require ( 'crypto' )
10
+ const oneShotAlgs = require ( './lib/oneShotAlgs' ) ;
11
+ const encodeBase64url = require ( './lib/base64url' ) ;
13
12
14
- const SUPPORTED_ALGS = [ 'RS256' , 'RS384' , 'RS512' , 'ES256' , 'ES384' , 'ES512' , 'HS256' , 'HS384' , 'HS512' , 'none' ] ;
15
- if ( PS_SUPPORTED ) {
16
- SUPPORTED_ALGS . splice ( 3 , 0 , 'PS256' , 'PS384' , 'PS512' ) ;
17
- }
13
+ const SUPPORTED_ALGS = [
14
+ 'RS256' , 'RS384' , 'RS512' ,
15
+ 'PS256' , 'PS384' , 'PS512' ,
16
+ 'ES256' , 'ES384' , 'ES512' ,
17
+ 'HS256' , 'HS384' , 'HS512' ,
18
+ 'none' ,
19
+ ] ;
18
20
19
21
const sign_options_schema = {
20
22
expiresIn : { isValid : function ( value ) { return isInteger ( value ) || ( isString ( value ) && value ) ; } , message : '"expiresIn" should be a number of seconds or string representing a timespan' } ,
@@ -39,6 +41,7 @@ const registered_claims_schema = {
39
41
nbf : { isValid : isNumber , message : '"nbf" should be a number of seconds' }
40
42
} ;
41
43
44
+
42
45
function validate ( schema , allowUnknown , object , parameterName ) {
43
46
if ( ! isPlainObject ( object ) ) {
44
47
throw new Error ( 'Expected "' + parameterName + '" to be a plain object.' ) ;
@@ -83,14 +86,41 @@ const options_for_objects = [
83
86
'jwtid' ,
84
87
] ;
85
88
86
- module . exports = function ( payload , secretOrPrivateKey , options , callback ) {
89
+ function encodePayload ( payload , encoding = 'utf8' ) {
90
+ let buf ;
91
+ if ( payload instanceof Uint8Array ) {
92
+ buf = Buffer . from ( payload )
93
+ } else if ( typeof payload === 'string' ) {
94
+ buf = Buffer . from ( payload , encoding ) ;
95
+ } else {
96
+ buf = Buffer . from ( JSON . stringify ( payload ) , encoding ) ;
97
+ }
98
+
99
+ return encodeBase64url ( buf ) ;
100
+ }
101
+
102
+ function encodeHeader ( header ) {
103
+ return encodeBase64url ( Buffer . from ( JSON . stringify ( header ) ) ) ;
104
+ }
105
+
106
+ module . exports = function ( payload , secretOrPrivateKey , options , callback ) {
87
107
if ( typeof options === 'function' ) {
88
108
callback = options ;
89
109
options = { } ;
90
110
} else {
91
111
options = options || { } ;
92
112
}
93
113
114
+ let done ;
115
+ if ( callback ) {
116
+ done = callback ;
117
+ } else {
118
+ done = function ( err , data ) {
119
+ if ( err ) throw err ;
120
+ return data ;
121
+ } ;
122
+ }
123
+
94
124
const isObjectPayload = typeof payload === 'object' &&
95
125
! Buffer . isBuffer ( payload ) ;
96
126
@@ -101,8 +131,8 @@ module.exports = function (payload, secretOrPrivateKey, options, callback) {
101
131
} , options . header ) ;
102
132
103
133
function failure ( err ) {
104
- if ( callback ) {
105
- return callback ( err ) ;
134
+ if ( done ) {
135
+ return done ( err ) ;
106
136
}
107
137
throw err ;
108
138
}
@@ -111,12 +141,12 @@ module.exports = function (payload, secretOrPrivateKey, options, callback) {
111
141
return failure ( new Error ( 'secretOrPrivateKey must have a value' ) ) ;
112
142
}
113
143
114
- if ( secretOrPrivateKey != null && ! ( secretOrPrivateKey instanceof KeyObject ) ) {
144
+ if ( secretOrPrivateKey != null && ! ( secretOrPrivateKey instanceof crypto . KeyObject ) ) {
115
145
try {
116
- secretOrPrivateKey = createPrivateKey ( secretOrPrivateKey )
146
+ secretOrPrivateKey = crypto . createPrivateKey ( secretOrPrivateKey )
117
147
} catch ( _ ) {
118
148
try {
119
- secretOrPrivateKey = createSecretKey ( typeof secretOrPrivateKey === 'string' ? Buffer . from ( secretOrPrivateKey ) : secretOrPrivateKey )
149
+ secretOrPrivateKey = crypto . createSecretKey ( typeof secretOrPrivateKey === 'string' ? Buffer . from ( secretOrPrivateKey ) : secretOrPrivateKey )
120
150
} catch ( _ ) {
121
151
return failure ( new Error ( 'secretOrPrivateKey is not valid key material' ) ) ;
122
152
}
@@ -129,12 +159,6 @@ module.exports = function (payload, secretOrPrivateKey, options, callback) {
129
159
if ( secretOrPrivateKey . type !== 'private' ) {
130
160
return failure ( new Error ( ( `secretOrPrivateKey must be an asymmetric key when using ${ header . alg } ` ) ) )
131
161
}
132
- if ( ! options . allowInsecureKeySizes &&
133
- ! header . alg . startsWith ( 'ES' ) &&
134
- secretOrPrivateKey . asymmetricKeyDetails !== undefined && //KeyObject.asymmetricKeyDetails is supported in Node 15+
135
- secretOrPrivateKey . asymmetricKeyDetails . modulusLength < 2048 ) {
136
- return failure ( new Error ( `secretOrPrivateKey has a minimum key size of 2048 bits for ${ header . alg } ` ) ) ;
137
- }
138
162
}
139
163
140
164
if ( typeof payload === 'undefined' ) {
@@ -224,30 +248,50 @@ module.exports = function (payload, secretOrPrivateKey, options, callback) {
224
248
}
225
249
} ) ;
226
250
227
- const encoding = options . encoding || 'utf8' ;
228
-
229
- if ( typeof callback === 'function' ) {
230
- callback = callback && once ( callback ) ;
231
-
232
- jws . createSign ( {
233
- header : header ,
234
- privateKey : secretOrPrivateKey ,
235
- payload : payload ,
236
- encoding : encoding
237
- } ) . once ( 'error' , callback )
238
- . once ( 'done' , function ( signature ) {
239
- // TODO: Remove in favor of the modulus length check before signing once node 15+ is the minimum supported version
240
- if ( ! options . allowInsecureKeySizes && / ^ (?: R S | P S ) / . test ( header . alg ) && signature . length < 256 ) {
241
- return callback ( new Error ( `secretOrPrivateKey has a minimum key size of 2048 bits for ${ header . alg } ` ) )
242
- }
243
- callback ( null , signature ) ;
244
- } ) ;
245
- } else {
246
- let signature = jws . sign ( { header : header , payload : payload , secret : secretOrPrivateKey , encoding : encoding } ) ;
247
- // TODO: Remove in favor of the modulus length check before signing once node 15+ is the minimum supported version
248
- if ( ! options . allowInsecureKeySizes && / ^ (?: R S | P S ) / . test ( header . alg ) && signature . length < 256 ) {
249
- throw new Error ( `secretOrPrivateKey has a minimum key size of 2048 bits for ${ header . alg } ` )
251
+ const sync = done !== callback || header . alg === 'none' || header . alg . startsWith ( 'HS' ) || parseInt ( process . versions . node , 10 ) < 16 ;
252
+ const data = Buffer . from ( `${ encodeHeader ( header ) } .${ encodePayload ( payload , options . encoding ) } ` ) ;
253
+
254
+ if ( header . alg === 'none' ) {
255
+ return done ( null , `${ data } .` )
256
+ }
257
+
258
+ if ( header . alg . startsWith ( 'HS' ) ) {
259
+ const signature = encodeBase64url ( crypto . createHmac ( `sha${ header . alg . substring ( 2 , 5 ) } ` , secretOrPrivateKey ) . update ( data ) . digest ( ) ) ;
260
+ return done ( null , `${ data } .${ signature } ` ) ;
261
+ }
262
+
263
+ if ( sync ) {
264
+ const { digest, key } = oneShotAlgs ( header . alg , secretOrPrivateKey ) ;
265
+
266
+ let signature ;
267
+ try {
268
+ signature = crypto . sign ( digest , data , key ) ;
269
+ } catch ( err ) {
270
+ return done ( err ) ;
250
271
}
251
- return signature
272
+
273
+ if ( ! options . allowInsecureKeySizes && ( header . alg . startsWith ( 'RS' ) || header . alg . startsWith ( 'PS' ) ) && signature . byteLength < 256 ) {
274
+ return done ( new Error ( `secretOrPrivateKey has a minimum key size of 2048 bits for ${ header . alg } ` ) ) ;
275
+ }
276
+
277
+ const token = `${ data } .${ encodeBase64url ( signature ) } ` ;
278
+
279
+ return done ( null , token ) ;
252
280
}
281
+
282
+ const { digest, key } = oneShotAlgs ( header . alg , secretOrPrivateKey ) ;
283
+
284
+ crypto . sign ( digest , data , key , ( err , signature ) => {
285
+ if ( err ) {
286
+ return done ( err ) ;
287
+ }
288
+
289
+ if ( ! options . allowInsecureKeySizes && ( header . alg . startsWith ( 'RS' ) || header . alg . startsWith ( 'PS' ) ) && signature . byteLength < 256 ) {
290
+ return done ( new Error ( `secretOrPrivateKey has a minimum key size of 2048 bits for ${ header . alg } ` ) ) ;
291
+ }
292
+
293
+ const token = `${ data } .${ encodeBase64url ( signature ) } ` ;
294
+
295
+ return done ( null , token ) ;
296
+ } ) ;
253
297
} ;
0 commit comments