├── .npmignore ├── LICENSE ├── README.md ├── examples ├── async_mode.js └── basic_examples.js ├── index.js ├── lib ├── common.js ├── ecdsa.js ├── jwe.js └── jws.js ├── package.json └── test ├── jwe_basic.js ├── jwe_expiration.js ├── jwe_negative.js ├── jwe_performance.js ├── jws_aud_iss.js ├── jws_basic.js ├── jws_expiration.js ├── jws_interop.js ├── jws_negative.js ├── jws_performance.js └── pem_keys ├── priEc256.key ├── priEc384.key ├── priEc521.key ├── priRsa.key ├── pubEc256.key ├── pubEc384.key ├── pubEc521.key └── pubRsa.key /.npmignore: -------------------------------------------------------------------------------- 1 | examples 2 | test 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017, 2019 Paolo Fiorini 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-webtokens 2 | 3 | Simple, opinionated implementation of [JWS](https://tools.ietf.org/html/rfc7515) and [JWE](https://tools.ietf.org/html/rfc7516) compact serialization. 4 | 5 | ### Simple 6 | 7 | All functions exposed through a single set of straightforward APIs. 8 | 9 | ```javascript 10 | const jwt = require('node-webtokens'); 11 | 12 | // JWS EXAMPLE 13 | token = jwt.generate(alg, payload, key); 14 | parsedToken = jwt.parse(token).verify(key); 15 | 16 | // JWE EXAMPLE 17 | token = jwt.generate(alg, enc, payload, key); 18 | parsedToken = jwt.parse(token).verify(key); 19 | ``` 20 | 21 | Token parsing and token verification/decryption supported through chainable methods. When necessary, this enables the user to inspect the token header before proceeding with the verification/decryption. Here is an example: 22 | 23 | ```javascript 24 | parsedToken = jwt.parse(token); 25 | 26 | if (parsedToken.error) { 27 | // error handling logic 28 | } else { 29 | // inspect parsedToken.header 30 | 31 | // proceed with verification 32 | parsedToken.verify(key); 33 | } 34 | ``` 35 | 36 | Token verification can be fine-tuned through additional chainable methods. Example: 37 | 38 | ```javascript 39 | parsedToken = jwt.parse(token) 40 | .setTokenLifetime(120000) 41 | .setAlgorithmList(['RS256', 'RS384']) 42 | .setIssuer(['auth.mydomain.com']) 43 | .setAudience(['A1B2C3D4E5.com.mydomain.myservice']) 44 | .verify(key); 45 | ``` 46 | 47 | Keys can be automatically managed out of keystores (JavaScript objects holding multiple keys). Example: 48 | 49 | ```javascript 50 | keystore = { 51 | 'e5739df2261c8a0ed41715e7f62cc295': 'SATKcp7AMnCg0YdEBPIcgknBplYttePtQoRddpJjyVak9F5vEp/7pL0Q1236MkVQd7nIXGoaPt4w1dlrpEmY4A==', 52 | 'f0fd89c4abe83811ee9afa92d0d687f7': '6Bzisgmhj9LGJDNjx/WBNRUsnZA8pXRpVxB7Pf8ar29XI158V4+t1GEqkCl5MYZhcOMTi5fa3yYr0Vcya6vUkA==', 53 | '20e009a52cd91dc7dc7a8d7da525fed5': '+PC/htwSB6pz4VRTcGL1iN74xlqoX6Q2oilsraVvSVefL+lr0tW1+/pOGQpdZpXtN20DjfbC0s4rHYZD2z924Q==' 54 | }; 55 | 56 | token = jwt.generate(alg, payload, keystore, kid); 57 | parsedToken = jwt.parse(token).verify(keystore); 58 | ``` 59 | 60 | ### Opinionated 61 | 62 | There are various [npm](https://www.npmjs.com/) packages that cover the [IETF JOSE](https://datatracker.ietf.org/wg/jose/documents/) scope striving for generality and flexibility. This specific package is shaped after the following strong assumptions, which somehow restrict its usability: 63 | 64 | - No effort to ensure compatibility with older [Node.js](https://nodejs.org) versions. Most stringent requirement comes from the use of `crypto.timingSafeEqual()`, which is not available in [Node.js](https://nodejs.org) versions prior to v6.6.0; 65 | - The [JWS](https://tools.ietf.org/html/rfc7515)/[JWE](https://tools.ietf.org/html/rfc7516) payload must be a JavaScript object (a.k.a. hash or dictionary); 66 | - The `iat` claim is automatically added to the payload at token generation time, and comes in the form of a Unix timestamp (number of seconds); 67 | - The [JWS](https://tools.ietf.org/html/rfc7515)/[JWE](https://tools.ietf.org/html/rfc7516) header is automatically generated at token generation time, with limited control by the user. 68 | 69 | ### Installation 70 | 71 | ``` 72 | npm install node-webtokens --save 73 | ``` 74 | 75 | ### Supported JWS algorithms 76 | 77 | | Algorithm | Minimum key requirements | 78 | |:----------|:-------------------------------------------------------------------------| 79 | | `HS256` | 32-octet key, passed either as base64 string or as buffer; same key for token generation and token verification | 80 | | `HS384` | 48-octet key, passed either as base64 string or as buffer; same key for token generation and token verification | 81 | | `HS512` | 64-octet key, passed either as base64 string or as buffer; same key for token generation and token verification | 82 | | `RS256` | 2048-bit RSA key in PEM format, passed either as UTF-8 string or as buffer; private key for token generation, public key or certificate for token verification | 83 | | `RS384` | 2048-bit RSA key in PEM format, passed either as UTF-8 string or as buffer; private key for token generation, public key or certificate for token verification | 84 | | `RS512` | 2048-bit RSA key in PEM format, passed either as UTF-8 string or as buffer; private key for token generation, public key or certificate for token verification | 85 | | `ES256` | P-256 EC key in PEM format, passed either as UTF-8 string or as buffer; private key for token generation, public key or certificate for token verification; P-256 keys are identified as `prime256v1` in OpenSSL | 86 | | `ES384` | P-384 EC key in PEM format, passed either as UTF-8 string or as buffer; private key for token generation, public key or certificate for token verification; P-384 keys are identified as `secp384r1` in OpenSSL | 87 | | `ES512` | P-521 EC key in PEM format, passed either as UTF-8 string or as buffer; private key for token generation, public key or certificate for token verification; P-521 keys are identified as `secp521r1` in OpenSSL | 88 | 89 | *Table 1 - List of JWS algorithms* 90 | 91 | ### Supported JWE key management algorithms 92 | 93 | | Algorithm | Minimum key requirements | 94 | |:---------------------|:---------------------------------------------------------------------------| 95 | | `RSA-OAEP` | 2048-bit RSA key in PEM format, passed either as UTF-8 string or as buffer; public key or certificate for token generation, private key for token decryption | 96 | | `A128KW` | 16-octet key, passed either as base64 string or as buffer; same key for token generation and token decryption | 97 | | `A192KW` | 24-octet key, passed either as base64 string or as buffer; same key for token generation and token decryption | 98 | | `A256KW` | 32-octet key, passed either as base64 string or as buffer; same key for token generation and token decryption | 99 | | `dir` | n/a | 100 | | `PBES2-HS256+A128KW` | Password, passed either as UTF-8 string or as buffer; same password for token generation and token decryption; a 16-octet key is derived from the password through [PBKDF2](https://tools.ietf.org/html/rfc8018) | 101 | | `PBES2-HS384+A192KW` | Password, passed either as UTF-8 string or as buffer; same password for token generation and token decryption; a 24-octet key is derived from the password through [PBKDF2](https://tools.ietf.org/html/rfc8018) | 102 | | `PBES2-HS512+A256KW` | Password, passed either as UTF-8 string or as buffer; same password for token generation and token decryption; a 32-octet key is derived from the password through [PBKDF2](https://tools.ietf.org/html/rfc8018) | 103 | 104 | *Table 2 - List of JWE key management algorithms* 105 | 106 | ### Supported JWE content encryption algorithms 107 | 108 | | Algorithm | Minimum key requirements (*) | 109 | |:----------------|:-------------------------------------------------------------| 110 | | `A128CBC-HS256` | 32-octet key, passed either as base64 string or as buffer; same key for token generation and token decryption | 111 | | `A192CBC-HS384` | 48-octet key, passed either as base64 string or as buffer; same key for token generation and token decryption | 112 | | `A256CBC-HS512` | 64-octet key, passed either as base64 string or as buffer; same key for token generation and token decryption | 113 | | `A128GCM` | 16-octet key, passed either as base64 string or as buffer; same key for token generation and token decryption | 114 | | `A192GCM` | 24-octet key, passed either as base64 string or as buffer; same key for token generation and token decryption | 115 | | `A256GCM` | 32-octet key, passed either as base64 string or as buffer; same key for token generation and token decryption | 116 | 117 | *Table 3 - List of JWE content encryption algorithms* 118 | 119 | (*) These requirements are relevant only when direct content encryption is used (key management algorithm equal to `dir`). In all the other cases, the [JWE](https://tools.ietf.org/html/rfc7516) generation API takes care of generating a single-use content encryption key of appropriate length. 120 | 121 | ### Synchronous vs. asynchronous 122 | 123 | The token generation API and token verification API can both be used in either synchronous or asynchronous mode. Example: 124 | 125 | ```javascript 126 | // SYNCHRONOUS API MODE 127 | token = jwt.generate('HS256', payload, key); 128 | parsedToken = jwt.parse(token).verify(key); 129 | 130 | // ASYNCHRONOUS API MODE 131 | jwt.generate('PBES2-HS512+A256KW', 'A256GCM', payload, pwd, (error, token) => { 132 | jwt.parse(token).verify(pwd, (error, parsedToken) => { 133 | // other statements 134 | }); 135 | }); 136 | ``` 137 | 138 | All the [Node.js](https://nodejs.org) crypto functions used in this package are synchronous, with the exception of [PBKDF2](https://tools.ietf.org/html/rfc8018), which can be invoked either synchronously as `crypto.pbkdf2Sync()` or asynchronously as `crypto.pbkdf2()`. This implies that the use of the asynchronous API mode makes a real difference in terms of execution only when one of the algorithms based on [PBKDF2](https://tools.ietf.org/html/rfc8018) is selected, namely `PBES2-HS256+A128KW`, `PBES2-HS384+A192KW` or `PBES2-HS512+A256KW`. 139 | 140 | > Use of the token generation and token verification APIs in asynchronous mode is recommended for [JWE](https://tools.ietf.org/html/rfc7516) when the selected key management algorithm is `PBES2-HS256+A128KW`, `PBES2-HS384+A192KW` or `PBES2-HS512+A256KW`. Conversely, use of the synchronous mode is preferable for [JWS](https://tools.ietf.org/html/rfc7515) and for all other [JWE](https://tools.ietf.org/html/rfc7516) cases. 141 | 142 | ### Token generation 143 | 144 | Single API, supporting two slightly different usage patterns, each with synchronous and asynchronous mode: 145 | 146 | **jwt.generate(alg, [enc,] payload, key[, callback])** 147 | **jwt.generate(alg, [enc,] payload, keystore, kid[, callback])** 148 | 149 | - `alg` - String corresponding to one of the algorithms listed in *Table 1* for [JWS](https://tools.ietf.org/html/rfc7515) or in *Table 2* for [JWE](https://tools.ietf.org/html/rfc7516) (case sensitive spelling); 150 | - `enc` - Present only for [JWE](https://tools.ietf.org/html/rfc7516); string corresponding to one of the algorithms listed in *Table 3* (case sensitive spelling); 151 | - `payload` - JavaScript object (a.k.a. hash or dictionary); if already present, the `iat` claim is overridden at token generation time; 152 | - `key` - Key subject to the requirements specified in *Table 1*, *Table 2* or *Table 3*, depending on the selected `alg` value; 153 | - `keystore` - JavaScript object holding multiple keys; 154 | - `kid` - Key identifier; string; must exist in `keystore`. 155 | 156 | When the `keystore` / `kid` pattern is used, the `kid` claim is automatically added to the token header. 157 | 158 | When used in synchronous mode, the token generation API returns the token as string. When used in asynchronous mode, the `callback` function is invoked with parameters `(error, token)`. 159 | 160 | ### Token parsing and verification/decryption 161 | 162 | Token parsing and token verification/decryption are supported through chainable methods. 163 | 164 | **jwt.parse(token)** 165 | 166 | The token parsing API is invoked with the token (string) as input and returns a `ParsedToken` object with the following properties: 167 | - `error` - Error condition as JavaScript object; present if parsing could not be completed (e.g., invalid token, non parsable header); 168 | - `parts` - Array of strings, with each string corresponding to one of the token parts (three parts for [JWS](https://tools.ietf.org/html/rfc7515), five parts for [JWE](https://tools.ietf.org/html/rfc7516)); 169 | - `type` - Either `JWS` or `JWE` or not present, with the latter relevant in case the token type could not be recognized (invalid token error); 170 | - `header` - Token header as JavaScript object; not present if the token header could not be parsed; 171 | - `payload` - Token payload as JavaScript object; present only for [JWS](https://tools.ietf.org/html/rfc7515) tokens and in absence of errors; for [JWE](https://tools.ietf.org/html/rfc7516) tokens the payload gets added to the `ParsedToken` object after decryption, which is performed when the token verification API is invoked. 172 | 173 | Token parsing never throws errors. Any error condition encountered during parsing is reported in the `error` object. 174 | 175 | **parsedToken.setTokenLifetime(lifetime)** 176 | 177 | The `setTokenLifetime` method can be used to configure the token lifetime to be considered when assessing the token validity. The parameter `lifetime` constraints the maximum number of seconds elapsed since the generation of the token (indicated by the `iat` claim in the token payload). 178 | 179 | If the `setTokenLifetime` method is not used, then token verification does not encompass expiration based on the `iat` claim. However, if the token payload contains the `exp` claim, then the token is still subject to expiration based on the `exp` claim. 180 | 181 | The `setTokenLifetime` method does not throw errors. The specified `lifetime` value is simply ignored if it is not an integer number greater than zero. 182 | 183 | > Token verification does not enforce the presence of the `exp` claim in the token payload. However, if present, the `exp` claim is processed. 184 | 185 | **parsedToken.setAlgorithmList(algList[, encList])** 186 | 187 | The `setAlgorithmList` method can be used to configure the list of algorithms that are considered acceptable: 188 | - `algList` - String or array of strings corresponding to one or multiple of the algorithms listed in *Table 1* for [JWS](https://tools.ietf.org/html/rfc7515) or in *Table 2* for [JWE](https://tools.ietf.org/html/rfc7516) (case-sensitive spelling); 189 | - `encList` - Only relevant for [JWE](https://tools.ietf.org/html/rfc7516); string or array of strings corresponding to one or multiple of the algorithms listed in *Table 3* (case-sensitive spelling); 190 | 191 | Integrity check/decryption is not attempted during verification if the token under verification does not comply with the configured algorithm list. In that case, the token is simply reported as invalid because of the unwanted algorithm. 192 | 193 | The `setAlgorithmList` method does not throw errors. If the algorithm list contains only invalid or non-existent algorithms, then all the tokens are reported as invalid. 194 | 195 | **parsedToken.setAudience(audList)** 196 | 197 | The `setAudience` method can be used to configure the acceptable values of the `aud` claim. Input parameter `audList` can be a string or an array of strings. 198 | 199 | The `setAudience` method does not throw errors. If `audList` is not a string or an array of strings, then the action is simply ignored. 200 | 201 | > Token verification enforces the presence of the `aud` claim in the token payload only if the `setAudience` method is invoked before proceeding with the verification. 202 | 203 | **parsedToken.setIssuer(issList)** 204 | 205 | The `setIssuer` method can be used to configure the acceptable values of the `iss` claim. Input parameter `issList` can be a string or an array of strings. 206 | 207 | The `setIssuer` method does not throw errors. If `issList` is not a string or an array of strings, then the action is simply ignored. 208 | 209 | > Token verification enforces the presence of the `iss` claim in the token payload only if the `setIssuer` method is invoked before proceeding with the verification. 210 | 211 | **parsedToken.verify(key[, callback])** 212 | **parsedToken.verify(keystore[, callback])** 213 | 214 | - `key` - Key subject to the requirements specified in *Table 1*, *Table 2* or *Table 3*, depending on the `alg` claim found in the token header; 215 | - `keystore` - JavaScript object holding multiple keys; the key used for verification is determined on the basis of the `kid` claim found in the token header. 216 | 217 | When used in synchronous mode, the `verify` method returns the `ParsedToken` object enriched with additional properties. When used in asynchronous mode, the `callback` function is invoked with parameters `(error, parsedToken)`. 218 | 219 | After the verification, the `ParsedToken` object exposes the following properties: 220 | - `valid` - Present and equal to `true` (boolean) if the token is valid and not expired; absent in all other cases; 221 | - `expired` - Present and equal to the token expiration time (Unix timestamp, seconds) if the token is valid but expired; absent in all other cases; 222 | - `error` - Error condition as JavaScript object; present if token verification could not be completed or the token was found invalid; absent in all other cases; when present, the `error` object always includes the `message` property that specifies the reason why token verification failed; 223 | - `parts` - Array of strings, with each string corresponding to one of the token parts (three parts for [JWS](https://tools.ietf.org/html/rfc7515), five parts for [JWE](https://tools.ietf.org/html/rfc7516)); 224 | - `type` - Either `JWS` or `JWE`; always present for valid or expired tokens; may not be present otherwise; 225 | - `header` - Token header as JavaScript object; always present for valid or expired tokens; may not be present otherwise; 226 | - `payload` - Token payload as JavaScript object; always present for valid or expired tokens; may not be present otherwise. 227 | 228 | The following example illustrates a plausible handling of the final `ParsedToken` object: 229 | 230 | ```javascript 231 | if (parsedToken.error) { 232 | // error handling; parsedToken.error.message provides details 233 | 234 | } else if (parsedToken.expired) { 235 | // token has expired; the parsedToken.expired value indicates when 236 | 237 | } else { 238 | // token is valid; parsedToken.payload is ready for use 239 | 240 | } 241 | ``` 242 | 243 | ### Examples 244 | 245 | Token generated/verified with individual key: 246 | 247 | ```javascript 248 | const jwt = require('node-webtokens'); 249 | 250 | var key = getKeyFromSomewhere(); 251 | 252 | var payload = { 253 | iss: 'auth.mydomain.com', 254 | aud: 'A1B2C3D4E5.com.mydomain.myservice', 255 | sub: 'jack.sparrow@example.com', 256 | info: 'Hello World!', 257 | list: [1, 2, 3] 258 | }; 259 | 260 | var token = jwt.generate('HS512', payload, key); 261 | console.log(token); 262 | // eyJhbGciOiJIUzUxMiJ9.eyJpc3MiOiJhdXRoLm15ZG9tYWluLmNvbSIsImF1ZCI6IkExQjJDM0Q0RTUuY29tLm15ZG9tYWluLm15c2VydmljZSIsInN1YiI6ImphY2suc3BhcnJvd0BleGFtcGxlLmNvbSIsImluZm8iOiJIZWxsbyBXb3JsZCEiLCJsaXN0IjpbMSwyLDNdLCJpYXQiOjE0OTQ0NTEwMDR9.Rzb8KJ6du4QKnd9goevhswj56Y3polY_IwF6_onDKxa9IbEtBCUBfmgdZDZdE5meLBUFw9PaMqbj3fo3L3JEQA 263 | 264 | var parsed = jwt.parse(token).verify(key); 265 | console.log(parsed.valid); 266 | // true 267 | console.log(parsed.header); 268 | // { alg: 'HS512' } 269 | console.log(parsed.payload); 270 | /* { iss: 'auth.mydomain.com', 271 | aud: 'A1B2C3D4E5.com.mydomain.myservice', 272 | sub: 'jack.sparrow@example.com', 273 | info: 'Hello World!', 274 | list: [ 1, 2, 3 ], 275 | iat: 1494451004 } */ 276 | ``` 277 | 278 | Token generated/verified with keystore: 279 | 280 | ```javascript 281 | var keystore = { 282 | 'e5739df2261c8a0ed41715e7f62cc295': 'SATKcp7AMnCg0YdEBPIcgknBplYttePtQoRddpJjyVak9F5vEp/7pL0Q1236MkVQd7nIXGoaPt4w1dlrpEmY4A==', 283 | 'f0fd89c4abe83811ee9afa92d0d687f7': '6Bzisgmhj9LGJDNjx/WBNRUsnZA8pXRpVxB7Pf8ar29XI158V4+t1GEqkCl5MYZhcOMTi5fa3yYr0Vcya6vUkA==', 284 | '20e009a52cd91dc7dc7a8d7da525fed5': '+PC/htwSB6pz4VRTcGL1iN74xlqoX6Q2oilsraVvSVefL+lr0tW1+/pOGQpdZpXtN20DjfbC0s4rHYZD2z924Q==' 285 | }; 286 | 287 | token = jwt.generate('HS512', payload, keystore, 'f0fd89c4abe83811ee9afa92d0d687f7'); 288 | console.log(token); 289 | // eyJhbGciOiJIUzUxMiIsImtpZCI6ImYwZmQ4OWM0YWJlODM4MTFlZTlhZmE5MmQwZDY4N2Y3In0.eyJpc3MiOiJhdXRoLm15ZG9tYWluLmNvbSIsImF1ZCI6IkExQjJDM0Q0RTUuY29tLm15ZG9tYWluLm15c2VydmljZSIsInN1YiI6ImphY2suc3BhcnJvd0BleGFtcGxlLmNvbSIsImluZm8iOiJIZWxsbyBXb3JsZCEiLCJsaXN0IjpbMSwyLDNdLCJpYXQiOjE0OTQ0NTEwMDR9.z9mawWuGjE0eIQV08YtWTrlD7OAnmxGaLWFiBlXMn9MwzYHE-Sa9KhPLeeWuSx1c8at62F2IegK8O61gDGUA_g 290 | 291 | parsed = jwt.parse(token).verify(keystore); 292 | console.log(parsed.valid); 293 | // true 294 | console.log(parsed.header); 295 | // { alg: 'HS512', kid: 'f0fd89c4abe83811ee9afa92d0d687f7' } 296 | ``` 297 | 298 | > Note the `kid` claim automatically added to the token header. 299 | 300 | Verification key not found in keystore: 301 | 302 | ```javascript 303 | token = jwt.generate('HS512', payload, keystore, 'f0fd89c4abe83811ee9afa92d0d687f7'); 304 | 305 | delete keystore['f0fd89c4abe83811ee9afa92d0d687f7']; 306 | 307 | parsed = jwt.parse(token).verify(keystore); 308 | console.log(parsed.error); 309 | /* { message: 'Key with id not found', 310 | kid: 'f0fd89c4abe83811ee9afa92d0d687f7' } */ 311 | ``` 312 | 313 | > The offending key identifier is exposed in the `error` object. 314 | 315 | Expired token: 316 | 317 | ```javascript 318 | token = jwt.generate('HS512', payload, key); 319 | 320 | setTimeout(() => { 321 | parsed = jwt.parse(token).setTokenLifetime(3).verify(key); 322 | console.log(parsed.expired); 323 | // 1494451007 324 | 325 | }, 5000); 326 | ``` 327 | 328 | > In the above example, expiration is determined on the basis of the `iat` claim and of the configured token lifetime (3 seconds). However, in case the token payload contains the `exp` claim, that is considered as well. 329 | 330 | Token using unwanted algorithm: 331 | 332 | ```javascript 333 | token = jwt.generate('HS256', payload, key); 334 | 335 | parsed = jwt.parse(token) 336 | .setAlgorithmList(['HS384', 'HS512']) 337 | .verify(key); 338 | 339 | console.log(parsed.error); 340 | // { message: 'Unwanted algorithm HS256' } 341 | ``` 342 | 343 | Parsing and verification as separate steps. [JWS](https://tools.ietf.org/html/rfc7515) example: 344 | 345 | ```javascript 346 | token = jwt.generate('HS512', payload, key); 347 | 348 | parsed = jwt.parse(token); 349 | console.log(parsed.header); 350 | // { alg: 'HS512' } 351 | console.log(parsed.payload); 352 | /* { iss: 'auth.mydomain.com', 353 | aud: 'A1B2C3D4E5.com.mydomain.myservice', 354 | sub: 'jack.sparrow@example.com', 355 | info: 'Hello World!', 356 | list: [ 1, 2, 3 ], 357 | iat: 1494451807 } */ 358 | 359 | parsed.setTokenLifetime(600).verify(key); 360 | console.log(parsed.valid); 361 | // true 362 | ``` 363 | 364 | Parsing and verification as separate steps. [JWE](https://tools.ietf.org/html/rfc7516) example: 365 | 366 | ```javascript 367 | token = jwt.generate('A256KW', 'A256GCM', payload, key); 368 | 369 | parsed = jwt.parse(token); 370 | console.log(parsed.header); 371 | // { alg: 'A256KW', enc: 'A256GCM' } 372 | console.log(parsed.payload); 373 | // undefined 374 | 375 | parsed.setTokenLifetime(600).verify(key); 376 | console.log(parsed.valid); 377 | // true 378 | console.log(parsed.payload); 379 | /* { iss: 'auth.mydomain.com', 380 | aud: 'A1B2C3D4E5.com.mydomain.myservice', 381 | sub: 'jack.sparrow@example.com', 382 | info: 'Hello World!', 383 | list: [ 1, 2, 3 ], 384 | iat: 1494451807 } */ 385 | ``` 386 | 387 | Token generation with asynchronous API: 388 | 389 | ```javascript 390 | jwt.generate('PBES2-HS512+A256KW', 'A256GCM', payload, key, (error, token) => { 391 | console.log(token); 392 | // eyJhbGciOiJQQkVTMi1IUzUxMitBMjU2S1ciLCJlbmMiOiJBMjU2R0NNIiwicDJjIjoxMDAwLCJwMnMiOiJVRUpGVXpJdFNGTTFNVElyUVRJMU5rdFhBRGN0N2dnMk1qWGsifQ.IeIzzbZBtb65xF9z1I_L39up0V7FBtSlTJKMNft4_DD6pdQEiIMXAw.kihjXwJhu2ZC3ckd.CTbf_iRZrYho2Y-iw-1IHVh-POCYzBX0QhZ3j3onycb3hjMU6iWKokiKKeyzyG8UGLKO8uT5pyndGUNmyGAc-sJSMwZN5chHovet2JRsxjDC4PWiaMoE423eMqI3cc3iK4k9c71aKOOQOsEXbBohKwOy-nnlwU62ombiRejptb5p22V-FL7OwqK14-EcKSJxnvU8XRq4pX9HWU9G.jMnFV6OK2yBVUnw-W7YJKA 393 | }); 394 | ``` 395 | 396 | > With [PBES2](https://tools.ietf.org/html/rfc7518#section-4.8), key derivation at token generation time performs 1024 [PBKDF2](https://tools.ietf.org/html/rfc8018) iterations. Hence the recommendation to use the asynchronous mode. 397 | 398 | Token verification with asynchronous API: 399 | 400 | ```javascript 401 | jwt.parse(token).setTokenLifetime(600).verify(key, (error, parsed) => { 402 | console.log(parsed.valid); 403 | // true 404 | console.log(parsed.header); 405 | /* { alg: 'PBES2-HS512+A256KW', 406 | enc: 'A256GCM', 407 | p2c: 1024, 408 | p2s: 'UEJFUzItSFM1MTIrQTI1NktXADct7gg2MjXk' } */ 409 | console.log(parsed.payload); 410 | /* { iss: 'auth.mydomain.com', 411 | aud: 'A1B2C3D4E5.com.mydomain.myservice', 412 | sub: 'jack.sparrow@example.com', 413 | info: 'Hello World!', 414 | list: [ 1, 2, 3 ], 415 | iat: 1494451023 } */ 416 | }); 417 | ``` 418 | 419 | > With [PBES2](https://tools.ietf.org/html/rfc7518#section-4.8), key derivation at token verification time performs the number of [PBKDF2](https://tools.ietf.org/html/rfc8018) iterations indicated by the `p2c` claim in the [JWE](https://tools.ietf.org/html/rfc7516) header. For protection against bogus tokens, the token verification API rejects `p2c` values larger than 1024 when used in synchronous mode or 16384 when used in asynchronous mode. 420 | 421 | ### Credits 422 | 423 | The JavaScript code used for ECDSA signature conversion from DER to concatenated and vice-versa is directly derived from the [ecdsa-sig-formatter](https://github.com/Brightspace/node-ecdsa-sig-formatter) module. 424 | -------------------------------------------------------------------------------- /examples/async_mode.js: -------------------------------------------------------------------------------- 1 | const crypto = require('crypto'); 2 | const jwt = require('../index.js'); 3 | 4 | const key = crypto.randomBytes(64); 5 | 6 | const payload = { 7 | iss: 'auth.mydomain.com', 8 | aud: 'A1B2C3D4E5.com.mydomain.myservice', 9 | sub: 'jack.sparrow@example.com', 10 | info: 'Hello World!', 11 | list: [1, 2, 3, 4] 12 | }; 13 | 14 | console.log('\nTOKEN GENERATION WITH ASYNCHRONOUS API\n'); 15 | 16 | jwt.generate('PBES2-HS512+A256KW', 'A256GCM', payload, key, (error, token) => { 17 | console.log('Sample JWE token (PBES2-HS512+A256KW, A256GCM)\n'); 18 | console.log(token); 19 | 20 | console.log('\nTOKEN VERIFICATION WITH ASYNCHRONOUS API\n'); 21 | 22 | jwt.parse(token).setTokenLifetime(600).verify(key, (error, parsed) => { 23 | console.log('Token is valid:', parsed.valid); 24 | console.log('\nHeader:\n'); 25 | console.log(parsed.header); 26 | console.log('\nPayload\n'); 27 | console.log(parsed.payload); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /examples/basic_examples.js: -------------------------------------------------------------------------------- 1 | const crypto = require('crypto'); 2 | const jwt = require('../index.js'); 3 | 4 | const key = crypto.randomBytes(64); 5 | 6 | const payload = { 7 | iss: 'auth.mydomain.com', 8 | aud: 'A1B2C3D4E5.com.mydomain.myservice', 9 | sub: 'jack.sparrow@example.com', 10 | info: 'Hello World!', 11 | list: [1, 2, 3, 4] 12 | }; 13 | 14 | console.log('\nTOKEN GENERATED/VERIFIED WITH INDIVIDUAL KEY\n'); 15 | 16 | let token = jwt.generate('HS512', payload, key); 17 | console.log('Sample JWS token (HS512)\n'); 18 | console.log(token); 19 | 20 | let parsed = jwt.parse(token).verify(key); 21 | console.log('\nToken is valid:', parsed.valid); 22 | console.log('\nHeader:\n'); 23 | console.log(parsed.header); 24 | console.log('\nPayload:\n'); 25 | console.log(parsed.payload); 26 | 27 | console.log('\nTOKEN GENERATED/VERIFIED WITH KEY FROM KEYSTORE\n'); 28 | 29 | let keystore = { 30 | 'e5739df2261c8a0ed41715e7f62cc295': 'SATKcp7AMnCg0YdEBPIcgknBplYttePtQoRddpJjyVak9F5vEp/7pL0Q1236MkVQd7nIXGoaPt4w1dlrpEmY4A==', 31 | 'f0fd89c4abe83811ee9afa92d0d687f7': '6Bzisgmhj9LGJDNjx/WBNRUsnZA8pXRpVxB7Pf8ar29XI158V4+t1GEqkCl5MYZhcOMTi5fa3yYr0Vcya6vUkA==', 32 | '20e009a52cd91dc7dc7a8d7da525fed5': '+PC/htwSB6pz4VRTcGL1iN74xlqoX6Q2oilsraVvSVefL+lr0tW1+/pOGQpdZpXtN20DjfbC0s4rHYZD2z924Q==' 33 | }; 34 | 35 | token = jwt.generate('HS512', payload, keystore, 'f0fd89c4abe83811ee9afa92d0d687f7'); 36 | console.log('Sample JWS token (HS512)\n'); 37 | console.log(token); 38 | 39 | parsed = jwt.parse(token).verify(keystore); 40 | console.log('\nToken is valid:', parsed.valid); 41 | console.log('\nHeader:\n'); 42 | console.log(parsed.header); 43 | 44 | console.log('\nVERIFICATION KEY NOT FOUND IN KEYSTORE\n'); 45 | 46 | token = jwt.generate('HS512', payload, keystore, 'f0fd89c4abe83811ee9afa92d0d687f7'); 47 | 48 | delete keystore['f0fd89c4abe83811ee9afa92d0d687f7']; 49 | 50 | parsed = jwt.parse(token).verify(keystore); 51 | console.log(parsed.error); 52 | 53 | console.log('\nTOKEN USING UNWANTED ALGORITHM\n'); 54 | 55 | token = jwt.generate('HS256', payload, key); 56 | 57 | parsed = jwt.parse(token) 58 | .setAlgorithmList(['HS384', 'HS512']) 59 | .verify(key); 60 | 61 | console.log(parsed.error); 62 | 63 | console.log('\nPARSING AND VERIFICATION AS SEPARATE STEPS - JWS EXAMPLE\n'); 64 | 65 | token = jwt.generate('HS512', payload, key); 66 | 67 | parsed = jwt.parse(token); 68 | console.log('\nHeader after parsing:\n'); 69 | console.log(parsed.header); 70 | console.log('\nPayload after parsing:\n'); 71 | console.log(parsed.payload); 72 | 73 | parsed.setTokenLifetime(600).verify(key); 74 | console.log('\nHeader after verification (unchanged):\n'); 75 | console.log(parsed.header); 76 | console.log('\nPayload after verification (unchanged):\n'); 77 | console.log(parsed.payload); 78 | 79 | console.log('\nPARSING AND VERIFICATION AS SEPARATE STEPS - JWE EXAMPLE\n'); 80 | 81 | token = jwt.generate('A256KW', 'A256GCM', payload, key); 82 | 83 | parsed = jwt.parse(token); 84 | console.log('\nHeader after parsing:\n'); 85 | console.log(parsed.header); 86 | console.log('\nPayload after parsing:\n'); 87 | console.log(parsed.payload); 88 | 89 | parsed.setTokenLifetime(600).verify(key); 90 | console.log('\nHeader after verification (unchanged) :\n'); 91 | console.log(parsed.header); 92 | console.log('\nPayload after verification (now visible):\n'); 93 | console.log(parsed.payload); 94 | 95 | console.log('\nEXPIRED TOKEN\n'); 96 | 97 | token = jwt.generate('HS512', payload, key); 98 | 99 | setTimeout(() => { 100 | parsed = jwt.parse(token).setTokenLifetime(3).verify(key); 101 | console.log('Expired:', parsed.expired); 102 | }, 5000); 103 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const jws = require('./lib/jws.js'); 2 | const jwe = require('./lib/jwe.js'); 3 | const { responder } = require('./lib/common.js'); 4 | 5 | // ===== TOKEN GENERATION ===================================================== 6 | 7 | exports.generate = (alg, ...rest) => { 8 | // alg, payload, key[, cb] or alg, payload, keystore, kid[, cb] 9 | if (rest[0].constructor === Object) return jws.generate(alg, ...rest); 10 | // alg, enc, payload, key[, cb] or alg, enc, payload, keystore, kid[, cb] 11 | if (rest[1].constructor === Object) return jwe.generate(alg, ...rest); 12 | // There is no payload object where expected 13 | let idx = rest.length - 1; 14 | if (idx > 1 && idx < 5 && typeof rest[idx] === 'function') { 15 | rest[idx](error); 16 | } else { 17 | throw error; 18 | } 19 | } 20 | 21 | // ===== TOKEN PARSING ======================================================== 22 | 23 | exports.parse = (token) => new ParsedToken(token); 24 | 25 | function ParsedToken(token) { 26 | this.parts = typeof token === 'string' ? token.split('.') : []; 27 | if (this.parts.length === 3) { 28 | this.type = 'JWS'; 29 | } else if (this.parts.length === 5) { 30 | this.type = 'JWE'; 31 | } else { 32 | this.error = { message: 'Invalid token' }; 33 | return; 34 | } 35 | try { 36 | this.header = JSON.parse(Buffer.from(this.parts[0], 'base64')); 37 | } catch (error) { 38 | this.error = { message: `Non parsable header. ${error.message}` }; 39 | return; 40 | } 41 | if (this.type === 'JWS') { 42 | // Parsing exposes payload for JWS only; for JWE happens at verify 43 | try { 44 | this.payload = JSON.parse(Buffer.from(this.parts[1], 'base64')); 45 | } catch (error) { 46 | this.error = { message: `Non parsable payload. ${error.message}` }; 47 | } 48 | } 49 | } 50 | 51 | // ===== POST-PARSING UTLITIES ================================================ 52 | 53 | ParsedToken.prototype.setAlgorithmList = function(algList, encList) { 54 | // algList is ignored if not string or array of strings 55 | if (typeof algList === 'string') { 56 | this.algList = [algList]; 57 | } else if (Array.isArray(algList)) { 58 | this.algList = algList; 59 | } 60 | // encList is ignored if not string or array of strings 61 | if (typeof encList === 'string') { 62 | this.encList = [encList]; 63 | } else if (Array.isArray(encList)) { 64 | this.encList = encList; 65 | } 66 | return this; 67 | } 68 | 69 | ParsedToken.prototype.setTokenLifetime = function(lifetime) { 70 | // lifetime is ignored if not integer greater than 0 71 | if (Number.isInteger(lifetime) && lifetime > 0) this.lifetime = lifetime; 72 | return this; 73 | } 74 | 75 | ParsedToken.prototype.setAudience = function(audList) { 76 | // audList is ignored if not string or array of strings 77 | if (typeof audList === 'string') { 78 | this.audList = [audList]; 79 | } else if (Array.isArray(audList)) { 80 | this.audList = audList; 81 | } 82 | return this; 83 | } 84 | 85 | ParsedToken.prototype.setIssuer = function(issList) { 86 | // issList is ignored if not string or array of strings 87 | if (typeof issList === 'string') { 88 | this.issList = [issList]; 89 | } else if (Array.isArray(issList)) { 90 | this.issList = issList; 91 | } 92 | return this; 93 | } 94 | 95 | // ===== TOKEN VERIFICATION =================================================== 96 | 97 | ParsedToken.prototype.verify = function(p0, cb) { 98 | // key[, cb] or keystore[, cb] 99 | cb = typeof cb === 'function' ? cb : undefined; 100 | if (this.error) return responder(null, this, cb); 101 | let key; 102 | if (p0.constructor !== Object) { 103 | key = p0; 104 | } else if (this.header.kid === undefined) { 105 | // Cannot extract key from keystore 106 | this.error = { message: 'Missing kid claim in header' }; 107 | return responder(null, this, cb); 108 | } else if (p0[this.header.kid] === undefined) { 109 | // Key not found in keystore 110 | this.error = { message: 'Key with id not found', kid: this.header.kid }; 111 | return responder(null, this, cb); 112 | } else { 113 | key = p0[this.header.kid]; 114 | } 115 | let verify = this.type === 'JWS' ? jws.verify : jwe.verify; 116 | return verify(this, key, cb); 117 | } 118 | -------------------------------------------------------------------------------- /lib/common.js: -------------------------------------------------------------------------------- 1 | exports.buf2b64url = (data) => { 2 | return data.toString('base64') 3 | .replace(/\+/g, '-') 4 | .replace(/\//g, '_') 5 | .replace(/=/g, ''); 6 | } 7 | 8 | function responder(error, result, callback) { 9 | if (callback) { 10 | callback(error, result); 11 | } else if (error) { 12 | throw error; 13 | } else { 14 | return result; 15 | } 16 | } 17 | 18 | exports.responder = responder; 19 | 20 | exports.payloadVerifications = (parsed, cb) => { 21 | if (parsed.audList) { 22 | if (parsed.payload.aud === undefined) { 23 | parsed.error = { message: 'Missing aud claim in payload' }; 24 | return responder(null, parsed, cb); 25 | } else if (!parsed.audList.includes(parsed.payload.aud)) { 26 | parsed.error = { message: 'Mismatching aud claim in payload' }; 27 | return responder(null, parsed, cb); 28 | } 29 | } 30 | if (parsed.issList) { 31 | if (parsed.payload.iss === undefined) { 32 | parsed.error = { message: 'Missing iss claim in payload' }; 33 | return responder(null, parsed, cb); 34 | } else if (!parsed.issList.includes(parsed.payload.iss)) { 35 | parsed.error = { message: 'Mismatching iss claim in payload' }; 36 | return responder(null, parsed, cb); 37 | } 38 | } 39 | let iat = Number(parsed.payload.iat); 40 | if (!iat) { 41 | parsed.error = { message: 'Missing or invalid iat claim in payload' }; 42 | return responder(null, parsed, cb); 43 | } 44 | let expiration = Number(parsed.payload.exp); 45 | if (parsed.lifetime) { 46 | let iatExp = iat + parsed.lifetime; 47 | if (!expiration || iatExp < expiration) expiration = iatExp; 48 | } 49 | if (expiration && Date.now() > expiration * 1000) { 50 | parsed.expired = expiration; 51 | return responder(null, parsed, cb); 52 | } 53 | parsed.valid = true; 54 | return responder(null, parsed, cb); 55 | } 56 | -------------------------------------------------------------------------------- /lib/ecdsa.js: -------------------------------------------------------------------------------- 1 | const ERR_MSG = 'Could not extract parameters from DER signature'; 2 | 3 | exports.derToConcat = (signature, size) => { 4 | let offset = 0; 5 | if (signature[offset++] !== 0x30) throw new Error(ERR_MSG); 6 | let seqLength = signature[offset++]; 7 | if (seqLength === 0x81) seqLength = signature[offset++]; 8 | if (seqLength > signature.length - offset || signature[offset++] !== 0x02) { 9 | throw new Error(ERR_MSG); 10 | } 11 | let rLength = signature[offset++]; 12 | if (rLength > signature.length - offset - 2 || rLength > size + 1) { 13 | throw new Error(ERR_MSG); 14 | } 15 | let rOffset = offset; 16 | offset += rLength; 17 | if (signature[offset++] !== 0x02) throw new Error(ERR_MSG); 18 | let sLength = signature[offset++]; 19 | if (sLength !== signature.length - offset || sLength > size + 1) { 20 | throw new Error(ERR_MSG); 21 | } 22 | let sOffset = offset; 23 | offset += sLength; 24 | if (offset !== signature.length) throw new Error(ERR_MSG); 25 | let rPadding = size - rLength; 26 | let sPadding = size - sLength; 27 | let dst = Buffer.allocUnsafe(rPadding + rLength + sPadding + sLength); 28 | for (offset = 0; offset < rPadding; offset++) dst[offset] = 0; 29 | let rPad = Math.max(-rPadding, 0); 30 | signature.copy(dst, offset, rOffset + rPad, rOffset + rLength); 31 | offset = size; 32 | for (let o = offset; offset < o + sPadding; offset++) dst[offset] = 0; 33 | let sPad = Math.max(-sPadding, 0); 34 | signature.copy(dst, offset, sOffset + sPad, sOffset + sLength); 35 | return dst; 36 | } 37 | 38 | exports.concatToDer = (signature, size) => { 39 | let rPadding = countPadding(signature, 0, size); 40 | let sPadding = countPadding(signature, size, signature.length); 41 | let rLength = size - rPadding; 42 | let sLength = size - sPadding; 43 | let rsBytes = rLength + sLength + 4; 44 | let shortLength = rsBytes < 0x80; 45 | let dst = Buffer.allocUnsafe((shortLength ? 2 : 3) + rsBytes); 46 | let offset = 0; 47 | dst[offset++] = 0x30; 48 | if (shortLength) { 49 | dst[offset++] = rsBytes; 50 | } else { 51 | dst[offset++] = 0x81; 52 | dst[offset++] = rsBytes & 0xFF; 53 | } 54 | dst[offset++] = 0x02; 55 | dst[offset++] = rLength; 56 | if (rPadding < 0) { 57 | dst[offset++] = 0; 58 | offset += signature.copy(dst, offset, 0, size); 59 | } else { 60 | offset += signature.copy(dst, offset, rPadding, size); 61 | } 62 | dst[offset++] = 0x02; 63 | dst[offset++] = sLength; 64 | if (sPadding < 0) { 65 | dst[offset++] = 0; 66 | signature.copy(dst, offset, size); 67 | } else { 68 | signature.copy(dst, offset, size + sPadding); 69 | } 70 | return dst; 71 | } 72 | 73 | function countPadding(buf, start, stop) { 74 | let padding = 0; 75 | while (start + padding < stop && buf[start + padding] === 0) padding++; 76 | let needsSign = buf[start + padding] >= 0x80; 77 | return needsSign ? --padding : padding; 78 | } 79 | -------------------------------------------------------------------------------- /lib/jwe.js: -------------------------------------------------------------------------------- 1 | const crypto = require('crypto'); 2 | const { responder, buf2b64url, payloadVerifications } = require('./common.js'); 3 | 4 | const ALG_RE = /^(PBES2-HS(256|384|512)\053)?(RSA-OAEP|dir|A(128|192|256)KW)$/; 5 | const ENC_RE = /^A(128|192|256)(GCM|CBC-HS(256|384|512))$/; 6 | 7 | // ===== JWE GENERATION ======================================================= 8 | 9 | exports.generate = (alg, enc, payload, ...rest) => { 10 | // alg, enc, payload, key[, cb] or alg, enc, payload, keystore, kid[, cb] 11 | let key; 12 | let cb; 13 | let header = { alg: alg, enc: enc }; 14 | if (rest[0].constructor !== Object) { 15 | key = rest[0]; 16 | cb = typeof rest[1] === 'function' ? rest[1] : undefined; 17 | } else { 18 | header.kid = rest[1]; 19 | key = rest[0][rest[1]]; 20 | cb = typeof rest[2] === 'function' ? rest[2] : undefined; 21 | if (!key) { 22 | return responder(new TypeError('Invalid key identifier'), null, cb); 23 | } 24 | } 25 | let aMatch = typeof alg === 'string' ? alg.match(ALG_RE) : null; 26 | if (!aMatch || (aMatch[2] && +aMatch[2] !== aMatch[4] * 2)) { 27 | let error = new TypeError('Unrecognized key management algorithm'); 28 | return responder(error, null, cb); 29 | } 30 | let eMatch = typeof enc === 'string' ? enc.match(ENC_RE) : null; 31 | if (!eMatch || (eMatch[3] && +eMatch[3] !== eMatch[1] * 2)) { 32 | let error = new TypeError('Unrecognized content encryption algorithm'); 33 | return responder(error, null, cb); 34 | } 35 | let salt; 36 | if (aMatch[2]) { 37 | let p2s = crypto.randomBytes(16); 38 | header.p2c = 1024; 39 | header.p2s = buf2b64url(p2s); 40 | salt = Buffer.concat([Buffer.from(alg), Buffer.from([0]), p2s]); 41 | if (!cb) { 42 | let bits = Number(aMatch[2]); 43 | key = crypto.pbkdf2Sync(key, salt, header.p2c, bits >> 4, `sha${bits}`); 44 | } 45 | } 46 | let aad = buf2b64url(Buffer.from(JSON.stringify(header))); 47 | if (!aMatch[2] || !cb) { 48 | return generateJwe(aMatch, eMatch, aad, payload, key, cb); 49 | } 50 | let bits = Number(aMatch[2]); 51 | crypto.pbkdf2(key, salt, header.p2c, bits >> 4, `sha${bits}`, (err, key) => { 52 | if (err) return cb(err); 53 | generateJwe(aMatch, eMatch, aad, payload, key, cb); 54 | }); 55 | } 56 | 57 | function generateJwe(aMatch, eMatch, aad, payload, key, cb) { 58 | let cekLen = eMatch[3] ? +eMatch[1] >> 2 : +eMatch[1] >> 3; 59 | let contEncr = eMatch[3] ? contentEncryptCbc : contentEncryptGcm; 60 | let cek; 61 | let cekEnc; 62 | if (aMatch[0] !== 'dir') { 63 | cek = crypto.randomBytes(cekLen); 64 | let keyEncr = aMatch[4] ? aesKeyWrap : rsaOaepEncrypt; 65 | try { 66 | cekEnc = keyEncr(cek, key, +aMatch[4]); 67 | } catch (error) { 68 | return responder(error, null, cb); 69 | } 70 | } else { 71 | // Key must be directly used for content encryption 72 | if (typeof key === 'string') { 73 | key = Buffer.from(key, 'base64'); 74 | } else if (!(key instanceof Buffer)) { 75 | let error = new TypeError('Key must be a buffer or a base64 string'); 76 | return responder(error, null, cb); 77 | } 78 | if (key.length < cekLen) { 79 | let error = new TypeError(`Key must be at least ${cekLen} bytes`); 80 | return responder(error, null, cb); 81 | } 82 | cek = key.slice(0, cekLen); 83 | cekEnc = ''; 84 | } 85 | payload.iat = Math.floor(Date.now() / 1000); 86 | let token = contEncr(aad, cek, cekEnc, JSON.stringify(payload), +eMatch[1]); 87 | return responder(null, token, cb); 88 | } 89 | 90 | // ===== JWE VERIFICATION ===================================================== 91 | 92 | exports.verify = (parsed, key, cb) => { 93 | if (parsed.error) return responder(null, parsed, cb); 94 | if (typeof parsed.header.alg !== 'string') { 95 | parsed.error = { message: 'Missing or invalid alg claim in header' }; 96 | return responder(null, parsed, cb); 97 | } 98 | if (typeof parsed.header.enc !== 'string') { 99 | parsed.error = { message: 'Missing or invalid enc claim in header' }; 100 | return responder(null, parsed, cb); 101 | } 102 | let aMatch = parsed.header.alg.match(ALG_RE); 103 | if (!aMatch || (aMatch[2] && +aMatch[2] !== aMatch[4] * 2)) { 104 | parsed.error = { 105 | message: `Unrecognized key management algorithm ${parsed.header.alg}` 106 | }; 107 | return responder(null, parsed, cb); 108 | } 109 | if (parsed.algList && !parsed.algList.includes(parsed.header.alg)) { 110 | parsed.error = { 111 | message: `Unwanted key management algorithm ${parsed.header.alg}` 112 | }; 113 | return responder(null, parsed, cb); 114 | } 115 | let eMatch = parsed.header.enc.match(ENC_RE); 116 | if (!eMatch || (eMatch[3] && +eMatch[3] !== eMatch[1] * 2)) { 117 | parsed.error = { 118 | message: `Unrecognized content encryption algorithm ${parsed.header.enc}` 119 | }; 120 | return responder(null, parsed, cb); 121 | } 122 | if (parsed.encList && !parsed.encList.includes(parsed.header.enc)) { 123 | parsed.error = { 124 | message: `Unwanted content encryption algorithm ${parsed.header.enc}` 125 | }; 126 | return responder(null, parsed, cb); 127 | } 128 | let salt; 129 | let iter; 130 | if (aMatch[2]) { 131 | iter = parsed.header.p2c; 132 | if (!Number.isInteger(iter) || iter < 1 || iter > 16384) { 133 | parsed.error = { message: 'Missing or invalid p2c claim in header' }; 134 | return responder(null, parsed, cb); 135 | } else if (!cb && iter > 1024) { 136 | parsed.error = { message: 'p2c value too large for synchronous mode' }; 137 | return responder(null, parsed, cb); 138 | } 139 | if (typeof parsed.header.p2s !== 'string') { 140 | parsed.error = { message: 'Missing or invalid p2s claim in header' }; 141 | return responder(null, parsed, cb); 142 | } 143 | let p2s = Buffer.from(parsed.header.p2s, 'base64'); 144 | let alg = parsed.header.alg; 145 | salt = Buffer.concat([Buffer.from(alg), Buffer.from([0]), p2s]); 146 | if (!cb) { 147 | let bits = Number(aMatch[2]); 148 | key = crypto.pbkdf2Sync(key, salt, iter, bits >> 4, `sha${bits}`); 149 | } 150 | } 151 | if (!aMatch[2] || !cb) { 152 | return decryptJwe(parsed, aMatch, eMatch, key, cb); 153 | } 154 | let bits = Number(aMatch[2]); 155 | crypto.pbkdf2(key, salt, iter, bits >> 4, `sha${bits}`, (error, key) => { 156 | if (error) return cb(error); 157 | decryptJwe(parsed, aMatch, eMatch, key, cb); 158 | }); 159 | } 160 | 161 | function decryptJwe(parsed, aMatch, eMatch, key, cb) { 162 | let aad = Buffer.from(parsed.parts[0]); 163 | let cekEnc = Buffer.from(parsed.parts[1], 'base64'); 164 | let iv = Buffer.from(parsed.parts[2], 'base64'); 165 | let content = Buffer.from(parsed.parts[3], 'base64'); 166 | let tag = Buffer.from(parsed.parts[4], 'base64'); 167 | let cekLen = eMatch[3] ? +eMatch[1] >> 2 : +eMatch[1] >> 3; 168 | let contDecr = eMatch[3] ? contentDecryptCbc : contentDecryptGcm; 169 | let cek; 170 | if (aMatch[0] !== 'dir') { 171 | let keyDecr = aMatch[4] ? aesKeyUnwrap : rsaOaepDecrypt; 172 | try { 173 | cek = keyDecr(cekEnc, key, +aMatch[4]); 174 | } catch (error) { 175 | parsed.error = { message: `Could not decrypt token. ${error.message}` }; 176 | return responder(null, parsed, cb); 177 | } 178 | } else { 179 | // Key must be directly used for content decryption 180 | if (typeof key === 'string') { 181 | key = Buffer.from(key, 'base64'); 182 | } else if (!(key instanceof Buffer)) { 183 | parsed.error = { message: 'Invalid key' }; 184 | return responder(null, parsed, cb); 185 | } 186 | if (key.length < cekLen) { 187 | parsed.error = { 188 | message: `Invalid key length. Must be at least ${cekLen} bytes` 189 | }; 190 | return responder(null, parsed, cb); 191 | } 192 | cek = key.slice(0, cekLen); 193 | } 194 | let plain; 195 | try { 196 | plain = contDecr(content, aad, tag, cek, iv, +eMatch[1]); 197 | } catch (error) { 198 | parsed.error = { message: `Could not decrypt token. ${error.message}` }; 199 | return responder(null, parsed, cb); 200 | } 201 | try { 202 | parsed.payload = JSON.parse(plain); 203 | } catch (error) { 204 | parsed.error = { message: `Non parsable payload. ${error.message}` }; 205 | return responder(null, parsed, cb); 206 | } 207 | return payloadVerifications(parsed, cb); 208 | } 209 | 210 | // ===== A128GCM, A192GCM, A256GCM ============================================ 211 | 212 | function contentEncryptGcm(aad, cek, cekEnc, plain, bits) { 213 | let iv = crypto.randomBytes(12); 214 | let cipher = crypto.createCipheriv(`id-aes${bits}-GCM`, cek, iv); 215 | cipher.setAutoPadding(false); 216 | cipher.setAAD(Buffer.from(aad)); 217 | let enc = buf2b64url(Buffer.concat([cipher.update(plain), cipher.final()])); 218 | let tag = buf2b64url(cipher.getAuthTag()); 219 | return `${aad}.${cekEnc}.${buf2b64url(iv)}.${enc}.${tag}`; 220 | } 221 | 222 | function contentDecryptGcm(content, aad, tag, cek, iv, bits) { 223 | let decipher = crypto.createDecipheriv(`id-aes${bits}-GCM`, cek, iv); 224 | decipher.setAutoPadding(false); 225 | decipher.setAAD(aad); 226 | decipher.setAuthTag(tag); 227 | return Buffer.concat([decipher.update(content), decipher.final()]); 228 | } 229 | 230 | // ===== A128CBC-HS256, A192CBC-HS384, A256CBC-HS512 ========================== 231 | 232 | function contentEncryptCbc(aad, cek, cekEnc, plain, bits) { 233 | let iv = crypto.randomBytes(16); 234 | let bytes = bits >> 3; 235 | let cipher = crypto.createCipheriv(`AES-${bits}-CBC`, cek.slice(bytes), iv); 236 | let enc = Buffer.concat([cipher.update(plain), cipher.final()]); 237 | let len = aad.length << 3; 238 | let al = Buffer.from(`000000000000000${len.toString(16)}`.slice(-16), 'hex'); 239 | let hmac = crypto.createHmac(`SHA${bits << 1}`, cek.slice(0, bytes)); 240 | hmac.update(Buffer.from(aad)).update(iv).update(enc).update(al); 241 | let tag = buf2b64url(hmac.digest().slice(0, bytes)); 242 | return `${aad}.${cekEnc}.${buf2b64url(iv)}.${buf2b64url(enc)}.${tag}`; 243 | } 244 | 245 | function contentDecryptCbc(content, aad, tag, cek, iv, bits) { 246 | let bytes = bits >> 3; 247 | let len = aad.length << 3; 248 | let al = Buffer.from(`000000000000000${len.toString(16)}`.slice(-16), 'hex'); 249 | let hmac = crypto.createHmac(`SHA${bits << 1}`, cek.slice(0, bytes)); 250 | hmac.update(aad).update(iv).update(content).update(al); 251 | if (!crypto.timingSafeEqual(hmac.digest().slice(0, bytes), tag)) { 252 | throw new Error('Authentication of encrypted data failed'); 253 | } 254 | let encKey = cek.slice(bytes); 255 | let decipher = crypto.createDecipheriv(`AES-${bits}-CBC`, encKey, iv); 256 | return Buffer.concat([decipher.update(content), decipher.final()]); 257 | } 258 | 259 | // ===== RSA-OAEP ============================================================= 260 | 261 | function rsaOaepEncrypt(cek, key) { 262 | if (key instanceof Buffer) { 263 | key = key.toString(); 264 | } else if (typeof key !== 'string') { 265 | throw new TypeError('Key must be a buffer or a string'); 266 | } 267 | if (!key.includes('-----BEGIN') || 268 | !(key.includes('KEY-----') || key.includes('CERTIFICATE-----'))) { 269 | throw new TypeError('Key must be a PEM formatted RSA public key'); 270 | } 271 | let options = {key: key, padding: crypto.constants.RSA_PKCS1_OAEP_PADDING}; 272 | return buf2b64url(crypto.publicEncrypt(options, cek)); 273 | } 274 | 275 | function rsaOaepDecrypt(cekEnc, key) { 276 | if (key instanceof Buffer) { 277 | key = key.toString(); 278 | } 279 | if (typeof key !== 'string' || 280 | !key.includes('-----BEGIN') || !key.includes('KEY-----')) { 281 | throw new TypeError('Key must be a PEM formatted RSA private key'); 282 | } 283 | let options = {key: key, padding: crypto.constants.RSA_PKCS1_OAEP_PADDING}; 284 | return crypto.privateDecrypt(options, cekEnc); 285 | } 286 | 287 | // ===== A128KW, A192KW, A256KW =============================================== 288 | 289 | function aesKeyWrap(cek, key, bits) { 290 | let bytes = bits >> 3; 291 | if (typeof key === 'string') { 292 | key = Buffer.from(key, 'base64'); 293 | } else if (!(key instanceof Buffer)) { 294 | throw new TypeError('Key must be a buffer or a base64-encoded string'); 295 | } 296 | if (key.length < bytes) { 297 | throw new TypeError(`Key length must be at least ${bytes} bytes`); 298 | } 299 | key = key.slice(0, bytes); 300 | let r = []; 301 | for (let i = 0; i < cek.length; i += 8) { 302 | r.push(cek.slice(i, i + 8)); 303 | } 304 | let iv = Buffer.alloc(16); 305 | let a = Buffer.from('A6A6A6A6A6A6A6A6', 'hex'); 306 | let count = 1; 307 | for (let j = 0; j < 6; j++) { 308 | for (let i = 0; i < r.length; i++) { 309 | let cipher = crypto.createCipheriv(`AES${bits}`, key, iv); 310 | let c = `000000000000000${count.toString(16)}`.slice(-16); 311 | let b = cipher.update(Buffer.concat([a, r[i]])); 312 | a = Buffer.from(c, 'hex'); 313 | for (let n = 0; n < 8; n++) { 314 | a[n] ^= b[n]; 315 | } 316 | r[i] = b.slice(8, 16); 317 | count++; 318 | } 319 | } 320 | return buf2b64url(Buffer.concat([a].concat(r))); 321 | } 322 | 323 | function aesKeyUnwrap(cekEnc, key, bits) { 324 | let bytes = bits >> 3; 325 | if (typeof key === 'string') { 326 | key = Buffer.from(key, 'base64'); 327 | } else if (!(key instanceof Buffer)) { 328 | throw new TypeError('Key must be a buffer or a base64 string'); 329 | } 330 | if (key.length < bytes) { 331 | throw new TypeError(`Key must be at least ${bytes} bytes`); 332 | } 333 | key = key.slice(0, bytes); 334 | let a = cekEnc.slice(0, 8); 335 | let r = []; 336 | for (let i = 8; i < cekEnc.length; i += 8) { 337 | r.push(cekEnc.slice(i, i + 8)); 338 | } 339 | let z = Buffer.alloc(16); 340 | let count = 6 * r.length; 341 | for (let j = 5; j >= 0 ; j--) { 342 | for (let i = r.length - 1; i >= 0; i--) { 343 | let c = `000000000000000${count.toString(16)}`.slice(-16); 344 | c = Buffer.from(c, 'hex'); 345 | for (let n = 0; n < 8; n++) { 346 | a[n] ^= c[n]; 347 | } 348 | let decipher = crypto.createDecipheriv(`AES${bits}`, key, z); 349 | let b = decipher.update(Buffer.concat([a, r[i], z])); 350 | a = b.slice(0, 8); 351 | r[i] = b.slice(8, 16); 352 | count--; 353 | } 354 | } 355 | if (!a.equals(Buffer.from('A6A6A6A6A6A6A6A6', 'hex'))) { 356 | throw new Error('Key unwrapping failed'); 357 | } 358 | return Buffer.concat(r); 359 | } 360 | -------------------------------------------------------------------------------- /lib/jws.js: -------------------------------------------------------------------------------- 1 | const crypto = require('crypto'); 2 | const ecdsa = require('./ecdsa.js'); 3 | const { responder, buf2b64url, payloadVerifications } = require('./common.js'); 4 | 5 | const ALG_RE = /^(HS|RS|ES)(256|384|512)$/; 6 | 7 | // ===== JWS GENERATION ======================================================= 8 | 9 | exports.generate = (alg, payload, ...rest) => { 10 | // alg, payload, key[, cb] or alg, payload, keystore, kid[, cb] 11 | let key; 12 | let cb; 13 | let header = { alg: alg }; 14 | if (rest[0].constructor !== Object) { 15 | key = rest[0]; 16 | cb = typeof rest[1] === 'function' ? rest[1] : undefined; 17 | } else { 18 | header.kid = rest[1]; 19 | key = rest[0][rest[1]]; 20 | cb = typeof rest[2] === 'function' ? rest[2] : undefined; 21 | if (!key) { 22 | return responder(new TypeError('Invalid key identifier'), null, cb); 23 | } 24 | } 25 | let match = typeof alg === 'string' ? alg.match(ALG_RE) : null; 26 | if (!match) { 27 | return responder(new TypeError('Unrecognized algorithm'), null, cb); 28 | } 29 | let generateJws; 30 | if (match[1] === 'HS') { 31 | generateJws = generateHsJws; 32 | } else if (match[1] === 'RS') { 33 | generateJws = generateRsJws; 34 | } else { 35 | generateJws = generateEsJws; 36 | } 37 | payload.iat = Math.floor(Date.now() / 1000); 38 | let h = buf2b64url(Buffer.from(JSON.stringify(header))); 39 | let p = buf2b64url(Buffer.from(JSON.stringify(payload))); 40 | let token; 41 | try { 42 | token = generateJws(`${h}.${p}`, +match[2], key); 43 | } catch (error) { 44 | return responder(error, null, cb); 45 | } 46 | return responder(null, token, cb); 47 | } 48 | 49 | // ===== JWS VERIFICATION ===================================================== 50 | 51 | exports.verify = (parsed, key, cb) => { 52 | if (parsed.error) return responder(null, parsed, cb); 53 | if (typeof parsed.header.alg !== 'string') { 54 | parsed.error = { message: 'Missing or invalid alg claim in header' }; 55 | return responder(null, parsed, cb); 56 | } 57 | let match = parsed.header.alg.match(ALG_RE); 58 | if (!match) { 59 | parsed.error = { message: `Unrecognized algorithm ${parsed.header.alg}` }; 60 | return responder(null, parsed, cb); 61 | } 62 | if (parsed.algList && !parsed.algList.includes(parsed.header.alg)) { 63 | parsed.error = { message: `Unwanted algorithm ${parsed.header.alg}` }; 64 | return responder(null, parsed, cb); 65 | } 66 | let protect = `${parsed.parts[0]}.${parsed.parts[1]}`; 67 | let verifyJws; 68 | if (match[1] === 'HS') { 69 | verifyJws = verifyHsJws; 70 | } else if (match[1] === 'RS') { 71 | verifyJws = verifyRsJws; 72 | } else { 73 | verifyJws = verifyEsJws; 74 | } 75 | let integrity; 76 | try { 77 | integrity = verifyJws(protect, parsed.parts[2], +match[2], key); 78 | } catch (error) { 79 | parsed.error = { message: `Could not verify integrity. ${error.message}` }; 80 | return responder(null, parsed, cb); 81 | } 82 | if (!integrity) { 83 | parsed.error = { message: 'Integrity check failed' }; 84 | return responder(null, parsed, cb); 85 | } 86 | return payloadVerifications(parsed, cb); 87 | } 88 | 89 | // ===== HS256, HS384, HS512 ================================================== 90 | 91 | function generateHsJws(protect, bits, key) { 92 | if (typeof key === 'string') { 93 | key = Buffer.from(key, 'base64'); 94 | } else if (!(key instanceof Buffer)) { 95 | throw new TypeError('Key must be a buffer or a base64 string'); 96 | } 97 | let bytes = bits >> 3; 98 | if (key.length < bytes) { 99 | throw new TypeError(`Key length must be at least ${bytes} bytes`); 100 | } 101 | let hmac = crypto.createHmac(`SHA${bits}`, key); 102 | let mac = buf2b64url(hmac.update(protect).digest()); 103 | return `${protect}.${mac}`; 104 | } 105 | 106 | function verifyHsJws(protect, mac, bits, key) { 107 | if (typeof key === 'string') { 108 | key = Buffer.from(key, 'base64'); 109 | } else if (!(key instanceof Buffer)) { 110 | throw new TypeError('Key must be a buffer or a base64 string'); 111 | } 112 | let bytes = bits >> 3; 113 | if (key.length < bytes) { 114 | throw new TypeError(`Key length must be at least ${bytes} bytes`); 115 | } 116 | let hmac = crypto.createHmac(`SHA${bits}`, key); 117 | hmac.update(protect); 118 | return crypto.timingSafeEqual(hmac.digest(), Buffer.from(mac, 'base64')); 119 | } 120 | 121 | // ===== RS256, RS384, RS512 ================================================== 122 | 123 | function generateRsJws(protect, bits, key) { 124 | if (key instanceof Buffer) { 125 | key = key.toString(); 126 | } else if (typeof key !== 'string') { 127 | throw new TypeError('Key must be a buffer or a UTF-8 string'); 128 | } 129 | if (!key.includes('-----BEGIN') || !key.includes('KEY-----')) { 130 | throw new TypeError('Key must be a PEM formatted RSA private key'); 131 | } 132 | let signer = crypto.createSign(`SHA${bits}`); 133 | let signature = buf2b64url(signer.update(protect).sign(key)); 134 | return `${protect}.${signature}`; 135 | } 136 | 137 | function verifyRsJws(protect, signature, bits, key) { 138 | if (key instanceof Buffer) { 139 | key = key.toString(); 140 | } else if (typeof key !== 'string') { 141 | throw new TypeError('Key must be a buffer or a UTF-8 string'); 142 | } 143 | if (!key.includes('-----BEGIN') || 144 | !(key.includes('KEY-----') || key.includes('CERTIFICATE-----'))) { 145 | throw new TypeError('Key must be a PEM formatted RSA public key'); 146 | } 147 | let verifier = crypto.createVerify(`SHA${bits}`); 148 | verifier.update(protect); 149 | return verifier.verify(key, signature, 'base64'); 150 | } 151 | 152 | // ===== ES256, ES384, ES512 ================================================== 153 | 154 | function generateEsJws(protect, bits, key) { 155 | if (key instanceof Buffer) { 156 | key = key.toString(); 157 | } else if (typeof key !== 'string') { 158 | throw new TypeError('Key must be a buffer or a UTF-8 string'); 159 | } 160 | if (!key.includes('-----BEGIN') || !key.includes('KEY-----')) { 161 | throw new TypeError('Key must be a PEM formatted EC private key'); 162 | } 163 | let signer = crypto.createSign(`SHA${bits}`); 164 | signer.update(protect); 165 | let size = 32; 166 | if (bits === 384) { 167 | size = 48; 168 | } else if (bits === 512) { 169 | size = 66; 170 | } 171 | let signature = buf2b64url(ecdsa.derToConcat(signer.sign(key), size)); 172 | return `${protect}.${signature}`; 173 | } 174 | 175 | function verifyEsJws(protect, signature, bits, key) { 176 | if (key instanceof Buffer) { 177 | key = key.toString(); 178 | } else if (typeof key !== 'string') { 179 | throw new TypeError('Key must be a buffer or a UTF-8 string'); 180 | } 181 | if (!key.includes('-----BEGIN') || 182 | !(key.includes('KEY-----') || key.includes('CERTIFICATE-----'))) { 183 | throw new TypeError('Key must be a PEM formatted EC public key'); 184 | } 185 | signature = Buffer.from(signature, 'base64'); 186 | let size = 32; 187 | if (bits === 384) { 188 | size = 48; 189 | } else if (bits === 512) { 190 | size = 66; 191 | } 192 | if (signature.length !== size << 1) { 193 | return false; 194 | } 195 | let verifier = crypto.createVerify(`SHA${bits}`); 196 | verifier.update(protect); 197 | return verifier.verify(key, ecdsa.concatToDer(signature, size)); 198 | } 199 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-webtokens", 3 | "version": "1.0.4", 4 | "description": "Simple, opinionated implementation of JWS and JWE compact serialization", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/teifip/node-webtokens.git" 9 | }, 10 | "keywords": [ 11 | "JWS", 12 | "JWE", 13 | "JWT", 14 | "JOSE", 15 | "JSON", 16 | "token" 17 | ], 18 | "engines": { 19 | "node": ">=6.6.0" 20 | }, 21 | "bugs": { 22 | "url": "https://github.com/teifip/node-webtokens/issues" 23 | }, 24 | "homepage": "https://github.com/teifip/node-webtokens#readme", 25 | "author": "Paolo Fiorini ", 26 | "license": "MIT" 27 | } 28 | -------------------------------------------------------------------------------- /test/jwe_basic.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const crypto = require('crypto'); 3 | const jwt = require('../index.js'); 4 | 5 | const KEYS_DIR = __dirname + '/pem_keys/'; 6 | 7 | const simKey = crypto.randomBytes(64); 8 | const pwd = 'My very secret password'; 9 | const priRsa = fs.readFileSync(KEYS_DIR + 'priRsa.key'); 10 | const pubRsa = fs.readFileSync(KEYS_DIR + 'pubRsa.key'); 11 | const priEc256 = fs.readFileSync(KEYS_DIR + 'priEc256.key'); 12 | const pubEc256 = fs.readFileSync(KEYS_DIR + 'pubEc256.key'); 13 | const priEc384 = fs.readFileSync(KEYS_DIR + 'priEc384.key'); 14 | const pubEc384 = fs.readFileSync(KEYS_DIR + 'pubEc384.key'); 15 | const priEc521 = fs.readFileSync(KEYS_DIR + 'priEc521.key'); 16 | const pubEc521 = fs.readFileSync(KEYS_DIR + 'pubEc521.key'); 17 | 18 | const payload = { 19 | iss: 'auth.mydomain.com', 20 | aud: 'A1B2C3D4E5.com.mydomain.myservice', 21 | sub: 'jack.sparrow@example.com', 22 | info: 'Hello World!', 23 | list: [1, 2, 3, 4] 24 | }; 25 | 26 | const tests = [ 27 | { 28 | alg: 'dir', 29 | enc: 'A128CBC-HS256', 30 | eKey: simKey, 31 | vKey: simKey, 32 | label: 'key as buffer at both encryption and decryption' 33 | }, 34 | { 35 | alg: 'dir', 36 | enc: 'A192CBC-HS384', 37 | eKey: simKey, 38 | vKey: simKey, 39 | label: 'key as buffer at both encryption and decryption' 40 | }, 41 | { 42 | alg: 'dir', 43 | enc: 'A256CBC-HS512', 44 | eKey: simKey, 45 | vKey: simKey, 46 | label: 'key as buffer at both encryption and decryption' 47 | }, 48 | { 49 | alg: 'dir', 50 | enc: 'A128GCM', 51 | eKey: simKey, 52 | vKey: simKey, 53 | label: 'key as buffer at both encryption and decryption' 54 | }, 55 | { 56 | alg: 'dir', 57 | enc: 'A192GCM', 58 | eKey: simKey, 59 | vKey: simKey, 60 | label: 'key as buffer at both encryption and decryption' 61 | }, 62 | { 63 | alg: 'dir', 64 | enc: 'A256GCM', 65 | eKey: simKey, 66 | vKey: simKey, 67 | label: 'key as buffer at both encryption and decryption' 68 | }, 69 | { 70 | alg: 'dir', 71 | enc: 'A128CBC-HS256', 72 | eKey: simKey, 73 | vKey: simKey.toString('base64'), 74 | label: 'key as buffer at encryption, as base64 string at decryption' 75 | }, 76 | { 77 | alg: 'dir', 78 | enc: 'A192CBC-HS384', 79 | eKey: simKey, 80 | vKey: simKey.toString('base64'), 81 | label: 'key as buffer at encryption, as base64 string at decryption' 82 | }, 83 | { 84 | alg: 'dir', 85 | enc: 'A256CBC-HS512', 86 | eKey: simKey, 87 | vKey: simKey.toString('base64'), 88 | label: 'key as buffer at encryption, as base64 string at decryption' 89 | }, 90 | { 91 | alg: 'dir', 92 | enc: 'A128GCM', 93 | eKey: simKey, 94 | vKey: simKey.toString('base64'), 95 | label: 'key as buffer at encryption, as base64 string at decryption' 96 | }, 97 | { 98 | alg: 'dir', 99 | enc: 'A192GCM', 100 | eKey: simKey, 101 | vKey: simKey.toString('base64'), 102 | label: 'key as buffer at encryption, as base64 string at decryption' 103 | }, 104 | { 105 | alg: 'dir', 106 | enc: 'A256GCM', 107 | eKey: simKey, 108 | vKey: simKey.toString('base64'), 109 | label: 'key as buffer at encryption, as base64 string at decryption' 110 | }, 111 | { 112 | alg: 'dir', 113 | enc: 'A128CBC-HS256', 114 | eKey: simKey.toString('base64'), 115 | vKey: simKey, 116 | label: 'key as base64 string at encryption, as buffer at decryption' 117 | }, 118 | { 119 | alg: 'dir', 120 | enc: 'A192CBC-HS384', 121 | eKey: simKey.toString('base64'), 122 | vKey: simKey, 123 | label: 'key as base64 string at encryption, as buffer at decryption' 124 | }, 125 | { 126 | alg: 'dir', 127 | enc: 'A256CBC-HS512', 128 | eKey: simKey.toString('base64'), 129 | vKey: simKey, 130 | label: 'key as base64 string at encryption, as buffer at decryption' 131 | }, 132 | { 133 | alg: 'dir', 134 | enc: 'A128GCM', 135 | eKey: simKey.toString('base64'), 136 | vKey: simKey, 137 | label: 'key as base64 string at encryption, as buffer at decryption' 138 | }, 139 | { 140 | alg: 'dir', 141 | enc: 'A192GCM', 142 | eKey: simKey.toString('base64'), 143 | vKey: simKey, 144 | label: 'key as base64 string at encryption, as buffer at decryption' 145 | }, 146 | { 147 | alg: 'dir', 148 | enc: 'A256GCM', 149 | eKey: simKey.toString('base64'), 150 | vKey: simKey, 151 | label: 'key as base64 string at encryption, as buffer at decryption' 152 | }, 153 | { 154 | alg: 'dir', 155 | enc: 'A128CBC-HS256', 156 | eKey: simKey.toString('base64'), 157 | vKey: simKey.toString('base64'), 158 | label: 'key as base64 string at both encryption and decryption' 159 | }, 160 | { 161 | alg: 'dir', 162 | enc: 'A192CBC-HS384', 163 | eKey: simKey.toString('base64'), 164 | vKey: simKey.toString('base64'), 165 | label: 'key as base64 string at both encryption and decryption' 166 | }, 167 | { 168 | alg: 'dir', 169 | enc: 'A256CBC-HS512', 170 | eKey: simKey.toString('base64'), 171 | vKey: simKey.toString('base64'), 172 | label: 'key as base64 string at both encryption and decryption' 173 | }, 174 | { 175 | alg: 'dir', 176 | enc: 'A128GCM', 177 | eKey: simKey.toString('base64'), 178 | vKey: simKey.toString('base64'), 179 | label: 'key as base64 string at both encryption and decryption' 180 | }, 181 | { 182 | alg: 'dir', 183 | enc: 'A192GCM', 184 | eKey: simKey.toString('base64'), 185 | vKey: simKey.toString('base64'), 186 | label: 'key as base64 string at both encryption and decryption' 187 | }, 188 | { 189 | alg: 'dir', 190 | enc: 'A256GCM', 191 | eKey: simKey.toString('base64'), 192 | vKey: simKey.toString('base64'), 193 | label: 'key as base64 string at both encryption and decryption' 194 | }, 195 | { 196 | alg: 'RSA-OAEP', 197 | enc: 'A128CBC-HS256', 198 | eKey: pubRsa, 199 | vKey: priRsa, 200 | label: 'both private and public key as buffer' 201 | }, 202 | { 203 | alg: 'RSA-OAEP', 204 | enc: 'A192CBC-HS384', 205 | eKey: pubRsa, 206 | vKey: priRsa, 207 | label: 'both private and public key as buffer' 208 | }, 209 | { 210 | alg: 'RSA-OAEP', 211 | enc: 'A256CBC-HS512', 212 | eKey: pubRsa, 213 | vKey: priRsa, 214 | label: 'both private and public key as buffer' 215 | }, 216 | { 217 | alg: 'RSA-OAEP', 218 | enc: 'A128GCM', 219 | eKey: pubRsa, 220 | vKey: priRsa, 221 | label: 'both private and public key as buffer' 222 | }, 223 | { 224 | alg: 'RSA-OAEP', 225 | enc: 'A192GCM', 226 | eKey: pubRsa, 227 | vKey: priRsa, 228 | label: 'both private and public key as buffer' 229 | }, 230 | { 231 | alg: 'RSA-OAEP', 232 | enc: 'A256GCM', 233 | eKey: pubRsa, 234 | vKey: priRsa, 235 | label: 'both private and public key as buffer' 236 | }, 237 | { 238 | alg: 'RSA-OAEP', 239 | enc: 'A128CBC-HS256', 240 | eKey: pubRsa, 241 | vKey: priRsa.toString(), 242 | label: 'public key as buffer, private key as UTF-8 string' 243 | }, 244 | { 245 | alg: 'RSA-OAEP', 246 | enc: 'A192CBC-HS384', 247 | eKey: pubRsa, 248 | vKey: priRsa.toString(), 249 | label: 'public key as buffer, private key as UTF-8 string' 250 | }, 251 | { 252 | alg: 'RSA-OAEP', 253 | enc: 'A256CBC-HS512', 254 | eKey: pubRsa, 255 | vKey: priRsa.toString(), 256 | label: 'public key as buffer, private key as UTF-8 string' 257 | }, 258 | { 259 | alg: 'RSA-OAEP', 260 | enc: 'A128GCM', 261 | eKey: pubRsa, 262 | vKey: priRsa.toString(), 263 | label: 'public key as buffer, private key as UTF-8 string' 264 | }, 265 | { 266 | alg: 'RSA-OAEP', 267 | enc: 'A192GCM', 268 | eKey: pubRsa, 269 | vKey: priRsa.toString(), 270 | label: 'public key as buffer, private key as UTF-8 string' 271 | }, 272 | { 273 | alg: 'RSA-OAEP', 274 | enc: 'A256GCM', 275 | eKey: pubRsa, 276 | vKey: priRsa.toString(), 277 | label: 'public key as buffer, private key as UTF-8 string' 278 | }, 279 | { 280 | alg: 'RSA-OAEP', 281 | enc: 'A128CBC-HS256', 282 | eKey: pubRsa.toString(), 283 | vKey: priRsa, 284 | label: 'public key as UTF-8 string, private key as buffer' 285 | }, 286 | { 287 | alg: 'RSA-OAEP', 288 | enc: 'A192CBC-HS384', 289 | eKey: pubRsa.toString(), 290 | vKey: priRsa, 291 | label: 'public key as UTF-8 string, private key as buffer' 292 | }, 293 | { 294 | alg: 'RSA-OAEP', 295 | enc: 'A256CBC-HS512', 296 | eKey: pubRsa.toString(), 297 | vKey: priRsa, 298 | label: 'public key as UTF-8 string, private key as buffer' 299 | }, 300 | { 301 | alg: 'RSA-OAEP', 302 | enc: 'A128GCM', 303 | eKey: pubRsa.toString(), 304 | vKey: priRsa, 305 | label: 'public key as UTF-8 string, private key as buffer' 306 | }, 307 | { 308 | alg: 'RSA-OAEP', 309 | enc: 'A192GCM', 310 | eKey: pubRsa.toString(), 311 | vKey: priRsa, 312 | label: 'public key as UTF-8 string, private key as buffer' 313 | }, 314 | { 315 | alg: 'RSA-OAEP', 316 | enc: 'A256GCM', 317 | eKey: pubRsa.toString(), 318 | vKey: priRsa, 319 | label: 'public key as UTF-8 string, private key as buffer' 320 | }, 321 | { 322 | alg: 'RSA-OAEP', 323 | enc: 'A128CBC-HS256', 324 | eKey: pubRsa.toString(), 325 | vKey: priRsa.toString(), 326 | label: 'both private and public key as UTF-8 string' 327 | }, 328 | { 329 | alg: 'RSA-OAEP', 330 | enc: 'A192CBC-HS384', 331 | eKey: pubRsa.toString(), 332 | vKey: priRsa.toString(), 333 | label: 'both private and public key as UTF-8 string' 334 | }, 335 | { 336 | alg: 'RSA-OAEP', 337 | enc: 'A256CBC-HS512', 338 | eKey: pubRsa.toString(), 339 | vKey: priRsa.toString(), 340 | label: 'both private and public key as UTF-8 string' 341 | }, 342 | { 343 | alg: 'RSA-OAEP', 344 | enc: 'A128GCM', 345 | eKey: pubRsa.toString(), 346 | vKey: priRsa.toString(), 347 | label: 'both private and public key as UTF-8 string' 348 | }, 349 | { 350 | alg: 'RSA-OAEP', 351 | enc: 'A192GCM', 352 | eKey: pubRsa.toString(), 353 | vKey: priRsa.toString(), 354 | label: 'both private and public key as UTF-8 string' 355 | }, 356 | { 357 | alg: 'RSA-OAEP', 358 | enc: 'A256GCM', 359 | eKey: pubRsa.toString(), 360 | vKey: priRsa.toString(), 361 | label: 'both private and public key as UTF-8 string' 362 | }, 363 | { 364 | alg: 'A128KW', 365 | enc: 'A128CBC-HS256', 366 | eKey: simKey, 367 | vKey: simKey, 368 | label: 'key as buffer at both encryption and decryption' 369 | }, 370 | { 371 | alg: 'A128KW', 372 | enc: 'A192CBC-HS384', 373 | eKey: simKey, 374 | vKey: simKey, 375 | label: 'key as buffer at both encryption and decryption' 376 | }, 377 | { 378 | alg: 'A128KW', 379 | enc: 'A256CBC-HS512', 380 | eKey: simKey, 381 | vKey: simKey, 382 | label: 'key as buffer at both encryption and decryption' 383 | }, 384 | { 385 | alg: 'A128KW', 386 | enc: 'A128GCM', 387 | eKey: simKey, 388 | vKey: simKey, 389 | label: 'key as buffer at both encryption and decryption' 390 | }, 391 | { 392 | alg: 'A128KW', 393 | enc: 'A192GCM', 394 | eKey: simKey, 395 | vKey: simKey, 396 | label: 'key as buffer at both encryption and decryption' 397 | }, 398 | { 399 | alg: 'A128KW', 400 | enc: 'A256GCM', 401 | eKey: simKey, 402 | vKey: simKey, 403 | label: 'key as buffer at both encryption and decryption' 404 | }, 405 | { 406 | alg: 'A192KW', 407 | enc: 'A128CBC-HS256', 408 | eKey: simKey, 409 | vKey: simKey, 410 | label: 'key as buffer at both encryption and decryption' 411 | }, 412 | { 413 | alg: 'A192KW', 414 | enc: 'A192CBC-HS384', 415 | eKey: simKey, 416 | vKey: simKey, 417 | label: 'key as buffer at both encryption and decryption' 418 | }, 419 | { 420 | alg: 'A192KW', 421 | enc: 'A256CBC-HS512', 422 | eKey: simKey, 423 | vKey: simKey, 424 | label: 'key as buffer at both encryption and decryption' 425 | }, 426 | { 427 | alg: 'A192KW', 428 | enc: 'A128GCM', 429 | eKey: simKey, 430 | vKey: simKey, 431 | label: 'key as buffer at both encryption and decryption' 432 | }, 433 | { 434 | alg: 'A192KW', 435 | enc: 'A192GCM', 436 | eKey: simKey, 437 | vKey: simKey, 438 | label: 'key as buffer at both encryption and decryption' 439 | }, 440 | { 441 | alg: 'A192KW', 442 | enc: 'A256GCM', 443 | eKey: simKey, 444 | vKey: simKey, 445 | label: 'key as buffer at both encryption and decryption' 446 | }, 447 | { 448 | alg: 'A256KW', 449 | enc: 'A128CBC-HS256', 450 | eKey: simKey, 451 | vKey: simKey, 452 | label: 'key as buffer at both encryption and decryption' 453 | }, 454 | { 455 | alg: 'A256KW', 456 | enc: 'A192CBC-HS384', 457 | eKey: simKey, 458 | vKey: simKey, 459 | label: 'key as buffer at both encryption and decryption' 460 | }, 461 | { 462 | alg: 'A256KW', 463 | enc: 'A256CBC-HS512', 464 | eKey: simKey, 465 | vKey: simKey, 466 | label: 'key as buffer at both encryption and decryption' 467 | }, 468 | { 469 | alg: 'A256KW', 470 | enc: 'A128GCM', 471 | eKey: simKey, 472 | vKey: simKey, 473 | label: 'key as buffer at both encryption and decryption' 474 | }, 475 | { 476 | alg: 'A256KW', 477 | enc: 'A192GCM', 478 | eKey: simKey, 479 | vKey: simKey, 480 | label: 'key as buffer at both encryption and decryption' 481 | }, 482 | { 483 | alg: 'A256KW', 484 | enc: 'A256GCM', 485 | eKey: simKey, 486 | vKey: simKey, 487 | label: 'key as buffer at both encryption and decryption' 488 | }, 489 | { 490 | alg: 'A128KW', 491 | enc: 'A128CBC-HS256', 492 | eKey: simKey, 493 | vKey: simKey.toString('base64'), 494 | label: 'key as buffer at encryption, as base64 string at decryption' 495 | }, 496 | { 497 | alg: 'A128KW', 498 | enc: 'A192CBC-HS384', 499 | eKey: simKey, 500 | vKey: simKey.toString('base64'), 501 | label: 'key as buffer at encryption, as base64 string at decryption' 502 | }, 503 | { 504 | alg: 'A128KW', 505 | enc: 'A256CBC-HS512', 506 | eKey: simKey, 507 | vKey: simKey.toString('base64'), 508 | label: 'key as buffer at encryption, as base64 string at decryption' 509 | }, 510 | { 511 | alg: 'A128KW', 512 | enc: 'A128GCM', 513 | eKey: simKey, 514 | vKey: simKey.toString('base64'), 515 | label: 'key as buffer at encryption, as base64 string at decryption' 516 | }, 517 | { 518 | alg: 'A128KW', 519 | enc: 'A192GCM', 520 | eKey: simKey, 521 | vKey: simKey.toString('base64'), 522 | label: 'key as buffer at encryption, as base64 string at decryption' 523 | }, 524 | { 525 | alg: 'A128KW', 526 | enc: 'A256GCM', 527 | eKey: simKey, 528 | vKey: simKey.toString('base64'), 529 | label: 'key as buffer at encryption, as base64 string at decryption' 530 | }, 531 | { 532 | alg: 'A192KW', 533 | enc: 'A128CBC-HS256', 534 | eKey: simKey, 535 | vKey: simKey.toString('base64'), 536 | label: 'key as buffer at encryption, as base64 string at decryption' 537 | }, 538 | { 539 | alg: 'A192KW', 540 | enc: 'A192CBC-HS384', 541 | eKey: simKey, 542 | vKey: simKey.toString('base64'), 543 | label: 'key as buffer at encryption, as base64 string at decryption' 544 | }, 545 | { 546 | alg: 'A192KW', 547 | enc: 'A256CBC-HS512', 548 | eKey: simKey, 549 | vKey: simKey.toString('base64'), 550 | label: 'key as buffer at encryption, as base64 string at decryption' 551 | }, 552 | { 553 | alg: 'A192KW', 554 | enc: 'A128GCM', 555 | eKey: simKey, 556 | vKey: simKey.toString('base64'), 557 | label: 'key as buffer at encryption, as base64 string at decryption' 558 | }, 559 | { 560 | alg: 'A192KW', 561 | enc: 'A192GCM', 562 | eKey: simKey, 563 | vKey: simKey.toString('base64'), 564 | label: 'key as buffer at encryption, as base64 string at decryption' 565 | }, 566 | { 567 | alg: 'A192KW', 568 | enc: 'A256GCM', 569 | eKey: simKey, 570 | vKey: simKey.toString('base64'), 571 | label: 'key as buffer at encryption, as base64 string at decryption' 572 | }, 573 | { 574 | alg: 'A256KW', 575 | enc: 'A128CBC-HS256', 576 | eKey: simKey, 577 | vKey: simKey.toString('base64'), 578 | label: 'key as buffer at encryption, as base64 string at decryption' 579 | }, 580 | { 581 | alg: 'A256KW', 582 | enc: 'A192CBC-HS384', 583 | eKey: simKey, 584 | vKey: simKey.toString('base64'), 585 | label: 'key as buffer at encryption, as base64 string at decryption' 586 | }, 587 | { 588 | alg: 'A256KW', 589 | enc: 'A256CBC-HS512', 590 | eKey: simKey, 591 | vKey: simKey.toString('base64'), 592 | label: 'key as buffer at encryption, as base64 string at decryption' 593 | }, 594 | { 595 | alg: 'A256KW', 596 | enc: 'A128GCM', 597 | eKey: simKey, 598 | vKey: simKey.toString('base64'), 599 | label: 'key as buffer at encryption, as base64 string at decryption' 600 | }, 601 | { 602 | alg: 'A256KW', 603 | enc: 'A192GCM', 604 | eKey: simKey, 605 | vKey: simKey.toString('base64'), 606 | label: 'key as buffer at encryption, as base64 string at decryption' 607 | }, 608 | { 609 | alg: 'A256KW', 610 | enc: 'A256GCM', 611 | eKey: simKey, 612 | vKey: simKey.toString('base64'), 613 | label: 'key as buffer at encryption, as base64 string at decryption' 614 | }, 615 | { 616 | alg: 'A128KW', 617 | enc: 'A128CBC-HS256', 618 | eKey: simKey.toString('base64'), 619 | vKey: simKey, 620 | label: 'key as base64 string at encryption, as buffer at decryption' 621 | }, 622 | { 623 | alg: 'A128KW', 624 | enc: 'A192CBC-HS384', 625 | eKey: simKey.toString('base64'), 626 | vKey: simKey, 627 | label: 'key as base64 string at encryption, as buffer at decryption' 628 | }, 629 | { 630 | alg: 'A128KW', 631 | enc: 'A256CBC-HS512', 632 | eKey: simKey.toString('base64'), 633 | vKey: simKey, 634 | label: 'key as base64 string at encryption, as buffer at decryption' 635 | }, 636 | { 637 | alg: 'A128KW', 638 | enc: 'A128GCM', 639 | eKey: simKey.toString('base64'), 640 | vKey: simKey, 641 | label: 'key as base64 string at encryption, as buffer at decryption' 642 | }, 643 | { 644 | alg: 'A128KW', 645 | enc: 'A192GCM', 646 | eKey: simKey.toString('base64'), 647 | vKey: simKey, 648 | label: 'key as base64 string at encryption, as buffer at decryption' 649 | }, 650 | { 651 | alg: 'A128KW', 652 | enc: 'A256GCM', 653 | eKey: simKey.toString('base64'), 654 | vKey: simKey, 655 | label: 'key as base64 string at encryption, as buffer at decryption' 656 | }, 657 | { 658 | alg: 'A192KW', 659 | enc: 'A128CBC-HS256', 660 | eKey: simKey.toString('base64'), 661 | vKey: simKey, 662 | label: 'key as base64 string at encryption, as buffer at decryption' 663 | }, 664 | { 665 | alg: 'A192KW', 666 | enc: 'A192CBC-HS384', 667 | eKey: simKey.toString('base64'), 668 | vKey: simKey, 669 | label: 'key as base64 string at encryption, as buffer at decryption' 670 | }, 671 | { 672 | alg: 'A192KW', 673 | enc: 'A256CBC-HS512', 674 | eKey: simKey.toString('base64'), 675 | vKey: simKey, 676 | label: 'key as base64 string at encryption, as buffer at decryption' 677 | }, 678 | { 679 | alg: 'A192KW', 680 | enc: 'A128GCM', 681 | eKey: simKey.toString('base64'), 682 | vKey: simKey, 683 | label: 'key as base64 string at encryption, as buffer at decryption' 684 | }, 685 | { 686 | alg: 'A192KW', 687 | enc: 'A192GCM', 688 | eKey: simKey.toString('base64'), 689 | vKey: simKey, 690 | label: 'key as base64 string at encryption, as buffer at decryption' 691 | }, 692 | { 693 | alg: 'A192KW', 694 | enc: 'A256GCM', 695 | eKey: simKey.toString('base64'), 696 | vKey: simKey, 697 | label: 'key as base64 string at encryption, as buffer at decryption' 698 | }, 699 | { 700 | alg: 'A256KW', 701 | enc: 'A128CBC-HS256', 702 | eKey: simKey.toString('base64'), 703 | vKey: simKey, 704 | label: 'key as base64 string at encryption, as buffer at decryption' 705 | }, 706 | { 707 | alg: 'A256KW', 708 | enc: 'A192CBC-HS384', 709 | eKey: simKey.toString('base64'), 710 | vKey: simKey, 711 | label: 'key as base64 string at encryption, as buffer at decryption' 712 | }, 713 | { 714 | alg: 'A256KW', 715 | enc: 'A256CBC-HS512', 716 | eKey: simKey.toString('base64'), 717 | vKey: simKey, 718 | label: 'key as base64 string at encryption, as buffer at decryption' 719 | }, 720 | { 721 | alg: 'A256KW', 722 | enc: 'A128GCM', 723 | eKey: simKey.toString('base64'), 724 | vKey: simKey, 725 | label: 'key as base64 string at encryption, as buffer at decryption' 726 | }, 727 | { 728 | alg: 'A256KW', 729 | enc: 'A192GCM', 730 | eKey: simKey.toString('base64'), 731 | vKey: simKey, 732 | label: 'key as base64 string at encryption, as buffer at decryption' 733 | }, 734 | { 735 | alg: 'A256KW', 736 | enc: 'A256GCM', 737 | eKey: simKey.toString('base64'), 738 | vKey: simKey, 739 | label: 'key as base64 string at encryption, as buffer at decryption' 740 | }, 741 | { 742 | alg: 'A128KW', 743 | enc: 'A128CBC-HS256', 744 | eKey: simKey.toString('base64'), 745 | vKey: simKey.toString('base64'), 746 | label: 'key as base64 string at both encryption and decryption' 747 | }, 748 | { 749 | alg: 'A128KW', 750 | enc: 'A192CBC-HS384', 751 | eKey: simKey.toString('base64'), 752 | vKey: simKey.toString('base64'), 753 | label: 'key as base64 string at both encryption and decryption' 754 | }, 755 | { 756 | alg: 'A128KW', 757 | enc: 'A256CBC-HS512', 758 | eKey: simKey.toString('base64'), 759 | vKey: simKey.toString('base64'), 760 | label: 'key as base64 string at both encryption and decryption' 761 | }, 762 | { 763 | alg: 'A128KW', 764 | enc: 'A128GCM', 765 | eKey: simKey.toString('base64'), 766 | vKey: simKey.toString('base64'), 767 | label: 'key as base64 string at both encryption and decryption' 768 | }, 769 | { 770 | alg: 'A128KW', 771 | enc: 'A192GCM', 772 | eKey: simKey.toString('base64'), 773 | vKey: simKey.toString('base64'), 774 | label: 'key as base64 string at both encryption and decryption' 775 | }, 776 | { 777 | alg: 'A128KW', 778 | enc: 'A256GCM', 779 | eKey: simKey.toString('base64'), 780 | vKey: simKey.toString('base64'), 781 | label: 'key as base64 string at both encryption and decryption' 782 | }, 783 | { 784 | alg: 'A192KW', 785 | enc: 'A128CBC-HS256', 786 | eKey: simKey.toString('base64'), 787 | vKey: simKey.toString('base64'), 788 | label: 'key as base64 string at both encryption and decryption' 789 | }, 790 | { 791 | alg: 'A192KW', 792 | enc: 'A192CBC-HS384', 793 | eKey: simKey.toString('base64'), 794 | vKey: simKey.toString('base64'), 795 | label: 'key as base64 string at both encryption and decryption' 796 | }, 797 | { 798 | alg: 'A192KW', 799 | enc: 'A256CBC-HS512', 800 | eKey: simKey.toString('base64'), 801 | vKey: simKey.toString('base64'), 802 | label: 'key as base64 string at both encryption and decryption' 803 | }, 804 | { 805 | alg: 'A192KW', 806 | enc: 'A128GCM', 807 | eKey: simKey.toString('base64'), 808 | vKey: simKey.toString('base64'), 809 | label: 'key as base64 string at both encryption and decryption' 810 | }, 811 | { 812 | alg: 'A192KW', 813 | enc: 'A192GCM', 814 | eKey: simKey.toString('base64'), 815 | vKey: simKey.toString('base64'), 816 | label: 'key as base64 string at both encryption and decryption' 817 | }, 818 | { 819 | alg: 'A192KW', 820 | enc: 'A256GCM', 821 | eKey: simKey.toString('base64'), 822 | vKey: simKey.toString('base64'), 823 | label: 'key as base64 string at both encryption and decryption' 824 | }, 825 | { 826 | alg: 'A256KW', 827 | enc: 'A128CBC-HS256', 828 | eKey: simKey.toString('base64'), 829 | vKey: simKey.toString('base64'), 830 | label: 'key as base64 string at both encryption and decryption' 831 | }, 832 | { 833 | alg: 'A256KW', 834 | enc: 'A192CBC-HS384', 835 | eKey: simKey.toString('base64'), 836 | vKey: simKey.toString('base64'), 837 | label: 'key as base64 string at both encryption and decryption' 838 | }, 839 | { 840 | alg: 'A256KW', 841 | enc: 'A256CBC-HS512', 842 | eKey: simKey.toString('base64'), 843 | vKey: simKey.toString('base64'), 844 | label: 'key as base64 string at both encryption and decryption' 845 | }, 846 | { 847 | alg: 'A256KW', 848 | enc: 'A128GCM', 849 | eKey: simKey.toString('base64'), 850 | vKey: simKey.toString('base64'), 851 | label: 'key as base64 string at both encryption and decryption' 852 | }, 853 | { 854 | alg: 'A256KW', 855 | enc: 'A192GCM', 856 | eKey: simKey.toString('base64'), 857 | vKey: simKey.toString('base64'), 858 | label: 'key as base64 string at both encryption and decryption' 859 | }, 860 | { 861 | alg: 'A256KW', 862 | enc: 'A256GCM', 863 | eKey: simKey.toString('base64'), 864 | vKey: simKey.toString('base64'), 865 | label: 'key as base64 string at both encryption and decryption' 866 | }, 867 | { 868 | alg: 'PBES2-HS256+A128KW', 869 | enc: 'A128CBC-HS256', 870 | eKey: pwd, 871 | vKey: pwd, 872 | label: 'password as UTF-8 string at both encryption and decryption' 873 | }, 874 | { 875 | alg: 'PBES2-HS256+A128KW', 876 | enc: 'A192CBC-HS384', 877 | eKey: pwd, 878 | vKey: pwd, 879 | label: 'password as UTF-8 string at both encryption and decryption' 880 | }, 881 | { 882 | alg: 'PBES2-HS256+A128KW', 883 | enc: 'A256CBC-HS512', 884 | eKey: pwd, 885 | vKey: pwd, 886 | label: 'password as UTF-8 string at both encryption and decryption' 887 | }, 888 | { 889 | alg: 'PBES2-HS256+A128KW', 890 | enc: 'A128GCM', 891 | eKey: pwd, 892 | vKey: pwd, 893 | label: 'password as UTF-8 string at both encryption and decryption' 894 | }, 895 | { 896 | alg: 'PBES2-HS256+A128KW', 897 | enc: 'A192GCM', 898 | eKey: pwd, 899 | vKey: pwd, 900 | label: 'password as UTF-8 string at both encryption and decryption' 901 | }, 902 | { 903 | alg: 'PBES2-HS256+A128KW', 904 | enc: 'A256GCM', 905 | eKey: pwd, 906 | vKey: pwd, 907 | label: 'password as UTF-8 string at both encryption and decryption' 908 | }, 909 | { 910 | alg: 'PBES2-HS384+A192KW', 911 | enc: 'A128CBC-HS256', 912 | eKey: pwd, 913 | vKey: pwd, 914 | label: 'password as UTF-8 string at both encryption and decryption' 915 | }, 916 | { 917 | alg: 'PBES2-HS384+A192KW', 918 | enc: 'A192CBC-HS384', 919 | eKey: pwd, 920 | vKey: pwd, 921 | label: 'password as UTF-8 string at both encryption and decryption' 922 | }, 923 | { 924 | alg: 'PBES2-HS384+A192KW', 925 | enc: 'A256CBC-HS512', 926 | eKey: pwd, 927 | vKey: pwd, 928 | label: 'password as UTF-8 string at both encryption and decryption' 929 | }, 930 | { 931 | alg: 'PBES2-HS384+A192KW', 932 | enc: 'A128GCM', 933 | eKey: pwd, 934 | vKey: pwd, 935 | label: 'password as UTF-8 string at both encryption and decryption' 936 | }, 937 | { 938 | alg: 'PBES2-HS384+A192KW', 939 | enc: 'A192GCM', 940 | eKey: pwd, 941 | vKey: pwd, 942 | label: 'password as UTF-8 string at both encryption and decryption' 943 | }, 944 | { 945 | alg: 'PBES2-HS384+A192KW', 946 | enc: 'A256GCM', 947 | eKey: pwd, 948 | vKey: pwd, 949 | label: 'password as UTF-8 string at both encryption and decryption' 950 | }, 951 | { 952 | alg: 'PBES2-HS512+A256KW', 953 | enc: 'A128CBC-HS256', 954 | eKey: pwd, 955 | vKey: pwd, 956 | label: 'password as UTF-8 string at both encryption and decryption' 957 | }, 958 | { 959 | alg: 'PBES2-HS512+A256KW', 960 | enc: 'A192CBC-HS384', 961 | eKey: pwd, 962 | vKey: pwd, 963 | label: 'password as UTF-8 string at both encryption and decryption' 964 | }, 965 | { 966 | alg: 'PBES2-HS512+A256KW', 967 | enc: 'A256CBC-HS512', 968 | eKey: pwd, 969 | vKey: pwd, 970 | label: 'password as UTF-8 string at both encryption and decryption' 971 | }, 972 | { 973 | alg: 'PBES2-HS512+A256KW', 974 | enc: 'A128GCM', 975 | eKey: pwd, 976 | vKey: pwd, 977 | label: 'password as UTF-8 string at both encryption and decryption' 978 | }, 979 | { 980 | alg: 'PBES2-HS512+A256KW', 981 | enc: 'A192GCM', 982 | eKey: pwd, 983 | vKey: pwd, 984 | label: 'password as UTF-8 string at both encryption and decryption' 985 | }, 986 | { 987 | alg: 'PBES2-HS512+A256KW', 988 | enc: 'A256GCM', 989 | eKey: pwd, 990 | vKey: pwd, 991 | label: 'password as UTF-8 string at both encryption and decryption' 992 | }, 993 | { 994 | alg: 'PBES2-HS256+A128KW', 995 | enc: 'A128CBC-HS256', 996 | eKey: pwd, 997 | vKey: Buffer.from(pwd), 998 | label: 'password as UTF-8 string at encryption, as buffer at decryption' 999 | }, 1000 | { 1001 | alg: 'PBES2-HS256+A128KW', 1002 | enc: 'A192CBC-HS384', 1003 | eKey: pwd, 1004 | vKey: Buffer.from(pwd), 1005 | label: 'password as UTF-8 string at encryption, as buffer at decryption' 1006 | }, 1007 | { 1008 | alg: 'PBES2-HS256+A128KW', 1009 | enc: 'A256CBC-HS512', 1010 | eKey: pwd, 1011 | vKey: Buffer.from(pwd), 1012 | label: 'password as UTF-8 string at encryption, as buffer at decryption' 1013 | }, 1014 | { 1015 | alg: 'PBES2-HS256+A128KW', 1016 | enc: 'A128GCM', 1017 | eKey: pwd, 1018 | vKey: Buffer.from(pwd), 1019 | label: 'password as UTF-8 string at encryption, as buffer at decryption' 1020 | }, 1021 | { 1022 | alg: 'PBES2-HS256+A128KW', 1023 | enc: 'A192GCM', 1024 | eKey: pwd, 1025 | vKey: Buffer.from(pwd), 1026 | label: 'password as UTF-8 string at encryption, as buffer at decryption' 1027 | }, 1028 | { 1029 | alg: 'PBES2-HS256+A128KW', 1030 | enc: 'A256GCM', 1031 | eKey: pwd, 1032 | vKey: Buffer.from(pwd), 1033 | label: 'password as UTF-8 string at encryption, as buffer at decryption' 1034 | }, 1035 | { 1036 | alg: 'PBES2-HS384+A192KW', 1037 | enc: 'A128CBC-HS256', 1038 | eKey: pwd, 1039 | vKey: Buffer.from(pwd), 1040 | label: 'password as UTF-8 string at encryption, as buffer at decryption' 1041 | }, 1042 | { 1043 | alg: 'PBES2-HS384+A192KW', 1044 | enc: 'A192CBC-HS384', 1045 | eKey: pwd, 1046 | vKey: Buffer.from(pwd), 1047 | label: 'password as UTF-8 string at encryption, as buffer at decryption' 1048 | }, 1049 | { 1050 | alg: 'PBES2-HS384+A192KW', 1051 | enc: 'A256CBC-HS512', 1052 | eKey: pwd, 1053 | vKey: Buffer.from(pwd), 1054 | label: 'password as UTF-8 string at encryption, as buffer at decryption' 1055 | }, 1056 | { 1057 | alg: 'PBES2-HS384+A192KW', 1058 | enc: 'A128GCM', 1059 | eKey: pwd, 1060 | vKey: Buffer.from(pwd), 1061 | label: 'password as UTF-8 string at encryption, as buffer at decryption' 1062 | }, 1063 | { 1064 | alg: 'PBES2-HS384+A192KW', 1065 | enc: 'A192GCM', 1066 | eKey: pwd, 1067 | vKey: Buffer.from(pwd), 1068 | label: 'password as UTF-8 string at encryption, as buffer at decryption' 1069 | }, 1070 | { 1071 | alg: 'PBES2-HS384+A192KW', 1072 | enc: 'A256GCM', 1073 | eKey: pwd, 1074 | vKey: Buffer.from(pwd), 1075 | label: 'password as UTF-8 string at encryption, as buffer at decryption' 1076 | }, 1077 | { 1078 | alg: 'PBES2-HS512+A256KW', 1079 | enc: 'A128CBC-HS256', 1080 | eKey: pwd, 1081 | vKey: Buffer.from(pwd), 1082 | label: 'password as UTF-8 string at encryption, as buffer at decryption' 1083 | }, 1084 | { 1085 | alg: 'PBES2-HS512+A256KW', 1086 | enc: 'A192CBC-HS384', 1087 | eKey: pwd, 1088 | vKey: Buffer.from(pwd), 1089 | label: 'password as UTF-8 string at encryption, as buffer at decryption' 1090 | }, 1091 | { 1092 | alg: 'PBES2-HS512+A256KW', 1093 | enc: 'A256CBC-HS512', 1094 | eKey: pwd, 1095 | vKey: Buffer.from(pwd), 1096 | label: 'password as UTF-8 string at encryption, as buffer at decryption' 1097 | }, 1098 | { 1099 | alg: 'PBES2-HS512+A256KW', 1100 | enc: 'A128GCM', 1101 | eKey: pwd, 1102 | vKey: Buffer.from(pwd), 1103 | label: 'password as UTF-8 string at encryption, as buffer at decryption' 1104 | }, 1105 | { 1106 | alg: 'PBES2-HS512+A256KW', 1107 | enc: 'A192GCM', 1108 | eKey: pwd, 1109 | vKey: Buffer.from(pwd), 1110 | label: 'password as UTF-8 string at encryption, as buffer at decryption' 1111 | }, 1112 | { 1113 | alg: 'PBES2-HS512+A256KW', 1114 | enc: 'A256GCM', 1115 | eKey: pwd, 1116 | vKey: Buffer.from(pwd), 1117 | label: 'password as UTF-8 string at encryption, as buffer at decryption' 1118 | }, 1119 | { 1120 | alg: 'PBES2-HS256+A128KW', 1121 | enc: 'A128CBC-HS256', 1122 | eKey: Buffer.from(pwd), 1123 | vKey: pwd, 1124 | label: 'password as buffer at encryption, as UTF-8 string at decryption' 1125 | }, 1126 | { 1127 | alg: 'PBES2-HS256+A128KW', 1128 | enc: 'A192CBC-HS384', 1129 | eKey: Buffer.from(pwd), 1130 | vKey: pwd, 1131 | label: 'password as buffer at encryption, as UTF-8 string at decryption' 1132 | }, 1133 | { 1134 | alg: 'PBES2-HS256+A128KW', 1135 | enc: 'A256CBC-HS512', 1136 | eKey: Buffer.from(pwd), 1137 | vKey: pwd, 1138 | label: 'password as buffer at encryption, as UTF-8 string at decryption' 1139 | }, 1140 | { 1141 | alg: 'PBES2-HS256+A128KW', 1142 | enc: 'A128GCM', 1143 | eKey: Buffer.from(pwd), 1144 | vKey: pwd, 1145 | label: 'ppassword as buffer at encryption, as UTF-8 string at decryption' 1146 | }, 1147 | { 1148 | alg: 'PBES2-HS256+A128KW', 1149 | enc: 'A192GCM', 1150 | eKey: Buffer.from(pwd), 1151 | vKey: pwd, 1152 | label: 'password as buffer at encryption, as UTF-8 string at decryption' 1153 | }, 1154 | { 1155 | alg: 'PBES2-HS256+A128KW', 1156 | enc: 'A256GCM', 1157 | eKey: Buffer.from(pwd), 1158 | vKey: pwd, 1159 | label: 'password as buffer at encryption, as UTF-8 string at decryption' 1160 | }, 1161 | { 1162 | alg: 'PBES2-HS384+A192KW', 1163 | enc: 'A128CBC-HS256', 1164 | eKey: Buffer.from(pwd), 1165 | vKey: pwd, 1166 | label: 'password as buffer at encryption, as UTF-8 string at decryption' 1167 | }, 1168 | { 1169 | alg: 'PBES2-HS384+A192KW', 1170 | enc: 'A192CBC-HS384', 1171 | eKey: Buffer.from(pwd), 1172 | vKey: pwd, 1173 | label: 'password as buffer at encryption, as UTF-8 string at decryption' 1174 | }, 1175 | { 1176 | alg: 'PBES2-HS384+A192KW', 1177 | enc: 'A256CBC-HS512', 1178 | eKey: Buffer.from(pwd), 1179 | vKey: pwd, 1180 | label: 'password as buffer at encryption, as UTF-8 string at decryption' 1181 | }, 1182 | { 1183 | alg: 'PBES2-HS384+A192KW', 1184 | enc: 'A128GCM', 1185 | eKey: Buffer.from(pwd), 1186 | vKey: pwd, 1187 | label: 'password as buffer at encryption, as UTF-8 string at decryption' 1188 | }, 1189 | { 1190 | alg: 'PBES2-HS384+A192KW', 1191 | enc: 'A192GCM', 1192 | eKey: Buffer.from(pwd), 1193 | vKey: pwd, 1194 | label: 'password as buffer at encryption, as UTF-8 string at decryption' 1195 | }, 1196 | { 1197 | alg: 'PBES2-HS384+A192KW', 1198 | enc: 'A256GCM', 1199 | eKey: Buffer.from(pwd), 1200 | vKey: pwd, 1201 | label: 'password as buffer at encryption, as UTF-8 string at decryption' 1202 | }, 1203 | { 1204 | alg: 'PBES2-HS512+A256KW', 1205 | enc: 'A128CBC-HS256', 1206 | eKey: Buffer.from(pwd), 1207 | vKey: pwd, 1208 | label: 'password as buffer at encryption, as UTF-8 string at decryption' 1209 | }, 1210 | { 1211 | alg: 'PBES2-HS512+A256KW', 1212 | enc: 'A192CBC-HS384', 1213 | eKey: Buffer.from(pwd), 1214 | vKey: pwd, 1215 | label: 'password as buffer at encryption, as UTF-8 string at decryption' 1216 | }, 1217 | { 1218 | alg: 'PBES2-HS512+A256KW', 1219 | enc: 'A256CBC-HS512', 1220 | eKey: Buffer.from(pwd), 1221 | vKey: pwd, 1222 | label: 'password as buffer at encryption, as UTF-8 string at decryption' 1223 | }, 1224 | { 1225 | alg: 'PBES2-HS512+A256KW', 1226 | enc: 'A128GCM', 1227 | eKey: Buffer.from(pwd), 1228 | vKey: pwd, 1229 | label: 'password as buffer at encryption, as UTF-8 string at decryption' 1230 | }, 1231 | { 1232 | alg: 'PBES2-HS512+A256KW', 1233 | enc: 'A192GCM', 1234 | eKey: Buffer.from(pwd), 1235 | vKey: pwd, 1236 | label: 'password as buffer at encryption, as UTF-8 string at decryption' 1237 | }, 1238 | { 1239 | alg: 'PBES2-HS512+A256KW', 1240 | enc: 'A256GCM', 1241 | eKey: Buffer.from(pwd), 1242 | vKey: pwd, 1243 | label: 'password as buffer at encryption, as UTF-8 string at decryption' 1244 | }, 1245 | { 1246 | alg: 'PBES2-HS256+A128KW', 1247 | enc: 'A128CBC-HS256', 1248 | eKey: Buffer.from(pwd), 1249 | vKey: Buffer.from(pwd), 1250 | label: 'password as buffer at both encryption and decryption' 1251 | }, 1252 | { 1253 | alg: 'PBES2-HS256+A128KW', 1254 | enc: 'A192CBC-HS384', 1255 | eKey: Buffer.from(pwd), 1256 | vKey: Buffer.from(pwd), 1257 | label: 'password as buffer at both encryption and decryption' 1258 | }, 1259 | { 1260 | alg: 'PBES2-HS256+A128KW', 1261 | enc: 'A256CBC-HS512', 1262 | eKey: Buffer.from(pwd), 1263 | vKey: Buffer.from(pwd), 1264 | label: 'password as buffer at both encryption and decryptionn' 1265 | }, 1266 | { 1267 | alg: 'PBES2-HS256+A128KW', 1268 | enc: 'A128GCM', 1269 | eKey: Buffer.from(pwd), 1270 | vKey: Buffer.from(pwd), 1271 | label: 'password as buffer at both encryption and decryption' 1272 | }, 1273 | { 1274 | alg: 'PBES2-HS256+A128KW', 1275 | enc: 'A192GCM', 1276 | eKey: Buffer.from(pwd), 1277 | vKey: Buffer.from(pwd), 1278 | label: 'password as buffer at both encryption and decryption' 1279 | }, 1280 | { 1281 | alg: 'PBES2-HS256+A128KW', 1282 | enc: 'A256GCM', 1283 | eKey: Buffer.from(pwd), 1284 | vKey: Buffer.from(pwd), 1285 | label: 'password as buffer at both encryption and decryption' 1286 | }, 1287 | { 1288 | alg: 'PBES2-HS384+A192KW', 1289 | enc: 'A128CBC-HS256', 1290 | eKey: Buffer.from(pwd), 1291 | vKey: Buffer.from(pwd), 1292 | label: 'password as buffer at both encryption and decryption' 1293 | }, 1294 | { 1295 | alg: 'PBES2-HS384+A192KW', 1296 | enc: 'A192CBC-HS384', 1297 | eKey: Buffer.from(pwd), 1298 | vKey: Buffer.from(pwd), 1299 | label: 'password as buffer at both encryption and decryption' 1300 | }, 1301 | { 1302 | alg: 'PBES2-HS384+A192KW', 1303 | enc: 'A256CBC-HS512', 1304 | eKey: Buffer.from(pwd), 1305 | vKey: Buffer.from(pwd), 1306 | label: 'password as buffer at both encryption and decryption' 1307 | }, 1308 | { 1309 | alg: 'PBES2-HS384+A192KW', 1310 | enc: 'A128GCM', 1311 | eKey: Buffer.from(pwd), 1312 | vKey: Buffer.from(pwd), 1313 | label: 'password as buffer at both encryption and decryption' 1314 | }, 1315 | { 1316 | alg: 'PBES2-HS384+A192KW', 1317 | enc: 'A192GCM', 1318 | eKey: Buffer.from(pwd), 1319 | vKey: Buffer.from(pwd), 1320 | label: 'password as buffer at both encryption and decryption' 1321 | }, 1322 | { 1323 | alg: 'PBES2-HS384+A192KW', 1324 | enc: 'A256GCM', 1325 | eKey: Buffer.from(pwd), 1326 | vKey: Buffer.from(pwd), 1327 | label: 'password as buffer at both encryption and decryption' 1328 | }, 1329 | { 1330 | alg: 'PBES2-HS512+A256KW', 1331 | enc: 'A128CBC-HS256', 1332 | eKey: Buffer.from(pwd), 1333 | vKey: Buffer.from(pwd), 1334 | label: 'password as buffer at both encryption and decryption' 1335 | }, 1336 | { 1337 | alg: 'PBES2-HS512+A256KW', 1338 | enc: 'A192CBC-HS384', 1339 | eKey: Buffer.from(pwd), 1340 | vKey: Buffer.from(pwd), 1341 | label: 'password as buffer at both encryption and decryption' 1342 | }, 1343 | { 1344 | alg: 'PBES2-HS512+A256KW', 1345 | enc: 'A256CBC-HS512', 1346 | eKey: Buffer.from(pwd), 1347 | vKey: Buffer.from(pwd), 1348 | label: 'password as buffer at both encryption and decryption' 1349 | }, 1350 | { 1351 | alg: 'PBES2-HS512+A256KW', 1352 | enc: 'A128GCM', 1353 | eKey: Buffer.from(pwd), 1354 | vKey: Buffer.from(pwd), 1355 | label: 'password as buffer at both encryption and decryption' 1356 | }, 1357 | { 1358 | alg: 'PBES2-HS512+A256KW', 1359 | enc: 'A192GCM', 1360 | eKey: Buffer.from(pwd), 1361 | vKey: Buffer.from(pwd), 1362 | label: 'password as buffer at both encryption and decryption' 1363 | }, 1364 | { 1365 | alg: 'PBES2-HS512+A256KW', 1366 | enc: 'A256GCM', 1367 | eKey: Buffer.from(pwd), 1368 | vKey: Buffer.from(pwd), 1369 | label: 'password as buffer at both encryption and decryption' 1370 | } 1371 | ]; 1372 | 1373 | console.log('\nBASIC TEST CASES - SYNCHRONOUS MODE\n'); 1374 | let token; 1375 | let result; 1376 | for (let test of tests) { 1377 | token = jwt.generate(test.alg, test.enc, payload, test.eKey); 1378 | result = jwt.parse(token) 1379 | .setTokenLifetime(60000) 1380 | .verify(test.vKey); 1381 | if (result.error) { 1382 | console.log(`[NOK] ${test.alg}, ${test.enc}, ${test.label}`); 1383 | console.log(result); 1384 | process.exit(); 1385 | } 1386 | console.log(`[OK] ${test.alg}, ${test.enc}, ${test.label}`); 1387 | } 1388 | 1389 | console.log('\nBASIC TEST CASES - ASYNCHRONOUS MODE\n'); 1390 | executeCaseAsync(0); 1391 | 1392 | function executeCaseAsync(idx) { 1393 | if (idx === tests.length) process.exit(); 1394 | let test = tests[idx]; 1395 | jwt.generate(test.alg, test.enc, payload, test.eKey, (error, token) => { 1396 | if (error) throw error; 1397 | jwt.parse(token) 1398 | .setTokenLifetime(60000) 1399 | .verify(test.vKey, (error, result) => { 1400 | if (error) throw error; 1401 | if (result.error) { 1402 | console.log(`[NOK] ${test.alg}, ${test.enc}, ${test.label}`); 1403 | console.log(result); 1404 | process.exit(); 1405 | } 1406 | console.log(`[OK] ${test.alg}, ${test.enc}, ${test.label}`); 1407 | executeCaseAsync(++idx); 1408 | }); 1409 | }); 1410 | } 1411 | -------------------------------------------------------------------------------- /test/jwe_expiration.js: -------------------------------------------------------------------------------- 1 | const crypto = require('crypto'); 2 | const jwt = require('../index.js'); 3 | 4 | const KEYS_DIR = __dirname + '/pem_keys/'; 5 | 6 | const key = crypto.randomBytes(64); 7 | 8 | const payload = { 9 | iss: 'auth.mydomain.com', 10 | aud: 'A1B2C3D4E5.com.mydomain.myservice', 11 | sub: 'jack.sparrow@example.com', 12 | info: 'Hello World!', 13 | list: [1, 2, 3, 4], 14 | exp: Math.floor(Date.now() / 1000) + 4 // expires in 4 seconds 15 | }; 16 | 17 | function pause(duration) { 18 | return new Promise((resolve) => { 19 | setTimeout(resolve, duration); 20 | }); 21 | } 22 | 23 | function testExpirationByIatAsync(token) { 24 | return new Promise((resolve) => { 25 | jwt.parse(token).setTokenLifetime(2).verify(key, (error, parsed) => { 26 | if (parsed.expired && parsed.expired === parsed.payload.iat + 2) { 27 | console.log('[OK] Expiration by iat; asynchronous verification API'); 28 | resolve(); 29 | } else { 30 | console.log('[NOK] Expiration by iat; asynchronous verification API'); 31 | process.exit(); 32 | } 33 | }); 34 | }); 35 | } 36 | 37 | function testExpirationByExpAsync(token) { 38 | return new Promise((resolve) => { 39 | jwt.parse(token).setTokenLifetime(60).verify(key, (error, parsed) => { 40 | if (parsed.expired && parsed.expired === parsed.payload.exp) { 41 | console.log('[OK] Expiration by exp; asynchronous verification API'); 42 | resolve(); 43 | } else { 44 | console.log('[NOK] Expiration by exp; asynchronous verification API'); 45 | process.exit(); 46 | } 47 | }); 48 | }); 49 | } 50 | 51 | (async () => { 52 | let token = jwt.generate('dir', 'A256CBC-HS512', payload, key); 53 | 54 | await pause(1500); 55 | let parsed = jwt.parse(token).setTokenLifetime(1).verify(key); 56 | if (parsed.expired && parsed.expired === parsed.payload.iat + 1) { 57 | console.log('\n[OK] Expiration by iat; synchronous verification API'); 58 | } else { 59 | console.log('\n[NOK] Expiration by iat; synchronous verification API'); 60 | process.exit(); 61 | } 62 | 63 | await pause(1500); 64 | await testExpirationByIatAsync(token); 65 | 66 | await pause(1500); 67 | parsed = jwt.parse(token).setTokenLifetime(60).verify(key); 68 | if (parsed.expired && parsed.expired === parsed.payload.exp) { 69 | console.log('[OK] Expiration by exp; synchronous verification API'); 70 | } else { 71 | console.log('[NOK] Expiration by exp; synchronous verification API'); 72 | process.exit(); 73 | } 74 | 75 | payload.exp = Math.floor(Date.now() / 1000) + 1 // expires in 1 second 76 | token = jwt.generate('dir', 'A256CBC-HS512', payload, key); 77 | await pause(1500); 78 | await testExpirationByExpAsync(token); 79 | 80 | })(); 81 | -------------------------------------------------------------------------------- /test/jwe_negative.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const crypto = require('crypto'); 3 | const jwt = require('../index.js'); 4 | const buf2b64url = require('../lib/common.js').buf2b64url; 5 | 6 | const KEYS_DIR = __dirname + '/pem_keys/'; 7 | 8 | const simKey = crypto.randomBytes(64); 9 | const pwd = 'My very secret password'; 10 | const priRsa = fs.readFileSync(KEYS_DIR + 'priRsa.key'); 11 | const pubRsa = fs.readFileSync(KEYS_DIR + 'pubRsa.key'); 12 | const priEc256 = fs.readFileSync(KEYS_DIR + 'priEc256.key'); 13 | const pubEc256 = fs.readFileSync(KEYS_DIR + 'pubEc256.key'); 14 | const priEc384 = fs.readFileSync(KEYS_DIR + 'priEc384.key'); 15 | const pubEc384 = fs.readFileSync(KEYS_DIR + 'pubEc384.key'); 16 | const priEc521 = fs.readFileSync(KEYS_DIR + 'priEc521.key'); 17 | const pubEc521 = fs.readFileSync(KEYS_DIR + 'pubEc521.key'); 18 | 19 | const payload = { 20 | iss: 'auth.mydomain.com', 21 | aud: 'A1B2C3D4E5.com.mydomain.myservice', 22 | sub: 'jack.sparrow@example.com', 23 | info: 'Hello World!', 24 | list: [1, 2, 3, 4] 25 | }; 26 | 27 | const tests = [ 28 | {alg: 'dir', enc: 'A128CBC-HS256', eKey: simKey, vKey: simKey}, 29 | {alg: 'dir', enc: 'A192CBC-HS384', eKey: simKey, vKey: simKey}, 30 | {alg: 'dir', enc: 'A256CBC-HS512', eKey: simKey, vKey: simKey}, 31 | {alg: 'dir', enc: 'A128GCM', eKey: simKey, vKey: simKey}, 32 | {alg: 'dir', enc: 'A192GCM', eKey: simKey, vKey: simKey}, 33 | {alg: 'dir', enc: 'A256GCM', eKey: simKey, vKey: simKey}, 34 | {alg: 'RSA-OAEP', enc: 'A128CBC-HS256', eKey: pubRsa, vKey: priRsa}, 35 | {alg: 'RSA-OAEP', enc: 'A192CBC-HS384', eKey: pubRsa, vKey: priRsa}, 36 | {alg: 'RSA-OAEP', enc: 'A256CBC-HS512', eKey: pubRsa, vKey: priRsa}, 37 | {alg: 'RSA-OAEP', enc: 'A128GCM', eKey: pubRsa, vKey: priRsa}, 38 | {alg: 'RSA-OAEP', enc: 'A192GCM', eKey: pubRsa, vKey: priRsa}, 39 | {alg: 'RSA-OAEP', enc: 'A256GCM', eKey: pubRsa, vKey: priRsa}, 40 | {alg: 'A128KW', enc: 'A128CBC-HS256', eKey: simKey, vKey: simKey}, 41 | {alg: 'A128KW', enc: 'A192CBC-HS384', eKey: simKey, vKey: simKey}, 42 | {alg: 'A128KW', enc: 'A256CBC-HS512', eKey: simKey, vKey: simKey}, 43 | {alg: 'A128KW', enc: 'A128GCM', eKey: simKey, vKey: simKey}, 44 | {alg: 'A128KW', enc: 'A192GCM', eKey: simKey, vKey: simKey}, 45 | {alg: 'A128KW', enc: 'A256GCM', eKey: simKey, vKey: simKey}, 46 | {alg: 'A192KW', enc: 'A128CBC-HS256', eKey: simKey, vKey: simKey}, 47 | {alg: 'A192KW', enc: 'A192CBC-HS384', eKey: simKey, vKey: simKey}, 48 | {alg: 'A192KW', enc: 'A256CBC-HS512', eKey: simKey, vKey: simKey}, 49 | {alg: 'A192KW', enc: 'A128GCM', eKey: simKey, vKey: simKey}, 50 | {alg: 'A192KW', enc: 'A192GCM', eKey: simKey, vKey: simKey}, 51 | {alg: 'A192KW', enc: 'A256GCM', eKey: simKey, vKey: simKey}, 52 | {alg: 'A256KW', enc: 'A128CBC-HS256', eKey: simKey, vKey: simKey}, 53 | {alg: 'A256KW', enc: 'A192CBC-HS384', eKey: simKey, vKey: simKey}, 54 | {alg: 'A256KW', enc: 'A256CBC-HS512', eKey: simKey, vKey: simKey}, 55 | {alg: 'A256KW', enc: 'A128GCM', eKey: simKey, vKey: simKey}, 56 | {alg: 'A256KW', enc: 'A192GCM', eKey: simKey, vKey: simKey}, 57 | {alg: 'A256KW', enc: 'A256GCM', eKey: simKey, vKey: simKey}, 58 | {alg: 'PBES2-HS256+A128KW', enc: 'A128CBC-HS256', eKey: pwd, vKey: pwd}, 59 | {alg: 'PBES2-HS256+A128KW', enc: 'A192CBC-HS384', eKey: pwd, vKey: pwd}, 60 | {alg: 'PBES2-HS256+A128KW', enc: 'A256CBC-HS512', eKey: pwd, vKey: pwd}, 61 | {alg: 'PBES2-HS256+A128KW', enc: 'A128GCM', eKey: pwd, vKey: pwd}, 62 | {alg: 'PBES2-HS256+A128KW', enc: 'A192GCM', eKey: pwd, vKey: pwd}, 63 | {alg: 'PBES2-HS256+A128KW', enc: 'A256GCM', eKey: pwd, vKey: pwd}, 64 | {alg: 'PBES2-HS384+A192KW', enc: 'A128CBC-HS256', eKey: pwd, vKey: pwd}, 65 | {alg: 'PBES2-HS384+A192KW', enc: 'A192CBC-HS384', eKey: pwd, vKey: pwd}, 66 | {alg: 'PBES2-HS384+A192KW', enc: 'A256CBC-HS512', eKey: pwd, vKey: pwd}, 67 | {alg: 'PBES2-HS384+A192KW', enc: 'A128GCM', eKey: pwd, vKey: pwd}, 68 | {alg: 'PBES2-HS384+A192KW', enc: 'A192GCM', eKey: pwd, vKey: pwd}, 69 | {alg: 'PBES2-HS384+A192KW', enc: 'A256GCM', eKey: pwd, vKey: pwd}, 70 | {alg: 'PBES2-HS512+A256KW', enc: 'A128CBC-HS256', eKey: pwd, vKey: pwd}, 71 | {alg: 'PBES2-HS512+A256KW', enc: 'A192CBC-HS384', eKey: pwd, vKey: pwd}, 72 | {alg: 'PBES2-HS512+A256KW', enc: 'A256CBC-HS512', eKey: pwd, vKey: pwd}, 73 | {alg: 'PBES2-HS512+A256KW', enc: 'A128GCM', eKey: pwd, vKey: pwd}, 74 | {alg: 'PBES2-HS512+A256KW', enc: 'A192GCM', eKey: pwd, vKey: pwd}, 75 | {alg: 'PBES2-HS512+A256KW', enc: 'A256GCM', eKey: pwd, vKey: pwd} 76 | ]; 77 | 78 | let token; 79 | let validToken; 80 | let parsed; 81 | let key; 82 | 83 | for (let test of tests) { 84 | console.log(`\n${test.alg}, ${test.enc}`); 85 | validToken = jwt.generate(test.alg, test.enc, payload, test.eKey); 86 | 87 | // no alg in header 88 | token = removeAlgFromHeader(validToken); 89 | parsed = jwt.parse(token).verify(test.vKey); 90 | if (parsed.error && 91 | parsed.error.message.includes('Missing or invalid alg claim in header')) { 92 | console.log(`[OK] missing alg claim`); 93 | } else { 94 | console.log(`[NOK] missing alg claim`); 95 | console.log(parsed); 96 | process.exit(); 97 | } 98 | 99 | // unrecognized alg in header 100 | token = messupAlgInHeader(validToken); 101 | parsed = jwt.parse(token).verify(test.vKey); 102 | if (parsed.error && 103 | parsed.error.message.includes('Unrecognized key management algorithm')) { 104 | console.log(`[OK] unrecognized alg claim`); 105 | } else { 106 | console.log(`[NOK] unrecognized alg claim`); 107 | console.log(parsed); 108 | process.exit(); 109 | } 110 | 111 | // unwanted alg in header 112 | parsed = jwt.parse(validToken) 113 | .setAlgorithmList(['dummy1', 'dummy2'], test.enc) 114 | .verify(test.vKey); 115 | if (parsed.error && 116 | parsed.error.message.includes('Unwanted key management algorithm')) { 117 | console.log(`[OK] unwanted alg claim`); 118 | } else { 119 | console.log(`[NOK] unwanted alg claim`); 120 | console.log(parsed); 121 | process.exit(); 122 | } 123 | 124 | // no enc in header 125 | token = removeEncFromHeader(validToken); 126 | parsed = jwt.parse(token).verify(test.vKey); 127 | if (parsed.error && 128 | parsed.error.message.includes('Missing or invalid enc claim in header')) { 129 | console.log(`[OK] missing enc claim`); 130 | } else { 131 | console.log(`[NOK] missing enc claim`); 132 | console.log(parsed); 133 | process.exit(); 134 | } 135 | 136 | // unrecognized enc in header 137 | token = messupEncInHeader(validToken); 138 | parsed = jwt.parse(token).verify(test.vKey); 139 | if (parsed.error && 140 | parsed.error.message.includes('Unrecognized content encryption algorithm')) { 141 | console.log(`[OK] unrecognized enc claim`); 142 | } else { 143 | console.log(`[NOK] unrecognized enc claim`); 144 | console.log(parsed); 145 | process.exit(); 146 | } 147 | 148 | // unwanted enc in header 149 | parsed = jwt.parse(validToken) 150 | .setAlgorithmList(test.alg, ['dummy1', 'dummy2']) 151 | .verify(test.vKey); 152 | if (parsed.error && 153 | parsed.error.message.includes('Unwanted content encryption algorithm')) { 154 | console.log(`[OK] unwanted enc claim`); 155 | } else { 156 | console.log(`[NOK] unwanted enc claim`); 157 | console.log(parsed); 158 | process.exit(); 159 | } 160 | 161 | // tampered header 162 | token = tamperHeader(validToken); 163 | parsed = jwt.parse(token).verify(test.vKey); 164 | if (parsed.error && 165 | parsed.error.message.includes('Could not decrypt token')) { 166 | console.log(`[OK] tampered header`); 167 | } else { 168 | console.log(`[NOK] tampered header`); 169 | console.log(parsed); 170 | process.exit(); 171 | } 172 | 173 | // tampered payload 174 | token = tamperPayload(validToken); 175 | parsed = jwt.parse(token).verify(test.vKey); 176 | if (parsed.error && 177 | parsed.error.message.includes('Could not decrypt token')) { 178 | console.log(`[OK] tampered payload`); 179 | } else { 180 | console.log(`[NOK] tampered payload`); 181 | console.log(parsed); 182 | process.exit(); 183 | } 184 | 185 | // tampered initialization vector 186 | token = tamperIv(validToken); 187 | parsed = jwt.parse(token).verify(test.vKey); 188 | if (parsed.error && 189 | parsed.error.message.includes('Could not decrypt token')) { 190 | console.log(`[OK] tampered initialization vector`); 191 | } else { 192 | console.log(`[NOK] tampered initialization vector`); 193 | console.log(parsed); 194 | process.exit(); 195 | } 196 | 197 | // tampered authentication tag 198 | token = tamperTag(validToken); 199 | parsed = jwt.parse(token).verify(test.vKey); 200 | if (parsed.error && 201 | parsed.error.message.includes('Could not decrypt token')) { 202 | console.log(`[OK] tampered authentication tag`); 203 | } else { 204 | console.log(`[NOK] tampered authentication tag`); 205 | console.log(parsed); 206 | process.exit(); 207 | } 208 | 209 | // check with wrong key 210 | key = messupVerificationKey(test.vKey); 211 | parsed = jwt.parse(token).verify(test.vKey); 212 | if (parsed.error && 213 | parsed.error.message.includes('Could not decrypt token')) { 214 | console.log(`[OK] wrong key`); 215 | } else { 216 | console.log(`[NOK] wrong key`); 217 | console.log(parsed); 218 | process.exit(); 219 | } 220 | 221 | // check with invalid key type 222 | key = messupVerificationKeyType(test.vKey); 223 | parsed = jwt.parse(token).verify(test.vKey); 224 | if (parsed.error && 225 | parsed.error.message.includes('Could not decrypt token')) { 226 | console.log(`[OK] wrong key type`); 227 | } else { 228 | console.log(`[NOK] wrong key type`); 229 | console.log(parsed); 230 | process.exit(); 231 | } 232 | 233 | // check with invalid key length 234 | key = messupVerificationKeyLength(test.vKey); 235 | parsed = jwt.parse(token).verify(test.vKey); 236 | if (parsed.error && 237 | parsed.error.message.includes('Could not decrypt token')) { 238 | console.log(`[OK] wrong key length`); 239 | } else { 240 | console.log(`[NOK] wrong key length`); 241 | console.log(parsed); 242 | process.exit(); 243 | } 244 | } 245 | 246 | 247 | function removeAlgFromHeader(token) { 248 | let parts = token.split('.'); 249 | let header = JSON.parse(Buffer.from(parts[0], 'base64')); 250 | delete header.alg; 251 | let newHeader = Buffer.from(JSON.stringify(header)).toString('base64'); 252 | parts[0] = buf2b64url(newHeader); 253 | return parts.join('.'); 254 | } 255 | 256 | function messupAlgInHeader(token) { 257 | let parts = token.split('.'); 258 | let header = JSON.parse(Buffer.from(parts[0], 'base64')); 259 | header.alg = 'dummy'; 260 | let newHeader = Buffer.from(JSON.stringify(header)).toString('base64'); 261 | parts[0] = buf2b64url(newHeader); 262 | return parts.join('.'); 263 | } 264 | 265 | function removeEncFromHeader(token) { 266 | let parts = token.split('.'); 267 | let header = JSON.parse(Buffer.from(parts[0], 'base64')); 268 | delete header.enc; 269 | let newHeader = Buffer.from(JSON.stringify(header)).toString('base64'); 270 | parts[0] = buf2b64url(newHeader); 271 | return parts.join('.'); 272 | } 273 | 274 | function messupEncInHeader(token) { 275 | let parts = token.split('.'); 276 | let header = JSON.parse(Buffer.from(parts[0], 'base64')); 277 | header.enc = 'dummy'; 278 | let newHeader = Buffer.from(JSON.stringify(header)).toString('base64'); 279 | parts[0] = buf2b64url(newHeader); 280 | return parts.join('.'); 281 | } 282 | 283 | function tamperHeader(token) { 284 | let parts = token.split('.'); 285 | let header = JSON.parse(Buffer.from(parts[0], 'base64')); 286 | header.extra = 'dummy'; 287 | let newHeader = Buffer.from(JSON.stringify(header)).toString('base64'); 288 | parts[0] = buf2b64url(newHeader); 289 | return parts.join('.'); 290 | } 291 | 292 | function tamperPayload(token) { 293 | let parts = token.split('.'); 294 | parts[3] = parts[3].slice(0, -1); 295 | return parts.join('.'); 296 | } 297 | 298 | function tamperIv(token) { 299 | let parts = token.split('.'); 300 | parts[2] = parts[2].slice(0, -1); 301 | return parts.join('.'); 302 | } 303 | 304 | function tamperTag(token) { 305 | let parts = token.split('.'); 306 | parts[4] = parts[4].slice(1); 307 | return parts.join('.'); 308 | } 309 | 310 | function messupVerificationKey(key) { 311 | let wrongKey = Buffer.from(key); 312 | wrongKey[0] ^= 1; 313 | return wrongKey; 314 | } 315 | 316 | function messupVerificationKeyType(key) { 317 | return Buffer.from(key).toString('hex'); 318 | } 319 | 320 | function messupVerificationKeyLength(key) { 321 | let len = key.length; 322 | return key.slice(0, len / 2); 323 | } 324 | -------------------------------------------------------------------------------- /test/jwe_performance.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const crypto = require('crypto'); 3 | const jwt = require('../index.js'); 4 | 5 | const ITER = 1000; 6 | const KEYS_DIR = __dirname + '/pem_keys/'; 7 | 8 | const simKey = crypto.randomBytes(64); 9 | const pwd = 'My very secret password'; 10 | const priRsa = fs.readFileSync(KEYS_DIR + 'priRsa.key'); 11 | const pubRsa = fs.readFileSync(KEYS_DIR + 'pubRsa.key'); 12 | const priEc256 = fs.readFileSync(KEYS_DIR + 'priEc256.key'); 13 | const pubEc256 = fs.readFileSync(KEYS_DIR + 'pubEc256.key'); 14 | const priEc384 = fs.readFileSync(KEYS_DIR + 'priEc384.key'); 15 | const pubEc384 = fs.readFileSync(KEYS_DIR + 'pubEc384.key'); 16 | const priEc521 = fs.readFileSync(KEYS_DIR + 'priEc521.key'); 17 | const pubEc521 = fs.readFileSync(KEYS_DIR + 'pubEc521.key'); 18 | 19 | const payload = { 20 | iss: 'auth.mydomain.com', 21 | aud: 'A1B2C3D4E5.com.mydomain.myservice', 22 | sub: 'jack.sparrow@example.com', 23 | info: 'Hello World!', 24 | list: [1, 2, 3, 4] 25 | }; 26 | 27 | const tests = [ 28 | {alg: 'dir', enc: 'A128CBC-HS256', eKey: simKey, vKey: simKey}, 29 | {alg: 'dir', enc: 'A192CBC-HS384', eKey: simKey, vKey: simKey}, 30 | {alg: 'dir', enc: 'A256CBC-HS512', eKey: simKey, vKey: simKey}, 31 | {alg: 'dir', enc: 'A128GCM', eKey: simKey, vKey: simKey}, 32 | {alg: 'dir', enc: 'A192GCM', eKey: simKey, vKey: simKey}, 33 | {alg: 'dir', enc: 'A256GCM', eKey: simKey, vKey: simKey}, 34 | {alg: 'RSA-OAEP', enc: 'A128CBC-HS256', eKey: pubRsa, vKey: priRsa}, 35 | {alg: 'RSA-OAEP', enc: 'A192CBC-HS384', eKey: pubRsa, vKey: priRsa}, 36 | {alg: 'RSA-OAEP', enc: 'A256CBC-HS512', eKey: pubRsa, vKey: priRsa}, 37 | {alg: 'RSA-OAEP', enc: 'A128GCM', eKey: pubRsa, vKey: priRsa}, 38 | {alg: 'RSA-OAEP', enc: 'A192GCM', eKey: pubRsa, vKey: priRsa}, 39 | {alg: 'RSA-OAEP', enc: 'A256GCM', eKey: pubRsa, vKey: priRsa}, 40 | {alg: 'A128KW', enc: 'A128CBC-HS256', eKey: simKey, vKey: simKey}, 41 | {alg: 'A128KW', enc: 'A192CBC-HS384', eKey: simKey, vKey: simKey}, 42 | {alg: 'A128KW', enc: 'A256CBC-HS512', eKey: simKey, vKey: simKey}, 43 | {alg: 'A128KW', enc: 'A128GCM', eKey: simKey, vKey: simKey}, 44 | {alg: 'A128KW', enc: 'A192GCM', eKey: simKey, vKey: simKey}, 45 | {alg: 'A128KW', enc: 'A256GCM', eKey: simKey, vKey: simKey}, 46 | {alg: 'A192KW', enc: 'A128CBC-HS256', eKey: simKey, vKey: simKey}, 47 | {alg: 'A192KW', enc: 'A192CBC-HS384', eKey: simKey, vKey: simKey}, 48 | {alg: 'A192KW', enc: 'A256CBC-HS512', eKey: simKey, vKey: simKey}, 49 | {alg: 'A192KW', enc: 'A128GCM', eKey: simKey, vKey: simKey}, 50 | {alg: 'A192KW', enc: 'A192GCM', eKey: simKey, vKey: simKey}, 51 | {alg: 'A192KW', enc: 'A256GCM', eKey: simKey, vKey: simKey}, 52 | {alg: 'A256KW', enc: 'A128CBC-HS256', eKey: simKey, vKey: simKey}, 53 | {alg: 'A256KW', enc: 'A192CBC-HS384', eKey: simKey, vKey: simKey}, 54 | {alg: 'A256KW', enc: 'A256CBC-HS512', eKey: simKey, vKey: simKey}, 55 | {alg: 'A256KW', enc: 'A128GCM', eKey: simKey, vKey: simKey}, 56 | {alg: 'A256KW', enc: 'A192GCM', eKey: simKey, vKey: simKey}, 57 | {alg: 'A256KW', enc: 'A256GCM', eKey: simKey, vKey: simKey}, 58 | {alg: 'PBES2-HS256+A128KW', enc: 'A128CBC-HS256', eKey: pwd, vKey: pwd}, 59 | {alg: 'PBES2-HS256+A128KW', enc: 'A192CBC-HS384', eKey: pwd, vKey: pwd}, 60 | {alg: 'PBES2-HS256+A128KW', enc: 'A256CBC-HS512', eKey: pwd, vKey: pwd}, 61 | {alg: 'PBES2-HS256+A128KW', enc: 'A128GCM', eKey: pwd, vKey: pwd}, 62 | {alg: 'PBES2-HS256+A128KW', enc: 'A192GCM', eKey: pwd, vKey: pwd}, 63 | {alg: 'PBES2-HS256+A128KW', enc: 'A256GCM', eKey: pwd, vKey: pwd}, 64 | {alg: 'PBES2-HS384+A192KW', enc: 'A128CBC-HS256', eKey: pwd, vKey: pwd}, 65 | {alg: 'PBES2-HS384+A192KW', enc: 'A192CBC-HS384', eKey: pwd, vKey: pwd}, 66 | {alg: 'PBES2-HS384+A192KW', enc: 'A256CBC-HS512', eKey: pwd, vKey: pwd}, 67 | {alg: 'PBES2-HS384+A192KW', enc: 'A128GCM', eKey: pwd, vKey: pwd}, 68 | {alg: 'PBES2-HS384+A192KW', enc: 'A192GCM', eKey: pwd, vKey: pwd}, 69 | {alg: 'PBES2-HS384+A192KW', enc: 'A256GCM', eKey: pwd, vKey: pwd}, 70 | {alg: 'PBES2-HS512+A256KW', enc: 'A128CBC-HS256', eKey: pwd, vKey: pwd}, 71 | {alg: 'PBES2-HS512+A256KW', enc: 'A192CBC-HS384', eKey: pwd, vKey: pwd}, 72 | {alg: 'PBES2-HS512+A256KW', enc: 'A256CBC-HS512', eKey: pwd, vKey: pwd}, 73 | {alg: 'PBES2-HS512+A256KW', enc: 'A128GCM', eKey: pwd, vKey: pwd}, 74 | {alg: 'PBES2-HS512+A256KW', enc: 'A192GCM', eKey: pwd, vKey: pwd}, 75 | {alg: 'PBES2-HS512+A256KW', enc: 'A256GCM', eKey: pwd, vKey: pwd} 76 | ]; 77 | 78 | let start; 79 | let delta; 80 | let token; 81 | let result; 82 | 83 | console.log(`\nGENERATION OF ${ITER} TOKENS\n`); 84 | for (let test of tests) { 85 | start = process.hrtime(); 86 | for (let j = 0; j < ITER; j++) { 87 | token = jwt.generate(test.alg, test.enc, payload, test.eKey); 88 | } 89 | delta = process.hrtime(start); 90 | console.log(`${test.alg} / ${test.enc} in ${formatResult(delta)}`); 91 | } 92 | 93 | console.log(`\nVERIFICATION OF ${ITER} TOKENS\n`); 94 | for (let test of tests) { 95 | token = jwt.generate(test.alg, test.enc, payload, test.eKey); 96 | start = process.hrtime(); 97 | for (let j = 0; j < ITER; j++) { 98 | result = jwt.parse(token).setTokenLifetime(60000).verify(test.vKey); 99 | } 100 | delta = process.hrtime(start); 101 | console.log(`${test.alg} / ${test.enc} in ${formatResult(delta)}`); 102 | } 103 | 104 | function formatResult(delta) { 105 | let ms = delta[0] * 1e3 + delta[1] * 1e-6; 106 | if (ms < 1000) return `${ms.toFixed(1)} ms`; 107 | return `${(ms / 1000).toFixed(3)} s`; 108 | } 109 | -------------------------------------------------------------------------------- /test/jws_aud_iss.js: -------------------------------------------------------------------------------- 1 | const crypto = require('crypto'); 2 | const jwt = require('../index.js'); 3 | 4 | const KEYS_DIR = __dirname + '/pem_keys/'; 5 | 6 | const key = crypto.randomBytes(64); 7 | 8 | const payload = { 9 | sub: 'jack.sparrow@example.com', 10 | info: 'Hello World!', 11 | list: [1, 2, 3, 4] 12 | }; 13 | 14 | let token = jwt.generate('HS512', payload, key); 15 | 16 | let parsed = jwt.parse(token) 17 | .setAudience('A1B2C3D4E5.com.mydomain.myservice') 18 | .verify(key); 19 | 20 | if (parsed.error && parsed.error.message.includes('Missing aud claim')) { 21 | console.log('\n[OK] Missing aud enforced when setAudience is used'); 22 | } else { 23 | console.log('\n[NOK] Missing aud enforced when setAudience is used'); 24 | process.exit(); 25 | } 26 | 27 | parsed = jwt.parse(token) 28 | .setIssuer('auth.mydomain.com') 29 | .verify(key); 30 | 31 | if (parsed.error && parsed.error.message.includes('Missing iss claim')) { 32 | console.log('[OK] Missing iss enforced when setIssuer is used'); 33 | } else { 34 | console.log('[NOK] Missing iss enforced when setIssuer is used'); 35 | process.exit(); 36 | } 37 | 38 | payload.iss = 'auth.mydomain.com'; 39 | payload.aud = 'A1B2C3D4E5.com.mydomain.myservice'; 40 | 41 | token = jwt.generate('HS512', payload, key); 42 | 43 | parsed = jwt.parse(token) 44 | .setAudience('AABBCCDDEE.com.mydomain.myservice') 45 | .verify(key); 46 | 47 | if (parsed.error && parsed.error.message.includes('Mismatching aud claim')) { 48 | console.log('[OK] Mismatching aud enforced when setAudience is used'); 49 | } else { 50 | console.log('[NOK] Mismatching aud enforced when setAudience is used'); 51 | process.exit(); 52 | } 53 | 54 | parsed = jwt.parse(token) 55 | .setAudience([ 'AABBCCDDEE.com.mydomain.myservice' ]) 56 | .verify(key); 57 | 58 | if (parsed.error && parsed.error.message.includes('Mismatching aud claim')) { 59 | console.log('[OK] Mismatching aud enforced when setAudience is used'); 60 | } else { 61 | console.log('[NOK] Mismatching aud enforced when setAudience is used'); 62 | process.exit(); 63 | } 64 | 65 | parsed = jwt.parse(token) 66 | .setAudience([ 67 | 'A1B2C3D4E5.com.mydomain.myservice', 68 | 'AABBCCDDEE.com.mydomain.myservice']) 69 | .verify(key); 70 | 71 | if (!parsed.error) { 72 | console.log('[OK] Matching aud recognized when setAudience is used'); 73 | } else { 74 | console.log('[NOK] Matching aud recognized when setAudience is used'); 75 | process.exit(); 76 | } 77 | 78 | parsed = jwt.parse(token) 79 | .setIssuer('aaaa.mydomain.com') 80 | .verify(key); 81 | 82 | if (parsed.error && parsed.error.message.includes('Mismatching iss claim')) { 83 | console.log('[OK] Mismatching iss enforced when setIssuer is used'); 84 | } else { 85 | console.log('[NOK] Mismatching iss enforced when setIssuer is used'); 86 | process.exit(); 87 | } 88 | 89 | parsed = jwt.parse(token) 90 | .setIssuer(['aaaa.mydomain.com']) 91 | .verify(key); 92 | 93 | if (parsed.error && parsed.error.message.includes('Mismatching iss claim')) { 94 | console.log('[OK] Mismatching iss enforced when setIssuer is used'); 95 | } else { 96 | console.log('[NOK] Mismatching iss enforced when setIssuer is used'); 97 | process.exit(); 98 | } 99 | 100 | parsed = jwt.parse(token) 101 | .setIssuer([ 'auth.mydomain.com', 'aaaa.mydomain.com' ]) 102 | .verify(key); 103 | 104 | if (!parsed.error) { 105 | console.log('[OK] Matching iss recognized when setIssuer is used'); 106 | } else { 107 | console.log('[NOK] Matching iss recognized when setIssuer is used'); 108 | process.exit(); 109 | } 110 | -------------------------------------------------------------------------------- /test/jws_basic.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const crypto = require('crypto'); 3 | const jwt = require('../index.js'); 4 | 5 | const KEYS_DIR = __dirname + '/pem_keys/'; 6 | 7 | const simKey = crypto.randomBytes(64); 8 | const priRsa = fs.readFileSync(KEYS_DIR + 'priRsa.key'); 9 | const pubRsa = fs.readFileSync(KEYS_DIR + 'pubRsa.key'); 10 | const priEc256 = fs.readFileSync(KEYS_DIR + 'priEc256.key'); 11 | const pubEc256 = fs.readFileSync(KEYS_DIR + 'pubEc256.key'); 12 | const priEc384 = fs.readFileSync(KEYS_DIR + 'priEc384.key'); 13 | const pubEc384 = fs.readFileSync(KEYS_DIR + 'pubEc384.key'); 14 | const priEc521 = fs.readFileSync(KEYS_DIR + 'priEc521.key'); 15 | const pubEc521 = fs.readFileSync(KEYS_DIR + 'pubEc521.key'); 16 | 17 | const payload = { 18 | iss: 'auth.mydomain.com', 19 | aud: 'A1B2C3D4E5.com.mydomain.myservice', 20 | sub: 'jack.sparrow@example.com', 21 | info: 'Hello World!', 22 | list: [1, 2, 3, 4] 23 | }; 24 | 25 | const tests = [ 26 | { 27 | alg: 'HS256', 28 | sKey: simKey, 29 | vKey: simKey, 30 | label: 'key as buffer at both generation and verification' 31 | }, 32 | { 33 | alg: 'HS384', 34 | sKey: simKey, 35 | vKey: simKey, 36 | label: 'key as buffer at both generation and verification' 37 | }, 38 | { 39 | alg: 'HS512', 40 | sKey: simKey, 41 | vKey: simKey, 42 | label: 'key as buffer at both generation and verification' 43 | }, 44 | { 45 | alg: 'HS256', 46 | sKey: simKey, 47 | vKey: simKey.toString('base64'), 48 | label: 'key as buffer at generation, as base64 string at verification' 49 | }, 50 | { 51 | alg: 'HS384', 52 | sKey: simKey, 53 | vKey: simKey.toString('base64'), 54 | label: 'key as buffer at generation, as base64 string at verification' 55 | }, 56 | { 57 | alg: 'HS512', 58 | sKey: simKey, 59 | vKey: simKey.toString('base64'), 60 | label: 'key as buffer at generation, as base64 string at verification' 61 | }, 62 | { 63 | alg: 'HS256', 64 | sKey: simKey.toString('base64'), 65 | vKey: simKey, 66 | label: 'key as base64 string at generation, as buffer at verification' 67 | }, 68 | { 69 | alg: 'HS384', 70 | sKey: simKey.toString('base64'), 71 | vKey: simKey, 72 | label: 'key as base64 string at generation, as buffer at verification' 73 | }, 74 | { 75 | alg: 'HS512', 76 | sKey: simKey.toString('base64'), 77 | vKey: simKey, 78 | label: 'key as base64 string at generation, as buffer at verification' 79 | }, 80 | { 81 | alg: 'HS256', 82 | sKey: simKey.toString('base64'), 83 | vKey: simKey.toString('base64'), 84 | label: 'key as base64 string at both generation and verification' 85 | }, 86 | { 87 | alg: 'HS384', 88 | sKey: simKey.toString('base64'), 89 | vKey: simKey.toString('base64'), 90 | label: 'key as base64 string at both generation and verification' 91 | }, 92 | { 93 | alg: 'HS512', 94 | sKey: simKey.toString('base64'), 95 | vKey: simKey.toString('base64'), 96 | label: 'key as base64 string at both generation and verification' 97 | }, 98 | { 99 | alg: 'RS256', 100 | sKey: priRsa, 101 | vKey: pubRsa, 102 | label: 'both private and public key as buffer' 103 | }, 104 | { 105 | alg: 'RS384', 106 | sKey: priRsa, 107 | vKey: pubRsa, 108 | label: 'both private and public key as buffer' 109 | }, 110 | { 111 | alg: 'RS512', 112 | sKey: priRsa, 113 | vKey: pubRsa, 114 | label: 'both private and public key as buffer' 115 | }, 116 | { 117 | alg: 'RS256', 118 | sKey: priRsa, 119 | vKey: pubRsa.toString(), 120 | label: 'private key as buffer, public key as UTF-8 string' 121 | }, 122 | { 123 | alg: 'RS384', 124 | sKey: priRsa, 125 | vKey: pubRsa.toString(), 126 | label: 'private key as buffer, public key as UTF-8 string' 127 | }, 128 | { 129 | alg: 'RS512', 130 | sKey: priRsa, 131 | vKey: pubRsa.toString(), 132 | label: 'private key as buffer, public key as UTF-8 string' 133 | }, 134 | { 135 | alg: 'RS256', 136 | sKey: priRsa.toString(), 137 | vKey: pubRsa, 138 | label: 'private key as UTF-8 string, public key as buffer' 139 | }, 140 | { 141 | alg: 'RS384', 142 | sKey: priRsa.toString(), 143 | vKey: pubRsa, 144 | label: 'private key as UTF-8 string, public key as buffer' 145 | }, 146 | { 147 | alg: 'RS512', 148 | sKey: priRsa.toString(), 149 | vKey: pubRsa, 150 | label: 'private key as UTF-8 string, public key as buffer' 151 | }, 152 | { 153 | alg: 'RS256', 154 | sKey: priRsa.toString(), 155 | vKey: pubRsa.toString(), 156 | label: 'both private and public key as UTF-8 string' 157 | }, 158 | { 159 | alg: 'RS384', 160 | sKey: priRsa.toString(), 161 | vKey: pubRsa.toString(), 162 | label: 'both private and public key as UTF-8 string' 163 | }, 164 | { 165 | alg: 'RS512', 166 | sKey: priRsa.toString(), 167 | vKey: pubRsa.toString(), 168 | label: 'both private and public key as UTF-8 string' 169 | }, 170 | { 171 | alg: 'ES256', 172 | sKey: priEc256, 173 | vKey: pubEc256, 174 | label: 'both private and public key as buffer' 175 | }, 176 | { 177 | alg: 'ES384', 178 | sKey: priEc384, 179 | vKey: pubEc384, 180 | label: 'both private and public key as buffer' 181 | }, 182 | { 183 | alg: 'ES512', 184 | sKey: priEc521, 185 | vKey: pubEc521, 186 | label: 'both private and public key as buffer' 187 | }, 188 | { 189 | alg: 'ES256', 190 | sKey: priEc256, 191 | vKey: pubEc256.toString(), 192 | label: 'private key as buffer, public key as UTF-8 string' 193 | }, 194 | { 195 | alg: 'ES384', 196 | sKey: priEc384, 197 | vKey: pubEc384.toString(), 198 | label: 'private key as buffer, public key as UTF-8 string' 199 | }, 200 | { 201 | alg: 'ES512', 202 | sKey: priEc521, 203 | vKey: pubEc521.toString(), 204 | label: 'private key as buffer, public key as UTF-8 string' 205 | }, 206 | { 207 | alg: 'ES256', 208 | sKey: priEc256.toString(), 209 | vKey: pubEc256, 210 | label: 'private key as UTF-8 string, public key as buffer' 211 | }, 212 | { 213 | alg: 'ES384', 214 | sKey: priEc384.toString(), 215 | vKey: pubEc384, 216 | label: 'private key as UTF-8 string, public key as buffer' 217 | }, 218 | { 219 | alg: 'ES512', 220 | sKey: priEc521.toString(), 221 | vKey: pubEc521, 222 | label: 'private key as UTF-8 string, public key as buffer' 223 | }, 224 | { 225 | alg: 'ES256', 226 | sKey: priEc256.toString(), 227 | vKey: pubEc256.toString(), 228 | label: 'both private and public key as UTF-8 string' 229 | }, 230 | { 231 | alg: 'ES384', 232 | sKey: priEc384.toString(), 233 | vKey: pubEc384.toString(), 234 | label: 'both private and public key as UTF-8 string' 235 | }, 236 | { 237 | alg: 'ES512', 238 | sKey: priEc521.toString(), 239 | vKey: pubEc521.toString(), 240 | label: 'both private and public key as UTF-8 string' 241 | } 242 | ]; 243 | 244 | console.log('\nBASIC TEST CASES - SYNCHRONOUS MODE\n'); 245 | let token; 246 | let result; 247 | for (let test of tests) { 248 | token = jwt.generate(test.alg, payload, test.sKey); 249 | result = jwt.parse(token) 250 | .setTokenLifetime(60000) 251 | .verify(test.vKey); 252 | if (result.error) { 253 | console.log(`[NOK] ${test.alg}, ${test.label}`); 254 | console.log(result); 255 | process.exit(); 256 | } 257 | console.log(`[OK] ${test.alg}, ${test.label}`); 258 | } 259 | 260 | console.log('\nBASIC TEST CASES - ASYNCHRONOUS MODE\n'); 261 | executeCaseAsync(0); 262 | 263 | function executeCaseAsync(idx) { 264 | if (idx === tests.length) process.exit(); 265 | let test = tests[idx]; 266 | jwt.generate(test.alg, payload, test.sKey, (error, token) => { 267 | if (error) throw error; 268 | jwt.parse(token) 269 | .setTokenLifetime(60000) 270 | .verify(test.vKey, (error, result) => { 271 | if (error) throw error; 272 | if (result.error) { 273 | console.log(`[NOK] ${test.alg}, ${test.label}`); 274 | console.log(result); 275 | process.exit(); 276 | } 277 | console.log(`[OK] ${test.alg}, ${test.label}`); 278 | executeCaseAsync(++idx); 279 | }); 280 | }); 281 | } 282 | -------------------------------------------------------------------------------- /test/jws_expiration.js: -------------------------------------------------------------------------------- 1 | const crypto = require('crypto'); 2 | const jwt = require('../index.js'); 3 | 4 | const KEYS_DIR = __dirname + '/pem_keys/'; 5 | 6 | const key = crypto.randomBytes(64); 7 | 8 | const payload = { 9 | iss: 'auth.mydomain.com', 10 | aud: 'A1B2C3D4E5.com.mydomain.myservice', 11 | sub: 'jack.sparrow@example.com', 12 | info: 'Hello World!', 13 | list: [1, 2, 3, 4], 14 | exp: Math.floor(Date.now() / 1000) + 4 // expires in 4 seconds 15 | }; 16 | 17 | function pause(duration) { 18 | return new Promise((resolve) => { 19 | setTimeout(resolve, duration); 20 | }); 21 | } 22 | 23 | function testExpirationByIatAsync(token) { 24 | return new Promise((resolve) => { 25 | jwt.parse(token).setTokenLifetime(2).verify(key, (error, parsed) => { 26 | if (parsed.expired && parsed.expired === parsed.payload.iat + 2) { 27 | console.log('[OK] Expiration by iat; asynchronous verification API'); 28 | resolve(); 29 | } else { 30 | console.log('[NOK] Expiration by iat; asynchronous verification API'); 31 | process.exit(); 32 | } 33 | }); 34 | }); 35 | } 36 | 37 | function testExpirationByExpAsync(token) { 38 | return new Promise((resolve) => { 39 | jwt.parse(token).setTokenLifetime(60).verify(key, (error, parsed) => { 40 | if (parsed.expired && parsed.expired === parsed.payload.exp) { 41 | console.log('[OK] Expiration by exp; asynchronous verification API'); 42 | resolve(); 43 | } else { 44 | console.log('[NOK] Expiration by exp; asynchronous verification API'); 45 | process.exit(); 46 | } 47 | }); 48 | }); 49 | } 50 | 51 | (async () => { 52 | let token = jwt.generate('HS512', payload, key); 53 | 54 | await pause(1500); 55 | let parsed = jwt.parse(token).setTokenLifetime(1).verify(key); 56 | if (parsed.expired && parsed.expired === parsed.payload.iat + 1) { 57 | console.log('\n[OK] Expiration by iat; synchronous verification API'); 58 | } else { 59 | console.log('\n[NOK] Expiration by iat; synchronous verification API'); 60 | process.exit(); 61 | } 62 | 63 | await pause(1500); 64 | await testExpirationByIatAsync(token); 65 | 66 | await pause(1500); 67 | parsed = jwt.parse(token).setTokenLifetime(60).verify(key); 68 | if (parsed.expired && parsed.expired === parsed.payload.exp) { 69 | console.log('[OK] Expiration by exp; synchronous verification API'); 70 | } else { 71 | console.log('[NOK] Expiration by exp; synchronous verification API'); 72 | process.exit(); 73 | } 74 | 75 | payload.exp = Math.floor(Date.now() / 1000) + 1 // expires in 1 second 76 | token = jwt.generate('HS512', payload, key); 77 | await pause(1500); 78 | await testExpirationByExpAsync(token); 79 | 80 | })(); 81 | -------------------------------------------------------------------------------- /test/jws_interop.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const crypto = require('crypto'); 3 | const jwt = require('../index.js'); 4 | 5 | const KEYS_DIR = __dirname + '/pem_keys/'; 6 | 7 | try { 8 | var jws = require('jws'); 9 | } catch (error) { 10 | console.log('\nThis test requires installation of the jws package'); 11 | console.log('\n$ npm install jws\n'); 12 | process.exit(); 13 | } 14 | 15 | const simKey = crypto.randomBytes(64); 16 | const priRsa = fs.readFileSync(KEYS_DIR + 'priRsa.key'); 17 | const pubRsa = fs.readFileSync(KEYS_DIR + 'pubRsa.key'); 18 | const priEc256 = fs.readFileSync(KEYS_DIR + 'priEc256.key'); 19 | const pubEc256 = fs.readFileSync(KEYS_DIR + 'pubEc256.key'); 20 | const priEc384 = fs.readFileSync(KEYS_DIR + 'priEc384.key'); 21 | const pubEc384 = fs.readFileSync(KEYS_DIR + 'pubEc384.key'); 22 | const priEc521 = fs.readFileSync(KEYS_DIR + 'priEc521.key'); 23 | const pubEc521 = fs.readFileSync(KEYS_DIR + 'pubEc521.key'); 24 | 25 | const payload = { 26 | iss: 'auth.mydomain.com', 27 | aud: 'A1B2C3D4E5.com.mydomain.myservice', 28 | sub: 'jack.sparrow@example.com', 29 | info: 'Hello World!', 30 | list: [1, 2, 3, 4] 31 | }; 32 | 33 | const tests = [ 34 | {alg: 'HS256', sKey: simKey, vKey: simKey}, 35 | {alg: 'HS384', sKey: simKey, vKey: simKey}, 36 | {alg: 'HS512', sKey: simKey, vKey: simKey}, 37 | {alg: 'RS256', sKey: priRsa, vKey: pubRsa}, 38 | {alg: 'RS384', sKey: priRsa, vKey: pubRsa}, 39 | {alg: 'RS512', sKey: priRsa, vKey: pubRsa}, 40 | {alg: 'ES256', sKey: priEc256, vKey: pubEc256}, 41 | {alg: 'ES384', sKey: priEc384, vKey: pubEc384}, 42 | {alg: 'ES512', sKey: priEc521, vKey: pubEc521} 43 | ]; 44 | 45 | let token; 46 | let parsed; 47 | let result; 48 | 49 | console.log('\nGENERATION WITH node-webtokens / VERIFICATION WITH jws\n'); 50 | for (let test of tests) { 51 | token = jwt.generate(test.alg, payload, test.sKey); 52 | parsed = jws.decode(token); 53 | if(jws.verify(token, parsed.header.alg, test.vKey)) { 54 | console.log(`[OK] ${test.alg}`); 55 | } else { 56 | console.log(`[NOK] ${test.alg}`); 57 | process.exit(); 58 | } 59 | } 60 | 61 | console.log('\nGENERATION WITH jws / VERIFICATION WITH node-webtokens\n'); 62 | for (let test of tests) { 63 | payload.iat = Date.now(); 64 | token = jws.sign({ 65 | header: {alg: test.alg}, 66 | payload: payload, 67 | secret: test.sKey 68 | }); 69 | result = jwt.parse(token).verify(test.vKey); 70 | if (result.error) { 71 | console.log(`[NOK] ${test.alg}`); 72 | process.exit(); 73 | } else { 74 | console.log(`[OK] ${test.alg}`); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /test/jws_negative.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const crypto = require('crypto'); 3 | const jwt = require('../index.js'); 4 | const buf2b64url = require('../lib/common.js').buf2b64url; 5 | 6 | const KEYS_DIR = __dirname + '/pem_keys/'; 7 | 8 | const simKey = crypto.randomBytes(64); 9 | const priRsa = fs.readFileSync(KEYS_DIR + 'priRsa.key'); 10 | const pubRsa = fs.readFileSync(KEYS_DIR + 'pubRsa.key'); 11 | const priEc256 = fs.readFileSync(KEYS_DIR + 'priEc256.key'); 12 | const pubEc256 = fs.readFileSync(KEYS_DIR + 'pubEc256.key'); 13 | const priEc384 = fs.readFileSync(KEYS_DIR + 'priEc384.key'); 14 | const pubEc384 = fs.readFileSync(KEYS_DIR + 'pubEc384.key'); 15 | const priEc521 = fs.readFileSync(KEYS_DIR + 'priEc521.key'); 16 | const pubEc521 = fs.readFileSync(KEYS_DIR + 'pubEc521.key'); 17 | 18 | const payload = { 19 | iss: 'auth.mydomain.com', 20 | aud: 'A1B2C3D4E5.com.mydomain.myservice', 21 | sub: 'jack.sparrow@example.com', 22 | info: 'Hello World!', 23 | list: [1, 2, 3, 4] 24 | }; 25 | 26 | const tests = [ 27 | {alg: 'HS256', sKey: simKey, vKey: simKey}, 28 | {alg: 'HS384', sKey: simKey, vKey: simKey}, 29 | {alg: 'HS512', sKey: simKey, vKey: simKey}, 30 | {alg: 'RS256', sKey: priRsa, vKey: pubRsa}, 31 | {alg: 'RS384', sKey: priRsa, vKey: pubRsa}, 32 | {alg: 'RS512', sKey: priRsa, vKey: pubRsa}, 33 | {alg: 'ES256', sKey: priEc256, vKey: pubEc256}, 34 | {alg: 'ES384', sKey: priEc384, vKey: pubEc384}, 35 | {alg: 'ES512', sKey: priEc521, vKey: pubEc521} 36 | ]; 37 | 38 | let token; 39 | let validToken; 40 | let parsed; 41 | let key; 42 | 43 | for (let test of tests) { 44 | console.log(`\n${test.alg}`); 45 | validToken = jwt.generate(test.alg, payload, test.sKey); 46 | // no alg in header 47 | token = removeAlgFromHeader(validToken); 48 | parsed = jwt.parse(token).verify(test.vKey); 49 | if (parsed.error && 50 | parsed.error.message.includes('Missing or invalid alg claim in header')) { 51 | console.log(`[OK] missing alg claim`); 52 | } else { 53 | console.log(`[NOK] missing alg claim`); 54 | console.log(parsed); 55 | process.exit(); 56 | } 57 | 58 | // unrecognized alg in header 59 | token = messupAlgInHeader(validToken); 60 | parsed = jwt.parse(token).verify(test.vKey); 61 | if (parsed.error && parsed.error.message.includes('Unrecognized algorithm')) { 62 | console.log(`[OK] unrecognized alg claim`); 63 | } else { 64 | console.log(`[NOK] unrecognized alg claim`); 65 | console.log(parsed); 66 | process.exit(); 67 | } 68 | 69 | // unwanted alg in header 70 | parsed = jwt.parse(validToken) 71 | .setAlgorithmList(['dummy1', 'dummy2']) 72 | .verify(test.vKey); 73 | if (parsed.error && parsed.error.message.includes('Unwanted algorithm')) { 74 | console.log(`[OK] unwanted alg claim`); 75 | } else { 76 | console.log(`[NOK] unwanted alg claim`); 77 | console.log(parsed); 78 | process.exit(); 79 | } 80 | 81 | // tampered header 82 | token = tamperHeader(validToken); 83 | parsed = jwt.parse(token).verify(test.vKey); 84 | if (parsed.error && parsed.error.message.includes('Integrity check failed')) { 85 | console.log(`[OK] tampered header`); 86 | } else { 87 | console.log(`[NOK] tampered header`); 88 | console.log(parsed); 89 | process.exit(); 90 | } 91 | 92 | // tampered payload 93 | token = tamperPayload(validToken); 94 | parsed = jwt.parse(token).verify(test.vKey); 95 | if (parsed.error && parsed.error.message.includes('Integrity check failed')) { 96 | console.log(`[OK] tampered payload`); 97 | } else { 98 | console.log(`[NOK] tampered payload`); 99 | console.log(parsed); 100 | process.exit(); 101 | } 102 | 103 | // check with wrong key 104 | key = messupVerificationKey(test.vKey); 105 | parsed = jwt.parse(token).verify(test.vKey); 106 | if (parsed.error && parsed.error.message.includes('Integrity check failed')) { 107 | console.log(`[OK] wrong key`); 108 | } else { 109 | console.log(`[NOK] wrong key`); 110 | console.log(parsed); 111 | process.exit(); 112 | } 113 | 114 | // check with invalid key type 115 | key = messupVerificationKeyType(test.vKey); 116 | parsed = jwt.parse(token).verify(test.vKey); 117 | if (parsed.error && parsed.error.message.includes('Integrity check failed')) { 118 | console.log(`[OK] wrong key type`); 119 | } else { 120 | console.log(`[NOK] wrong key type`); 121 | console.log(parsed); 122 | process.exit(); 123 | } 124 | 125 | // check with invalid key length 126 | key = messupVerificationKeyLength(test.vKey); 127 | parsed = jwt.parse(token).verify(test.vKey); 128 | if (parsed.error && parsed.error.message.includes('Integrity check failed')) { 129 | console.log(`[OK] wrong key length`); 130 | } else { 131 | console.log(`[NOK] wrong key length`); 132 | console.log(parsed); 133 | process.exit(); 134 | } 135 | } 136 | 137 | 138 | function removeAlgFromHeader(token) { 139 | let parts = token.split('.'); 140 | let header = JSON.parse(Buffer.from(parts[0], 'base64')); 141 | delete header.alg; 142 | let newHeader = Buffer.from(JSON.stringify(header)).toString('base64'); 143 | return `${buf2b64url(newHeader)}.${parts[1]}.${parts[2]}`; 144 | } 145 | 146 | function messupAlgInHeader(token) { 147 | let parts = token.split('.'); 148 | let header = JSON.parse(Buffer.from(parts[0], 'base64')); 149 | header.alg = 'dummy'; 150 | let newHeader = Buffer.from(JSON.stringify(header)).toString('base64'); 151 | return `${buf2b64url(newHeader)}.${parts[1]}.${parts[2]}`; 152 | } 153 | 154 | function tamperHeader(token) { 155 | let parts = token.split('.'); 156 | let header = JSON.parse(Buffer.from(parts[0], 'base64')); 157 | header.extra = 'dummy'; 158 | let newHeader = Buffer.from(JSON.stringify(header)).toString('base64'); 159 | return `${buf2b64url(newHeader)}.${parts[1]}.${parts[2]}`; 160 | } 161 | 162 | function tamperPayload(token) { 163 | let parts = token.split('.'); 164 | let payload = JSON.parse(Buffer.from(parts[1], 'base64')); 165 | payload.extra = 'dummy'; 166 | let dummyPayload = Buffer.from(JSON.stringify(payload)).toString('base64'); 167 | return `${buf2b64url(parts[0])}.${dummyPayload}.${parts[2]}`; 168 | } 169 | 170 | function messupVerificationKey(key) { 171 | let wrongKey = Buffer.from(key); 172 | wrongKey[0] ^= 1; 173 | return wrongKey; 174 | } 175 | 176 | function messupVerificationKeyType(key) { 177 | return key.toString('hex'); 178 | } 179 | 180 | function messupVerificationKeyLength(key) { 181 | let len = key.length; 182 | return key.slice(0, len / 2); 183 | } 184 | -------------------------------------------------------------------------------- /test/jws_performance.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const crypto = require('crypto'); 3 | const jwt = require('../index.js'); 4 | 5 | const ITER = 1000; 6 | const KEYS_DIR = __dirname + '/pem_keys/'; 7 | 8 | const simKey = crypto.randomBytes(64); 9 | const priRsa = fs.readFileSync(KEYS_DIR + 'priRsa.key'); 10 | const pubRsa = fs.readFileSync(KEYS_DIR + 'pubRsa.key'); 11 | const priEc256 = fs.readFileSync(KEYS_DIR + 'priEc256.key'); 12 | const pubEc256 = fs.readFileSync(KEYS_DIR + 'pubEc256.key'); 13 | const priEc384 = fs.readFileSync(KEYS_DIR + 'priEc384.key'); 14 | const pubEc384 = fs.readFileSync(KEYS_DIR + 'pubEc384.key'); 15 | const priEc521 = fs.readFileSync(KEYS_DIR + 'priEc521.key'); 16 | const pubEc521 = fs.readFileSync(KEYS_DIR + 'pubEc521.key'); 17 | 18 | const payload = { 19 | iss: 'auth.mydomain.com', 20 | aud: 'A1B2C3D4E5.com.mydomain.myservice', 21 | sub: 'jack.sparrow@example.com', 22 | info: 'Hello World!', 23 | list: [1, 2, 3, 4] 24 | }; 25 | 26 | const tests = [ 27 | {alg: 'HS256', sKey: simKey, vKey: simKey}, 28 | {alg: 'HS384', sKey: simKey, vKey: simKey}, 29 | {alg: 'HS512', sKey: simKey, vKey: simKey}, 30 | {alg: 'RS256', sKey: priRsa, vKey: pubRsa}, 31 | {alg: 'RS384', sKey: priRsa, vKey: pubRsa}, 32 | {alg: 'RS512', sKey: priRsa, vKey: pubRsa}, 33 | {alg: 'ES256', sKey: priEc256, vKey: pubEc256}, 34 | {alg: 'ES384', sKey: priEc384, vKey: pubEc384}, 35 | {alg: 'ES512', sKey: priEc521, vKey: pubEc521} 36 | ]; 37 | 38 | let start; 39 | let delta; 40 | let token; 41 | let result; 42 | 43 | console.log(`\nGENERATION OF ${ITER} TOKENS\n`); 44 | for (let test of tests) { 45 | start = process.hrtime(); 46 | for (let j = 0; j < ITER; j++) { 47 | token = jwt.generate(test.alg, payload, test.sKey); 48 | } 49 | delta = process.hrtime(start); 50 | console.log(`${test.alg} in ${formatResult(delta)}`); 51 | } 52 | 53 | console.log(`\nVERIFICATION OF ${ITER} TOKENS\n`); 54 | for (let test of tests) { 55 | token = jwt.generate(test.alg, payload, test.sKey); 56 | start = process.hrtime(); 57 | for (let j = 0; j < ITER; j++) { 58 | result = jwt.parse(token).setTokenLifetime(60).verify(test.vKey); 59 | } 60 | delta = process.hrtime(start); 61 | console.log(`${test.alg} in ${formatResult(delta)}`); 62 | } 63 | 64 | function formatResult(delta) { 65 | let ms = delta[0] * 1e3 + delta[1] * 1e-6; 66 | if (ms < 1000) return `${ms.toFixed(1)} ms`; 67 | return `${(ms / 1000).toFixed(3)} s`; 68 | } 69 | -------------------------------------------------------------------------------- /test/pem_keys/priEc256.key: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PARAMETERS----- 2 | BggqhkjOPQMBBw== 3 | -----END EC PARAMETERS----- 4 | -----BEGIN EC PRIVATE KEY----- 5 | MHcCAQEEID9B4GxZExd2lX7Hv1JUQGztp5hK46ENr2gUDBqhyxeCoAoGCCqGSM49 6 | AwEHoUQDQgAEnJTpPslFr456pNFxqHSzE2X00KZUq91d8rEUmLnXgw2DpKuSFT0I 7 | T8lU756/QlzTdlZAM6a2eTQ+UlwJEFeUYA== 8 | -----END EC PRIVATE KEY----- 9 | -------------------------------------------------------------------------------- /test/pem_keys/priEc384.key: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PARAMETERS----- 2 | BgUrgQQAIg== 3 | -----END EC PARAMETERS----- 4 | -----BEGIN EC PRIVATE KEY----- 5 | MIGkAgEBBDDpGHpb1a+lKoPXTcOXODkhrt4KQ+oOvP3WQhR72HZi/8n4/M8JrFbl 6 | BMB10Rs2VnOgBwYFK4EEACKhZANiAARwiSEmUKTuWOcoxMxWC83l7q66plfVH3GU 7 | 3YZbaBtKPvV7cE65laKcAeDUA69SCvvDaj+5fYMSW7tDVXrb1mWHit1vFQaS2p3i 8 | usZh+V8qRmFfdb6Tly3KY+e1MXfE/ag= 9 | -----END EC PRIVATE KEY----- 10 | -------------------------------------------------------------------------------- /test/pem_keys/priEc521.key: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PARAMETERS----- 2 | BgUrgQQAIw== 3 | -----END EC PARAMETERS----- 4 | -----BEGIN EC PRIVATE KEY----- 5 | MIHcAgEBBEIAor1S2VIQlBK+jGA31HBzoBQrGHqg5xdx2Q+NRT75IzcgY3sdf0x7 6 | n/FaCcQ6zhhV57sHR+dyk4e5AK4oD5nqlcWgBwYFK4EEACOhgYkDgYYABAHfZXsb 7 | WJVpyq6aH23g9fDDHIp/Z7WtAZkK2h3mXMypRp7x5RSg1uFFwdLzRl9h1JQDdZY0 8 | He0SqssOrTl8rwt/7AFjxfWv4c5IXYfuVyhY1TcKTBpLtJqbNlI1QJqmRW0VKgIv 9 | 7ZtZifOi/SeWo23A9Sw5m+aahvqgH6jHMP2ophRkFg== 10 | -----END EC PRIVATE KEY----- 11 | -------------------------------------------------------------------------------- /test/pem_keys/priRsa.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEAvw/zIkXvzzuuHAfdoIzwDk20qJxJFPyRUMRNRnat92YKJ4Sw 3 | CO7kCVlSoVPQuQ/FKj6JRp12SzKULQdDC6wm9dVte7HmKuIrM4rNXbd9PCWYWJ7h 4 | acM+OsLYJB3+FUVVJPo7R9Ab36qEv/esbxAYs5M5OG/L3IHH/r/l3+jm4xspP1f/ 5 | 1v1NhhpzS59i+r+6OTAbR4yYb6JQ14QZE9O5emZ3Il/lYn8yUUfxP4Oa4cn49EIx 6 | 1+X+694i2fqIGdm94KMBlv2IpMcFg6aaZ+v9PoFrUCYpYfaVpirpZsbCRM/LIfcC 7 | +HYUWy6gNX09SgTeSAX9rfvqFhFHSpKtG3RapQIDAQABAoIBADRhPAfGYqNOukHn 8 | CiSM3vcc6I1dI1AEmhYpawIJYVI1AE2rO9OHIq9JdLTT1KIXp7cYI+xfGOIuwJ7D 9 | oebeUGQPGg/SSsaw8KUVxY2O01+c8vJAxI2JQFM9vaHRMypDsr8LdxsN+8ZK6k/y 10 | p6xfHlaE4ygMxjfywJCfrXbAVCQogbFW4kQUAjPjW32OqtlghZYzfCLhCWryysz0 11 | 6uA+T/XogJcD2hOPDtr7NEK2pPl2jI5QhvErZxNJJoHoT2WGF1s1lDYhG50kQEIp 12 | O1SaX4bgHmHXrhbLZmWK3PxM6AINqMJnou9t57rpPw6sidhXlgU+cuZDoOX0Yv5T 13 | cpZza8ECgYEA6G8XGRa9ECKPbUZemwxKyZALJlxEafDjrRpGl7Zs3AJDcu7qw/v6 14 | l3UewKqplWs/lIQjMk4kp/VBKhNFexmJnY6QWmuxfCFbv0i0k/r/0RxHFDjVAkL/ 15 | GVT1Xq9b6QR/n4URsmkH5Z2tyEwpg7juaqxwOkeoIADFVBXD71yz9tECgYEA0m8L 16 | CLSQOdtSJynTiHujay+pdiV4QFWQYPmdUEbqTZ1nYxoTL6Dy0v93qoxnWuipkUPk 17 | nnuSvn8JsIlJA/ha/9vMXCQ5r7l+UZSPfaTLZjwi7S8GfhJkiirc93UfElEAsh3H 18 | 2CHgjUCLr9T8derU9gZ4KtxsnzdCu2nyl1yNQ5UCgYBkkGP6++aTC5Doo5CdHsVg 19 | zgy07SjAeXPYWkz+qPQSgHw0RwCWCPiT/R1wGKbEuiFJHZ6ozjx32tn13V6rcxIi 20 | dCMqq/ufjENI0KkoeWmjTMvICQrILrp2eFatP8vRVpvtJcLRYTYEd6NPzPwNPL8S 21 | skbgQE6SgiLeTYh3kLFgcQKBgQCaL1ijWUGv0r4gCep/PzAe8j41XgD9CnSOSXb1 22 | gfBdTC6bk1hpdoFVDnAM23FDT+QmlttlL9/2ijh8TCreqNNsUw0qmjRWSBCBD9wP 23 | UHbkAr4IkYMtTqayAfQDsKJClm1vFJkACNpyRM/U1rbgY18EoPrdVMKV6jSQA3IH 24 | a33p/QKBgC4cYYt8XSDRbN+ynq69i6vCJUYZ7MI6AohbZZN59eObQupIG6cM9T1A 25 | jp2nh6R4WedbMUj3JnSp2ka0hMPgD7iCSZ3rmYu+d4Gcts3DddgZFnlon/JlIdEe 26 | CqAM6YJH3ZMoeKbaCpjOLGQVswL6a5uRvSkHibaF4+JISMRWOecr 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /test/pem_keys/pubEc256.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEnJTpPslFr456pNFxqHSzE2X00KZU 3 | q91d8rEUmLnXgw2DpKuSFT0IT8lU756/QlzTdlZAM6a2eTQ+UlwJEFeUYA== 4 | -----END PUBLIC KEY----- 5 | -------------------------------------------------------------------------------- /test/pem_keys/pubEc384.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEcIkhJlCk7ljnKMTMVgvN5e6uuqZX1R9x 3 | lN2GW2gbSj71e3BOuZWinAHg1AOvUgr7w2o/uX2DElu7Q1V629Zlh4rdbxUGktqd 4 | 4rrGYflfKkZhX3W+k5ctymPntTF3xP2o 5 | -----END PUBLIC KEY----- 6 | -------------------------------------------------------------------------------- /test/pem_keys/pubEc521.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQB32V7G1iVacqumh9t4PXwwxyKf2e1 3 | rQGZCtod5lzMqUae8eUUoNbhRcHS80ZfYdSUA3WWNB3tEqrLDq05fK8Lf+wBY8X1 4 | r+HOSF2H7lcoWNU3CkwaS7SamzZSNUCapkVtFSoCL+2bWYnzov0nlqNtwPUsOZvm 5 | mob6oB+oxzD9qKYUZBY= 6 | -----END PUBLIC KEY----- 7 | -------------------------------------------------------------------------------- /test/pem_keys/pubRsa.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvw/zIkXvzzuuHAfdoIzw 3 | Dk20qJxJFPyRUMRNRnat92YKJ4SwCO7kCVlSoVPQuQ/FKj6JRp12SzKULQdDC6wm 4 | 9dVte7HmKuIrM4rNXbd9PCWYWJ7hacM+OsLYJB3+FUVVJPo7R9Ab36qEv/esbxAY 5 | s5M5OG/L3IHH/r/l3+jm4xspP1f/1v1NhhpzS59i+r+6OTAbR4yYb6JQ14QZE9O5 6 | emZ3Il/lYn8yUUfxP4Oa4cn49EIx1+X+694i2fqIGdm94KMBlv2IpMcFg6aaZ+v9 7 | PoFrUCYpYfaVpirpZsbCRM/LIfcC+HYUWy6gNX09SgTeSAX9rfvqFhFHSpKtG3Ra 8 | pQIDAQAB 9 | -----END PUBLIC KEY----- 10 | --------------------------------------------------------------------------------