lnd.xprv/brontide/conn.go
Olaoluwa Osuntokun ab97d9693f
brontide: an authenticated key agreement protocol in three acts
This commit introduces Brontide: an authenticated key agreement
protocol in three acts. Brontide is the successor to lndc within lnd,
and ultimately within the greater Lighting Network. Brontide uses the
Noise_XK handshake for initial key agreement, then implements an AEAD
scheme which encrypts+authenticates both packets, and the lengths of
the packets on the wire. The initial authentication handshake preserves
the responder’s identity by never transmitting it to the initiator and
performing mutual authentication via an incremental Triple-DH based on
ECDH of secp256k1 and an HKDF which uses SHA-256.

Bronzed isn’t yet integrated within the wider daemon yet. Full
integration will land in a future pull request.
2016-10-17 19:41:29 -07:00

172 lines
4.9 KiB
Go

package brontide
import (
"bytes"
"io"
"net"
"time"
"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 BrontideMachine struct for
// additional details w.r.t to the handshake and encryption scheme.
type Conn struct {
conn net.Conn
noise *BrontideMachine
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, remotePub *btcec.PublicKey,
address string) (*Conn, error) {
conn, err := net.Dial("tcp", address)
if err != nil {
return nil, err
}
b := &Conn{
conn: conn,
noise: NewBrontideMachine(true, localPriv, remotePub),
}
// Initiate the handshake by sending the first act to the receiver.
actOne, err := b.noise.GenActOne()
if err != nil {
return nil, err
}
if _, err := conn.Write(actOne[:]); err != nil {
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 {
return nil, err
}
if err := b.noise.RecvActTwo(actTwo); err != nil {
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 {
return nil, err
}
if _, err := conn.Write(actThree[:]); err != nil {
return nil, err
}
return b, 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 {
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) {
return len(b), c.noise.WriteMessage(c.conn, b)
}
// 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()
}