@sphereon/ssi-sdk-ext.jwt-service 0.26.1-next.3 → 0.26.1-next.30
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/dist/agent/JwtService.d.ts.map +1 -1
- package/dist/agent/JwtService.js +8 -5
- package/dist/agent/JwtService.js.map +1 -1
- package/dist/functions/JWE.d.ts +6 -6
- package/dist/functions/JWE.d.ts.map +1 -1
- package/dist/functions/JWE.js +13 -13
- package/dist/functions/JWE.js.map +1 -1
- package/dist/functions/index.d.ts.map +1 -1
- package/dist/functions/index.js +2 -2
- package/dist/functions/index.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/tsdoc-metadata.json +1 -1
- package/dist/types/IJwtService.d.ts +2 -2
- package/dist/types/IJwtService.d.ts.map +1 -1
- package/dist/types/IJwtService.js +20 -2
- package/dist/types/IJwtService.js.map +1 -1
- package/package.json +10 -10
- package/plugin.schema.json +12 -0
- package/src/agent/JwtService.ts +99 -90
- package/src/functions/JWE.ts +281 -282
- package/src/functions/index.ts +8 -10
- package/src/index.ts +1 -1
- package/src/types/IJwtService.ts +166 -170
package/src/functions/JWE.ts
CHANGED
|
@@ -1,59 +1,60 @@
|
|
|
1
|
-
import {defaultRandomSource, randomBytes, RandomSource} from '@stablelib/random'
|
|
2
|
-
import {base64ToBytes, bytesToBase64url, decodeBase64url} from
|
|
3
|
-
import * as jose from
|
|
4
|
-
import {JWEKeyManagementHeaderParameters, JWTDecryptOptions} from
|
|
5
|
-
import type {KeyLike} from
|
|
1
|
+
import { defaultRandomSource, randomBytes, RandomSource } from '@stablelib/random'
|
|
2
|
+
import { base64ToBytes, bytesToBase64url, decodeBase64url } from '@veramo/utils'
|
|
3
|
+
import * as jose from 'jose'
|
|
4
|
+
import { JWEKeyManagementHeaderParameters, JWTDecryptOptions } from 'jose'
|
|
5
|
+
import type { KeyLike } from 'jose/dist/types/types'
|
|
6
6
|
import * as u8a from 'uint8arrays'
|
|
7
7
|
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
} from
|
|
19
|
-
|
|
8
|
+
JweAlg,
|
|
9
|
+
JweAlgs,
|
|
10
|
+
JweEnc,
|
|
11
|
+
JweEncs,
|
|
12
|
+
JweHeader,
|
|
13
|
+
JweJsonGeneral,
|
|
14
|
+
JweProtectedHeader,
|
|
15
|
+
JweRecipient,
|
|
16
|
+
JweRecipientUnprotectedHeader,
|
|
17
|
+
JwsPayload,
|
|
18
|
+
} from '../types/IJwtService'
|
|
20
19
|
|
|
21
20
|
export interface EncryptionResult {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
21
|
+
ciphertext: Uint8Array
|
|
22
|
+
tag: Uint8Array
|
|
23
|
+
iv: Uint8Array
|
|
24
|
+
protectedHeader?: string
|
|
25
|
+
recipients?: JweRecipient[]
|
|
26
|
+
cek?: Uint8Array
|
|
28
27
|
}
|
|
29
28
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
export const generateContentEncryptionKey = async ({
|
|
30
|
+
alg,
|
|
31
|
+
randomSource = defaultRandomSource,
|
|
32
|
+
}: {
|
|
33
|
+
alg: JweEnc
|
|
34
|
+
randomSource?: RandomSource
|
|
34
35
|
}): Promise<Uint8Array> => {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
36
|
+
let length: number
|
|
37
|
+
switch (alg) {
|
|
38
|
+
case 'A128GCM':
|
|
39
|
+
length = 16
|
|
40
|
+
break
|
|
41
|
+
case 'A192GCM':
|
|
42
|
+
length = 24
|
|
43
|
+
break
|
|
44
|
+
case 'A128CBC-HS256':
|
|
45
|
+
case 'A256GCM':
|
|
46
|
+
length = 32
|
|
47
|
+
break
|
|
48
|
+
case 'A192CBC-HS384':
|
|
49
|
+
length = 48
|
|
50
|
+
break
|
|
51
|
+
case 'A256CBC-HS512':
|
|
52
|
+
length = 64
|
|
53
|
+
break
|
|
54
|
+
default:
|
|
55
|
+
length = 32
|
|
56
|
+
}
|
|
57
|
+
return randomBytes(length, randomSource)
|
|
57
58
|
}
|
|
58
59
|
|
|
59
60
|
/*
|
|
@@ -68,241 +69,236 @@ export const generateContentEncryptionKeyfdsdf = async ({type = 'Secp256r1', ...
|
|
|
68
69
|
}
|
|
69
70
|
*/
|
|
70
71
|
export interface JwtEncrypter {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
72
|
+
alg: string
|
|
73
|
+
enc: string
|
|
74
|
+
encrypt: (payload: JwsPayload, protectedHeader: JweProtectedHeader, aad?: Uint8Array) => Promise<EncryptionResult>
|
|
75
|
+
encryptCek?: (cek: Uint8Array) => Promise<JweRecipient>
|
|
75
76
|
}
|
|
76
77
|
|
|
77
|
-
|
|
78
78
|
export interface JweEncrypter {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
79
|
+
alg: string
|
|
80
|
+
enc: string
|
|
81
|
+
encrypt: (payload: Uint8Array, protectedHeader: JweProtectedHeader, aad?: Uint8Array) => Promise<EncryptionResult>
|
|
82
|
+
encryptCek?: (cek: Uint8Array) => Promise<JweRecipient>
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
export interface JweDecrypter {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
86
|
+
alg: string
|
|
87
|
+
enc: string
|
|
88
|
+
decrypt: (sealed: Uint8Array, iv: Uint8Array, aad?: Uint8Array, recipient?: JweRecipient) => Promise<Uint8Array | null>
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
function jweAssertValid(jwe: JweJsonGeneral) {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
92
|
+
if (!(jwe.protected && jwe.iv && jwe.ciphertext && jwe.tag)) {
|
|
93
|
+
throw Error('JWE is missing properties: protected, iv, ciphertext and/or tag')
|
|
94
|
+
}
|
|
95
|
+
if (jwe.recipients) {
|
|
96
|
+
jwe.recipients.map((recipient: JweRecipient) => {
|
|
97
|
+
if (!(recipient.header && recipient.encrypted_key)) {
|
|
98
|
+
throw Error('Malformed JWE recipients; no header and encrypted key present')
|
|
99
|
+
}
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
function jweEncode({
|
|
105
|
-
|
|
106
|
-
|
|
104
|
+
function jweEncode({
|
|
105
|
+
ciphertext,
|
|
106
|
+
tag,
|
|
107
|
+
iv,
|
|
108
|
+
protectedHeader,
|
|
109
|
+
recipients,
|
|
110
|
+
aad,
|
|
111
|
+
unprotected,
|
|
112
|
+
}: EncryptionResult & {
|
|
113
|
+
aad?: Uint8Array
|
|
114
|
+
unprotected?: JweHeader
|
|
107
115
|
}): JweJsonGeneral {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
116
|
+
if (!recipients || recipients.length === 0) {
|
|
117
|
+
throw Error(`No recipient found`)
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
...(unprotected && { unprotected }),
|
|
121
|
+
protected: <string>protectedHeader,
|
|
122
|
+
iv: bytesToBase64url(iv),
|
|
123
|
+
ciphertext: bytesToBase64url(ciphertext),
|
|
124
|
+
...(tag && { tag: bytesToBase64url(tag) }),
|
|
125
|
+
...(aad && { aad: bytesToBase64url(aad) }),
|
|
126
|
+
recipients,
|
|
127
|
+
} satisfies JweJsonGeneral
|
|
120
128
|
}
|
|
121
129
|
|
|
122
130
|
export class CompactJwtEncrypter implements JweEncrypter {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
}
|
|
143
|
-
if (args?.enc) {
|
|
144
|
-
this._enc = args.enc
|
|
145
|
-
}
|
|
146
|
-
this._keyManagementParams = args.keyManagementParams
|
|
147
|
-
this.recipientKey = args.key
|
|
148
|
-
this.expirationTime = args.expirationTime
|
|
149
|
-
this.issuer = args.issuer
|
|
150
|
-
this.audience = args.audience
|
|
131
|
+
private _alg: JweAlg | undefined
|
|
132
|
+
private _enc: JweEnc | undefined
|
|
133
|
+
private _keyManagementParams: JWEKeyManagementHeaderParameters | undefined
|
|
134
|
+
private recipientKey: Uint8Array | jose.KeyLike //,EphemeralPublicKey | BaseJWK;
|
|
135
|
+
private expirationTime
|
|
136
|
+
private issuer: string | undefined
|
|
137
|
+
private audience: string | string[] | undefined
|
|
138
|
+
|
|
139
|
+
constructor(args: {
|
|
140
|
+
key: Uint8Array | jose.KeyLike /*EphemeralPublicKey | BaseJWK*/
|
|
141
|
+
alg?: JweAlg
|
|
142
|
+
enc?: JweEnc
|
|
143
|
+
keyManagementParams?: JWEKeyManagementHeaderParameters
|
|
144
|
+
expirationTime?: number | string | Date
|
|
145
|
+
issuer?: string
|
|
146
|
+
audience?: string | string[]
|
|
147
|
+
}) {
|
|
148
|
+
if (args?.alg) {
|
|
149
|
+
this._alg = args.alg
|
|
151
150
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
if (!this._enc) {
|
|
155
|
-
throw Error(`enc not set`)
|
|
156
|
-
}
|
|
157
|
-
return this._enc;
|
|
151
|
+
if (args?.enc) {
|
|
152
|
+
this._enc = args.enc
|
|
158
153
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
154
|
+
this._keyManagementParams = args.keyManagementParams
|
|
155
|
+
this.recipientKey = args.key
|
|
156
|
+
this.expirationTime = args.expirationTime
|
|
157
|
+
this.issuer = args.issuer
|
|
158
|
+
this.audience = args.audience
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
get enc(): string {
|
|
162
|
+
if (!this._enc) {
|
|
163
|
+
throw Error(`enc not set`)
|
|
166
164
|
}
|
|
165
|
+
return this._enc
|
|
166
|
+
}
|
|
167
167
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
return this._alg;
|
|
168
|
+
set enc(value: JweEnc | string) {
|
|
169
|
+
// @ts-ignore
|
|
170
|
+
if (!JweEncs.includes(value)) {
|
|
171
|
+
throw Error(`invalid JWE enc value ${value}`)
|
|
173
172
|
}
|
|
173
|
+
this._enc = value as JweEnc
|
|
174
|
+
}
|
|
174
175
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
throw Error(`invalid JWE alg value ${value}`)
|
|
179
|
-
}
|
|
180
|
-
this._alg = value as JweAlg;
|
|
176
|
+
get alg(): string {
|
|
177
|
+
if (!this._alg) {
|
|
178
|
+
throw Error(`alg not set`)
|
|
181
179
|
}
|
|
180
|
+
return this._alg
|
|
181
|
+
}
|
|
182
182
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
): Promise<string> {
|
|
188
|
-
const protectedHeader = {
|
|
189
|
-
...jweProtectedHeader,
|
|
190
|
-
alg: jweProtectedHeader.alg ?? this._alg,
|
|
191
|
-
enc: jweProtectedHeader.enc ?? this._enc
|
|
192
|
-
}
|
|
193
|
-
if (!protectedHeader.alg || !protectedHeader.enc) {
|
|
194
|
-
return Promise.reject(Error(`no 'alg' or 'enc' value set for the protected JWE header!`))
|
|
195
|
-
}
|
|
196
|
-
this.enc = protectedHeader.enc
|
|
197
|
-
this.alg = protectedHeader.alg
|
|
198
|
-
if (payload.exp) {
|
|
199
|
-
this.expirationTime = payload.exp
|
|
200
|
-
}
|
|
201
|
-
if (payload.iss) {
|
|
202
|
-
this.issuer = payload.iss
|
|
203
|
-
}
|
|
204
|
-
if (payload.aud) {
|
|
205
|
-
this.audience = payload.aud
|
|
206
|
-
}
|
|
207
|
-
const encrypt = new jose.EncryptJWT(payload).setProtectedHeader({
|
|
208
|
-
...protectedHeader,
|
|
209
|
-
alg: this.alg,
|
|
210
|
-
enc: this.enc
|
|
211
|
-
})
|
|
212
|
-
if (this._alg!.startsWith('ECDH')) {
|
|
213
|
-
if (!this._keyManagementParams) {
|
|
214
|
-
return Promise.reject(Error(`ECDH requires key management params`))
|
|
215
|
-
}
|
|
216
|
-
encrypt.setKeyManagementParameters(this._keyManagementParams!)
|
|
217
|
-
}
|
|
218
|
-
if(this.expirationTime !== undefined) {
|
|
219
|
-
encrypt.setExpirationTime(this.expirationTime)
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
if (this.issuer) {
|
|
223
|
-
encrypt.setIssuer(this.issuer)
|
|
224
|
-
}
|
|
225
|
-
if (this.audience) {
|
|
226
|
-
encrypt.setAudience(this.audience)
|
|
227
|
-
}
|
|
228
|
-
return await encrypt.encrypt(this.recipientKey)
|
|
183
|
+
set alg(value: JweAlg | string) {
|
|
184
|
+
// @ts-ignore
|
|
185
|
+
if (!JweAlgs.includes(value)) {
|
|
186
|
+
throw Error(`invalid JWE alg value ${value}`)
|
|
229
187
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
188
|
+
this._alg = value as JweAlg
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async encryptCompactJWT(payload: JwsPayload, jweProtectedHeader: JweProtectedHeader, aad?: Uint8Array | undefined): Promise<string> {
|
|
192
|
+
const protectedHeader = {
|
|
193
|
+
...jweProtectedHeader,
|
|
194
|
+
alg: jweProtectedHeader.alg ?? this._alg,
|
|
195
|
+
enc: jweProtectedHeader.enc ?? this._enc,
|
|
196
|
+
}
|
|
197
|
+
if (!protectedHeader.alg || !protectedHeader.enc) {
|
|
198
|
+
return Promise.reject(Error(`no 'alg' or 'enc' value set for the protected JWE header!`))
|
|
199
|
+
}
|
|
200
|
+
this.enc = protectedHeader.enc
|
|
201
|
+
this.alg = protectedHeader.alg
|
|
202
|
+
if (payload.exp) {
|
|
203
|
+
this.expirationTime = payload.exp
|
|
204
|
+
}
|
|
205
|
+
if (payload.iss) {
|
|
206
|
+
this.issuer = payload.iss
|
|
207
|
+
}
|
|
208
|
+
if (payload.aud) {
|
|
209
|
+
this.audience = payload.aud
|
|
210
|
+
}
|
|
211
|
+
const encrypt = new jose.EncryptJWT(payload).setProtectedHeader({
|
|
212
|
+
...protectedHeader,
|
|
213
|
+
alg: this.alg,
|
|
214
|
+
enc: this.enc,
|
|
215
|
+
})
|
|
216
|
+
if (this._alg!.startsWith('ECDH')) {
|
|
217
|
+
if (!this._keyManagementParams) {
|
|
218
|
+
return Promise.reject(Error(`ECDH requires key management params`))
|
|
219
|
+
}
|
|
220
|
+
encrypt.setKeyManagementParameters(this._keyManagementParams!)
|
|
221
|
+
}
|
|
222
|
+
if (this.expirationTime !== undefined) {
|
|
223
|
+
encrypt.setExpirationTime(this.expirationTime)
|
|
233
224
|
}
|
|
234
225
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
console.log(`FIXME: TO EncryptionResult`)
|
|
244
|
-
|
|
245
|
-
return {
|
|
246
|
-
protectedHeader,
|
|
247
|
-
tag: base64ToBytes(tagB64),
|
|
248
|
-
ciphertext: base64ToBytes(payloadB64),
|
|
249
|
-
iv: base64ToBytes(ivB64),
|
|
250
|
-
recipients: [
|
|
226
|
+
if (this.issuer) {
|
|
227
|
+
encrypt.setIssuer(this.issuer)
|
|
228
|
+
}
|
|
229
|
+
if (this.audience) {
|
|
230
|
+
encrypt.setAudience(this.audience)
|
|
231
|
+
}
|
|
232
|
+
return await encrypt.encrypt(this.recipientKey)
|
|
233
|
+
}
|
|
251
234
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
...(encryptedKey && { encrypted_key: encryptedKey})
|
|
235
|
+
public static async decryptCompactJWT(jwt: string, key: KeyLike | Uint8Array, options?: JWTDecryptOptions) {
|
|
236
|
+
return await jose.jwtDecrypt(jwt, key, options)
|
|
237
|
+
}
|
|
256
238
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
239
|
+
async encrypt(payload: Uint8Array, jweProtectedHeader: JweProtectedHeader, aad?: Uint8Array | undefined): Promise<EncryptionResult> {
|
|
240
|
+
const jwt = await this.encryptCompactJWT(JSON.parse(u8a.toString(payload)), jweProtectedHeader, aad)
|
|
241
|
+
const [protectedHeader, encryptedKey, ivB64, payloadB64, tagB64] = jwt.split('.')
|
|
242
|
+
//[jwe.protected, jwe.encrypted_key, jwe.iv, jwe.ciphertext, jwe.tag].join('.');
|
|
243
|
+
console.log(`FIXME: TO EncryptionResult`)
|
|
260
244
|
|
|
245
|
+
return {
|
|
246
|
+
protectedHeader,
|
|
247
|
+
tag: base64ToBytes(tagB64),
|
|
248
|
+
ciphertext: base64ToBytes(payloadB64),
|
|
249
|
+
iv: base64ToBytes(ivB64),
|
|
250
|
+
recipients: [
|
|
251
|
+
{
|
|
252
|
+
//fixme
|
|
253
|
+
// header: protectedHeader,
|
|
254
|
+
...(encryptedKey && { encrypted_key: encryptedKey }),
|
|
255
|
+
},
|
|
256
|
+
],
|
|
261
257
|
}
|
|
258
|
+
}
|
|
262
259
|
|
|
263
|
-
|
|
264
|
-
|
|
260
|
+
// encryptCek?: ((cek: Uint8Array) => Promise<JweRecipient>) | undefined;
|
|
265
261
|
}
|
|
266
262
|
|
|
267
263
|
export async function createJwe(
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
264
|
+
cleartext: Uint8Array,
|
|
265
|
+
encrypters: JweEncrypter[],
|
|
266
|
+
protectedHeader: JweProtectedHeader,
|
|
267
|
+
aad?: Uint8Array
|
|
272
268
|
): Promise<JweJsonGeneral> {
|
|
273
|
-
|
|
274
|
-
|
|
269
|
+
if (encrypters.length === 0) {
|
|
270
|
+
throw Error('JWE needs at least 1 encryptor')
|
|
271
|
+
}
|
|
272
|
+
if (encrypters.find((enc) => enc.alg === 'dir' || enc.alg === 'ECDH-ES')) {
|
|
273
|
+
if (encrypters.length !== 1) {
|
|
274
|
+
throw Error(`JWE can only do "dir" or "ECDH-ES" encryption with one key. ${encrypters.length} supplied`)
|
|
275
275
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
} else {
|
|
295
|
-
const recipient = await encrypter.encryptCek?.(cek)
|
|
296
|
-
if (recipient) {
|
|
297
|
-
jwe?.recipients?.push(recipient)
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
if (!jwe) {
|
|
302
|
-
throw Error(`No JWE constructed`)
|
|
276
|
+
const encryptionResult = await encrypters[0].encrypt(cleartext, protectedHeader, aad)
|
|
277
|
+
return jweEncode({ ...encryptionResult, aad })
|
|
278
|
+
} else {
|
|
279
|
+
const tmpEnc = encrypters[0].enc
|
|
280
|
+
if (!encrypters.reduce((acc, encrypter) => acc && encrypter.enc === tmpEnc, true)) {
|
|
281
|
+
throw new Error('invalid_argument: Incompatible encrypters passed')
|
|
282
|
+
}
|
|
283
|
+
let cek: Uint8Array | undefined = undefined
|
|
284
|
+
let jwe: JweJsonGeneral | undefined = undefined
|
|
285
|
+
for (const encrypter of encrypters) {
|
|
286
|
+
if (!cek) {
|
|
287
|
+
const encryptionResult = await encrypter.encrypt(cleartext, protectedHeader, aad)
|
|
288
|
+
cek = encryptionResult.cek
|
|
289
|
+
jwe = jweEncode({ ...encryptionResult, aad })
|
|
290
|
+
} else {
|
|
291
|
+
const recipient = await encrypter.encryptCek?.(cek)
|
|
292
|
+
if (recipient) {
|
|
293
|
+
jwe?.recipients?.push(recipient)
|
|
303
294
|
}
|
|
304
|
-
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
if (!jwe) {
|
|
298
|
+
throw Error(`No JWE constructed`)
|
|
305
299
|
}
|
|
300
|
+
return jwe
|
|
301
|
+
}
|
|
306
302
|
}
|
|
307
303
|
|
|
308
304
|
/**
|
|
@@ -312,49 +308,52 @@ export async function createJwe(
|
|
|
312
308
|
* @param unprotectedHeader
|
|
313
309
|
* @param recipientUnprotectedHeader
|
|
314
310
|
*/
|
|
315
|
-
export function jweMergeHeaders({
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
311
|
+
export function jweMergeHeaders({
|
|
312
|
+
protectedHeader,
|
|
313
|
+
unprotectedHeader,
|
|
314
|
+
recipientUnprotectedHeader,
|
|
315
|
+
}: {
|
|
316
|
+
protectedHeader?: JweProtectedHeader
|
|
317
|
+
unprotectedHeader?: JweHeader
|
|
318
|
+
recipientUnprotectedHeader?: JweRecipientUnprotectedHeader
|
|
319
319
|
}): JweHeader {
|
|
320
|
-
|
|
321
|
-
|
|
320
|
+
// TODO: Check that all headers/params are disjoint!
|
|
321
|
+
const header = { ...protectedHeader, ...unprotectedHeader, ...recipientUnprotectedHeader }
|
|
322
322
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
323
|
+
if (!header.alg || !header.enc) {
|
|
324
|
+
throw Error(`Either 'alg' or 'enc' are missing from the headers`)
|
|
325
|
+
}
|
|
326
|
+
return header as JweHeader
|
|
327
327
|
}
|
|
328
328
|
|
|
329
329
|
export async function decryptJwe(jwe: JweJsonGeneral, decrypter: JweDecrypter): Promise<Uint8Array> {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
330
|
+
jweAssertValid(jwe)
|
|
331
|
+
const protectedHeader: JweProtectedHeader = JSON.parse(decodeBase64url(jwe.protected))
|
|
332
|
+
if (protectedHeader?.enc !== decrypter.enc) {
|
|
333
|
+
return Promise.reject(Error(`Decrypter enc '${decrypter.enc}' does not support header enc '${protectedHeader.enc}'`))
|
|
334
|
+
} else if (!jwe.tag) {
|
|
335
|
+
return Promise.reject(Error(`Decrypter enc '${decrypter.enc}' does not support header enc '${protectedHeader.enc}'`))
|
|
336
|
+
}
|
|
337
|
+
const sealed = toWebCryptoCiphertext(jwe.ciphertext, jwe.tag)
|
|
338
|
+
const aad = u8a.fromString(jwe.aad ? `${jwe.protected}.${jwe.aad}` : jwe.protected)
|
|
339
|
+
let cleartext = null
|
|
340
|
+
if (protectedHeader.alg === 'dir' && decrypter.alg === 'dir') {
|
|
341
|
+
cleartext = await decrypter.decrypt(sealed, base64ToBytes(jwe.iv), aad)
|
|
342
|
+
} else if (!jwe.recipients || jwe.recipients.length === 0) {
|
|
343
|
+
throw Error('missing recipients for JWE')
|
|
344
|
+
} else {
|
|
345
|
+
for (let i = 0; !cleartext && i < jwe.recipients.length; i++) {
|
|
346
|
+
const recipient: JweRecipient = jwe.recipients[i]
|
|
347
|
+
recipient.header = { ...recipient.header, ...protectedHeader } as JweRecipientUnprotectedHeader
|
|
348
|
+
if (recipient.header.alg === decrypter.alg) {
|
|
349
|
+
cleartext = await decrypter.decrypt(sealed, base64ToBytes(jwe.iv), aad, recipient)
|
|
350
|
+
}
|
|
336
351
|
}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
if (protectedHeader.alg === 'dir' && decrypter.alg === 'dir') {
|
|
341
|
-
cleartext = await decrypter.decrypt(sealed, base64ToBytes(jwe.iv), aad)
|
|
342
|
-
} else if (!jwe.recipients || jwe.recipients.length === 0) {
|
|
343
|
-
throw Error('missing recipients for JWE')
|
|
344
|
-
} else {
|
|
345
|
-
for (let i = 0; !cleartext && i < jwe.recipients.length; i++) {
|
|
346
|
-
const recipient: JweRecipient = jwe.recipients[i]
|
|
347
|
-
recipient.header = {...recipient.header, ...protectedHeader} as JweRecipientUnprotectedHeader
|
|
348
|
-
if (recipient.header.alg === decrypter.alg) {
|
|
349
|
-
cleartext = await decrypter.decrypt(sealed, base64ToBytes(jwe.iv), aad, recipient)
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
if (cleartext === null) throw new Error('failure: Failed to decrypt')
|
|
354
|
-
return cleartext
|
|
352
|
+
}
|
|
353
|
+
if (cleartext === null) throw new Error('failure: Failed to decrypt')
|
|
354
|
+
return cleartext
|
|
355
355
|
}
|
|
356
356
|
|
|
357
|
-
|
|
358
357
|
export function toWebCryptoCiphertext(ciphertext: string, tag: string): Uint8Array {
|
|
359
|
-
|
|
358
|
+
return u8a.concat([base64ToBytes(ciphertext), base64ToBytes(tag)])
|
|
360
359
|
}
|