From 535a22c590d9d32a5652a0179685b63e364ee243 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Tue, 28 Apr 2020 10:06:29 +0200 Subject: [PATCH] server+brontide: use ECDH interface for brontide handshake --- brontide/conn.go | 6 ++- brontide/listener.go | 9 ++-- brontide/noise.go | 87 +++++++++++++++++++------------- brontide/noise_test.go | 32 ++++++++---- server.go | 22 ++++---- watchtower/standalone.go | 2 +- watchtower/wtclient/interface.go | 2 +- 7 files changed, 94 insertions(+), 66 deletions(-) diff --git a/brontide/conn.go b/brontide/conn.go index 0ebed66f..33b550b8 100644 --- a/brontide/conn.go +++ b/brontide/conn.go @@ -8,6 +8,7 @@ import ( "time" "github.com/btcsuite/btcd/btcec" + "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnwire" ) @@ -32,8 +33,9 @@ var _ net.Conn = (*Conn)(nil) // remote peer located at address which has remotePub as its long-term static // public key. In the case of a handshake failure, the connection is closed and // a non-nil error is returned. -func Dial(localPriv *btcec.PrivateKey, netAddr *lnwire.NetAddress, +func Dial(local keychain.SingleKeyECDH, netAddr *lnwire.NetAddress, dialer func(string, string) (net.Conn, error)) (*Conn, error) { + ipAddr := netAddr.Address.String() var conn net.Conn var err error @@ -44,7 +46,7 @@ func Dial(localPriv *btcec.PrivateKey, netAddr *lnwire.NetAddress, b := &Conn{ conn: conn, - noise: NewBrontideMachine(true, localPriv, netAddr.IdentityKey), + noise: NewBrontideMachine(true, local, netAddr.IdentityKey), } // Initiate the handshake by sending the first act to the receiver. diff --git a/brontide/listener.go b/brontide/listener.go index 95505ecf..f82b8457 100644 --- a/brontide/listener.go +++ b/brontide/listener.go @@ -7,7 +7,7 @@ import ( "net" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/lightningnetwork/lnd/keychain" ) // defaultHandshakes is the maximum number of handshakes that can be done in @@ -20,7 +20,7 @@ const defaultHandshakes = 1000 // details w.r.t the handshake and encryption scheme used within the // connection. type Listener struct { - localStatic *btcec.PrivateKey + localStatic keychain.SingleKeyECDH tcp *net.TCPListener @@ -34,8 +34,9 @@ var _ net.Listener = (*Listener)(nil) // NewListener returns a new net.Listener which enforces the Brontide scheme // during both initial connection establishment and data transfer. -func NewListener(localStatic *btcec.PrivateKey, listenAddr string) (*Listener, - error) { +func NewListener(localStatic keychain.SingleKeyECDH, + listenAddr string) (*Listener, error) { + addr, err := net.ResolveTCPAddr("tcp", listenAddr) if err != nil { return nil, err diff --git a/brontide/noise.go b/brontide/noise.go index 17e0322f..18bdd789 100644 --- a/brontide/noise.go +++ b/brontide/noise.go @@ -14,6 +14,7 @@ import ( "golang.org/x/crypto/hkdf" "github.com/btcsuite/btcd/btcec" + "github.com/lightningnetwork/lnd/keychain" ) const ( @@ -71,14 +72,9 @@ var ( // ecdh performs an ECDH operation between pub and priv. The returned value is // the sha256 of the compressed shared point. -func ecdh(pub *btcec.PublicKey, priv *btcec.PrivateKey) []byte { - s := &btcec.PublicKey{} - x, y := btcec.S256().ScalarMult(pub.X, pub.Y, priv.D.Bytes()) - s.X = x - s.Y = y - - h := sha256.Sum256(s.SerializeCompressed()) - return h[:] +func ecdh(pub *btcec.PublicKey, priv keychain.SingleKeyECDH) ([]byte, error) { + hash, err := priv.ECDH(pub) + return hash[:], err } // cipherState encapsulates the state for the AEAD which will be used to @@ -289,8 +285,8 @@ type handshakeState struct { initiator bool - localStatic *btcec.PrivateKey - localEphemeral *btcec.PrivateKey + localStatic keychain.SingleKeyECDH + localEphemeral keychain.SingleKeyECDH // nolint (false positive) remoteStatic *btcec.PublicKey remoteEphemeral *btcec.PublicKey @@ -300,11 +296,12 @@ type handshakeState struct { // with the prologue and protocol name. If this is the responder's handshake // state, then the remotePub can be nil. func newHandshakeState(initiator bool, prologue []byte, - localPub *btcec.PrivateKey, remotePub *btcec.PublicKey) handshakeState { + localKey keychain.SingleKeyECDH, + remotePub *btcec.PublicKey) handshakeState { h := handshakeState{ initiator: initiator, - localStatic: localPub, + localStatic: localKey, remoteStatic: remotePub, } @@ -322,7 +319,7 @@ func newHandshakeState(initiator bool, prologue []byte, if initiator { h.mixHash(remotePub.SerializeCompressed()) } else { - h.mixHash(localPub.PubKey().SerializeCompressed()) + h.mixHash(localKey.PubKey().SerializeCompressed()) } return h @@ -393,11 +390,11 @@ type Machine struct { // string "lightning" as the prologue. The last parameter is a set of variadic // arguments for adding additional options to the brontide Machine // initialization. -func NewBrontideMachine(initiator bool, localPub *btcec.PrivateKey, +func NewBrontideMachine(initiator bool, localKey keychain.SingleKeyECDH, remotePub *btcec.PublicKey, options ...func(*Machine)) *Machine { handshake := newHandshakeState( - initiator, lightningPrologue, localPub, remotePub, + initiator, lightningPrologue, localKey, remotePub, ) m := &Machine{ @@ -451,22 +448,25 @@ const ( // // -> e, es func (b *Machine) GenActOne() ([ActOneSize]byte, error) { - var ( - err error - actOne [ActOneSize]byte - ) + var actOne [ActOneSize]byte // e - b.localEphemeral, err = b.ephemeralGen() + localEphemeral, err := b.ephemeralGen() if err != nil { return actOne, err } + b.localEphemeral = &keychain.PrivKeyECDH{ + PrivKey: localEphemeral, + } - ephemeral := b.localEphemeral.PubKey().SerializeCompressed() + ephemeral := localEphemeral.PubKey().SerializeCompressed() b.mixHash(ephemeral) // es - s := ecdh(b.remoteStatic, b.localEphemeral) + s, err := ecdh(b.remoteStatic, b.localEphemeral) + if err != nil { + return actOne, err + } b.mixKey(s[:]) authPayload := b.EncryptAndHash([]byte{}) @@ -508,7 +508,10 @@ func (b *Machine) RecvActOne(actOne [ActOneSize]byte) error { b.mixHash(b.remoteEphemeral.SerializeCompressed()) // es - s := ecdh(b.remoteEphemeral, b.localStatic) + s, err := ecdh(b.remoteEphemeral, b.localStatic) + if err != nil { + return err + } b.mixKey(s) // If the initiator doesn't know our static key, then this operation @@ -524,22 +527,25 @@ func (b *Machine) RecvActOne(actOne [ActOneSize]byte) error { // // <- e, ee func (b *Machine) GenActTwo() ([ActTwoSize]byte, error) { - var ( - err error - actTwo [ActTwoSize]byte - ) + var actTwo [ActTwoSize]byte // e - b.localEphemeral, err = b.ephemeralGen() + localEphemeral, err := b.ephemeralGen() if err != nil { return actTwo, err } + b.localEphemeral = &keychain.PrivKeyECDH{ + PrivKey: localEphemeral, + } - ephemeral := b.localEphemeral.PubKey().SerializeCompressed() - b.mixHash(b.localEphemeral.PubKey().SerializeCompressed()) + ephemeral := localEphemeral.PubKey().SerializeCompressed() + b.mixHash(localEphemeral.PubKey().SerializeCompressed()) // ee - s := ecdh(b.remoteEphemeral, b.localEphemeral) + s, err := ecdh(b.remoteEphemeral, b.localEphemeral) + if err != nil { + return actTwo, err + } b.mixKey(s) authPayload := b.EncryptAndHash([]byte{}) @@ -580,7 +586,10 @@ func (b *Machine) RecvActTwo(actTwo [ActTwoSize]byte) error { b.mixHash(b.remoteEphemeral.SerializeCompressed()) // ee - s := ecdh(b.remoteEphemeral, b.localEphemeral) + s, err := ecdh(b.remoteEphemeral, b.localEphemeral) + if err != nil { + return err + } b.mixKey(s) _, err = b.DecryptAndHash(p[:]) @@ -600,7 +609,10 @@ func (b *Machine) GenActThree() ([ActThreeSize]byte, error) { ourPubkey := b.localStatic.PubKey().SerializeCompressed() ciphertext := b.EncryptAndHash(ourPubkey) - s := ecdh(b.remoteEphemeral, b.localStatic) + s, err := ecdh(b.remoteEphemeral, b.localStatic) + if err != nil { + return actThree, err + } b.mixKey(s) authPayload := b.EncryptAndHash([]byte{}) @@ -649,7 +661,10 @@ func (b *Machine) RecvActThree(actThree [ActThreeSize]byte) error { } // se - se := ecdh(b.remoteStatic, b.localEphemeral) + se, err := ecdh(b.remoteStatic, b.localEphemeral) + if err != nil { + return err + } b.mixKey(se) if _, err := b.DecryptAndHash(p[:]); err != nil { @@ -875,11 +890,11 @@ func (b *Machine) ReadBody(r io.Reader, buf []byte) ([]byte, error) { // This allows us to log the Machine object without spammy log messages. func (b *Machine) SetCurveToNil() { if b.localStatic != nil { - b.localStatic.Curve = nil + b.localStatic.PubKey().Curve = nil } if b.localEphemeral != nil { - b.localEphemeral.Curve = nil + b.localEphemeral.PubKey().Curve = nil } if b.remoteStatic != nil { diff --git a/brontide/noise_test.go b/brontide/noise_test.go index ce833170..ed2229c1 100644 --- a/brontide/noise_test.go +++ b/brontide/noise_test.go @@ -11,6 +11,7 @@ import ( "testing/iotest" "github.com/btcsuite/btcd/btcec" + "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnwire" ) @@ -25,13 +26,14 @@ func makeListener() (*Listener, *lnwire.NetAddress, error) { if err != nil { return nil, nil, err } + localKeyECDH := &keychain.PrivKeyECDH{PrivKey: localPriv} // Having a port of ":0" means a random port, and interface will be // chosen for our listener. addr := "localhost:0" // Our listener will be local, and the connection remote. - listener, err := NewListener(localPriv, addr) + listener, err := NewListener(localKeyECDH, addr) if err != nil { return nil, nil, err } @@ -57,13 +59,14 @@ func establishTestConnection() (net.Conn, net.Conn, func(), error) { if err != nil { return nil, nil, nil, err } + remoteKeyECDH := &keychain.PrivKeyECDH{PrivKey: remotePriv} // Initiate a connection with a separate goroutine, and listen with our // main one. If both errors are nil, then encryption+auth was // successful. remoteConnChan := make(chan maybeNetConn, 1) go func() { - remoteConn, err := Dial(remotePriv, netAddr, net.Dial) + remoteConn, err := Dial(remoteKeyECDH, netAddr, net.Dial) remoteConnChan <- maybeNetConn{remoteConn, err} }() @@ -190,9 +193,10 @@ func TestConcurrentHandshakes(t *testing.T) { if err != nil { t.Fatalf("unable to generate private key: %v", err) } + remoteKeyECDH := &keychain.PrivKeyECDH{PrivKey: remotePriv} go func() { - remoteConn, err := Dial(remotePriv, netAddr, net.Dial) + remoteConn, err := Dial(remoteKeyECDH, netAddr, net.Dial) connChan <- maybeNetConn{remoteConn, err} }() @@ -314,8 +318,10 @@ func TestBolt0008TestVectors(t *testing.T) { if err != nil { t.Fatalf("unable to decode hex: %v", err) } - initiatorPriv, _ := btcec.PrivKeyFromBytes(btcec.S256(), - initiatorKeyBytes) + initiatorPriv, _ := btcec.PrivKeyFromBytes( + btcec.S256(), initiatorKeyBytes, + ) + initiatorKeyECDH := &keychain.PrivKeyECDH{PrivKey: initiatorPriv} // We'll then do the same for the responder. responderKeyBytes, err := hex.DecodeString("212121212121212121212121" + @@ -323,8 +329,10 @@ func TestBolt0008TestVectors(t *testing.T) { if err != nil { t.Fatalf("unable to decode hex: %v", err) } - responderPriv, responderPub := btcec.PrivKeyFromBytes(btcec.S256(), - responderKeyBytes) + responderPriv, responderPub := btcec.PrivKeyFromBytes( + btcec.S256(), responderKeyBytes, + ) + responderKeyECDH := &keychain.PrivKeyECDH{PrivKey: responderPriv} // With the initiator's key data parsed, we'll now define a custom // EphemeralGenerator function for the state machine to ensure that the @@ -355,10 +363,12 @@ func TestBolt0008TestVectors(t *testing.T) { // Finally, we'll create both brontide state machines, so we can begin // our test. - initiator := NewBrontideMachine(true, initiatorPriv, responderPub, - initiatorEphemeral) - responder := NewBrontideMachine(false, responderPriv, nil, - responderEphemeral) + initiator := NewBrontideMachine( + true, initiatorKeyECDH, responderPub, initiatorEphemeral, + ) + responder := NewBrontideMachine( + false, responderKeyECDH, nil, responderEphemeral, + ) // We'll start with the initiator generating the initial payload for // act one. This should consist of exactly 50 bytes. We'll assert that diff --git a/server.go b/server.go index b8b51fc8..b285cfcb 100644 --- a/server.go +++ b/server.go @@ -138,7 +138,7 @@ type server struct { // identityECDH is an ECDH capable wrapper for the private key used // to authenticate any incoming connections. - identityECDH *btcec.PrivateKey + identityECDH keychain.SingleKeyECDH // nodeSigner is an implementation of the MessageSigner implementation // that's backed by the identity private key of the running lnd node. @@ -312,12 +312,12 @@ func parseAddr(address string, netCfg tor.Net) (net.Addr, error) { // noiseDial is a factory function which creates a connmgr compliant dialing // function by returning a closure which includes the server's identity key. -func noiseDial(idPriv *btcec.PrivateKey, +func noiseDial(idKey keychain.SingleKeyECDH, netCfg tor.Net) func(net.Addr) (net.Conn, error) { return func(a net.Addr) (net.Conn, error) { lnAddr := a.(*lnwire.NetAddress) - return brontide.Dial(idPriv, lnAddr, netCfg.Dial) + return brontide.Dial(idKey, lnAddr, netCfg.Dial) } } @@ -344,7 +344,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr, chanDB *channeldb.DB, // doesn't need to call the general lndResolveTCP function // since we are resolving a local address. listeners[i], err = brontide.NewListener( - privKey, listenAddr.String(), + nodeKeyECDH, listenAddr.String(), ) if err != nil { return nil, err @@ -373,7 +373,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr, chanDB *channeldb.DB, } var serializedPubKey [33]byte - copy(serializedPubKey[:], privKey.PubKey().SerializeCompressed()) + copy(serializedPubKey[:], nodeKeyECDH.PubKey().SerializeCompressed()) // Initialize the sphinx router, placing it's persistent replay log in // the same directory as the channel graph database. @@ -434,7 +434,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr, chanDB *channeldb.DB, channelNotifier: channelnotifier.New(chanDB), - identityECDH: privKey, + identityECDH: nodeKeyECDH, nodeSigner: netann.NewNodeSigner(nodeKeySigner), listenAddrs: listenAddrs, @@ -521,7 +521,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr, chanDB *channeldb.DB, ChanStatusSampleInterval: cfg.ChanStatusSampleInterval, ChanEnableTimeout: cfg.ChanEnableTimeout, ChanDisableTimeout: cfg.ChanDisableTimeout, - OurPubKey: privKey.PubKey(), + OurPubKey: nodeKeyECDH.PubKey(), MessageSigner: s.nodeSigner, IsChannelActive: s.htlcSwitch.HasActiveLink, ApplyChannelUpdate: s.applyChannelUpdate, @@ -647,7 +647,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr, chanDB *channeldb.DB, Features: s.featureMgr.Get(feature.SetNodeAnn), Color: color, } - copy(selfNode.PubKeyBytes[:], privKey.PubKey().SerializeCompressed()) + copy(selfNode.PubKeyBytes[:], nodeKeyECDH.PubKey().SerializeCompressed()) // Based on the disk representation of the node announcement generated // above, we'll generate a node announcement that can go out on the @@ -989,7 +989,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr, chanDB *channeldb.DB, } s.fundingMgr, err = newFundingManager(fundingConfig{ - IDKey: privKey.PubKey(), + IDKey: nodeKeyECDH.PubKey(), Wallet: cc.wallet, PublishTransaction: cc.wallet.PublishTransaction, Notifier: cc.chainNotifier, @@ -997,7 +997,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr, chanDB *channeldb.DB, SignMessage: func(pubKey *btcec.PublicKey, msg []byte) (input.Signature, error) { - if pubKey.IsEqual(privKey.PubKey()) { + if pubKey.IsEqual(nodeKeyECDH.PubKey()) { return s.nodeSigner.SignMessage(pubKey, msg) } @@ -1010,7 +1010,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr, chanDB *channeldb.DB, optionalFields ...discovery.OptionalMsgField) chan error { return s.authGossiper.ProcessLocalAnnouncement( - msg, privKey.PubKey(), optionalFields..., + msg, nodeKeyECDH.PubKey(), optionalFields..., ) }, NotifyWhenOnline: s.NotifyWhenOnline, diff --git a/watchtower/standalone.go b/watchtower/standalone.go index 8e56d962..26ebed8c 100644 --- a/watchtower/standalone.go +++ b/watchtower/standalone.go @@ -72,7 +72,7 @@ func New(cfg *Config) (*Standalone, error) { listeners := make([]net.Listener, 0, len(cfg.ListenAddrs)) for _, listenAddr := range cfg.ListenAddrs { listener, err := brontide.NewListener( - cfg.NodeKeyECDH, listenAddr.String(), + nil/*TODO fix in next commit*/, listenAddr.String(), ) if err != nil { return nil, err diff --git a/watchtower/wtclient/interface.go b/watchtower/wtclient/interface.go index f7cc40f8..c9da8314 100644 --- a/watchtower/wtclient/interface.go +++ b/watchtower/wtclient/interface.go @@ -109,7 +109,7 @@ type AuthDialer func(localPriv *btcec.PrivateKey, netAddr *lnwire.NetAddress, func AuthDial(localPriv *btcec.PrivateKey, netAddr *lnwire.NetAddress, dialer func(string, string) (net.Conn, error)) (wtserver.Peer, error) { - return brontide.Dial(localPriv, netAddr, dialer) + return brontide.Dial(nil/*TODO fix in next commit*/, netAddr, dialer) } // ECDHKeyRing abstracts the ability to derive shared ECDH keys given a