Source file src/crypto/internal/cryptotest/hash.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  	"hash"
    10  	"io"
    11  	"math/rand"
    12  	"testing"
    13  	"time"
    14  )
    15  
    16  type MakeHash func() hash.Hash
    17  
    18  // TestHash performs a set of tests on hash.Hash implementations, checking the
    19  // documented requirements of Write, Sum, Reset, Size, and BlockSize.
    20  func TestHash(t *testing.T, mh MakeHash) {
    21  
    22  	// Test that Sum returns an appended digest matching output of Size
    23  	t.Run("SumAppend", func(t *testing.T) {
    24  		h := mh()
    25  		rng := newRandReader(t)
    26  
    27  		emptyBuff := []byte("")
    28  		shortBuff := []byte("a")
    29  		longBuff := make([]byte, h.BlockSize()+1)
    30  		rng.Read(longBuff)
    31  
    32  		// Set of example strings to append digest to
    33  		prefixes := [][]byte{nil, emptyBuff, shortBuff, longBuff}
    34  
    35  		// Go to each string and check digest gets appended to and is correct size.
    36  		for _, prefix := range prefixes {
    37  			h.Reset()
    38  
    39  			sum := getSum(t, h, prefix) // Append new digest to prefix
    40  
    41  			// Check that Sum didn't alter the prefix
    42  			if !bytes.Equal(sum[0:len(prefix)], prefix) {
    43  				t.Errorf("Sum alters passed buffer instead of appending; got %x, want %x", sum[0:len(prefix)], prefix)
    44  			}
    45  
    46  			// Check that the appended sum wasn't affected by the prefix
    47  			if expectedSum := getSum(t, h, nil); !bytes.Equal(sum[len(prefix):], expectedSum) {
    48  				t.Errorf("Sum behavior affected by data in the input buffer; got %x, want %x", sum[len(prefix):], expectedSum)
    49  			}
    50  
    51  			// Check size of append
    52  			if got, want := len(sum)-len(prefix), h.Size(); got != want {
    53  				t.Errorf("Sum appends number of bytes != Size; got %v , want %v", got, want)
    54  			}
    55  		}
    56  	})
    57  
    58  	// Test that Hash.Write never returns error.
    59  	t.Run("WriteWithoutError", func(t *testing.T) {
    60  		h := mh()
    61  		rng := newRandReader(t)
    62  
    63  		emptySlice := []byte("")
    64  		shortSlice := []byte("a")
    65  		longSlice := make([]byte, h.BlockSize()+1)
    66  		rng.Read(longSlice)
    67  
    68  		// Set of example strings to append digest to
    69  		slices := [][]byte{emptySlice, shortSlice, longSlice}
    70  
    71  		for _, slice := range slices {
    72  			writeToHash(t, h, slice) // Writes and checks Write doesn't error
    73  		}
    74  	})
    75  
    76  	t.Run("ResetState", func(t *testing.T) {
    77  		h := mh()
    78  		rng := newRandReader(t)
    79  
    80  		emptySum := getSum(t, h, nil)
    81  
    82  		// Write to hash and then Reset it and see if Sum is same as emptySum
    83  		writeEx := make([]byte, h.BlockSize())
    84  		rng.Read(writeEx)
    85  		writeToHash(t, h, writeEx)
    86  		h.Reset()
    87  		resetSum := getSum(t, h, nil)
    88  
    89  		if !bytes.Equal(emptySum, resetSum) {
    90  			t.Errorf("Reset hash yields different Sum than new hash; got %x, want %x", emptySum, resetSum)
    91  		}
    92  	})
    93  
    94  	// Check that Write isn't reading from beyond input slice's bounds
    95  	t.Run("OutOfBoundsRead", func(t *testing.T) {
    96  		h := mh()
    97  		blockSize := h.BlockSize()
    98  		rng := newRandReader(t)
    99  
   100  		msg := make([]byte, blockSize)
   101  		rng.Read(msg)
   102  		writeToHash(t, h, msg)
   103  		expectedDigest := getSum(t, h, nil) // Record control digest
   104  
   105  		h.Reset()
   106  
   107  		// Make a buffer with msg in the middle and data on either end
   108  		buff := make([]byte, blockSize*3)
   109  		endOfPrefix, startOfSuffix := blockSize, blockSize*2
   110  
   111  		copy(buff[endOfPrefix:startOfSuffix], msg)
   112  		rng.Read(buff[:endOfPrefix])
   113  		rng.Read(buff[startOfSuffix:])
   114  
   115  		writeToHash(t, h, buff[endOfPrefix:startOfSuffix])
   116  		testDigest := getSum(t, h, nil)
   117  
   118  		if !bytes.Equal(testDigest, expectedDigest) {
   119  			t.Errorf("Write affected by data outside of input slice bounds; got %x, want %x", testDigest, expectedDigest)
   120  		}
   121  	})
   122  
   123  	// Test that multiple calls to Write is stateful
   124  	t.Run("StatefulWrite", func(t *testing.T) {
   125  		h := mh()
   126  		rng := newRandReader(t)
   127  
   128  		prefix, suffix := make([]byte, h.BlockSize()), make([]byte, h.BlockSize())
   129  		rng.Read(prefix)
   130  		rng.Read(suffix)
   131  
   132  		// Write prefix then suffix sequentially and record resulting hash
   133  		writeToHash(t, h, prefix)
   134  		writeToHash(t, h, suffix)
   135  		serialSum := getSum(t, h, nil)
   136  
   137  		h.Reset()
   138  
   139  		// Write prefix and suffix at the same time and record resulting hash
   140  		writeToHash(t, h, append(prefix, suffix...))
   141  		compositeSum := getSum(t, h, nil)
   142  
   143  		// Check that sequential writing results in the same as writing all at once
   144  		if !bytes.Equal(compositeSum, serialSum) {
   145  			t.Errorf("two successive Write calls resulted in a different Sum than a single one; got %x, want %x", compositeSum, serialSum)
   146  		}
   147  	})
   148  }
   149  
   150  // Helper function for writing. Verifies that Write does not error.
   151  func writeToHash(t *testing.T, h hash.Hash, p []byte) {
   152  	t.Helper()
   153  
   154  	before := make([]byte, len(p))
   155  	copy(before, p)
   156  
   157  	n, err := h.Write(p)
   158  	if err != nil || n != len(p) {
   159  		t.Errorf("Write returned error; got (%v, %v), want (nil, %v)", err, n, len(p))
   160  	}
   161  
   162  	if !bytes.Equal(p, before) {
   163  		t.Errorf("Write modified input slice; got %x, want %x", p, before)
   164  	}
   165  }
   166  
   167  // Helper function for getting Sum. Checks that Sum doesn't change hash state.
   168  func getSum(t *testing.T, h hash.Hash, buff []byte) []byte {
   169  	t.Helper()
   170  
   171  	testBuff := make([]byte, len(buff))
   172  	copy(testBuff, buff)
   173  
   174  	sum := h.Sum(buff)
   175  	testSum := h.Sum(testBuff)
   176  
   177  	// Check that Sum doesn't change underlying hash state
   178  	if !bytes.Equal(sum, testSum) {
   179  		t.Errorf("successive calls to Sum yield different results; got %x, want %x", sum, testSum)
   180  	}
   181  
   182  	return sum
   183  }
   184  
   185  func newRandReader(t *testing.T) io.Reader {
   186  	seed := time.Now().UnixNano()
   187  	t.Logf("Deterministic RNG seed: 0x%x", seed)
   188  	return rand.New(rand.NewSource(seed))
   189  }
   190  

View as plain text