mirror of
https://github.com/emmansun/gmsm.git
synced 2025-04-25 19:56:18 +08:00
255 lines
7.8 KiB
Go
255 lines
7.8 KiB
Go
// 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()
|
|
}
|