@sphereon/ssi-sdk-ext.jwt-service 0.24.1-unstable.130 → 0.24.1-unstable.133
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 +2 -0
- package/dist/agent/JwtService.d.ts.map +1 -1
- package/dist/agent/JwtService.js +71 -0
- package/dist/agent/JwtService.js.map +1 -1
- package/dist/functions/JWE.d.ts +75 -0
- package/dist/functions/JWE.d.ts.map +1 -0
- package/dist/functions/JWE.js +280 -0
- package/dist/functions/JWE.js.map +1 -0
- package/dist/functions/index.d.ts +3 -3
- package/dist/functions/index.js +4 -4
- package/dist/functions/index.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/types/IJwtService.d.ts +116 -13
- package/dist/types/IJwtService.d.ts.map +1 -1
- package/dist/types/IJwtService.js +36 -3
- package/dist/types/IJwtService.js.map +1 -1
- package/package.json +15 -13
- package/plugin.schema.json +2171 -896
- package/src/agent/JwtService.ts +103 -47
- package/src/functions/JWE.ts +358 -0
- package/src/functions/index.ts +15 -15
- package/src/index.ts +4 -0
- package/src/types/IJwtService.ts +239 -76
package/src/agent/JwtService.ts
CHANGED
|
@@ -1,55 +1,111 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {IAgentPlugin} from '@veramo/core'
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
3
|
+
createJwsCompact,
|
|
4
|
+
CreateJwsCompactArgs,
|
|
5
|
+
CreateJwsFlattenedArgs,
|
|
6
|
+
CreateJwsJsonArgs,
|
|
7
|
+
createJwsJsonFlattened,
|
|
8
|
+
createJwsJsonGeneral,
|
|
9
|
+
DecryptJweCompactJwtArgs,
|
|
10
|
+
EncryptJweCompactJwtArgs,
|
|
11
|
+
IJwsValidationResult,
|
|
12
|
+
IJwtService,
|
|
13
|
+
IRequiredContext,
|
|
14
|
+
jweAlg,
|
|
15
|
+
jweEnc,
|
|
16
|
+
JwsJsonFlattened,
|
|
17
|
+
JwsJsonGeneral,
|
|
18
|
+
JwtCompactResult,
|
|
19
|
+
JwtLogger,
|
|
20
|
+
PreparedJwsObject,
|
|
21
|
+
prepareJwsObject,
|
|
22
|
+
schema,
|
|
23
|
+
verifyJws,
|
|
24
|
+
VerifyJwsArgs,
|
|
20
25
|
} from '..'
|
|
26
|
+
import {CompactJwtEncrypter} from "../functions/JWE";
|
|
27
|
+
|
|
28
|
+
import * as u8a from 'uint8arrays'
|
|
21
29
|
|
|
22
30
|
/**
|
|
23
31
|
* @public
|
|
24
32
|
*/
|
|
25
33
|
export class JwtService implements IAgentPlugin {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
34
|
+
readonly schema = schema.IJwtService
|
|
35
|
+
readonly methods: IJwtService = {
|
|
36
|
+
jwtPrepareJws: this.jwtPrepareJws.bind(this),
|
|
37
|
+
jwtCreateJwsJsonGeneralSignature: this.jwtCreateJwsJsonGeneralSignature.bind(this),
|
|
38
|
+
jwtCreateJwsJsonFlattenedSignature: this.jwtCreateJwsJsonFlattenedSignature.bind(this),
|
|
39
|
+
jwtCreateJwsCompactSignature: this.jwtCreateJwsCompactSignature.bind(this),
|
|
40
|
+
jwtVerifyJwsSignature: this.jwtVerifyJwsSignature.bind(this),
|
|
41
|
+
jwtEncryptJweCompactJwt: this.jwtEncryptJweCompactJwt.bind(this),
|
|
42
|
+
jwtDecryptJweCompactJwt: this.jwtDecryptJweCompactJwt.bind(this)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private async jwtPrepareJws(args: CreateJwsJsonArgs, context: IRequiredContext): Promise<PreparedJwsObject> {
|
|
46
|
+
return await prepareJwsObject(args, context)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private async jwtCreateJwsJsonGeneralSignature(args: CreateJwsJsonArgs, context: IRequiredContext): Promise<JwsJsonGeneral> {
|
|
50
|
+
return await createJwsJsonGeneral(args, context)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
private async jwtCreateJwsJsonFlattenedSignature(args: CreateJwsFlattenedArgs, context: IRequiredContext): Promise<JwsJsonFlattened> {
|
|
54
|
+
return await createJwsJsonFlattened(args, context)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private async jwtCreateJwsCompactSignature(args: CreateJwsCompactArgs, context: IRequiredContext): Promise<JwtCompactResult> {
|
|
58
|
+
// We wrap it in a json object for remote REST calls
|
|
59
|
+
return {jwt: await createJwsCompact(args, context)}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private async jwtVerifyJwsSignature(args: VerifyJwsArgs, context: IRequiredContext): Promise<IJwsValidationResult> {
|
|
63
|
+
return await verifyJws(args, context)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private async jwtEncryptJweCompactJwt(args: EncryptJweCompactJwtArgs, context: IRequiredContext): Promise<JwtCompactResult> {
|
|
67
|
+
const {payload, protectedHeader = {alg: args.alg, enc: args.enc}, recipientKey, issuer, expirationTime, audience} = args
|
|
68
|
+
|
|
69
|
+
console.log(JSON.stringify(args, null, 2))
|
|
70
|
+
|
|
71
|
+
const alg = jweAlg(args.alg) ?? jweAlg(protectedHeader.alg) ?? 'ECDH-ES'
|
|
72
|
+
const enc = jweEnc(args.enc) ?? jweEnc(protectedHeader.enc) ?? 'A256GCM'
|
|
73
|
+
const encJwks = recipientKey.jwks.length === 1 ? [recipientKey.jwks[0]] : recipientKey.jwks.filter(jwk => (jwk.kid && (jwk.kid === jwk.jwk.kid || jwk.kid === jwk.jwkThumbprint)) || jwk.jwk.use === 'enc')
|
|
74
|
+
if (encJwks.length === 0) {
|
|
75
|
+
return Promise.reject(Error(`No public JWK found that can be used to encrypt against`))
|
|
76
|
+
}
|
|
77
|
+
const jwkInfo = encJwks[0]
|
|
78
|
+
if (encJwks.length > 0) {
|
|
79
|
+
JwtLogger.warning(`More than one JWK with 'enc' usage found. Selected the first one as no 'kid' was provided`, encJwks)
|
|
80
|
+
}
|
|
81
|
+
if (jwkInfo.jwk.kty?.startsWith('EC') !== true || !alg.startsWith('ECDH')) {
|
|
82
|
+
return Promise.reject(Error(`Currently only ECDH-ES is supported for encryption. JWK alg ${jwkInfo.jwk.kty}, header alg ${alg}`)) // TODO: Probably we support way more already
|
|
83
|
+
}
|
|
84
|
+
const apuVal = protectedHeader.apu ?? args.apu
|
|
85
|
+
const apu = apuVal ? u8a.fromString(apuVal, 'base64url') : undefined
|
|
86
|
+
const apvVal = protectedHeader.apv ?? args.apv
|
|
87
|
+
const apv = apvVal ? u8a.fromString(apvVal, 'base64url') : undefined
|
|
88
|
+
|
|
89
|
+
const pubKey = await crypto.subtle.importKey('jwk', jwkInfo.jwk, {
|
|
90
|
+
name: 'ECDH',
|
|
91
|
+
namedCurve: 'P-256',
|
|
92
|
+
}, true, [])
|
|
93
|
+
const encrypter = new CompactJwtEncrypter({
|
|
94
|
+
enc,
|
|
95
|
+
alg,
|
|
96
|
+
keyManagementParams: {apu, apv},
|
|
97
|
+
key: pubKey,
|
|
98
|
+
issuer,
|
|
99
|
+
expirationTime,
|
|
100
|
+
audience
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
const jwe = await encrypter.encryptCompactJWT(payload, {})
|
|
104
|
+
return {jwt: jwe}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private async jwtDecryptJweCompactJwt(args: DecryptJweCompactJwtArgs, context: IRequiredContext): Promise<JwtCompactResult> {
|
|
108
|
+
|
|
109
|
+
return {jwt: "FIXME"}
|
|
110
|
+
}
|
|
55
111
|
}
|
|
@@ -0,0 +1,358 @@
|
|
|
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
|
+
import * as u8a from 'uint8arrays'
|
|
7
|
+
import {
|
|
8
|
+
JweAlg,
|
|
9
|
+
JweAlgs,
|
|
10
|
+
JweEnc,
|
|
11
|
+
JweEncs,
|
|
12
|
+
JweHeader,
|
|
13
|
+
JweJsonGeneral,
|
|
14
|
+
JweProtectedHeader,
|
|
15
|
+
JweRecipient,
|
|
16
|
+
JweRecipientUnprotectedHeader,
|
|
17
|
+
JwsPayload
|
|
18
|
+
} from "../types/IJwtService";
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
export interface EncryptionResult {
|
|
22
|
+
ciphertext: Uint8Array
|
|
23
|
+
tag: Uint8Array
|
|
24
|
+
iv: Uint8Array
|
|
25
|
+
protectedHeader?: string
|
|
26
|
+
recipients?: JweRecipient[]
|
|
27
|
+
cek?: Uint8Array
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
export const generateContentEncryptionKey = async ({alg, randomSource = defaultRandomSource}: {
|
|
32
|
+
alg: JweEnc,
|
|
33
|
+
randomSource?: RandomSource
|
|
34
|
+
}): Promise<Uint8Array> => {
|
|
35
|
+
let length: number
|
|
36
|
+
switch (alg) {
|
|
37
|
+
case "A128GCM":
|
|
38
|
+
length = 16
|
|
39
|
+
break
|
|
40
|
+
case "A192GCM":
|
|
41
|
+
length = 24
|
|
42
|
+
break
|
|
43
|
+
case "A128CBC-HS256":
|
|
44
|
+
case "A256GCM":
|
|
45
|
+
length = 32
|
|
46
|
+
break
|
|
47
|
+
case "A192CBC-HS384":
|
|
48
|
+
length = 48
|
|
49
|
+
break
|
|
50
|
+
case "A256CBC-HS512":
|
|
51
|
+
length = 64
|
|
52
|
+
break
|
|
53
|
+
default:
|
|
54
|
+
length = 32
|
|
55
|
+
}
|
|
56
|
+
return randomBytes(length, randomSource)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/*
|
|
60
|
+
export const generateContentEncryptionKeyfdsdf = async ({type = 'Secp256r1', ...rest}: {
|
|
61
|
+
type?: Extract<TKeyType, 'Secp256r1' | 'RSA'>,
|
|
62
|
+
kms?: string
|
|
63
|
+
}, context: IAgentContext<ISphereonKeyManager>): Promise<EphemeralPublicKey> => {
|
|
64
|
+
|
|
65
|
+
const kms = rest.kms ?? await context.agent.keyManagerGetDefaultKeyManagementSystem()
|
|
66
|
+
const key = await context.agent.keyManagerCreate({kms, type, opts: {ephemeral: true}})
|
|
67
|
+
const jwk = toJwkFromKey(key, {use: JwkKeyUse.Encryption, noKidThumbprint: true})
|
|
68
|
+
}
|
|
69
|
+
*/
|
|
70
|
+
export interface JwtEncrypter {
|
|
71
|
+
alg: string
|
|
72
|
+
enc: string
|
|
73
|
+
encrypt: (payload: JwsPayload, protectedHeader: JweProtectedHeader, aad?: Uint8Array) => Promise<EncryptionResult>
|
|
74
|
+
encryptCek?: (cek: Uint8Array) => Promise<JweRecipient>
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
export interface JweEncrypter {
|
|
79
|
+
alg: string
|
|
80
|
+
enc: string
|
|
81
|
+
encrypt: (payload: Uint8Array, protectedHeader: JweProtectedHeader, aad?: Uint8Array) => Promise<EncryptionResult>
|
|
82
|
+
encryptCek?: (cek: Uint8Array) => Promise<JweRecipient>
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface JweDecrypter {
|
|
86
|
+
alg: string
|
|
87
|
+
enc: string
|
|
88
|
+
decrypt: (sealed: Uint8Array, iv: Uint8Array, aad?: Uint8Array, recipient?: JweRecipient) => Promise<Uint8Array | null>
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function jweAssertValid(jwe: JweJsonGeneral) {
|
|
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
|
+
}
|
|
103
|
+
|
|
104
|
+
function jweEncode({ciphertext, tag, iv, protectedHeader, recipients, aad, unprotected}: EncryptionResult & {
|
|
105
|
+
aad?: Uint8Array,
|
|
106
|
+
unprotected?: JweHeader
|
|
107
|
+
}): JweJsonGeneral {
|
|
108
|
+
if (!recipients || recipients.length === 0) {
|
|
109
|
+
throw Error(`No recipient found`)
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
...(unprotected && {unprotected}),
|
|
113
|
+
protected: <string>protectedHeader,
|
|
114
|
+
iv: bytesToBase64url(iv),
|
|
115
|
+
ciphertext: bytesToBase64url(ciphertext),
|
|
116
|
+
...(tag && {tag: bytesToBase64url(tag)}),
|
|
117
|
+
...(aad && {aad: bytesToBase64url(aad)}),
|
|
118
|
+
recipients
|
|
119
|
+
} satisfies JweJsonGeneral
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export class CompactJwtEncrypter implements JweEncrypter {
|
|
123
|
+
private _alg: JweAlg | undefined;
|
|
124
|
+
private _enc: JweEnc | undefined;
|
|
125
|
+
private _keyManagementParams: JWEKeyManagementHeaderParameters | undefined
|
|
126
|
+
private recipientKey: Uint8Array | jose.KeyLike //,EphemeralPublicKey | BaseJWK;
|
|
127
|
+
private expirationTime
|
|
128
|
+
private issuer: string | undefined
|
|
129
|
+
private audience: string | string[] | undefined
|
|
130
|
+
|
|
131
|
+
constructor(args: {
|
|
132
|
+
key: Uint8Array | jose.KeyLike /*EphemeralPublicKey | BaseJWK*/,
|
|
133
|
+
alg?: JweAlg,
|
|
134
|
+
enc?: JweEnc,
|
|
135
|
+
keyManagementParams?: JWEKeyManagementHeaderParameters,
|
|
136
|
+
expirationTime?: number | string | Date
|
|
137
|
+
issuer?: string
|
|
138
|
+
audience?: string | string[]
|
|
139
|
+
}) {
|
|
140
|
+
if (args?.alg) {
|
|
141
|
+
this._alg = args.alg
|
|
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 ?? '10 minutes'
|
|
149
|
+
this.issuer = args.issuer
|
|
150
|
+
this.audience = args.audience
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
get enc(): string {
|
|
154
|
+
if (!this._enc) {
|
|
155
|
+
throw Error(`enc not set`)
|
|
156
|
+
}
|
|
157
|
+
return this._enc;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
set enc(value: JweEnc | string) {
|
|
161
|
+
// @ts-ignore
|
|
162
|
+
if (!JweEncs.includes(value)) {
|
|
163
|
+
throw Error(`invalid JWE enc value ${value}`)
|
|
164
|
+
}
|
|
165
|
+
this._enc = value as JweEnc;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
get alg(): string {
|
|
169
|
+
if (!this._alg) {
|
|
170
|
+
throw Error(`alg not set`)
|
|
171
|
+
}
|
|
172
|
+
return this._alg;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
set alg(value: JweAlg | string) {
|
|
176
|
+
// @ts-ignore
|
|
177
|
+
if (!JweAlgs.includes(value)) {
|
|
178
|
+
throw Error(`invalid JWE alg value ${value}`)
|
|
179
|
+
}
|
|
180
|
+
this._alg = value as JweAlg;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async encryptCompactJWT(
|
|
184
|
+
payload: JwsPayload,
|
|
185
|
+
jweProtectedHeader: JweProtectedHeader,
|
|
186
|
+
aad?: Uint8Array | undefined
|
|
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
|
+
// We always set the expiration time for encrypted JWTs (values are set above)
|
|
219
|
+
encrypt.setExpirationTime(this.expirationTime)
|
|
220
|
+
if (this.issuer) {
|
|
221
|
+
encrypt.setIssuer(this.issuer)
|
|
222
|
+
}
|
|
223
|
+
if (this.audience) {
|
|
224
|
+
encrypt.setAudience(this.audience)
|
|
225
|
+
}
|
|
226
|
+
return await encrypt.encrypt(this.recipientKey)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
public static async decryptCompactJWT(jwt: string, key: KeyLike | Uint8Array, options?: JWTDecryptOptions) {
|
|
230
|
+
return await jose.jwtDecrypt(jwt, key, options)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
async encrypt(
|
|
234
|
+
payload: Uint8Array,
|
|
235
|
+
jweProtectedHeader: JweProtectedHeader,
|
|
236
|
+
aad?: Uint8Array | undefined
|
|
237
|
+
): Promise<EncryptionResult> {
|
|
238
|
+
const jwt = await this.encryptCompactJWT(JSON.parse(u8a.toString(payload)), jweProtectedHeader, aad)
|
|
239
|
+
const [protectedHeader, encryptedKey, ivB64, payloadB64, tagB64,] = jwt.split('.')
|
|
240
|
+
//[jwe.protected, jwe.encrypted_key, jwe.iv, jwe.ciphertext, jwe.tag].join('.');
|
|
241
|
+
console.log(`FIXME: TO EncryptionResult`)
|
|
242
|
+
|
|
243
|
+
return {
|
|
244
|
+
protectedHeader,
|
|
245
|
+
tag: base64ToBytes(tagB64),
|
|
246
|
+
ciphertext: base64ToBytes(payloadB64),
|
|
247
|
+
iv: base64ToBytes(ivB64),
|
|
248
|
+
recipients: [
|
|
249
|
+
|
|
250
|
+
{
|
|
251
|
+
//fixme
|
|
252
|
+
// header: protectedHeader,
|
|
253
|
+
...(encryptedKey && { encrypted_key: encryptedKey})
|
|
254
|
+
|
|
255
|
+
}
|
|
256
|
+
]
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// encryptCek?: ((cek: Uint8Array) => Promise<JweRecipient>) | undefined;
|
|
262
|
+
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export async function createJwe(
|
|
266
|
+
cleartext: Uint8Array,
|
|
267
|
+
encrypters: JweEncrypter[],
|
|
268
|
+
protectedHeader: JweProtectedHeader,
|
|
269
|
+
aad?: Uint8Array
|
|
270
|
+
): Promise<JweJsonGeneral> {
|
|
271
|
+
if (encrypters.length === 0) {
|
|
272
|
+
throw Error('JWE needs at least 1 encryptor')
|
|
273
|
+
}
|
|
274
|
+
if (encrypters.find(enc => enc.alg === 'dir' || enc.alg === 'ECDH-ES')) {
|
|
275
|
+
if (encrypters.length !== 1) {
|
|
276
|
+
throw Error(`JWE can only do "dir" or "ECDH-ES" encryption with one key. ${encrypters.length} supplied`)
|
|
277
|
+
}
|
|
278
|
+
const encryptionResult = await encrypters[0].encrypt(cleartext, protectedHeader, aad)
|
|
279
|
+
return jweEncode({...encryptionResult, aad})
|
|
280
|
+
} else {
|
|
281
|
+
const tmpEnc = encrypters[0].enc
|
|
282
|
+
if (!encrypters.reduce((acc, encrypter) => acc && encrypter.enc === tmpEnc, true)) {
|
|
283
|
+
throw new Error('invalid_argument: Incompatible encrypters passed')
|
|
284
|
+
}
|
|
285
|
+
let cek: Uint8Array | undefined = undefined
|
|
286
|
+
let jwe: JweJsonGeneral | undefined = undefined
|
|
287
|
+
for (const encrypter of encrypters) {
|
|
288
|
+
if (!cek) {
|
|
289
|
+
const encryptionResult = await encrypter.encrypt(cleartext, protectedHeader, aad)
|
|
290
|
+
cek = encryptionResult.cek
|
|
291
|
+
jwe = jweEncode({...encryptionResult, aad})
|
|
292
|
+
} else {
|
|
293
|
+
const recipient = await encrypter.encryptCek?.(cek)
|
|
294
|
+
if (recipient) {
|
|
295
|
+
jwe?.recipients?.push(recipient)
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
if (!jwe) {
|
|
300
|
+
throw Error(`No JWE constructed`)
|
|
301
|
+
}
|
|
302
|
+
return jwe
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Merges all headers, so we get a unified header.
|
|
308
|
+
*
|
|
309
|
+
* @param protectedHeader
|
|
310
|
+
* @param unprotectedHeader
|
|
311
|
+
* @param recipientUnprotectedHeader
|
|
312
|
+
*/
|
|
313
|
+
export function jweMergeHeaders({protectedHeader, unprotectedHeader, recipientUnprotectedHeader}: {
|
|
314
|
+
protectedHeader?: JweProtectedHeader,
|
|
315
|
+
unprotectedHeader?: JweHeader,
|
|
316
|
+
recipientUnprotectedHeader?: JweRecipientUnprotectedHeader
|
|
317
|
+
}): JweHeader {
|
|
318
|
+
// TODO: Check that all headers/params are disjoint!
|
|
319
|
+
const header = {...protectedHeader, ...unprotectedHeader, ...recipientUnprotectedHeader}
|
|
320
|
+
|
|
321
|
+
if (!header.alg || !header.enc) {
|
|
322
|
+
throw Error(`Either 'alg' or 'enc' are missing from the headers`)
|
|
323
|
+
}
|
|
324
|
+
return header as JweHeader
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
export async function decryptJwe(jwe: JweJsonGeneral, decrypter: JweDecrypter): Promise<Uint8Array> {
|
|
328
|
+
jweAssertValid(jwe)
|
|
329
|
+
const protectedHeader: JweProtectedHeader = JSON.parse(decodeBase64url(jwe.protected))
|
|
330
|
+
if (protectedHeader?.enc !== decrypter.enc) {
|
|
331
|
+
return Promise.reject(Error(`Decrypter enc '${decrypter.enc}' does not support header enc '${protectedHeader.enc}'`))
|
|
332
|
+
} else if (!jwe.tag) {
|
|
333
|
+
return Promise.reject(Error(`Decrypter enc '${decrypter.enc}' does not support header enc '${protectedHeader.enc}'`))
|
|
334
|
+
}
|
|
335
|
+
const sealed = toWebCryptoCiphertext(jwe.ciphertext, jwe.tag)
|
|
336
|
+
const aad = u8a.fromString(jwe.aad ? `${jwe.protected}.${jwe.aad}` : jwe.protected)
|
|
337
|
+
let cleartext = null
|
|
338
|
+
if (protectedHeader.alg === 'dir' && decrypter.alg === 'dir') {
|
|
339
|
+
cleartext = await decrypter.decrypt(sealed, base64ToBytes(jwe.iv), aad)
|
|
340
|
+
} else if (!jwe.recipients || jwe.recipients.length === 0) {
|
|
341
|
+
throw Error('missing recipients for JWE')
|
|
342
|
+
} else {
|
|
343
|
+
for (let i = 0; !cleartext && i < jwe.recipients.length; i++) {
|
|
344
|
+
const recipient: JweRecipient = jwe.recipients[i]
|
|
345
|
+
recipient.header = {...recipient.header, ...protectedHeader} as JweRecipientUnprotectedHeader
|
|
346
|
+
if (recipient.header.alg === decrypter.alg) {
|
|
347
|
+
cleartext = await decrypter.decrypt(sealed, base64ToBytes(jwe.iv), aad, recipient)
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
if (cleartext === null) throw new Error('failure: Failed to decrypt')
|
|
352
|
+
return cleartext
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
export function toWebCryptoCiphertext(ciphertext: string, tag: string): Uint8Array {
|
|
357
|
+
return u8a.concat([base64ToBytes(ciphertext), base64ToBytes(tag)])
|
|
358
|
+
}
|
package/src/functions/index.ts
CHANGED
|
@@ -33,13 +33,13 @@ import {
|
|
|
33
33
|
JwsJsonGeneral,
|
|
34
34
|
JwsJsonGeneralWithIdentifiers,
|
|
35
35
|
JwsJsonSignature, JwsJsonSignatureWithIdentifier,
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
JwsHeader,
|
|
37
|
+
JwsPayload,
|
|
38
38
|
PreparedJwsObject,
|
|
39
|
-
VerifyJwsArgs,
|
|
39
|
+
VerifyJwsArgs, JweHeader,
|
|
40
40
|
} from '../types/IJwtService'
|
|
41
41
|
|
|
42
|
-
const payloadToBytes = (payload: string |
|
|
42
|
+
const payloadToBytes = (payload: string | JwsPayload | Uint8Array): Uint8Array => {
|
|
43
43
|
const isBytes = payload instanceof Uint8Array
|
|
44
44
|
const isString = typeof payload === 'string'
|
|
45
45
|
return isBytes ? payload : isString ? u8a.fromString(payload, 'base64url') : u8a.fromString(JSON.stringify(payload), 'utf-8')
|
|
@@ -50,7 +50,7 @@ export const prepareJwsObject = async (args: CreateJwsJsonArgs, context: IRequir
|
|
|
50
50
|
|
|
51
51
|
const { noIdentifierInHeader = false } = issuer
|
|
52
52
|
const identifier = await ensureManagedIdentifierResult(issuer, context)
|
|
53
|
-
await
|
|
53
|
+
await checkAndUpdateJwsHeader({ mode, identifier, noIdentifierInHeader, header: protectedHeader }, context)
|
|
54
54
|
const isBytes = payload instanceof Uint8Array
|
|
55
55
|
const isString = typeof payload === 'string'
|
|
56
56
|
if (!isBytes && !isString) {
|
|
@@ -138,7 +138,7 @@ export const createJwsJsonGeneral = async (args: CreateJwsJsonArgs, context: IRe
|
|
|
138
138
|
* @param context
|
|
139
139
|
*/
|
|
140
140
|
|
|
141
|
-
export const
|
|
141
|
+
export const checkAndUpdateJwsHeader = async (
|
|
142
142
|
{
|
|
143
143
|
mode = 'auto',
|
|
144
144
|
identifier,
|
|
@@ -148,7 +148,7 @@ export const checkAndUpdateJwtHeader = async (
|
|
|
148
148
|
mode?: JwsIdentifierMode
|
|
149
149
|
identifier: ManagedIdentifierResult
|
|
150
150
|
noIdentifierInHeader?: boolean
|
|
151
|
-
header:
|
|
151
|
+
header: JwsHeader
|
|
152
152
|
},
|
|
153
153
|
context: IRequiredContext
|
|
154
154
|
) => {
|
|
@@ -179,7 +179,7 @@ const checkAndUpdateX5cHeader = async (
|
|
|
179
179
|
identifier,
|
|
180
180
|
noIdentifierInHeader = false,
|
|
181
181
|
}: {
|
|
182
|
-
header:
|
|
182
|
+
header: JwsHeader | JweHeader
|
|
183
183
|
identifier: ManagedIdentifierResult
|
|
184
184
|
noIdentifierInHeader?: boolean
|
|
185
185
|
},
|
|
@@ -208,7 +208,7 @@ const checkAndUpdateDidHeader = async (
|
|
|
208
208
|
identifier,
|
|
209
209
|
noIdentifierInHeader = false,
|
|
210
210
|
}: {
|
|
211
|
-
header:
|
|
211
|
+
header: JwsHeader | JweHeader
|
|
212
212
|
identifier: ManagedIdentifierResult
|
|
213
213
|
noIdentifierInHeader?: boolean
|
|
214
214
|
},
|
|
@@ -237,7 +237,7 @@ const checkAndUpdateJwkHeader = async (
|
|
|
237
237
|
identifier,
|
|
238
238
|
noIdentifierInHeader = false,
|
|
239
239
|
}: {
|
|
240
|
-
header:
|
|
240
|
+
header: JwsHeader | JweHeader
|
|
241
241
|
identifier: ManagedIdentifierResult
|
|
242
242
|
noIdentifierInHeader?: boolean
|
|
243
243
|
},
|
|
@@ -246,7 +246,7 @@ const checkAndUpdateJwkHeader = async (
|
|
|
246
246
|
const { jwk } = header
|
|
247
247
|
if (jwk) {
|
|
248
248
|
// let's resolve the provided x5c to be sure
|
|
249
|
-
const jwkIdentifier = await context.agent.identifierManagedGetByJwk({ identifier: jwk })
|
|
249
|
+
const jwkIdentifier = await context.agent.identifierManagedGetByJwk({ identifier: jwk as JWK })
|
|
250
250
|
if (jwkIdentifier.kmsKeyRef !== identifier.kmsKeyRef) {
|
|
251
251
|
return Promise.reject(Error(`A jwk header was present, but its value did not match the provided signing jwk or kid!`))
|
|
252
252
|
}
|
|
@@ -265,7 +265,7 @@ const checkAndUpdateKidHeader = async (
|
|
|
265
265
|
identifier,
|
|
266
266
|
noIdentifierInHeader = false,
|
|
267
267
|
}: {
|
|
268
|
-
header:
|
|
268
|
+
header: JwsHeader | JweHeader
|
|
269
269
|
identifier: ManagedIdentifierResult
|
|
270
270
|
noIdentifierInHeader?: boolean
|
|
271
271
|
},
|
|
@@ -376,7 +376,7 @@ export const toJwsJsonGeneral = async ({ jws }: { jws: Jws }, context: IAgentCon
|
|
|
376
376
|
}
|
|
377
377
|
|
|
378
378
|
async function resolveExternalIdentifierFromJwsHeader(
|
|
379
|
-
protectedHeader:
|
|
379
|
+
protectedHeader: JwsHeader,
|
|
380
380
|
context: IAgentContext<IIdentifierResolution>,
|
|
381
381
|
args: {
|
|
382
382
|
jws: Jws
|
|
@@ -422,7 +422,7 @@ export const toJwsJsonGeneralWithIdentifiers = async (
|
|
|
422
422
|
const jws = await toJwsJsonGeneral(args, context)
|
|
423
423
|
const signatures = (await Promise.all(
|
|
424
424
|
jws.signatures.map(async (signature) => {
|
|
425
|
-
const protectedHeader:
|
|
425
|
+
const protectedHeader: JwsHeader = decodeJoseBlob(signature.protected)
|
|
426
426
|
const identifier = args.jwk
|
|
427
427
|
? await resolveExternalJwkIdentifier({ identifier: args.jwk }, context)
|
|
428
428
|
: await resolveExternalIdentifierFromJwsHeader(protectedHeader, context, args)
|
|
@@ -432,6 +432,6 @@ export const toJwsJsonGeneralWithIdentifiers = async (
|
|
|
432
432
|
return undefined
|
|
433
433
|
})
|
|
434
434
|
)).filter(signature => signature !== undefined ) as JwsJsonSignatureWithIdentifier[]
|
|
435
|
-
|
|
435
|
+
|
|
436
436
|
return { payload: jws.payload, signatures }
|
|
437
437
|
}
|
package/src/index.ts
CHANGED