github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/chat/signencrypt/codec_test.go (about)

     1  package signencrypt
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/hex"
     6  	"fmt"
     7  	"io"
     8  	"strings"
     9  	"testing"
    10  
    11  	"golang.org/x/crypto/nacl/secretbox"
    12  
    13  	"github.com/keybase/client/go/kbcrypto"
    14  	"github.com/keybase/go-crypto/ed25519"
    15  	"github.com/stretchr/testify/require"
    16  )
    17  
    18  var plaintextInputs = []string{
    19  	"",
    20  	"1",
    21  	"short",
    22  	strings.Repeat("long", 1000000),
    23  }
    24  
    25  type arbitraryMsg struct {
    26  	Message string `codec:"m" json:"m"`
    27  }
    28  type arbitraryNested struct {
    29  	Fun    arbitraryMsg `codec:"f" json:"f"`
    30  	Boring arbitraryMsg `codec:"b" json:"b"`
    31  }
    32  
    33  var associatedDataInputs = []interface{}{
    34  	"",
    35  	nil,
    36  	map[string]map[float64]map[string]string{"first": {2.22000222: {"third": "fourth"}}},
    37  	strings.Repeat("long", 1000000),
    38  	arbitraryNested{
    39  		Fun:    arbitraryMsg{Message: "🤠"},
    40  		Boring: arbitraryMsg{Message: "📄"},
    41  	},
    42  }
    43  
    44  func zeroSecretboxKey() SecretboxKey {
    45  	var key [SecretboxKeySize]byte // all zeroes
    46  	return &key
    47  }
    48  
    49  func zeroNonce() Nonce {
    50  	var nonce [NonceSize]byte // all zeroes
    51  	return &nonce
    52  }
    53  
    54  func zeroChunkNonce(chunkNum uint64) SecretboxNonce {
    55  	return makeChunkNonce(zeroNonce(), chunkNum)
    56  }
    57  
    58  func zeroVerifyKey() VerifyKey {
    59  	var key [ed25519.PublicKeySize]byte
    60  	// Generated from libsodium's crypto_sign_seed_keypair with a zero seed.
    61  	copy(key[:], ";j'\xbc\xce\xb6\xa4-b\xa3\xa8\xd0*o\rse2\x15w\x1d\xe2C\xa6:\xc0H\xa1\x8bY\xda)")
    62  	return &key
    63  }
    64  
    65  func zeroSignKey() SignKey {
    66  	var key [ed25519.PrivateKeySize]byte
    67  	// Generated from libsodium's crypto_sign_seed_keypair with a zero seed.
    68  	copy(key[:], "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00;j'\xbc\xce\xb6\xa4-b\xa3\xa8\xd0*o\rse2\x15w\x1d\xe2C\xa6:\xc0H\xa1\x8bY\xda)")
    69  	return &key
    70  }
    71  
    72  func testingPrefix() kbcrypto.SignaturePrefix {
    73  	return kbcrypto.SignaturePrefixTesting
    74  }
    75  
    76  func zeroEncoder() *Encoder {
    77  	return NewEncoder(zeroSecretboxKey(), zeroSignKey(), testingPrefix(), zeroNonce())
    78  }
    79  
    80  func zeroDecoder() *Decoder {
    81  	return NewDecoder(zeroSecretboxKey(), zeroVerifyKey(), testingPrefix(), zeroNonce())
    82  }
    83  
    84  func zeroSealWhole(plaintext []byte) []byte {
    85  	return SealWhole(plaintext, zeroSecretboxKey(), zeroSignKey(), testingPrefix(), zeroNonce())
    86  }
    87  
    88  func zeroOpenWhole(plaintext []byte) ([]byte, error) {
    89  	return OpenWhole(plaintext, zeroSecretboxKey(), zeroVerifyKey(), testingPrefix(), zeroNonce())
    90  }
    91  
    92  func zeroSealWithAssociatedData(plaintext []byte, associatedData interface{}) []byte {
    93  	res, err := SealWithAssociatedData(plaintext, associatedData, zeroSecretboxKey(), zeroSignKey(), testingPrefix(), zeroNonce())
    94  	if err != nil {
    95  		// this should never actually error
    96  		panic(err)
    97  	}
    98  	return res
    99  }
   100  
   101  func zeroOpenWithAssociatedData(plaintext []byte, associatedData interface{}) ([]byte, error) {
   102  	return OpenWithAssociatedData(plaintext, associatedData, zeroSecretboxKey(), zeroVerifyKey(), testingPrefix(), zeroNonce())
   103  }
   104  
   105  func assertErrorType(t *testing.T, err error, expectedType ErrorType) {
   106  	if err == nil {
   107  		t.Fatal("expected an error, but error was nil")
   108  	}
   109  	concreteError, ok := err.(Error)
   110  	if !ok {
   111  		t.Fatal("failed to cast to Error")
   112  	}
   113  	if concreteError.Type != expectedType {
   114  		t.Fatalf("expected error type %d but found %d", expectedType, concreteError.Type)
   115  	}
   116  }
   117  
   118  func TestPacketRoundtrips(t *testing.T) {
   119  	for index, input := range plaintextInputs {
   120  		// Vary the chunk number, just for fun.
   121  		chunkNum := uint64(index)
   122  		sealed := sealPacket(
   123  			[]byte(input),
   124  			zeroSecretboxKey(),
   125  			zeroSignKey(),
   126  			testingPrefix(),
   127  			zeroChunkNonce(chunkNum))
   128  
   129  		opened, err := openPacket(
   130  			sealed,
   131  			zeroSecretboxKey(),
   132  			zeroVerifyKey(),
   133  			testingPrefix(),
   134  			zeroChunkNonce(chunkNum))
   135  		if err != nil {
   136  			t.Fatal(err)
   137  		}
   138  		if !bytes.Equal([]byte(input), opened) {
   139  			t.Fatal("opened bytes don't equal the input")
   140  		}
   141  
   142  		if int64(len(sealed)) != getPacketLen(int64(len(input))) {
   143  			t.Fatalf("Expected len %d but found %d", getPacketLen(int64(len(input))), len(sealed))
   144  		}
   145  	}
   146  }
   147  
   148  func TestWholeRoundtrips(t *testing.T) {
   149  	for _, input := range plaintextInputs {
   150  		sealed := zeroSealWhole([]byte(input))
   151  		opened, err := zeroOpenWhole(sealed)
   152  		if err != nil {
   153  			t.Fatal(err)
   154  		}
   155  		if !bytes.Equal([]byte(input), opened) {
   156  			t.Fatal("opened bytes don't equal the input")
   157  		}
   158  
   159  		if int64(len(sealed)) != GetSealedSize(int64(len(input))) {
   160  			t.Fatalf("Expected len %d but found %d", GetSealedSize(int64(len(input))), len(sealed))
   161  		}
   162  	}
   163  }
   164  
   165  func TestByteAtATimeRoundtrips(t *testing.T) {
   166  	for _, input := range plaintextInputs {
   167  		encoder := zeroEncoder()
   168  		var sealed []byte
   169  		for i := 0; i < len(input); i++ {
   170  			output := encoder.Write([]byte{input[i]})
   171  			sealed = append(sealed, output...)
   172  		}
   173  		lastOutput := encoder.Finish()
   174  		sealed = append(sealed, lastOutput...)
   175  
   176  		var opened []byte
   177  		decoder := zeroDecoder()
   178  		for i := 0; i < len(sealed); i++ {
   179  			output, err := decoder.Write([]byte{sealed[i]})
   180  			if err != nil {
   181  				t.Fatal(err)
   182  			}
   183  			opened = append(opened, output...)
   184  		}
   185  		lastOutput, err := decoder.Finish()
   186  		if err != nil {
   187  			t.Fatal(err)
   188  		}
   189  		opened = append(opened, lastOutput...)
   190  		if !bytes.Equal([]byte(input), opened) {
   191  			t.Fatal("opened bytes don't equal the input")
   192  		}
   193  
   194  		if int64(len(sealed)) != GetSealedSize(int64(len(input))) {
   195  			t.Fatalf("Expected len %d but found %d", GetSealedSize(int64(len(input))), len(sealed))
   196  		}
   197  	}
   198  }
   199  
   200  func TestReaderWrapperRoundtrips(t *testing.T) {
   201  	for _, input := range plaintextInputs {
   202  		inputBuffer := bytes.NewBuffer([]byte(input))
   203  		encodingReader := NewEncodingReader(
   204  			zeroSecretboxKey(),
   205  			zeroSignKey(),
   206  			testingPrefix(),
   207  			zeroNonce(),
   208  			inputBuffer)
   209  		encoded, err := io.ReadAll(encodingReader)
   210  		if err != nil {
   211  			t.Fatalf("errors shouldn't be possible for encoding: %s", err)
   212  		}
   213  		encodedBuffer := bytes.NewBuffer(encoded)
   214  		decodingReader := NewDecodingReader(
   215  			zeroSecretboxKey(),
   216  			zeroVerifyKey(),
   217  			testingPrefix(),
   218  			zeroNonce(),
   219  			encodedBuffer)
   220  		decoded, err := io.ReadAll(decodingReader)
   221  		if err != nil {
   222  			t.Fatalf("error during decoding: %s", err)
   223  		}
   224  		if !bytes.Equal([]byte(input), decoded) {
   225  			t.Fatal("decoded bytes don't equal the input")
   226  		}
   227  		if int64(len(encoded)) != GetSealedSize(int64(len(input))) {
   228  			t.Fatalf("Expected encoded len %d but found %d", GetSealedSize(int64(len(input))), len(encoded))
   229  		}
   230  	}
   231  }
   232  
   233  func TestBadSecretbox(t *testing.T) {
   234  	// Test several different cases. First, a secretbox that's too short to even
   235  	// contain an authenticator (just one byte for the secretbox).
   236  	shortPacket := []byte{0xc6, 0, 0, 0, 1, 42}
   237  	_, err := openPacket(shortPacket, zeroSecretboxKey(), zeroVerifyKey(), testingPrefix(), zeroChunkNonce(0))
   238  	assertErrorType(t, err, BadSecretbox)
   239  
   240  	// Then also test a secretbox that's long enough to be real, but has an
   241  	// invalid authenticator (just a bunch of constant bytes).
   242  	badAuthenticatorPacket := []byte{0xc6, 0, 0, 0, 100}
   243  	for i := 0; i < 100; i++ {
   244  		badAuthenticatorPacket = append(badAuthenticatorPacket, 42)
   245  	}
   246  	_, err = openPacket(badAuthenticatorPacket, zeroSecretboxKey(), zeroVerifyKey(), testingPrefix(), zeroChunkNonce(0))
   247  	assertErrorType(t, err, BadSecretbox)
   248  
   249  	// Test a correct packet opened with the wrong chunk number.
   250  	var rightChunkNum uint64 = 5
   251  	var wrongChunkNum uint64 = 6
   252  	correctPacket := sealPacket([]byte{}, zeroSecretboxKey(), zeroSignKey(), testingPrefix(), zeroChunkNonce(rightChunkNum))
   253  	_, err = openPacket(correctPacket, zeroSecretboxKey(), zeroVerifyKey(), testingPrefix(), zeroChunkNonce(wrongChunkNum))
   254  	assertErrorType(t, err, BadSecretbox)
   255  }
   256  
   257  func TestShortSignature(t *testing.T) {
   258  	// Signatures are 64 bytes, so this slice is too short to be one.
   259  	shortSignedChunk := []byte{1, 2, 3, 4, 5, 6, 7}
   260  	var chunkNum uint64 = 999
   261  	chunkNonce := makeChunkNonce(zeroNonce(), chunkNum)
   262  	packet := secretbox.Seal(nil, shortSignedChunk, chunkNonce, zeroSecretboxKey())
   263  	_, err := openPacket(packet, zeroSecretboxKey(), zeroVerifyKey(), testingPrefix(), zeroChunkNonce(chunkNum))
   264  	assertErrorType(t, err, ShortSignature)
   265  }
   266  
   267  func TestInvalidSignature(t *testing.T) {
   268  	// A chunk that's long enough to contain a signature, but isn't valid (just
   269  	// a bunch of zeroes).
   270  	invalidSignedChunk := bytes.Repeat([]byte{42}, 100)
   271  	var chunkNum uint64 = 999
   272  	chunkNonce := makeChunkNonce(zeroNonce(), chunkNum)
   273  	packet := secretbox.Seal(nil, invalidSignedChunk, chunkNonce, zeroSecretboxKey())
   274  	_, err := openPacket(packet, zeroSecretboxKey(), zeroVerifyKey(), testingPrefix(), zeroChunkNonce(chunkNum))
   275  	assertErrorType(t, err, BadSignature)
   276  }
   277  
   278  func TestErrorsReturnedFromDecoder(t *testing.T) {
   279  	// We need bad bytes long enough to trigger an open. This indirectly tests
   280  	// that the exact packet length is enough for that.
   281  	badPacket := bytes.Repeat([]byte{0}, int(getPacketLen(DefaultPlaintextChunkLength)))
   282  	decoder := zeroDecoder()
   283  	_, err := decoder.Write(badPacket)
   284  	assertErrorType(t, err, BadSecretbox)
   285  
   286  	// Make sure we get the same error again for any subsequent writes, even
   287  	// empty ones.
   288  	_, err = decoder.Write([]byte{})
   289  	assertErrorType(t, err, BadSecretbox)
   290  
   291  	// And also for Finish().
   292  	_, err = decoder.Finish()
   293  	assertErrorType(t, err, BadSecretbox)
   294  
   295  	// And make sure we get the same error independently for an all at once
   296  	// decode.
   297  	_, err = zeroOpenWhole(badPacket)
   298  	assertErrorType(t, err, BadSecretbox)
   299  }
   300  
   301  func TestErrorsReturnedFromDecoderDuringFinish(t *testing.T) {
   302  	// There are two errors that might have to be returned by OpenWhole. This
   303  	// tests the second path, where an error occurs first during Finish().
   304  	badSealed := zeroSealWhole([]byte("foobar"))
   305  	// Flip the very last bit.
   306  	badSealed[len(badSealed)-1] ^= 1
   307  	_, err := zeroOpenWhole(badSealed)
   308  	assertErrorType(t, err, BadSecretbox)
   309  }
   310  
   311  func throwawayBuffer() []byte {
   312  	var buf [4096]byte
   313  	return buf[:]
   314  }
   315  
   316  // Similar to TestErrorsReturnedFromDecoder above, but for the reader.
   317  func TestErrorsReturnedFromDecodingReader(t *testing.T) {
   318  	badPacket := bytes.Repeat([]byte{0}, int(getPacketLen(DefaultPlaintextChunkLength)))
   319  	reader := NewDecodingReader(
   320  		zeroSecretboxKey(),
   321  		zeroVerifyKey(),
   322  		testingPrefix(),
   323  		zeroNonce(),
   324  		bytes.NewBuffer(badPacket))
   325  	n, err := reader.Read(throwawayBuffer())
   326  	require.Equal(t, n, 0)
   327  	assertErrorType(t, err, BadSecretbox)
   328  
   329  	// Make sure we get the same error again for any subsequent reads, even
   330  	// empty ones.
   331  	n, err = reader.Read(throwawayBuffer())
   332  	require.Equal(t, n, 0)
   333  	assertErrorType(t, err, BadSecretbox)
   334  }
   335  
   336  // Similar to TestErrorsReturnedFromDecoderDuringFinish above, but for the reader.
   337  func TestErrorsReturnedFromReadingDecoderDuringFinish(t *testing.T) {
   338  	badSealed := zeroSealWhole([]byte("foobar"))
   339  	// Flip the very last bit.
   340  	badSealed[len(badSealed)-1] ^= 1
   341  	reader := NewDecodingReader(
   342  		zeroSecretboxKey(),
   343  		zeroVerifyKey(),
   344  		testingPrefix(),
   345  		zeroNonce(),
   346  		bytes.NewBuffer(badSealed))
   347  	n, err := reader.Read(throwawayBuffer())
   348  	require.Equal(t, n, 0)
   349  	assertErrorType(t, err, BadSecretbox)
   350  }
   351  
   352  func TestReencryptedPacketFails(t *testing.T) {
   353  	// Make sure that a packet can't be (legitimately) decrypted and then
   354  	// (illegitimately) reencrypted for another symmetric key, or with any
   355  	// other modified encryption metadata. This isn't proof that someone can't
   356  	// break the format in some clever way, but it's a sanity check that we're
   357  	// preventing at least the attacks we think we are.
   358  
   359  	// First create a valid packet.
   360  	var originalChunkNum uint64 // = 0, but lint doesn't let us write it :p
   361  	originalNonce := zeroNonce()
   362  	originalEncryptionKey := zeroSecretboxKey()
   363  	originalSignKey := zeroSignKey()
   364  	originalVerifyKey := zeroVerifyKey()
   365  	packet := sealPacket([]byte("foo"), originalEncryptionKey, originalSignKey, testingPrefix(), makeChunkNonce(originalNonce, originalChunkNum))
   366  
   367  	// Now strip off the outer layer of encryption, as a recipient would.
   368  	originalChunkNonce := makeChunkNonce(originalNonce, originalChunkNum)
   369  	unboxedSig, valid := secretbox.Open(nil, packet, originalChunkNonce, originalEncryptionKey)
   370  	if !valid {
   371  		t.Fatal("expected this secretbox to open cleanly")
   372  	}
   373  
   374  	// Here's the attack: reencrypt the packet under a *different* key.
   375  	newEncryptionKey := zeroSecretboxKey()
   376  	newEncryptionKey[0] = 42
   377  	rekeyedPacket := secretbox.Seal(nil, unboxedSig, originalChunkNonce, newEncryptionKey)
   378  
   379  	// This new packet will have a bad secretbox if someone tries to decrypt it
   380  	// with the old key, of course.
   381  	_, err := openPacket(rekeyedPacket, originalEncryptionKey, originalVerifyKey, testingPrefix(), makeChunkNonce(originalNonce, originalChunkNum))
   382  	assertErrorType(t, err, BadSecretbox)
   383  
   384  	// And here's the part we really care about: If someone tries to decrypt
   385  	// the packet with the *new* key, unboxing will succeed, but it should now
   386  	// give a bad *signature* error. This is the whole point of asserting the
   387  	// symmetric key inside the sig.
   388  	_, err = openPacket(rekeyedPacket, newEncryptionKey, originalVerifyKey, testingPrefix(), makeChunkNonce(originalNonce, originalChunkNum))
   389  	assertErrorType(t, err, BadSignature)
   390  
   391  	// Another test along the same lines: it should also be a signature error if the chunk number changes.
   392  	var newChunkNum uint64 = 1
   393  	newChunkNumNonce := makeChunkNonce(originalNonce, newChunkNum)
   394  	renumberedPacket := secretbox.Seal(nil, unboxedSig, newChunkNumNonce, originalEncryptionKey)
   395  	_, err = openPacket(renumberedPacket, originalEncryptionKey, originalVerifyKey, testingPrefix(), makeChunkNonce(originalNonce, newChunkNum))
   396  	assertErrorType(t, err, BadSignature)
   397  
   398  	// And: it should be a signature error if the caller's nonce changes.
   399  	newNonce := zeroNonce()
   400  	newNonce[0] = 42
   401  	newChunkNonce := makeChunkNonce(newNonce, originalChunkNum)
   402  	renoncedPacket := secretbox.Seal(nil, unboxedSig, newChunkNonce, originalEncryptionKey)
   403  	_, err = openPacket(renoncedPacket, originalEncryptionKey, originalVerifyKey, testingPrefix(), makeChunkNonce(newNonce, originalChunkNum))
   404  	assertErrorType(t, err, BadSignature)
   405  }
   406  
   407  func TestTruncatedFails(t *testing.T) {
   408  	// Another sanity check test. This isn't proof that truncation is always
   409  	// detectable, but it exercises the simplest cases.
   410  
   411  	// One full packet's worth and then a little bit more.
   412  	plaintext := bytes.Repeat([]byte{0}, int(DefaultPlaintextChunkLength+42))
   413  	sealed := zeroSealWhole(plaintext)
   414  
   415  	// Try truncating in the middle of a packet.
   416  	truncated := sealed[:999]
   417  	_, err := zeroOpenWhole(truncated)
   418  	assertErrorType(t, err, BadSecretbox)
   419  
   420  	// And try truncating at the first packet boundary. We still expect a
   421  	// BadSecretbox error, because secretbox.Open will fail on an empty slice.
   422  	packetLen := getPacketLen(DefaultPlaintextChunkLength)
   423  	truncated = sealed[:packetLen]
   424  	_, err = zeroOpenWhole(truncated)
   425  	assertErrorType(t, err, BadSecretbox)
   426  }
   427  
   428  func TestPacketSwapInOneMessageFails(t *testing.T) {
   429  	// Another sanity check test. This isn't proof that swapping is always
   430  	// detectable, but it exercises the simplest cases.
   431  
   432  	// Two full packets' worth.
   433  	plaintext := bytes.Repeat([]byte{0}, int(DefaultPlaintextChunkLength*2))
   434  	sealed := zeroSealWhole(plaintext)
   435  
   436  	// Swap the first two packets. Make sure to make *copies* of both packets,
   437  	// or else the second swap will be a no-op.
   438  	packetLen := getPacketLen(DefaultPlaintextChunkLength)
   439  	packet1 := append([]byte{}, sealed[:packetLen]...)
   440  	packet2 := append([]byte{}, sealed[packetLen:2*packetLen]...)
   441  	copy(sealed, packet2)
   442  	copy(sealed[packetLen:], packet1)
   443  
   444  	// This should break both decoding.
   445  	_, err := zeroOpenWhole(sealed)
   446  	assertErrorType(t, err, BadSecretbox)
   447  }
   448  
   449  func TestPacketSwapBetweenMessagesFails(t *testing.T) {
   450  	// Another sanity check test. This isn't proof that swapping is always
   451  	// detectable, but it exercises the simplest cases.
   452  
   453  	// One full packet's worth and then a little bit more.
   454  	plaintext1 := bytes.Repeat([]byte{1}, int(DefaultPlaintextChunkLength+42))
   455  	sealed1 := zeroSealWhole(plaintext1)
   456  
   457  	// Encrypt another same plaintext with a different nonce. (If we used the
   458  	// same nonce, packet swapping *would* be possible, not to mention all the
   459  	// crypto would be ruined.)
   460  	plaintext2 := bytes.Repeat([]byte{2}, int(DefaultPlaintextChunkLength+42))
   461  	var nonce2 [16]byte
   462  	nonce2[0] = 42
   463  	sealed2 := SealWhole(plaintext2, zeroSecretboxKey(), zeroSignKey(), testingPrefix(), &nonce2)
   464  
   465  	// Swap the first packet between them. Make sure to make *copies* and not
   466  	// just slices, or else the second swap will be a no-op.
   467  	packetLen := getPacketLen(DefaultPlaintextChunkLength)
   468  	firstPacket1 := append([]byte{}, sealed1[:packetLen]...)
   469  	firstPacket2 := append([]byte{}, sealed2[:packetLen]...)
   470  	copy(sealed1, firstPacket2)
   471  	copy(sealed2, firstPacket1)
   472  
   473  	// This should break both messages.
   474  	_, err := zeroOpenWhole(sealed1)
   475  	assertErrorType(t, err, BadSecretbox)
   476  	_, err = OpenWhole(sealed2, zeroSecretboxKey(), zeroVerifyKey(), testingPrefix(), &nonce2)
   477  	assertErrorType(t, err, BadSecretbox)
   478  }
   479  
   480  // This type returns a random error the first time you Read from it, and then
   481  // defers to the inner reader for every read after that.
   482  type FakeIOErrorReader struct {
   483  	inner                io.Reader
   484  	returnedErrorAlready bool
   485  }
   486  
   487  var _ io.Reader = (*FakeIOErrorReader)(nil)
   488  
   489  var fakeErrorString = "random error for the first read"
   490  
   491  func (f *FakeIOErrorReader) Read(buf []byte) (int, error) {
   492  	if !f.returnedErrorAlready {
   493  		f.returnedErrorAlready = true
   494  		return 0, fmt.Errorf(fakeErrorString)
   495  	}
   496  	return f.inner.Read(buf)
   497  }
   498  
   499  func TestTransientIOErrorsInReaderWrappers(t *testing.T) {
   500  	// If our DecodingReader gets a decryption error, it'll give up and fail
   501  	// forever. But if either reader gets an IO error from its inner reader, it
   502  	// should be willing to retry. Simulate this case on both ends, with a
   503  	// FakeIOErrorReader that returns a Read error one time and then returns
   504  	// real bytes on subsequent calls.
   505  
   506  	plaintext := []byte("foo")
   507  	plaintextBuf := bytes.NewBuffer(plaintext)
   508  	fakePlaintextErrorReader := &FakeIOErrorReader{inner: plaintextBuf}
   509  	encodingReader := NewEncodingReader(
   510  		zeroSecretboxKey(),
   511  		zeroSignKey(),
   512  		testingPrefix(),
   513  		zeroNonce(),
   514  		fakePlaintextErrorReader)
   515  
   516  	// The first read is an error.
   517  	n, err := encodingReader.Read(throwawayBuffer())
   518  	if n != 0 {
   519  		t.Fatalf("Expected 0 bytes, but received %d", n)
   520  	}
   521  	if err.Error() != fakeErrorString {
   522  		t.Fatalf("Expected a fake error, but found: %s", err)
   523  	}
   524  
   525  	// Subsequent reads should succeed.
   526  	encoded, err := io.ReadAll(encodingReader)
   527  	if err != nil {
   528  		t.Fatalf("no more errors expected during encoding, but found: %s", err)
   529  	}
   530  
   531  	// Similar test for the decoder.
   532  	encodedBuffer := bytes.NewBuffer(encoded)
   533  	fakeCiphertextErrorReader := &FakeIOErrorReader{inner: encodedBuffer}
   534  	decodingReader := NewDecodingReader(
   535  		zeroSecretboxKey(),
   536  		zeroVerifyKey(),
   537  		testingPrefix(),
   538  		zeroNonce(),
   539  		fakeCiphertextErrorReader)
   540  
   541  	// Again, the first read is an error.
   542  	n, err = decodingReader.Read(throwawayBuffer())
   543  	if n != 0 {
   544  		t.Fatalf("Expected 0 bytes, but received %d", n)
   545  	}
   546  	if err.Error() != fakeErrorString {
   547  		t.Fatalf("Expected a fake error, but found: %s", err)
   548  	}
   549  
   550  	// And again, subsequent reads should succeed.
   551  	decoded, err := io.ReadAll(decodingReader)
   552  	if err != nil {
   553  		t.Fatalf("no more errors expected during decoding, but found: %s", err)
   554  	}
   555  	if !bytes.Equal(plaintext, decoded) {
   556  		t.Fatal("decoded bytes don't equal the input")
   557  	}
   558  }
   559  
   560  func shouldPanic(t *testing.T, f func()) {
   561  	defer func() {
   562  		err := recover()
   563  		require.NotNil(t, err)
   564  	}()
   565  	f()
   566  }
   567  
   568  func TestCoverageHacks(t *testing.T) {
   569  	// Deliberately hit lines that don't/can't come up in normal execution.
   570  
   571  	err := NewError(BadSecretbox, "blah blah blah")
   572  	_ = err.Error()
   573  
   574  	encoder := Encoder{}
   575  	encoder.ChangePlaintextChunkLenForTesting(42)
   576  	// Try to seal a packet longer than the internal buffer.
   577  	shouldPanic(t, func() {
   578  		encoder.sealOnePacket(999)
   579  	})
   580  	// Try to Finish with too much data in the buffer.
   581  	encoder.buf = bytes.Repeat([]byte{0}, 999)
   582  	shouldPanic(t, func() {
   583  		encoder.Finish()
   584  	})
   585  
   586  	decoder := Decoder{}
   587  	decoder.ChangePlaintextChunkLenForTesting(42)
   588  	// Try to open a packet longer than the internal buffer.
   589  	shouldPanic(t, func() {
   590  		_, _ = decoder.openOnePacket(999)
   591  	})
   592  	// Try to Finish with too much data in the buffer.
   593  	decoder.buf = bytes.Repeat([]byte{0}, 999)
   594  	shouldPanic(t, func() {
   595  		_, _ = decoder.Finish()
   596  	})
   597  }
   598  
   599  func TestNullInPrefix(t *testing.T) {
   600  	encoder := NewEncoder(zeroSecretboxKey(), zeroSignKey(), kbcrypto.SignaturePrefix("Keybase-bad-prefix\x00"), zeroNonce())
   601  	encoder.Write([]byte("kaboom"))
   602  	shouldPanic(t, func() {
   603  		encoder.Finish()
   604  	})
   605  }
   606  
   607  func TestPrefixDifference(t *testing.T) {
   608  	// Test that different prefixes fail verification
   609  	for index, input := range plaintextInputs {
   610  		// Vary the chunk number, just for fun.
   611  		chunkNum := uint64(index)
   612  		sealed := sealPacket(
   613  			[]byte(input),
   614  			zeroSecretboxKey(),
   615  			zeroSignKey(),
   616  			testingPrefix(),
   617  			zeroChunkNonce(chunkNum))
   618  
   619  		// Use the correct prefix
   620  		opened, err := openPacket(
   621  			sealed,
   622  			zeroSecretboxKey(),
   623  			zeroVerifyKey(),
   624  			testingPrefix(),
   625  			zeroChunkNonce(chunkNum))
   626  		if err != nil {
   627  			t.Fatal(err)
   628  		}
   629  		if !bytes.Equal([]byte(input), opened) {
   630  			t.Fatal("opened bytes don't equal the input")
   631  		}
   632  
   633  		// Use the wrong prefix
   634  		_, err = openPacket(
   635  			sealed,
   636  			zeroSecretboxKey(),
   637  			zeroVerifyKey(),
   638  			testingPrefix()+"other",
   639  			zeroChunkNonce(chunkNum))
   640  		assertErrorType(t, err, BadSignature)
   641  	}
   642  }
   643  
   644  func TestVectors(t *testing.T) {
   645  	if len(testVectors) < 1 {
   646  		t.Fatalf("missing test vectors")
   647  	}
   648  
   649  	for i, v := range testVectors {
   650  		if !v.chunked {
   651  			t.Fatalf("i%d: non-chunked tests not supported yet", i)
   652  		}
   653  		sealedRef, err := hex.DecodeString(v.sealedHex)
   654  		if err != nil {
   655  			t.Fatalf("i:%d sealedHex is invalid hex: %v", i, err)
   656  		}
   657  
   658  		// Test seal
   659  		encoder := zeroEncoder()
   660  		encoder.Write([]byte(v.plaintext))
   661  		sealed := encoder.Finish()
   662  		if !bytes.Equal(sealedRef, sealed) {
   663  			t.Fatalf("i:%d sealed bytes not equal\n     got: %x\nexpected: %x", i, sealed, sealedRef)
   664  		}
   665  
   666  		// Test open
   667  		decoder := zeroDecoder()
   668  		_, err = decoder.Write(sealedRef)
   669  		require.NoError(t, err)
   670  		opened, err := decoder.Finish()
   671  		if err != nil {
   672  			t.Fatalf("i:%d error opening: %v", i, err)
   673  		}
   674  		if !bytes.Equal([]byte(v.plaintext), opened) {
   675  			t.Fatalf("i:%d opened bytes not equal\n     got: %x\nexpected: %x", i, opened, v.plaintext)
   676  		}
   677  	}
   678  }
   679  
   680  var testVectors = []struct {
   681  	chunked   bool
   682  	plaintext string
   683  	sealedHex string
   684  }{
   685  	{
   686  		chunked: true,
   687  		plaintext: `The KID format
   688  
   689  Version 1 of Keybase KIDs are 35-bytes long, and take the form:
   690  
   691  ┌──────────┬──────────┬─────────────────────────────────┬──────────────┐
   692  │ version  │ key type │             payload             │ '0a' trailer │
   693  │ (1 byte) │ (1 byte) │           (32 bytes)            │   (1 byte)   │
   694  └──────────┴──────────┴─────────────────────────────────┴──────────────┘
   695  The fields are described as follows:
   696  `,
   697  		sealedHex: `9c488f76be8f8f5eb84e37737017ce5dc92ea5c4752b6af99dd17df6f71d625252344511a903d0a8bfeac4574c52c1ecdfdba71beb95c8d9b60e0bd1bb4c4f83742d7b46c7d827c6a79397cd4dedd8a52d769e92798608a4389f46722f4f45391862a323f3006ec74f1b9d92d709291a17216119445b1dce49912f59b00eeb74af2e6779623de2b5d8e229bc2934dbf8d98c5dfd558dca8080fad3bf217e25f313ddaa3cc0cb193cd7561d8be207aa11b44822b6fd80dabcb817683883c44ee5cab7390ce13103cd098c5f7a9c2e36bf62462d163fbd78efb429e90f141d579f01eeeb33713c40b86069da04d53f9aa33ecadd7af28556573e76a11b88d27253cd90f743b3c8087bbdf18b1f3f3b8d1d7adc15f0a4021590812b822b9d38e6e79a59168dfb51be1ded8c47cc228b59d75ea1e1f61f7a26fb6d0e4992cffb0cf4709e3d9f9ad6252719fa795acc8f71bacf3e32bcf35b55f899d3768eb3dc5147eb96dd8c09f7818487bc5ab15d3d0ded506bc596a0a182236138010e28cda2e1dd63cf6706707888174562949bc6a75aa22823c4a82ecfec3ae30b1081465d46c3596c21017520f7ef2b63b7a7b733f2b32a7f00746dba953805048ef2af1cab77eb12c29227f42aaaedc2c394120fde461ab6c078b503fcaa73f20be05bef9c5e6718d49904295fc32f753316789cb61a65507ba8800eac82856dda77cfd961518887caeda8ccce8b16e2750911272daccff1c9a104a1481552d8340975ee6fe9c9e371a7053267b67ef97903d9f4a8071f85667f67cc09730e0789c3e230f529d1c4caeb047642e225063d5c305a1d03a1941c18f15b9e36692e41bf3340f1e9876db480974cbf41eedaaacd01ca6d62d270f4c8f0df25c1d781b1eaeac0b1d3887fd5e07f12c5bf576fe1e99471c8894f8981d0fb86e7ac860f9b2da2e0654520ac9b53cf3949a01d5866a06f7d8ad8865042d96d2cae118f9ab5980ada48a720e47b0ade9e984ef2e12904ca41ef30f2ff0464107042aca152ffd5b7081fd481fe76aa23f04d840f43a6e2f17ae5dea74298730c7ffce42bafe108cc70b5839a9ebb28cd8318d03529d680d75a68cc3dbf261c43eebc698bcf4c6f90`,
   698  	},
   699  }
   700  
   701  func TestAssociatedData(t *testing.T) {
   702  	for _, associatedData := range associatedDataInputs {
   703  		for _, input := range plaintextInputs {
   704  			sealed := zeroSealWithAssociatedData([]byte(input), associatedData)
   705  			opened, err := zeroOpenWithAssociatedData(sealed, associatedData)
   706  			if err != nil {
   707  				t.Fatal(err)
   708  			}
   709  			if !bytes.Equal([]byte(input), opened) {
   710  				t.Fatal("opened bytes don't equal the input")
   711  			}
   712  		}
   713  	}
   714  
   715  	plaintext := "This is one time where television really fails to capture the true excitement of a large squirrel predicting the weather."
   716  	associatedData := "groundhog day"
   717  	sealed := zeroSealWithAssociatedData([]byte(plaintext), associatedData)
   718  	opened, err := zeroOpenWithAssociatedData(sealed, associatedData)
   719  	require.NoError(t, err)
   720  	require.Equal(t, opened, []byte(plaintext))
   721  	// tweaking the associated data should fail to open
   722  	incorrectAssociatedData := "groundhog day 2, the repeatening"
   723  	opened, err = zeroOpenWithAssociatedData(sealed, incorrectAssociatedData)
   724  	require.True(t, bytes.Equal(opened, []byte{}))
   725  	assertErrorType(t, err, AssociatedDataMismatch)
   726  }