11const timespan = require ( './lib/timespan' ) ;
2- const PS_SUPPORTED = require ( './lib/psSupported' ) ;
32const validateAsymmetricKey = require ( './lib/validateAsymmetricKey' ) ;
4- const jws = require ( 'jws' ) ;
53const includes = require ( 'lodash.includes' ) ;
64const isBoolean = require ( 'lodash.isboolean' ) ;
75const isInteger = require ( 'lodash.isinteger' ) ;
86const isNumber = require ( 'lodash.isnumber' ) ;
97const isPlainObject = require ( 'lodash.isplainobject' ) ;
108const 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' ) ;
1312
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+ ] ;
1820
1921const sign_options_schema = {
2022 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 = {
3941 nbf : { isValid : isNumber , message : '"nbf" should be a number of seconds' }
4042} ;
4143
44+
4245function validate ( schema , allowUnknown , object , parameterName ) {
4346 if ( ! isPlainObject ( object ) ) {
4447 throw new Error ( 'Expected "' + parameterName + '" to be a plain object.' ) ;
@@ -83,14 +86,41 @@ const options_for_objects = [
8386 'jwtid' ,
8487] ;
8588
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 ) {
87107 if ( typeof options === 'function' ) {
88108 callback = options ;
89109 options = { } ;
90110 } else {
91111 options = options || { } ;
92112 }
93113
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+
94124 const isObjectPayload = typeof payload === 'object' &&
95125 ! Buffer . isBuffer ( payload ) ;
96126
@@ -101,8 +131,8 @@ module.exports = function (payload, secretOrPrivateKey, options, callback) {
101131 } , options . header ) ;
102132
103133 function failure ( err ) {
104- if ( callback ) {
105- return callback ( err ) ;
134+ if ( done ) {
135+ return done ( err ) ;
106136 }
107137 throw err ;
108138 }
@@ -111,12 +141,12 @@ module.exports = function (payload, secretOrPrivateKey, options, callback) {
111141 return failure ( new Error ( 'secretOrPrivateKey must have a value' ) ) ;
112142 }
113143
114- if ( secretOrPrivateKey != null && ! ( secretOrPrivateKey instanceof KeyObject ) ) {
144+ if ( secretOrPrivateKey != null && ! ( secretOrPrivateKey instanceof crypto . KeyObject ) ) {
115145 try {
116- secretOrPrivateKey = createPrivateKey ( secretOrPrivateKey )
146+ secretOrPrivateKey = crypto . createPrivateKey ( secretOrPrivateKey )
117147 } catch ( _ ) {
118148 try {
119- secretOrPrivateKey = createSecretKey ( typeof secretOrPrivateKey === 'string' ? Buffer . from ( secretOrPrivateKey ) : secretOrPrivateKey )
149+ secretOrPrivateKey = crypto . createSecretKey ( typeof secretOrPrivateKey === 'string' ? Buffer . from ( secretOrPrivateKey ) : secretOrPrivateKey )
120150 } catch ( _ ) {
121151 return failure ( new Error ( 'secretOrPrivateKey is not valid key material' ) ) ;
122152 }
@@ -129,12 +159,6 @@ module.exports = function (payload, secretOrPrivateKey, options, callback) {
129159 if ( secretOrPrivateKey . type !== 'private' ) {
130160 return failure ( new Error ( ( `secretOrPrivateKey must be an asymmetric key when using ${ header . alg } ` ) ) )
131161 }
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- }
138162 }
139163
140164 if ( typeof payload === 'undefined' ) {
@@ -224,30 +248,50 @@ module.exports = function (payload, secretOrPrivateKey, options, callback) {
224248 }
225249 } ) ;
226250
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 ) ;
250271 }
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 ) ;
252280 }
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+ } ) ;
253297} ;
0 commit comments