lndc: restore new implementation of land
This commit is contained in:
parent
f2a1c0368a
commit
af42bb0a99
368
lndc/conn.go
Normal file
368
lndc/conn.go
Normal file
@ -0,0 +1,368 @@
|
|||||||
|
package lndc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcutil"
|
||||||
|
"github.com/btcsuite/fastsha256"
|
||||||
|
"github.com/codahale/chacha20poly1305"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/btcec"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Conn...
|
||||||
|
type Conn struct {
|
||||||
|
longTermPriv *btcec.PrivateKey
|
||||||
|
|
||||||
|
remotePub *btcec.PublicKey
|
||||||
|
remoteLNId [16]byte
|
||||||
|
|
||||||
|
myNonceInt uint64
|
||||||
|
remoteNonceInt uint64
|
||||||
|
|
||||||
|
// If Authed == false, the remotePub is the EPHEMERAL key.
|
||||||
|
// once authed == true, remotePub is who you're actually talking to.
|
||||||
|
authed bool
|
||||||
|
|
||||||
|
// chachaStream saves some time as you don't have to init it with
|
||||||
|
// the session key every time. Make SessionKey redundant; remove later.
|
||||||
|
chachaStream cipher.AEAD
|
||||||
|
|
||||||
|
// ViaPbx specifies whether this is a direct TCP connection or an
|
||||||
|
// encapsulated PBX connection.
|
||||||
|
// If going ViaPbx, Cn isn't used channels are used for Read() and
|
||||||
|
// Write(), which are filled by the PBXhandler.
|
||||||
|
viaPbx bool
|
||||||
|
pbxIncoming chan []byte
|
||||||
|
pbxOutgoing chan []byte
|
||||||
|
|
||||||
|
version uint8
|
||||||
|
|
||||||
|
readBuf bytes.Buffer
|
||||||
|
|
||||||
|
conn net.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewConn...
|
||||||
|
func NewConn(connPrivKey *btcec.PrivateKey, conn net.Conn) *Conn {
|
||||||
|
return &Conn{longTermPriv: connPrivKey, conn: conn}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dial...
|
||||||
|
func (c *Conn) Dial(address string, remoteId []byte) error {
|
||||||
|
var err error
|
||||||
|
if c.conn != nil {
|
||||||
|
return fmt.Errorf("connection already established")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Before dialing out to the remote host, verify that `remoteId` is either
|
||||||
|
// a pubkey or a pubkey hash.
|
||||||
|
if len(remoteId) != 33 && len(remoteId) != 20 {
|
||||||
|
return fmt.Errorf("must supply either remote pubkey or " +
|
||||||
|
"pubkey hash")
|
||||||
|
}
|
||||||
|
|
||||||
|
// First, open the TCP connection itself.
|
||||||
|
c.conn, err = net.Dial("tcp", address)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calc remote LNId; need this for creating pbx connections just because
|
||||||
|
// LNid is in the struct does not mean it's authed!
|
||||||
|
if len(remoteId) == 20 {
|
||||||
|
copy(c.remoteLNId[:], remoteId[:16])
|
||||||
|
} else {
|
||||||
|
theirAdr := btcutil.Hash160(remoteId)
|
||||||
|
copy(c.remoteLNId[:], theirAdr[:16])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make up an ephemeral keypair for this session.
|
||||||
|
ourEphemeralPriv, err := btcec.NewPrivateKey(btcec.S256())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ourEphemeralPub := ourEphemeralPriv.PubKey()
|
||||||
|
|
||||||
|
// Sned 1. Send my ephemeral pubkey. Can add version bits.
|
||||||
|
if _, err = writeClear(c.conn, ourEphemeralPub.SerializeCompressed()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read, then deserialize their ephemeral public key.
|
||||||
|
theirEphPubBytes, err := readClear(c.conn)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
theirEphPub, err := btcec.ParsePubKey(theirEphPubBytes, btcec.S256())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do non-interactive diffie with ephemeral pubkeys. Sha256 for good
|
||||||
|
// luck.
|
||||||
|
sessionKey := fastsha256.Sum256(
|
||||||
|
btcec.GenerateSharedSecret(ourEphemeralPriv, theirEphPub),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Now that we've derive the session key, we can initialize the
|
||||||
|
// chacha20poly1305 AEAD instance which will be used for the remainder of
|
||||||
|
// the session.
|
||||||
|
c.chachaStream, err = chacha20poly1305.New(sessionKey[:])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// display private key for debug only
|
||||||
|
fmt.Printf("made session key %x\n", sessionKey)
|
||||||
|
|
||||||
|
c.myNonceInt = 1 << 63
|
||||||
|
c.remoteNonceInt = 0
|
||||||
|
|
||||||
|
c.remotePub = theirEphPub
|
||||||
|
c.authed = false
|
||||||
|
|
||||||
|
// Session is now open and confidential but not yet authenticated...
|
||||||
|
// So auth!
|
||||||
|
if len(remoteId) == 20 {
|
||||||
|
// Only know pubkey hash (20 bytes).
|
||||||
|
err = c.authPKH(remoteId, ourEphemeralPub.SerializeCompressed())
|
||||||
|
} else {
|
||||||
|
// Must be 33 byte pubkey.
|
||||||
|
err = c.authPubKey(remoteId, ourEphemeralPub.SerializeCompressed())
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// authPubKey...
|
||||||
|
func (c *Conn) authPubKey(remotePubBytes, localEphPubBytes []byte) error {
|
||||||
|
if c.authed {
|
||||||
|
return fmt.Errorf("%s already authed", c.remotePub)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since we already know their public key, we can immediately generate
|
||||||
|
// the DH proof without an additional round-trip.
|
||||||
|
theirPub, err := btcec.ParsePubKey(remotePubBytes, btcec.S256())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
theirPKH := btcutil.Hash160(remotePubBytes)
|
||||||
|
idDH := fastsha256.Sum256(btcec.GenerateSharedSecret(c.longTermPriv, theirPub))
|
||||||
|
myDHproof := btcutil.Hash160(append(c.remotePub.SerializeCompressed(), idDH[:]...))
|
||||||
|
|
||||||
|
// Send over the 73 byte authentication message: my pubkey, their
|
||||||
|
// pubkey hash, DH proof.
|
||||||
|
var authMsg [73]byte
|
||||||
|
copy(authMsg[:33], c.longTermPriv.PubKey().SerializeCompressed())
|
||||||
|
copy(authMsg[33:], theirPKH)
|
||||||
|
copy(authMsg[53:], myDHproof)
|
||||||
|
if _, err = c.conn.Write(authMsg[:]); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Await, their response. They should send only the 20-byte DH proof.
|
||||||
|
resp := make([]byte, 20)
|
||||||
|
_, err = c.conn.Read(resp)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that their proof matches our locally computed version.
|
||||||
|
theirDHproof := btcutil.Hash160(append(localEphPubBytes, idDH[:]...))
|
||||||
|
if bytes.Equal(resp, theirDHproof) == false {
|
||||||
|
return fmt.Errorf("invalid DH proof %x", theirDHproof)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Proof checks out, auth complete.
|
||||||
|
c.remotePub = theirPub
|
||||||
|
theirAdr := btcutil.Hash160(theirPub.SerializeCompressed())
|
||||||
|
copy(c.remoteLNId[:], theirAdr[:16])
|
||||||
|
c.authed = true
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// authPKH...
|
||||||
|
func (c *Conn) authPKH(theirPKH, localEphPubBytes []byte) error {
|
||||||
|
if c.authed {
|
||||||
|
return fmt.Errorf("%s already authed", c.remotePub)
|
||||||
|
}
|
||||||
|
if len(theirPKH) != 20 {
|
||||||
|
return fmt.Errorf("remote PKH must be 20 bytes, got %d",
|
||||||
|
len(theirPKH))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send 53 bytes: our pubkey, and the remote's pubkey hash.
|
||||||
|
var greetingMsg [53]byte
|
||||||
|
copy(greetingMsg[:33], c.longTermPriv.PubKey().SerializeCompressed())
|
||||||
|
copy(greetingMsg[:33], theirPKH)
|
||||||
|
if _, err := c.conn.Write(greetingMsg[:]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for their response.
|
||||||
|
// TODO(tadge): add timeout here
|
||||||
|
// * NOTE(roasbeef): read timeout should be set on the underlying
|
||||||
|
// net.Conn.
|
||||||
|
resp := make([]byte, 53)
|
||||||
|
if _, err := c.conn.Read(resp); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse their long-term public key, and generate the DH proof.
|
||||||
|
theirPub, err := btcec.ParsePubKey(resp[:33], btcec.S256())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
idDH := fastsha256.Sum256(btcec.GenerateSharedSecret(c.longTermPriv, theirPub))
|
||||||
|
fmt.Printf("made idDH %x\n", idDH)
|
||||||
|
theirDHproof := btcutil.Hash160(append(localEphPubBytes, idDH[:]...))
|
||||||
|
|
||||||
|
// Verify that their DH proof matches the one we just generated.
|
||||||
|
if bytes.Equal(resp[33:], theirDHproof) == false {
|
||||||
|
return fmt.Errorf("Invalid DH proof %x", theirDHproof)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If their DH proof checks out, then send our own.
|
||||||
|
myDHproof := btcutil.Hash160(append(c.remotePub.SerializeCompressed(), idDH[:]...))
|
||||||
|
if _, err = c.conn.Write(myDHproof); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Proof sent, auth complete.
|
||||||
|
c.remotePub = theirPub
|
||||||
|
theirAdr := btcutil.Hash160(theirPub.SerializeCompressed())
|
||||||
|
copy(c.remoteLNId[:], theirAdr[:16])
|
||||||
|
c.authed = true
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read reads data from the connection.
|
||||||
|
// Read can be made to time out and return a Error with Timeout() == true
|
||||||
|
// after a fixed time limit; see SetDeadline and SetReadDeadline.
|
||||||
|
// Part of the net.Conn interface.
|
||||||
|
func (c *Conn) Read(b []byte) (n int, err error) {
|
||||||
|
// In order to reconcile the differences between the record abstraction
|
||||||
|
// of our AEAD connection, and the stream abstraction of TCP, we maintain
|
||||||
|
// an intermediate read buffer. If this buffer becomes depleated, then
|
||||||
|
// we read the next record, and feed it into the buffer. Otherwise, we
|
||||||
|
// read directly from the buffer.
|
||||||
|
if c.readBuf.Len() == 0 {
|
||||||
|
// The buffer is empty, so read the next cipher text.
|
||||||
|
ctext, err := readClear(c.conn)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode the current remote nonce, so we can use it to decrypt
|
||||||
|
// the cipher text.
|
||||||
|
var nonceBuf [8]byte
|
||||||
|
binary.BigEndian.PutUint64(nonceBuf[:], c.remoteNonceInt)
|
||||||
|
|
||||||
|
fmt.Printf("decrypt %d byte from %x nonce %d\n",
|
||||||
|
len(ctext), c.remoteLNId, c.remoteNonceInt)
|
||||||
|
|
||||||
|
c.remoteNonceInt++ // increment remote nonce, no matter what...
|
||||||
|
|
||||||
|
msg, err := c.chachaStream.Open(nil, nonceBuf[:], ctext, nil)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("decrypt %d byte ciphertext failed\n", len(ctext))
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := c.readBuf.Write(msg); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.readBuf.Read(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write writes data to the connection.
|
||||||
|
// Write can be made to time out and return a Error with Timeout() == true
|
||||||
|
// after a fixed time limit; see SetDeadline and SetWriteDeadline.
|
||||||
|
// Part of the net.Conn interface.
|
||||||
|
func (c *Conn) Write(b []byte) (n int, err error) {
|
||||||
|
if b == nil {
|
||||||
|
return 0, fmt.Errorf("write to %x nil", c.remoteLNId)
|
||||||
|
}
|
||||||
|
fmt.Printf("Encrypt %d byte plaintext to %x nonce %d\n",
|
||||||
|
len(b), c.remoteLNId, c.myNonceInt)
|
||||||
|
|
||||||
|
// first encrypt message with shared key
|
||||||
|
var nonceBuf [8]byte
|
||||||
|
binary.BigEndian.PutUint64(nonceBuf[:], c.myNonceInt)
|
||||||
|
c.myNonceInt++ // increment mine
|
||||||
|
|
||||||
|
ctext := c.chachaStream.Seal(nil, nonceBuf[:], b, nil)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if len(ctext) > 65530 {
|
||||||
|
return 0, fmt.Errorf("Write to %x too long, %d bytes",
|
||||||
|
c.remoteLNId, len(ctext))
|
||||||
|
}
|
||||||
|
|
||||||
|
// use writeClear to prepend length / destination header
|
||||||
|
return writeClear(c.conn, ctext)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the connection.
|
||||||
|
// Any blocked Read or Write operations will be unblocked and return errors.
|
||||||
|
// Part of the net.Conn interface.
|
||||||
|
func (c *Conn) Close() error {
|
||||||
|
c.myNonceInt = 0
|
||||||
|
c.remoteNonceInt = 0
|
||||||
|
c.remotePub = nil
|
||||||
|
|
||||||
|
return c.conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// LocalAddr returns the local network address.
|
||||||
|
// Part of the net.Conn interface.
|
||||||
|
// If PBX reports address of pbx host.
|
||||||
|
func (c *Conn) LocalAddr() net.Addr {
|
||||||
|
return c.conn.LocalAddr()
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoteAddr returns the remote network address.
|
||||||
|
// Part of the net.Conn interface.
|
||||||
|
func (c *Conn) RemoteAddr() net.Addr {
|
||||||
|
return c.conn.RemoteAddr()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDeadline sets the read and write deadlines associated
|
||||||
|
// with the connection. It is equivalent to calling both
|
||||||
|
// SetReadDeadline and SetWriteDeadline.
|
||||||
|
// Part of the net.Conn interface.
|
||||||
|
func (c *Conn) SetDeadline(t time.Time) error {
|
||||||
|
return c.conn.SetDeadline(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetReadDeadline sets the deadline for future Read calls.
|
||||||
|
// A zero value for t means Read will not time out.
|
||||||
|
// Part of the net.Conn interface.
|
||||||
|
func (c *Conn) SetReadDeadline(t time.Time) error {
|
||||||
|
return c.conn.SetReadDeadline(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetWriteDeadline sets the deadline for future Write calls.
|
||||||
|
// Even if write times out, it may return n > 0, indicating that
|
||||||
|
// some of the data was successfully written.
|
||||||
|
// A zero value for t means Write will not time out.
|
||||||
|
// Part of the net.Conn interface.
|
||||||
|
func (c *Conn) SetWriteDeadline(t time.Time) error {
|
||||||
|
return c.conn.SetWriteDeadline(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ net.Conn = (*Conn)(nil)
|
206
lndc/listener.go
Normal file
206
lndc/listener.go
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
package lndc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/btcec"
|
||||||
|
"github.com/btcsuite/btcutil"
|
||||||
|
"github.com/btcsuite/fastsha256"
|
||||||
|
"github.com/codahale/chacha20poly1305"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
lndc := NewConn(l.longTermPriv, 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(lndc, ephemeralPub); err != nil {
|
||||||
|
lndc.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return lndc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// createCipherConn....
|
||||||
|
func (l *Listener) createCipherConn(lnConn *Conn) (*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[:])
|
||||||
|
|
||||||
|
// display private key for debug only
|
||||||
|
fmt.Printf("made session key %x\n", sessionKey)
|
||||||
|
|
||||||
|
lnConn.remoteNonceInt = 1 << 63
|
||||||
|
lnConn.myNonceInt = 0
|
||||||
|
|
||||||
|
lnConn.remotePub = theirEphPub
|
||||||
|
lnConn.authed = false
|
||||||
|
|
||||||
|
return myEph, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthListen...
|
||||||
|
func (l *Listener) authenticateConnection(lnConn *Conn, 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 {
|
||||||
|
fmt.Printf("Read error: %s\n", err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("read %d bytes\n", n)
|
||||||
|
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(lnConn.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()
|
||||||
|
}
|
97
lndc/lndc_test.go
Normal file
97
lndc/lndc_test.go
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
package lndc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/btcec"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConnectionCorrectness(t *testing.T) {
|
||||||
|
// First, generate the long-term private keys both ends of the connection
|
||||||
|
// within our test.
|
||||||
|
localPriv, err := btcec.NewPrivateKey(btcec.S256())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to generate local priv key: %v", err)
|
||||||
|
}
|
||||||
|
remotePriv, err := btcec.NewPrivateKey(btcec.S256())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to generate remote priv key: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Having a port of "0" means a random port will be chosen for our
|
||||||
|
// listener.
|
||||||
|
addr := "127.0.0.1:0"
|
||||||
|
|
||||||
|
// Our listener will be local, and the connection remote.
|
||||||
|
listener, err := NewListener(localPriv, addr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to create listener: %v", err)
|
||||||
|
}
|
||||||
|
conn := NewConn(remotePriv, nil)
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
var dialErr error
|
||||||
|
|
||||||
|
// Initiate a connection with a separate goroutine, and listen with our
|
||||||
|
// main one. If both errors are nil, then encryption+auth was succesful.
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
dialErr = conn.Dial(listener.Addr().String(),
|
||||||
|
localPriv.PubKey().SerializeCompressed())
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
|
||||||
|
localConn, listenErr := listener.Accept()
|
||||||
|
if listenErr != nil {
|
||||||
|
t.Fatalf("unable to accept connection: %v", listenErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
if dialErr != nil {
|
||||||
|
t.Fatalf("unable to establish connection: %v", dialErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test out some message full-message reads.
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
msg := []byte("hello" + string(i))
|
||||||
|
|
||||||
|
if _, err := conn.Write(msg); err != nil {
|
||||||
|
t.Fatalf("remote conn failed to write: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
readBuf := make([]byte, len(msg))
|
||||||
|
if _, err := localConn.Read(readBuf); err != nil {
|
||||||
|
t.Fatalf("local conn failed to read: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(readBuf, msg) {
|
||||||
|
t.Fatalf("messages don't match, %v vs %v",
|
||||||
|
string(readBuf), string(msg))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now try incremental message reads. This simulates first writing a
|
||||||
|
// message header, then a message body.
|
||||||
|
outMsg := []byte("hello world")
|
||||||
|
fmt.Println("write")
|
||||||
|
if _, err := conn.Write(outMsg); err != nil {
|
||||||
|
t.Fatalf("remote conn failed to write: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
readBuf := make([]byte, len(outMsg))
|
||||||
|
if _, err := localConn.Read(readBuf[:len(outMsg)/2]); err != nil {
|
||||||
|
t.Fatalf("local conn failed to read: %v", err)
|
||||||
|
}
|
||||||
|
if _, err := localConn.Read(readBuf[len(outMsg)/2:]); err != nil {
|
||||||
|
t.Fatalf("local conn failed to read: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(outMsg, readBuf) {
|
||||||
|
t.Fatalf("messages don't match, %v vs %v",
|
||||||
|
string(readBuf), string(outMsg))
|
||||||
|
}
|
||||||
|
}
|
676
lndc/netio.go
676
lndc/netio.go
@ -2,632 +2,90 @@ package lndc
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/cipher"
|
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/btcec"
|
|
||||||
"github.com/btcsuite/fastsha256"
|
|
||||||
"github.com/codahale/chacha20poly1305"
|
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
|
||||||
"golang.org/x/crypto/ripemd160"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
/* good ol' OP_HASH160, which is just ripemd160(sha256(input)) */
|
// New & improved tcp open session.
|
||||||
func H160(input []byte) []byte {
|
// There's connector A and listener B. Once the connection is set up there's no
|
||||||
rp := ripemd160.New()
|
// difference, but there can be during the setup.
|
||||||
shaout := fastsha256.Sum256(input)
|
// Setup:
|
||||||
_, _ = rp.Write(shaout[:])
|
// 1 -> A sends B ephemeral secp256k1 pubkey (33 bytes)
|
||||||
return rp.Sum(nil)
|
// 2 <- B sends A ephemeral secp256k1 pubkey (33 bytes)
|
||||||
}
|
// A and B do DH, get a shared secret.
|
||||||
|
// ==========
|
||||||
// Lightning Network Data Conection. Encrypted.
|
// Seesion is open! Done! Well not quite. Session is confidential but not
|
||||||
type LNDConn struct {
|
// yet authenticated. From here on, can use the Send() and Recv() functions with
|
||||||
Version uint8
|
// chacha20poly1305.
|
||||||
Cn net.Conn
|
// ==========
|
||||||
RemotePub *btcec.PublicKey
|
|
||||||
RemoteLNId [16]byte
|
// Nodes authenticate by doing a DH with their persistent identity keys, and then
|
||||||
MyNonceInt uint64
|
// exchanging hash based proofs that they got the same shared IDDH secret.
|
||||||
RemoteNonceInt uint64
|
// The DH proof is h160(remote eph pubkey, IDDH secret)
|
||||||
// if Authed == false, the RemotePub is the EPHEMERAL key.
|
// A initiates auth.
|
||||||
// once authed == true, RemotePub is who you're actually talking to.
|
//
|
||||||
Authed bool
|
// If A does not know B's pubkey but only B's pubkey hash:
|
||||||
// chachaStream saves some time as you don't have to init it with
|
//
|
||||||
// the session key every time. Make SessionKey redundant; remove later
|
// 1 -> A sends [PubKeyA, PubKeyHashB] (53 bytes)
|
||||||
chachaStream cipher.AEAD
|
// B computes ID pubkey DH
|
||||||
|
// 2 <- B sends [PubkeyB, DH proof] (53 bytes)
|
||||||
// ViaPbx specifies whether this is a direct TCP connection or an
|
// 3 -> A sends DH proof (20 bytes)
|
||||||
// encapsulated PBX connection.
|
// done.
|
||||||
// if going ViaPbx, Cn isn't used
|
//
|
||||||
// channels are used for Read() and Write(),
|
// This exchange can be sped up if A already knows B's pubkey:
|
||||||
// which are filled by the PBXhandler.
|
//
|
||||||
ViaPbx bool
|
// A already knows who they're talking to, or trying to talk to
|
||||||
PbxIncoming chan []byte
|
// 1 -> A sends [PubKeyA, PubkeyHashB, DH proof] (73 bytes)
|
||||||
PbxOutgoing chan []byte
|
// 2 <- B sends DH proof (20 bytes)
|
||||||
}
|
//
|
||||||
|
// A and B both verify those H160 hashes, and if matching consider their
|
||||||
/* new & improved tcp open session.
|
// session counterparty authenticated.
|
||||||
There's connector A and listener B. Once the connection is set up there's no
|
//
|
||||||
difference, but there can be during the setup.
|
// A possible weakness of the DH proof is if B re-uses eph keys. That potentially
|
||||||
Setup:
|
// makes *A*'s proof weaker though. A gets to choose the proof B creates. As
|
||||||
1 -> A sends B ephemeral secp256k1 pubkey (33 bytes)
|
// long as your software makes new eph keys each time, you should be OK.
|
||||||
2 <- B sends A ephemeral secp256k1 pubkey (33 bytes)
|
|
||||||
A and B do DH, get a shared secret.
|
// readClear and writeClear don't encrypt but directly read and write to the
|
||||||
==========
|
// underlying data link, only adding or subtracting a 2 byte length header.
|
||||||
Seesion is open! Done! Well not quite. Session is confidential but not
|
// All Read() and Write() calls for lndc's use these functions internally
|
||||||
yet authenticated. From here on, can use the Send() and Recv() functions with
|
// (they aren't exported). They're also used in the key agreement phase.
|
||||||
chacha20poly1305.
|
|
||||||
==========
|
// readClear reads the next length-prefixed message from the underlying raw
|
||||||
The DH proof is h160(remote eph pubkey, IDDH secret)
|
// TCP connection.
|
||||||
A initiates auth.
|
func readClear(c net.Conn) ([]byte, error) {
|
||||||
|
|
||||||
If A does not know B's pubkey but only B's pubkey hash:
|
|
||||||
|
|
||||||
1 -> A sends [PubKeyA, PubKeyHashB] (53 bytes)
|
|
||||||
B computes ID pubkey DH
|
|
||||||
2 <- B sends [PubkeyB, DH proof] (53 bytes)
|
|
||||||
3 -> A sends DH proof (20 bytes)
|
|
||||||
done.
|
|
||||||
|
|
||||||
This exchange can be sped up if A already knows B's pubkey:
|
|
||||||
|
|
||||||
A already knows who they're talking to, or trying to talk to
|
|
||||||
1 -> A sends [PubKeyA, PubkeyHashB, DH proof] (73 bytes)
|
|
||||||
2 <- B sends DH proof (20 bytes)
|
|
||||||
|
|
||||||
A and B both verify those H160 hashes, and if matching consider their
|
|
||||||
session counterparty authenticated.
|
|
||||||
|
|
||||||
A possible weakness of the DH proof is if B re-uses eph keys. That potentially
|
|
||||||
makes *A*'s proof weaker though. A gets to choose the proof B creates. As
|
|
||||||
long as your software makes new eph keys each time, you should be
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Open creates and auths an lndc connections,
|
|
||||||
// after the TCP or pbx connection is already assigned / dialed.
|
|
||||||
func (lndc *LNDConn) Open(
|
|
||||||
me *btcec.PrivateKey, remote []byte) error {
|
|
||||||
// make TCP connection to listening host
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if len(remote) != 33 && len(remote) != 20 {
|
|
||||||
return fmt.Errorf("must supply either remote pubkey or pubkey hash")
|
|
||||||
}
|
|
||||||
// calc remote LNId; need this for creating pbx connections
|
|
||||||
// just because LNid is in the struct does not mean it's authed!
|
|
||||||
if len(remote) == 20 {
|
|
||||||
copy(lndc.RemoteLNId[:], remote[:16])
|
|
||||||
} else {
|
|
||||||
theirAdr := H160(remote)
|
|
||||||
copy(lndc.RemoteLNId[:], theirAdr[:16])
|
|
||||||
}
|
|
||||||
|
|
||||||
// make up an ephtemeral keypair. Doesn't exit this function.
|
|
||||||
myEph, err := btcec.NewPrivateKey(btcec.S256())
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sned 1. Send my ephemeral pubkey. Can add version bits.
|
|
||||||
err = lndc.writeClear(myEph.PubKey().SerializeCompressed())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// read their ephemeral pubkey
|
|
||||||
TheirEphPubBytes, err := lndc.readClear()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// deserialize their ephemeral pubkey
|
|
||||||
TheirEphPub, err := btcec.ParsePubKey(TheirEphPubBytes, btcec.S256())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// do non-interactive diffie with ephemeral pubkeys
|
|
||||||
// sha256 for good luck
|
|
||||||
sessionKey :=
|
|
||||||
fastsha256.Sum256(btcec.GenerateSharedSecret(myEph, TheirEphPub))
|
|
||||||
|
|
||||||
lndc.chachaStream, err = chacha20poly1305.New(sessionKey[:])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// display private key for debug only
|
|
||||||
fmt.Printf("made session key %x\n", sessionKey)
|
|
||||||
|
|
||||||
lndc.MyNonceInt = 1 << 63
|
|
||||||
lndc.RemoteNonceInt = 0
|
|
||||||
|
|
||||||
lndc.RemotePub = TheirEphPub
|
|
||||||
lndc.Authed = false
|
|
||||||
|
|
||||||
// session is now open and confidential but not yet authenticated.
|
|
||||||
// So auth!
|
|
||||||
if len(remote) == 20 { // only know pubkey hash (20 bytes)
|
|
||||||
return lndc.AuthPKH(me, remote, myEph.PubKey().SerializeCompressed())
|
|
||||||
} else { // must be 33 byte pubkey
|
|
||||||
return lndc.AuthPubKey(me, remote, myEph.PubKey().SerializeCompressed())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TcpListen takes a lndc that's just connected, and sets it up.
|
|
||||||
// Call this just after a connection comes in.
|
|
||||||
// calls AuthListen, waiting for the auth step before returning.
|
|
||||||
// in the case of a pbx connection, there is no lndc.Cn
|
|
||||||
func (lndc *LNDConn) Setup(me *btcec.PrivateKey) error {
|
|
||||||
var err error
|
|
||||||
var TheirEphPubBytes []byte
|
|
||||||
|
|
||||||
TheirEphPubBytes, err = lndc.readClear()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(TheirEphPubBytes) != 33 {
|
|
||||||
return fmt.Errorf("Got invalid %d byte eph pubkey %x\n",
|
|
||||||
len(TheirEphPubBytes), TheirEphPubBytes)
|
|
||||||
}
|
|
||||||
// deserialize their ephemeral pubkey
|
|
||||||
TheirEphPub, err := btcec.ParsePubKey(TheirEphPubBytes, btcec.S256())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// their key looks OK, make our own
|
|
||||||
myEph, err := btcec.NewPrivateKey(btcec.S256())
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// and send out our eph key
|
|
||||||
err = lndc.writeClear(myEph.PubKey().SerializeCompressed())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// do non-interactive diffie with ephemeral pubkeys
|
|
||||||
// sha256 for good luck
|
|
||||||
sessionKey :=
|
|
||||||
fastsha256.Sum256(btcec.GenerateSharedSecret(myEph, TheirEphPub))
|
|
||||||
|
|
||||||
lndc.chachaStream, err = chacha20poly1305.New(sessionKey[:])
|
|
||||||
|
|
||||||
// display private key for debug only
|
|
||||||
fmt.Printf("made session key %x\n", sessionKey)
|
|
||||||
|
|
||||||
lndc.RemoteNonceInt = 1 << 63
|
|
||||||
lndc.MyNonceInt = 0
|
|
||||||
|
|
||||||
lndc.RemotePub = TheirEphPub
|
|
||||||
lndc.Authed = false
|
|
||||||
|
|
||||||
// session is open and confidential but not yet authenticated.
|
|
||||||
// Listen for auth message
|
|
||||||
return lndc.AuthListen(me, myEph.PubKey().SerializeCompressed())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lndc *LNDConn) AuthListen(
|
|
||||||
myId *btcec.PrivateKey, localEphPubBytes []byte) error {
|
|
||||||
|
|
||||||
var err error
|
|
||||||
slice := make([]byte, 65535)
|
|
||||||
n, err := lndc.Read(slice)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Printf("read %d bytes\n", n)
|
|
||||||
slice = slice[:n]
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Read error: %s\n", err.Error())
|
|
||||||
err2 := lndc.Close()
|
|
||||||
if err2 != nil {
|
|
||||||
return err2
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
authmsg := slice
|
|
||||||
if len(authmsg) != 53 && len(authmsg) != 73 {
|
|
||||||
err = lndc.Close()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return fmt.Errorf(
|
|
||||||
"Got auth message of %d bytes, expect 53 or 73", len(authmsg))
|
|
||||||
}
|
|
||||||
theirPub, err := btcec.ParsePubKey(authmsg[:33], btcec.S256())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
myPKH := H160(myId.PubKey().SerializeCompressed())
|
|
||||||
if bytes.Equal(myPKH, authmsg[33:53]) == false {
|
|
||||||
err = lndc.Close()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return fmt.Errorf(
|
|
||||||
"remote host asking for PKH %x, that's not me", authmsg[33:53])
|
|
||||||
}
|
|
||||||
|
|
||||||
// do DH with id keys
|
|
||||||
idDH := fastsha256.Sum256(btcec.GenerateSharedSecret(myId, theirPub))
|
|
||||||
myDHproof := H160(append(lndc.RemotePub.SerializeCompressed(), idDH[:]...))
|
|
||||||
theirDHproof := H160(append(localEphPubBytes, idDH[:]...))
|
|
||||||
if len(authmsg) == 73 { // quick mode
|
|
||||||
// verify their DH proof
|
|
||||||
if bytes.Equal(authmsg[53:], theirDHproof) == false {
|
|
||||||
err = lndc.Close()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return fmt.Errorf(
|
|
||||||
"Invalid DH proof from %s", lndc.Cn.RemoteAddr().String())
|
|
||||||
}
|
|
||||||
// looks good, send my own
|
|
||||||
_, err = lndc.Write(myDHproof)
|
|
||||||
if err != nil {
|
|
||||||
err2 := lndc.Close()
|
|
||||||
if err2 != nil {
|
|
||||||
return err2
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// and we're authed
|
|
||||||
} else { // 53 byte, they don't know my pubkey
|
|
||||||
msg := append(myId.PubKey().SerializeCompressed(), myDHproof...)
|
|
||||||
_, err = lndc.Write(msg)
|
|
||||||
if err != nil {
|
|
||||||
err2 := lndc.Close()
|
|
||||||
if err2 != nil {
|
|
||||||
return err2
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
resp := make([]byte, 65535)
|
|
||||||
n, err := lndc.Read(resp)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
resp = resp[:n]
|
|
||||||
|
|
||||||
if n != 20 {
|
|
||||||
err2 := lndc.Close()
|
|
||||||
if err2 != nil {
|
|
||||||
return err2
|
|
||||||
}
|
|
||||||
fmt.Errorf("expected 20 byte DH proof, got %d", n)
|
|
||||||
}
|
|
||||||
|
|
||||||
// verify their DH proof
|
|
||||||
if bytes.Equal(resp, theirDHproof) == false {
|
|
||||||
err = lndc.Close()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return fmt.Errorf("Invalid DH proof %x", theirDHproof)
|
|
||||||
}
|
|
||||||
//proof looks good, auth
|
|
||||||
}
|
|
||||||
lndc.RemotePub = theirPub
|
|
||||||
theirAdr := H160(theirPub.SerializeCompressed())
|
|
||||||
copy(lndc.RemoteLNId[:], theirAdr[:16])
|
|
||||||
lndc.Authed = true
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lndc *LNDConn) AuthPKH(
|
|
||||||
myId *btcec.PrivateKey, theirPKH, localEphPubBytes []byte) error {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if myId == nil {
|
|
||||||
return fmt.Errorf("can't auth: supplied privkey is nil")
|
|
||||||
}
|
|
||||||
if lndc.Authed {
|
|
||||||
return fmt.Errorf("%s already authed", lndc.RemotePub)
|
|
||||||
}
|
|
||||||
if len(theirPKH) != 20 {
|
|
||||||
return fmt.Errorf("remote PKH must be 20 bytes, got %d", len(theirPKH))
|
|
||||||
}
|
|
||||||
|
|
||||||
// send 53 bytes; my pubkey, and remote pubkey hash
|
|
||||||
msg := myId.PubKey().SerializeCompressed()
|
|
||||||
msg = append(msg, theirPKH...)
|
|
||||||
|
|
||||||
_, err = lndc.Write(msg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// wait for their response.
|
|
||||||
// TODO add timeout here
|
|
||||||
resp := make([]byte, 65535)
|
|
||||||
n, err := lndc.Read(resp)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
resp = resp[:n]
|
|
||||||
// response should be 53 bytes, their pubkey and DH proof
|
|
||||||
if n != 53 {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"PKH auth response should be 53 bytes, got %d", len(resp))
|
|
||||||
}
|
|
||||||
theirPub, err := btcec.ParsePubKey(resp[:33], btcec.S256())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
idDH := fastsha256.Sum256(btcec.GenerateSharedSecret(myId, theirPub))
|
|
||||||
fmt.Printf("made idDH %x\n", idDH)
|
|
||||||
|
|
||||||
theirDHproof := H160(append(localEphPubBytes, idDH[:]...))
|
|
||||||
// verify their DH proof
|
|
||||||
if bytes.Equal(resp[33:], theirDHproof) == false {
|
|
||||||
return fmt.Errorf("Invalid DH proof %x", theirDHproof)
|
|
||||||
}
|
|
||||||
// their DH proof checks out, send our own
|
|
||||||
myDHproof := H160(append(lndc.RemotePub.SerializeCompressed(), idDH[:]...))
|
|
||||||
_, err = lndc.Write(myDHproof)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// proof sent, auth complete
|
|
||||||
lndc.RemotePub = theirPub
|
|
||||||
theirAdr := H160(theirPub.SerializeCompressed())
|
|
||||||
copy(lndc.RemoteLNId[:], theirAdr[:16])
|
|
||||||
lndc.Authed = true
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lndc *LNDConn) AuthPubKey(
|
|
||||||
myId *btcec.PrivateKey, remotePubBytes, localEphPubBytes []byte) error {
|
|
||||||
|
|
||||||
var err error
|
|
||||||
if myId == nil {
|
|
||||||
return fmt.Errorf("can't auth: supplied privkey is nil")
|
|
||||||
}
|
|
||||||
if lndc.Authed {
|
|
||||||
return fmt.Errorf("%s already authed", lndc.RemotePub)
|
|
||||||
}
|
|
||||||
theirPub, err := btcec.ParsePubKey(remotePubBytes, btcec.S256())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
theirPKH := H160(remotePubBytes)
|
|
||||||
// I know enough to generate DH, do so
|
|
||||||
idDH := fastsha256.Sum256(btcec.GenerateSharedSecret(myId, theirPub))
|
|
||||||
myDHproof := H160(append(lndc.RemotePub.SerializeCompressed(), idDH[:]...))
|
|
||||||
// message is 73 bytes; my pubkey, their pubkey hash, DH proof
|
|
||||||
msg := myId.PubKey().SerializeCompressed()
|
|
||||||
msg = append(msg, theirPKH...)
|
|
||||||
msg = append(msg, myDHproof...)
|
|
||||||
|
|
||||||
_, err = lndc.Write(msg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
resp := make([]byte, 65535)
|
|
||||||
n, err := lndc.Read(resp)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
resp = resp[:n]
|
|
||||||
if n != 20 {
|
|
||||||
fmt.Errorf("expected 20 byte DH proof, got %d", len(resp))
|
|
||||||
}
|
|
||||||
|
|
||||||
theirDHproof := H160(append(localEphPubBytes, idDH[:]...))
|
|
||||||
// verify their DH proof
|
|
||||||
if bytes.Equal(resp, theirDHproof) == false {
|
|
||||||
return fmt.Errorf("Invalid DH proof %x", theirDHproof)
|
|
||||||
}
|
|
||||||
// proof checks out, auth complete
|
|
||||||
lndc.RemotePub = theirPub
|
|
||||||
theirAdr := H160(theirPub.SerializeCompressed())
|
|
||||||
copy(lndc.RemoteLNId[:], theirAdr[:16])
|
|
||||||
lndc.Authed = true
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lndc *LNDConn) WhoAreYou(host string) (*btcec.PublicKey, error) {
|
|
||||||
var err error
|
|
||||||
if lndc.Cn == nil {
|
|
||||||
return nil, fmt.Errorf("no connection to ask on")
|
|
||||||
}
|
|
||||||
fmt.Printf("connecting to address %s\n", host)
|
|
||||||
// make TCP connection to listening host
|
|
||||||
lndc.Cn, err = net.Dial("tcp", host)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = lndc.writeClear([]byte("WHOAREYOU"))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var b bytes.Buffer
|
|
||||||
lndc.Read(b.Bytes())
|
|
||||||
|
|
||||||
IamResp, err := lndc.readClear()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return btcec.ParsePubKey(IamResp[:33], btcec.S256())
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Make ETcpCons adhere to Conn interface.
|
|
||||||
need:
|
|
||||||
Read(b []byte) (n int, err error)
|
|
||||||
Write(b []byte) (n int, err error)
|
|
||||||
Close() error
|
|
||||||
LocalAddr() Addr
|
|
||||||
RemoteAddr() Addr
|
|
||||||
SetDeadline(t time.Time) error
|
|
||||||
SetReadDeadline(t time.Time) error
|
|
||||||
SetWriteDeadline(t time.Time) error
|
|
||||||
|
|
||||||
ETcpCons can be either regular TCP connections, which is good, or PBX-routed
|
|
||||||
connections, which is worse, but there are levels of connectivity we are
|
|
||||||
prepared to accept.
|
|
||||||
|
|
||||||
When it's PBX, don't try to use Cn
|
|
||||||
*/
|
|
||||||
|
|
||||||
func (lndc *LNDConn) Read(b []byte) (n int, err error) {
|
|
||||||
// first get message length from first 2 bytes
|
|
||||||
var ctext []byte
|
|
||||||
ctext, err = lndc.readClear()
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// now decrypt
|
|
||||||
nonceBuf := new(bytes.Buffer)
|
|
||||||
err = binary.Write(nonceBuf, binary.BigEndian, lndc.RemoteNonceInt)
|
|
||||||
fmt.Printf("decrypt %d byte from %x nonce %d\n",
|
|
||||||
len(ctext), lndc.RemoteLNId, lndc.RemoteNonceInt)
|
|
||||||
lndc.RemoteNonceInt++ // increment remote nonce, no matter what...
|
|
||||||
|
|
||||||
msg, err := lndc.chachaStream.Open(nil, nonceBuf.Bytes(), ctext, nil)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("decrypt %d byte ciphertext failed\n", len(ctext))
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
n = copy(b, msg)
|
|
||||||
if n < len(msg) {
|
|
||||||
return 0, fmt.Errorf(
|
|
||||||
"Can't read from %x: Slice provided too small. %d bytes, need %d",
|
|
||||||
lndc.RemoteLNId, len(b), len(msg))
|
|
||||||
}
|
|
||||||
|
|
||||||
return n, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lndc *LNDConn) Write(b []byte) (n int, err error) {
|
|
||||||
if b == nil {
|
|
||||||
return 0, fmt.Errorf("Write to %x nil", lndc.RemoteLNId)
|
|
||||||
}
|
|
||||||
fmt.Printf("Encrypt %d byte plaintext to %x nonce %d\n",
|
|
||||||
len(b), lndc.RemoteLNId, lndc.MyNonceInt)
|
|
||||||
// first encrypt message with shared key
|
|
||||||
nonceBuf := new(bytes.Buffer)
|
|
||||||
err = binary.Write(nonceBuf, binary.BigEndian, lndc.MyNonceInt)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
lndc.MyNonceInt++ // increment mine
|
|
||||||
|
|
||||||
ctext := lndc.chachaStream.Seal(nil, nonceBuf.Bytes(), b, nil)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
if len(ctext) > 65530 {
|
|
||||||
return 0, fmt.Errorf("Write to %x too long, %d bytes",
|
|
||||||
lndc.RemoteLNId, len(ctext))
|
|
||||||
}
|
|
||||||
|
|
||||||
// use writeClear to prepend length / destination header
|
|
||||||
err = lndc.writeClear(ctext)
|
|
||||||
// returns len of how much you put in, not how much written on the wire
|
|
||||||
return len(b), err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lndc *LNDConn) Close() error {
|
|
||||||
// return lndc.Cn.Close()
|
|
||||||
// return nil
|
|
||||||
lndc.MyNonceInt = 0
|
|
||||||
lndc.RemoteNonceInt = 0
|
|
||||||
lndc.RemotePub = nil
|
|
||||||
|
|
||||||
// if lndc.ViaPbx {
|
|
||||||
// return nil // don't close pbx connection
|
|
||||||
// }
|
|
||||||
err := lndc.Cn.Close()
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// network address; if PBX reports address of pbx host
|
|
||||||
func (lndc *LNDConn) LocalAddr() net.Addr {
|
|
||||||
return lndc.Cn.LocalAddr()
|
|
||||||
}
|
|
||||||
func (lndc *LNDConn) RemoteAddr() net.Addr {
|
|
||||||
return lndc.Cn.RemoteAddr()
|
|
||||||
}
|
|
||||||
|
|
||||||
// timing stuff for net.conn compatibility
|
|
||||||
func (lndc *LNDConn) SetDeadline(t time.Time) error {
|
|
||||||
return lndc.Cn.SetDeadline(t)
|
|
||||||
}
|
|
||||||
func (lndc *LNDConn) SetReadDeadline(t time.Time) error {
|
|
||||||
|
|
||||||
return lndc.Cn.SetReadDeadline(t)
|
|
||||||
}
|
|
||||||
func (lndc *LNDConn) SetWriteDeadline(t time.Time) error {
|
|
||||||
return lndc.Cn.SetWriteDeadline(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ReadClear and WriteClear don't encrypt but directly read and write to the
|
|
||||||
underlying data link, only adding or subtracting a 2 byte length header.
|
|
||||||
All Read() and Write() calls for lndc's use these functions internally
|
|
||||||
(they aren't exported). They're also used in the key agreement phase.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// encapsulation for sending to Pbx host.
|
|
||||||
// put FWDMSG, then 16 byte destination ID, then message (with msgtype)
|
|
||||||
func (lndc *LNDConn) PbxEncapsulate(b *[]byte) {
|
|
||||||
fmt.Printf("PbxEncapsulate %d byte message, dest ID %x\n",
|
|
||||||
len(*b), lndc.RemoteLNId)
|
|
||||||
*b = append(lndc.RemoteLNId[:], *b...)
|
|
||||||
*b = append([]byte{lnwire.MSGID_FWDMSG}, *b...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lndc *LNDConn) readClear() ([]byte, error) {
|
|
||||||
var msgLen uint16
|
var msgLen uint16
|
||||||
// needs to be pointer or will silently not do anything.
|
|
||||||
var msg []byte
|
|
||||||
|
|
||||||
if lndc.ViaPbx { //pbx mode
|
if err := binary.Read(c, binary.BigEndian, &msgLen); err != nil {
|
||||||
msg = <-lndc.PbxIncoming
|
|
||||||
} else { // normal tcp mode
|
|
||||||
err := binary.Read(lndc.Cn, binary.BigEndian, &msgLen)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// fmt.Printf("%d byte LMsg incoming\n", msgLen)
|
|
||||||
msg = make([]byte, msgLen)
|
msg := make([]byte, msgLen)
|
||||||
_, err = io.ReadFull(lndc.Cn, msg)
|
if _, err := io.ReadFull(c, msg); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return msg, nil
|
return msg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (lndc *LNDConn) writeClear(msg []byte) error {
|
// TODO(roasbeef): incorporate buffer pool
|
||||||
var err error
|
|
||||||
|
// writeClear writes the passed message with a prefixed 2-byte length header.
|
||||||
|
func writeClear(conn net.Conn, msg []byte) (int, error) {
|
||||||
if len(msg) > 65530 {
|
if len(msg) > 65530 {
|
||||||
return fmt.Errorf("LMsg too long, %d bytes", len(msg))
|
return 0, fmt.Errorf("lmsg too long, %d bytes", len(msg))
|
||||||
}
|
}
|
||||||
if msg == nil {
|
|
||||||
return fmt.Errorf("LMsg nil")
|
// Add 2 byte length header (pbx doesn't need it) and send over TCP.
|
||||||
|
var msgBuf bytes.Buffer
|
||||||
|
if err := binary.Write(&msgBuf, binary.BigEndian, uint16(len(msg))); err != nil {
|
||||||
|
return 0, err
|
||||||
}
|
}
|
||||||
if lndc.ViaPbx {
|
|
||||||
lndc.PbxEncapsulate(&msg)
|
if _, err := msgBuf.Write(msg); err != nil {
|
||||||
lndc.PbxOutgoing <- msg
|
return 0, err
|
||||||
} else {
|
|
||||||
// add 2 byte length header (pbx doesn't need it) and send over TCP
|
|
||||||
hdr := new(bytes.Buffer)
|
|
||||||
err = binary.Write(hdr, binary.BigEndian, uint16(len(msg)))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
msg = append(hdr.Bytes(), msg...)
|
|
||||||
_, err = lndc.Cn.Write(msg)
|
return conn.Write(msgBuf.Bytes())
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user