server+brontide: use ECDH interface for brontide handshake

This commit is contained in:
Oliver Gugger
2020-04-28 10:06:29 +02:00
parent ee74e4ba86
commit 535a22c590
7 changed files with 94 additions and 66 deletions

View File

@@ -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.

View File

@@ -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

View File

@@ -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 {

View File

@@ -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