mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 18:10:34 +01:00
177f365538
This commit lays the groundwork for enabling the option of encrypting a Tor private key on disk, and removes the onion type parameters from the OnionStore interface methods, since they are unused.
232 lines
5.9 KiB
Go
232 lines
5.9 KiB
Go
package tor
|
|
|
|
import (
|
|
"errors"
|
|
"io"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/mock"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
var (
|
|
privateKey = []byte("RSA1024 hide_me_plz")
|
|
anotherKey = []byte("another_key")
|
|
)
|
|
|
|
// TestOnionFile tests that the File implementation of the OnionStore
|
|
// interface behaves as expected.
|
|
func TestOnionFile(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tempDir := t.TempDir()
|
|
privateKeyPath := filepath.Join(tempDir, "secret")
|
|
mockEncrypter := MockEncrypter{}
|
|
|
|
// Create a new file-based onion store. A private key should not exist
|
|
// yet.
|
|
onionFile := NewOnionFile(
|
|
privateKeyPath, 0600, false, mockEncrypter,
|
|
)
|
|
_, err := onionFile.PrivateKey()
|
|
require.ErrorIs(t, err, ErrNoPrivateKey)
|
|
|
|
// Store the private key and ensure what's stored matches.
|
|
err = onionFile.StorePrivateKey(privateKey)
|
|
require.NoError(t, err)
|
|
|
|
storePrivateKey, err := onionFile.PrivateKey()
|
|
require.NoError(t, err)
|
|
require.Equal(t, storePrivateKey, privateKey)
|
|
|
|
// Finally, delete the private key. We should no longer be able to
|
|
// retrieve it.
|
|
err = onionFile.DeletePrivateKey()
|
|
require.NoError(t, err)
|
|
|
|
_, err = onionFile.PrivateKey()
|
|
require.ErrorIs(t, err, ErrNoPrivateKey)
|
|
|
|
// Create a new file-based onion store that encrypts the key this time
|
|
// to ensure that an encrypted key is properly handled.
|
|
encryptedOnionFile := NewOnionFile(
|
|
privateKeyPath, 0600, true, mockEncrypter,
|
|
)
|
|
|
|
err = encryptedOnionFile.StorePrivateKey(privateKey)
|
|
require.NoError(t, err)
|
|
|
|
storedPrivateKey, err := encryptedOnionFile.PrivateKey()
|
|
require.NoError(t, err, "unable to retrieve encrypted private key")
|
|
// Check that PrivateKey returns anotherKey, to make sure the mock
|
|
// decrypter is actually called.
|
|
require.Equal(t, storedPrivateKey, anotherKey)
|
|
|
|
err = encryptedOnionFile.DeletePrivateKey()
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// TestPrepareKeyParam checks that the key param is created as expected.
|
|
func TestPrepareKeyParam(t *testing.T) {
|
|
testKey := []byte("hide_me_plz")
|
|
dummyErr := errors.New("dummy")
|
|
|
|
// Create a dummy controller.
|
|
controller := NewController("", "", "")
|
|
|
|
// Test that a V3 keyParam is used.
|
|
cfg := AddOnionConfig{Type: V3}
|
|
keyParam, err := controller.prepareKeyparam(cfg)
|
|
|
|
require.Equal(t, "NEW:ED25519-V3", keyParam)
|
|
require.NoError(t, err)
|
|
|
|
// Create a mock store which returns the test private key.
|
|
store := &mockStore{}
|
|
store.On("PrivateKey").Return(testKey, nil)
|
|
|
|
// Check that the test private is returned.
|
|
cfg = AddOnionConfig{Type: V3, Store: store}
|
|
keyParam, err = controller.prepareKeyparam(cfg)
|
|
|
|
require.Equal(t, string(testKey), keyParam)
|
|
require.NoError(t, err)
|
|
store.AssertExpectations(t)
|
|
|
|
// Create a mock store which returns ErrNoPrivateKey.
|
|
store = &mockStore{}
|
|
store.On("PrivateKey").Return(nil, ErrNoPrivateKey)
|
|
|
|
// Check that the V3 keyParam is returned.
|
|
cfg = AddOnionConfig{Type: V3, Store: store}
|
|
keyParam, err = controller.prepareKeyparam(cfg)
|
|
|
|
require.Equal(t, "NEW:ED25519-V3", keyParam)
|
|
require.NoError(t, err)
|
|
store.AssertExpectations(t)
|
|
|
|
// Create a mock store which returns an dummy error.
|
|
store = &mockStore{}
|
|
store.On("PrivateKey").Return(nil, dummyErr)
|
|
|
|
// Check that an error is returned.
|
|
cfg = AddOnionConfig{Type: V3, Store: store}
|
|
keyParam, err = controller.prepareKeyparam(cfg)
|
|
|
|
require.Empty(t, keyParam)
|
|
require.ErrorIs(t, dummyErr, err)
|
|
store.AssertExpectations(t)
|
|
}
|
|
|
|
// TestPrepareAddOnion checks that the cmd used to add onion service is created
|
|
// as expected.
|
|
func TestPrepareAddOnion(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// Create a mock store.
|
|
store := &mockStore{}
|
|
testKey := []byte("hide_me_plz")
|
|
|
|
testCases := []struct {
|
|
name string
|
|
targetIPAddress string
|
|
cfg AddOnionConfig
|
|
expectedCmd string
|
|
expectedErr error
|
|
}{
|
|
{
|
|
name: "empty target IP and ports",
|
|
targetIPAddress: "",
|
|
cfg: AddOnionConfig{VirtualPort: 9735},
|
|
expectedCmd: "ADD_ONION NEW:RSA1024 Port=9735,9735 ",
|
|
expectedErr: nil,
|
|
},
|
|
{
|
|
name: "specified target IP and empty ports",
|
|
targetIPAddress: "127.0.0.1",
|
|
cfg: AddOnionConfig{VirtualPort: 9735},
|
|
expectedCmd: "ADD_ONION NEW:RSA1024 " +
|
|
"Port=9735,127.0.0.1:9735 ",
|
|
expectedErr: nil,
|
|
},
|
|
{
|
|
name: "specified target IP and ports",
|
|
targetIPAddress: "127.0.0.1",
|
|
cfg: AddOnionConfig{
|
|
VirtualPort: 9735,
|
|
TargetPorts: []int{18000, 18001},
|
|
},
|
|
expectedCmd: "ADD_ONION NEW:RSA1024 " +
|
|
"Port=9735,127.0.0.1:18000 " +
|
|
"Port=9735,127.0.0.1:18001 ",
|
|
expectedErr: nil,
|
|
},
|
|
{
|
|
name: "specified private key from store",
|
|
targetIPAddress: "",
|
|
cfg: AddOnionConfig{
|
|
VirtualPort: 9735,
|
|
Store: store,
|
|
},
|
|
expectedCmd: "ADD_ONION hide_me_plz " +
|
|
"Port=9735,9735 ",
|
|
expectedErr: nil,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
tc := tc
|
|
|
|
if tc.cfg.Store != nil {
|
|
store.On("PrivateKey").Return(
|
|
testKey, tc.expectedErr,
|
|
)
|
|
}
|
|
|
|
controller := NewController("", tc.targetIPAddress, "")
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
cmd, _, err := controller.prepareAddOnion(tc.cfg)
|
|
require.Equal(t, tc.expectedErr, err)
|
|
require.Equal(t, tc.expectedCmd, cmd)
|
|
|
|
// Check that the mocker is satisfied.
|
|
store.AssertExpectations(t)
|
|
})
|
|
}
|
|
}
|
|
|
|
// mockStore implements a mock of the interface OnionStore.
|
|
type mockStore struct {
|
|
mock.Mock
|
|
}
|
|
|
|
// A compile-time constraint to ensure mockStore satisfies the OnionStore
|
|
// interface.
|
|
var _ OnionStore = (*mockStore)(nil)
|
|
|
|
func (m *mockStore) StorePrivateKey(key []byte) error {
|
|
args := m.Called(key)
|
|
return args.Error(0)
|
|
}
|
|
|
|
func (m *mockStore) PrivateKey() ([]byte, error) {
|
|
args := m.Called()
|
|
return []byte("hide_me_plz"), args.Error(1)
|
|
}
|
|
|
|
func (m *mockStore) DeletePrivateKey() error {
|
|
args := m.Called()
|
|
return args.Error(0)
|
|
}
|
|
|
|
type MockEncrypter struct{}
|
|
|
|
func (m MockEncrypter) EncryptPayloadToWriter(_ []byte, _ io.Writer) error {
|
|
return nil
|
|
}
|
|
|
|
func (m MockEncrypter) DecryptPayloadFromReader(_ io.Reader) ([]byte, error) {
|
|
return anotherKey, nil
|
|
}
|