From 04b83d024e0e852edfc9024a1ec46adb1ccabd9b Mon Sep 17 00:00:00 2001 From: Tadge Dryja Date: Sat, 16 Jan 2016 11:07:20 -0800 Subject: [PATCH] rever to older netio --- lndc/conn.go | 368 ------------------------- lndc/listener.go | 206 -------------- lndc/lndc_test.go | 97 ------- lndc/netio.go | 668 ++++++++++++++++++++++++++++++++++++++++------ 4 files changed, 582 insertions(+), 757 deletions(-) delete mode 100644 lndc/conn.go delete mode 100644 lndc/listener.go delete mode 100644 lndc/lndc_test.go diff --git a/lndc/conn.go b/lndc/conn.go deleted file mode 100644 index e080fb146..000000000 --- a/lndc/conn.go +++ /dev/null @@ -1,368 +0,0 @@ -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 LNDConn 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) *LNDConn { - return &LNDConn{longTermPriv: connPrivKey, conn: conn} -} - -// Dial... -func (c *LNDConn) 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 *LNDConn) 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 *LNDConn) 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 *LNDConn) 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 *LNDConn) 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 *LNDConn) 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 *LNDConn) LocalAddr() net.Addr { - return c.conn.LocalAddr() -} - -// RemoteAddr returns the remote network address. -// Part of the net.Conn interface. -func (c *LNDConn) 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 *LNDConn) 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 *LNDConn) 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 *LNDConn) SetWriteDeadline(t time.Time) error { - return c.conn.SetWriteDeadline(t) -} - -var _ net.Conn = (*LNDConn)(nil) diff --git a/lndc/listener.go b/lndc/listener.go deleted file mode 100644 index 7afe1e468..000000000 --- a/lndc/listener.go +++ /dev/null @@ -1,206 +0,0 @@ -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 *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[:]) - - // 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 *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 { - 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() -} diff --git a/lndc/lndc_test.go b/lndc/lndc_test.go deleted file mode 100644 index 0d2fae975..000000000 --- a/lndc/lndc_test.go +++ /dev/null @@ -1,97 +0,0 @@ -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)) - } -} diff --git a/lndc/netio.go b/lndc/netio.go index 8ce99abd0..38ba9f3b9 100644 --- a/lndc/netio.go +++ b/lndc/netio.go @@ -2,56 +2,436 @@ package lndc import ( "bytes" + "crypto/cipher" "encoding/binary" "fmt" "io" "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" ) -// New & improved tcp open session. -// There's connector A and listener B. Once the connection is set up there's no -// difference, but there can be during the setup. -// Setup: -// 1 -> A sends B ephemeral secp256k1 pubkey (33 bytes) -// 2 <- B sends A ephemeral secp256k1 pubkey (33 bytes) -// A and B do DH, get a shared secret. -// ========== -// Seesion is open! Done! Well not quite. Session is confidential but not -// yet authenticated. From here on, can use the Send() and Recv() functions with -// chacha20poly1305. -// ========== +/* good ol' OP_HASH160, which is just ripemd160(sha256(input)) */ +func H160(input []byte) []byte { + rp := ripemd160.New() + shaout := fastsha256.Sum256(input) + _, _ = rp.Write(shaout[:]) + return rp.Sum(nil) +} -// Nodes authenticate by doing a DH with their persistent identity keys, and then -// exchanging hash based proofs that they got the same shared IDDH secret. -// The DH proof is h160(remote eph pubkey, IDDH secret) -// A initiates auth. -// -// 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 OK. +// Lightning Network Data Conection. Encrypted. +type LNDConn struct { + Version uint8 + Cn net.Conn + 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 -// WhoAreYou... -/*func (lndc *LNDConn) WhoAreYou(host string) (*btcec.PublicKey, error) { + // 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 +} + +/* new & improved tcp open session. +There's connector A and listener B. Once the connection is set up there's no +difference, but there can be during the setup. +Setup: +1 -> A sends B ephemeral secp256k1 pubkey (33 bytes) +2 <- B sends A ephemeral secp256k1 pubkey (33 bytes) +A and B do DH, get a shared secret. +========== +Seesion is open! Done! Well not quite. Session is confidential but not +yet authenticated. From here on, can use the Send() and Recv() functions with +chacha20poly1305. +========== +The DH proof is h160(remote eph pubkey, IDDH secret) +A initiates auth. + +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") @@ -74,64 +454,180 @@ import ( return nil, err } return btcec.ParsePubKey(IamResp[:33], btcec.S256()) -}*/ - -// PbxEncapsulate... -// 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 H160(input []byte) []byte { - rp := ripemd160.New() - shaout := fastsha256.Sum256(input) - _, _ = rp.Write(shaout[:]) - return rp.Sum(nil) } -// 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. +/* +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 -// readClear reads the next length-prefixed message from the underlying raw -// TCP connection. -func readClear(c net.Conn) ([]byte, 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 + // needs to be pointer or will silently not do anything. + var msg []byte - if err := binary.Read(c, binary.BigEndian, &msgLen); err != nil { - return nil, err + if lndc.ViaPbx { //pbx mode + msg = <-lndc.PbxIncoming + } else { // normal tcp mode + err := binary.Read(lndc.Cn, binary.BigEndian, &msgLen) + if err != nil { + return nil, err + } + // fmt.Printf("%d byte LMsg incoming\n", msgLen) + msg = make([]byte, msgLen) + _, err = io.ReadFull(lndc.Cn, msg) + if err != nil { + return nil, err + } } - - msg := make([]byte, msgLen) - if _, err := io.ReadFull(c, msg); err != nil { - return nil, err - } - return msg, nil } -// TODO(roasbeef): incorporate buffer pool - -// writeClear writes the passed message with a prefixed 2-byte length header. -func writeClear(conn net.Conn, msg []byte) (int, error) { +func (lndc *LNDConn) writeClear(msg []byte) error { + var err error if len(msg) > 65530 { - return 0, fmt.Errorf("lmsg too long, %d bytes", len(msg)) + return fmt.Errorf("LMsg too long, %d bytes", len(msg)) } - - // 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 msg == nil { + return fmt.Errorf("LMsg nil") } - - if _, err := msgBuf.Write(msg); err != nil { - return 0, err + if lndc.ViaPbx { + lndc.PbxEncapsulate(&msg) + lndc.PbxOutgoing <- msg + } 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 err } - - return conn.Write(msgBuf.Bytes()) + return nil }