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("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  			// Record what output is when src and dst are different
    86  			rng.Read(buff)
    87  			bm(b, iv).CryptBlocks(expectedOutput[:length], buff[:length])
    88  
    89  			// Check that the same output is generated when src=dst alias to the same
    90  			// memory
    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) { // Issue 21104
    99  		rng := newRandReader(t)
   100  
   101  		src := make([]byte, blockSize)
   102  		rng.Read(src)
   103  
   104  		// Make a buffer with dst in the middle and data on either end
   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  		// Record the prefix and suffix data to make sure they aren't written to
   112  		initPrefix, initSuffix := make([]byte, blockSize), make([]byte, blockSize)
   113  		copy(initPrefix, buff[:endOfPrefix])
   114  		copy(initSuffix, buff[startOfSuffix:])
   115  
   116  		// Write to dst (the middle of the buffer) and make sure it doesn't write
   117  		// beyond the dst slice on a valid CryptBlocks call
   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  		// Check that dst isn't written to beyond len(src) even if there is room in
   127  		// the slice
   128  		dst = buff[endOfPrefix:] // Extend dst to include suffix
   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  		// Issue 21104: Shouldn't write to anything outside of dst even if src is bigger
   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  	// Check that output of cipher isn't affected by adjacent data beyond input
   151  	// slice scope
   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  		// Make a buffer with src in the middle and data on either end
   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  		// Make src and dst slices point to same array with inexact overlap
   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  		// Only overlap on one byte
   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  		// src comes after dst with one byte overlap
   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  	// Input to CryptBlocks should be a multiple of BlockSize
   199  	t.Run("PartialBlocks", func(t *testing.T) {
   200  		// Check a few cases of not being a multiple of BlockSize
   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) // Make a dst large enough for all src
   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