watchtower/wtclient: use HD session key derivation
This commit is contained in:
parent
0404aedede
commit
bebe6461a9
@ -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,
|
||||||
|
Loading…
Reference in New Issue
Block a user