github.com/jonasnick/go-ethereum@v0.7.12-0.20150216215225-22176f05d387/p2p/crypto.go (about)

     1  package p2p
     2  
     3  import (
     4  	// "binary"
     5  	"crypto/ecdsa"
     6  	"crypto/rand"
     7  	"fmt"
     8  	"io"
     9  
    10  	"github.com/jonasnick/go-ethereum/crypto"
    11  	"github.com/jonasnick/go-ethereum/crypto/ecies"
    12  	"github.com/jonasnick/go-ethereum/crypto/secp256k1"
    13  	ethlogger "github.com/jonasnick/go-ethereum/logger"
    14  	"github.com/jonasnick/go-ethereum/p2p/discover"
    15  )
    16  
    17  var clogger = ethlogger.NewLogger("CRYPTOID")
    18  
    19  const (
    20  	sskLen = 16 // ecies.MaxSharedKeyLength(pubKey) / 2
    21  	sigLen = 65 // elliptic S256
    22  	pubLen = 64 // 512 bit pubkey in uncompressed representation without format byte
    23  	shaLen = 32 // hash length (for nonce etc)
    24  
    25  	authMsgLen  = sigLen + shaLen + pubLen + shaLen + 1
    26  	authRespLen = pubLen + shaLen + 1
    27  
    28  	eciesBytes = 65 + 16 + 32
    29  	iHSLen     = authMsgLen + eciesBytes  // size of the final ECIES payload sent as initiator's handshake
    30  	rHSLen     = authRespLen + eciesBytes // size of the final ECIES payload sent as receiver's handshake
    31  )
    32  
    33  type hexkey []byte
    34  
    35  func (self hexkey) String() string {
    36  	return fmt.Sprintf("(%d) %x", len(self), []byte(self))
    37  }
    38  
    39  func encHandshake(conn io.ReadWriter, prv *ecdsa.PrivateKey, dial *discover.Node) (
    40  	remoteID discover.NodeID,
    41  	sessionToken []byte,
    42  	err error,
    43  ) {
    44  	if dial == nil {
    45  		var remotePubkey []byte
    46  		sessionToken, remotePubkey, err = inboundEncHandshake(conn, prv, nil)
    47  		copy(remoteID[:], remotePubkey)
    48  	} else {
    49  		remoteID = dial.ID
    50  		sessionToken, err = outboundEncHandshake(conn, prv, remoteID[:], nil)
    51  	}
    52  	return remoteID, sessionToken, err
    53  }
    54  
    55  // outboundEncHandshake negotiates a session token on conn.
    56  // it should be called on the dialing side of the connection.
    57  //
    58  // privateKey is the local client's private key
    59  // remotePublicKey is the remote peer's node ID
    60  // sessionToken is the token from a previous session with this node.
    61  func outboundEncHandshake(conn io.ReadWriter, prvKey *ecdsa.PrivateKey, remotePublicKey []byte, sessionToken []byte) (
    62  	newSessionToken []byte,
    63  	err error,
    64  ) {
    65  	auth, initNonce, randomPrivKey, err := authMsg(prvKey, remotePublicKey, sessionToken)
    66  	if err != nil {
    67  		return nil, err
    68  	}
    69  	if sessionToken != nil {
    70  		clogger.Debugf("session-token: %v", hexkey(sessionToken))
    71  	}
    72  
    73  	clogger.Debugf("initiator-nonce: %v", hexkey(initNonce))
    74  	clogger.Debugf("initiator-random-private-key: %v", hexkey(crypto.FromECDSA(randomPrivKey)))
    75  	randomPublicKeyS, _ := exportPublicKey(&randomPrivKey.PublicKey)
    76  	clogger.Debugf("initiator-random-public-key: %v", hexkey(randomPublicKeyS))
    77  	if _, err = conn.Write(auth); err != nil {
    78  		return nil, err
    79  	}
    80  	clogger.Debugf("initiator handshake: %v", hexkey(auth))
    81  
    82  	response := make([]byte, rHSLen)
    83  	if _, err = io.ReadFull(conn, response); err != nil {
    84  		return nil, err
    85  	}
    86  	recNonce, remoteRandomPubKey, _, err := completeHandshake(response, prvKey)
    87  	if err != nil {
    88  		return nil, err
    89  	}
    90  
    91  	clogger.Debugf("receiver-nonce: %v", hexkey(recNonce))
    92  	remoteRandomPubKeyS, _ := exportPublicKey(remoteRandomPubKey)
    93  	clogger.Debugf("receiver-random-public-key: %v", hexkey(remoteRandomPubKeyS))
    94  	return newSession(initNonce, recNonce, randomPrivKey, remoteRandomPubKey)
    95  }
    96  
    97  // authMsg creates the initiator handshake.
    98  func authMsg(prvKey *ecdsa.PrivateKey, remotePubKeyS, sessionToken []byte) (
    99  	auth, initNonce []byte,
   100  	randomPrvKey *ecdsa.PrivateKey,
   101  	err error,
   102  ) {
   103  	// session init, common to both parties
   104  	remotePubKey, err := importPublicKey(remotePubKeyS)
   105  	if err != nil {
   106  		return
   107  	}
   108  
   109  	var tokenFlag byte // = 0x00
   110  	if sessionToken == nil {
   111  		// no session token found means we need to generate shared secret.
   112  		// ecies shared secret is used as initial session token for new peers
   113  		// generate shared key from prv and remote pubkey
   114  		if sessionToken, err = ecies.ImportECDSA(prvKey).GenerateShared(ecies.ImportECDSAPublic(remotePubKey), sskLen, sskLen); err != nil {
   115  			return
   116  		}
   117  		// tokenFlag = 0x00 // redundant
   118  	} else {
   119  		// for known peers, we use stored token from the previous session
   120  		tokenFlag = 0x01
   121  	}
   122  
   123  	//E(remote-pubk, S(ecdhe-random, ecdh-shared-secret^nonce) || H(ecdhe-random-pubk) || pubk || nonce || 0x0)
   124  	// E(remote-pubk, S(ecdhe-random, token^nonce) || H(ecdhe-random-pubk) || pubk || nonce || 0x1)
   125  	// allocate msgLen long message,
   126  	var msg []byte = make([]byte, authMsgLen)
   127  	initNonce = msg[authMsgLen-shaLen-1 : authMsgLen-1]
   128  	if _, err = rand.Read(initNonce); err != nil {
   129  		return
   130  	}
   131  	// create known message
   132  	// ecdh-shared-secret^nonce for new peers
   133  	// token^nonce for old peers
   134  	var sharedSecret = xor(sessionToken, initNonce)
   135  
   136  	// generate random keypair to use for signing
   137  	if randomPrvKey, err = crypto.GenerateKey(); err != nil {
   138  		return
   139  	}
   140  	// sign shared secret (message known to both parties): shared-secret
   141  	var signature []byte
   142  	// signature = sign(ecdhe-random, shared-secret)
   143  	// uses secp256k1.Sign
   144  	if signature, err = crypto.Sign(sharedSecret, randomPrvKey); err != nil {
   145  		return
   146  	}
   147  
   148  	// message
   149  	// signed-shared-secret || H(ecdhe-random-pubk) || pubk || nonce || 0x0
   150  	copy(msg, signature) // copy signed-shared-secret
   151  	// H(ecdhe-random-pubk)
   152  	var randomPubKey64 []byte
   153  	if randomPubKey64, err = exportPublicKey(&randomPrvKey.PublicKey); err != nil {
   154  		return
   155  	}
   156  	var pubKey64 []byte
   157  	if pubKey64, err = exportPublicKey(&prvKey.PublicKey); err != nil {
   158  		return
   159  	}
   160  	copy(msg[sigLen:sigLen+shaLen], crypto.Sha3(randomPubKey64))
   161  	// pubkey copied to the correct segment.
   162  	copy(msg[sigLen+shaLen:sigLen+shaLen+pubLen], pubKey64)
   163  	// nonce is already in the slice
   164  	// stick tokenFlag byte to the end
   165  	msg[authMsgLen-1] = tokenFlag
   166  
   167  	// encrypt using remote-pubk
   168  	// auth = eciesEncrypt(remote-pubk, msg)
   169  	if auth, err = crypto.Encrypt(remotePubKey, msg); err != nil {
   170  		return
   171  	}
   172  	return
   173  }
   174  
   175  // completeHandshake is called when the initiator receives an
   176  // authentication response (aka receiver handshake). It completes the
   177  // handshake by reading off parameters the remote peer provides needed
   178  // to set up the secure session.
   179  func completeHandshake(auth []byte, prvKey *ecdsa.PrivateKey) (
   180  	respNonce []byte,
   181  	remoteRandomPubKey *ecdsa.PublicKey,
   182  	tokenFlag bool,
   183  	err error,
   184  ) {
   185  	var msg []byte
   186  	// they prove that msg is meant for me,
   187  	// I prove I possess private key if i can read it
   188  	if msg, err = crypto.Decrypt(prvKey, auth); err != nil {
   189  		return
   190  	}
   191  
   192  	respNonce = msg[pubLen : pubLen+shaLen]
   193  	var remoteRandomPubKeyS = msg[:pubLen]
   194  	if remoteRandomPubKey, err = importPublicKey(remoteRandomPubKeyS); err != nil {
   195  		return
   196  	}
   197  	if msg[authRespLen-1] == 0x01 {
   198  		tokenFlag = true
   199  	}
   200  	return
   201  }
   202  
   203  // inboundEncHandshake negotiates a session token on conn.
   204  // it should be called on the listening side of the connection.
   205  //
   206  // privateKey is the local client's private key
   207  // sessionToken is the token from a previous session with this node.
   208  func inboundEncHandshake(conn io.ReadWriter, prvKey *ecdsa.PrivateKey, sessionToken []byte) (
   209  	token, remotePubKey []byte,
   210  	err error,
   211  ) {
   212  	// we are listening connection. we are responders in the
   213  	// handshake. Extract info from the authentication. The initiator
   214  	// starts by sending us a handshake that we need to respond to. so
   215  	// we read auth message first, then respond.
   216  	auth := make([]byte, iHSLen)
   217  	if _, err := io.ReadFull(conn, auth); err != nil {
   218  		return nil, nil, err
   219  	}
   220  	response, recNonce, initNonce, remotePubKey, randomPrivKey, remoteRandomPubKey, err := authResp(auth, sessionToken, prvKey)
   221  	if err != nil {
   222  		return nil, nil, err
   223  	}
   224  	clogger.Debugf("receiver-nonce: %v", hexkey(recNonce))
   225  	clogger.Debugf("receiver-random-priv-key: %v", hexkey(crypto.FromECDSA(randomPrivKey)))
   226  	if _, err = conn.Write(response); err != nil {
   227  		return nil, nil, err
   228  	}
   229  	clogger.Debugf("receiver handshake:\n%v", hexkey(response))
   230  	token, err = newSession(initNonce, recNonce, randomPrivKey, remoteRandomPubKey)
   231  	return token, remotePubKey, err
   232  }
   233  
   234  // authResp is called by peer if it accepted (but not
   235  // initiated) the connection from the remote. It is passed the initiator
   236  // handshake received and the session token belonging to the
   237  // remote initiator.
   238  //
   239  // The first return value is the authentication response (aka receiver
   240  // handshake) that is to be sent to the remote initiator.
   241  func authResp(auth, sessionToken []byte, prvKey *ecdsa.PrivateKey) (
   242  	authResp, respNonce, initNonce, remotePubKeyS []byte,
   243  	randomPrivKey *ecdsa.PrivateKey,
   244  	remoteRandomPubKey *ecdsa.PublicKey,
   245  	err error,
   246  ) {
   247  	// they prove that msg is meant for me,
   248  	// I prove I possess private key if i can read it
   249  	msg, err := crypto.Decrypt(prvKey, auth)
   250  	if err != nil {
   251  		return
   252  	}
   253  
   254  	remotePubKeyS = msg[sigLen+shaLen : sigLen+shaLen+pubLen]
   255  	remotePubKey, _ := importPublicKey(remotePubKeyS)
   256  
   257  	var tokenFlag byte
   258  	if sessionToken == nil {
   259  		// no session token found means we need to generate shared secret.
   260  		// ecies shared secret is used as initial session token for new peers
   261  		// generate shared key from prv and remote pubkey
   262  		if sessionToken, err = ecies.ImportECDSA(prvKey).GenerateShared(ecies.ImportECDSAPublic(remotePubKey), sskLen, sskLen); err != nil {
   263  			return
   264  		}
   265  		// tokenFlag = 0x00 // redundant
   266  	} else {
   267  		// for known peers, we use stored token from the previous session
   268  		tokenFlag = 0x01
   269  	}
   270  
   271  	// the initiator nonce is read off the end of the message
   272  	initNonce = msg[authMsgLen-shaLen-1 : authMsgLen-1]
   273  	// I prove that i own prv key (to derive shared secret, and read
   274  	// nonce off encrypted msg) and that I own shared secret they
   275  	// prove they own the private key belonging to ecdhe-random-pubk
   276  	// we can now reconstruct the signed message and recover the peers
   277  	// pubkey
   278  	var signedMsg = xor(sessionToken, initNonce)
   279  	var remoteRandomPubKeyS []byte
   280  	if remoteRandomPubKeyS, err = secp256k1.RecoverPubkey(signedMsg, msg[:sigLen]); err != nil {
   281  		return
   282  	}
   283  	// convert to ECDSA standard
   284  	if remoteRandomPubKey, err = importPublicKey(remoteRandomPubKeyS); err != nil {
   285  		return
   286  	}
   287  
   288  	// now we find ourselves a long task too, fill it random
   289  	var resp = make([]byte, authRespLen)
   290  	// generate shaLen long nonce
   291  	respNonce = resp[pubLen : pubLen+shaLen]
   292  	if _, err = rand.Read(respNonce); err != nil {
   293  		return
   294  	}
   295  	// generate random keypair for session
   296  	if randomPrivKey, err = crypto.GenerateKey(); err != nil {
   297  		return
   298  	}
   299  	// responder auth message
   300  	// E(remote-pubk, ecdhe-random-pubk || nonce || 0x0)
   301  	var randomPubKeyS []byte
   302  	if randomPubKeyS, err = exportPublicKey(&randomPrivKey.PublicKey); err != nil {
   303  		return
   304  	}
   305  	copy(resp[:pubLen], randomPubKeyS)
   306  	// nonce is already in the slice
   307  	resp[authRespLen-1] = tokenFlag
   308  
   309  	// encrypt using remote-pubk
   310  	// auth = eciesEncrypt(remote-pubk, msg)
   311  	// why not encrypt with ecdhe-random-remote
   312  	if authResp, err = crypto.Encrypt(remotePubKey, resp); err != nil {
   313  		return
   314  	}
   315  	return
   316  }
   317  
   318  // newSession is called after the handshake is completed. The
   319  // arguments are values negotiated in the handshake. The return value
   320  // is a new session Token to be remembered for the next time we
   321  // connect with this peer.
   322  func newSession(initNonce, respNonce []byte, privKey *ecdsa.PrivateKey, remoteRandomPubKey *ecdsa.PublicKey) ([]byte, error) {
   323  	// 3) Now we can trust ecdhe-random-pubk to derive new keys
   324  	//ecdhe-shared-secret = ecdh.agree(ecdhe-random, remote-ecdhe-random-pubk)
   325  	pubKey := ecies.ImportECDSAPublic(remoteRandomPubKey)
   326  	dhSharedSecret, err := ecies.ImportECDSA(privKey).GenerateShared(pubKey, sskLen, sskLen)
   327  	if err != nil {
   328  		return nil, err
   329  	}
   330  	sharedSecret := crypto.Sha3(dhSharedSecret, crypto.Sha3(respNonce, initNonce))
   331  	sessionToken := crypto.Sha3(sharedSecret)
   332  	return sessionToken, nil
   333  }
   334  
   335  // importPublicKey unmarshals 512 bit public keys.
   336  func importPublicKey(pubKey []byte) (pubKeyEC *ecdsa.PublicKey, err error) {
   337  	var pubKey65 []byte
   338  	switch len(pubKey) {
   339  	case 64:
   340  		// add 'uncompressed key' flag
   341  		pubKey65 = append([]byte{0x04}, pubKey...)
   342  	case 65:
   343  		pubKey65 = pubKey
   344  	default:
   345  		return nil, fmt.Errorf("invalid public key length %v (expect 64/65)", len(pubKey))
   346  	}
   347  	return crypto.ToECDSAPub(pubKey65), nil
   348  }
   349  
   350  func exportPublicKey(pubKeyEC *ecdsa.PublicKey) (pubKey []byte, err error) {
   351  	if pubKeyEC == nil {
   352  		return nil, fmt.Errorf("no ECDSA public key given")
   353  	}
   354  	return crypto.FromECDSAPub(pubKeyEC)[1:], nil
   355  }
   356  
   357  func xor(one, other []byte) (xor []byte) {
   358  	xor = make([]byte, len(one))
   359  	for i := 0; i < len(one); i++ {
   360  		xor[i] = one[i] ^ other[i]
   361  	}
   362  	return xor
   363  }