Dmitry eeca435064 Add rendezvous implementation for discovery interface
Update vendor

Integrate rendezvous into status node

Add a test with failover using rendezvous

Use multiple servers in client

Use discovery V5 by default and test that node can be started with rendezvous discovet

Fix linter

Update rendezvous client to one with instrumented stream

Address feedback

Fix test with updated topic limits

Apply several suggestions

Change log to debug for request errors because we continue execution

Remove web3js after rebase

Update rendezvous package
2018-07-25 15:10:57 +03:00

387 lines
11 KiB
Go

package secio
import (
"bytes"
"context"
"crypto/rand"
"errors"
"fmt"
"net"
"time"
proto "github.com/gogo/protobuf/proto"
logging "github.com/ipfs/go-log"
cs "github.com/libp2p/go-conn-security"
ci "github.com/libp2p/go-libp2p-crypto"
peer "github.com/libp2p/go-libp2p-peer"
pb "github.com/libp2p/go-libp2p-secio/pb"
msgio "github.com/libp2p/go-msgio"
mh "github.com/multiformats/go-multihash"
)
var log = logging.Logger("secio")
// ErrUnsupportedKeyType is returned when a private key cast/type switch fails.
var ErrUnsupportedKeyType = errors.New("unsupported key type")
// ErrClosed signals the closing of a connection.
var ErrClosed = errors.New("connection closed")
// ErrWrongPeer is returned when we attempt to handshake with the wrong peer.
var ErrWrongPeer = errors.New("connected to wrong peer")
// ErrBadSig signals that the peer sent us a handshake packet with a bad signature.
var ErrBadSig = errors.New("bad signature")
// ErrEcho is returned when we're attempting to handshake with the same keys and nonces.
var ErrEcho = errors.New("same keys and nonces. one side talking to self")
// HandshakeTimeout governs how long the handshake will be allowed to take place for.
// Making this number large means there could be many bogus connections waiting to
// timeout in flight. Typical handshakes take ~3RTTs, so it should be completed within
// seconds across a typical planet in the solar system.
var HandshakeTimeout = time.Second * 30
// nonceSize is the size of our nonces (in bytes)
const nonceSize = 16
// secureSession encapsulates all the parameters needed for encrypting
// and decrypting traffic from an insecure channel.
type secureSession struct {
msgio.ReadWriteCloser
insecure net.Conn
insecureM msgio.ReadWriter
localKey ci.PrivKey
localPeer peer.ID
remotePeer peer.ID
local encParams
remote encParams
sharedSecret []byte
}
var _ cs.Conn = &secureSession{}
func (s *secureSession) Loggable() map[string]interface{} {
m := make(map[string]interface{})
m["localPeer"] = s.localPeer.Pretty()
m["remotePeer"] = s.remotePeer.Pretty()
m["established"] = (s.ReadWriteCloser != nil)
return m
}
func newSecureSession(ctx context.Context, local peer.ID, key ci.PrivKey, insecure net.Conn, remotePeer peer.ID) (*secureSession, error) {
s := &secureSession{localPeer: local, localKey: key}
switch {
case s.localPeer == "":
return nil, errors.New("no local id provided")
case s.localKey == nil:
return nil, errors.New("no local private key provided")
case !s.localPeer.MatchesPrivateKey(s.localKey):
return nil, fmt.Errorf("peer.ID does not match PrivateKey")
case insecure == nil:
return nil, fmt.Errorf("insecure ReadWriter is nil")
}
s.insecure = insecure
s.insecureM = msgio.NewReadWriter(insecure)
s.remotePeer = remotePeer
handshakeCtx, cancel := context.WithTimeout(ctx, HandshakeTimeout) // remove
defer cancel()
if err := s.runHandshake(handshakeCtx); err != nil {
return nil, err
}
return s, nil
}
func hashSha256(data []byte) mh.Multihash {
h, err := mh.Sum(data, mh.SHA2_256, -1)
if err != nil {
// this error can be safely ignored (panic) because multihash only fails
// from the selection of hash function. If the fn + length are valid, it
// won't error.
panic("multihash failed to hash using SHA2_256.")
}
return h
}
// runHandshake performs initial communication over insecure channel to share
// keys, IDs, and initiate communication, assigning all necessary params.
// requires the duplex channel to be a msgio.ReadWriter (for framed messaging)
func (s *secureSession) runHandshake(ctx context.Context) error {
defer log.EventBegin(ctx, "secureHandshake", s).Done()
result := make(chan error, 1)
go func() {
// do *not* close the channel (will look like a success).
result <- s.runHandshakeSync()
}()
var err error
select {
case <-ctx.Done():
// State unknown. We *have* to close this.
s.insecure.Close()
err = ctx.Err()
case err = <-result:
}
return err
}
func (s *secureSession) runHandshakeSync() error {
// =============================================================================
// step 1. Propose -- propose cipher suite + send pubkeys + nonce
// Generate and send Hello packet.
// Hello = (rand, PublicKey, Supported)
nonceOut := make([]byte, nonceSize)
_, err := rand.Read(nonceOut)
if err != nil {
return err
}
s.local.permanentPubKey = s.localKey.GetPublic()
myPubKeyBytes, err := s.local.permanentPubKey.Bytes()
if err != nil {
return err
}
proposeOut := new(pb.Propose)
proposeOut.Rand = nonceOut
proposeOut.Pubkey = myPubKeyBytes
proposeOut.Exchanges = &SupportedExchanges
proposeOut.Ciphers = &SupportedCiphers
proposeOut.Hashes = &SupportedHashes
// log.Debugf("1.0 Propose: nonce:%s exchanges:%s ciphers:%s hashes:%s",
// nonceOut, SupportedExchanges, SupportedCiphers, SupportedHashes)
// Marshal our propose packet
proposeOutBytes, err := proto.Marshal(proposeOut)
if err != nil {
return err
}
// Send Propose packet and Receive their Propose packet
proposeInBytes, err := readWriteMsg(s.insecureM, proposeOutBytes)
if err != nil {
return err
}
defer s.insecureM.ReleaseMsg(proposeInBytes)
// Parse their propose packet
proposeIn := new(pb.Propose)
if err = proto.Unmarshal(proposeInBytes, proposeIn); err != nil {
return err
}
// log.Debugf("1.0.1 Propose recv: nonce:%s exchanges:%s ciphers:%s hashes:%s",
// proposeIn.GetRand(), proposeIn.GetExchanges(), proposeIn.GetCiphers(), proposeIn.GetHashes())
// =============================================================================
// step 1.1 Identify -- get identity from their key
// get remote identity
s.remote.permanentPubKey, err = ci.UnmarshalPublicKey(proposeIn.GetPubkey())
if err != nil {
return err
}
// get peer id
actualRemotePeer, err := peer.IDFromPublicKey(s.remote.permanentPubKey)
if err != nil {
return err
}
switch s.remotePeer {
case actualRemotePeer:
// All good.
case "":
// No peer set. We're accepting a remote connection.
s.remotePeer = actualRemotePeer
default:
// Peer mismatch. Bail.
s.insecure.Close()
log.Debugf("expected peer %s, got peer %s", s.remotePeer, actualRemotePeer)
return ErrWrongPeer
}
log.Debugf("1.1 Identify: %s Remote Peer Identified as %s", s.localPeer, s.remotePeer)
// =============================================================================
// step 1.2 Selection -- select/agree on best encryption parameters
// to determine order, use cmp(H(remote_pubkey||local_rand), H(local_pubkey||remote_rand)).
oh1 := hashSha256(append(proposeIn.GetPubkey(), nonceOut...))
oh2 := hashSha256(append(myPubKeyBytes, proposeIn.GetRand()...))
order := bytes.Compare(oh1, oh2)
if order == 0 {
return ErrEcho // talking to self (same socket. must be reuseport + dialing self)
}
s.local.curveT, err = selectBest(order, SupportedExchanges, proposeIn.GetExchanges())
if err != nil {
return err
}
s.local.cipherT, err = selectBest(order, SupportedCiphers, proposeIn.GetCiphers())
if err != nil {
return err
}
s.local.hashT, err = selectBest(order, SupportedHashes, proposeIn.GetHashes())
if err != nil {
return err
}
// we use the same params for both directions (must choose same curve)
// WARNING: if they dont SelectBest the same way, this won't work...
s.remote.curveT = s.local.curveT
s.remote.cipherT = s.local.cipherT
s.remote.hashT = s.local.hashT
// log.Debugf("1.2 selection: exchange:%s cipher:%s hash:%s",
// s.local.curveT, s.local.cipherT, s.local.hashT)
// =============================================================================
// step 2. Exchange -- exchange (signed) ephemeral keys. verify signatures.
// Generate EphemeralPubKey
var genSharedKey ci.GenSharedKey
s.local.ephemeralPubKey, genSharedKey, err = ci.GenerateEKeyPair(s.local.curveT)
if err != nil {
return err
}
// Gather corpus to sign.
selectionOut := new(bytes.Buffer)
selectionOut.Write(proposeOutBytes)
selectionOut.Write(proposeInBytes)
selectionOut.Write(s.local.ephemeralPubKey)
selectionOutBytes := selectionOut.Bytes()
// log.Debugf("2.0 exchange: %v", selectionOutBytes)
exchangeOut := new(pb.Exchange)
exchangeOut.Epubkey = s.local.ephemeralPubKey
exchangeOut.Signature, err = s.localKey.Sign(selectionOutBytes)
if err != nil {
return err
}
// Marshal our exchange packet
exchangeOutBytes, err := proto.Marshal(exchangeOut)
if err != nil {
return err
}
// Send Exchange packet and receive their Exchange packet
exchangeInBytes, err := readWriteMsg(s.insecureM, exchangeOutBytes)
if err != nil {
return err
}
defer s.insecureM.ReleaseMsg(exchangeInBytes)
// Parse their Exchange packet.
exchangeIn := new(pb.Exchange)
if err = proto.Unmarshal(exchangeInBytes, exchangeIn); err != nil {
return err
}
// =============================================================================
// step 2.1. Verify -- verify their exchange packet is good.
// get their ephemeral pub key
s.remote.ephemeralPubKey = exchangeIn.GetEpubkey()
selectionIn := new(bytes.Buffer)
selectionIn.Write(proposeInBytes)
selectionIn.Write(proposeOutBytes)
selectionIn.Write(s.remote.ephemeralPubKey)
selectionInBytes := selectionIn.Bytes()
// log.Debugf("2.0.1 exchange recv: %v", selectionInBytes)
// u.POut("Remote Peer Identified as %s\n", s.remote)
sigOK, err := s.remote.permanentPubKey.Verify(selectionInBytes, exchangeIn.GetSignature())
if err != nil {
// log.Error("2.1 Verify: failed: %s", err)
return err
}
if !sigOK {
// log.Error("2.1 Verify: failed: %s", ErrBadSig)
return ErrBadSig
}
// log.Debugf("2.1 Verify: signature verified.")
// =============================================================================
// step 2.2. Keys -- generate keys for mac + encryption
// OK! seems like we're good to go.
s.sharedSecret, err = genSharedKey(exchangeIn.GetEpubkey())
if err != nil {
return err
}
// generate two sets of keys (stretching)
k1, k2 := ci.KeyStretcher(s.local.cipherT, s.local.hashT, s.sharedSecret)
// use random nonces to decide order.
switch {
case order > 0:
// just break
case order < 0:
k1, k2 = k2, k1 // swap
default:
// we should've bailed before this. but if not, bail here.
return ErrEcho
}
s.local.keys = k1
s.remote.keys = k2
// log.Debug("2.2 keys:\n\tshared: %v\n\tk1: %v\n\tk2: %v",
// s.sharedSecret, s.local.keys, s.remote.keys)
// =============================================================================
// step 2.3. MAC + Cipher -- prepare MAC + cipher
if err := s.local.makeMacAndCipher(); err != nil {
return err
}
if err := s.remote.makeMacAndCipher(); err != nil {
return err
}
// log.Debug("2.3 mac + cipher.")
// =============================================================================
// step 3. Finish -- send expected message to verify encryption works (send local nonce)
// setup ETM ReadWriter
w := NewETMWriter(s.insecure, s.local.cipher, s.local.mac)
r := NewETMReader(s.insecure, s.remote.cipher, s.remote.mac)
s.ReadWriteCloser = msgio.Combine(w, r).(msgio.ReadWriteCloser)
// log.Debug("3.0 finish. sending: %v", proposeIn.GetRand())
// send their Nonce and receive ours
nonceOut2, err := readWriteMsg(s.ReadWriteCloser, proposeIn.GetRand())
if err != nil {
return err
}
defer s.ReleaseMsg(nonceOut2)
// log.Debug("3.0 finish.\n\texpect: %v\n\tactual: %v", nonceOut, nonceOut2)
if !bytes.Equal(nonceOut, nonceOut2) {
return fmt.Errorf("Failed to read our encrypted nonce: %s != %s", nonceOut2, nonceOut)
}
// Whew! ok, that's all folks.
return nil
}