lnd.xprv/brontide/noise.go

908 lines
28 KiB
Go

package brontide
import (
"crypto/cipher"
"crypto/sha256"
"encoding/binary"
"errors"
"fmt"
"io"
"math"
"time"
"golang.org/x/crypto/chacha20poly1305"
"golang.org/x/crypto/hkdf"
"github.com/btcsuite/btcd/btcec"
"github.com/lightningnetwork/lnd/keychain"
)
const (
// protocolName is the precise instantiation of the Noise protocol
// handshake at the center of Brontide. This value will be used as part
// of the prologue. If the initiator and responder aren't using the
// exact same string for this value, along with prologue of the Bitcoin
// network, then the initial handshake will fail.
protocolName = "Noise_XK_secp256k1_ChaChaPoly_SHA256"
// macSize is the length in bytes of the tags generated by poly1305.
macSize = 16
// lengthHeaderSize is the number of bytes used to prefix encode the
// length of a message payload.
lengthHeaderSize = 2
// encHeaderSize is the number of bytes required to hold an encrypted
// header and it's MAC.
encHeaderSize = lengthHeaderSize + macSize
// keyRotationInterval is the number of messages sent on a single
// cipher stream before the keys are rotated forwards.
keyRotationInterval = 1000
// handshakeReadTimeout is a read timeout that will be enforced when
// waiting for data payloads during the various acts of Brontide. If
// the remote party fails to deliver the proper payload within this
// time frame, then we'll fail the connection.
handshakeReadTimeout = time.Second * 5
)
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")
// lightningPrologue is the noise prologue that is used to initialize
// the brontide noise handshake.
lightningPrologue = []byte("lightning")
// ephemeralGen is the default ephemeral key generator, used to derive a
// unique ephemeral key for each brontide handshake.
ephemeralGen = func() (*btcec.PrivateKey, error) {
return btcec.NewPrivateKey(btcec.S256())
}
)
// TODO(roasbeef): free buffer pool?
// ecdh performs an ECDH operation between pub and priv. The returned value is
// the sha256 of the compressed shared point.
func ecdh(pub *btcec.PublicKey, priv keychain.SingleKeyECDH) ([]byte, error) {
hash, err := priv.ECDH(pub)
return hash[:], err
}
// cipherState encapsulates the state for the AEAD which will be used to
// encrypt+authenticate any payloads sent during the handshake, and messages
// sent once the handshake has completed.
type cipherState struct {
// nonce is the nonce passed into the chacha20-poly1305 instance for
// encryption+decryption. The nonce is incremented after each successful
// encryption/decryption.
//
// TODO(roasbeef): this should actually be 96 bit
nonce uint64
// secretKey is the shared symmetric key which will be used to
// instantiate the cipher.
//
// TODO(roasbeef): m-lock??
secretKey [32]byte
// salt is an additional secret which is used during key rotation to
// generate new keys.
salt [32]byte
// cipher is an instance of the ChaCha20-Poly1305 AEAD construction
// created using the secretKey above.
cipher cipher.AEAD
}
// Encrypt returns a ciphertext which is the encryption of the plainText
// observing the passed associatedData within the AEAD construction.
func (c *cipherState) Encrypt(associatedData, cipherText, plainText []byte) []byte {
defer func() {
c.nonce++
if c.nonce == keyRotationInterval {
c.rotateKey()
}
}()
var nonce [12]byte
binary.LittleEndian.PutUint64(nonce[4:], c.nonce)
return c.cipher.Seal(cipherText, nonce[:], plainText, associatedData)
}
// Decrypt attempts to decrypt the passed ciphertext observing the specified
// associatedData within the AEAD construction. In the case that the final MAC
// check fails, then a non-nil error will be returned.
func (c *cipherState) Decrypt(associatedData, plainText, cipherText []byte) ([]byte, error) {
defer func() {
c.nonce++
if c.nonce == keyRotationInterval {
c.rotateKey()
}
}()
var nonce [12]byte
binary.LittleEndian.PutUint64(nonce[4:], c.nonce)
return c.cipher.Open(plainText, nonce[:], cipherText, associatedData)
}
// InitializeKey initializes the secret key and AEAD cipher scheme based off of
// the passed key.
func (c *cipherState) InitializeKey(key [32]byte) {
c.secretKey = key
c.nonce = 0
// Safe to ignore the error here as our key is properly sized
// (32-bytes).
c.cipher, _ = chacha20poly1305.New(c.secretKey[:])
}
// InitializeKeyWithSalt is identical to InitializeKey however it also sets the
// cipherState's salt field which is used for key rotation.
func (c *cipherState) InitializeKeyWithSalt(salt, key [32]byte) {
c.salt = salt
c.InitializeKey(key)
}
// rotateKey rotates the current encryption/decryption key for this cipherState
// instance. Key rotation is performed by ratcheting the current key forward
// using an HKDF invocation with the cipherState's salt as the salt, and the
// current key as the input.
func (c *cipherState) rotateKey() {
var (
info []byte
nextKey [32]byte
)
oldKey := c.secretKey
h := hkdf.New(sha256.New, oldKey[:], c.salt[:], info)
// hkdf(ck, k, zero)
// |
// | \
// | \
// ck k'
h.Read(c.salt[:])
h.Read(nextKey[:])
c.InitializeKey(nextKey)
}
// symmetricState encapsulates a cipherState object and houses the ephemeral
// handshake digest state. This struct is used during the handshake to derive
// new shared secrets based off of the result of ECDH operations. Ultimately,
// the final key yielded by this struct is the result of an incremental
// Triple-DH operation.
type symmetricState struct {
cipherState
// chainingKey is used as the salt to the HKDF function to derive a new
// chaining key as well as a new tempKey which is used for
// encryption/decryption.
chainingKey [32]byte
// tempKey is the latter 32 bytes resulted from the latest HKDF
// iteration. This key is used to encrypt/decrypt any handshake
// messages or payloads sent until the next DH operation is executed.
tempKey [32]byte
// handshakeDigest is the cumulative hash digest of all handshake
// messages sent from start to finish. This value is never transmitted
// to the other side, but will be used as the AD when
// encrypting/decrypting messages using our AEAD construction.
handshakeDigest [32]byte
}
// mixKey implements a basic HKDF-based key ratchet. This method is called
// with the result of each DH output generated during the handshake process.
// The first 32 bytes extract from the HKDF reader is the next chaining key,
// then latter 32 bytes become the temp secret key using within any future AEAD
// operations until another DH operation is performed.
func (s *symmetricState) mixKey(input []byte) {
var info []byte
secret := input
salt := s.chainingKey
h := hkdf.New(sha256.New, secret, salt[:], info)
// hkdf(ck, input, zero)
// |
// | \
// | \
// ck k
h.Read(s.chainingKey[:])
h.Read(s.tempKey[:])
// cipher.k = temp_key
s.InitializeKey(s.tempKey)
}
// mixHash hashes the passed input data into the cumulative handshake digest.
// The running result of this value (h) is used as the associated data in all
// decryption/encryption operations.
func (s *symmetricState) mixHash(data []byte) {
h := sha256.New()
h.Write(s.handshakeDigest[:])
h.Write(data)
copy(s.handshakeDigest[:], h.Sum(nil))
}
// EncryptAndHash returns the authenticated encryption of the passed plaintext.
// When encrypting the handshake digest (h) is used as the associated data to
// the AEAD cipher.
func (s *symmetricState) EncryptAndHash(plaintext []byte) []byte {
ciphertext := s.Encrypt(s.handshakeDigest[:], nil, plaintext)
s.mixHash(ciphertext)
return ciphertext
}
// DecryptAndHash returns the authenticated decryption of the passed
// ciphertext. When encrypting the handshake digest (h) is used as the
// associated data to the AEAD cipher.
func (s *symmetricState) DecryptAndHash(ciphertext []byte) ([]byte, error) {
plaintext, err := s.Decrypt(s.handshakeDigest[:], nil, ciphertext)
if err != nil {
return nil, err
}
s.mixHash(ciphertext)
return plaintext, nil
}
// InitializeSymmetric initializes the symmetric state by setting the handshake
// digest (h) and the chaining key (ck) to protocol name.
func (s *symmetricState) InitializeSymmetric(protocolName []byte) {
var empty [32]byte
s.handshakeDigest = sha256.Sum256(protocolName)
s.chainingKey = s.handshakeDigest
s.InitializeKey(empty)
}
// handshakeState encapsulates the symmetricState and keeps track of all the
// public keys (static and ephemeral) for both sides during the handshake
// transcript. If the handshake completes successfully, then two instances of a
// cipherState are emitted: one to encrypt messages from initiator to
// responder, and the other for the opposite direction.
type handshakeState struct {
symmetricState
initiator bool
localStatic keychain.SingleKeyECDH
localEphemeral keychain.SingleKeyECDH // nolint (false positive)
remoteStatic *btcec.PublicKey
remoteEphemeral *btcec.PublicKey
}
// newHandshakeState returns a new instance of the handshake state initialized
// with the prologue and protocol name. If this is the responder's handshake
// state, then the remotePub can be nil.
func newHandshakeState(initiator bool, prologue []byte,
localKey keychain.SingleKeyECDH,
remotePub *btcec.PublicKey) handshakeState {
h := handshakeState{
initiator: initiator,
localStatic: localKey,
remoteStatic: remotePub,
}
// Set the current chaining key and handshake digest to the hash of the
// protocol name, and additionally mix in the prologue. If either sides
// disagree about the prologue or protocol name, then the handshake
// will fail.
h.InitializeSymmetric([]byte(protocolName))
h.mixHash(prologue)
// In Noise_XK, the initiator should know the responder's static
// public key, therefore we include the responder's static key in the
// handshake digest. If the initiator gets this value wrong, then the
// handshake will fail.
if initiator {
h.mixHash(remotePub.SerializeCompressed())
} else {
h.mixHash(localKey.PubKey().SerializeCompressed())
}
return h
}
// 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.
func EphemeralGenerator(gen func() (*btcec.PrivateKey, error)) func(*Machine) {
return func(m *Machine) {
m.ephemeralGen = gen
}
}
// 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
type Machine struct {
sendCipher cipherState
recvCipher cipherState
ephemeralGen func() (*btcec.PrivateKey, error)
handshakeState
// nextCipherHeader is a static buffer that we'll use to read in the
// next ciphertext header from the wire. The header is a 2 byte length
// (of the next ciphertext), followed by a 16 byte MAC.
nextCipherHeader [encHeaderSize]byte
// nextHeaderSend holds a reference to the remaining header bytes to
// write out for a pending message. This allows us to tolerate timeout
// errors that cause partial writes.
nextHeaderSend []byte
// nextHeaderBody holds a reference to the remaining body bytes to write
// out for a pending message. This allows us to tolerate timeout errors
// that cause partial writes.
nextBodySend []byte
}
// 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 NewBrontideMachine(initiator bool, localKey keychain.SingleKeyECDH,
remotePub *btcec.PublicKey, options ...func(*Machine)) *Machine {
handshake := newHandshakeState(
initiator, lightningPrologue, localKey, remotePub,
)
m := &Machine{
handshakeState: handshake,
ephemeralGen: ephemeralGen,
}
// With the default options established, we'll now process all the
// options passed in as parameters.
for _, option := range options {
option(m)
}
return m
}
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
)
// 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 (b *Machine) GenActOne() ([ActOneSize]byte, error) {
var actOne [ActOneSize]byte
// e
localEphemeral, err := b.ephemeralGen()
if err != nil {
return actOne, err
}
b.localEphemeral = &keychain.PrivKeyECDH{
PrivKey: localEphemeral,
}
ephemeral := localEphemeral.PubKey().SerializeCompressed()
b.mixHash(ephemeral)
// es
s, err := ecdh(b.remoteStatic, b.localEphemeral)
if err != nil {
return actOne, err
}
b.mixKey(s[:])
authPayload := b.EncryptAndHash([]byte{})
actOne[0] = HandshakeVersion
copy(actOne[1:34], ephemeral)
copy(actOne[34:], authPayload)
return actOne, nil
}
// 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 (b *Machine) RecvActOne(actOne [ActOneSize]byte) error {
var (
err error
e [33]byte
p [16]byte
)
// If the handshake version is unknown, then the handshake fails
// immediately.
if actOne[0] != HandshakeVersion {
return fmt.Errorf("act one: invalid handshake version: %v, "+
"only %v is valid, msg=%x", actOne[0], HandshakeVersion,
actOne[:])
}
copy(e[:], actOne[1:34])
copy(p[:], actOne[34:])
// e
b.remoteEphemeral, err = btcec.ParsePubKey(e[:], btcec.S256())
if err != nil {
return err
}
b.mixHash(b.remoteEphemeral.SerializeCompressed())
// es
s, err := ecdh(b.remoteEphemeral, b.localStatic)
if err != nil {
return err
}
b.mixKey(s)
// If the initiator doesn't know our static key, then this operation
// will fail.
_, err = b.DecryptAndHash(p[:])
return err
}
// 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 (b *Machine) GenActTwo() ([ActTwoSize]byte, error) {
var actTwo [ActTwoSize]byte
// e
localEphemeral, err := b.ephemeralGen()
if err != nil {
return actTwo, err
}
b.localEphemeral = &keychain.PrivKeyECDH{
PrivKey: localEphemeral,
}
ephemeral := localEphemeral.PubKey().SerializeCompressed()
b.mixHash(localEphemeral.PubKey().SerializeCompressed())
// ee
s, err := ecdh(b.remoteEphemeral, b.localEphemeral)
if err != nil {
return actTwo, err
}
b.mixKey(s)
authPayload := b.EncryptAndHash([]byte{})
actTwo[0] = HandshakeVersion
copy(actTwo[1:34], ephemeral)
copy(actTwo[34:], authPayload)
return actTwo, nil
}
// 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 (b *Machine) RecvActTwo(actTwo [ActTwoSize]byte) error {
var (
err error
e [33]byte
p [16]byte
)
// If the handshake version is unknown, then the handshake fails
// immediately.
if actTwo[0] != HandshakeVersion {
return fmt.Errorf("act two: invalid handshake version: %v, "+
"only %v is valid, msg=%x", actTwo[0], HandshakeVersion,
actTwo[:])
}
copy(e[:], actTwo[1:34])
copy(p[:], actTwo[34:])
// e
b.remoteEphemeral, err = btcec.ParsePubKey(e[:], btcec.S256())
if err != nil {
return err
}
b.mixHash(b.remoteEphemeral.SerializeCompressed())
// ee
s, err := ecdh(b.remoteEphemeral, b.localEphemeral)
if err != nil {
return err
}
b.mixKey(s)
_, err = b.DecryptAndHash(p[:])
return err
}
// 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 (b *Machine) GenActThree() ([ActThreeSize]byte, error) {
var actThree [ActThreeSize]byte
ourPubkey := b.localStatic.PubKey().SerializeCompressed()
ciphertext := b.EncryptAndHash(ourPubkey)
s, err := ecdh(b.remoteEphemeral, b.localStatic)
if err != nil {
return actThree, err
}
b.mixKey(s)
authPayload := b.EncryptAndHash([]byte{})
actThree[0] = HandshakeVersion
copy(actThree[1:50], ciphertext)
copy(actThree[50:], authPayload)
// With the final ECDH operation complete, derive the session sending
// and receiving keys.
b.split()
return actThree, nil
}
// 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 (b *Machine) RecvActThree(actThree [ActThreeSize]byte) error {
var (
err error
s [33 + 16]byte
p [16]byte
)
// If the handshake version is unknown, then the handshake fails
// immediately.
if actThree[0] != HandshakeVersion {
return fmt.Errorf("act three: invalid handshake version: %v, "+
"only %v is valid, msg=%x", actThree[0], HandshakeVersion,
actThree[:])
}
copy(s[:], actThree[1:33+16+1])
copy(p[:], actThree[33+16+1:])
// s
remotePub, err := b.DecryptAndHash(s[:])
if err != nil {
return err
}
b.remoteStatic, err = btcec.ParsePubKey(remotePub, btcec.S256())
if err != nil {
return err
}
// se
se, err := ecdh(b.remoteStatic, b.localEphemeral)
if err != nil {
return err
}
b.mixKey(se)
if _, err := b.DecryptAndHash(p[:]); err != nil {
return err
}
// With the final ECDH operation complete, derive the session sending
// and receiving keys.
b.split()
return nil
}
// split is the final wrap-up act to be executed at the end of a successful
// three act handshake. This function creates two internal cipherState
// instances: one which is used to encrypt messages from the initiator to the
// responder, and another which is used to encrypt message for the opposite
// direction.
func (b *Machine) split() {
var (
empty []byte
sendKey [32]byte
recvKey [32]byte
)
h := hkdf.New(sha256.New, empty, b.chainingKey[:], empty)
// If we're the initiator the first 32 bytes are used to encrypt our
// messages and the second 32-bytes to decrypt their messages. For the
// responder the opposite is true.
if b.initiator {
h.Read(sendKey[:])
b.sendCipher = cipherState{}
b.sendCipher.InitializeKeyWithSalt(b.chainingKey, sendKey)
h.Read(recvKey[:])
b.recvCipher = cipherState{}
b.recvCipher.InitializeKeyWithSalt(b.chainingKey, recvKey)
} else {
h.Read(recvKey[:])
b.recvCipher = cipherState{}
b.recvCipher.InitializeKeyWithSalt(b.chainingKey, recvKey)
h.Read(sendKey[:])
b.sendCipher = cipherState{}
b.sendCipher.InitializeKeyWithSalt(b.chainingKey, sendKey)
}
}
// 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.
func (b *Machine) WriteMessage(p []byte) error {
// The total length of each message payload including the MAC size
// payload exceed the largest number encodable within a 16-bit unsigned
// integer.
if len(p) > math.MaxUint16 {
return ErrMaxMessageLengthExceeded
}
// If a prior message was written but it hasn't been fully flushed,
// return an error as we only support buffering of one message at a
// time.
if len(b.nextHeaderSend) > 0 || len(b.nextBodySend) > 0 {
return ErrMessageNotFlushed
}
// The full length of the packet is only the packet length, and does
// NOT include the MAC.
fullLength := uint16(len(p))
var pktLen [2]byte
binary.BigEndian.PutUint16(pktLen[:], fullLength)
// First, generate the encrypted+MAC'd length prefix for the packet.
b.nextHeaderSend = b.sendCipher.Encrypt(nil, nil, pktLen[:])
// Finally, generate the encrypted packet itself.
b.nextBodySend = b.sendCipher.Encrypt(nil, nil, p)
return nil
}
// 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 (b *Machine) Flush(w io.Writer) (int, error) {
// First, write out the pending header bytes, if any exist. Any header
// bytes written will not count towards the total amount flushed.
if len(b.nextHeaderSend) > 0 {
// Write any remaining header bytes and shift the slice to point
// to the next segment of unwritten bytes. If an error is
// encountered, we can continue to write the header from where
// we left off on a subsequent call to Flush.
n, err := w.Write(b.nextHeaderSend)
b.nextHeaderSend = b.nextHeaderSend[n:]
if err != nil {
return 0, err
}
}
// Next, write the pending body bytes, if any exist. Only the number of
// bytes written that correspond to the ciphertext will be included in
// the total bytes written, bytes written as part of the MAC will not be
// counted.
var nn int
if len(b.nextBodySend) > 0 {
// Write out all bytes excluding the mac and shift the body
// slice depending on the number of actual bytes written.
n, err := w.Write(b.nextBodySend)
b.nextBodySend = b.nextBodySend[n:]
// If we partially or fully wrote any of the body's MAC, we'll
// subtract that contribution from the total amount flushed to
// preserve the abstraction of returning the number of plaintext
// bytes written by the connection.
//
// There are three possible scenarios we must handle to ensure
// the returned value is correct. In the first case, the write
// straddles both payload and MAC bytes, and we must subtract
// the number of MAC bytes written from n. In the second, only
// payload bytes are written, thus we can return n unmodified.
// The final scenario pertains to the case where only MAC bytes
// are written, none of which count towards the total.
//
// |-----------Payload------------|----MAC----|
// Straddle: S---------------------------------E--------0
// Payload-only: S------------------------E-----------------0
// MAC-only: S-------E-0
start, end := n+len(b.nextBodySend), len(b.nextBodySend)
switch {
// Straddles payload and MAC bytes, subtract number of MAC bytes
// written from the actual number written.
case start > macSize && end <= macSize:
nn = n - (macSize - end)
// Only payload bytes are written, return n directly.
case start > macSize && end > macSize:
nn = n
// Only MAC bytes are written, return 0 bytes written.
default:
}
if err != nil {
return nn, err
}
}
return nn, nil
}
// 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 (b *Machine) ReadMessage(r io.Reader) ([]byte, error) {
pktLen, err := b.ReadHeader(r)
if err != nil {
return nil, err
}
buf := make([]byte, pktLen)
return b.ReadBody(r, buf)
}
// 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 (b *Machine) ReadHeader(r io.Reader) (uint32, error) {
_, err := io.ReadFull(r, b.nextCipherHeader[:])
if err != nil {
return 0, err
}
// Attempt to decrypt+auth the packet length present in the stream.
pktLenBytes, err := b.recvCipher.Decrypt(
nil, nil, b.nextCipherHeader[:],
)
if err != nil {
return 0, err
}
// Compute the packet length that we will need to read off the wire.
pktLen := uint32(binary.BigEndian.Uint16(pktLenBytes)) + macSize
return pktLen, nil
}
// 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 eerror, a non-nil error is returned.
func (b *Machine) ReadBody(r io.Reader, buf []byte) ([]byte, error) {
// Next, using the length read from the packet header, read the
// encrypted packet itself into the buffer allocated by the read
// pool.
_, err := io.ReadFull(r, buf)
if err != nil {
return nil, err
}
// Finally, decrypt the message held in the buffer, and return a
// new byte slice containing the plaintext.
// TODO(roasbeef): modify to let pass in slice
return b.recvCipher.Decrypt(nil, nil, buf)
}
// SetCurveToNil sets the 'Curve' parameter to nil on the handshakeState keys.
// This allows us to log the Machine object without spammy log messages.
func (b *Machine) SetCurveToNil() {
if b.localStatic != nil {
b.localStatic.PubKey().Curve = nil
}
if b.localEphemeral != nil {
b.localEphemeral.PubKey().Curve = nil
}
if b.remoteStatic != nil {
b.remoteStatic.Curve = nil
}
if b.remoteEphemeral != nil {
b.remoteEphemeral.Curve = nil
}
}