Merge pull request #6500 from voltagecloud/encrypt-tor-privkey
[tor] Add ability to encrypt the Tor private key on disk
This commit is contained in:
22 files changed
+298
-232
No files matched your search
@@ -9,6 +9,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/lightningnetwork/lnd/lnencrypt"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -184,7 +185,7 @@ func assertMultiEqual(t *testing.T, a, b *Multi) {
|
|||||||
func TestExtractMulti(t *testing.T) {
|
func TestExtractMulti(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
keyRing := &mockKeyRing{}
|
keyRing := &lnencrypt.MockKeyRing{}
|
||||||
|
|
||||||
// First, as prep, we'll create a single chan backup, then pack that
|
// First, as prep, we'll create a single chan backup, then pack that
|
||||||
// fully into a multi backup.
|
// fully into a multi backup.
|
||||||
|
|||||||
@@ -1,140 +0,0 @@
|
|||||||
package chanbackup
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/sha256"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
|
|
||||||
"github.com/lightningnetwork/lnd/keychain"
|
|
||||||
"golang.org/x/crypto/chacha20poly1305"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TODO(roasbeef): interface in front of?
|
|
||||||
|
|
||||||
// baseEncryptionKeyLoc is the KeyLocator that we'll use to derive the base
|
|
||||||
// encryption key used for encrypting all static channel backups. We use this
|
|
||||||
// to then derive the actual key that we'll use for encryption. We do this
|
|
||||||
// rather than using the raw key, as we assume that we can't obtain the raw
|
|
||||||
// keys, and we don't want to require that the HSM know our target cipher for
|
|
||||||
// encryption.
|
|
||||||
//
|
|
||||||
// TODO(roasbeef): possibly unique encrypt?
|
|
||||||
var baseEncryptionKeyLoc = keychain.KeyLocator{
|
|
||||||
Family: keychain.KeyFamilyStaticBackup,
|
|
||||||
Index: 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
// genEncryptionKey derives the key that we'll use to encrypt all of our static
|
|
||||||
// channel backups. The key itself, is the sha2 of a base key that we get from
|
|
||||||
// the keyring. We derive the key this way as we don't force the HSM (or any
|
|
||||||
// future abstractions) to be able to derive and know of the cipher that we'll
|
|
||||||
// use within our protocol.
|
|
||||||
func genEncryptionKey(keyRing keychain.KeyRing) ([]byte, error) {
|
|
||||||
// key = SHA256(baseKey)
|
|
||||||
baseKey, err := keyRing.DeriveKey(
|
|
||||||
baseEncryptionKeyLoc,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
encryptionKey := sha256.Sum256(
|
|
||||||
baseKey.PubKey.SerializeCompressed(),
|
|
||||||
)
|
|
||||||
|
|
||||||
// TODO(roasbeef): throw back in ECDH?
|
|
||||||
|
|
||||||
return encryptionKey[:], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// encryptPayloadToWriter attempts to write the set of bytes contained within
|
|
||||||
// the passed byes.Buffer into the passed io.Writer in an encrypted form. We
|
|
||||||
// use a 24-byte chachapoly AEAD instance with a randomized nonce that's
|
|
||||||
// pre-pended to the final payload and used as associated data in the AEAD. We
|
|
||||||
// use the passed keyRing to generate the encryption key, see genEncryptionKey
|
|
||||||
// for further details.
|
|
||||||
func encryptPayloadToWriter(payload bytes.Buffer, w io.Writer,
|
|
||||||
keyRing keychain.KeyRing) error {
|
|
||||||
|
|
||||||
// First, we'll derive the key that we'll use to encrypt the payload
|
|
||||||
// for safe storage without giving away the details of any of our
|
|
||||||
// channels. The final operation is:
|
|
||||||
//
|
|
||||||
// key = SHA256(baseKey)
|
|
||||||
encryptionKey, err := genEncryptionKey(keyRing)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Before encryption, we'll initialize our cipher with the target
|
|
||||||
// encryption key, and also read out our random 24-byte nonce we use
|
|
||||||
// for encryption. Note that we use NewX, not New, as the latter
|
|
||||||
// version requires a 12-byte nonce, not a 24-byte nonce.
|
|
||||||
cipher, err := chacha20poly1305.NewX(encryptionKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var nonce [chacha20poly1305.NonceSizeX]byte
|
|
||||||
if _, err := rand.Read(nonce[:]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finally, we encrypted the final payload, and write out our
|
|
||||||
// ciphertext with nonce pre-pended.
|
|
||||||
ciphertext := cipher.Seal(nil, nonce[:], payload.Bytes(), nonce[:])
|
|
||||||
|
|
||||||
if _, err := w.Write(nonce[:]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if _, err := w.Write(ciphertext); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// decryptPayloadFromReader attempts to decrypt the encrypted bytes within the
|
|
||||||
// passed io.Reader instance using the key derived from the passed keyRing. For
|
|
||||||
// further details regarding the key derivation protocol, see the
|
|
||||||
// genEncryptionKey method.
|
|
||||||
func decryptPayloadFromReader(payload io.Reader,
|
|
||||||
keyRing keychain.KeyRing) ([]byte, error) {
|
|
||||||
|
|
||||||
// First, we'll re-generate the encryption key that we use for all the
|
|
||||||
// SCBs.
|
|
||||||
encryptionKey, err := genEncryptionKey(keyRing)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Next, we'll read out the entire blob as we need to isolate the nonce
|
|
||||||
// from the rest of the ciphertext.
|
|
||||||
packedBackup, err := ioutil.ReadAll(payload)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(packedBackup) < chacha20poly1305.NonceSizeX {
|
|
||||||
return nil, fmt.Errorf("payload size too small, must be at "+
|
|
||||||
"least %v bytes", chacha20poly1305.NonceSizeX)
|
|
||||||
}
|
|
||||||
|
|
||||||
nonce := packedBackup[:chacha20poly1305.NonceSizeX]
|
|
||||||
ciphertext := packedBackup[chacha20poly1305.NonceSizeX:]
|
|
||||||
|
|
||||||
// Now that we have the cipher text and the nonce separated, we can go
|
|
||||||
// ahead and decrypt the final blob so we can properly serialized the
|
|
||||||
// SCB.
|
|
||||||
cipher, err := chacha20poly1305.NewX(encryptionKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
plaintext, err := cipher.Open(nil, nonce, ciphertext, nonce)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return plaintext, nil
|
|
||||||
}
|
|
||||||
+12
-2
@@ -6,6 +6,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/lightningnetwork/lnd/keychain"
|
"github.com/lightningnetwork/lnd/keychain"
|
||||||
|
"github.com/lightningnetwork/lnd/lnencrypt"
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -89,7 +90,12 @@ func (m Multi) PackToWriter(w io.Writer, keyRing keychain.KeyRing) error {
|
|||||||
|
|
||||||
// With the plaintext multi backup assembled, we'll now encrypt it
|
// With the plaintext multi backup assembled, we'll now encrypt it
|
||||||
// directly to the passed writer.
|
// directly to the passed writer.
|
||||||
return encryptPayloadToWriter(multiBackupBuffer, w, keyRing)
|
e, err := lnencrypt.KeyRingEncrypter(keyRing)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to generate encrypt key %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.EncryptPayloadToWriter(multiBackupBuffer.Bytes(), w)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnpackFromReader attempts to unpack (decrypt+deserialize) a packed
|
// UnpackFromReader attempts to unpack (decrypt+deserialize) a packed
|
||||||
@@ -99,7 +105,11 @@ func (m *Multi) UnpackFromReader(r io.Reader, keyRing keychain.KeyRing) error {
|
|||||||
// We'll attempt to read the entire packed backup, and also decrypt it
|
// We'll attempt to read the entire packed backup, and also decrypt it
|
||||||
// using the passed key ring which is expected to be able to derive the
|
// using the passed key ring which is expected to be able to derive the
|
||||||
// encryption keys.
|
// encryption keys.
|
||||||
plaintextBackup, err := decryptPayloadFromReader(r, keyRing)
|
e, err := lnencrypt.KeyRingEncrypter(keyRing)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to generate encrypt key %v", err)
|
||||||
|
}
|
||||||
|
plaintextBackup, err := e.DecryptPayloadFromReader(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/lightningnetwork/lnd/lnencrypt"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -27,7 +28,7 @@ func TestMultiPackUnpack(t *testing.T) {
|
|||||||
multi.StaticBackups = append(multi.StaticBackups, single)
|
multi.StaticBackups = append(multi.StaticBackups, single)
|
||||||
}
|
}
|
||||||
|
|
||||||
keyRing := &mockKeyRing{}
|
keyRing := &lnencrypt.MockKeyRing{}
|
||||||
|
|
||||||
versionTestCases := []struct {
|
versionTestCases := []struct {
|
||||||
// version is the pack/unpack version that we should use to
|
// version is the pack/unpack version that we should use to
|
||||||
@@ -93,14 +94,17 @@ func TestMultiPackUnpack(t *testing.T) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
encrypter, err := lnencrypt.KeyRingEncrypter(keyRing)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Next, we'll make a fake packed multi, it'll have an
|
// Next, we'll make a fake packed multi, it'll have an
|
||||||
// unknown version relative to what's implemented atm.
|
// unknown version relative to what's implemented atm.
|
||||||
var fakePackedMulti bytes.Buffer
|
var fakePackedMulti bytes.Buffer
|
||||||
fakeRawMulti := bytes.NewBuffer(
|
fakeRawMulti := bytes.NewBuffer(
|
||||||
bytes.Repeat([]byte{99}, 20),
|
bytes.Repeat([]byte{99}, 20),
|
||||||
)
|
)
|
||||||
err := encryptPayloadToWriter(
|
err = encrypter.EncryptPayloadToWriter(
|
||||||
*fakeRawMulti, &fakePackedMulti, keyRing,
|
fakeRawMulti.Bytes(), &fakePackedMulti,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to pack fake multi; %v", err)
|
t.Fatalf("unable to pack fake multi; %v", err)
|
||||||
@@ -124,7 +128,7 @@ func TestMultiPackUnpack(t *testing.T) {
|
|||||||
func TestPackedMultiUnpack(t *testing.T) {
|
func TestPackedMultiUnpack(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
keyRing := &mockKeyRing{}
|
keyRing := &lnencrypt.MockKeyRing{}
|
||||||
|
|
||||||
// First, we'll make a new unpacked multi with a random channel.
|
// First, we'll make a new unpacked multi with a random channel.
|
||||||
testChannel, err := genRandomOpenChannelShell()
|
testChannel, err := genRandomOpenChannelShell()
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
|
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
"github.com/lightningnetwork/lnd/keychain"
|
"github.com/lightningnetwork/lnd/keychain"
|
||||||
|
"github.com/lightningnetwork/lnd/lnencrypt"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -80,7 +81,7 @@ func (m *mockChannelNotifier) SubscribeChans(chans map[wire.OutPoint]struct{}) (
|
|||||||
func TestNewSubSwapperSubscribeFail(t *testing.T) {
|
func TestNewSubSwapperSubscribeFail(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
keyRing := &mockKeyRing{}
|
keyRing := &lnencrypt.MockKeyRing{}
|
||||||
|
|
||||||
var swapper mockSwapper
|
var swapper mockSwapper
|
||||||
chanNotifier := mockChannelNotifier{
|
chanNotifier := mockChannelNotifier{
|
||||||
@@ -152,7 +153,7 @@ func assertExpectedBackupSwap(t *testing.T, swapper *mockSwapper,
|
|||||||
func TestSubSwapperIdempotentStartStop(t *testing.T) {
|
func TestSubSwapperIdempotentStartStop(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
keyRing := &mockKeyRing{}
|
keyRing := &lnencrypt.MockKeyRing{}
|
||||||
|
|
||||||
var chanNotifier mockChannelNotifier
|
var chanNotifier mockChannelNotifier
|
||||||
|
|
||||||
@@ -181,7 +182,7 @@ func TestSubSwapperIdempotentStartStop(t *testing.T) {
|
|||||||
func TestSubSwapperUpdater(t *testing.T) {
|
func TestSubSwapperUpdater(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
keyRing := &mockKeyRing{}
|
keyRing := &lnencrypt.MockKeyRing{}
|
||||||
chanNotifier := newMockChannelNotifier()
|
chanNotifier := newMockChannelNotifier()
|
||||||
swapper := newMockSwapper(keyRing)
|
swapper := newMockSwapper(keyRing)
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/btcec/v2"
|
"github.com/btcsuite/btcd/btcec/v2"
|
||||||
|
"github.com/lightningnetwork/lnd/lnencrypt"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -49,7 +50,7 @@ func (m *mockPeerConnector) ConnectPeer(node *btcec.PublicKey,
|
|||||||
func TestUnpackAndRecoverSingles(t *testing.T) {
|
func TestUnpackAndRecoverSingles(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
keyRing := &mockKeyRing{}
|
keyRing := &lnencrypt.MockKeyRing{}
|
||||||
|
|
||||||
// First, we'll create a number of single chan backups that we'll
|
// First, we'll create a number of single chan backups that we'll
|
||||||
// shortly back to so we can begin our recovery attempt.
|
// shortly back to so we can begin our recovery attempt.
|
||||||
@@ -123,7 +124,7 @@ func TestUnpackAndRecoverSingles(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If we modify the keyRing, then unpacking should fail.
|
// If we modify the keyRing, then unpacking should fail.
|
||||||
keyRing.fail = true
|
keyRing.Fail = true
|
||||||
err = UnpackAndRecoverSingles(
|
err = UnpackAndRecoverSingles(
|
||||||
packedBackups, keyRing, &chanRestorer, &peerConnector,
|
packedBackups, keyRing, &chanRestorer, &peerConnector,
|
||||||
)
|
)
|
||||||
@@ -139,7 +140,7 @@ func TestUnpackAndRecoverSingles(t *testing.T) {
|
|||||||
func TestUnpackAndRecoverMulti(t *testing.T) {
|
func TestUnpackAndRecoverMulti(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
keyRing := &mockKeyRing{}
|
keyRing := &lnencrypt.MockKeyRing{}
|
||||||
|
|
||||||
// First, we'll create a number of single chan backups that we'll
|
// First, we'll create a number of single chan backups that we'll
|
||||||
// shortly back to so we can begin our recovery attempt.
|
// shortly back to so we can begin our recovery attempt.
|
||||||
@@ -217,7 +218,7 @@ func TestUnpackAndRecoverMulti(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If we modify the keyRing, then unpacking should fail.
|
// If we modify the keyRing, then unpacking should fail.
|
||||||
keyRing.fail = true
|
keyRing.Fail = true
|
||||||
err = UnpackAndRecoverMulti(
|
err = UnpackAndRecoverMulti(
|
||||||
packedMulti, keyRing, &chanRestorer, &peerConnector,
|
packedMulti, keyRing, &chanRestorer, &peerConnector,
|
||||||
)
|
)
|
||||||
|
|||||||
+15
-6
@@ -12,6 +12,7 @@ import (
|
|||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
"github.com/lightningnetwork/lnd/keychain"
|
"github.com/lightningnetwork/lnd/keychain"
|
||||||
|
"github.com/lightningnetwork/lnd/lnencrypt"
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -333,10 +334,10 @@ func (s *Single) Serialize(w io.Writer) error {
|
|||||||
// global counter to use as a sequence number for nonces, and want to ensure
|
// global counter to use as a sequence number for nonces, and want to ensure
|
||||||
// that we're able to decrypt these blobs without any additional context. We
|
// that we're able to decrypt these blobs without any additional context. We
|
||||||
// derive the key that we use for encryption via a SHA2 operation of the with
|
// derive the key that we use for encryption via a SHA2 operation of the with
|
||||||
// the golden keychain.KeyFamilyStaticBackup base encryption key. We then take
|
// the golden keychain.KeyFamilyBaseEncryption base encryption key. We then
|
||||||
// the serialized resulting shared secret point, and hash it using sha256 to
|
// take the serialized resulting shared secret point, and hash it using sha256
|
||||||
// obtain the key that we'll use for encryption. When using the AEAD, we pass
|
// to obtain the key that we'll use for encryption. When using the AEAD, we
|
||||||
// the nonce as associated data such that we'll be able to package the two
|
// pass the nonce as associated data such that we'll be able to package the two
|
||||||
// together for storage. Before writing out the encrypted payload, we prepend
|
// together for storage. Before writing out the encrypted payload, we prepend
|
||||||
// the nonce to the final blob.
|
// the nonce to the final blob.
|
||||||
func (s *Single) PackToWriter(w io.Writer, keyRing keychain.KeyRing) error {
|
func (s *Single) PackToWriter(w io.Writer, keyRing keychain.KeyRing) error {
|
||||||
@@ -351,7 +352,11 @@ func (s *Single) PackToWriter(w io.Writer, keyRing keychain.KeyRing) error {
|
|||||||
// Finally, we'll encrypt the raw serialized SCB (using the nonce as
|
// Finally, we'll encrypt the raw serialized SCB (using the nonce as
|
||||||
// associated data), and write out the ciphertext prepend with the
|
// associated data), and write out the ciphertext prepend with the
|
||||||
// nonce that we used to the passed io.Reader.
|
// nonce that we used to the passed io.Reader.
|
||||||
return encryptPayloadToWriter(rawBytes, w, keyRing)
|
e, err := lnencrypt.KeyRingEncrypter(keyRing)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to generate encrypt key %v", err)
|
||||||
|
}
|
||||||
|
return e.EncryptPayloadToWriter(rawBytes.Bytes(), w)
|
||||||
}
|
}
|
||||||
|
|
||||||
// readLocalKeyDesc reads a KeyDescriptor encoded within an unpacked Single.
|
// readLocalKeyDesc reads a KeyDescriptor encoded within an unpacked Single.
|
||||||
@@ -528,7 +533,11 @@ func (s *Single) Deserialize(r io.Reader) error {
|
|||||||
// payload for whatever reason (wrong key, wrong nonce, etc), then this method
|
// payload for whatever reason (wrong key, wrong nonce, etc), then this method
|
||||||
// will return an error.
|
// will return an error.
|
||||||
func (s *Single) UnpackFromReader(r io.Reader, keyRing keychain.KeyRing) error {
|
func (s *Single) UnpackFromReader(r io.Reader, keyRing keychain.KeyRing) error {
|
||||||
plaintext, err := decryptPayloadFromReader(r, keyRing)
|
e, err := lnencrypt.KeyRingEncrypter(keyRing)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to generate key decrypter %v", err)
|
||||||
|
}
|
||||||
|
plaintext, err := e.DecryptPayloadFromReader(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
"github.com/lightningnetwork/lnd/keychain"
|
"github.com/lightningnetwork/lnd/keychain"
|
||||||
|
"github.com/lightningnetwork/lnd/lnencrypt"
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
"github.com/lightningnetwork/lnd/shachain"
|
"github.com/lightningnetwork/lnd/shachain"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@@ -207,7 +208,7 @@ func TestSinglePackUnpack(t *testing.T) {
|
|||||||
|
|
||||||
singleChanBackup := NewSingle(channel, []net.Addr{addr1, addr2})
|
singleChanBackup := NewSingle(channel, []net.Addr{addr1, addr2})
|
||||||
|
|
||||||
keyRing := &mockKeyRing{}
|
keyRing := &lnencrypt.MockKeyRing{}
|
||||||
|
|
||||||
versionTestCases := []struct {
|
versionTestCases := []struct {
|
||||||
// version is the pack/unpack version that we should use to
|
// version is the pack/unpack version that we should use to
|
||||||
@@ -312,7 +313,7 @@ func TestSinglePackUnpack(t *testing.T) {
|
|||||||
func TestPackedSinglesUnpack(t *testing.T) {
|
func TestPackedSinglesUnpack(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
keyRing := &mockKeyRing{}
|
keyRing := &lnencrypt.MockKeyRing{}
|
||||||
|
|
||||||
// To start, we'll create 10 new singles, and them assemble their
|
// To start, we'll create 10 new singles, and them assemble their
|
||||||
// packed forms into a slice.
|
// packed forms into a slice.
|
||||||
@@ -361,7 +362,7 @@ func TestPackedSinglesUnpack(t *testing.T) {
|
|||||||
func TestSinglePackStaticChanBackups(t *testing.T) {
|
func TestSinglePackStaticChanBackups(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
keyRing := &mockKeyRing{}
|
keyRing := &lnencrypt.MockKeyRing{}
|
||||||
|
|
||||||
// First, we'll create a set of random single, and along the way,
|
// First, we'll create a set of random single, and along the way,
|
||||||
// create a map that will let us look up each single by its chan point.
|
// create a map that will let us look up each single by its chan point.
|
||||||
@@ -407,8 +408,9 @@ func TestSinglePackStaticChanBackups(t *testing.T) {
|
|||||||
|
|
||||||
// If we attempt to pack again, but force the key ring to fail, then
|
// If we attempt to pack again, but force the key ring to fail, then
|
||||||
// the entire method should fail.
|
// the entire method should fail.
|
||||||
|
keyRing.Fail = true
|
||||||
_, err = PackStaticChanBackups(
|
_, err = PackStaticChanBackups(
|
||||||
unpackedSingles, &mockKeyRing{true},
|
unpackedSingles, &lnencrypt.MockKeyRing{Fail: true},
|
||||||
)
|
)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatalf("pack attempt should fail")
|
t.Fatalf("pack attempt should fail")
|
||||||
@@ -432,7 +434,7 @@ func TestSingleUnconfirmedChannel(t *testing.T) {
|
|||||||
channel.FundingBroadcastHeight = fundingBroadcastHeight
|
channel.FundingBroadcastHeight = fundingBroadcastHeight
|
||||||
|
|
||||||
singleChanBackup := NewSingle(channel, []net.Addr{addr1, addr2})
|
singleChanBackup := NewSingle(channel, []net.Addr{addr1, addr2})
|
||||||
keyRing := &mockKeyRing{}
|
keyRing := &lnencrypt.MockKeyRing{}
|
||||||
|
|
||||||
// Pack it and then unpack it again to make sure everything is written
|
// Pack it and then unpack it again to make sure everything is written
|
||||||
// correctly, then check that the block height of the unpacked
|
// correctly, then check that the block height of the unpacked
|
||||||
|
|||||||
@@ -182,3 +182,8 @@ base directory. This will allow `lnd` to recreate the same hidden service upon
|
|||||||
restart. If you wish to generate a new onion service, you can simply delete this
|
restart. If you wish to generate a new onion service, you can simply delete this
|
||||||
file. The path to this private key file can also be modified with the
|
file. The path to this private key file can also be modified with the
|
||||||
`--tor.privatekeypath` argument.
|
`--tor.privatekeypath` argument.
|
||||||
|
|
||||||
|
You can optionally encrypt the Tor private key by using the `--tor.encryptkey`
|
||||||
|
flag. This will still write to the same private key files. However instead of
|
||||||
|
writing the plaintext private key, `lnd` encrypts the private key using the
|
||||||
|
wallet's seed and writes the encrypted blob to the file.
|
||||||
@@ -58,8 +58,10 @@ minimum version needed to build the project.
|
|||||||
[With the module updated](https://github.com/lightningnetwork/lnd/pull/6836),
|
[With the module updated](https://github.com/lightningnetwork/lnd/pull/6836),
|
||||||
`lnd` now parses Tor control port messages correctly.
|
`lnd` now parses Tor control port messages correctly.
|
||||||
|
|
||||||
* [Update Tor module](https://github.com/lightningnetwork/lnd/pull/6526) to
|
* [Add option to encrypt Tor private
|
||||||
allow the option to encrypt the private key on disk.
|
key](https://github.com/lightningnetwork/lnd/pull/6500), and [update the Tor
|
||||||
|
module](https://github.com/lightningnetwork/lnd/pull/6526) to pave the way for
|
||||||
|
this functionality.
|
||||||
|
|
||||||
* [Fixed potential data race on funding manager
|
* [Fixed potential data race on funding manager
|
||||||
restart](https://github.com/lightningnetwork/lnd/pull/6929).
|
restart](https://github.com/lightningnetwork/lnd/pull/6929).
|
||||||
@@ -110,6 +112,7 @@ minimum version needed to build the project.
|
|||||||
* Elle Mouton
|
* Elle Mouton
|
||||||
* ErikEk
|
* ErikEk
|
||||||
* Eugene Siegel
|
* Eugene Siegel
|
||||||
|
* Graham Krizek
|
||||||
* hieblmi
|
* hieblmi
|
||||||
* Jesse de Wit
|
* Jesse de Wit
|
||||||
* Matt Morehouse
|
* Matt Morehouse
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ require (
|
|||||||
github.com/lightningnetwork/lnd/queue v1.1.0
|
github.com/lightningnetwork/lnd/queue v1.1.0
|
||||||
github.com/lightningnetwork/lnd/ticker v1.1.0
|
github.com/lightningnetwork/lnd/ticker v1.1.0
|
||||||
github.com/lightningnetwork/lnd/tlv v1.0.3
|
github.com/lightningnetwork/lnd/tlv v1.0.3
|
||||||
github.com/lightningnetwork/lnd/tor v1.0.2
|
github.com/lightningnetwork/lnd/tor v1.1.0
|
||||||
github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796
|
github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796
|
||||||
github.com/miekg/dns v1.1.43
|
github.com/miekg/dns v1.1.43
|
||||||
github.com/prometheus/client_golang v1.11.0
|
github.com/prometheus/client_golang v1.11.0
|
||||||
|
|||||||
@@ -465,6 +465,8 @@ github.com/lightningnetwork/lnd/tlv v1.0.3/go.mod h1:dzR/aZetBri+ZY/fHbwV06fNn/3
|
|||||||
github.com/lightningnetwork/lnd/tor v1.0.0/go.mod h1:RDtaAdwfAm+ONuPYwUhNIH1RAvKPv+75lHPOegUcz64=
|
github.com/lightningnetwork/lnd/tor v1.0.0/go.mod h1:RDtaAdwfAm+ONuPYwUhNIH1RAvKPv+75lHPOegUcz64=
|
||||||
github.com/lightningnetwork/lnd/tor v1.0.2 h1:GlumRkKdzXCX0AIvIi2UXKpeY1Q4RT7Lz/CfGpKSLrU=
|
github.com/lightningnetwork/lnd/tor v1.0.2 h1:GlumRkKdzXCX0AIvIi2UXKpeY1Q4RT7Lz/CfGpKSLrU=
|
||||||
github.com/lightningnetwork/lnd/tor v1.0.2/go.mod h1:RDtaAdwfAm+ONuPYwUhNIH1RAvKPv+75lHPOegUcz64=
|
github.com/lightningnetwork/lnd/tor v1.0.2/go.mod h1:RDtaAdwfAm+ONuPYwUhNIH1RAvKPv+75lHPOegUcz64=
|
||||||
|
github.com/lightningnetwork/lnd/tor v1.1.0 h1:iXO7fSzjxTI+p88KmtpbuyuRJeNfgtpl9QeaAliILXE=
|
||||||
|
github.com/lightningnetwork/lnd/tor v1.1.0/go.mod h1:RDtaAdwfAm+ONuPYwUhNIH1RAvKPv+75lHPOegUcz64=
|
||||||
github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796 h1:sjOGyegMIhvgfq5oaue6Td+hxZuf3tDC8lAPrFldqFw=
|
github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796 h1:sjOGyegMIhvgfq5oaue6Td+hxZuf3tDC8lAPrFldqFw=
|
||||||
github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796/go.mod h1:3p7ZTf9V1sNPI5H8P3NkTFF4LuwMdPl2DodF60qAKqY=
|
github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796/go.mod h1:3p7ZTf9V1sNPI5H8P3NkTFF4LuwMdPl2DodF60qAKqY=
|
||||||
github.com/ltcsuite/ltcutil v0.0.0-20181217130922-17f3b04680b6/go.mod h1:8Vg/LTOO0KYa/vlHWJ6XZAevPQThGH5sufO0Hrou/lA=
|
github.com/ltcsuite/ltcutil v0.0.0-20181217130922-17f3b04680b6/go.mod h1:8Vg/LTOO0KYa/vlHWJ6XZAevPQThGH5sufO0Hrou/lA=
|
||||||
|
|||||||
@@ -102,12 +102,11 @@ const (
|
|||||||
// p2p level (BOLT-0008).
|
// p2p level (BOLT-0008).
|
||||||
KeyFamilyNodeKey KeyFamily = 6
|
KeyFamilyNodeKey KeyFamily = 6
|
||||||
|
|
||||||
// KeyFamilyStaticBackup is the family of keys that will be used to
|
// KeyFamilyBaseEncryption is the family of keys that will be used to
|
||||||
// derive keys that we use to encrypt and decrypt our set of static
|
// derive keys that we use to encrypt and decrypt any general blob data
|
||||||
// backups. These backups may either be stored within watch towers for
|
// like static channel backups and the TLS private key. Often used when
|
||||||
// a payment, or self stored on disk in a single file containing all
|
// encrypting files on disk.
|
||||||
// the static channel backups.
|
KeyFamilyBaseEncryption KeyFamily = 7
|
||||||
KeyFamilyStaticBackup KeyFamily = 7
|
|
||||||
|
|
||||||
// KeyFamilyTowerSession is the family of keys that will be used to
|
// KeyFamilyTowerSession is the family of keys that will be used to
|
||||||
// derive session keys when negotiating sessions with watchtowers. The
|
// derive session keys when negotiating sessions with watchtowers. The
|
||||||
@@ -133,7 +132,7 @@ var VersionZeroKeyFamilies = []KeyFamily{
|
|||||||
KeyFamilyDelayBase,
|
KeyFamilyDelayBase,
|
||||||
KeyFamilyRevocationRoot,
|
KeyFamilyRevocationRoot,
|
||||||
KeyFamilyNodeKey,
|
KeyFamilyNodeKey,
|
||||||
KeyFamilyStaticBackup,
|
KeyFamilyBaseEncryption,
|
||||||
KeyFamilyTowerSession,
|
KeyFamilyTowerSession,
|
||||||
KeyFamilyTowerID,
|
KeyFamilyTowerID,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,5 +13,6 @@ type Tor struct {
|
|||||||
V2 bool `long:"v2" description:"Automatically set up a v2 onion service to listen for inbound connections"`
|
V2 bool `long:"v2" description:"Automatically set up a v2 onion service to listen for inbound connections"`
|
||||||
V3 bool `long:"v3" description:"Automatically set up a v3 onion service to listen for inbound connections"`
|
V3 bool `long:"v3" description:"Automatically set up a v3 onion service to listen for inbound connections"`
|
||||||
PrivateKeyPath string `long:"privatekeypath" description:"The path to the private key of the onion service being created"`
|
PrivateKeyPath string `long:"privatekeypath" description:"The path to the private key of the onion service being created"`
|
||||||
|
EncryptKey bool `long:"encryptkey" description:"Encrypts the Tor private key file on disk"`
|
||||||
WatchtowerKeyPath string `long:"watchtowerkeypath" description:"The path to the private key of the watchtower onion service being created"`
|
WatchtowerKeyPath string `long:"watchtowerkeypath" description:"The path to the private key of the watchtower onion service being created"`
|
||||||
}
|
}
|
||||||
@@ -474,6 +474,8 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg,
|
|||||||
if torController != nil {
|
if torController != nil {
|
||||||
wtCfg.TorController = torController
|
wtCfg.TorController = torController
|
||||||
wtCfg.WatchtowerKeyPath = cfg.Tor.WatchtowerKeyPath
|
wtCfg.WatchtowerKeyPath = cfg.Tor.WatchtowerKeyPath
|
||||||
|
wtCfg.EncryptKey = cfg.Tor.EncryptKey
|
||||||
|
wtCfg.KeyRing = activeChainControl.KeyRing
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case cfg.Tor.V2:
|
case cfg.Tor.V2:
|
||||||
|
|||||||
@@ -0,0 +1,139 @@
|
|||||||
|
package lnencrypt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
|
"github.com/lightningnetwork/lnd/keychain"
|
||||||
|
"golang.org/x/crypto/chacha20poly1305"
|
||||||
|
)
|
||||||
|
|
||||||
|
// baseEncryptionKeyLoc is the KeyLocator that we'll use to derive the base
|
||||||
|
// encryption key used for encrypting all payloads. We use this to then
|
||||||
|
// derive the actual key that we'll use for encryption. We do this
|
||||||
|
// rather than using the raw key, as we assume that we can't obtain the raw
|
||||||
|
// keys, and we don't want to require that the HSM know our target cipher for
|
||||||
|
// encryption.
|
||||||
|
//
|
||||||
|
// TODO(roasbeef): possibly unique encrypt?
|
||||||
|
var baseEncryptionKeyLoc = keychain.KeyLocator{
|
||||||
|
Family: keychain.KeyFamilyBaseEncryption,
|
||||||
|
Index: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncrypterDecrypter is an interface representing an object that encrypts or
|
||||||
|
// decrypts data.
|
||||||
|
type EncrypterDecrypter interface {
|
||||||
|
// EncryptPayloadToWriter attempts to write the set of provided bytes
|
||||||
|
// into the passed io.Writer in an encrypted form.
|
||||||
|
EncryptPayloadToWriter([]byte, io.Writer) error
|
||||||
|
|
||||||
|
// DecryptPayloadFromReader attempts to decrypt the encrypted bytes
|
||||||
|
// within the passed io.Reader instance using the key derived from
|
||||||
|
// the passed keyRing.
|
||||||
|
DecryptPayloadFromReader(io.Reader) ([]byte, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypter is a struct responsible for encrypting and decrypting data.
|
||||||
|
type Encrypter struct {
|
||||||
|
encryptionKey []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeyRingEncrypter derives an encryption key to encrypt all our files that are
|
||||||
|
// written to disk and returns an Encrypter object holding the key.
|
||||||
|
//
|
||||||
|
// The key itself, is the sha2 of a base key that we get from the keyring. We
|
||||||
|
// derive the key this way as we don't force the HSM (or any future
|
||||||
|
// abstractions) to be able to derive and know of the cipher that we'll use
|
||||||
|
// within our protocol.
|
||||||
|
func KeyRingEncrypter(keyRing keychain.KeyRing) (*Encrypter, error) {
|
||||||
|
// key = SHA256(baseKey)
|
||||||
|
baseKey, err := keyRing.DeriveKey(
|
||||||
|
baseEncryptionKeyLoc,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
encryptionKey := sha256.Sum256(
|
||||||
|
baseKey.PubKey.SerializeCompressed(),
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO(roasbeef): throw back in ECDH?
|
||||||
|
|
||||||
|
return &Encrypter{
|
||||||
|
encryptionKey: encryptionKey[:],
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncryptPayloadToWriter attempts to write the set of provided bytes into the
|
||||||
|
// passed io.Writer in an encrypted form. We use a 24-byte chachapoly AEAD
|
||||||
|
// instance with a randomized nonce that's pre-pended to the final payload and
|
||||||
|
// used as associated data in the AEAD.
|
||||||
|
func (e Encrypter) EncryptPayloadToWriter(payload []byte,
|
||||||
|
w io.Writer) error {
|
||||||
|
|
||||||
|
// Before encryption, we'll initialize our cipher with the target
|
||||||
|
// encryption key, and also read out our random 24-byte nonce we use
|
||||||
|
// for encryption. Note that we use NewX, not New, as the latter
|
||||||
|
// version requires a 12-byte nonce, not a 24-byte nonce.
|
||||||
|
cipher, err := chacha20poly1305.NewX(e.encryptionKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var nonce [chacha20poly1305.NonceSizeX]byte
|
||||||
|
if _, err := rand.Read(nonce[:]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, we encrypted the final payload, and write out our
|
||||||
|
// ciphertext with nonce pre-pended.
|
||||||
|
ciphertext := cipher.Seal(nil, nonce[:], payload, nonce[:])
|
||||||
|
|
||||||
|
if _, err := w.Write(nonce[:]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := w.Write(ciphertext); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecryptPayloadFromReader attempts to decrypt the encrypted bytes within the
|
||||||
|
// passed io.Reader instance using the key derived from the passed keyRing. For
|
||||||
|
// further details regarding the key derivation protocol, see the
|
||||||
|
// KeyRingEncrypter function.
|
||||||
|
func (e Encrypter) DecryptPayloadFromReader(payload io.Reader) ([]byte,
|
||||||
|
error) {
|
||||||
|
|
||||||
|
// Next, we'll read out the entire blob as we need to isolate the nonce
|
||||||
|
// from the rest of the ciphertext.
|
||||||
|
packedPayload, err := ioutil.ReadAll(payload)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(packedPayload) < chacha20poly1305.NonceSizeX {
|
||||||
|
return nil, fmt.Errorf("payload size too small, must be at "+
|
||||||
|
"least %v bytes", chacha20poly1305.NonceSizeX)
|
||||||
|
}
|
||||||
|
|
||||||
|
nonce := packedPayload[:chacha20poly1305.NonceSizeX]
|
||||||
|
ciphertext := packedPayload[chacha20poly1305.NonceSizeX:]
|
||||||
|
|
||||||
|
// Now that we have the cipher text and the nonce separated, we can go
|
||||||
|
// ahead and decrypt the final blob so we can properly serialize.
|
||||||
|
cipher, err := chacha20poly1305.NewX(e.encryptionKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
plaintext, err := cipher.Open(nil, nonce, ciphertext, nonce)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return plaintext, nil
|
||||||
|
}
|
||||||
@@ -1,41 +1,12 @@
|
|||||||
package chanbackup
|
package lnencrypt
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/btcec/v2"
|
"github.com/stretchr/testify/require"
|
||||||
"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(testWalletPrivKey)
|
|
||||||
return keychain.KeyDescriptor{
|
|
||||||
PubKey: pub,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestEncryptDecryptPayload tests that given a static key, we're able to
|
// TestEncryptDecryptPayload tests that given a static key, we're able to
|
||||||
// properly decrypt and encrypted payload. We also test that we'll reject a
|
// properly decrypt and encrypted payload. We also test that we'll reject a
|
||||||
// ciphertext that has been modified.
|
// ciphertext that has been modified.
|
||||||
@@ -81,15 +52,16 @@ func TestEncryptDecryptPayload(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
keyRing := &mockKeyRing{}
|
keyRing := &MockKeyRing{}
|
||||||
|
|
||||||
for i, payloadCase := range payloadCases {
|
for i, payloadCase := range payloadCases {
|
||||||
var cipherBuffer bytes.Buffer
|
var cipherBuffer bytes.Buffer
|
||||||
|
encrypter, err := KeyRingEncrypter(keyRing)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
// First, we'll encrypt the passed payload with our scheme.
|
// First, we'll encrypt the passed payload with our scheme.
|
||||||
payloadReader := bytes.NewBuffer(payloadCase.plaintext)
|
err = encrypter.EncryptPayloadToWriter(
|
||||||
err := encryptPayloadToWriter(
|
payloadCase.plaintext, &cipherBuffer,
|
||||||
*payloadReader, &cipherBuffer, keyRing,
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable encrypt paylaod: %v", err)
|
t.Fatalf("unable encrypt paylaod: %v", err)
|
||||||
@@ -107,7 +79,9 @@ func TestEncryptDecryptPayload(t *testing.T) {
|
|||||||
cipherBuffer.Write(cipherText)
|
cipherBuffer.Write(cipherText)
|
||||||
}
|
}
|
||||||
|
|
||||||
plaintext, err := decryptPayloadFromReader(&cipherBuffer, keyRing)
|
plaintext, err := encrypter.DecryptPayloadFromReader(
|
||||||
|
&cipherBuffer,
|
||||||
|
)
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
// If this was meant to be a valid decryption, but we failed,
|
// If this was meant to be a valid decryption, but we failed,
|
||||||
@@ -131,26 +105,13 @@ func TestEncryptDecryptPayload(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestInvalidKeyEncryption tests that encryption fails if we're unable to
|
// TestInvalidKeyGeneration tests that key generation fails when deriving the
|
||||||
// obtain a valid key.
|
// key fails.
|
||||||
func TestInvalidKeyEncryption(t *testing.T) {
|
func TestInvalidKeyGeneration(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
var b bytes.Buffer
|
_, err := KeyRingEncrypter(&MockKeyRing{true})
|
||||||
err := encryptPayloadToWriter(b, &b, &mockKeyRing{true})
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatalf("expected error due to fail key gen")
|
t.Fatal("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")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package lnencrypt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/btcec/v2"
|
||||||
|
"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(testWalletPrivKey)
|
||||||
|
return keychain.KeyDescriptor{
|
||||||
|
PubKey: pub,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
@@ -935,6 +935,8 @@ litecoin.node=ltcd
|
|||||||
;The path to the private key of the watchtower onion service being created
|
;The path to the private key of the watchtower onion service being created
|
||||||
; tor.watchtowerkeypath=/other/path/
|
; tor.watchtowerkeypath=/other/path/
|
||||||
|
|
||||||
|
; Instructs lnd to encrypt the private key using the wallet's seed.
|
||||||
|
; tor.encryptkey=true
|
||||||
|
|
||||||
[watchtower]
|
[watchtower]
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/keychain"
|
"github.com/lightningnetwork/lnd/keychain"
|
||||||
"github.com/lightningnetwork/lnd/kvdb"
|
"github.com/lightningnetwork/lnd/kvdb"
|
||||||
"github.com/lightningnetwork/lnd/lncfg"
|
"github.com/lightningnetwork/lnd/lncfg"
|
||||||
|
"github.com/lightningnetwork/lnd/lnencrypt"
|
||||||
"github.com/lightningnetwork/lnd/lnpeer"
|
"github.com/lightningnetwork/lnd/lnpeer"
|
||||||
"github.com/lightningnetwork/lnd/lnrpc"
|
"github.com/lightningnetwork/lnd/lnrpc"
|
||||||
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
|
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
|
||||||
@@ -2766,13 +2767,21 @@ func (s *server) createNewHiddenService() error {
|
|||||||
listenPorts = append(listenPorts, port)
|
listenPorts = append(listenPorts, port)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
encrypter, err := lnencrypt.KeyRingEncrypter(s.cc.KeyRing)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Once the port mapping has been set, we can go ahead and automatically
|
// Once the port mapping has been set, we can go ahead and automatically
|
||||||
// create our onion service. The service's private key will be saved to
|
// create our onion service. The service's private key will be saved to
|
||||||
// disk in order to regain access to this service when restarting `lnd`.
|
// disk in order to regain access to this service when restarting `lnd`.
|
||||||
onionCfg := tor.AddOnionConfig{
|
onionCfg := tor.AddOnionConfig{
|
||||||
VirtualPort: defaultPeerPort,
|
VirtualPort: defaultPeerPort,
|
||||||
TargetPorts: listenPorts,
|
TargetPorts: listenPorts,
|
||||||
Store: tor.NewOnionFile(s.cfg.Tor.PrivateKeyPath, 0600),
|
Store: tor.NewOnionFile(
|
||||||
|
s.cfg.Tor.PrivateKeyPath, 0600, s.cfg.Tor.EncryptKey,
|
||||||
|
encrypter,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
|
|||||||
@@ -98,6 +98,12 @@ type Config struct {
|
|||||||
// for a watchtower hidden service should be stored.
|
// for a watchtower hidden service should be stored.
|
||||||
WatchtowerKeyPath string
|
WatchtowerKeyPath string
|
||||||
|
|
||||||
|
// EncryptKey will encrypt the Tor private key on disk.
|
||||||
|
EncryptKey bool
|
||||||
|
|
||||||
|
// KeyRing is the KeyRing to use when encrypting the Tor private key.
|
||||||
|
KeyRing keychain.KeyRing
|
||||||
|
|
||||||
// Type specifies the hidden service type (V2 or V3) that the watchtower
|
// Type specifies the hidden service type (V2 or V3) that the watchtower
|
||||||
// will create.
|
// will create.
|
||||||
Type tor.OnionType
|
Type tor.OnionType
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
|
|
||||||
"github.com/btcsuite/btcd/btcec/v2"
|
"github.com/btcsuite/btcd/btcec/v2"
|
||||||
"github.com/lightningnetwork/lnd/brontide"
|
"github.com/lightningnetwork/lnd/brontide"
|
||||||
|
"github.com/lightningnetwork/lnd/lnencrypt"
|
||||||
"github.com/lightningnetwork/lnd/tor"
|
"github.com/lightningnetwork/lnd/tor"
|
||||||
"github.com/lightningnetwork/lnd/watchtower/lookout"
|
"github.com/lightningnetwork/lnd/watchtower/lookout"
|
||||||
"github.com/lightningnetwork/lnd/watchtower/wtserver"
|
"github.com/lightningnetwork/lnd/watchtower/wtserver"
|
||||||
@@ -163,14 +164,22 @@ func (w *Standalone) createNewHiddenService() error {
|
|||||||
listenPorts = append(listenPorts, port)
|
listenPorts = append(listenPorts, port)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
encrypter, err := lnencrypt.KeyRingEncrypter(w.cfg.KeyRing)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Once we've created the port mapping, we can automatically create the
|
// Once we've created the port mapping, we can automatically create the
|
||||||
// hidden service. The service's private key will be saved on disk in order
|
// hidden service. The service's private key will be saved on disk in order
|
||||||
// to persistently have access to this hidden service across restarts.
|
// to persistently have access to this hidden service across restarts.
|
||||||
onionCfg := tor.AddOnionConfig{
|
onionCfg := tor.AddOnionConfig{
|
||||||
VirtualPort: DefaultPeerPort,
|
VirtualPort: DefaultPeerPort,
|
||||||
TargetPorts: listenPorts,
|
TargetPorts: listenPorts,
|
||||||
Store: tor.NewOnionFile(w.cfg.WatchtowerKeyPath, 0600),
|
Store: tor.NewOnionFile(
|
||||||
Type: w.cfg.Type,
|
w.cfg.WatchtowerKeyPath, 0600, w.cfg.EncryptKey,
|
||||||
|
encrypter,
|
||||||
|
),
|
||||||
|
Type: w.cfg.Type,
|
||||||
}
|
}
|
||||||
|
|
||||||
addr, err := w.cfg.TorController.AddOnion(onionCfg)
|
addr, err := w.cfg.TorController.AddOnion(onionCfg)
|
||||||
|
|||||||
Reference in new issue
Block a user