1
2
3
4
5 package cryptotest
6
7 import (
8 "bytes"
9 "crypto/cipher"
10 "testing"
11 )
12
13
14
15 type MakeBlockMode func(b cipher.Block, iv []byte) cipher.BlockMode
16
17
18
19 func TestBlockMode(t *testing.T, block cipher.Block, makeEncrypter, makeDecrypter MakeBlockMode) {
20 rng := newRandReader(t)
21 iv := make([]byte, block.BlockSize())
22 rng.Read(iv)
23
24 testBlockModePair(t, block, makeEncrypter, makeDecrypter, iv)
25 }
26
27 func testBlockModePair(t *testing.T, b cipher.Block, enc, dec MakeBlockMode, iv []byte) {
28 t.Run("Encryption", func(t *testing.T) {
29 testBlockMode(t, enc, b, iv)
30 })
31
32 t.Run("Decryption", func(t *testing.T) {
33 testBlockMode(t, dec, b, iv)
34 })
35
36 t.Run("Roundtrip", func(t *testing.T) {
37 rng := newRandReader(t)
38
39 blockSize := enc(b, iv).BlockSize()
40 if decBlockSize := dec(b, iv).BlockSize(); decBlockSize != blockSize {
41 t.Errorf("decryption blocksize different than encryption's; got %d, want %d", decBlockSize, blockSize)
42 }
43
44 before, dst, after := make([]byte, blockSize*2), make([]byte, blockSize*2), make([]byte, blockSize*2)
45 rng.Read(before)
46
47 enc(b, iv).CryptBlocks(dst, before)
48 dec(b, iv).CryptBlocks(after, dst)
49 if !bytes.Equal(after, before) {
50 t.Errorf("plaintext is different after an encrypt/decrypt cycle; got %x, want %x", after, before)
51 }
52 })
53 }
54
55 func testBlockMode(t *testing.T, bm MakeBlockMode, b cipher.Block, iv []byte) {
56 blockSize := bm(b, iv).BlockSize()
57
58 t.Run("WrongIVLen", func(t *testing.T) {
59 iv := make([]byte, b.BlockSize()+1)
60 mustPanic(t, "IV length must equal block size", func() { bm(b, iv) })
61 })
62
63 t.Run("AlterInput", func(t *testing.T) {
64 rng := newRandReader(t)
65
66 src, dst, before := make([]byte, blockSize*2), make([]byte, blockSize*2), make([]byte, blockSize*2)
67
68 for _, length := range []int{0, blockSize, blockSize * 2} {
69 rng.Read(src)
70 copy(before, src)
71
72 bm(b, iv).CryptBlocks(dst[:length], src[:length])
73 if !bytes.Equal(src, before) {
74 t.Errorf("CryptBlocks modified src; got %x, want %x", src, before)
75 }
76 }
77 })
78
79 t.Run("Aliasing", func(t *testing.T) {
80 rng := newRandReader(t)
81
82 buff, expectedOutput := make([]byte, blockSize*2), make([]byte, blockSize*2)
83
84 for _, length := range []int{0, blockSize, blockSize * 2} {
85
86 rng.Read(buff)
87 bm(b, iv).CryptBlocks(expectedOutput[:length], buff[:length])
88
89
90
91 bm(b, iv).CryptBlocks(buff[:length], buff[:length])
92 if !bytes.Equal(buff[:length], expectedOutput[:length]) {
93 t.Errorf("block cipher produced different output when dst = src; got %x, want %x", buff[:length], expectedOutput[:length])
94 }
95 }
96 })
97
98 t.Run("OutOfBoundsWrite", func(t *testing.T) {
99 rng := newRandReader(t)
100
101 src := make([]byte, blockSize)
102 rng.Read(src)
103
104
105 buff := make([]byte, blockSize*3)
106 endOfPrefix, startOfSuffix := blockSize, blockSize*2
107 rng.Read(buff[:endOfPrefix])
108 rng.Read(buff[startOfSuffix:])
109 dst := buff[endOfPrefix:startOfSuffix]
110
111
112 initPrefix, initSuffix := make([]byte, blockSize), make([]byte, blockSize)
113 copy(initPrefix, buff[:endOfPrefix])
114 copy(initSuffix, buff[startOfSuffix:])
115
116
117
118 bm(b, iv).CryptBlocks(dst, src)
119 if !bytes.Equal(buff[startOfSuffix:], initSuffix) {
120 t.Errorf("block cipher did out of bounds write after end of dst slice; got %x, want %x", buff[startOfSuffix:], initSuffix)
121 }
122 if !bytes.Equal(buff[:endOfPrefix], initPrefix) {
123 t.Errorf("block cipher did out of bounds write before beginning of dst slice; got %x, want %x", buff[:endOfPrefix], initPrefix)
124 }
125
126
127
128 dst = buff[endOfPrefix:]
129 bm(b, iv).CryptBlocks(dst, src)
130 if !bytes.Equal(buff[startOfSuffix:], initSuffix) {
131 t.Errorf("CryptBlocks modified dst past len(src); got %x, want %x", buff[startOfSuffix:], initSuffix)
132 }
133
134
135 src = make([]byte, blockSize*3)
136 rng.Read(src)
137
138 mustPanic(t, "output smaller than input", func() {
139 bm(b, iv).CryptBlocks(dst, src)
140 })
141
142 if !bytes.Equal(buff[startOfSuffix:], initSuffix) {
143 t.Errorf("block cipher did out of bounds write after end of dst slice; got %x, want %x", buff[startOfSuffix:], initSuffix)
144 }
145 if !bytes.Equal(buff[:endOfPrefix], initPrefix) {
146 t.Errorf("block cipher did out of bounds write before beginning of dst slice; got %x, want %x", buff[:endOfPrefix], initPrefix)
147 }
148 })
149
150
151
152 t.Run("OutOfBoundsRead", func(t *testing.T) {
153 rng := newRandReader(t)
154
155 src := make([]byte, blockSize)
156 rng.Read(src)
157 expectedDst := make([]byte, blockSize)
158 bm(b, iv).CryptBlocks(expectedDst, src)
159
160
161 buff := make([]byte, blockSize*3)
162 endOfPrefix, startOfSuffix := blockSize, blockSize*2
163
164 copy(buff[endOfPrefix:startOfSuffix], src)
165 rng.Read(buff[:endOfPrefix])
166 rng.Read(buff[startOfSuffix:])
167
168 testDst := make([]byte, blockSize)
169 bm(b, iv).CryptBlocks(testDst, buff[endOfPrefix:startOfSuffix])
170
171 if !bytes.Equal(testDst, expectedDst) {
172 t.Errorf("CryptBlocks affected by data outside of src slice bounds; got %x, want %x", testDst, expectedDst)
173 }
174 })
175
176 t.Run("BufferOverlap", func(t *testing.T) {
177 rng := newRandReader(t)
178
179 buff := make([]byte, blockSize*2)
180 rng.Read(buff)
181
182
183 src := buff[:blockSize]
184 dst := buff[1 : blockSize+1]
185 mustPanic(t, "invalid buffer overlap", func() { bm(b, iv).CryptBlocks(dst, src) })
186
187
188 src = buff[:blockSize]
189 dst = buff[blockSize-1 : 2*blockSize-1]
190 mustPanic(t, "invalid buffer overlap", func() { bm(b, iv).CryptBlocks(dst, src) })
191
192
193 src = buff[blockSize-1 : 2*blockSize-1]
194 dst = buff[:blockSize]
195 mustPanic(t, "invalid buffer overlap", func() { bm(b, iv).CryptBlocks(dst, src) })
196 })
197
198
199 t.Run("PartialBlocks", func(t *testing.T) {
200
201 for _, srcSize := range []int{blockSize - 1, blockSize + 1, 2*blockSize - 1, 2*blockSize + 1} {
202 src := make([]byte, srcSize)
203 dst := make([]byte, 3*blockSize)
204 mustPanic(t, "input not full blocks", func() { bm(b, iv).CryptBlocks(dst, src) })
205 }
206 })
207
208 t.Run("KeepState", func(t *testing.T) {
209 rng := newRandReader(t)
210
211 src, serialDst, compositeDst := make([]byte, blockSize*4), make([]byte, blockSize*4), make([]byte, blockSize*4)
212 rng.Read(src)
213
214 length, block := 2*blockSize, bm(b, iv)
215 block.CryptBlocks(serialDst, src[:length])
216 block.CryptBlocks(serialDst[length:], src[length:])
217
218 bm(b, iv).CryptBlocks(compositeDst, src)
219
220 if !bytes.Equal(serialDst, compositeDst) {
221 t.Errorf("two successive CryptBlocks calls returned a different result than a single one; got %x, want %x", serialDst, compositeDst)
222 }
223 })
224 }
225
View as plain text