UNPKG

2.47 kBJavaScriptView Raw
1const { createCipheriv, createDecipheriv, getCiphers } = require('crypto')
2
3const uint64be = require('../help/uint64be')
4const timingSafeEqual = require('../help/timing_safe_equal')
5const { KEYOBJECT } = require('../help/consts')
6const { JWEInvalid, JWEDecryptionFailed } = require('../errors')
7
8const 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
19const 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
35const 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
60module.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}