lnd.xprv/brontide/conn.go
Olaoluwa Osuntokun 38d3c72dc8
brontide: add new ReadMessage method to brontide.Conn
This commit adds a new message to the brontide.Conn struct which allows
callers to read an _entire_ message from the stream. As defined now,
brontide is a message crypto messaging protocol. Previously the only
method that allowed callers to read attempted to hide this feature with
a stream-like abstraction. However, having this as the sole interface
is at odds with the message oriented Lightning wire protocol, and isn’t
sufficient to allow parsing messages that have been padded as is
allowed by the protocol.

This new ReadNextMessage is intended to be used by higher level systems
which implement the Lightning p2p protocol.
2017-04-20 15:35:35 -07:00

215 lines
6.2 KiB
Go

package brontide
import (
"bytes"
"io"
"math"
"net"
"time"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/roasbeef/btcd/btcec"
)
// Conn is an implementation of net.Conn which enforces an authenticated key
// exchange and message encryption protocol dubbed "Brontide" after initial TCP
// connection establishment. In the case of a successful handshake, all
// messages sent via the .Write() method are encrypted with an AEAD cipher
// along with an encrypted length-prefix. See the Machine struct for
// additional details w.r.t to the handshake and encryption scheme.
type Conn struct {
conn net.Conn
noise *Machine
readBuf bytes.Buffer
}
// A compile-time assertion to ensure that Conn meets the net.Conn interface.
var _ net.Conn = (*Conn)(nil)
// Dial attempts to establish an encrypted+authenticated connection with the
// remote peer located at address which has remotePub as its long-term static
// public key. In the case of a handshake failure, the connection is closed and
// a non-nil error is returned.
func Dial(localPriv *btcec.PrivateKey, netAddr *lnwire.NetAddress) (*Conn, error) {
ipAddr := netAddr.Address.String()
conn, err := net.Dial("tcp", ipAddr)
if err != nil {
return nil, err
}
b := &Conn{
conn: conn,
noise: NewBrontideMachine(true, localPriv, netAddr.IdentityKey),
}
// Initiate the handshake by sending the first act to the receiver.
actOne, err := b.noise.GenActOne()
if err != nil {
b.conn.Close()
return nil, err
}
if _, err := conn.Write(actOne[:]); err != nil {
b.conn.Close()
return nil, err
}
// If the first act was successful (we know that address is actually
// remotePub), then read the second act after which we'll be able to
// send our static public key to the remote peer with strong forward
// secrecy.
var actTwo [ActTwoSize]byte
if _, err := io.ReadFull(conn, actTwo[:]); err != nil {
b.conn.Close()
return nil, err
}
if err := b.noise.RecvActTwo(actTwo); err != nil {
b.conn.Close()
return nil, err
}
// Finally, complete the handshake by sending over our encrypted static
// key and execute the final ECDH operation.
actThree, err := b.noise.GenActThree()
if err != nil {
b.conn.Close()
return nil, err
}
if _, err := conn.Write(actThree[:]); err != nil {
b.conn.Close()
return nil, err
}
return b, nil
}
// ReadMessage uses the connection in a message-oriented instructing it to read
// the next _full_ message with the brontide stream. This function will block
// until the read succeeds.
func (c *Conn) ReadNextMessage() ([]byte, error) {
return c.noise.ReadMessage(c.conn)
}
// 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 {
plaintext, err := c.noise.ReadMessage(c.conn)
if err != nil {
return 0, err
}
if _, err := c.readBuf.Write(plaintext); 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 the message doesn't require any chunking, then we can go ahead
// with a single write.
if len(b) <= math.MaxUint16 {
return len(b), c.noise.WriteMessage(c.conn, b)
}
// If we need to split the message into fragments, then we'll write
// chunks which maximize usage of the available payload.
chunkSize := math.MaxUint16
bytesToWrite := len(b)
bytesWritten := 0
for bytesWritten < bytesToWrite {
// If we're on the last chunk, then truncate the chunk size as
// necessary to avoid an out-of-bounds array memory access.
if bytesWritten+chunkSize > len(b) {
chunkSize = len(b) - bytesWritten
}
// Slice off the next chunk to be written based on our running
// counter and next chunk size.
chunk := b[bytesWritten : bytesWritten+chunkSize]
if err := c.noise.WriteMessage(c.conn, chunk); err != nil {
return bytesWritten, err
}
bytesWritten += len(chunk)
}
return bytesWritten, nil
}
// 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 {
// TODO(roasbeef): reset brontide state?
return c.conn.Close()
}
// LocalAddr returns the local network address.
//
// Part of the net.Conn interface.
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)
}
// RemotePub returns the remote peer's static public key.
func (c *Conn) RemotePub() *btcec.PublicKey {
return c.noise.remoteStatic
}
// LocalPub returns the local peer's static public key.
func (c *Conn) LocalPub() *btcec.PublicKey {
return c.noise.localStatic.PubKey()
}