jose 1.27.0 → 1.28.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/CHANGELOG.md +37 -0
- package/README.md +2 -2
- package/lib/help/key_utils.js +15 -3
- package/lib/jwe/encrypt.js +5 -1
- package/lib/jws/serializers.js +11 -0
- package/lib/jws/sign.js +15 -26
- package/lib/jwt/verify.js +8 -5
- package/package.json +4 -4
- package/types/index.d.ts +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,43 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
# [1.28.0](https://github.com/panva/jose/compare/v1.27.3...v1.28.0) (2020-08-10)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* support for validating issuer from a list of values ([#91](https://github.com/panva/jose/issues/91)) ([ce6836a](https://github.com/panva/jose/commit/ce6836af88c9e73c29560233f15ed1760c7dcc13))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
## [1.27.3](https://github.com/panva/jose/compare/v1.27.2...v1.27.3) (2020-08-04)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Bug Fixes
|
|
18
|
+
|
|
19
|
+
* do not mutate unencoded payload when signing for multiple parties ([1695423](https://github.com/panva/jose/commit/169542363f884e4028db9f80086d631e626eb469)), closes [#89](https://github.com/panva/jose/issues/89)
|
|
20
|
+
* ensure "b64" is the same for all recipients edge cases ([d56ec9f](https://github.com/panva/jose/commit/d56ec9f5ddc2612e5ff21fe35d45a56e7153e0e4))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
## [1.27.2](https://github.com/panva/jose/compare/v1.27.1...v1.27.2) (2020-07-01)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
### Bug Fixes
|
|
28
|
+
|
|
29
|
+
* handle private EC keys without public component ([#86](https://github.com/panva/jose/issues/86)) ([e8ad389](https://github.com/panva/jose/commit/e8ad38993e29747098f7fd1594dde4ce893ba802)), closes [#85](https://github.com/panva/jose/issues/85)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
## [1.27.1](https://github.com/panva/jose/compare/v1.27.0...v1.27.1) (2020-06-01)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
### Bug Fixes
|
|
37
|
+
|
|
38
|
+
* allow any JSON numeric value for timestamp values ([7ba4922](https://github.com/panva/jose/commit/7ba492237aaf788914166c134d50fb046041efa0))
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
5
42
|
# [1.27.0](https://github.com/panva/jose/compare/v1.26.1...v1.27.0) (2020-05-05)
|
|
6
43
|
|
|
7
44
|
|
package/README.md
CHANGED
|
@@ -326,8 +326,8 @@ Legend:
|
|
|
326
326
|
<sup>2</sup> Unsecured JWS is [supported][documentation-none] for the JWS and JWT sign and verify
|
|
327
327
|
operations but it is an entirely opt-in behaviour, downgrade attacks are prevented by the required
|
|
328
328
|
use of a special `JWK.Key`-like object that cannot be instantiated through the key import API
|
|
329
|
-
<sup>3</sup> RSAES OAEP using SHA-2 and MGF1 with SHA-2 is only supported when Node.js
|
|
330
|
-
<sup>4</sup> ECDH-ES with X25519 and X448 keys is only supported when Node.js >=
|
|
329
|
+
<sup>3</sup> RSAES OAEP using SHA-2 and MGF1 with SHA-2 is only supported when Node.js `>=12.9.0` runtime is detected
|
|
330
|
+
<sup>4</sup> ECDH-ES with X25519 and X448 keys is only supported when Node.js `^12.17.0 || >=13.9.0` runtime is detected
|
|
331
331
|
<sup>5</sup> Draft specification profiles are updated as minor versions of the library, therefore,
|
|
332
332
|
since they may have breaking changes use the `~` semver operator when using these and pay close
|
|
333
333
|
attention to changelog and the drafts themselves.
|
package/lib/help/key_utils.js
CHANGED
|
@@ -4,6 +4,7 @@ const { EOL } = require('os')
|
|
|
4
4
|
|
|
5
5
|
const errors = require('../errors')
|
|
6
6
|
|
|
7
|
+
const { keyObjectSupported } = require('./runtime_support')
|
|
7
8
|
const { createPublicKey } = require('./key_object')
|
|
8
9
|
const base64url = require('./base64url')
|
|
9
10
|
const asn1 = require('./asn1')
|
|
@@ -89,10 +90,21 @@ const keyObjectToJWK = {
|
|
|
89
90
|
const ECPrivateKey = asn1.get('ECPrivateKey')
|
|
90
91
|
|
|
91
92
|
const { privateKey, algorithm: { parameters: { value: crv } } } = PrivateKeyInfo.decode(der)
|
|
92
|
-
const { privateKey: d, publicKey
|
|
93
|
+
const { privateKey: d, publicKey } = ECPrivateKey.decode(privateKey)
|
|
94
|
+
|
|
95
|
+
if (typeof publicKey === 'undefined') {
|
|
96
|
+
if (keyObjectSupported) {
|
|
97
|
+
return {
|
|
98
|
+
...keyObjectToJWK.ec.public(createPublicKey(keyObject)),
|
|
99
|
+
d: base64url.encodeBuffer(d)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
93
102
|
|
|
94
|
-
|
|
95
|
-
|
|
103
|
+
throw new errors.JOSENotSupported('Private EC keys without the public key embedded are not supported in your Node.js runtime version')
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const x = publicKey.data.slice(1, ((publicKey.data.length - 1) / 2) + 1)
|
|
107
|
+
const y = publicKey.data.slice(((publicKey.data.length - 1) / 2) + 1)
|
|
96
108
|
|
|
97
109
|
return {
|
|
98
110
|
kty: 'EC',
|
package/lib/jwe/encrypt.js
CHANGED
|
@@ -190,7 +190,11 @@ class Encrypt {
|
|
|
190
190
|
let aad
|
|
191
191
|
if (this._aad) {
|
|
192
192
|
final.aad = base64url.encode(this._aad)
|
|
193
|
-
aad = Buffer.concat([
|
|
193
|
+
aad = Buffer.concat([
|
|
194
|
+
Buffer.from(final.protected || ''),
|
|
195
|
+
Buffer.from('.'),
|
|
196
|
+
Buffer.from(final.aad)
|
|
197
|
+
])
|
|
194
198
|
} else {
|
|
195
199
|
aad = Buffer.from(final.protected || '')
|
|
196
200
|
}
|
package/lib/jws/serializers.js
CHANGED
|
@@ -44,9 +44,20 @@ const generalSerializer = (payload, recipients) => {
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
generalSerializer.validate = (jws, recipients) => {
|
|
47
|
+
let validateB64 = false
|
|
47
48
|
recipients.forEach(({ protectedHeader, unprotectedHeader }) => {
|
|
49
|
+
if (protectedHeader && !validateB64 && 'b64' in protectedHeader) {
|
|
50
|
+
validateB64 = true
|
|
51
|
+
}
|
|
48
52
|
validateCrit(protectedHeader, unprotectedHeader, protectedHeader ? protectedHeader.crit : undefined)
|
|
49
53
|
})
|
|
54
|
+
|
|
55
|
+
if (validateB64) {
|
|
56
|
+
const values = recipients.map(({ protectedHeader }) => protectedHeader && protectedHeader.b64)
|
|
57
|
+
if (!values.every((actual, i, [expected]) => actual === expected)) {
|
|
58
|
+
throw new JWSInvalid('the "b64" Header Parameter value MUST be the same for all recipients')
|
|
59
|
+
}
|
|
60
|
+
}
|
|
50
61
|
}
|
|
51
62
|
|
|
52
63
|
const isJSON = (input) => {
|
package/lib/jws/sign.js
CHANGED
|
@@ -57,7 +57,7 @@ class Sign {
|
|
|
57
57
|
/*
|
|
58
58
|
* @private
|
|
59
59
|
*/
|
|
60
|
-
[PROCESS_RECIPIENT] (recipient) {
|
|
60
|
+
[PROCESS_RECIPIENT] (recipient, first) {
|
|
61
61
|
const { key, protectedHeader, unprotectedHeader } = recipient
|
|
62
62
|
|
|
63
63
|
if (key.use === 'enc') {
|
|
@@ -87,32 +87,21 @@ class Sign {
|
|
|
87
87
|
recipient.header = unprotectedHeader
|
|
88
88
|
recipient.protected = Object.keys(joseHeader.protected).length ? base64url.JSON.encode(joseHeader.protected) : ''
|
|
89
89
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
throw new JWSInvalid('the "b64" Header Parameter value MUST be the same for all recipients')
|
|
90
|
+
if (first && joseHeader.protected.crit && joseHeader.protected.crit.includes('b64') && joseHeader.protected.b64 === false) {
|
|
91
|
+
if (this._binary) {
|
|
92
|
+
this._payload = base64url.decodeToBuffer(this._payload)
|
|
94
93
|
} else {
|
|
95
|
-
this.
|
|
94
|
+
this._payload = base64url.decode(this._payload)
|
|
96
95
|
}
|
|
97
|
-
|
|
98
|
-
if (!joseHeader.protected.b64) {
|
|
99
|
-
if (this._binary) {
|
|
100
|
-
this._payload = base64url.decodeToBuffer(this._payload)
|
|
101
|
-
} else {
|
|
102
|
-
this._payload = base64url.decode(this._payload)
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
toBeSigned = Buffer.concat([
|
|
107
|
-
Buffer.from(recipient.protected || ''),
|
|
108
|
-
Buffer.from('.'),
|
|
109
|
-
Buffer.isBuffer(this._payload) ? this._payload : Buffer.from(this._payload)
|
|
110
|
-
])
|
|
111
|
-
} else {
|
|
112
|
-
toBeSigned = `${recipient.protected || ''}.${this._payload}`
|
|
113
96
|
}
|
|
114
97
|
|
|
115
|
-
|
|
98
|
+
const data = Buffer.concat([
|
|
99
|
+
Buffer.from(recipient.protected || ''),
|
|
100
|
+
Buffer.from('.'),
|
|
101
|
+
Buffer.from(this._payload)
|
|
102
|
+
])
|
|
103
|
+
|
|
104
|
+
recipient.signature = base64url.encodeBuffer(sign(alg, key, data))
|
|
116
105
|
}
|
|
117
106
|
|
|
118
107
|
/*
|
|
@@ -130,9 +119,9 @@ class Sign {
|
|
|
130
119
|
|
|
131
120
|
serializer.validate(this, this._recipients)
|
|
132
121
|
|
|
133
|
-
|
|
134
|
-
this[PROCESS_RECIPIENT](recipient)
|
|
135
|
-
}
|
|
122
|
+
this._recipients.forEach((recipient, i) => {
|
|
123
|
+
this[PROCESS_RECIPIENT](recipient, i === 0)
|
|
124
|
+
})
|
|
136
125
|
|
|
137
126
|
return serializer(this._payload, this._recipients)
|
|
138
127
|
}
|
package/lib/jwt/verify.js
CHANGED
|
@@ -20,8 +20,8 @@ const isTimestamp = (value, label, required = false) => {
|
|
|
20
20
|
throw new JWTClaimInvalid(`"${label}" claim is missing`, label, 'missing')
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
if (value !== undefined && (typeof value !== 'number'
|
|
24
|
-
throw new JWTClaimInvalid(`"${label}" claim must be a
|
|
23
|
+
if (value !== undefined && (typeof value !== 'number')) {
|
|
24
|
+
throw new JWTClaimInvalid(`"${label}" claim must be a JSON numeric value`, label, 'invalid')
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
|
|
@@ -63,12 +63,15 @@ const validateOptions = ({
|
|
|
63
63
|
|
|
64
64
|
isOptionString(maxTokenAge, 'options.maxTokenAge')
|
|
65
65
|
isOptionString(subject, 'options.subject')
|
|
66
|
-
isOptionString(issuer, 'options.issuer')
|
|
67
66
|
isOptionString(maxAuthAge, 'options.maxAuthAge')
|
|
68
67
|
isOptionString(jti, 'options.jti')
|
|
69
68
|
isOptionString(clockTolerance, 'options.clockTolerance')
|
|
70
69
|
isOptionString(typ, 'options.typ')
|
|
71
70
|
|
|
71
|
+
if (issuer !== undefined && (isNotString(issuer) && isNotArrayOfStrings(issuer))) {
|
|
72
|
+
throw new TypeError('options.issuer must be a string or an array of strings')
|
|
73
|
+
}
|
|
74
|
+
|
|
72
75
|
if (audience !== undefined && (isNotString(audience) && isNotArrayOfStrings(audience))) {
|
|
73
76
|
throw new TypeError('options.audience must be a string or an array of strings')
|
|
74
77
|
}
|
|
@@ -161,7 +164,7 @@ const validateTypes = ({ header, payload }, profile, options) => {
|
|
|
161
164
|
isPayloadString(payload.jti, '"jti" claim', 'jti', profile === LOGOUTTOKEN || profile === ATJWT || !!options.jti)
|
|
162
165
|
isPayloadString(payload.acr, '"acr" claim', 'acr')
|
|
163
166
|
isPayloadString(payload.nonce, '"nonce" claim', 'nonce', !!options.nonce)
|
|
164
|
-
|
|
167
|
+
isStringOrArrayOfStrings(payload.iss, 'iss', !!options.issuer)
|
|
165
168
|
isPayloadString(payload.sub, '"sub" claim', 'sub', profile === IDTOKEN || profile === ATJWT || !!options.subject)
|
|
166
169
|
isStringOrArrayOfStrings(payload.aud, 'aud', !!options.audience)
|
|
167
170
|
isPayloadString(payload.azp, '"azp" claim', 'azp', profile === IDTOKEN && Array.isArray(payload.aud) && payload.aud.length > 1)
|
|
@@ -235,7 +238,7 @@ module.exports = (token, key, options = {}) => {
|
|
|
235
238
|
const unix = epoch(now)
|
|
236
239
|
validateTypes(decoded, profile, options)
|
|
237
240
|
|
|
238
|
-
if (issuer && decoded.payload.iss !== issuer) {
|
|
241
|
+
if (issuer && (typeof decoded.payload.iss !== 'string' || !(typeof issuer === 'string' ? [issuer] : issuer).includes(decoded.payload.iss))) {
|
|
239
242
|
throw new JWTClaimInvalid('unexpected "iss" claim value', 'iss', 'check_failed')
|
|
240
243
|
}
|
|
241
244
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jose",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.28.0",
|
|
4
4
|
"description": "JSON Web Almost Everything - JWA, JWS, JWE, JWK, JWT, JWKS for Node.js with minimal dependencies",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"access token",
|
|
@@ -81,11 +81,11 @@
|
|
|
81
81
|
"@panva/asn1.js": "^1.0.0"
|
|
82
82
|
},
|
|
83
83
|
"devDependencies": {
|
|
84
|
-
"@commitlint/cli": "^
|
|
85
|
-
"@commitlint/config-conventional": "^
|
|
84
|
+
"@commitlint/cli": "^9.1.1",
|
|
85
|
+
"@commitlint/config-conventional": "^9.1.1",
|
|
86
86
|
"ava": "^2.4.0",
|
|
87
87
|
"babel-eslint": "^10.0.3",
|
|
88
|
-
"c8": "^7.
|
|
88
|
+
"c8": "^7.2.1",
|
|
89
89
|
"husky": "^4.0.0",
|
|
90
90
|
"standard": "^14.3.1"
|
|
91
91
|
},
|
package/types/index.d.ts
CHANGED
|
@@ -444,7 +444,7 @@ export namespace JWT {
|
|
|
444
444
|
ignoreIat?: boolean;
|
|
445
445
|
maxTokenAge?: string;
|
|
446
446
|
subject?: string;
|
|
447
|
-
issuer?: string;
|
|
447
|
+
issuer?: string | string[];
|
|
448
448
|
maxAuthAge?: string;
|
|
449
449
|
jti?: string;
|
|
450
450
|
clockTolerance?: string;
|
|
@@ -479,7 +479,7 @@ export namespace JWT {
|
|
|
479
479
|
function sign(payload: object, key: ProduceKeyInputWithNone, options?: SignOptions): string;
|
|
480
480
|
|
|
481
481
|
interface VerifyProfileOptions<profile> {
|
|
482
|
-
issuer: string;
|
|
482
|
+
issuer: string | string[];
|
|
483
483
|
audience: string | string[];
|
|
484
484
|
profile?: profile;
|
|
485
485
|
}
|