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 }