Source file src/crypto/internal/cryptotest/blockmode.go

     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  // MakeBlockMode returns a cipher.BlockMode instance.
    14  // It expects len(iv) == b.BlockSize().
    15  type MakeBlockMode func(b cipher.Block, iv []byte) cipher.BlockMode
    16  
    17  // TestBlockMode performs a set of tests on cipher.BlockMode implementations,
    18  // checking the documented requirements of CryptBlocks.
    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("EmptyInput", func(t *testing.T) {
    64  		rng := newRandReader(t)
    65  
    66  		src, dst := make([]byte, blockSize), make([]byte, blockSize)
    67  		rng.Read(dst)
    68  		before := bytes.Clone(dst)
    69  
    70  		bm(b, iv).CryptBlocks(dst, src[:0])
    71  		if !bytes.Equal(dst, before) {
    72  			t.Errorf("CryptBlocks modified dst on empty input; got %x, want %x", dst, before)
    73  		}
    74  	})
    75  
    76  	t.Run("AlterInput", func(t *testing.T) {
    77  		rng := newRandReader(t)
    78  
    79  		src, dst, before := make([]byte, blockSize*2), make([]byte, blockSize*2), make([]byte, blockSize*2)
    80  
    81  		for _, length := range []int{0, blockSize, blockSize * 2} {
    82  			rng.Read(src)
    83  			copy(before, src)
    84  
    85  			bm(b, iv).CryptBlocks(dst[:length], src[:length])
    86  			if !bytes.Equal(src, before) {
    87  				t.Errorf("CryptBlocks modified src; got %x, want %x", src, before)
    88  			}
    89  		}
    90  	})
    91  
    92  	t.Run("Aliasing", func(t *testing.T) {
    93  		rng := newRandReader(t)
    94  
    95  		buff, expectedOutput := make([]byte, blockSize*2), make([]byte, blockSize*2)
    96  
    97  		for _, length := range []int{0, blockSize, blockSize * 2} {
    98  			// Record what output is when src and dst are different
    99  			rng.Read(buff)
   100  			bm(b, iv).CryptBlocks(expectedOutput[:length], buff[:length])
   101  
   102  			// Check that the same output is generated when src=dst alias to the same
   103  			// memory
   104  			bm(b, iv).CryptBlocks(buff[:length], buff[:length])
   105  			if !bytes.Equal(buff[:length], expectedOutput[:length]) {
   106  				t.Errorf("block cipher produced different output when dst = src; got %x, want %x", buff[:length], expectedOutput[:length])
   107  			}
   108  		}
   109  	})
   110  
   111  	t.Run("OutOfBoundsWrite", func(t *testing.T) { // Issue 21104
   112  		rng := newRandReader(t)
   113  
   114  		src := make([]byte, blockSize)
   115  		rng.Read(src)
   116  
   117  		// Make a buffer with dst in the middle and data on either end
   118  		buff := make([]byte, blockSize*3)
   119  		endOfPrefix, startOfSuffix := blockSize, blockSize*2
   120  		rng.Read(buff[:endOfPrefix])
   121  		rng.Read(buff[startOfSuffix:])
   122  		dst := buff[endOfPrefix:startOfSuffix]
   123  
   124  		// Record the prefix and suffix data to make sure they aren't written to
   125  		initPrefix, initSuffix := make([]byte, blockSize), make([]byte, blockSize)
   126  		copy(initPrefix, buff[:endOfPrefix])
   127  		copy(initSuffix, buff[startOfSuffix:])
   128  
   129  		// Write to dst (the middle of the buffer) and make sure it doesn't write
   130  		// beyond the dst slice on a valid CryptBlocks call
   131  		bm(b, iv).CryptBlocks(dst, src)
   132  		if !bytes.Equal(buff[startOfSuffix:], initSuffix) {
   133  			t.Errorf("block cipher did out of bounds write after end of dst slice; got %x, want %x", buff[startOfSuffix:], initSuffix)
   134  		}
   135  		if !bytes.Equal(buff[:endOfPrefix], initPrefix) {
   136  			t.Errorf("block cipher did out of bounds write before beginning of dst slice; got %x, want %x", buff[:endOfPrefix], initPrefix)
   137  		}
   138  
   139  		// Check that dst isn't written to beyond len(src) even if there is room in
   140  		// the slice
   141  		dst = buff[endOfPrefix:] // Extend dst to include suffix
   142  		bm(b, iv).CryptBlocks(dst, src)
   143  		if !bytes.Equal(buff[startOfSuffix:], initSuffix) {
   144  			t.Errorf("CryptBlocks modified dst past len(src); got %x, want %x", buff[startOfSuffix:], initSuffix)
   145  		}
   146  
   147  		// Issue 21104: Shouldn't write to anything outside of dst even if src is bigger
   148  		src = make([]byte, blockSize*3)
   149  		rng.Read(src)
   150  
   151  		mustPanic(t, "output smaller than input", func() {
   152  			bm(b, iv).CryptBlocks(dst, src)
   153  		})
   154  
   155  		if !bytes.Equal(buff[startOfSuffix:], initSuffix) {
   156  			t.Errorf("block cipher did out of bounds write after end of dst slice; got %x, want %x", buff[startOfSuffix:], initSuffix)
   157  		}
   158  		if !bytes.Equal(buff[:endOfPrefix], initPrefix) {
   159  			t.Errorf("block cipher did out of bounds write before beginning of dst slice; got %x, want %x", buff[:endOfPrefix], initPrefix)
   160  		}
   161  	})
   162  
   163  	// Check that output of cipher isn't affected by adjacent data beyond input
   164  	// slice scope
   165  	t.Run("OutOfBoundsRead", func(t *testing.T) {
   166  		rng := newRandReader(t)
   167  
   168  		src := make([]byte, blockSize)
   169  		rng.Read(src)
   170  		expectedDst := make([]byte, blockSize)
   171  		bm(b, iv).CryptBlocks(expectedDst, src)
   172  
   173  		// Make a buffer with src in the middle and data on either end
   174  		buff := make([]byte, blockSize*3)
   175  		endOfPrefix, startOfSuffix := blockSize, blockSize*2
   176  
   177  		copy(buff[endOfPrefix:startOfSuffix], src)
   178  		rng.Read(buff[:endOfPrefix])
   179  		rng.Read(buff[startOfSuffix:])
   180  
   181  		testDst := make([]byte, blockSize)
   182  		bm(b, iv).CryptBlocks(testDst, buff[endOfPrefix:startOfSuffix])
   183  
   184  		if !bytes.Equal(testDst, expectedDst) {
   185  			t.Errorf("CryptBlocks affected by data outside of src slice bounds; got %x, want %x", testDst, expectedDst)
   186  		}
   187  	})
   188  
   189  	t.Run("BufferOverlap", func(t *testing.T) {
   190  		rng := newRandReader(t)
   191  
   192  		buff := make([]byte, blockSize*2)
   193  		rng.Read(buff)
   194  
   195  		// Make src and dst slices point to same array with inexact overlap
   196  		src := buff[:blockSize]
   197  		dst := buff[1 : blockSize+1]
   198  		mustPanic(t, "invalid buffer overlap", func() { bm(b, iv).CryptBlocks(dst, src) })
   199  
   200  		// Only overlap on one byte
   201  		src = buff[:blockSize]
   202  		dst = buff[blockSize-1 : 2*blockSize-1]
   203  		mustPanic(t, "invalid buffer overlap", func() { bm(b, iv).CryptBlocks(dst, src) })
   204  
   205  		// src comes after dst with one byte overlap
   206  		src = buff[blockSize-1 : 2*blockSize-1]
   207  		dst = buff[:blockSize]
   208  		mustPanic(t, "invalid buffer overlap", func() { bm(b, iv).CryptBlocks(dst, src) })
   209  	})
   210  
   211  	// Input to CryptBlocks should be a multiple of BlockSize
   212  	t.Run("PartialBlocks", func(t *testing.T) {
   213  		// Check a few cases of not being a multiple of BlockSize
   214  		for _, srcSize := range []int{blockSize - 1, blockSize + 1, 2*blockSize - 1, 2*blockSize + 1} {
   215  			src := make([]byte, srcSize)
   216  			dst := make([]byte, 3*blockSize) // Make a dst large enough for all src
   217  			mustPanic(t, "input not full blocks", func() { bm(b, iv).CryptBlocks(dst, src) })
   218  		}
   219  	})
   220  
   221  	t.Run("KeepState", func(t *testing.T) {
   222  		rng := newRandReader(t)
   223  
   224  		src, serialDst, compositeDst := make([]byte, blockSize*4), make([]byte, blockSize*4), make([]byte, blockSize*4)
   225  		rng.Read(src)
   226  
   227  		length, block := 2*blockSize, bm(b, iv)
   228  		block.CryptBlocks(serialDst, src[:length])
   229  		block.CryptBlocks(serialDst[length:], src[length:])
   230  
   231  		bm(b, iv).CryptBlocks(compositeDst, src)
   232  
   233  		if !bytes.Equal(serialDst, compositeDst) {
   234  			t.Errorf("two successive CryptBlocks calls returned a different result than a single one; got %x, want %x", serialDst, compositeDst)
   235  		}
   236  	})
   237  }
   238  

View as plain text