1 | const { createCipheriv, createDecipheriv, getCiphers } = require('crypto')
|
2 |
|
3 | const uint64be = require('../help/uint64be')
|
4 | const timingSafeEqual = require('../help/timing_safe_equal')
|
5 | const { KEYOBJECT } = require('../help/consts')
|
6 | const { JWEInvalid, JWEDecryptionFailed } = require('../errors')
|
7 |
|
8 | const checkInput = function (size, iv, tag) {
|
9 | if (iv.length !== 16) {
|
10 | throw new JWEInvalid('invalid iv')
|
11 | }
|
12 | if (arguments.length === 3) {
|
13 | if (tag.length !== size / 8) {
|
14 | throw new JWEInvalid('invalid tag')
|
15 | }
|
16 | }
|
17 | }
|
18 |
|
19 | const encrypt = (size, sign, { [KEYOBJECT]: keyObject }, cleartext, { iv, aad = Buffer.alloc(0) }) => {
|
20 | const key = keyObject.export()
|
21 | checkInput(size, iv)
|
22 |
|
23 | const keySize = size / 8
|
24 | const encKey = key.slice(keySize)
|
25 | const cipher = createCipheriv(`aes-${size}-cbc`, encKey, iv)
|
26 | const ciphertext = Buffer.concat([cipher.update(cleartext), cipher.final()])
|
27 | const macData = Buffer.concat([aad, iv, ciphertext, uint64be(aad.length * 8)])
|
28 |
|
29 | const macKey = key.slice(0, keySize)
|
30 | const tag = sign({ [KEYOBJECT]: macKey }, macData).slice(0, keySize)
|
31 |
|
32 | return { ciphertext, tag }
|
33 | }
|
34 |
|
35 | const decrypt = (size, sign, { [KEYOBJECT]: keyObject }, ciphertext, { iv, tag = Buffer.alloc(0), aad = Buffer.alloc(0) }) => {
|
36 | checkInput(size, iv, tag)
|
37 |
|
38 | const keySize = size / 8
|
39 | const key = keyObject.export()
|
40 | const encKey = key.slice(keySize)
|
41 | const macKey = key.slice(0, keySize)
|
42 |
|
43 | const macData = Buffer.concat([aad, iv, ciphertext, uint64be(aad.length * 8)])
|
44 | const expectedTag = sign({ [KEYOBJECT]: macKey }, macData, tag).slice(0, keySize)
|
45 | const macCheckPassed = timingSafeEqual(tag, expectedTag)
|
46 |
|
47 | let cleartext
|
48 | try {
|
49 | const cipher = createDecipheriv(`aes-${size}-cbc`, encKey, iv)
|
50 | cleartext = Buffer.concat([cipher.update(ciphertext), cipher.final()])
|
51 | } catch (err) {}
|
52 |
|
53 | if (!cleartext || !macCheckPassed) {
|
54 | throw new JWEDecryptionFailed()
|
55 | }
|
56 |
|
57 | return cleartext
|
58 | }
|
59 |
|
60 | module.exports = (JWA, JWK) => {
|
61 | ['A128CBC-HS256', 'A192CBC-HS384', 'A256CBC-HS512'].forEach((jwaAlg) => {
|
62 | const size = parseInt(jwaAlg.substr(1, 3), 10)
|
63 | const sign = JWA.sign.get(`HS${size * 2}`)
|
64 | if (getCiphers().includes(`aes-${size}-cbc`)) {
|
65 | JWA.encrypt.set(jwaAlg, encrypt.bind(undefined, size, sign))
|
66 | JWA.decrypt.set(jwaAlg, decrypt.bind(undefined, size, sign))
|
67 | JWK.oct.encrypt[jwaAlg] = JWK.oct.decrypt[jwaAlg] = key => (key.use === 'enc' || key.use === undefined) && key.length / 2 === size
|
68 | }
|
69 | })
|
70 | }
|