Source file src/crypto/internal/cryptotest/aead.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  	"fmt"
    11  	"testing"
    12  )
    13  
    14  var lengths = []int{0, 156, 8192, 8193, 8208}
    15  
    16  // MakeAEAD returns a cipher.AEAD instance.
    17  //
    18  // Multiple calls to MakeAEAD must return equivalent instances, so for example
    19  // the key must be fixed.
    20  type MakeAEAD func() (cipher.AEAD, error)
    21  
    22  // TestAEAD performs a set of tests on cipher.AEAD implementations, checking
    23  // the documented requirements of NonceSize, Overhead, Seal and Open.
    24  func TestAEAD(t *testing.T, mAEAD MakeAEAD) {
    25  	aead, err := mAEAD()
    26  	if err != nil {
    27  		t.Fatal(err)
    28  	}
    29  
    30  	t.Run("Roundtrip", func(t *testing.T) {
    31  
    32  		// Test all combinations of plaintext and additional data lengths.
    33  		for _, ptLen := range lengths {
    34  			for _, adLen := range lengths {
    35  				t.Run(fmt.Sprintf("Plaintext-Length=%d,AddData-Length=%d", ptLen, adLen), func(t *testing.T) {
    36  					rng := newRandReader(t)
    37  
    38  					nonce := make([]byte, aead.NonceSize())
    39  					rng.Read(nonce)
    40  
    41  					before, addData := make([]byte, adLen), make([]byte, ptLen)
    42  					rng.Read(before)
    43  					rng.Read(addData)
    44  
    45  					ciphertext := sealMsg(t, aead, nil, nonce, before, addData)
    46  					after := openWithoutError(t, aead, nil, nonce, ciphertext, addData)
    47  
    48  					if !bytes.Equal(after, before) {
    49  						t.Errorf("plaintext is different after a seal/open cycle; got %s, want %s", truncateHex(after), truncateHex(before))
    50  					}
    51  				})
    52  			}
    53  		}
    54  	})
    55  
    56  	t.Run("InputNotModified", func(t *testing.T) {
    57  
    58  		// Test all combinations of plaintext and additional data lengths.
    59  		for _, ptLen := range lengths {
    60  			for _, adLen := range lengths {
    61  				t.Run(fmt.Sprintf("Plaintext-Length=%d,AddData-Length=%d", ptLen, adLen), func(t *testing.T) {
    62  					t.Run("Seal", func(t *testing.T) {
    63  						rng := newRandReader(t)
    64  
    65  						nonce := make([]byte, aead.NonceSize())
    66  						rng.Read(nonce)
    67  
    68  						src, before := make([]byte, ptLen), make([]byte, ptLen)
    69  						rng.Read(src)
    70  						copy(before, src)
    71  
    72  						addData := make([]byte, adLen)
    73  						rng.Read(addData)
    74  
    75  						sealMsg(t, aead, nil, nonce, src, addData)
    76  						if !bytes.Equal(src, before) {
    77  							t.Errorf("Seal modified src; got %s, want %s", truncateHex(src), truncateHex(before))
    78  						}
    79  					})
    80  
    81  					t.Run("Open", func(t *testing.T) {
    82  						rng := newRandReader(t)
    83  
    84  						nonce := make([]byte, aead.NonceSize())
    85  						rng.Read(nonce)
    86  
    87  						plaintext, addData := make([]byte, ptLen), make([]byte, adLen)
    88  						rng.Read(plaintext)
    89  						rng.Read(addData)
    90  
    91  						// Record the ciphertext that shouldn't be modified as the input of
    92  						// Open.
    93  						ciphertext := sealMsg(t, aead, nil, nonce, plaintext, addData)
    94  						before := make([]byte, len(ciphertext))
    95  						copy(before, ciphertext)
    96  
    97  						openWithoutError(t, aead, nil, nonce, ciphertext, addData)
    98  						if !bytes.Equal(ciphertext, before) {
    99  							t.Errorf("Open modified src; got %s, want %s", truncateHex(ciphertext), truncateHex(before))
   100  						}
   101  					})
   102  				})
   103  			}
   104  		}
   105  	})
   106  
   107  	t.Run("BufferOverlap", func(t *testing.T) {
   108  
   109  		// Test all combinations of plaintext and additional data lengths.
   110  		for _, ptLen := range lengths {
   111  			if ptLen <= 1 { // We need enough room for an inexact overlap to occur.
   112  				continue
   113  			}
   114  			for _, adLen := range lengths {
   115  				t.Run(fmt.Sprintf("Plaintext-Length=%d,AddData-Length=%d", ptLen, adLen), func(t *testing.T) {
   116  					t.Run("Seal", func(t *testing.T) {
   117  						rng := newRandReader(t)
   118  
   119  						nonce := make([]byte, aead.NonceSize())
   120  						rng.Read(nonce)
   121  
   122  						// Make a buffer that can hold a plaintext and ciphertext as we
   123  						// overlap their slices to check for panic on inexact overlaps.
   124  						ctLen := ptLen + aead.Overhead()
   125  						buff := make([]byte, ptLen+ctLen)
   126  						rng.Read(buff)
   127  
   128  						addData := make([]byte, adLen)
   129  						rng.Read(addData)
   130  
   131  						// Make plaintext and dst slices point to same array with inexact overlap.
   132  						plaintext := buff[:ptLen]
   133  						dst := buff[1:1] // Shift dst to not start at start of plaintext.
   134  						mustPanic(t, "invalid buffer overlap", func() { sealMsg(t, aead, dst, nonce, plaintext, addData) })
   135  
   136  						// Only overlap on one byte
   137  						plaintext = buff[:ptLen]
   138  						dst = buff[ptLen-1 : ptLen-1]
   139  						mustPanic(t, "invalid buffer overlap", func() { sealMsg(t, aead, dst, nonce, plaintext, addData) })
   140  					})
   141  
   142  					t.Run("Open", func(t *testing.T) {
   143  						rng := newRandReader(t)
   144  
   145  						nonce := make([]byte, aead.NonceSize())
   146  						rng.Read(nonce)
   147  
   148  						// Create a valid ciphertext to test Open with.
   149  						plaintext := make([]byte, ptLen)
   150  						rng.Read(plaintext)
   151  						addData := make([]byte, adLen)
   152  						rng.Read(addData)
   153  						validCT := sealMsg(t, aead, nil, nonce, plaintext, addData)
   154  
   155  						// Make a buffer that can hold a plaintext and ciphertext as we
   156  						// overlap their slices to check for panic on inexact overlaps.
   157  						buff := make([]byte, ptLen+len(validCT))
   158  
   159  						// Make ciphertext and dst slices point to same array with inexact overlap.
   160  						ciphertext := buff[:len(validCT)]
   161  						copy(ciphertext, validCT)
   162  						dst := buff[1:1] // Shift dst to not start at start of ciphertext.
   163  						mustPanic(t, "invalid buffer overlap", func() { aead.Open(dst, nonce, ciphertext, addData) })
   164  
   165  						// Only overlap on one byte.
   166  						ciphertext = buff[:len(validCT)]
   167  						copy(ciphertext, validCT)
   168  						// Make sure it is the actual ciphertext being overlapped and not
   169  						// the hash digest which might be extracted/truncated in some
   170  						// implementations: Go one byte past the hash digest/tag and into
   171  						// the ciphertext.
   172  						beforeTag := len(validCT) - aead.Overhead()
   173  						dst = buff[beforeTag-1 : beforeTag-1]
   174  						mustPanic(t, "invalid buffer overlap", func() { aead.Open(dst, nonce, ciphertext, addData) })
   175  					})
   176  				})
   177  			}
   178  		}
   179  	})
   180  
   181  	t.Run("AppendDst", func(t *testing.T) {
   182  
   183  		// Test all combinations of plaintext and additional data lengths.
   184  		for _, ptLen := range lengths {
   185  			for _, adLen := range lengths {
   186  				t.Run(fmt.Sprintf("Plaintext-Length=%d,AddData-Length=%d", ptLen, adLen), func(t *testing.T) {
   187  
   188  					t.Run("Seal", func(t *testing.T) {
   189  						rng := newRandReader(t)
   190  
   191  						nonce := make([]byte, aead.NonceSize())
   192  						rng.Read(nonce)
   193  
   194  						shortBuff := []byte("a")
   195  						longBuff := make([]byte, 512)
   196  						rng.Read(longBuff)
   197  						prefixes := [][]byte{shortBuff, longBuff}
   198  
   199  						// Check each prefix gets appended to by Seal without altering them.
   200  						for _, prefix := range prefixes {
   201  							plaintext, addData := make([]byte, ptLen), make([]byte, adLen)
   202  							rng.Read(plaintext)
   203  							rng.Read(addData)
   204  							out := sealMsg(t, aead, prefix, nonce, plaintext, addData)
   205  
   206  							// Check that Seal didn't alter the prefix
   207  							if !bytes.Equal(out[:len(prefix)], prefix) {
   208  								t.Errorf("Seal alters dst instead of appending; got %s, want %s", truncateHex(out[:len(prefix)]), truncateHex(prefix))
   209  							}
   210  
   211  							ciphertext := out[len(prefix):]
   212  							// Check that the appended ciphertext wasn't affected by the prefix
   213  							if expectedCT := sealMsg(t, aead, nil, nonce, plaintext, addData); !bytes.Equal(ciphertext, expectedCT) {
   214  								t.Errorf("Seal behavior affected by pre-existing data in dst; got %s, want %s", truncateHex(ciphertext), truncateHex(expectedCT))
   215  							}
   216  						}
   217  					})
   218  
   219  					t.Run("Open", func(t *testing.T) {
   220  						rng := newRandReader(t)
   221  
   222  						nonce := make([]byte, aead.NonceSize())
   223  						rng.Read(nonce)
   224  
   225  						shortBuff := []byte("a")
   226  						longBuff := make([]byte, 512)
   227  						rng.Read(longBuff)
   228  						prefixes := [][]byte{shortBuff, longBuff}
   229  
   230  						// Check each prefix gets appended to by Open without altering them.
   231  						for _, prefix := range prefixes {
   232  							before, addData := make([]byte, adLen), make([]byte, ptLen)
   233  							rng.Read(before)
   234  							rng.Read(addData)
   235  							ciphertext := sealMsg(t, aead, nil, nonce, before, addData)
   236  
   237  							out := openWithoutError(t, aead, prefix, nonce, ciphertext, addData)
   238  
   239  							// Check that Open didn't alter the prefix
   240  							if !bytes.Equal(out[:len(prefix)], prefix) {
   241  								t.Errorf("Open alters dst instead of appending; got %s, want %s", truncateHex(out[:len(prefix)]), truncateHex(prefix))
   242  							}
   243  
   244  							after := out[len(prefix):]
   245  							// Check that the appended plaintext wasn't affected by the prefix
   246  							if !bytes.Equal(after, before) {
   247  								t.Errorf("Open behavior affected by pre-existing data in dst; got %s, want %s", truncateHex(after), truncateHex(before))
   248  							}
   249  						}
   250  					})
   251  				})
   252  			}
   253  		}
   254  	})
   255  
   256  	t.Run("WrongNonce", func(t *testing.T) {
   257  
   258  		// Test all combinations of plaintext and additional data lengths.
   259  		for _, ptLen := range lengths {
   260  			for _, adLen := range lengths {
   261  				t.Run(fmt.Sprintf("Plaintext-Length=%d,AddData-Length=%d", ptLen, adLen), func(t *testing.T) {
   262  					rng := newRandReader(t)
   263  
   264  					nonce := make([]byte, aead.NonceSize())
   265  					rng.Read(nonce)
   266  
   267  					plaintext, addData := make([]byte, ptLen), make([]byte, adLen)
   268  					rng.Read(plaintext)
   269  					rng.Read(addData)
   270  
   271  					ciphertext := sealMsg(t, aead, nil, nonce, plaintext, addData)
   272  
   273  					// Perturb the nonce and check for an error when Opening
   274  					alterNonce := make([]byte, aead.NonceSize())
   275  					copy(alterNonce, nonce)
   276  					alterNonce[len(alterNonce)-1] += 1
   277  					_, err := aead.Open(nil, alterNonce, ciphertext, addData)
   278  
   279  					if err == nil {
   280  						t.Errorf("Open did not error when given different nonce than Sealed with")
   281  					}
   282  				})
   283  			}
   284  		}
   285  	})
   286  
   287  	t.Run("WrongAddData", func(t *testing.T) {
   288  
   289  		// Test all combinations of plaintext and additional data lengths.
   290  		for _, ptLen := range lengths {
   291  			for _, adLen := range lengths {
   292  				if adLen == 0 {
   293  					continue
   294  				}
   295  
   296  				t.Run(fmt.Sprintf("Plaintext-Length=%d,AddData-Length=%d", ptLen, adLen), func(t *testing.T) {
   297  					rng := newRandReader(t)
   298  
   299  					nonce := make([]byte, aead.NonceSize())
   300  					rng.Read(nonce)
   301  
   302  					plaintext, addData := make([]byte, ptLen), make([]byte, adLen)
   303  					rng.Read(plaintext)
   304  					rng.Read(addData)
   305  
   306  					ciphertext := sealMsg(t, aead, nil, nonce, plaintext, addData)
   307  
   308  					// Perturb the Additional Data and check for an error when Opening
   309  					alterAD := make([]byte, adLen)
   310  					copy(alterAD, addData)
   311  					alterAD[len(alterAD)-1] += 1
   312  					_, err := aead.Open(nil, nonce, ciphertext, alterAD)
   313  
   314  					if err == nil {
   315  						t.Errorf("Open did not error when given different Additional Data than Sealed with")
   316  					}
   317  				})
   318  			}
   319  		}
   320  	})
   321  
   322  	t.Run("WrongCiphertext", func(t *testing.T) {
   323  
   324  		// Test all combinations of plaintext and additional data lengths.
   325  		for _, ptLen := range lengths {
   326  			for _, adLen := range lengths {
   327  
   328  				t.Run(fmt.Sprintf("Plaintext-Length=%d,AddData-Length=%d", ptLen, adLen), func(t *testing.T) {
   329  					rng := newRandReader(t)
   330  
   331  					nonce := make([]byte, aead.NonceSize())
   332  					rng.Read(nonce)
   333  
   334  					plaintext, addData := make([]byte, ptLen), make([]byte, adLen)
   335  					rng.Read(plaintext)
   336  					rng.Read(addData)
   337  
   338  					ciphertext := sealMsg(t, aead, nil, nonce, plaintext, addData)
   339  
   340  					// Perturb the ciphertext and check for an error when Opening
   341  					alterCT := make([]byte, len(ciphertext))
   342  					copy(alterCT, ciphertext)
   343  					alterCT[len(alterCT)-1] += 1
   344  					_, err := aead.Open(nil, nonce, alterCT, addData)
   345  
   346  					if err == nil {
   347  						t.Errorf("Open did not error when given different ciphertext than was produced by Seal")
   348  					}
   349  				})
   350  			}
   351  		}
   352  	})
   353  }
   354  
   355  // Helper function to Seal a plaintext with additional data. Checks that
   356  // ciphertext isn't bigger than the plaintext length plus Overhead()
   357  func sealMsg(t *testing.T, aead cipher.AEAD, ciphertext, nonce, plaintext, addData []byte) []byte {
   358  	t.Helper()
   359  
   360  	initialLen := len(ciphertext)
   361  
   362  	ciphertext = aead.Seal(ciphertext, nonce, plaintext, addData)
   363  
   364  	lenCT := len(ciphertext) - initialLen
   365  
   366  	// Appended ciphertext shouldn't ever be longer than the length of the
   367  	// plaintext plus Overhead
   368  	if lenCT > len(plaintext)+aead.Overhead() {
   369  		t.Errorf("length of ciphertext from Seal exceeds length of plaintext by more than Overhead(); got %d, want <=%d", lenCT, len(plaintext)+aead.Overhead())
   370  	}
   371  
   372  	return ciphertext
   373  }
   374  
   375  // Helper function to Open and authenticate ciphertext. Checks that Open
   376  // doesn't error (assuming ciphertext was well-formed with corresponding nonce
   377  // and additional data).
   378  func openWithoutError(t *testing.T, aead cipher.AEAD, plaintext, nonce, ciphertext, addData []byte) []byte {
   379  	t.Helper()
   380  
   381  	plaintext, err := aead.Open(plaintext, nonce, ciphertext, addData)
   382  	if err != nil {
   383  		t.Fatalf("Open returned error on properly formed ciphertext; got \"%s\", want \"nil\"", err)
   384  	}
   385  
   386  	return plaintext
   387  }
   388  

View as plain text