brontide

package
v0.0.0-...-9b6e4cd Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 19, 2022 License: MIT Imports: 16 Imported by: 2

README

brontide

Build Status MIT licensed GoDoc

The brontide package implements a secure crypto messaging protocol based off of the Noise Protocol Framework. The package exposes the raw state machine that handles the handshake and subsequent message encryption/decryption scheme. Additionally, the package exposes a net.Conn and a net.Listener interface implementation which allows the encrypted transport to be seamlessly integrated into a codebase.

The secure messaging scheme implemented within this package is described in detail in BOLT #8 of the Lightning Network specifications.

This package has intentionally been designed so it can be used as a standalone package for any projects needing secure encrypted+authenticated communications between network enabled programs.

Installation and Updating

⛰  go get -u github.com/brolightningnetwork/broln/brontide

Documentation

Index

Constants

View Source
const (
	// HandshakeVersion is the expected version of the brontide handshake.
	// Any messages that carry a different version will cause the handshake
	// to abort immediately.
	HandshakeVersion = byte(0)

	// ActOneSize is the size of the packet sent from initiator to
	// responder in ActOne. The packet consists of a handshake version, an
	// ephemeral key in compressed format, and a 16-byte poly1305 tag.
	//
	// 1 + 33 + 16
	ActOneSize = 50

	// ActTwoSize is the size the packet sent from responder to initiator
	// in ActTwo. The packet consists of a handshake version, an ephemeral
	// key in compressed format and a 16-byte poly1305 tag.
	//
	// 1 + 33 + 16
	ActTwoSize = 50

	// ActThreeSize is the size of the packet sent from initiator to
	// responder in ActThree. The packet consists of a handshake version,
	// the initiators static key encrypted with strong forward secrecy and
	// a 16-byte poly1035 tag.
	//
	// 1 + 33 + 16 + 16
	ActThreeSize = 66
)

Variables

View Source
var (
	// ErrMaxMessageLengthExceeded is returned when a message to be written to
	// the cipher session exceeds the maximum allowed message payload.
	ErrMaxMessageLengthExceeded = errors.New("the generated payload exceeds " +
		"the max allowed message length of (2^16)-1")

	// ErrMessageNotFlushed signals that the connection cannot accept a new
	// message because the prior message has not been fully flushed.
	ErrMessageNotFlushed = errors.New("prior message not flushed")
)

Functions

func EphemeralGenerator

func EphemeralGenerator(gen func() (*btcec.PrivateKey, error)) func(*Machine)

EphemeralGenerator is a functional option that allows callers to substitute a custom function for use when generating ephemeral keys for ActOne or ActTwo. The function closure returned by this function can be passed into NewBrontideMachine as a function option parameter.

Types

type Conn

type Conn struct {
	// contains filtered or unexported fields
}

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.

func Dial

func Dial(local keychain.SingleKeyECDH, netAddr *lnwire.NetAddress,
	timeout time.Duration, dialer tor.DialFunc) (*Conn, error)

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 (*Conn) Close

func (c *Conn) Close() error

Close closes the connection. Any blocked Read or Write operations will be unblocked and return errors.

Part of the net.Conn interface.

func (*Conn) Flush

func (c *Conn) Flush() (int, error)

Flush attempts to write a message buffered using WriteMessage to the underlying connection. If no buffered message exists, this will result in a NOP. Otherwise, it will continue to write the remaining bytes, picking up where the byte stream left off in the event of a partial write. The number of bytes returned reflects the number of plaintext bytes in the payload, and does not account for the overhead of the header or MACs.

NOTE: It is safe to call this method again iff a timeout error is returned.

func (*Conn) LocalAddr

func (c *Conn) LocalAddr() net.Addr

LocalAddr returns the local network address.

Part of the net.Conn interface.

func (*Conn) LocalPub

func (c *Conn) LocalPub() *btcec.PublicKey

LocalPub returns the local peer's static public key.

func (*Conn) Read

func (c *Conn) Read(b []byte) (n int, err error)

Read reads data from the connection. Read can be made to time out and return an Error with Timeout() == true after a fixed time limit; see SetDeadline and SetReadDeadline.

Part of the net.Conn interface.

func (*Conn) ReadNextBody

func (c *Conn) ReadNextBody(buf []byte) ([]byte, error)

ReadNextBody uses the connection to read the next message body from the brontide stream. This function will block until the read of the body succeeds and return the decrypted payload. The provided buffer MUST be the packet length returned by the preceding call to ReadNextHeader.

func (*Conn) ReadNextHeader

func (c *Conn) ReadNextHeader() (uint32, error)

ReadNextHeader uses the connection to read the next header from the brontide stream. This function will block until the read of the header succeeds and return the packet length (including MAC overhead) that is expected from the subsequent call to ReadNextBody.

func (*Conn) ReadNextMessage

func (c *Conn) ReadNextMessage() ([]byte, error)

ReadNextMessage uses the connection in a message-oriented manner, instructing it to read the next _full_ message with the brontide stream. This function will block until the read of the header and body succeeds.

NOTE: This method SHOULD NOT be used in the case that the connection may be adversarial and induce long delays. If the caller needs to set read deadlines appropriately, it is preferred that they use the split ReadNextHeader and ReadNextBody methods so that the deadlines can be set appropriately on each.

func (*Conn) RemoteAddr

func (c *Conn) RemoteAddr() net.Addr

RemoteAddr returns the remote network address.

Part of the net.Conn interface.

func (*Conn) RemotePub

func (c *Conn) RemotePub() *btcec.PublicKey

RemotePub returns the remote peer's static public key.

func (*Conn) SetDeadline

func (c *Conn) SetDeadline(t time.Time) error

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 (*Conn) SetReadDeadline

func (c *Conn) SetReadDeadline(t time.Time) error

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 (*Conn) SetWriteDeadline

func (c *Conn) SetWriteDeadline(t time.Time) error

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 (*Conn) Write

func (c *Conn) Write(b []byte) (n int, err error)

Write writes data to the connection. Write can be made to time out and return an Error with Timeout() == true after a fixed time limit; see SetDeadline and SetWriteDeadline.

Part of the net.Conn interface.

func (*Conn) WriteMessage

func (c *Conn) WriteMessage(b []byte) error

WriteMessage encrypts and buffers the next message p for the connection. The ciphertext of the message is prepended with an encrypt+auth'd length which must be used as the AD to the AEAD construction when being decrypted by the other side.

NOTE: This DOES NOT write the message to the wire, it should be followed by a call to Flush to ensure the message is written.

type Listener

type Listener struct {
	// contains filtered or unexported fields
}

Listener is an implementation of a net.Conn which executes an authenticated key exchange and message encryption protocol dubbed "Machine" after initial connection acceptance. See the Machine struct for additional details w.r.t the handshake and encryption scheme used within the connection.

func NewListener

func NewListener(localStatic keychain.SingleKeyECDH,
	listenAddr string) (*Listener, error)

NewListener returns a new net.Listener which enforces the Brontide scheme during both initial connection establishment and data transfer.

func (*Listener) Accept

func (l *Listener) Accept() (net.Conn, error)

Accept waits for and returns the next connection to the listener. All incoming connections are authenticated via the three act Brontide key-exchange scheme. This function will fail with a non-nil error in the case that either the handshake breaks down, or the remote peer doesn't know our static public key.

Part of the net.Listener interface.

func (*Listener) Addr

func (l *Listener) Addr() net.Addr

Addr returns the listener's network address.

Part of the net.Listener interface.

func (*Listener) Close

func (l *Listener) Close() error

Close closes the listener. Any blocked Accept operations will be unblocked and return errors.

Part of the net.Listener interface.

type Machine

type Machine struct {
	// contains filtered or unexported fields
}

Machine is a state-machine which implements Brontide: an Authenticated-key Exchange in Three Acts. Brontide is derived from the Noise framework, specifically implementing the Noise_XK handshake. Once the initial 3-act handshake has completed all messages are encrypted with a chacha20 AEAD cipher. On the wire, all messages are prefixed with an authenticated+encrypted length field. Additionally, the encrypted+auth'd length prefix is used as the AD when encrypting+decryption messages. This construction provides confidentiality of packet length, avoids introducing a padding-oracle, and binds the encrypted packet length to the packet itself.

The acts proceeds the following order (initiator on the left):

GenActOne()   ->
                  RecvActOne()
              <-  GenActTwo()
RecvActTwo()
GenActThree() ->
                  RecvActThree()

This exchange corresponds to the following Noise handshake:

<- s
...
-> e, es
<- e, ee
-> s, se

func NewBrontideMachine

func NewBrontideMachine(initiator bool, localKey keychain.SingleKeyECDH,
	remotePub *btcec.PublicKey, options ...func(*Machine)) *Machine

NewBrontideMachine creates a new instance of the brontide state-machine. If the responder (listener) is creating the object, then the remotePub should be nil. The handshake state within brontide is initialized using the ascii string "lightning" as the prologue. The last parameter is a set of variadic arguments for adding additional options to the brontide Machine initialization.

func (*Machine) Flush

func (b *Machine) Flush(w io.Writer) (int, error)

Flush attempts to write a message buffered using WriteMessage to the provided io.Writer. If no buffered message exists, this will result in a NOP. Otherwise, it will continue to write the remaining bytes, picking up where the byte stream left off in the event of a partial write. The number of bytes returned reflects the number of plaintext bytes in the payload, and does not account for the overhead of the header or MACs.

NOTE: It is safe to call this method again iff a timeout error is returned.

func (*Machine) GenActOne

func (b *Machine) GenActOne() ([ActOneSize]byte, error)

GenActOne generates the initial packet (act one) to be sent from initiator to responder. During act one the initiator generates a fresh ephemeral key, hashes it into the handshake digest, and performs an ECDH between this key and the responder's static key. Future payloads are encrypted with a key derived from this result.

-> e, es

func (*Machine) GenActThree

func (b *Machine) GenActThree() ([ActThreeSize]byte, error)

GenActThree creates the final (act three) packet of the handshake. Act three is to be sent from the initiator to the responder. The purpose of act three is to transmit the initiator's public key under strong forward secrecy to the responder. This act also includes the final ECDH operation which yields the final session.

-> s, se

func (*Machine) GenActTwo

func (b *Machine) GenActTwo() ([ActTwoSize]byte, error)

GenActTwo generates the second packet (act two) to be sent from the responder to the initiator. The packet for act two is identical to that of act one, but then results in a different ECDH operation between the initiator's and responder's ephemeral keys.

<- e, ee

func (*Machine) ReadBody

func (b *Machine) ReadBody(r io.Reader, buf []byte) ([]byte, error)

ReadBody attempts to ready the next message body from the passed io.Reader. The provided buffer MUST be the length indicated by the packet length returned by the preceding call to ReadHeader. In the case of an authentication error, a non-nil error is returned.

func (*Machine) ReadHeader

func (b *Machine) ReadHeader(r io.Reader) (uint32, error)

ReadHeader attempts to read the next message header from the passed io.Reader. The header contains the length of the next body including additional overhead of the MAC. In the case of an authentication error, a non-nil error is returned.

NOTE: This method SHOULD NOT be used in the case that the io.Reader may be adversarial and induce long delays. If the caller needs to set read deadlines appropriately, it is preferred that they use the split ReadHeader and ReadBody methods so that the deadlines can be set appropriately on each.

func (*Machine) ReadMessage

func (b *Machine) ReadMessage(r io.Reader) ([]byte, error)

ReadMessage attempts to read the next message from the passed io.Reader. In the case of an authentication error, a non-nil error is returned.

func (*Machine) RecvActOne

func (b *Machine) RecvActOne(actOne [ActOneSize]byte) error

RecvActOne processes the act one packet sent by the initiator. The responder executes the mirrored actions to that of the initiator extending the handshake digest and deriving a new shared secret based on an ECDH with the initiator's ephemeral key and responder's static key.

func (*Machine) RecvActThree

func (b *Machine) RecvActThree(actThree [ActThreeSize]byte) error

RecvActThree processes the final act (act three) sent from the initiator to the responder. After processing this act, the responder learns of the initiator's static public key. Decryption of the static key serves to authenticate the initiator to the responder.

func (*Machine) RecvActTwo

func (b *Machine) RecvActTwo(actTwo [ActTwoSize]byte) error

RecvActTwo processes the second packet (act two) sent from the responder to the initiator. A successful processing of this packet authenticates the initiator to the responder.

func (*Machine) SetCurveToNil

func (b *Machine) SetCurveToNil()

SetCurveToNil sets the 'Curve' parameter to nil on the handshakeState keys. This allows us to log the Machine object without spammy log messages.

func (*Machine) WriteMessage

func (b *Machine) WriteMessage(p []byte) error

WriteMessage encrypts and buffers the next message p. The ciphertext of the message is prepended with an encrypt+auth'd length which must be used as the AD to the AEAD construction when being decrypted by the other side.

NOTE: This DOES NOT write the message to the wire, it should be followed by a call to Flush to ensure the message is written.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL