2020-03-07 01:26:51 +01:00
|
|
|
package tor
|
|
|
|
|
|
|
|
import (
|
2021-09-23 11:03:28 +02:00
|
|
|
"errors"
|
2022-05-09 20:20:39 +02:00
|
|
|
"io"
|
2020-03-07 01:26:51 +01:00
|
|
|
"path/filepath"
|
|
|
|
"testing"
|
2021-09-23 11:03:28 +02:00
|
|
|
|
|
|
|
"github.com/stretchr/testify/mock"
|
|
|
|
"github.com/stretchr/testify/require"
|
2020-03-07 01:26:51 +01:00
|
|
|
)
|
|
|
|
|
2022-05-09 20:20:39 +02:00
|
|
|
var (
|
|
|
|
privateKey = []byte("RSA1024 hide_me_plz")
|
|
|
|
anotherKey = []byte("another_key")
|
|
|
|
)
|
|
|
|
|
|
|
|
// TestOnionFile tests that the File implementation of the OnionStore
|
2020-03-07 01:26:51 +01:00
|
|
|
// interface behaves as expected.
|
|
|
|
func TestOnionFile(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
2022-05-09 20:20:39 +02:00
|
|
|
tempDir := t.TempDir()
|
|
|
|
privateKeyPath := filepath.Join(tempDir, "secret")
|
|
|
|
mockEncrypter := MockEncrypter{}
|
2020-03-07 01:26:51 +01:00
|
|
|
|
|
|
|
// Create a new file-based onion store. A private key should not exist
|
|
|
|
// yet.
|
2022-05-09 20:20:39 +02:00
|
|
|
onionFile := NewOnionFile(
|
|
|
|
privateKeyPath, 0600, false, mockEncrypter,
|
|
|
|
)
|
|
|
|
_, err := onionFile.PrivateKey()
|
|
|
|
require.ErrorIs(t, err, ErrNoPrivateKey)
|
2020-03-07 01:26:51 +01:00
|
|
|
|
|
|
|
// Store the private key and ensure what's stored matches.
|
2022-05-09 20:20:39 +02:00
|
|
|
err = onionFile.StorePrivateKey(privateKey)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
storePrivateKey, err := onionFile.PrivateKey()
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, storePrivateKey, privateKey)
|
2020-03-07 01:26:51 +01:00
|
|
|
|
|
|
|
// Finally, delete the private key. We should no longer be able to
|
|
|
|
// retrieve it.
|
2022-05-09 20:20:39 +02:00
|
|
|
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)
|
2020-03-07 01:26:51 +01:00
|
|
|
}
|
2021-09-23 11:03:28 +02:00
|
|
|
|
|
|
|
// 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{}
|
2022-05-09 20:20:39 +02:00
|
|
|
store.On("PrivateKey").Return(testKey, nil)
|
2021-09-23 11:03:28 +02:00
|
|
|
|
|
|
|
// 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{}
|
2022-05-09 20:20:39 +02:00
|
|
|
store.On("PrivateKey").Return(nil, ErrNoPrivateKey)
|
2021-09-23 11:03:28 +02:00
|
|
|
|
|
|
|
// 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{}
|
2022-05-09 20:20:39 +02:00
|
|
|
store.On("PrivateKey").Return(nil, dummyErr)
|
2021-09-23 11:03:28 +02:00
|
|
|
|
|
|
|
// 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 {
|
2022-05-09 20:20:39 +02:00
|
|
|
store.On("PrivateKey").Return(
|
2021-09-23 11:03:28 +02:00
|
|
|
testKey, tc.expectedErr,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
controller := NewController("", tc.targetIPAddress, "")
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
2022-05-09 20:20:39 +02:00
|
|
|
cmd, _, err := controller.prepareAddOnion(tc.cfg)
|
2021-09-23 11:03:28 +02:00
|
|
|
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)
|
|
|
|
|
2022-05-09 20:20:39 +02:00
|
|
|
func (m *mockStore) StorePrivateKey(key []byte) error {
|
|
|
|
args := m.Called(key)
|
2021-09-23 11:03:28 +02:00
|
|
|
return args.Error(0)
|
|
|
|
}
|
|
|
|
|
2022-05-09 20:20:39 +02:00
|
|
|
func (m *mockStore) PrivateKey() ([]byte, error) {
|
|
|
|
args := m.Called()
|
|
|
|
return []byte("hide_me_plz"), args.Error(1)
|
2021-09-23 11:03:28 +02:00
|
|
|
}
|
|
|
|
|
2022-05-09 20:20:39 +02:00
|
|
|
func (m *mockStore) DeletePrivateKey() error {
|
|
|
|
args := m.Called()
|
2021-09-23 11:03:28 +02:00
|
|
|
return args.Error(0)
|
|
|
|
}
|
2022-05-09 20:20:39 +02:00
|
|
|
|
|
|
|
type MockEncrypter struct{}
|
|
|
|
|
|
|
|
func (m MockEncrypter) EncryptPayloadToWriter(_ []byte, _ io.Writer) error {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m MockEncrypter) DecryptPayloadFromReader(_ io.Reader) ([]byte, error) {
|
|
|
|
return anotherKey, nil
|
|
|
|
}
|