diff --git a/litrpc/netcmds.go b/litrpc/netcmds.go index 6e760dba4..9aaf67cd4 100644 --- a/litrpc/netcmds.go +++ b/litrpc/netcmds.go @@ -82,7 +82,6 @@ func (r *LitRPC) Connect(args ConnectArgs, reply *ConnectReply) error { // use string as is, try to convert to ln address connectAdr = args.LNAddr } - err = r.Node.DialPeer(connectAdr) if err != nil { return err diff --git a/lncore/peers.go b/lncore/peers.go index e4930d662..6657557d6 100644 --- a/lncore/peers.go +++ b/lncore/peers.go @@ -22,6 +22,7 @@ type PeerInfo struct { LnAddr *LnAddr `json:"lnaddr"` Nickname *string `json:"name"` NetAddr *string `json:"netaddr"` // ip address, port, I guess + Pubkey *string `json:pubkey` // TEMP This is again, for adapting to the old system. PeerIdx uint32 `json:"hint_peeridx"` diff --git a/lndc/conn.go b/lndc/conn.go index 3e0ccd514..1f128f2ca 100644 --- a/lndc/conn.go +++ b/lndc/conn.go @@ -2,6 +2,7 @@ package lndc import ( "bytes" + "encoding/hex" "fmt" "io" "math" @@ -29,15 +30,28 @@ 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 *koblitz.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) == 66 { // hex encoded remotePK + temp, _ := hex.DecodeString(remoteAddress) + copy(remotePK[:], temp) + logging.Info("Got remote PK: ", remotePK, " using noise_xk to connect") + Noise_XK = true + SetConsts() + } var conn net.Conn var err error + conn, err = dialer("tcp", ipAddr) logging.Info("ipAddr is", ipAddr) if err != nil { @@ -50,7 +64,7 @@ func Dial(localPriv *koblitz.PrivateKey, ipAddr string, remotePKH string, } // 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 @@ -69,22 +83,29 @@ 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 { + // 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)) // Finally, complete the handshake by sending over our encrypted static // key and execute the final ECDH operation. diff --git a/lndc/listener.go b/lndc/listener.go index b8e676b1d..4574450bc 100644 --- a/lndc/listener.go +++ b/lndc/listener.go @@ -8,6 +8,7 @@ import ( "time" "github.com/mit-dci/lit/crypto/koblitz" + "github.com/mit-dci/lit/logging" ) // defaultHandshakes is the maximum number of handshakes that can be done in @@ -115,12 +116,19 @@ 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) 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 + logging.Infof("remote node wants to connect via noise_xk") + } // no need for else as default covers XX + if err := lndcConn.noise.RecvActOne(actOne); err != nil { lndcConn.conn.Close() l.rejectConn(err) @@ -128,7 +136,7 @@ func (l *Listener) doHandshake(conn net.Conn) { } // 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) @@ -154,7 +162,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) diff --git a/lndc/noise.go b/lndc/noise.go index 59c14c55f..e89122a60 100644 --- a/lndc/noise.go +++ b/lndc/noise.go @@ -4,8 +4,8 @@ import ( "crypto/cipher" "crypto/sha256" "encoding/binary" - "fmt" "errors" + "fmt" "io" "math" "time" @@ -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 @@ -373,11 +382,11 @@ 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) // ActOneSize is the size of the packet sent from initiator to // responder in ActOne. The packet consists of a handshake version, an @@ -403,18 +412,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 { @@ -426,6 +442,16 @@ func (b *Machine) GenActOne() ([ActOneSize]byte, error) { // Hash it into the handshake digest b.mixHash(e) + if Noise_XK { + b.remoteStatic, err = koblitz.ParsePubKey(remotePK[:], koblitz.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) @@ -437,7 +463,7 @@ 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 @@ -446,7 +472,7 @@ func (b *Machine) RecvActOne(actOne [ActOneSize]byte) error { // 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[:]) @@ -462,6 +488,12 @@ 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 } @@ -469,12 +501,9 @@ func (b *Machine) RecvActOne(actOne [ActOneSize]byte) error { // 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() if err != nil { @@ -488,19 +517,28 @@ 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 } @@ -508,7 +546,7 @@ func (b *Machine) GenActTwo() ([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 (b *Machine) RecvActTwo(actTwo [ActTwoSize]byte) ([33]byte, error) { +func (b *Machine) RecvActTwo(actTwo []byte) ([33]byte, error) { var ( err error e [33]byte @@ -524,9 +562,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()) @@ -539,17 +582,18 @@ 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()) - - // es - es := ecdh(b.remoteStatic, b.localEphemeral) - b.mixKey(es) + 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) + } _, err = b.DecryptAndHash(p[:]) return s, err } @@ -560,9 +604,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() encryptedS := b.EncryptAndHash(s) @@ -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 diff --git a/lndc/noise_test.go b/lndc/noise_test.go index 861c8c851..724955cfe 100644 --- a/lndc/noise_test.go +++ b/lndc/noise_test.go @@ -342,7 +342,8 @@ func TestBolt0008TestVectors(t *testing.T) { // act one. This should consist of exactly 50 bytes. We'll assert that // the payload return is _exactly_ the same as what's specified within // the test vectors. - actOne, err := initiator.GenActOne() + var empty [33]byte + actOne, err := initiator.GenActOne(empty) if err != nil { t.Fatalf("unable to generate act one: %v", err) } @@ -367,7 +368,7 @@ func TestBolt0008TestVectors(t *testing.T) { // its contribution to the crypto handshake. We'll also verify that we // produce the _exact_ same byte stream as advertised within the spec's // test vectors. - actTwo, err := responder.GenActTwo() + actTwo, err := responder.GenActTwo(byte(1)) if err != nil { t.Fatalf("unable to generate act two: %v", err) } diff --git a/lnp2p/peermgr.go b/lnp2p/peermgr.go index b9c54774f..88748ed71 100644 --- a/lnp2p/peermgr.go +++ b/lnp2p/peermgr.go @@ -3,6 +3,7 @@ package lnp2p //"crypto/ecdsa" // TODO Use ecdsa not koblitz import ( "crypto/ecdsa" + "encoding/hex" "fmt" "net" "strconv" @@ -145,7 +146,6 @@ func (pm *PeerManager) GetPeerByIdx(id int32) *Peer { // TryConnectAddress attempts to connect to the specified LN address. func (pm *PeerManager) TryConnectAddress(addr string, settings *NetSettings) (*Peer, error) { - // Figure out who we're trying to connect to. who, where := splitAdrString(addr) if where == "" { @@ -211,10 +211,25 @@ func (pm *PeerManager) tryConnectPeer(netaddr string, lnaddr *lncore.LnAddr, set dialer = d } - // Set up the connection. - lndcconn, err := lndc.Dial(pm.idkey, netaddr, string(*lnaddr), dialer) - if err != nil { - return nil, err + var remotePK *string + var lndcconn *lndc.Conn + x, err := pm.peerdb.GetPeerInfo(*lnaddr) + if x != nil { + if *(x.LnAddr) == *lnaddr { + // we have some entry in the db, we can use noise_xk + remotePK = x.Pubkey + // Set up the connection. + lndcconn, err = lndc.Dial(pm.idkey, netaddr, *remotePK, dialer) + if err != nil { + return nil, err + } + } + } else { + // Set up the connection. + lndcconn, err = lndc.Dial(pm.idkey, netaddr, string(*lnaddr), dialer) + if err != nil { + return nil, err + } } pi, err := pm.peerdb.GetPeerInfo(*lnaddr) @@ -241,11 +256,17 @@ func (pm *PeerManager) tryConnectPeer(netaddr string, lnaddr *lncore.LnAddr, set p.idx = &pidx } raddr := lndcconn.RemoteAddr().String() + // before we store the pubkey, we need to convert it to a hex encoded string + convertedPubKey := (*koblitz.PublicKey)(pk) + pStore := convertedPubKey.SerializeCompressed() // now we have a byte string + pStore2 := hex.EncodeToString(pStore) + logging.Infof("SOTREING PK OF REMOTE PEER", pStore2) pi = &lncore.PeerInfo{ LnAddr: &rlitaddr, Nickname: nil, NetAddr: &raddr, PeerIdx: pidx, + Pubkey: &pStore2, } err = pm.peerdb.AddPeer(p.GetLnAddr(), *pi) if err != nil { diff --git a/qln/netio.go b/qln/netio.go index 36b89c662..9bc806a7a 100644 --- a/qln/netio.go +++ b/qln/netio.go @@ -2,11 +2,12 @@ package qln import ( "fmt" + "strings" + "github.com/mit-dci/lit/crypto/koblitz" "github.com/mit-dci/lit/lncore" "github.com/mit-dci/lit/lnutil" "github.com/mit-dci/lit/logging" - "strings" ) // GetLisAddressAndPorts . @@ -85,8 +86,6 @@ func (nd *LitNode) DialPeer(connectAdr string) error { return err } - // TEMP The event handler handles actually setting up the peer in the LitNode - return nil }