Source file src/crypto/internal/cryptotest/stream.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  	"crypto/subtle"
    11  	"fmt"
    12  	"strings"
    13  	"testing"
    14  )
    15  
    16  // Each test is executed with each of the buffer lengths in bufLens.
    17  var (
    18  	bufLens = []int{0, 1, 3, 4, 8, 10, 15, 16, 20, 32, 50, 4096, 5000}
    19  	bufCap  = 10000
    20  )
    21  
    22  // MakeStream returns a cipher.Stream instance.
    23  //
    24  // Multiple calls to MakeStream must return equivalent instances,
    25  // so for example the key and/or IV must be fixed.
    26  type MakeStream func() cipher.Stream
    27  
    28  // TestStream performs a set of tests on cipher.Stream implementations,
    29  // checking the documented requirements of XORKeyStream.
    30  func TestStream(t *testing.T, ms MakeStream) {
    31  
    32  	t.Run("XORSemantics", func(t *testing.T) {
    33  		if strings.Contains(t.Name(), "TestCFBStream") {
    34  			// This is ugly, but so is CFB's abuse of cipher.Stream.
    35  			// Don't want to make it easier for anyone else to do that.
    36  			t.Skip("CFB implements cipher.Stream but does not follow XOR semantics")
    37  		}
    38  
    39  		// Test that XORKeyStream inverts itself for encryption/decryption.
    40  		t.Run("Roundtrip", func(t *testing.T) {
    41  
    42  			for _, length := range bufLens {
    43  				t.Run(fmt.Sprintf("BuffLength=%d", length), func(t *testing.T) {
    44  					rng := newRandReader(t)
    45  
    46  					plaintext := make([]byte, length)
    47  					rng.Read(plaintext)
    48  
    49  					ciphertext := make([]byte, length)
    50  					decrypted := make([]byte, length)
    51  
    52  					ms().XORKeyStream(ciphertext, plaintext) // Encrypt plaintext
    53  					ms().XORKeyStream(decrypted, ciphertext) // Decrypt ciphertext
    54  					if !bytes.Equal(decrypted, plaintext) {
    55  						t.Errorf("plaintext is different after an encrypt/decrypt cycle; got %s, want %s", truncateHex(decrypted), truncateHex(plaintext))
    56  					}
    57  				})
    58  			}
    59  		})
    60  
    61  		// Test that XORKeyStream behaves the same as directly XORing
    62  		// plaintext with the stream.
    63  		t.Run("DirectXOR", func(t *testing.T) {
    64  
    65  			for _, length := range bufLens {
    66  				t.Run(fmt.Sprintf("BuffLength=%d", length), func(t *testing.T) {
    67  					rng := newRandReader(t)
    68  
    69  					plaintext := make([]byte, length)
    70  					rng.Read(plaintext)
    71  
    72  					// Encrypting all zeros should reveal the stream itself
    73  					stream, directXOR := make([]byte, length), make([]byte, length)
    74  					ms().XORKeyStream(stream, stream)
    75  					// Encrypt plaintext by directly XORing the stream
    76  					subtle.XORBytes(directXOR, stream, plaintext)
    77  
    78  					// Encrypt plaintext with XORKeyStream
    79  					ciphertext := make([]byte, length)
    80  					ms().XORKeyStream(ciphertext, plaintext)
    81  					if !bytes.Equal(ciphertext, directXOR) {
    82  						t.Errorf("xor semantics were not preserved; got %s, want %s", truncateHex(ciphertext), truncateHex(directXOR))
    83  					}
    84  				})
    85  			}
    86  		})
    87  	})
    88  
    89  	t.Run("AlterInput", func(t *testing.T) {
    90  		rng := newRandReader(t)
    91  		src, dst, before := make([]byte, bufCap), make([]byte, bufCap), make([]byte, bufCap)
    92  		rng.Read(src)
    93  
    94  		for _, length := range bufLens {
    95  
    96  			t.Run(fmt.Sprintf("BuffLength=%d", length), func(t *testing.T) {
    97  				copy(before, src)
    98  
    99  				ms().XORKeyStream(dst[:length], src[:length])
   100  				if !bytes.Equal(src, before) {
   101  					t.Errorf("XORKeyStream modified src; got %s, want %s", truncateHex(src), truncateHex(before))
   102  				}
   103  			})
   104  		}
   105  	})
   106  
   107  	t.Run("Aliasing", func(t *testing.T) {
   108  		rng := newRandReader(t)
   109  
   110  		buff, expectedOutput := make([]byte, bufCap), make([]byte, bufCap)
   111  
   112  		for _, length := range bufLens {
   113  			// Record what output is when src and dst are different
   114  			rng.Read(buff)
   115  			ms().XORKeyStream(expectedOutput[:length], buff[:length])
   116  
   117  			// Check that the same output is generated when src=dst alias to the same
   118  			// memory
   119  			ms().XORKeyStream(buff[:length], buff[:length])
   120  			if !bytes.Equal(buff[:length], expectedOutput[:length]) {
   121  				t.Errorf("block cipher produced different output when dst = src; got %x, want %x", buff[:length], expectedOutput[:length])
   122  			}
   123  		}
   124  	})
   125  
   126  	t.Run("OutOfBoundsWrite", func(t *testing.T) { // Issue 21104
   127  		rng := newRandReader(t)
   128  
   129  		plaintext := make([]byte, bufCap)
   130  		rng.Read(plaintext)
   131  		ciphertext := make([]byte, bufCap)
   132  
   133  		for _, length := range bufLens {
   134  			copy(ciphertext, plaintext) // Reset ciphertext buffer
   135  
   136  			t.Run(fmt.Sprintf("BuffLength=%d", length), func(t *testing.T) {
   137  				mustPanic(t, "output smaller than input", func() { ms().XORKeyStream(ciphertext[:length], plaintext) })
   138  
   139  				if !bytes.Equal(ciphertext[length:], plaintext[length:]) {
   140  					t.Errorf("XORKeyStream did out of bounds write; got %s, want %s", truncateHex(ciphertext[length:]), truncateHex(plaintext[length:]))
   141  				}
   142  			})
   143  		}
   144  	})
   145  
   146  	t.Run("BufferOverlap", func(t *testing.T) {
   147  		rng := newRandReader(t)
   148  
   149  		buff := make([]byte, bufCap)
   150  		rng.Read(buff)
   151  
   152  		for _, length := range bufLens {
   153  			if length == 0 || length == 1 {
   154  				continue
   155  			}
   156  
   157  			t.Run(fmt.Sprintf("BuffLength=%d", length), func(t *testing.T) {
   158  				// Make src and dst slices point to same array with inexact overlap
   159  				src := buff[:length]
   160  				dst := buff[1 : length+1]
   161  				mustPanic(t, "invalid buffer overlap", func() { ms().XORKeyStream(dst, src) })
   162  
   163  				// Only overlap on one byte
   164  				src = buff[:length]
   165  				dst = buff[length-1 : 2*length-1]
   166  				mustPanic(t, "invalid buffer overlap", func() { ms().XORKeyStream(dst, src) })
   167  
   168  				// src comes after dst with one byte overlap
   169  				src = buff[length-1 : 2*length-1]
   170  				dst = buff[:length]
   171  				mustPanic(t, "invalid buffer overlap", func() { ms().XORKeyStream(dst, src) })
   172  			})
   173  		}
   174  	})
   175  
   176  	t.Run("KeepState", func(t *testing.T) {
   177  		rng := newRandReader(t)
   178  
   179  		plaintext := make([]byte, bufCap)
   180  		rng.Read(plaintext)
   181  		ciphertext := make([]byte, bufCap)
   182  
   183  		// Make one long call to XORKeyStream
   184  		ms().XORKeyStream(ciphertext, plaintext)
   185  
   186  		for _, step := range bufLens {
   187  			if step == 0 {
   188  				continue
   189  			}
   190  			stepMsg := fmt.Sprintf("step %d: ", step)
   191  
   192  			dst := make([]byte, bufCap)
   193  
   194  			// Make a bunch of small calls to (stateful) XORKeyStream
   195  			stream := ms()
   196  			i := 0
   197  			for i+step < len(plaintext) {
   198  				stream.XORKeyStream(dst[i:], plaintext[i:i+step])
   199  				i += step
   200  			}
   201  			stream.XORKeyStream(dst[i:], plaintext[i:])
   202  
   203  			if !bytes.Equal(dst, ciphertext) {
   204  				t.Errorf(stepMsg+"successive XORKeyStream calls returned a different result than a single one; got %s, want %s", truncateHex(dst), truncateHex(ciphertext))
   205  			}
   206  		}
   207  	})
   208  }
   209  
   210  // TestStreamFromBlock creates a Stream from a cipher.Block used in a
   211  // cipher.BlockMode. It addresses Issue 68377 by checking for a panic when the
   212  // BlockMode uses an IV with incorrect length.
   213  // For a valid IV, it also runs all TestStream tests on the resulting stream.
   214  func TestStreamFromBlock(t *testing.T, block cipher.Block, blockMode func(b cipher.Block, iv []byte) cipher.Stream) {
   215  
   216  	t.Run("WrongIVLen", func(t *testing.T) {
   217  		t.Skip("see Issue 68377")
   218  
   219  		rng := newRandReader(t)
   220  		iv := make([]byte, block.BlockSize()+1)
   221  		rng.Read(iv)
   222  		mustPanic(t, "IV length must equal block size", func() { blockMode(block, iv) })
   223  	})
   224  
   225  	t.Run("BlockModeStream", func(t *testing.T) {
   226  		rng := newRandReader(t)
   227  		iv := make([]byte, block.BlockSize())
   228  		rng.Read(iv)
   229  
   230  		TestStream(t, func() cipher.Stream { return blockMode(block, iv) })
   231  	})
   232  }
   233  
   234  func truncateHex(b []byte) string {
   235  	numVals := 50
   236  
   237  	if len(b) <= numVals {
   238  		return fmt.Sprintf("%x", b)
   239  	}
   240  	return fmt.Sprintf("%x...", b[:numVals])
   241  }
   242  

View as plain text