172 lines
4.9 KiB
Go
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()
|
||
|
}
|