server+brontide: use ECDH interface for brontide handshake
This commit is contained in:
parent
ee74e4ba86
commit
535a22c590
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
22
server.go
22
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,
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user