UNPKG

6.26 kBJavaScriptView Raw
1const { deflateRawSync } = require('zlib')
2
3const { KEYOBJECT } = require('../help/consts')
4const generateIV = require('../help/generate_iv')
5const base64url = require('../help/base64url')
6const getKey = require('../help/get_key')
7const isObject = require('../help/is_object')
8const { createSecretKey } = require('../help/key_object')
9const deepClone = require('../help/deep_clone')
10const importKey = require('../jwk/import')
11const { JWEInvalid } = require('../errors')
12const { check, keyManagementEncrypt, encrypt } = require('../jwa')
13
14const serializers = require('./serializers')
15const generateCEK = require('./generate_cek')
16const validateHeaders = require('./validate_headers')
17
18const PROCESS_RECIPIENT = Symbol('PROCESS_RECIPIENT')
19
20class Encrypt {
21 // TODO: in v2.x swap unprotectedHeader and aad
22 constructor (cleartext, protectedHeader, unprotectedHeader, aad) {
23 if (!Buffer.isBuffer(cleartext) && typeof cleartext !== 'string') {
24 throw new TypeError('cleartext argument must be a Buffer or a string')
25 }
26 cleartext = Buffer.from(cleartext)
27
28 if (aad !== undefined && !Buffer.isBuffer(aad) && typeof aad !== 'string') {
29 throw new TypeError('aad argument must be a Buffer or a string when provided')
30 }
31 aad = aad ? Buffer.from(aad) : undefined
32
33 if (protectedHeader !== undefined && !isObject(protectedHeader)) {
34 throw new TypeError('protectedHeader argument must be a plain object when provided')
35 }
36
37 if (unprotectedHeader !== undefined && !isObject(unprotectedHeader)) {
38 throw new TypeError('unprotectedHeader argument must be a plain object when provided')
39 }
40
41 this._recipients = []
42 this._cleartext = cleartext
43 this._aad = aad
44 this._unprotected = unprotectedHeader ? deepClone(unprotectedHeader) : undefined
45 this._protected = protectedHeader ? deepClone(protectedHeader) : undefined
46 }
47
48 /*
49 * @public
50 */
51 recipient (key, header) {
52 key = getKey(key)
53
54 if (header !== undefined && !isObject(header)) {
55 throw new TypeError('header argument must be a plain object when provided')
56 }
57
58 this._recipients.push({
59 key,
60 header: header ? deepClone(header) : undefined
61 })
62
63 return this
64 }
65
66 /*
67 * @private
68 */
69 [PROCESS_RECIPIENT] (recipient) {
70 const unprotectedHeader = this._unprotected
71 const protectedHeader = this._protected
72 const { length: recipientCount } = this._recipients
73
74 const jweHeader = {
75 ...protectedHeader,
76 ...unprotectedHeader,
77 ...recipient.header
78 }
79 const { key } = recipient
80
81 const enc = jweHeader.enc
82 let alg = jweHeader.alg
83
84 if (key.use === 'sig') {
85 throw new TypeError('a key with "use":"sig" is not usable for encryption')
86 }
87
88 if (alg === 'dir') {
89 check(key, 'encrypt', enc)
90 } else if (alg) {
91 check(key, 'keyManagementEncrypt', alg)
92 } else {
93 alg = key.alg || [...key.algorithms('wrapKey')][0] || [...key.algorithms('deriveKey')][0]
94
95 if (alg === 'ECDH-ES' && recipientCount !== 1) {
96 alg = [...key.algorithms('deriveKey')][1]
97 }
98
99 if (!alg) {
100 throw new JWEInvalid('could not resolve a usable "alg" for a recipient')
101 }
102
103 if (recipientCount === 1) {
104 if (protectedHeader) {
105 protectedHeader.alg = alg
106 } else {
107 this._protected = { alg }
108 }
109 } else {
110 if (recipient.header) {
111 recipient.header.alg = alg
112 } else {
113 recipient.header = { alg }
114 }
115 }
116 }
117
118 let wrapped
119 let generatedHeader
120
121 if (key.kty === 'oct' && alg === 'dir') {
122 this._cek = importKey(key[KEYOBJECT], { use: 'enc', alg: enc })
123 } else {
124 check(this._cek, 'encrypt', enc)
125 ;({ wrapped, header: generatedHeader } = keyManagementEncrypt(alg, key, this._cek[KEYOBJECT].export(), { enc, alg }))
126 if (alg === 'ECDH-ES') {
127 this._cek = importKey(createSecretKey(wrapped), { use: 'enc', alg: enc })
128 }
129 }
130
131 if (alg === 'dir' || alg === 'ECDH-ES') {
132 recipient.encrypted_key = ''
133 } else {
134 recipient.encrypted_key = base64url.encodeBuffer(wrapped)
135 }
136
137 if (generatedHeader) {
138 recipient.generatedHeader = generatedHeader
139 }
140 }
141
142 /*
143 * @public
144 */
145 encrypt (serialization) {
146 const serializer = serializers[serialization]
147 if (!serializer) {
148 throw new TypeError('serialization must be one of "compact", "flattened", "general"')
149 }
150
151 if (!this._recipients.length) {
152 throw new JWEInvalid('missing recipients')
153 }
154
155 serializer.validate(this._protected, this._unprotected, this._aad, this._recipients)
156
157 let enc = validateHeaders(this._protected, this._unprotected, this._recipients, false, this._protected ? this._protected.crit : undefined)
158 if (!enc) {
159 enc = 'A128CBC-HS256'
160 if (this._protected) {
161 this._protected.enc = enc
162 } else {
163 this._protected = { enc }
164 }
165 }
166 const final = {}
167 this._cek = generateCEK(enc)
168
169 for (const recipient of this._recipients) {
170 this[PROCESS_RECIPIENT](recipient)
171 }
172
173 const iv = generateIV(enc)
174 final.iv = base64url.encodeBuffer(iv)
175
176 if (this._recipients.length === 1 && this._recipients[0].generatedHeader) {
177 const [{ generatedHeader }] = this._recipients
178 delete this._recipients[0].generatedHeader
179 this._protected = {
180 ...this._protected,
181 ...generatedHeader
182 }
183 }
184
185 if (this._protected) {
186 final.protected = base64url.JSON.encode(this._protected)
187 }
188 final.unprotected = this._unprotected
189
190 let aad
191 if (this._aad) {
192 final.aad = base64url.encode(this._aad)
193 aad = Buffer.concat([Buffer.from(final.protected || ''), Buffer.from('.'), Buffer.from(final.aad)])
194 } else {
195 aad = Buffer.from(final.protected || '')
196 }
197
198 let cleartext = this._cleartext
199 if (this._protected && 'zip' in this._protected) {
200 cleartext = deflateRawSync(cleartext)
201 }
202
203 const { ciphertext, tag } = encrypt(enc, this._cek, cleartext, { iv, aad })
204 final.tag = base64url.encodeBuffer(tag)
205 final.ciphertext = base64url.encodeBuffer(ciphertext)
206
207 return serializer(final, this._recipients)
208 }
209}
210
211module.exports = Encrypt