|
| 1 | +var aes = require('./aes') |
| 2 | +var Buffer = require('safe-buffer').Buffer |
| 3 | +var Transform = require('cipher-base') |
| 4 | +var inherits = require('inherits') |
| 5 | +var xorInplace = require('buffer-xor/inplace') |
| 6 | +var xorTest = require('timing-safe-equal') |
| 7 | +function writeUIntBE (buff, value, start, length) { |
| 8 | + if (length > 6) { |
| 9 | + start += length - 6 |
| 10 | + length = 6 |
| 11 | + } |
| 12 | + buff.writeUIntBE(value, start, length) |
| 13 | +} |
| 14 | + |
| 15 | +function cbc (prev, data, self) { |
| 16 | + var rump = 16 - (data.length % 16) |
| 17 | + if (rump !== 16) { |
| 18 | + data = Buffer.concat([data, Buffer.alloc(rump)]) |
| 19 | + } |
| 20 | + var place = 0 |
| 21 | + while (place < data.length) { |
| 22 | + xorInplace(prev, data.slice(place, place + 16)) |
| 23 | + place += 16 |
| 24 | + prev = self._cipher.encryptBlock(prev) |
| 25 | + } |
| 26 | + return prev |
| 27 | +} |
| 28 | +function StreamCipher (mode, key, iv, decrypt, options) { |
| 29 | + Transform.call(this) |
| 30 | + |
| 31 | + if (!options || !options.authTagLength) throw new Error('options authTagLength is required') |
| 32 | + |
| 33 | + if (options.authTagLength < 4 || options.authTagLength > 16 || options.authTagLength % 2 === 1) throw new Error('authTagLength must be one of 4, 6, 8, 10, 12, 14 or 16') |
| 34 | + |
| 35 | + if (iv.length < 7 || iv.length > 13) throw new Error('iv must be between 7 and 13 bytes') |
| 36 | + |
| 37 | + this._n = iv.length |
| 38 | + this._l = 15 - this._n |
| 39 | + this._cipher = new aes.AES(key) |
| 40 | + this.authTagLength = options.authTagLength |
| 41 | + this._mode = mode |
| 42 | + this._add = null |
| 43 | + this._decrypt = decrypt |
| 44 | + this._authTag = null |
| 45 | + this._called = false |
| 46 | + this._plainLength = null |
| 47 | + this._prev = null |
| 48 | + this._iv = iv |
| 49 | + this._cache = Buffer.allocUnsafe(0) |
| 50 | + this._failed = false |
| 51 | + this._firstBlock = null |
| 52 | +} |
| 53 | +function validSize (ivLen, chunkLen) { |
| 54 | + if (ivLen === 13 && chunkLen >= 65536) { |
| 55 | + return false |
| 56 | + } |
| 57 | + if (ivLen === 12 && chunkLen >= 16777216) { |
| 58 | + return false |
| 59 | + } |
| 60 | + return true |
| 61 | +} |
| 62 | +inherits(StreamCipher, Transform) |
| 63 | +function createTag (self, data) { |
| 64 | + var firstBlock = self._firstBlock |
| 65 | + if (!firstBlock) { |
| 66 | + firstBlock = Buffer.alloc(16) |
| 67 | + firstBlock[0] = ((self.authTagLength - 2) / 2) * 8 + self._l - 1 |
| 68 | + self._iv.copy(firstBlock, 1) |
| 69 | + writeUIntBE(firstBlock, data.length, self._n + 1, self._l) |
| 70 | + firstBlock = self._cipher.encryptBlock(firstBlock) |
| 71 | + } |
| 72 | + return cbc(firstBlock, data, self) |
| 73 | +} |
| 74 | +StreamCipher.prototype._update = function (chunk) { |
| 75 | + if (this._called) throw new Error('Trying to add data in unsupported state') |
| 76 | + |
| 77 | + if (!validSize(this._iv.length, chunk.length)) throw new Error('Message exceeds maximum size') |
| 78 | + |
| 79 | + if (this._plainLength !== null && this._plainLength !== chunk.length) throw new Error('Trying to add data in unsupported state') |
| 80 | + |
| 81 | + this._called = true |
| 82 | + this._prev = Buffer.alloc(16) |
| 83 | + this._prev[0] = this._l - 1 |
| 84 | + this._iv.copy(this._prev, 1) |
| 85 | + var toXor |
| 86 | + if (this._decrypt) { |
| 87 | + toXor = this._mode.encrypt(this, Buffer.alloc(16)).slice(0, this.authTagLength) |
| 88 | + } else { |
| 89 | + this._authTag = this._mode.encrypt(this, createTag(this, chunk)).slice(0, this.authTagLength) |
| 90 | + } |
| 91 | + var out = this._mode.encrypt(this, chunk) |
| 92 | + if (this._decrypt) { |
| 93 | + var rawAuth = createTag(this, out).slice(0, this.authTagLength) |
| 94 | + xorInplace(rawAuth, toXor) |
| 95 | + this._failed = !xorTest(rawAuth, this._authTag) |
| 96 | + } |
| 97 | + this._cipher.scrub() |
| 98 | + return out |
| 99 | +} |
| 100 | + |
| 101 | +StreamCipher.prototype._final = function () { |
| 102 | + if (this._decrypt && !this._authTag) throw new Error('Unsupported state or unable to authenticate data') |
| 103 | + |
| 104 | + if (this._failed) throw new Error('Unsupported state or unable to authenticate data') |
| 105 | +} |
| 106 | + |
| 107 | +StreamCipher.prototype.getAuthTag = function getAuthTag () { |
| 108 | + if (this._decrypt || !Buffer.isBuffer(this._authTag)) throw new Error('Attempting to get auth tag in unsupported state') |
| 109 | + |
| 110 | + return this._authTag |
| 111 | +} |
| 112 | + |
| 113 | +StreamCipher.prototype.setAuthTag = function setAuthTag (tag) { |
| 114 | + if (!this._decrypt) throw new Error('Attempting to set auth tag in unsupported state') |
| 115 | + |
| 116 | + this._authTag = tag |
| 117 | +} |
| 118 | + |
| 119 | +StreamCipher.prototype.setAAD = function setAAD (buf, options) { |
| 120 | + if (this._called) throw new Error('Attempting to set AAD in unsupported state') |
| 121 | + |
| 122 | + if (!options || !options.plaintextLength) throw new Error('options plaintextLength is required') |
| 123 | + |
| 124 | + if (!validSize(this._iv.length, options.plaintextLength)) throw new Error('Message exceeds maximum size') |
| 125 | + |
| 126 | + this._plainLength = options.plaintextLength |
| 127 | + |
| 128 | + if (!buf.length) return |
| 129 | + |
| 130 | + var firstBlock = Buffer.alloc(16) |
| 131 | + firstBlock[0] = 64 + ((this.authTagLength - 2) / 2) * 8 + this._l - 1 |
| 132 | + this._iv.copy(firstBlock, 1) |
| 133 | + writeUIntBE(firstBlock, options.plaintextLength, this._n + 1, this._l) |
| 134 | + firstBlock = this._cipher.encryptBlock(firstBlock) |
| 135 | + |
| 136 | + var la = buf.length |
| 137 | + var ltag |
| 138 | + if (la < 65280) { |
| 139 | + ltag = Buffer.allocUnsafe(2) |
| 140 | + ltag.writeUInt16BE(la, 0) |
| 141 | + } else if (la < 4294967296) { |
| 142 | + ltag = Buffer.allocUnsafe(6) |
| 143 | + ltag[0] = 0xff |
| 144 | + ltag[1] = 0xfe |
| 145 | + ltag.writeUInt32BE(la, 2) |
| 146 | + } else { |
| 147 | + ltag = Buffer.alloc(10) |
| 148 | + ltag[0] = 0xff |
| 149 | + ltag[1] = 0xff |
| 150 | + ltag.writeUIntBE(la, 4, 6) |
| 151 | + } |
| 152 | + var aToAuth = Buffer.concat([ltag, buf]) |
| 153 | + this._firstBlock = cbc(firstBlock, aToAuth, this) |
| 154 | +} |
| 155 | + |
| 156 | +module.exports = StreamCipher |
0 commit comments