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:
Oliver Gugger authored and GitHub committed 2022-09-30 10:06:42 +02:00
commit 35c59643cd
22 files changed
+298 -232

No files matched your search

+2 -1
View File
@@ -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.
-140
View File
@@ -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
View File
@@ -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
} }
+8 -4
View File
@@ -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()
+4 -3
View File
@@ -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)
+5 -4
View File
@@ -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
View File
@@ -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
} }
+7 -5
View File
@@ -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
+5
View File
@@ -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.
+5 -2
View 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
+1 -1
View File
@@ -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
+2
View File
@@ -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=
+6 -7
View File
@@ -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,
} }
+1
View File
@@ -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"`
} }
+2
View File
@@ -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:
+139
View File
@@ -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")
} }
} }
+40
View File
@@ -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
}
+2
View File
@@ -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]
+10 -1
View File
@@ -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 {
+6
View File
@@ -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
+11 -2
View File
@@ -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)