UNPKG

3.55 kBPlain TextView Raw
1import { base64ToBytes, bytesToBase64url, decodeBase64url, stringToBytes, toSealed } from '../util.js'
2import type { Decrypter, Encrypter, EncryptionResult, EphemeralKeyPair, JWE, ProtectedHeader } from './types.js'
3
4function validateJWE(jwe: JWE) {
5 if (!(jwe.protected && jwe.iv && jwe.ciphertext && jwe.tag)) {
6 throw new Error('bad_jwe: missing properties')
7 }
8 if (jwe.recipients) {
9 jwe.recipients.map((rec) => {
10 if (!(rec.header && rec.encrypted_key)) {
11 throw new Error('bad_jwe: malformed recipients')
12 }
13 })
14 }
15}
16
17function encodeJWE({ ciphertext, tag, iv, protectedHeader, recipient }: EncryptionResult, aad?: Uint8Array): JWE {
18 const jwe: JWE = {
19 protected: <string>protectedHeader,
20 iv: bytesToBase64url(iv ?? new Uint8Array(0)),
21 ciphertext: bytesToBase64url(ciphertext),
22 tag: bytesToBase64url(tag ?? new Uint8Array(0)),
23 }
24 if (aad) jwe.aad = bytesToBase64url(aad)
25 if (recipient) jwe.recipients = [recipient]
26 return jwe
27}
28
29export async function createJWE(
30 cleartext: Uint8Array,
31 encrypters: Encrypter[],
32 protectedHeader: ProtectedHeader = {},
33 aad?: Uint8Array,
34 useSingleEphemeralKey = false
35): Promise<JWE> {
36 if (encrypters[0].alg === 'dir') {
37 if (encrypters.length > 1) throw new Error('not_supported: Can only do "dir" encryption to one key.')
38 const encryptionResult = await encrypters[0].encrypt(cleartext, protectedHeader, aad)
39 return encodeJWE(encryptionResult, aad)
40 } else {
41 const tmpEnc = encrypters[0].enc
42 if (!encrypters.reduce((acc, encrypter) => acc && encrypter.enc === tmpEnc, true)) {
43 throw new Error('invalid_argument: Incompatible encrypters passed')
44 }
45 let cek: Uint8Array | undefined
46 let jwe: JWE | undefined
47 let epk: EphemeralKeyPair | undefined
48 if (useSingleEphemeralKey) {
49 epk = encrypters[0].genEpk?.()
50 const alg = encrypters[0].alg
51 protectedHeader = { ...protectedHeader, alg, epk: epk?.publicKeyJWK }
52 }
53
54 for (const encrypter of encrypters) {
55 if (!cek) {
56 const encryptionResult = await encrypter.encrypt(cleartext, protectedHeader, aad, epk)
57 cek = encryptionResult.cek
58 jwe = encodeJWE(encryptionResult, aad)
59 } else {
60 const recipient = await encrypter.encryptCek?.(cek, epk)
61 if (recipient) {
62 jwe?.recipients?.push(recipient)
63 }
64 }
65 }
66 return <JWE>jwe
67 }
68}
69
70export async function decryptJWE(jwe: JWE, decrypter: Decrypter): Promise<Uint8Array> {
71 validateJWE(jwe)
72 const protHeader = JSON.parse(decodeBase64url(jwe.protected))
73 if (protHeader.enc !== decrypter.enc)
74 throw new Error(`not_supported: Decrypter does not supported: '${protHeader.enc}'`)
75 const sealed = toSealed(jwe.ciphertext, jwe.tag)
76 const aad = stringToBytes(jwe.aad ? `${jwe.protected}.${jwe.aad}` : jwe.protected)
77 let cleartext = null
78 if (protHeader.alg === 'dir' && decrypter.alg === 'dir') {
79 cleartext = await decrypter.decrypt(sealed, base64ToBytes(jwe.iv), aad)
80 } else if (!jwe.recipients || jwe.recipients.length === 0) {
81 throw new Error('bad_jwe: missing recipients')
82 } else {
83 for (let i = 0; !cleartext && i < jwe.recipients.length; i++) {
84 const recipient = jwe.recipients[i]
85 Object.assign(recipient.header, protHeader)
86 if (recipient.header.alg === decrypter.alg) {
87 cleartext = await decrypter.decrypt(sealed, base64ToBytes(jwe.iv), aad, recipient)
88 }
89 }
90 }
91 if (cleartext === null) throw new Error('failure: Failed to decrypt')
92 return cleartext
93}