-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathblock.go
254 lines (198 loc) · 7.83 KB
/
block.go
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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package cryptotest
import (
"bytes"
"crypto/cipher"
"testing"
)
type MakeBlock func(key []byte) (cipher.Block, error)
// TestBlock performs a set of tests on cipher.Block implementations, checking
// the documented requirements of BlockSize, Encrypt, and Decrypt.
func TestBlock(t *testing.T, keySize int, mb MakeBlock) {
// Generate random key
key := make([]byte, keySize)
newRandReader(t).Read(key)
t.Logf("Cipher key: 0x%x", key)
block, err := mb(key)
if err != nil {
t.Fatal(err)
}
blockSize := block.BlockSize()
t.Run("Encryption", func(t *testing.T) {
testCipher(t, block.Encrypt, blockSize)
})
t.Run("Decryption", func(t *testing.T) {
testCipher(t, block.Decrypt, blockSize)
})
// Checks baseline Encrypt/Decrypt functionality. More thorough
// implementation-specific characterization/golden tests should be done
// for each block cipher implementation.
t.Run("Roundtrip", func(t *testing.T) {
rng := newRandReader(t)
// Check Decrypt inverts Encrypt
before, ciphertext, after := make([]byte, blockSize), make([]byte, blockSize), make([]byte, blockSize)
rng.Read(before)
block.Encrypt(ciphertext, before)
block.Decrypt(after, ciphertext)
if !bytes.Equal(after, before) {
t.Errorf("plaintext is different after an encrypt/decrypt cycle; got %x, want %x", after, before)
}
// Check Encrypt inverts Decrypt (assumes block ciphers are deterministic)
before, plaintext, after := make([]byte, blockSize), make([]byte, blockSize), make([]byte, blockSize)
rng.Read(before)
block.Decrypt(plaintext, before)
block.Encrypt(after, plaintext)
if !bytes.Equal(after, before) {
t.Errorf("ciphertext is different after a decrypt/encrypt cycle; got %x, want %x", after, before)
}
})
}
func testCipher(t *testing.T, cipher func(dst, src []byte), blockSize int) {
t.Run("AlterInput", func(t *testing.T) {
rng := newRandReader(t)
// Make long src that shouldn't be modified at all, within block
// size scope or beyond it
src, before := make([]byte, blockSize*2), make([]byte, blockSize*2)
rng.Read(src)
copy(before, src)
dst := make([]byte, blockSize)
cipher(dst, src)
if !bytes.Equal(src, before) {
t.Errorf("block cipher modified src; got %x, want %x", src, before)
}
})
t.Run("Aliasing", func(t *testing.T) {
rng := newRandReader(t)
buff, expectedOutput := make([]byte, blockSize), make([]byte, blockSize)
// Record what output is when src and dst are different
rng.Read(buff)
cipher(expectedOutput, buff)
// Check that the same output is generated when src=dst alias to the same
// memory
cipher(buff, buff)
if !bytes.Equal(buff, expectedOutput) {
t.Errorf("block cipher produced different output when dst = src; got %x, want %x", buff, expectedOutput)
}
})
t.Run("OutOfBoundsWrite", func(t *testing.T) {
rng := newRandReader(t)
src := make([]byte, blockSize)
rng.Read(src)
// Make a buffer with dst in the middle and data on either end
buff := make([]byte, blockSize*3)
endOfPrefix, startOfSuffix := blockSize, blockSize*2
rng.Read(buff[:endOfPrefix])
rng.Read(buff[startOfSuffix:])
dst := buff[endOfPrefix:startOfSuffix]
// Record the prefix and suffix data to make sure they aren't written to
initPrefix, initSuffix := make([]byte, blockSize), make([]byte, blockSize)
copy(initPrefix, buff[:endOfPrefix])
copy(initSuffix, buff[startOfSuffix:])
// Write to dst (the middle of the buffer) and make sure it doesn't write
// beyond the dst slice
cipher(dst, src)
if !bytes.Equal(buff[startOfSuffix:], initSuffix) {
t.Errorf("block cipher did out of bounds write after end of dst slice; got %x, want %x", buff[startOfSuffix:], initSuffix)
}
if !bytes.Equal(buff[:endOfPrefix], initPrefix) {
t.Errorf("block cipher did out of bounds write before beginning of dst slice; got %x, want %x", buff[:endOfPrefix], initPrefix)
}
// Check that dst isn't written to beyond BlockSize even if there is room
// in the slice
dst = buff[endOfPrefix:] // Extend dst to include suffix
cipher(dst, src)
if !bytes.Equal(buff[startOfSuffix:], initSuffix) {
t.Errorf("block cipher modified dst past BlockSize bytes; got %x, want %x", buff[startOfSuffix:], initSuffix)
}
})
// Check that output of cipher isn't affected by adjacent data beyond input
// slice scope
// For encryption, this assumes block ciphers encrypt deterministically
t.Run("OutOfBoundsRead", func(t *testing.T) {
rng := newRandReader(t)
src := make([]byte, blockSize)
rng.Read(src)
expectedDst := make([]byte, blockSize)
cipher(expectedDst, src)
// Make a buffer with src in the middle and data on either end
buff := make([]byte, blockSize*3)
endOfPrefix, startOfSuffix := blockSize, blockSize*2
copy(buff[endOfPrefix:startOfSuffix], src)
rng.Read(buff[:endOfPrefix])
rng.Read(buff[startOfSuffix:])
testDst := make([]byte, blockSize)
cipher(testDst, buff[endOfPrefix:startOfSuffix])
if !bytes.Equal(testDst, expectedDst) {
t.Errorf("block cipher affected by data outside of src slice bounds; got %x, want %x", testDst, expectedDst)
}
// Check that src isn't read from beyond BlockSize even if the slice is
// longer and contains data in the suffix
cipher(testDst, buff[endOfPrefix:]) // Input long src
if !bytes.Equal(testDst, expectedDst) {
t.Errorf("block cipher affected by src data beyond BlockSize bytes; got %x, want %x", buff[startOfSuffix:], expectedDst)
}
})
t.Run("NonZeroDst", func(t *testing.T) {
rng := newRandReader(t)
// Record what the cipher writes into a destination of zeroes
src := make([]byte, blockSize)
rng.Read(src)
expectedDst := make([]byte, blockSize)
cipher(expectedDst, src)
// Make nonzero dst
dst := make([]byte, blockSize*2)
rng.Read(dst)
// Remember the random suffix which shouldn't be written to
expectedDst = append(expectedDst, dst[blockSize:]...)
cipher(dst, src)
if !bytes.Equal(dst, expectedDst) {
t.Errorf("block cipher behavior differs when given non-zero dst; got %x, want %x", dst, expectedDst)
}
})
t.Run("BufferOverlap", func(t *testing.T) {
rng := newRandReader(t)
buff := make([]byte, blockSize*2)
rng.Read((buff))
// Make src and dst slices point to same array with inexact overlap
src := buff[:blockSize]
dst := buff[1 : blockSize+1]
mustPanic(t, "invalid buffer overlap", func() { cipher(dst, src) })
// Only overlap on one byte
src = buff[:blockSize]
dst = buff[blockSize-1 : 2*blockSize-1]
mustPanic(t, "invalid buffer overlap", func() { cipher(dst, src) })
// src comes after dst with one byte overlap
src = buff[blockSize-1 : 2*blockSize-1]
dst = buff[:blockSize]
mustPanic(t, "invalid buffer overlap", func() { cipher(dst, src) })
})
// Test short input/output.
// Assembly used to not notice.
// See issue 7928.
t.Run("ShortBlock", func(t *testing.T) {
// Returns slice of n bytes of an n+1 length array. Lets us test that a
// slice is still considered too short even if the underlying array it
// points to is large enough
byteSlice := func(n int) []byte { return make([]byte, n+1)[0:n] }
// Off by one byte
mustPanic(t, "input not full block", func() { cipher(byteSlice(blockSize), byteSlice(blockSize-1)) })
mustPanic(t, "output not full block", func() { cipher(byteSlice(blockSize-1), byteSlice(blockSize)) })
// Small slices
mustPanic(t, "input not full block", func() { cipher(byteSlice(1), byteSlice(1)) })
mustPanic(t, "input not full block", func() { cipher(byteSlice(100), byteSlice(1)) })
mustPanic(t, "output not full block", func() { cipher(byteSlice(1), byteSlice(100)) })
})
}
func mustPanic(t *testing.T, msg string, f func()) {
t.Helper()
defer func() {
t.Helper()
err := recover()
if err == nil {
t.Errorf("function did not panic for %q", msg)
}
}()
f()
}