github.com/emmansun/gmsm@v0.29.1/internal/cryptotest/block.go (about) 1 // Copyright 2024 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package cryptotest 6 7 import ( 8 "bytes" 9 "crypto/cipher" 10 "testing" 11 ) 12 13 type MakeBlock func(key []byte) (cipher.Block, error) 14 15 // TestBlock performs a set of tests on cipher.Block implementations, checking 16 // the documented requirements of BlockSize, Encrypt, and Decrypt. 17 func TestBlock(t *testing.T, keySize int, mb MakeBlock) { 18 // Generate random key 19 key := make([]byte, keySize) 20 newRandReader(t).Read(key) 21 t.Logf("Cipher key: 0x%x", key) 22 23 block, err := mb(key) 24 if err != nil { 25 t.Fatal(err) 26 } 27 28 blockSize := block.BlockSize() 29 30 t.Run("Encryption", func(t *testing.T) { 31 testCipher(t, block.Encrypt, blockSize) 32 }) 33 34 t.Run("Decryption", func(t *testing.T) { 35 testCipher(t, block.Decrypt, blockSize) 36 }) 37 38 // Checks baseline Encrypt/Decrypt functionality. More thorough 39 // implementation-specific characterization/golden tests should be done 40 // for each block cipher implementation. 41 t.Run("Roundtrip", func(t *testing.T) { 42 rng := newRandReader(t) 43 44 // Check Decrypt inverts Encrypt 45 before, ciphertext, after := make([]byte, blockSize), make([]byte, blockSize), make([]byte, blockSize) 46 47 rng.Read(before) 48 49 block.Encrypt(ciphertext, before) 50 block.Decrypt(after, ciphertext) 51 52 if !bytes.Equal(after, before) { 53 t.Errorf("plaintext is different after an encrypt/decrypt cycle; got %x, want %x", after, before) 54 } 55 56 // Check Encrypt inverts Decrypt (assumes block ciphers are deterministic) 57 before, plaintext, after := make([]byte, blockSize), make([]byte, blockSize), make([]byte, blockSize) 58 59 rng.Read(before) 60 61 block.Decrypt(plaintext, before) 62 block.Encrypt(after, plaintext) 63 64 if !bytes.Equal(after, before) { 65 t.Errorf("ciphertext is different after a decrypt/encrypt cycle; got %x, want %x", after, before) 66 } 67 }) 68 69 } 70 71 func testCipher(t *testing.T, cipher func(dst, src []byte), blockSize int) { 72 t.Run("AlterInput", func(t *testing.T) { 73 rng := newRandReader(t) 74 75 // Make long src that shouldn't be modified at all, within block 76 // size scope or beyond it 77 src, before := make([]byte, blockSize*2), make([]byte, blockSize*2) 78 rng.Read(src) 79 copy(before, src) 80 81 dst := make([]byte, blockSize) 82 83 cipher(dst, src) 84 if !bytes.Equal(src, before) { 85 t.Errorf("block cipher modified src; got %x, want %x", src, before) 86 } 87 }) 88 89 t.Run("Aliasing", func(t *testing.T) { 90 rng := newRandReader(t) 91 92 buff, expectedOutput := make([]byte, blockSize), make([]byte, blockSize) 93 94 // Record what output is when src and dst are different 95 rng.Read(buff) 96 cipher(expectedOutput, buff) 97 98 // Check that the same output is generated when src=dst alias to the same 99 // memory 100 cipher(buff, buff) 101 if !bytes.Equal(buff, expectedOutput) { 102 t.Errorf("block cipher produced different output when dst = src; got %x, want %x", buff, expectedOutput) 103 } 104 }) 105 106 t.Run("OutOfBoundsWrite", func(t *testing.T) { 107 rng := newRandReader(t) 108 109 src := make([]byte, blockSize) 110 rng.Read(src) 111 112 // Make a buffer with dst in the middle and data on either end 113 buff := make([]byte, blockSize*3) 114 endOfPrefix, startOfSuffix := blockSize, blockSize*2 115 rng.Read(buff[:endOfPrefix]) 116 rng.Read(buff[startOfSuffix:]) 117 dst := buff[endOfPrefix:startOfSuffix] 118 119 // Record the prefix and suffix data to make sure they aren't written to 120 initPrefix, initSuffix := make([]byte, blockSize), make([]byte, blockSize) 121 copy(initPrefix, buff[:endOfPrefix]) 122 copy(initSuffix, buff[startOfSuffix:]) 123 124 // Write to dst (the middle of the buffer) and make sure it doesn't write 125 // beyond the dst slice 126 cipher(dst, src) 127 if !bytes.Equal(buff[startOfSuffix:], initSuffix) { 128 t.Errorf("block cipher did out of bounds write after end of dst slice; got %x, want %x", buff[startOfSuffix:], initSuffix) 129 } 130 if !bytes.Equal(buff[:endOfPrefix], initPrefix) { 131 t.Errorf("block cipher did out of bounds write before beginning of dst slice; got %x, want %x", buff[:endOfPrefix], initPrefix) 132 } 133 134 // Check that dst isn't written to beyond BlockSize even if there is room 135 // in the slice 136 dst = buff[endOfPrefix:] // Extend dst to include suffix 137 cipher(dst, src) 138 if !bytes.Equal(buff[startOfSuffix:], initSuffix) { 139 t.Errorf("block cipher modified dst past BlockSize bytes; got %x, want %x", buff[startOfSuffix:], initSuffix) 140 } 141 }) 142 143 // Check that output of cipher isn't affected by adjacent data beyond input 144 // slice scope 145 // For encryption, this assumes block ciphers encrypt deterministically 146 t.Run("OutOfBoundsRead", func(t *testing.T) { 147 rng := newRandReader(t) 148 149 src := make([]byte, blockSize) 150 rng.Read(src) 151 expectedDst := make([]byte, blockSize) 152 cipher(expectedDst, src) 153 154 // Make a buffer with src in the middle and data on either end 155 buff := make([]byte, blockSize*3) 156 endOfPrefix, startOfSuffix := blockSize, blockSize*2 157 158 copy(buff[endOfPrefix:startOfSuffix], src) 159 rng.Read(buff[:endOfPrefix]) 160 rng.Read(buff[startOfSuffix:]) 161 162 testDst := make([]byte, blockSize) 163 cipher(testDst, buff[endOfPrefix:startOfSuffix]) 164 if !bytes.Equal(testDst, expectedDst) { 165 t.Errorf("block cipher affected by data outside of src slice bounds; got %x, want %x", testDst, expectedDst) 166 } 167 168 // Check that src isn't read from beyond BlockSize even if the slice is 169 // longer and contains data in the suffix 170 cipher(testDst, buff[endOfPrefix:]) // Input long src 171 if !bytes.Equal(testDst, expectedDst) { 172 t.Errorf("block cipher affected by src data beyond BlockSize bytes; got %x, want %x", buff[startOfSuffix:], expectedDst) 173 } 174 }) 175 176 t.Run("NonZeroDst", func(t *testing.T) { 177 rng := newRandReader(t) 178 179 // Record what the cipher writes into a destination of zeroes 180 src := make([]byte, blockSize) 181 rng.Read(src) 182 expectedDst := make([]byte, blockSize) 183 184 cipher(expectedDst, src) 185 186 // Make nonzero dst 187 dst := make([]byte, blockSize*2) 188 rng.Read(dst) 189 190 // Remember the random suffix which shouldn't be written to 191 expectedDst = append(expectedDst, dst[blockSize:]...) 192 193 cipher(dst, src) 194 if !bytes.Equal(dst, expectedDst) { 195 t.Errorf("block cipher behavior differs when given non-zero dst; got %x, want %x", dst, expectedDst) 196 } 197 }) 198 199 t.Run("BufferOverlap", func(t *testing.T) { 200 rng := newRandReader(t) 201 202 buff := make([]byte, blockSize*2) 203 rng.Read((buff)) 204 205 // Make src and dst slices point to same array with inexact overlap 206 src := buff[:blockSize] 207 dst := buff[1 : blockSize+1] 208 mustPanic(t, "invalid buffer overlap", func() { cipher(dst, src) }) 209 210 // Only overlap on one byte 211 src = buff[:blockSize] 212 dst = buff[blockSize-1 : 2*blockSize-1] 213 mustPanic(t, "invalid buffer overlap", func() { cipher(dst, src) }) 214 215 // src comes after dst with one byte overlap 216 src = buff[blockSize-1 : 2*blockSize-1] 217 dst = buff[:blockSize] 218 mustPanic(t, "invalid buffer overlap", func() { cipher(dst, src) }) 219 }) 220 221 // Test short input/output. 222 // Assembly used to not notice. 223 // See issue 7928. 224 t.Run("ShortBlock", func(t *testing.T) { 225 // Returns slice of n bytes of an n+1 length array. Lets us test that a 226 // slice is still considered too short even if the underlying array it 227 // points to is large enough 228 byteSlice := func(n int) []byte { return make([]byte, n+1)[0:n] } 229 230 // Off by one byte 231 mustPanic(t, "input not full block", func() { cipher(byteSlice(blockSize), byteSlice(blockSize-1)) }) 232 mustPanic(t, "output not full block", func() { cipher(byteSlice(blockSize-1), byteSlice(blockSize)) }) 233 234 // Small slices 235 mustPanic(t, "input not full block", func() { cipher(byteSlice(1), byteSlice(1)) }) 236 mustPanic(t, "input not full block", func() { cipher(byteSlice(100), byteSlice(1)) }) 237 mustPanic(t, "output not full block", func() { cipher(byteSlice(1), byteSlice(100)) }) 238 }) 239 } 240 241 func mustPanic(t *testing.T, msg string, f func()) { 242 t.Helper() 243 244 defer func() { 245 t.Helper() 246 247 err := recover() 248 249 if err == nil { 250 t.Errorf("function did not panic for %q", msg) 251 } 252 }() 253 f() 254 }