lnd.xprv/chanbackup/crypto_test.go
Olaoluwa Osuntokun 67c6d0d331
chanbackup: implement crypto operations for serialized static channel baackups
In this commit, we implement a series of new crypto operations that will
allow us to encrypt and decrypt a set of serialized channel backups.
Their various backups may have distinct encodings when serialized, but
to the functions defined in this file, we treat them as simple opaque
blobs.

For encryption, we utilize chacha20poly1305 with a random 24 byte nonce.
We use a larger nonce size as this can be safely generated via a CSPRNG
without fear of frequency collisions between nonces generated. To
encrypt a blob, we then use this nonce as the AD (associated data) and
prepend the nonce to the front of the ciphertext package.

For key generation, in order to ensure the user only needs their
passphrase and the backup file, we utilize the existing keychain to
derive a private key. In order to ensure that at we don't force any
hardware signer to be aware of our crypto operations, we instead opt to
utilize a public key that will be hashed to derive our private key. The
assumption here is that this key will only be exposed to this software,
and never derived as a public facing address.
2019-01-23 18:11:21 -08:00

157 lines
4.0 KiB
Go

package chanbackup
import (
"bytes"
"fmt"
"testing"
"github.com/btcsuite/btcd/btcec"
"github.com/lightningnetwork/lnd/keychain"
)
var (
testWalletPrivKey = []byte{
0x2b, 0xd8, 0x06, 0xc9, 0x7f, 0x0e, 0x00, 0xaf,
0x1a, 0x1f, 0xc3, 0x32, 0x8f, 0xa7, 0x63, 0xa9,
0x26, 0x97, 0x23, 0xc8, 0xdb, 0x8f, 0xac, 0x4f,
0x93, 0xaf, 0x71, 0xdb, 0x18, 0x6d, 0x6e, 0x90,
}
)
type mockKeyRing struct {
fail bool
}
func (m *mockKeyRing) DeriveNextKey(keyFam keychain.KeyFamily) (keychain.KeyDescriptor, error) {
return keychain.KeyDescriptor{}, nil
}
func (m *mockKeyRing) DeriveKey(keyLoc keychain.KeyLocator) (keychain.KeyDescriptor, error) {
if m.fail {
return keychain.KeyDescriptor{}, fmt.Errorf("fail")
}
_, pub := btcec.PrivKeyFromBytes(btcec.S256(), testWalletPrivKey)
return keychain.KeyDescriptor{
PubKey: pub,
}, nil
}
// TestEncryptDecryptPayload tests that given a static key, we're able to
// properly decrypt and encrypted payload. We also test that we'll reject a
// ciphertext that has been modified.
func TestEncryptDecryptPayload(t *testing.T) {
t.Parallel()
payloadCases := []struct {
// plaintext is the string that we'll be encrypting.
plaintext []byte
// mutator allows a test case to modify the ciphertext before
// we attempt to decrypt it.
mutator func(*[]byte)
// valid indicates if this test should pass or fail.
valid bool
}{
// Proper payload, should decrypt.
{
plaintext: []byte("payload test plain text"),
mutator: nil,
valid: true,
},
// Mutator modifies cipher text, shouldn't decrypt.
{
plaintext: []byte("payload test plain text"),
mutator: func(p *[]byte) {
// Flip a byte in the payload to render it invalid.
(*p)[0] ^= 1
},
valid: false,
},
// Cipher text is too small, shouldn't decrypt.
{
plaintext: []byte("payload test plain text"),
mutator: func(p *[]byte) {
// Modify the cipher text to be zero length.
*p = []byte{}
},
valid: false,
},
}
keyRing := &mockKeyRing{}
for i, payloadCase := range payloadCases {
var cipherBuffer bytes.Buffer
// First, we'll encrypt the passed payload with our scheme.
payloadReader := bytes.NewBuffer(payloadCase.plaintext)
err := encryptPayloadToWriter(
*payloadReader, &cipherBuffer, keyRing,
)
if err != nil {
t.Fatalf("unable encrypt paylaod: %v", err)
}
// If we have a mutator, then we'll wrong the mutator over the
// cipher text, then reset the main buffer and re-write the new
// cipher text.
if payloadCase.mutator != nil {
cipherText := cipherBuffer.Bytes()
payloadCase.mutator(&cipherText)
cipherBuffer.Reset()
cipherBuffer.Write(cipherText)
}
plaintext, err := decryptPayloadFromReader(&cipherBuffer, keyRing)
switch {
// If this was meant to be a valid decryption, but we failed,
// then we'll return an error.
case err != nil && payloadCase.valid:
t.Fatalf("unable to decrypt valid payload case %v", i)
// If this was meant to be an invalid decryption, and we didn't
// fail, then we'll return an error.
case err == nil && !payloadCase.valid:
t.Fatalf("payload was invalid yet was able to decrypt")
}
// Only if this case was mean to be valid will we ensure the
// resulting decrypted plaintext matches the original input.
if payloadCase.valid &&
!bytes.Equal(plaintext, payloadCase.plaintext) {
t.Fatalf("#%v: expected %v, got %v: ", i,
payloadCase.plaintext, plaintext)
}
}
}
// TestInvalidKeyEncryption tests that encryption fails if we're unable to
// obtain a valid key.
func TestInvalidKeyEncryption(t *testing.T) {
t.Parallel()
var b bytes.Buffer
err := encryptPayloadToWriter(b, &b, &mockKeyRing{true})
if err == nil {
t.Fatalf("expected error due to fail key gen")
}
}
// TestInvalidKeyDecrytion tests that decryption fails if we're unable to
// obtain a valid key.
func TestInvalidKeyDecrytion(t *testing.T) {
t.Parallel()
var b bytes.Buffer
_, err := decryptPayloadFromReader(&b, &mockKeyRing{true})
if err == nil {
t.Fatalf("expected error due to fail key gen")
}
}