package lndc import ( "bytes" "encoding/binary" "fmt" "io" "net" ) // New & improved tcp open session. // There's connector A and listener B. Once the connection is set up there's no // difference, but there can be during the setup. // Setup: // 1 -> A sends B ephemeral secp256k1 pubkey (33 bytes) // 2 <- B sends A ephemeral secp256k1 pubkey (33 bytes) // A and B do DH, get a shared secret. // ========== // Seesion is open! Done! Well not quite. Session is confidential but not // yet authenticated. From here on, can use the Send() and Recv() functions with // chacha20poly1305. // ========== // Nodes authenticate by doing a DH with their persistent identity keys, and then // exchanging hash based proofs that they got the same shared IDDH secret. // The DH proof is h160(remote eph pubkey, IDDH secret) // A initiates auth. // // If A does not know B's pubkey but only B's pubkey hash: // // 1 -> A sends [PubKeyA, PubKeyHashB] (53 bytes) // B computes ID pubkey DH // 2 <- B sends [PubkeyB, DH proof] (53 bytes) // 3 -> A sends DH proof (20 bytes) // done. // // This exchange can be sped up if A already knows B's pubkey: // // A already knows who they're talking to, or trying to talk to // 1 -> A sends [PubKeyA, PubkeyHashB, DH proof] (73 bytes) // 2 <- B sends DH proof (20 bytes) // // A and B both verify those H160 hashes, and if matching consider their // session counterparty authenticated. // // A possible weakness of the DH proof is if B re-uses eph keys. That potentially // makes *A*'s proof weaker though. A gets to choose the proof B creates. As // long as your software makes new eph keys each time, you should be OK. // readClear and writeClear don't encrypt but directly read and write to the // underlying data link, only adding or subtracting a 2 byte length header. // All Read() and Write() calls for lndc's use these functions internally // (they aren't exported). They're also used in the key agreement phase. // readClear reads the next length-prefixed message from the underlying raw // TCP connection. func readClear(c net.Conn) ([]byte, error) { var msgLen uint16 if err := binary.Read(c, binary.BigEndian, &msgLen); err != nil { return nil, err } msg := make([]byte, msgLen) if _, err := io.ReadFull(c, msg); err != nil { return nil, err } return msg, nil } // TODO(roasbeef): incorporate buffer pool // writeClear writes the passed message with a prefixed 2-byte length header. func writeClear(conn net.Conn, msg []byte) (int, error) { if len(msg) > 65530 { return 0, fmt.Errorf("lmsg too long, %d bytes", len(msg)) } // Add 2 byte length header (pbx doesn't need it) and send over TCP. var msgBuf bytes.Buffer if err := binary.Write(&msgBuf, binary.BigEndian, uint16(len(msg))); err != nil { return 0, err } if _, err := msgBuf.Write(msg); err != nil { return 0, err } return conn.Write(msgBuf.Bytes()) }