203 lines
5.1 KiB
Go
203 lines
5.1 KiB
Go
package lndc
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"net"
|
|
|
|
"github.com/btcsuite/fastsha256"
|
|
"github.com/codahale/chacha20poly1305"
|
|
"github.com/roasbeef/btcd/btcec"
|
|
"github.com/roasbeef/btcutil"
|
|
)
|
|
|
|
// Listener...
|
|
type Listener struct {
|
|
longTermPriv *btcec.PrivateKey
|
|
|
|
tcp *net.TCPListener
|
|
}
|
|
|
|
var _ net.Listener = (*Listener)(nil)
|
|
|
|
// NewListener...
|
|
func NewListener(localPriv *btcec.PrivateKey, listenAddr string) (*Listener, error) {
|
|
addr, err := net.ResolveTCPAddr("tcp", listenAddr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
l, err := net.ListenTCP("tcp", addr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &Listener{localPriv, l}, nil
|
|
}
|
|
|
|
// Accept waits for and returns the next connection to the listener.
|
|
// Part of the net.Listener interface.
|
|
func (l *Listener) Accept() (c net.Conn, err error) {
|
|
conn, err := l.tcp.Accept()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
lndc := NewConn(conn)
|
|
|
|
// Exchange an ephemeral public key with the remote connection in order
|
|
// to establish a confidential connection before we attempt to
|
|
// authenticated.
|
|
ephemeralKey, err := l.createCipherConn(lndc)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Now that we've established an encrypted connection, authenticate the
|
|
// identity of the remote host.
|
|
ephemeralPub := ephemeralKey.PubKey().SerializeCompressed()
|
|
if err := l.authenticateConnection(l.longTermPriv, lndc, ephemeralPub); err != nil {
|
|
lndc.Close()
|
|
return nil, err
|
|
}
|
|
|
|
return lndc, nil
|
|
}
|
|
|
|
// createCipherConn....
|
|
func (l *Listener) createCipherConn(lnConn *LNDConn) (*btcec.PrivateKey, error) {
|
|
var err error
|
|
var theirEphPubBytes []byte
|
|
|
|
// First, read and deserialize their ephemeral public key.
|
|
theirEphPubBytes, err = readClear(lnConn.Conn)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(theirEphPubBytes) != 33 {
|
|
return nil, fmt.Errorf("Got invalid %d byte eph pubkey %x\n",
|
|
len(theirEphPubBytes), theirEphPubBytes)
|
|
}
|
|
theirEphPub, err := btcec.ParsePubKey(theirEphPubBytes, btcec.S256())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Once we've parsed and verified their key, generate, and send own
|
|
// ephemeral key pair for use within this session.
|
|
myEph, err := btcec.NewPrivateKey(btcec.S256())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if _, err := writeClear(lnConn.Conn, myEph.PubKey().SerializeCompressed()); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Now that we have both keys, do non-interactive diffie with ephemeral
|
|
// pubkeys, sha256 for good luck.
|
|
sessionKey := fastsha256.Sum256(
|
|
btcec.GenerateSharedSecret(myEph, theirEphPub),
|
|
)
|
|
|
|
lnConn.chachaStream, err = chacha20poly1305.New(sessionKey[:])
|
|
|
|
lnConn.remoteNonceInt = 1 << 63
|
|
lnConn.myNonceInt = 0
|
|
|
|
lnConn.RemotePub = theirEphPub
|
|
lnConn.Authed = false
|
|
|
|
return myEph, nil
|
|
}
|
|
|
|
// AuthListen...
|
|
func (l *Listener) authenticateConnection(
|
|
myId *btcec.PrivateKey, lnConn *LNDConn, localEphPubBytes []byte) error {
|
|
var err error
|
|
|
|
// TODO(roasbeef): should be using read/write clear here?
|
|
slice := make([]byte, 73)
|
|
n, err := lnConn.Conn.Read(slice)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
authmsg := slice[:n]
|
|
if len(authmsg) != 53 && len(authmsg) != 73 {
|
|
return fmt.Errorf("got auth message of %d bytes, "+
|
|
"expect 53 or 73", len(authmsg))
|
|
}
|
|
|
|
myPKH := btcutil.Hash160(l.longTermPriv.PubKey().SerializeCompressed())
|
|
if !bytes.Equal(myPKH, authmsg[33:53]) {
|
|
return fmt.Errorf(
|
|
"remote host asking for PKH %x, that's not me", authmsg[33:53])
|
|
}
|
|
|
|
// do DH with id keys
|
|
theirPub, err := btcec.ParsePubKey(authmsg[:33], btcec.S256())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
idDH := fastsha256.Sum256(
|
|
btcec.GenerateSharedSecret(l.longTermPriv, theirPub),
|
|
)
|
|
|
|
myDHproof := btcutil.Hash160(
|
|
append(lnConn.RemotePub.SerializeCompressed(), idDH[:]...),
|
|
)
|
|
theirDHproof := btcutil.Hash160(append(localEphPubBytes, idDH[:]...))
|
|
|
|
// If they already know our public key, then execute the fast path.
|
|
// Verify their DH proof, and send our own.
|
|
if len(authmsg) == 73 {
|
|
// Verify their DH proof.
|
|
if !bytes.Equal(authmsg[53:], theirDHproof) {
|
|
return fmt.Errorf("invalid DH proof from %s",
|
|
lnConn.RemoteAddr().String())
|
|
}
|
|
|
|
// Their DH proof checks out, so send ours now.
|
|
if _, err = lnConn.Conn.Write(myDHproof); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
// Otherwise, they don't yet know our public key. So we'll send
|
|
// it over to them, so we can both compute the DH proof.
|
|
msg := append(l.longTermPriv.PubKey().SerializeCompressed(), myDHproof...)
|
|
if _, err = lnConn.Conn.Write(msg); err != nil {
|
|
return err
|
|
}
|
|
|
|
resp := make([]byte, 20)
|
|
if _, err := lnConn.Conn.Read(resp); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Verify their DH proof.
|
|
if bytes.Equal(resp, theirDHproof) == false {
|
|
return fmt.Errorf("Invalid DH proof %x", theirDHproof)
|
|
}
|
|
}
|
|
|
|
theirAdr := btcutil.Hash160(theirPub.SerializeCompressed())
|
|
copy(lnConn.RemoteLNId[:], theirAdr[:16])
|
|
lnConn.RemotePub = theirPub
|
|
lnConn.Authed = true
|
|
|
|
return nil
|
|
}
|
|
|
|
// Close closes the listener.
|
|
// Any blocked Accept operations will be unblocked and return errors.
|
|
// Part of the net.Listener interface.
|
|
func (l *Listener) Close() error {
|
|
return l.tcp.Close()
|
|
}
|
|
|
|
// Addr returns the listener's network address.
|
|
// Part of the net.Listener interface.
|
|
func (l *Listener) Addr() net.Addr {
|
|
return l.tcp.Addr()
|
|
}
|