github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/libkbfs/crypto_common_test.go (about)

     1  // Copyright 2016 Keybase Inc. All rights reserved.
     2  // Use of this source code is governed by a BSD
     3  // license that can be found in the LICENSE file.
     4  
     5  package libkbfs
     6  
     7  import (
     8  	"fmt"
     9  	"testing"
    10  
    11  	"github.com/keybase/client/go/kbfs/data"
    12  	"github.com/keybase/client/go/kbfs/kbfscodec"
    13  	"github.com/keybase/client/go/kbfs/kbfscrypto"
    14  	"github.com/stretchr/testify/assert"
    15  	"github.com/stretchr/testify/require"
    16  	"golang.org/x/crypto/nacl/secretbox"
    17  )
    18  
    19  type simpleBlockCryptVersioner struct {
    20  	t kbfscrypto.EncryptionVer
    21  }
    22  
    23  func (sbcv simpleBlockCryptVersioner) BlockCryptVersion() kbfscrypto.EncryptionVer {
    24  	return sbcv.t
    25  }
    26  
    27  func makeBlockCryptV1() simpleBlockCryptVersioner {
    28  	return simpleBlockCryptVersioner{kbfscrypto.EncryptionSecretbox}
    29  }
    30  
    31  func makeBlockCryptV2() simpleBlockCryptVersioner {
    32  	return simpleBlockCryptVersioner{kbfscrypto.EncryptionSecretboxWithKeyNonce}
    33  }
    34  
    35  // Test (very superficially) that MakeRandomTLFEphemeralKeys() returns
    36  // non-zero values that aren't equal.
    37  func TestCryptoCommonRandomTLFEphemeralKeys(t *testing.T) {
    38  	c := MakeCryptoCommon(kbfscodec.NewMsgpack(), makeBlockCryptV1())
    39  
    40  	a1, a2, err := c.MakeRandomTLFEphemeralKeys()
    41  	require.NoError(t, err)
    42  	require.NotEqual(t, kbfscrypto.TLFEphemeralPublicKey{}, a1)
    43  	require.NotEqual(t, kbfscrypto.TLFEphemeralPrivateKey{}, a2)
    44  
    45  	b1, b2, err := c.MakeRandomTLFEphemeralKeys()
    46  	require.NoError(t, err)
    47  	require.NotEqual(t, kbfscrypto.TLFEphemeralPublicKey{}, b1)
    48  	require.NotEqual(t, kbfscrypto.TLFEphemeralPrivateKey{}, b2)
    49  
    50  	require.NotEqual(t, a1, b1)
    51  	require.NotEqual(t, a2, b2)
    52  }
    53  
    54  // Test (very superficially) that MakeRandomTLFKeys() returns non-zero
    55  // values that aren't equal.
    56  func TestCryptoCommonRandomTLFKeys(t *testing.T) {
    57  	c := MakeCryptoCommon(kbfscodec.NewMsgpack(), makeBlockCryptV1())
    58  
    59  	a1, a2, a3, err := c.MakeRandomTLFKeys()
    60  	require.NoError(t, err)
    61  	require.NotEqual(t, kbfscrypto.TLFPublicKey{}, a1)
    62  	require.NotEqual(t, kbfscrypto.TLFPrivateKey{}, a2)
    63  	require.NotEqual(t, kbfscrypto.TLFCryptKey{}, a3)
    64  
    65  	b1, b2, b3, err := c.MakeRandomTLFKeys()
    66  	require.NoError(t, err)
    67  	require.NotEqual(t, kbfscrypto.TLFPublicKey{}, b1)
    68  	require.NotEqual(t, kbfscrypto.TLFPrivateKey{}, b2)
    69  	require.NotEqual(t, kbfscrypto.TLFCryptKey{}, b3)
    70  
    71  	require.NotEqual(t, a1, b1)
    72  	require.NotEqual(t, a2, b2)
    73  	require.NotEqual(t, a3, b3)
    74  }
    75  
    76  type TestBlock struct {
    77  	A int
    78  }
    79  
    80  func (TestBlock) DataVersion() data.Ver {
    81  	return data.FirstValidVer
    82  }
    83  
    84  func (tb TestBlock) GetEncodedSize() uint32 {
    85  	return 0
    86  }
    87  
    88  func (tb TestBlock) SetEncodedSize(size uint32) {
    89  }
    90  
    91  func (tb TestBlock) NewEmpty() data.Block {
    92  	return &TestBlock{}
    93  }
    94  
    95  func (tb TestBlock) NewEmptier() func() data.Block {
    96  	return tb.NewEmpty
    97  }
    98  
    99  func (tb *TestBlock) Set(other data.Block) {
   100  	otherTb := other.(*TestBlock)
   101  	tb.A = otherTb.A
   102  }
   103  
   104  func (tb *TestBlock) ToCommonBlock() *data.CommonBlock {
   105  	return nil
   106  }
   107  
   108  func (tb *TestBlock) IsIndirect() bool {
   109  	return false
   110  }
   111  
   112  func (tb *TestBlock) IsTail() bool {
   113  	return true
   114  }
   115  
   116  func (tb TestBlock) OffsetExceedsData(_, _ data.Offset) bool {
   117  	return false
   118  }
   119  
   120  func (tb TestBlock) BytesCanBeDirtied() int64 {
   121  	return 0
   122  }
   123  
   124  func TestCryptoCommonEncryptDecryptBlock(t *testing.T) {
   125  	c := MakeCryptoCommon(kbfscodec.NewMsgpack(), makeBlockCryptV1())
   126  
   127  	block := TestBlock{42}
   128  	tlfCryptKey := kbfscrypto.TLFCryptKey{}
   129  	blockServerHalf := kbfscrypto.BlockCryptKeyServerHalf{}
   130  
   131  	_, encryptedBlock, err := c.EncryptBlock(
   132  		&block, tlfCryptKey, blockServerHalf)
   133  	require.NoError(t, err)
   134  
   135  	var decryptedBlock TestBlock
   136  	err = c.DecryptBlock(
   137  		encryptedBlock, tlfCryptKey, blockServerHalf, &decryptedBlock)
   138  	require.NoError(t, err)
   139  	require.Equal(t, block, decryptedBlock)
   140  }
   141  
   142  func checkSecretboxOpenPrivateMetadata(t *testing.T, encryptedPrivateMetadata kbfscrypto.EncryptedPrivateMetadata, key kbfscrypto.TLFCryptKey) (encodedData []byte) {
   143  	require.Equal(
   144  		t, kbfscrypto.EncryptionSecretbox, encryptedPrivateMetadata.Version)
   145  	require.Equal(t, 24, len(encryptedPrivateMetadata.Nonce))
   146  
   147  	var nonce [24]byte
   148  	copy(nonce[:], encryptedPrivateMetadata.Nonce)
   149  	require.NotEqual(t, [24]byte{}, nonce)
   150  
   151  	keyData := key.Data()
   152  	encodedData, ok := secretbox.Open(nil, encryptedPrivateMetadata.EncryptedData, &nonce, &keyData)
   153  	require.True(t, ok)
   154  
   155  	return encodedData
   156  }
   157  
   158  // Test that crypto.EncryptPrivateMetadata() encrypts its passed-in
   159  // PrivateMetadata object properly.
   160  func TestEncryptPrivateMetadata(t *testing.T) {
   161  	c := MakeCryptoCommon(kbfscodec.NewMsgpack(), makeBlockCryptV1())
   162  
   163  	_, tlfPrivateKey, cryptKey, err := c.MakeRandomTLFKeys()
   164  	require.NoError(t, err)
   165  
   166  	privateMetadata := PrivateMetadata{
   167  		TLFPrivateKey: tlfPrivateKey,
   168  	}
   169  	expectedEncodedPrivateMetadata, err := c.codec.Encode(privateMetadata)
   170  	require.NoError(t, err)
   171  
   172  	encryptedPrivateMetadata, err := c.EncryptPrivateMetadata(privateMetadata, cryptKey)
   173  	require.NoError(t, err)
   174  
   175  	encodedPrivateMetadata := checkSecretboxOpenPrivateMetadata(t, encryptedPrivateMetadata, cryptKey)
   176  
   177  	require.Equal(t, expectedEncodedPrivateMetadata, encodedPrivateMetadata)
   178  }
   179  
   180  // Test that crypto.DecryptPrivateMetadata() decrypts an encrypted
   181  // PrivateMetadata object.
   182  func TestDecryptPrivateMetadata(t *testing.T) {
   183  	codec := kbfscodec.NewMsgpack()
   184  	c := MakeCryptoCommon(codec, makeBlockCryptV1())
   185  
   186  	_, tlfPrivateKey, cryptKey, err := c.MakeRandomTLFKeys()
   187  	require.NoError(t, err)
   188  
   189  	privateMetadata := PrivateMetadata{
   190  		TLFPrivateKey: tlfPrivateKey,
   191  	}
   192  
   193  	encodedPrivateMetadata, err := codec.Encode(privateMetadata)
   194  	require.NoError(t, err)
   195  
   196  	encryptedPrivateMetadata, err := kbfscrypto.EncryptEncodedPrivateMetadata(
   197  		encodedPrivateMetadata, cryptKey)
   198  	require.NoError(t, err)
   199  
   200  	decryptedPrivateMetadata, err := c.DecryptPrivateMetadata(
   201  		encryptedPrivateMetadata, cryptKey)
   202  	require.NoError(t, err)
   203  	require.Equal(t, privateMetadata, decryptedPrivateMetadata)
   204  }
   205  
   206  func makeFakeBlockCryptKey(t *testing.T) (
   207  	kbfscrypto.TLFCryptKey, kbfscrypto.BlockCryptKeyServerHalf,
   208  	kbfscrypto.BlockCryptKey) {
   209  	tlfCryptKey, err := kbfscrypto.MakeRandomTLFCryptKey()
   210  	require.NoError(t, err)
   211  	blockServerHalf, err := kbfscrypto.MakeRandomBlockCryptKeyServerHalf()
   212  	require.NoError(t, err)
   213  	key := kbfscrypto.UnmaskBlockCryptKey(blockServerHalf, tlfCryptKey)
   214  	return tlfCryptKey, blockServerHalf, key
   215  }
   216  
   217  func checkSecretboxOpenBlock(t *testing.T, encryptedBlock kbfscrypto.EncryptedBlock, key kbfscrypto.BlockCryptKey) (encodedData []byte) {
   218  	require.Equal(
   219  		t, kbfscrypto.EncryptionSecretbox, encryptedBlock.Version)
   220  	require.Equal(t, 24, len(encryptedBlock.Nonce))
   221  
   222  	var nonce [24]byte
   223  	copy(nonce[:], encryptedBlock.Nonce)
   224  	require.NotEqual(t, [24]byte{}, nonce)
   225  
   226  	keyData := key.Data()
   227  	encodedData, ok := secretbox.Open(nil, encryptedBlock.EncryptedData, &nonce, &keyData)
   228  	require.True(t, ok)
   229  
   230  	return encodedData
   231  }
   232  
   233  // Test that crypto.EncryptBlock() encrypts its passed-in Block object
   234  // properly.
   235  func TestEncryptBlock(t *testing.T) {
   236  	c := MakeCryptoCommon(kbfscodec.NewMsgpack(), makeBlockCryptV1())
   237  
   238  	tlfCryptKey, blockServerHalf, cryptKey := makeFakeBlockCryptKey(t)
   239  
   240  	block := TestBlock{50}
   241  	expectedEncodedBlock, err := c.codec.Encode(block)
   242  	require.NoError(t, err)
   243  
   244  	plainSize, encryptedBlock, err := c.EncryptBlock(
   245  		&block, tlfCryptKey, blockServerHalf)
   246  	require.NoError(t, err)
   247  	require.Equal(t, len(expectedEncodedBlock), plainSize)
   248  
   249  	paddedBlock := checkSecretboxOpenBlock(t, encryptedBlock, cryptKey)
   250  	encodedBlock, err := kbfscrypto.DepadBlock(paddedBlock)
   251  	require.NoError(t, err)
   252  	require.Equal(t, expectedEncodedBlock, encodedBlock)
   253  }
   254  
   255  func testDecryptEncryptedBlock(t *testing.T, c CryptoCommon) {
   256  	tlfCryptKey, blockServerHalf, _ := makeFakeBlockCryptKey(t)
   257  
   258  	block := TestBlock{50}
   259  
   260  	_, encryptedBlock, err := c.EncryptBlock(
   261  		&block, tlfCryptKey, blockServerHalf)
   262  	require.NoError(t, err)
   263  
   264  	require.Equal(
   265  		t, c.blockCryptVersioner.BlockCryptVersion(), encryptedBlock.Version)
   266  
   267  	var decryptedBlock TestBlock
   268  	err = c.DecryptBlock(
   269  		encryptedBlock, tlfCryptKey, blockServerHalf, &decryptedBlock)
   270  	require.NoError(t, err)
   271  	require.Equal(t, block, decryptedBlock)
   272  }
   273  
   274  // Test that crypto.DecryptBlock() decrypts a encrypted Block object.
   275  func TestDecryptEncryptedBlock(t *testing.T) {
   276  	c := MakeCryptoCommon(kbfscodec.NewMsgpack(), makeBlockCryptV1())
   277  	testDecryptEncryptedBlock(t, c)
   278  }
   279  
   280  // Test that crypto.DecryptBlock() decrypts a V2-encrypted Block object.
   281  func TestDecryptV2EncryptedBlock(t *testing.T) {
   282  	c := MakeCryptoCommon(kbfscodec.NewMsgpack(), makeBlockCryptV2())
   283  	testDecryptEncryptedBlock(t, c)
   284  }
   285  
   286  // Test that secretbox encrypted data length is a deterministic
   287  // function of the input data length.
   288  func TestSecretboxEncryptedLen(t *testing.T) {
   289  	const startSize = 100
   290  	const endSize = 100000
   291  	const iterations = 5
   292  
   293  	// Generating random data is slow, so do it all up-front and
   294  	// index into it. Note that we're intentionally re-using most
   295  	// of the data between iterations intentionally.
   296  	randomData := make([]byte, endSize+iterations)
   297  	err := kbfscrypto.RandRead(randomData)
   298  	require.NoError(t, err)
   299  
   300  	cryptKeys := make([]kbfscrypto.TLFCryptKey, iterations)
   301  	serverHalfs := make([]kbfscrypto.BlockCryptKeyServerHalf, iterations)
   302  	for j := 0; j < iterations; j++ {
   303  		cryptKeys[j], serverHalfs[j], _ = makeFakeBlockCryptKey(t)
   304  	}
   305  
   306  	for i := startSize; i < endSize; i += 1000 {
   307  		var enclen int
   308  		for j := 0; j < iterations; j++ {
   309  			data := randomData[j : j+i]
   310  			enc, err := kbfscrypto.EncryptPaddedEncodedBlock(
   311  				data, cryptKeys[j], serverHalfs[j],
   312  				kbfscrypto.EncryptionSecretboxWithKeyNonce)
   313  			require.NoError(t, err)
   314  			if j == 0 {
   315  				enclen = len(enc.EncryptedData)
   316  			} else {
   317  				assert.Equal(t, len(enc.EncryptedData), enclen)
   318  			}
   319  		}
   320  	}
   321  }
   322  
   323  type testBlockArray []byte
   324  
   325  func (tba testBlockArray) GetEncodedSize() uint32 {
   326  	return 0
   327  }
   328  
   329  func (tba testBlockArray) SetEncodedSize(size uint32) {
   330  }
   331  
   332  func (testBlockArray) DataVersion() data.Ver {
   333  	return data.FirstValidVer
   334  }
   335  
   336  func (tba testBlockArray) NewEmpty() data.Block {
   337  	return &testBlockArray{}
   338  }
   339  
   340  func (tba testBlockArray) NewEmptier() func() data.Block {
   341  	return tba.NewEmpty
   342  }
   343  
   344  func (tba testBlockArray) ToCommonBlock() *data.CommonBlock {
   345  	return nil
   346  }
   347  
   348  func (tba *testBlockArray) Set(other data.Block) {
   349  	otherTba := other.(*testBlockArray)
   350  	*tba = *otherTba
   351  }
   352  
   353  func (tba testBlockArray) IsIndirect() bool {
   354  	return false
   355  }
   356  
   357  func (tba *testBlockArray) IsTail() bool {
   358  	return true
   359  }
   360  
   361  func (tba testBlockArray) OffsetExceedsData(_, _ data.Offset) bool {
   362  	return false
   363  }
   364  
   365  func (tba testBlockArray) BytesCanBeDirtied() int64 {
   366  	return 0
   367  }
   368  
   369  // Test that block encrypted data length is the same for data
   370  // length within same power of 2.
   371  func TestBlockEncryptedLen(t *testing.T) {
   372  	c := MakeCryptoCommon(kbfscodec.NewMsgpack(), makeBlockCryptV1())
   373  	tlfCryptKey, blockServerHalf, _ := makeFakeBlockCryptKey(t)
   374  
   375  	const startSize = 1025
   376  	const endSize = 2000
   377  
   378  	// Generating random data is slow, so do it all up-front and
   379  	// index into it. Note that we're intentionally re-using most
   380  	// of the data between iterations intentionally.
   381  	randomData := make(testBlockArray, endSize)
   382  	err := kbfscrypto.RandRead(randomData)
   383  	require.NoError(t, err)
   384  
   385  	var expectedLen int
   386  	for i := startSize; i < endSize; i++ {
   387  		data := randomData[:i]
   388  		_, encBlock, err := c.EncryptBlock(&data, tlfCryptKey, blockServerHalf)
   389  		require.NoError(t, err)
   390  
   391  		if expectedLen == 0 {
   392  			expectedLen = len(encBlock.EncryptedData)
   393  			continue
   394  		}
   395  		require.Equal(t, expectedLen, len(encBlock.EncryptedData))
   396  	}
   397  }
   398  
   399  func benchmarkEncryptBlock(b *testing.B, blockSize int) {
   400  	c := MakeCryptoCommon(kbfscodec.NewMsgpack(), makeBlockCryptV1())
   401  
   402  	// Fill in the block with varying data to make sure not to
   403  	// trigger any encoding optimizations.
   404  	buf := make([]byte, 512<<10)
   405  	for i := 0; i < len(buf); i++ {
   406  		buf[i] = byte(i)
   407  	}
   408  	block := data.FileBlock{
   409  		Contents: buf,
   410  	}
   411  	tlfCryptKey := kbfscrypto.TLFCryptKey{}
   412  	blockServerHalf := kbfscrypto.BlockCryptKeyServerHalf{}
   413  
   414  	b.ResetTimer()
   415  	for i := 0; i < b.N; i++ {
   416  		_, _, err := c.EncryptBlock(&block, tlfCryptKey, blockServerHalf)
   417  		require.NoError(b, err)
   418  	}
   419  }
   420  
   421  func BenchmarkEncryptBlock(b *testing.B) {
   422  	blockSizes := []int{
   423  		0,
   424  		1024,
   425  		32 * 1024,
   426  		512 * 1024,
   427  	}
   428  	for _, blockSize := range blockSizes {
   429  		// Capture range variable.
   430  		blockSize := blockSize
   431  		b.Run(fmt.Sprintf("blockSize=%d", blockSize),
   432  			func(b *testing.B) {
   433  				benchmarkEncryptBlock(b, blockSize)
   434  			})
   435  	}
   436  }