Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 36 additions & 12 deletions lndc/conn.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"fmt"
"io"
"log"
"math"
"net"
"time"
Expand All @@ -29,15 +30,31 @@ type Conn struct {

// A compile-time assertion to ensure that Conn meets the net.Conn interface.
var _ net.Conn = (*Conn)(nil)
var Noise_XK bool

// 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 *koblitz.PrivateKey, ipAddr string, remotePKH string,
func Dial(localPriv *btcec.PrivateKey, ipAddr string, remoteAddress string,
dialer func(string, string) (net.Conn, error)) (*Conn, error) {

var remotePKH string
var remotePK [33]byte
if remoteAddress[0:3] == "ln1" { // its a remote PKH
remotePKH = remoteAddress
} else if len(remoteAddress) == 33 { // remotePK
temp := []byte(remoteAddress)
copy(remotePK[:], temp)
}
var conn net.Conn
var err error
var empty [33]byte
if remotePK != empty {
log.Println("Connecting via Noise_XK since we know remotePK")
Noise_XK = true
SetConsts()
}
conn, err = dialer("tcp", ipAddr)
logging.Info("ipAddr is", ipAddr)
if err != nil {
Expand All @@ -48,9 +65,8 @@ func Dial(localPriv *koblitz.PrivateKey, ipAddr string, remotePKH string,
conn: conn,
noise: NewNoiseMachine(true, localPriv),
}

// Initiate the handshake by sending the first act to the receiver.
actOne, err := b.noise.GenActOne()
actOne, err := b.noise.GenActOne(remotePK)
if err != nil {
b.conn.Close()
return nil, err
Expand All @@ -69,22 +85,30 @@ func Dial(localPriv *koblitz.PrivateKey, ipAddr string, remotePKH string,
// 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
actTwo := make([]byte, ActTwoSize)
if _, err := io.ReadFull(conn, actTwo[:]); err != nil {
b.conn.Close()
return nil, err
}
s, err := b.noise.RecvActTwo(actTwo)
if err != nil {
b.conn.Close()
return nil, err
if !Noise_XK {
remotePK, err = b.noise.RecvActTwo(actTwo)
if err != nil {
b.conn.Close()
return nil, err
}
} else {
if _, err := b.noise.RecvActTwo(actTwo); err != nil {
b.conn.Close()
return nil, err
}
}

logging.Info("Received pubkey", s)
if lnutil.LitAdrFromPubkey(s) != remotePKH {
logging.Infoln("Received pubkey", remotePK)
if lnutil.LitAdrFromPubkey(remotePK) != remotePKH && !Noise_XK {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems to make sense to switch these clauses around, so that if Noise_XK is true it will not do the comparison.

// for noise_XK dont check PKH and PK because we'd have already checked this
// the last time we connected to this guy
return nil, fmt.Errorf("Remote PKH doesn't match. Quitting!")
}
logging.Infof("Received PKH %s matches", lnutil.LitAdrFromPubkey(s))
logging.Infof("Received PKH %s matches", lnutil.LitAdrFromPubkey(remotePK))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logging statement should be different when Noise_XK is true - you didn't check that it matches. Something like "Assuming PKH still matches" or just don't log it at all.


// Finally, complete the handshake by sending over our encrypted static
// key and execute the final ECDH operation.
Expand Down
12 changes: 9 additions & 3 deletions lndc/listener.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package lndc
import (
"errors"
"io"
"log"
"net"
"time"

Expand Down Expand Up @@ -111,20 +112,25 @@ func (l *Listener) doHandshake(conn net.Conn) {
// Attempt to carry out the first act of the handshake protocol. If the
// connecting node doesn't know our long-term static public key, then
// this portion will fail with a non-nil error.
var actOne [ActOneSize]byte
actOne := make([]byte, ActOneSize)
log.Println("Handshake Version", HandshakeVersion)
if _, err := io.ReadFull(conn, actOne[:]); err != nil {
lndcConn.conn.Close()
l.rejectConn(err)
return
}
if actOne[0] == 0 { // remote node wants to connect via XK
HandshakeVersion = byte(0)
ActTwoSize = 50
} // no need for else as default covers XX
if err := lndcConn.noise.RecvActOne(actOne); err != nil {
lndcConn.conn.Close()
l.rejectConn(err)
return
}
// Next, progress the handshake processes by sending over our ephemeral
// key for the session along with an authenticating tag.
actTwo, err := lndcConn.noise.GenActTwo()
actTwo, err := lndcConn.noise.GenActTwo(HandshakeVersion)
if err != nil {
lndcConn.conn.Close()
l.rejectConn(err)
Expand All @@ -150,7 +156,7 @@ func (l *Listener) doHandshake(conn net.Conn) {
// Finally, finish the handshake processes by reading and decrypting
// the connection peer's static public key. If this succeeds then both
// sides have mutually authenticated each other.
var actThree [ActThreeSize]byte
actThree := make([]byte, ActThreeSize)
if _, err := io.ReadFull(conn, actThree[:]); err != nil {
lndcConn.conn.Close()
l.rejectConn(err)
Expand Down
132 changes: 88 additions & 44 deletions lndc/noise.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import (
"crypto/cipher"
"crypto/sha256"
"encoding/binary"
"fmt"
"errors"
"fmt"
"io"
"math"
"time"
Expand Down Expand Up @@ -316,10 +316,19 @@ func EphemeralGenerator(gen func() (*koblitz.PrivateKey, error)) func(*Machine)
// INITIATOR -> e RESPONDER
// INITIATOR <- e, ee, s, es RESPONDER
// INITIATOR -> s, se RESPONDER
// The protocol has the following steps involved:
// XK(s, rs):
// INITIATOR <- s
// INITIATOR -> e, es RESPONDER
// INITIATOR <- e, ee RESPONDER
// INITIATOR -> s, se RESPONDER
// s refers to the static key (or public key) belonging to an entity
// e refers to the ephemeral key
// e, ee, es refer to a DH exchange between the initiator's key pair and the
// responder's key pair. The letters e and s hold the same meaning as before.
// lit uses Noise_XX to connect with nodes that it does not know of and uses
// Noise_XK for nodes that it has previously connected to. This saves 33 bytes
// in Act Two.

type Machine struct {
sendCipher cipherState
Expand Down Expand Up @@ -373,12 +382,13 @@ func NewNoiseMachine(initiator bool, localStatic *koblitz.PrivateKey,
return m
}

const (
var (
// HandshakeVersion is the expected version of the lndc handshake.
// Any messages that carry a different version will cause the handshake
// to abort immediately.
HandshakeVersion = byte(1) // TODO: add support for noise_XK (brontide) as well

HandshakeVersion = byte(1)
// noise_XX HandshakeVersion = 1
// noise_XK HandshakeVersion = 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.
Expand All @@ -403,18 +413,25 @@ const (
ActThreeSize = 66
)

func SetConsts() {
HandshakeVersion = byte(0) // Noise_XK's hadnshake version
// 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.
// <- e, ee
// 1 + 33 + 16
ActTwoSize = 50
}

// GenActOne generates the initial packet (act one) to be sent from initiator
// to responder. During act one the initiator generates an ephemeral key and
// hashes it into the handshake digest. Future payloads are encrypted with a key
// derived from this result.
// -> e

func (b *Machine) GenActOne() ([ActOneSize]byte, error) {
var (
err error
actOne [ActOneSize]byte
)

func (b *Machine) GenActOne(remotePK [33]byte) ([]byte, error) {
var err error
actOne := make([]byte, ActOneSize)
// Generate e
b.localEphemeral, err = b.ephemeralGen()
if err != nil {
Expand All @@ -426,6 +443,15 @@ func (b *Machine) GenActOne() ([ActOneSize]byte, error) {
// Hash it into the handshake digest
b.mixHash(e)

if Noise_XK {
b.remoteStatic, err = btcec.ParsePubKey(remotePK[:], btcec.S256())
if err != nil {
return nil, err
}
// es
s := ecdh(b.remoteStatic, b.localEphemeral)
b.mixKey(s[:])
}
authPayload := b.EncryptAndHash([]byte{})
actOne[0] = HandshakeVersion
copy(actOne[1:34], e)
Expand All @@ -437,16 +463,15 @@ func (b *Machine) GenActOne() ([ActOneSize]byte, error) {
// 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 {
func (b *Machine) RecvActOne(actOne []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 {
if !(actOne[0] == 0 || actOne[0] == 1) {
return fmt.Errorf("Act One: invalid handshake version: %v, "+
"only %v is valid, msg=%x", actOne[0], HandshakeVersion,
actOne[:])
Expand All @@ -462,18 +487,21 @@ func (b *Machine) RecvActOne(actOne [ActOneSize]byte) error {
}
b.mixHash(b.remoteEphemeral.SerializeCompressed())

if actOne[0] == 0 {
// es
es := ecdh(b.remoteEphemeral, b.localStatic)
b.mixKey(es)
}
_, err = b.DecryptAndHash(p[:])
return err // nil means Act one completed successfully
}

// GenActTwo generates the second packet (act two) to be sent from the
// responder to the initiator
// <- e, ee, s, es
func (b *Machine) GenActTwo() ([ActTwoSize]byte, error) {
var (
err error
actTwo [ActTwoSize]byte
)
func (b *Machine) GenActTwo(HandshakeVersion byte) ([]byte, error) {
var err error
actTwo := make([]byte, ActTwoSize)

// e
b.localEphemeral, err = b.ephemeralGen()
Expand All @@ -488,27 +516,35 @@ func (b *Machine) GenActTwo() ([ActTwoSize]byte, error) {
ee := ecdh(b.remoteEphemeral, b.localEphemeral)
b.mixKey(ee)

// s
s := b.localStatic.PubKey().SerializeCompressed()
b.mixHash(s)

// es
es := ecdh(b.remoteEphemeral, b.localStatic)
b.mixKey(es)

if HandshakeVersion == 1 {
// s
s := b.localStatic.PubKey().SerializeCompressed()
b.mixHash(s)

// es
es := ecdh(b.remoteEphemeral, b.localStatic)
b.mixKey(es)

authPayload := b.EncryptAndHash([]byte{})
actTwo[0] = HandshakeVersion
copy(actTwo[1:34], e)
copy(actTwo[34:67], s)
copy(actTwo[67:], authPayload)
// add additional stuff based on what we need
return actTwo, nil
}
authPayload := b.EncryptAndHash([]byte{})
actTwo[0] = HandshakeVersion
copy(actTwo[1:34], e)
copy(actTwo[34:67], s)
copy(actTwo[67:], authPayload)
copy(actTwo[34:], authPayload)
// add additional stuff based on what we need
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) ([33]byte, error) {
func (b *Machine) RecvActTwo(actTwo []byte) ([33]byte, error) {
var (
err error
e [33]byte
Expand All @@ -524,9 +560,14 @@ func (b *Machine) RecvActTwo(actTwo [ActTwoSize]byte) ([33]byte, error) {
actTwo[:])
}

copy(e[:], actTwo[1:34])
copy(s[:], actTwo[34:67])
copy(p[:], actTwo[67:])
if HandshakeVersion == 0 {
copy(e[:], actTwo[1:34])
copy(p[:], actTwo[34:])
} else {
copy(e[:], actTwo[1:34])
copy(s[:], actTwo[34:67])
copy(p[:], actTwo[67:])
}

// e
b.remoteEphemeral, err = koblitz.ParsePubKey(e[:], koblitz.S256())
Expand All @@ -539,17 +580,19 @@ func (b *Machine) RecvActTwo(actTwo [ActTwoSize]byte) ([33]byte, error) {
ee := ecdh(b.remoteEphemeral, b.localEphemeral)
b.mixKey(ee)

// s
b.remoteStatic, err = koblitz.ParsePubKey(s[:], koblitz.S256())
if err != nil {
return empty, err
}
b.mixHash(b.remoteStatic.SerializeCompressed())
if HandshakeVersion == 1 {
// s
b.remoteStatic, err = koblitz.ParsePubKey(s[:], koblitz.S256())
if err != nil {
return empty, err
}
b.mixHash(b.remoteStatic.SerializeCompressed())

// es
es := ecdh(b.remoteStatic, b.localEphemeral)
b.mixKey(es)
// es
es := ecdh(b.remoteStatic, b.localEphemeral)
b.mixKey(es)

}
_, err = b.DecryptAndHash(p[:])
return s, err
}
Expand All @@ -560,8 +603,9 @@ func (b *Machine) RecvActTwo(actTwo [ActTwoSize]byte) ([33]byte, error) {
// 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
func (b *Machine) GenActThree() ([]byte, error) {
//var actThree [ActThreeSize]byte
actThree := make([]byte, ActThreeSize)

// s
s := b.localStatic.PubKey().SerializeCompressed()
Expand All @@ -587,7 +631,7 @@ func (b *Machine) GenActThree() ([ActThreeSize]byte, error) {
// 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 {
func (b *Machine) RecvActThree(actThree []byte) error {
var (
err error
s [49]byte
Expand Down
Loading