github.com/keltia/go-ipfs@v0.3.8-0.20150909044612-210793031c63/p2p/crypto/secio/protocol.go (about)

     1  package secio
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/rand"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  	"sync"
    10  	"time"
    11  
    12  	msgio "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-msgio"
    13  	context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
    14  	ci "github.com/ipfs/go-ipfs/p2p/crypto"
    15  	pb "github.com/ipfs/go-ipfs/p2p/crypto/secio/pb"
    16  	peer "github.com/ipfs/go-ipfs/p2p/peer"
    17  	eventlog "github.com/ipfs/go-ipfs/thirdparty/eventlog"
    18  	u "github.com/ipfs/go-ipfs/util"
    19  )
    20  
    21  var log = eventlog.Logger("secio")
    22  
    23  // ErrUnsupportedKeyType is returned when a private key cast/type switch fails.
    24  var ErrUnsupportedKeyType = errors.New("unsupported key type")
    25  
    26  // ErrClosed signals the closing of a connection.
    27  var ErrClosed = errors.New("connection closed")
    28  
    29  // ErrEcho is returned when we're attempting to handshake with the same keys and nonces.
    30  var ErrEcho = errors.New("same keys and nonces. one side talking to self.")
    31  
    32  // HandshakeTimeout governs how long the handshake will be allowed to take place for.
    33  // Making this number large means there could be many bogus connections waiting to
    34  // timeout in flight. Typical handshakes take ~3RTTs, so it should be completed within
    35  // seconds across a typical planet in the solar system.
    36  var HandshakeTimeout = time.Second * 30
    37  
    38  // nonceSize is the size of our nonces (in bytes)
    39  const nonceSize = 16
    40  
    41  // secureSession encapsulates all the parameters needed for encrypting
    42  // and decrypting traffic from an insecure channel.
    43  type secureSession struct {
    44  	ctx    context.Context
    45  	cancel context.CancelFunc
    46  
    47  	secure    msgio.ReadWriteCloser
    48  	insecure  io.ReadWriteCloser
    49  	insecureM msgio.ReadWriter
    50  
    51  	localKey   ci.PrivKey
    52  	localPeer  peer.ID
    53  	remotePeer peer.ID
    54  
    55  	local  encParams
    56  	remote encParams
    57  
    58  	sharedSecret []byte
    59  
    60  	handshakeMu   sync.Mutex // guards handshakeDone + handshakeErr
    61  	handshakeDone bool
    62  	handshakeErr  error
    63  }
    64  
    65  func (s *secureSession) Loggable() map[string]interface{} {
    66  	m := make(map[string]interface{})
    67  	m["localPeer"] = s.localPeer.Pretty()
    68  	m["remotePeer"] = s.remotePeer.Pretty()
    69  	m["established"] = (s.secure != nil)
    70  	return m
    71  }
    72  
    73  func newSecureSession(ctx context.Context, local peer.ID, key ci.PrivKey, insecure io.ReadWriteCloser) (*secureSession, error) {
    74  	s := &secureSession{localPeer: local, localKey: key}
    75  	s.ctx, s.cancel = context.WithCancel(ctx)
    76  
    77  	switch {
    78  	case s.localPeer == "":
    79  		return nil, errors.New("no local id provided")
    80  	case s.localKey == nil:
    81  		return nil, errors.New("no local private key provided")
    82  	case !s.localPeer.MatchesPrivateKey(s.localKey):
    83  		return nil, fmt.Errorf("peer.ID does not match PrivateKey")
    84  	case insecure == nil:
    85  		return nil, fmt.Errorf("insecure ReadWriter is nil")
    86  	}
    87  
    88  	s.ctx = ctx
    89  	s.insecure = insecure
    90  	s.insecureM = msgio.NewReadWriter(insecure)
    91  	return s, nil
    92  }
    93  
    94  func (s *secureSession) Handshake() error {
    95  	s.handshakeMu.Lock()
    96  	defer s.handshakeMu.Unlock()
    97  
    98  	if s.handshakeErr != nil {
    99  		return s.handshakeErr
   100  	}
   101  
   102  	if !s.handshakeDone {
   103  		s.handshakeErr = s.runHandshake()
   104  		s.handshakeDone = true
   105  	}
   106  	return s.handshakeErr
   107  }
   108  
   109  // runHandshake performs initial communication over insecure channel to share
   110  // keys, IDs, and initiate communication, assigning all necessary params.
   111  // requires the duplex channel to be a msgio.ReadWriter (for framed messaging)
   112  func (s *secureSession) runHandshake() error {
   113  	ctx, cancel := context.WithTimeout(s.ctx, HandshakeTimeout) // remove
   114  	defer cancel()
   115  
   116  	// =============================================================================
   117  	// step 1. Propose -- propose cipher suite + send pubkeys + nonce
   118  
   119  	// Generate and send Hello packet.
   120  	// Hello = (rand, PublicKey, Supported)
   121  	nonceOut := make([]byte, nonceSize)
   122  	_, err := rand.Read(nonceOut)
   123  	if err != nil {
   124  		return err
   125  	}
   126  
   127  	defer log.EventBegin(ctx, "secureHandshake", s).Done()
   128  
   129  	s.local.permanentPubKey = s.localKey.GetPublic()
   130  	myPubKeyBytes, err := s.local.permanentPubKey.Bytes()
   131  	if err != nil {
   132  		return err
   133  	}
   134  
   135  	proposeOut := new(pb.Propose)
   136  	proposeOut.Rand = nonceOut
   137  	proposeOut.Pubkey = myPubKeyBytes
   138  	proposeOut.Exchanges = &SupportedExchanges
   139  	proposeOut.Ciphers = &SupportedCiphers
   140  	proposeOut.Hashes = &SupportedHashes
   141  
   142  	// log.Debugf("1.0 Propose: nonce:%s exchanges:%s ciphers:%s hashes:%s",
   143  	// 	nonceOut, SupportedExchanges, SupportedCiphers, SupportedHashes)
   144  
   145  	// Send Propose packet (respects ctx)
   146  	proposeOutBytes, err := writeMsgCtx(ctx, s.insecureM, proposeOut)
   147  	if err != nil {
   148  		return err
   149  	}
   150  
   151  	// Receive + Parse their Propose packet and generate an Exchange packet.
   152  	proposeIn := new(pb.Propose)
   153  	proposeInBytes, err := readMsgCtx(ctx, s.insecureM, proposeIn)
   154  	if err != nil {
   155  		return err
   156  	}
   157  
   158  	// log.Debugf("1.0.1 Propose recv: nonce:%s exchanges:%s ciphers:%s hashes:%s",
   159  	// 	proposeIn.GetRand(), proposeIn.GetExchanges(), proposeIn.GetCiphers(), proposeIn.GetHashes())
   160  
   161  	// =============================================================================
   162  	// step 1.1 Identify -- get identity from their key
   163  
   164  	// get remote identity
   165  	s.remote.permanentPubKey, err = ci.UnmarshalPublicKey(proposeIn.GetPubkey())
   166  	if err != nil {
   167  		return err
   168  	}
   169  
   170  	// get peer id
   171  	s.remotePeer, err = peer.IDFromPublicKey(s.remote.permanentPubKey)
   172  	if err != nil {
   173  		return err
   174  	}
   175  
   176  	log.Debugf("1.1 Identify: %s Remote Peer Identified as %s", s.localPeer, s.remotePeer)
   177  
   178  	// =============================================================================
   179  	// step 1.2 Selection -- select/agree on best encryption parameters
   180  
   181  	// to determine order, use cmp(H(remote_pubkey||local_rand), H(local_pubkey||remote_rand)).
   182  	oh1 := u.Hash(append(proposeIn.GetPubkey(), nonceOut...))
   183  	oh2 := u.Hash(append(myPubKeyBytes, proposeIn.GetRand()...))
   184  	order := bytes.Compare(oh1, oh2)
   185  	if order == 0 {
   186  		return ErrEcho // talking to self (same socket. must be reuseport + dialing self)
   187  	}
   188  
   189  	s.local.curveT, err = selectBest(order, SupportedExchanges, proposeIn.GetExchanges())
   190  	if err != nil {
   191  		return err
   192  	}
   193  
   194  	s.local.cipherT, err = selectBest(order, SupportedCiphers, proposeIn.GetCiphers())
   195  	if err != nil {
   196  		return err
   197  	}
   198  
   199  	s.local.hashT, err = selectBest(order, SupportedHashes, proposeIn.GetHashes())
   200  	if err != nil {
   201  		return err
   202  	}
   203  
   204  	// we use the same params for both directions (must choose same curve)
   205  	// WARNING: if they dont SelectBest the same way, this won't work...
   206  	s.remote.curveT = s.local.curveT
   207  	s.remote.cipherT = s.local.cipherT
   208  	s.remote.hashT = s.local.hashT
   209  
   210  	// log.Debugf("1.2 selection: exchange:%s cipher:%s hash:%s",
   211  	// 	s.local.curveT, s.local.cipherT, s.local.hashT)
   212  
   213  	// =============================================================================
   214  	// step 2. Exchange -- exchange (signed) ephemeral keys. verify signatures.
   215  
   216  	// Generate EphemeralPubKey
   217  	var genSharedKey ci.GenSharedKey
   218  	s.local.ephemeralPubKey, genSharedKey, err = ci.GenerateEKeyPair(s.local.curveT)
   219  
   220  	// Gather corpus to sign.
   221  	selectionOut := new(bytes.Buffer)
   222  	selectionOut.Write(proposeOutBytes)
   223  	selectionOut.Write(proposeInBytes)
   224  	selectionOut.Write(s.local.ephemeralPubKey)
   225  	selectionOutBytes := selectionOut.Bytes()
   226  
   227  	// log.Debugf("2.0 exchange: %v", selectionOutBytes)
   228  	exchangeOut := new(pb.Exchange)
   229  	exchangeOut.Epubkey = s.local.ephemeralPubKey
   230  	exchangeOut.Signature, err = s.localKey.Sign(selectionOutBytes)
   231  	if err != nil {
   232  		return err
   233  	}
   234  
   235  	// Send Propose packet (respects ctx)
   236  	if _, err := writeMsgCtx(ctx, s.insecureM, exchangeOut); err != nil {
   237  		return err
   238  	}
   239  
   240  	// Receive + Parse their Exchange packet.
   241  	exchangeIn := new(pb.Exchange)
   242  	if _, err := readMsgCtx(ctx, s.insecureM, exchangeIn); err != nil {
   243  		return err
   244  	}
   245  
   246  	// =============================================================================
   247  	// step 2.1. Verify -- verify their exchange packet is good.
   248  
   249  	// get their ephemeral pub key
   250  	s.remote.ephemeralPubKey = exchangeIn.GetEpubkey()
   251  
   252  	selectionIn := new(bytes.Buffer)
   253  	selectionIn.Write(proposeInBytes)
   254  	selectionIn.Write(proposeOutBytes)
   255  	selectionIn.Write(s.remote.ephemeralPubKey)
   256  	selectionInBytes := selectionIn.Bytes()
   257  	// log.Debugf("2.0.1 exchange recv: %v", selectionInBytes)
   258  
   259  	// u.POut("Remote Peer Identified as %s\n", s.remote)
   260  	sigOK, err := s.remote.permanentPubKey.Verify(selectionInBytes, exchangeIn.GetSignature())
   261  	if err != nil {
   262  		// log.Error("2.1 Verify: failed: %s", err)
   263  		return err
   264  	}
   265  
   266  	if !sigOK {
   267  		err := errors.New("Bad signature!")
   268  		// log.Error("2.1 Verify: failed: %s", err)
   269  		return err
   270  	}
   271  	// log.Debugf("2.1 Verify: signature verified.")
   272  
   273  	// =============================================================================
   274  	// step 2.2. Keys -- generate keys for mac + encryption
   275  
   276  	// OK! seems like we're good to go.
   277  	s.sharedSecret, err = genSharedKey(exchangeIn.GetEpubkey())
   278  	if err != nil {
   279  		return err
   280  	}
   281  
   282  	// generate two sets of keys (stretching)
   283  	k1, k2 := ci.KeyStretcher(s.local.cipherT, s.local.hashT, s.sharedSecret)
   284  
   285  	// use random nonces to decide order.
   286  	switch {
   287  	case order > 0:
   288  		// just break
   289  	case order < 0:
   290  		k1, k2 = k2, k1 // swap
   291  	default:
   292  		// we should've bailed before this. but if not, bail here.
   293  		return ErrEcho
   294  	}
   295  	s.local.keys = k1
   296  	s.remote.keys = k2
   297  
   298  	// log.Debug("2.2 keys:\n\tshared: %v\n\tk1: %v\n\tk2: %v",
   299  	// 	s.sharedSecret, s.local.keys, s.remote.keys)
   300  
   301  	// =============================================================================
   302  	// step 2.3. MAC + Cipher -- prepare MAC + cipher
   303  
   304  	if err := s.local.makeMacAndCipher(); err != nil {
   305  		return err
   306  	}
   307  
   308  	if err := s.remote.makeMacAndCipher(); err != nil {
   309  		return err
   310  	}
   311  
   312  	// log.Debug("2.3 mac + cipher.")
   313  
   314  	// =============================================================================
   315  	// step 3. Finish -- send expected message to verify encryption works (send local nonce)
   316  
   317  	// setup ETM ReadWriter
   318  	w := NewETMWriter(s.insecure, s.local.cipher, s.local.mac)
   319  	r := NewETMReader(s.insecure, s.remote.cipher, s.remote.mac)
   320  	s.secure = msgio.Combine(w, r).(msgio.ReadWriteCloser)
   321  
   322  	// log.Debug("3.0 finish. sending: %v", proposeIn.GetRand())
   323  	// send their Nonce.
   324  	if _, err := s.secure.Write(proposeIn.GetRand()); err != nil {
   325  		return fmt.Errorf("Failed to write Finish nonce: %s", err)
   326  	}
   327  
   328  	// read our Nonce
   329  	nonceOut2 := make([]byte, len(nonceOut))
   330  	if _, err := io.ReadFull(s.secure, nonceOut2); err != nil {
   331  		return fmt.Errorf("Failed to read Finish nonce: %s", err)
   332  	}
   333  
   334  	// log.Debug("3.0 finish.\n\texpect: %v\n\tactual: %v", nonceOut, nonceOut2)
   335  	if !bytes.Equal(nonceOut, nonceOut2) {
   336  		return fmt.Errorf("Failed to read our encrypted nonce: %s != %s", nonceOut2, nonceOut)
   337  	}
   338  
   339  	// Whew! ok, that's all folks.
   340  	return nil
   341  }