mm_njwt 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js ADDED
@@ -0,0 +1,509 @@
1
+ 'use strict';
2
+
3
+ var util = require('util');
4
+ var uuid = require('uuid');
5
+ var crypto = require('crypto');
6
+ var ecdsaSigFormatter = require('ecdsa-sig-formatter');
7
+ var properties = require('./properties.json');
8
+
9
+ var algCryptoMap = {
10
+ HS256: 'SHA256',
11
+ HS384: 'SHA384',
12
+ HS512: 'SHA512',
13
+ RS256: 'RSA-SHA256',
14
+ RS384: 'RSA-SHA384',
15
+ RS512: 'RSA-SHA512',
16
+ ES256: 'RSA-SHA256',
17
+ ES384: 'RSA-SHA384',
18
+ ES512: 'RSA-SHA512',
19
+ none: 'none'
20
+ };
21
+
22
+ var algTypeMap = {
23
+ HS256: 'hmac',
24
+ HS384: 'hmac',
25
+ HS512: 'hmac',
26
+ RS256: 'sign',
27
+ RS384: 'sign',
28
+ RS512: 'sign',
29
+ ES256: 'sign',
30
+ ES384: 'sign',
31
+ ES512: 'sign'
32
+ };
33
+
34
+ function isECDSA(algorithm) {
35
+ return algorithm.indexOf('ES') === 0;
36
+ }
37
+
38
+ function nowEpochSeconds() {
39
+ return Math.floor(new Date().getTime() / 1000);
40
+ }
41
+
42
+ function base64urlEncode(data) {
43
+ const str = typeof data === 'number' ? data.toString() : data;
44
+ return Buffer.from(str)
45
+ .toString('base64')
46
+ .replace(/\+/g, '-')
47
+ .replace(/\//g, '_')
48
+ .replace(/=/g, '');
49
+ }
50
+
51
+ function base64urlUnescape(str) {
52
+ str += new Array(5 - str.length % 4).join('=');
53
+ return str.replace(/\-/g, '+').replace(/_/g, '/');
54
+ }
55
+
56
+ function isSupportedAlg(alg) {
57
+ return !!algCryptoMap[alg];
58
+ }
59
+
60
+ function handleError(cb, err, value) {
61
+ if (typeof cb === 'function') {
62
+ return process.nextTick(function() {
63
+ cb(err, value);
64
+ });
65
+ } else if (err) {
66
+ throw err;
67
+ } else {
68
+ return value;
69
+ }
70
+ }
71
+
72
+ function defaultKeyResolver(kid, cb) {
73
+ return cb(null, this.signingKey);
74
+ }
75
+
76
+ function JwtError(message) {
77
+ this.name = 'JwtError';
78
+ this.message = this.userMessage = message;
79
+ }
80
+ util.inherits(JwtError, Error);
81
+
82
+ function JwtParseError(message, jwtString, parsedHeader, parsedBody, innerError) {
83
+ this.name = 'JwtParseError';
84
+ this.message = this.userMessage = message;
85
+ this.jwtString = jwtString;
86
+ this.parsedHeader = parsedHeader;
87
+ this.parsedBody = parsedBody;
88
+ this.innerError = innerError;
89
+ }
90
+ util.inherits(JwtParseError, Error);
91
+
92
+ function JwtBody(claims) {
93
+ if (!(this instanceof JwtBody)) {
94
+ return new JwtBody(claims);
95
+ }
96
+ var self = this;
97
+ if (claims) {
98
+ Object.keys(claims).forEach(function(k) {
99
+ self[k] = claims[k];
100
+ });
101
+ }
102
+ return this;
103
+ }
104
+
105
+ JwtBody.prototype.toJSON = function() {
106
+ var self = this;
107
+ return Object.keys(self).reduce(function(acc, key) {
108
+ acc[key] = self[key];
109
+ return acc;
110
+ }, {});
111
+ };
112
+ JwtBody.prototype.compact = function compact() {
113
+ return base64urlEncode(JSON.stringify(this));
114
+ };
115
+
116
+ var reservedHeaderKeys = ['typ', 'alg'];
117
+
118
+ function JwtHeader(header, enforceDefaultFields) {
119
+ if (!(this instanceof JwtHeader)) {
120
+ return new JwtHeader(header);
121
+ }
122
+ this.typ = header && header.typ;
123
+ this.alg = header && header.alg;
124
+ if (enforceDefaultFields !== false) {
125
+ this.typ = this.typ || 'JWT';
126
+ this.alg = this.alg || 'HS256';
127
+ }
128
+
129
+ if (header) {
130
+ return Object.keys(header).reduce(function(acc, key) {
131
+ if (reservedHeaderKeys.indexOf(key) === -1 && header.hasOwnProperty(key)) {
132
+ acc[key] = header[key];
133
+ }
134
+ return acc;
135
+ }, this);
136
+ } else {
137
+ return this;
138
+ }
139
+ }
140
+ JwtHeader.prototype.compact = function compact() {
141
+ return base64urlEncode(JSON.stringify(this));
142
+ };
143
+
144
+ function Jwt(claims, enforceDefaultFields) {
145
+ if (!(this instanceof Jwt)) {
146
+ return new Jwt(claims, enforceDefaultFields);
147
+ }
148
+
149
+ this.header = new JwtHeader();
150
+ this.body = new JwtBody(claims);
151
+
152
+ if (enforceDefaultFields !== false) {
153
+ this.setSigningAlgorithm('none');
154
+
155
+ if (!this.body.jti) {
156
+ this.setJti(uuid.v4());
157
+ }
158
+
159
+ if (!this.body.iat) {
160
+ this.setIssuedAt(nowEpochSeconds());
161
+ }
162
+ }
163
+
164
+ return this;
165
+ }
166
+ Jwt.prototype.setClaim = function setClaim(claim, value) {
167
+ this.body[claim] = value;
168
+ return this;
169
+ };
170
+ Jwt.prototype.setHeader = function setHeader(param, value) {
171
+ this.header[param] = value;
172
+ return this;
173
+ };
174
+ Jwt.prototype.setJti = function setJti(jti) {
175
+ this.body.jti = jti;
176
+ return this;
177
+ };
178
+ Jwt.prototype.setSubject = function setSubject(sub) {
179
+ this.body.sub = sub;
180
+ return this;
181
+ };
182
+ Jwt.prototype.setIssuer = function setIssuer(iss) {
183
+ this.body.iss = iss;
184
+ return this;
185
+ };
186
+ Jwt.prototype.setIssuedAt = function setIssuedAt(iat) {
187
+ this.body.iat = iat;
188
+ return this;
189
+ };
190
+ Jwt.prototype.setExpiration = function setExpiration(exp) {
191
+ if (exp) {
192
+ this.body.exp = Math.floor((exp instanceof Date ? exp : new Date(exp)).getTime() / 1000);
193
+ } else {
194
+ delete this.body.exp;
195
+ }
196
+
197
+ return this;
198
+ };
199
+ Jwt.prototype.setNotBefore = function setNotBefore(nbf) {
200
+ if (nbf) {
201
+ this.body.nbf = Math.floor((nbf instanceof Date ? nbf : new Date(nbf)).getTime() / 1000);
202
+ } else {
203
+ delete this.body.nbf;
204
+ }
205
+
206
+ return this;
207
+ };
208
+ Jwt.prototype.setSigningKey = function setSigningKey(key) {
209
+ this.signingKey = key;
210
+ return this;
211
+ };
212
+ Jwt.prototype.setSigningAlgorithm = function setSigningAlgorithm(alg) {
213
+ if (!this.isSupportedAlg(alg)) {
214
+ throw new JwtError(properties.errors.UNSUPPORTED_SIGNING_ALG);
215
+ }
216
+ this.header.alg = alg;
217
+ return this;
218
+ };
219
+
220
+ Jwt.prototype.sign = function sign(payload, algorithm, cryptoInput) {
221
+ var buffer;
222
+ var signature;
223
+ var cryptoAlgName = algCryptoMap[algorithm];
224
+ var signingType = algTypeMap[algorithm];
225
+
226
+ if (!cryptoAlgName) {
227
+ throw new JwtError(properties.errors.UNSUPPORTED_SIGNING_ALG);
228
+ }
229
+
230
+ if (signingType === 'hmac') {
231
+ buffer = crypto.createHmac(cryptoAlgName, cryptoInput).update(payload).digest();
232
+ } else {
233
+ buffer = crypto.createSign(cryptoAlgName).update(payload).sign(cryptoInput);
234
+ }
235
+
236
+ if (isECDSA(algorithm)) {
237
+ signature = ecdsaSigFormatter.derToJose(buffer, algorithm);
238
+ } else {
239
+ signature = base64urlEncode(buffer);
240
+ }
241
+
242
+ return signature;
243
+ };
244
+
245
+ Jwt.prototype.isSupportedAlg = isSupportedAlg;
246
+
247
+ Jwt.prototype.compact = function compact() {
248
+
249
+ var segments = [];
250
+ segments.push(this.header.compact());
251
+ segments.push(this.body.compact());
252
+
253
+ if (this.header.alg !== 'none') {
254
+ if (this.signingKey) {
255
+ this.signature = this.sign(segments.join('.'), this.header.alg, this.signingKey);
256
+ segments.push(this.signature);
257
+ } else {
258
+ throw new Error(properties.errors.SIGNING_KEY_REQUIRED);
259
+ }
260
+ }
261
+
262
+ return segments.join('.');
263
+ };
264
+
265
+ Jwt.prototype.toString = function() {
266
+ return this.compact();
267
+ };
268
+
269
+ Jwt.prototype.isExpired = function() {
270
+ return new Date(this.body.exp * 1000) < new Date();
271
+ };
272
+
273
+ Jwt.prototype.isNotBefore = function() {
274
+ return new Date(this.body.nbf * 1000) >= new Date();
275
+ };
276
+
277
+ function Parser(options) {
278
+ return this;
279
+ }
280
+
281
+ Parser.prototype.isSupportedAlg = isSupportedAlg;
282
+ Parser.prototype.safeJsonParse = function(input) {
283
+ var result;
284
+ try {
285
+ result = JSON.parse(Buffer.from(base64urlUnescape(input), 'base64'));
286
+ } catch (e) {
287
+ return e;
288
+ }
289
+ return result;
290
+ };
291
+ Parser.prototype.parse = function parse(jwtString, cb) {
292
+
293
+ var done = handleError.bind(null, cb);
294
+ var segments = jwtString.split('.');
295
+ var signature;
296
+
297
+ if (segments.length < 2 || segments.length > 3) {
298
+ return done(new JwtParseError(properties.errors.PARSE_ERROR, jwtString, null, null));
299
+ }
300
+
301
+ var header = this.safeJsonParse(segments[0]);
302
+ var body = this.safeJsonParse(segments[1]);
303
+
304
+ if (segments[2]) {
305
+ signature = Buffer.from(base64urlUnescape(segments[2]), 'base64')
306
+ .toString('base64');
307
+ }
308
+
309
+ if (header instanceof Error) {
310
+ return done(new JwtParseError(properties.errors.PARSE_ERROR, jwtString, null, null, header));
311
+ }
312
+ if (body instanceof Error) {
313
+ return done(new JwtParseError(properties.errors.PARSE_ERROR, jwtString, header, null, body));
314
+ }
315
+ var jwt = new Jwt(body, false);
316
+ jwt.setSigningAlgorithm(header.alg);
317
+ jwt.signature = signature;
318
+ jwt.verificationInput = segments[0] + '.' + segments[1];
319
+ jwt.header = new JwtHeader(header, false);
320
+ return done(null, jwt);
321
+ };
322
+
323
+ function Verifier() {
324
+ if (!(this instanceof Verifier)) {
325
+ return new Verifier();
326
+ }
327
+ this.setSigningAlgorithm('HS256');
328
+ this.setKeyResolver(defaultKeyResolver.bind(this));
329
+ return this;
330
+ }
331
+ Verifier.prototype.setSigningAlgorithm = function setSigningAlgorithm(alg) {
332
+ if (!this.isSupportedAlg(alg)) {
333
+ throw new JwtError(properties.errors.UNSUPPORTED_SIGNING_ALG);
334
+ }
335
+ this.signingAlgorithm = alg;
336
+ return this;
337
+ };
338
+ Verifier.prototype.setSigningKey = function setSigningKey(keyStr) {
339
+ this.signingKey = keyStr;
340
+ return this;
341
+ };
342
+ Verifier.prototype.setKeyResolver = function setKeyResolver(keyResolver) {
343
+ this.keyResolver = keyResolver.bind(this);
344
+ };
345
+ Verifier.prototype.isSupportedAlg = isSupportedAlg;
346
+
347
+ Verifier.prototype.verify = function verify(jwtString, cb) {
348
+ var jwt;
349
+
350
+ var done = handleError.bind(null, cb);
351
+
352
+ try {
353
+ jwt = new Parser().parse(jwtString);
354
+ } catch (e) {
355
+ return done(e);
356
+ }
357
+
358
+ var body = jwt.body;
359
+ var header = jwt.header;
360
+ var signature = jwt.signature;
361
+
362
+ var cryptoAlgName = algCryptoMap[header.alg];
363
+ var signingType = algTypeMap[header.alg];
364
+
365
+ if (header.alg !== this.signingAlgorithm) {
366
+ return done(new JwtParseError(properties.errors.SIGNATURE_ALGORITHM_MISMTACH, jwtString, header, body));
367
+ }
368
+
369
+ if (jwt.isExpired()) {
370
+ return done(new JwtParseError(properties.errors.EXPIRED, jwtString, header, body));
371
+ }
372
+
373
+ if (jwt.isNotBefore()) {
374
+ return done(new JwtParseError(properties.errors.NOT_ACTIVE, jwtString, header, body));
375
+ }
376
+
377
+ var digstInput = jwt.verificationInput;
378
+ var verified, digest;
379
+
380
+ return this.keyResolver(header.kid, function(err, signingKey) {
381
+
382
+ if (err) {
383
+ return done(new JwtParseError(util.format(properties.errors.KEY_RESOLVER_ERROR, header.kid),
384
+ jwtString, header, body, err));
385
+ }
386
+
387
+
388
+ if (cryptoAlgName === 'none') {
389
+ verified = true;
390
+ } else if (signingType === 'hmac') {
391
+ digest = crypto.createHmac(cryptoAlgName, signingKey)
392
+ .update(digstInput)
393
+ .digest('base64');
394
+ verified = signature === digest;
395
+ } else {
396
+ var unescapedSignature;
397
+ var signatureType = undefined;
398
+
399
+ if (isECDSA(header.alg)) {
400
+ try {
401
+ unescapedSignature = ecdsaSigFormatter.joseToDer(signature, header.alg);
402
+ } catch (err) {
403
+ return done(new JwtParseError(properties.errors.SIGNATURE_MISMTACH, jwtString, header,
404
+ body, err));
405
+ }
406
+ } else {
407
+ signatureType = 'base64';
408
+ unescapedSignature = base64urlUnescape(signature);
409
+ }
410
+
411
+ verified = crypto.createVerify(cryptoAlgName)
412
+ .update(digstInput)
413
+ .verify(signingKey, unescapedSignature, signatureType);
414
+ }
415
+
416
+ var newJwt = new Jwt(body, false);
417
+
418
+ // since prototype is now frozen, .toString can no longer be overloaded
419
+ // newJwt.toString = function () {
420
+ // return jwtString;
421
+ // };
422
+
423
+ newJwt.header = new JwtHeader(header, false);
424
+
425
+ if (!verified) {
426
+ return done(new JwtParseError(properties.errors.SIGNATURE_MISMTACH, jwtString, header, body));
427
+ }
428
+
429
+ return done(null, newJwt);
430
+ });
431
+ };
432
+
433
+ Verifier.prototype.withKeyResolver = function withKeyResolver(keyResolver) {
434
+ this.keyResolver = keyResolver;
435
+ return this;
436
+ };
437
+
438
+ // vuln: https://security.snyk.io/vuln/SNYK-JS-NJWT-6861582
439
+ Object.freeze(Jwt);
440
+ Object.freeze(Jwt.prototype);
441
+ Object.freeze(JwtBody);
442
+ Object.freeze(JwtBody.prototype);
443
+ Object.freeze(JwtHeader);
444
+ Object.freeze(JwtHeader.prototype);
445
+ Object.freeze(Verifier);
446
+ Object.freeze(Verifier.prototype);
447
+ Object.freeze(Parser);
448
+ Object.freeze(Parser.prototype);
449
+ Object.freeze(JwtParseError);
450
+ Object.freeze(JwtParseError.prototype);
451
+ Object.freeze(JwtError);
452
+ Object.freeze(JwtError.prototype);
453
+
454
+ var jwtLib = {
455
+ Jwt: Jwt,
456
+ JwtBody: JwtBody,
457
+ JwtHeader: JwtHeader,
458
+ Verifier: Verifier,
459
+ base64urlEncode: base64urlEncode,
460
+ base64urlUnescape: base64urlUnescape,
461
+ verify: function( /*jwtTokenString, [signingKey], [algOverride], [callbck] */ ) {
462
+
463
+ var args = Array.prototype.slice.call(arguments);
464
+ var cb = typeof args[args.length - 1] === 'function' ? args.pop() : null;
465
+
466
+ var verifier = new Verifier();
467
+
468
+ if (args.length === 3) {
469
+ verifier.setSigningAlgorithm(args[2]);
470
+ verifier.setSigningKey(args[1]);
471
+ }
472
+
473
+ if (args.length === 2) {
474
+ verifier.setSigningKey(args[1]);
475
+ }
476
+
477
+ if (args.length === 1) {
478
+ verifier.setSigningAlgorithm('none');
479
+ }
480
+
481
+ return verifier.verify(args[0], cb);
482
+
483
+ },
484
+ createVerifier: function() {
485
+ return new Verifier();
486
+ },
487
+ create: function(claims, secret, alg) {
488
+ var args = Array.prototype.slice.call(arguments);
489
+ var jwt;
490
+ if (args.length >= 2) {
491
+ jwt = new Jwt(claims);
492
+ } else if (args.length === 1 && typeof claims === 'string') {
493
+ jwt = new Jwt({});
494
+ secret = claims;
495
+ } else {
496
+ jwt = new Jwt(claims);
497
+ }
498
+ if (alg !== 'none' && !secret) {
499
+ throw new Error(properties.errors.SIGNING_KEY_REQUIRED);
500
+ } else {
501
+ jwt.setSigningAlgorithm(args.length === 3 ? alg : 'HS256');
502
+ jwt.setSigningKey(secret);
503
+ }
504
+ jwt.setExpiration((nowEpochSeconds() + (60 * 60)) * 1000); // one hour
505
+ return jwt;
506
+ }
507
+ };
508
+
509
+ module.exports = jwtLib;
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "mm_njwt",
3
+ "version": "1.0.0",
4
+ "description": "用于加解密通讯",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "test": "node test.js"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/qiuwenwu/mm_njwt.git"
12
+ },
13
+ "keywords": [
14
+ "wechat",
15
+ "public",
16
+ "bot",
17
+ "robot",
18
+ "encode",
19
+ "decode",
20
+ "njwt"
21
+ ],
22
+ "author": "qww",
23
+ "license": "ISC",
24
+ "bugs": {
25
+ "url": "https://github.com/qiuwenwu/mm_njwt/issues"
26
+ },
27
+ "homepage": "https://github.com/qiuwenwu/mm_njwt#readme",
28
+ "dependencies": {
29
+ "ecdsa-sig-formatter": "^1.0.11",
30
+ "secure-random": "^1.1.2",
31
+ "uuid": "^10.0.0"
32
+ }
33
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "errors": {
3
+ "PARSE_ERROR": "Jwt cannot be parsed",
4
+ "EXPIRED": "Jwt is expired",
5
+ "UNSUPPORTED_SIGNING_ALG": "Unsupported signing algorithm",
6
+ "SIGNING_KEY_REQUIRED": "Signing key is required",
7
+ "SIGNATURE_MISMTACH": "Signature verification failed",
8
+ "SIGNATURE_ALGORITHM_MISMTACH": "Unexpected signature algorithm",
9
+ "NOT_ACTIVE": "Jwt not active",
10
+ "KEY_RESOLVER_ERROR": "Error while resolving signing key for kid \"%s\""
11
+ }
12
+ }
package/test.js ADDED
@@ -0,0 +1,13 @@
1
+ var nJwt = require('./index.js');
2
+ var secureRandom = require('secure-random');
3
+
4
+ var signingKey = secureRandom(256, {type: 'Buffer'}); // Create a highly random byte array of 256 bytes
5
+
6
+ var claims = {
7
+ iss: "http://myapp.com/", // The URL of your service
8
+ sub: "users/user1234", // The UID of the user in your system
9
+ scope: "self, admins"
10
+ }
11
+
12
+ var jwt = nJwt.create(claims,signingKey);
13
+ console.log(jwt);