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