watchtower/wtclient: use HD session key derivation

This commit is contained in:
Conner Fromknecht 2019-04-23 20:04:55 -07:00
parent 0404aedede
commit bebe6461a9
No known key found for this signature in database
GPG Key ID: E7D737B67FA592C7
4 changed files with 53 additions and 27 deletions

@ -9,7 +9,6 @@ import (
"github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/watchtower/wtdb" "github.com/lightningnetwork/lnd/watchtower/wtdb"
@ -77,7 +76,7 @@ type Config struct {
// SecretKeyRing is used to derive the session keys used to communicate // SecretKeyRing is used to derive the session keys used to communicate
// with the tower. The client only stores the KeyLocators internally so // with the tower. The client only stores the KeyLocators internally so
// that we never store private keys on disk. // that we never store private keys on disk.
SecretKeyRing keychain.SecretKeyRing SecretKeyRing SecretKeyRing
// Dial connects to an addr using the specified net and returns the // Dial connects to an addr using the specified net and returns the
// connection object. // connection object.
@ -201,15 +200,16 @@ func New(config *Config) (*TowerClient, error) {
forceQuit: make(chan struct{}), forceQuit: make(chan struct{}),
} }
c.negotiator = newSessionNegotiator(&NegotiatorConfig{ c.negotiator = newSessionNegotiator(&NegotiatorConfig{
DB: cfg.DB, DB: cfg.DB,
Policy: cfg.Policy, SecretKeyRing: cfg.SecretKeyRing,
ChainHash: cfg.ChainHash, Policy: cfg.Policy,
SendMessage: c.sendMessage, ChainHash: cfg.ChainHash,
ReadMessage: c.readMessage, SendMessage: c.sendMessage,
Dial: c.dial, ReadMessage: c.readMessage,
Candidates: newTowerListIterator(tower), Dial: c.dial,
MinBackoff: cfg.MinBackoff, Candidates: newTowerListIterator(tower),
MaxBackoff: cfg.MaxBackoff, MinBackoff: cfg.MinBackoff,
MaxBackoff: cfg.MaxBackoff,
}) })
// Next, load all active sessions from the db into the client. We will // Next, load all active sessions from the db into the client. We will
@ -222,14 +222,25 @@ func New(config *Config) (*TowerClient, error) {
} }
// Reload any towers from disk using the tower IDs contained in each // Reload any towers from disk using the tower IDs contained in each
// candidate session. // candidate session. We will also rederive any session keys needed to
// be able to communicate with the towers and authenticate session
// requests. This prevents us from having to store the private keys on
// disk.
for _, s := range c.candidateSessions { for _, s := range c.candidateSessions {
tower, err := c.cfg.DB.LoadTower(s.TowerID) tower, err := c.cfg.DB.LoadTower(s.TowerID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
sessionPriv, err := DeriveSessionKey(
c.cfg.SecretKeyRing, s.KeyIndex,
)
if err != nil {
return nil, err
}
s.Tower = tower s.Tower = tower
s.SessionPrivKey = sessionPriv
} }
// Finally, load the sweep pkscripts that have been generated for all // Finally, load the sweep pkscripts that have been generated for all

@ -430,10 +430,11 @@ func newHarness(t *testing.T, cfg harnessCfg) *testHarness {
Dial: func(string, string) (net.Conn, error) { Dial: func(string, string) (net.Conn, error) {
return nil, nil return nil, nil
}, },
DB: clientDB, DB: clientDB,
AuthDial: mockNet.AuthDial, AuthDial: mockNet.AuthDial,
PrivateTower: towerAddr, SecretKeyRing: wtmock.NewSecretKeyRing(),
Policy: cfg.policy, PrivateTower: towerAddr,
Policy: cfg.policy,
NewAddress: func() ([]byte, error) { NewAddress: func() ([]byte, error) {
return addrScript, nil return addrScript, nil
}, },

@ -42,6 +42,10 @@ type NegotiatorConfig struct {
// negotiated sessions. // negotiated sessions.
DB DB DB DB
// SecretKeyRing allows the client to derive new session private keys
// when attempting to negotiate session with a tower.
SecretKeyRing SecretKeyRing
// Candidates is an abstract set of tower candidates that the negotiator // Candidates is an abstract set of tower candidates that the negotiator
// will traverse serially when attempting to negotiate a new session. // will traverse serially when attempting to negotiate a new session.
Candidates TowerCandidateIterator Candidates TowerCandidateIterator
@ -255,12 +259,23 @@ retryWithBackoff:
goto retryWithBackoff goto retryWithBackoff
} }
towerPub := tower.IdentityKey.SerializeCompressed()
log.Debugf("Attempting session negotiation with tower=%x", log.Debugf("Attempting session negotiation with tower=%x",
tower.IdentityKey.SerializeCompressed()) towerPub)
// Before proceeding, we will reserve a session key index to use
// with this specific tower. If one is already reserved, the
// existing index will be returned.
keyIndex, err := n.cfg.DB.NextSessionKeyIndex(tower.ID)
if err != nil {
log.Debugf("Unable to reserve session key index "+
"for tower=%x: %v", towerPub, err)
continue
}
// We'll now attempt the CreateSession dance with the tower to // We'll now attempt the CreateSession dance with the tower to
// get a new session, trying all addresses if necessary. // get a new session, trying all addresses if necessary.
err = n.createSession(tower) err = n.createSession(tower, keyIndex)
if err != nil { if err != nil {
log.Debugf("Session negotiation with tower=%x "+ log.Debugf("Session negotiation with tower=%x "+
"failed, trying again -- reason: %v", "failed, trying again -- reason: %v",
@ -277,22 +292,21 @@ retryWithBackoff:
// its stored addresses. This method returns after the first successful // its stored addresses. This method returns after the first successful
// negotiation, or after all addresses have failed with ErrFailedNegotiation. If // negotiation, or after all addresses have failed with ErrFailedNegotiation. If
// the tower has no addresses, ErrNoTowerAddrs is returned. // the tower has no addresses, ErrNoTowerAddrs is returned.
func (n *sessionNegotiator) createSession(tower *wtdb.Tower) error { func (n *sessionNegotiator) createSession(tower *wtdb.Tower,
keyIndex uint32) error {
// If the tower has no addresses, there's nothing we can do. // If the tower has no addresses, there's nothing we can do.
if len(tower.Addresses) == 0 { if len(tower.Addresses) == 0 {
return ErrNoTowerAddrs return ErrNoTowerAddrs
} }
// TODO(conner): create with hdkey at random index sessionPriv, err := DeriveSessionKey(n.cfg.SecretKeyRing, keyIndex)
sessionPrivKey, err := btcec.NewPrivateKey(btcec.S256())
if err != nil { if err != nil {
return err return err
} }
// TODO(conner): write towerAddr+privkey
for _, lnAddr := range tower.LNAddrs() { for _, lnAddr := range tower.LNAddrs() {
err = n.tryAddress(sessionPrivKey, tower, lnAddr) err = n.tryAddress(sessionPriv, keyIndex, tower, lnAddr)
switch { switch {
case err == ErrPermanentTowerFailure: case err == ErrPermanentTowerFailure:
// TODO(conner): report to iterator? can then be reset // TODO(conner): report to iterator? can then be reset
@ -318,7 +332,7 @@ func (n *sessionNegotiator) createSession(tower *wtdb.Tower) error {
// returns true if all steps succeed and the new session has been persisted, and // returns true if all steps succeed and the new session has been persisted, and
// fails otherwise. // fails otherwise.
func (n *sessionNegotiator) tryAddress(privKey *btcec.PrivateKey, func (n *sessionNegotiator) tryAddress(privKey *btcec.PrivateKey,
tower *wtdb.Tower, lnAddr *lnwire.NetAddress) error { keyIndex uint32, tower *wtdb.Tower, lnAddr *lnwire.NetAddress) error {
// Connect to the tower address using our generated session key. // Connect to the tower address using our generated session key.
conn, err := n.cfg.Dial(privKey, lnAddr) conn, err := n.cfg.Dial(privKey, lnAddr)
@ -394,7 +408,8 @@ func (n *sessionNegotiator) tryAddress(privKey *btcec.PrivateKey,
clientSession := &wtdb.ClientSession{ clientSession := &wtdb.ClientSession{
TowerID: tower.ID, TowerID: tower.ID,
Tower: tower, Tower: tower,
SessionPrivKey: privKey, // remove after using HD keys KeyIndex: keyIndex,
SessionPrivKey: privKey,
ID: sessionID, ID: sessionID,
Policy: n.cfg.Policy, Policy: n.cfg.Policy,
SeqNum: 0, SeqNum: 0,

@ -109,7 +109,6 @@ func (m *ClientDB) CreateClientSession(session *wtdb.ClientSession) error {
m.activeSessions[session.ID] = &wtdb.ClientSession{ m.activeSessions[session.ID] = &wtdb.ClientSession{
TowerID: session.TowerID, TowerID: session.TowerID,
KeyIndex: session.KeyIndex, KeyIndex: session.KeyIndex,
SessionPrivKey: session.SessionPrivKey,
ID: session.ID, ID: session.ID,
Policy: session.Policy, Policy: session.Policy,
SeqNum: session.SeqNum, SeqNum: session.SeqNum,