mirror of https://github.com/status-im/op-geth.git
make crypto handshake calls package level, store privateKey on peer + tests ok
This commit is contained in:
parent
4499743522
commit
68205dec9f
|
@ -32,47 +32,6 @@ type secretRW struct {
|
|||
aesSecret, macSecret, egressMac, ingressMac []byte
|
||||
}
|
||||
|
||||
/*
|
||||
cryptoId implements the crypto layer for the p2p networking
|
||||
It is initialised on the node's own identity (which has access to the node's private key) and run separately on a peer connection to set up a secure session after a crypto handshake
|
||||
After it performs a crypto handshake it returns
|
||||
*/
|
||||
type cryptoId struct {
|
||||
prvKey *ecdsa.PrivateKey
|
||||
pubKey *ecdsa.PublicKey
|
||||
pubKeyS []byte
|
||||
}
|
||||
|
||||
/*
|
||||
newCryptoId(id ClientIdentity) initialises a crypto layer manager. This object has a short lifecycle when the peer connection starts. It is survived by a secretRW (an message read writer with encryption and authentication) if the crypto handshake is successful.
|
||||
*/
|
||||
func newCryptoId(id ClientIdentity) (self *cryptoId, err error) {
|
||||
// will be at server init
|
||||
var prvKeyS []byte = id.PrivKey()
|
||||
if prvKeyS == nil {
|
||||
err = fmt.Errorf("no private key for client")
|
||||
return
|
||||
}
|
||||
// initialise ecies private key via importing keys (known via our own clientIdentity)
|
||||
// the key format is what elliptic package is using: elliptic.Marshal(Curve, X, Y)
|
||||
var prvKey = crypto.ToECDSA(prvKeyS)
|
||||
if prvKey == nil {
|
||||
err = fmt.Errorf("invalid private key for client")
|
||||
return
|
||||
}
|
||||
self = &cryptoId{
|
||||
prvKey: prvKey,
|
||||
// initialise public key from the imported private key
|
||||
pubKey: &prvKey.PublicKey,
|
||||
// to be created at server init shared between peers and sessions
|
||||
// for reuse, call wth ReadAt, no reset seek needed
|
||||
}
|
||||
self.pubKeyS = id.Pubkey()[1:]
|
||||
clogger.Debugf("initialise crypto for NodeId %v", hexkey(self.pubKeyS))
|
||||
clogger.Debugf("private-key %v\npublic key %v", hexkey(prvKeyS), hexkey(self.pubKeyS))
|
||||
return
|
||||
}
|
||||
|
||||
type hexkey []byte
|
||||
|
||||
func (self hexkey) String() string {
|
||||
|
@ -80,27 +39,29 @@ func (self hexkey) String() string {
|
|||
}
|
||||
|
||||
/*
|
||||
Run(connection, remotePublicKey, sessionToken) is called when the peer connection starts to set up a secure session by performing a crypto handshake.
|
||||
NewSecureSession(connection, privateKey, remotePublicKey, sessionToken, initiator) is called when the peer connection starts to set up a secure session by performing a crypto handshake.
|
||||
|
||||
connection is (a buffered) network connection.
|
||||
|
||||
remotePublicKey is the remote peer's node Id.
|
||||
privateKey is the local client's private key (*ecdsa.PrivateKey)
|
||||
|
||||
remotePublicKey is the remote peer's node Id ([]byte)
|
||||
|
||||
sessionToken is the token from the previous session with this same peer. Nil if no token is found.
|
||||
|
||||
initiator is a boolean flag. True if the node represented by cryptoId is the initiator of the connection (ie., remote is an outbound peer reached by dialing out). False if the connection was established by accepting a call from the remote peer via a listener.
|
||||
initiator is a boolean flag. True if the node is the initiator of the connection (ie., remote is an outbound peer reached by dialing out). False if the connection was established by accepting a call from the remote peer via a listener.
|
||||
|
||||
It returns a secretRW which implements the MsgReadWriter interface.
|
||||
*/
|
||||
|
||||
func (self *cryptoId) Run(conn io.ReadWriter, remotePubKeyS []byte, sessionToken []byte, initiator bool) (token []byte, rw *secretRW, err error) {
|
||||
func NewSecureSession(conn io.ReadWriter, prvKey *ecdsa.PrivateKey, remotePubKeyS []byte, sessionToken []byte, initiator bool) (token []byte, rw *secretRW, err error) {
|
||||
var auth, initNonce, recNonce []byte
|
||||
var read int
|
||||
var randomPrivKey *ecdsa.PrivateKey
|
||||
var remoteRandomPubKey *ecdsa.PublicKey
|
||||
clogger.Debugf("attempting session with %v", hexkey(remotePubKeyS))
|
||||
if initiator {
|
||||
if auth, initNonce, randomPrivKey, _, err = self.startHandshake(remotePubKeyS, sessionToken); err != nil {
|
||||
if auth, initNonce, randomPrivKey, _, err = startHandshake(prvKey, remotePubKeyS, sessionToken); err != nil {
|
||||
return
|
||||
}
|
||||
if sessionToken != nil {
|
||||
|
@ -125,7 +86,7 @@ func (self *cryptoId) Run(conn io.ReadWriter, remotePubKeyS []byte, sessionToken
|
|||
}
|
||||
// write out auth message
|
||||
// wait for response, then call complete
|
||||
if recNonce, remoteRandomPubKey, _, err = self.completeHandshake(response); err != nil {
|
||||
if recNonce, remoteRandomPubKey, _, err = completeHandshake(response, prvKey); err != nil {
|
||||
return
|
||||
}
|
||||
clogger.Debugf("receiver-nonce: %v", hexkey(recNonce))
|
||||
|
@ -147,7 +108,7 @@ func (self *cryptoId) Run(conn io.ReadWriter, remotePubKeyS []byte, sessionToken
|
|||
// Extract info from the authentication. The initiator starts by sending us a handshake that we need to respond to.
|
||||
// so we read auth message first, then respond
|
||||
var response []byte
|
||||
if response, recNonce, initNonce, randomPrivKey, remoteRandomPubKey, err = self.respondToHandshake(auth, remotePubKeyS, sessionToken); err != nil {
|
||||
if response, recNonce, initNonce, randomPrivKey, remoteRandomPubKey, err = respondToHandshake(auth, prvKey, remotePubKeyS, sessionToken); err != nil {
|
||||
return
|
||||
}
|
||||
clogger.Debugf("receiver-nonce: %v", hexkey(recNonce))
|
||||
|
@ -157,7 +118,7 @@ func (self *cryptoId) Run(conn io.ReadWriter, remotePubKeyS []byte, sessionToken
|
|||
}
|
||||
clogger.Debugf("receiver handshake (sent to %v):\n%v", hexkey(remotePubKeyS), hexkey(response))
|
||||
}
|
||||
return self.newSession(initiator, initNonce, recNonce, auth, randomPrivKey, remoteRandomPubKey)
|
||||
return newSession(initiator, initNonce, recNonce, auth, randomPrivKey, remoteRandomPubKey)
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -192,7 +153,7 @@ The caller provides the public key of the peer as conjuctured from lookup based
|
|||
|
||||
The first return value is the auth message that is to be sent out to the remote receiver.
|
||||
*/
|
||||
func (self *cryptoId) startHandshake(remotePubKeyS, sessionToken []byte) (auth []byte, initNonce []byte, randomPrvKey *ecdsa.PrivateKey, remotePubKey *ecdsa.PublicKey, err error) {
|
||||
func startHandshake(prvKey *ecdsa.PrivateKey, remotePubKeyS, sessionToken []byte) (auth []byte, initNonce []byte, randomPrvKey *ecdsa.PrivateKey, remotePubKey *ecdsa.PublicKey, err error) {
|
||||
// session init, common to both parties
|
||||
if remotePubKey, err = ImportPublicKey(remotePubKeyS); err != nil {
|
||||
return
|
||||
|
@ -203,7 +164,7 @@ func (self *cryptoId) startHandshake(remotePubKeyS, sessionToken []byte) (auth [
|
|||
// no session token found means we need to generate shared secret.
|
||||
// ecies shared secret is used as initial session token for new peers
|
||||
// generate shared key from prv and remote pubkey
|
||||
if sessionToken, err = ecies.ImportECDSA(self.prvKey).GenerateShared(ecies.ImportECDSAPublic(remotePubKey), sskLen, sskLen); err != nil {
|
||||
if sessionToken, err = ecies.ImportECDSA(prvKey).GenerateShared(ecies.ImportECDSAPublic(remotePubKey), sskLen, sskLen); err != nil {
|
||||
return
|
||||
}
|
||||
// tokenFlag = 0x00 // redundant
|
||||
|
@ -245,9 +206,13 @@ func (self *cryptoId) startHandshake(remotePubKeyS, sessionToken []byte) (auth [
|
|||
if randomPubKey64, err = ExportPublicKey(&randomPrvKey.PublicKey); err != nil {
|
||||
return
|
||||
}
|
||||
var pubKey64 []byte
|
||||
if pubKey64, err = ExportPublicKey(&prvKey.PublicKey); err != nil {
|
||||
return
|
||||
}
|
||||
copy(msg[sigLen:sigLen+shaLen], crypto.Sha3(randomPubKey64))
|
||||
// pubkey copied to the correct segment.
|
||||
copy(msg[sigLen+shaLen:sigLen+shaLen+pubLen], self.pubKeyS)
|
||||
copy(msg[sigLen+shaLen:sigLen+shaLen+pubLen], pubKey64)
|
||||
// nonce is already in the slice
|
||||
// stick tokenFlag byte to the end
|
||||
msg[msgLen-1] = tokenFlag
|
||||
|
@ -267,7 +232,7 @@ respondToHandshake is called by peer if it accepted (but not initiated) the conn
|
|||
|
||||
The first return value is the authentication response (aka receiver handshake) that is to be sent to the remote initiator.
|
||||
*/
|
||||
func (self *cryptoId) respondToHandshake(auth, remotePubKeyS, sessionToken []byte) (authResp []byte, respNonce []byte, initNonce []byte, randomPrivKey *ecdsa.PrivateKey, remoteRandomPubKey *ecdsa.PublicKey, err error) {
|
||||
func respondToHandshake(auth []byte, prvKey *ecdsa.PrivateKey, remotePubKeyS, sessionToken []byte) (authResp []byte, respNonce []byte, initNonce []byte, randomPrivKey *ecdsa.PrivateKey, remoteRandomPubKey *ecdsa.PublicKey, err error) {
|
||||
var msg []byte
|
||||
var remotePubKey *ecdsa.PublicKey
|
||||
if remotePubKey, err = ImportPublicKey(remotePubKeyS); err != nil {
|
||||
|
@ -276,7 +241,7 @@ func (self *cryptoId) respondToHandshake(auth, remotePubKeyS, sessionToken []byt
|
|||
|
||||
// they prove that msg is meant for me,
|
||||
// I prove I possess private key if i can read it
|
||||
if msg, err = crypto.Decrypt(self.prvKey, auth); err != nil {
|
||||
if msg, err = crypto.Decrypt(prvKey, auth); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -285,7 +250,7 @@ func (self *cryptoId) respondToHandshake(auth, remotePubKeyS, sessionToken []byt
|
|||
// no session token found means we need to generate shared secret.
|
||||
// ecies shared secret is used as initial session token for new peers
|
||||
// generate shared key from prv and remote pubkey
|
||||
if sessionToken, err = ecies.ImportECDSA(self.prvKey).GenerateShared(ecies.ImportECDSAPublic(remotePubKey), sskLen, sskLen); err != nil {
|
||||
if sessionToken, err = ecies.ImportECDSA(prvKey).GenerateShared(ecies.ImportECDSAPublic(remotePubKey), sskLen, sskLen); err != nil {
|
||||
return
|
||||
}
|
||||
// tokenFlag = 0x00 // redundant
|
||||
|
@ -342,11 +307,11 @@ func (self *cryptoId) respondToHandshake(auth, remotePubKeyS, sessionToken []byt
|
|||
/*
|
||||
completeHandshake is called when the initiator receives an authentication response (aka receiver handshake). It completes the handshake by reading off parameters the remote peer provides needed to set up the secure session
|
||||
*/
|
||||
func (self *cryptoId) completeHandshake(auth []byte) (respNonce []byte, remoteRandomPubKey *ecdsa.PublicKey, tokenFlag bool, err error) {
|
||||
func completeHandshake(auth []byte, prvKey *ecdsa.PrivateKey) (respNonce []byte, remoteRandomPubKey *ecdsa.PublicKey, tokenFlag bool, err error) {
|
||||
var msg []byte
|
||||
// they prove that msg is meant for me,
|
||||
// I prove I possess private key if i can read it
|
||||
if msg, err = crypto.Decrypt(self.prvKey, auth); err != nil {
|
||||
if msg, err = crypto.Decrypt(prvKey, auth); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -364,7 +329,7 @@ func (self *cryptoId) completeHandshake(auth []byte) (respNonce []byte, remoteRa
|
|||
/*
|
||||
newSession is called after the handshake is completed. The arguments are values negotiated in the handshake and the return value is a new session : a new session Token to be remembered for the next time we connect with this peer. And a MsgReadWriter that implements an encrypted and authenticated connection with key material obtained from the crypto handshake key exchange
|
||||
*/
|
||||
func (self *cryptoId) newSession(initiator bool, initNonce, respNonce, auth []byte, privKey *ecdsa.PrivateKey, remoteRandomPubKey *ecdsa.PublicKey) (sessionToken []byte, rw *secretRW, err error) {
|
||||
func newSession(initiator bool, initNonce, respNonce, auth []byte, privKey *ecdsa.PrivateKey, remoteRandomPubKey *ecdsa.PublicKey) (sessionToken []byte, rw *secretRW, err error) {
|
||||
// 3) Now we can trust ecdhe-random-pubk to derive new keys
|
||||
//ecdhe-shared-secret = ecdh.agree(ecdhe-random, remote-ecdhe-random-pubk)
|
||||
var dhSharedSecret []byte
|
||||
|
@ -372,16 +337,10 @@ func (self *cryptoId) newSession(initiator bool, initNonce, respNonce, auth []by
|
|||
if dhSharedSecret, err = ecies.ImportECDSA(privKey).GenerateShared(pubKey, sskLen, sskLen); err != nil {
|
||||
return
|
||||
}
|
||||
// shared-secret = crypto.Sha3(ecdhe-shared-secret || crypto.Sha3(nonce || initiator-nonce))
|
||||
var sharedSecret = crypto.Sha3(append(dhSharedSecret, crypto.Sha3(append(respNonce, initNonce...))...))
|
||||
// token = crypto.Sha3(shared-secret)
|
||||
sessionToken = crypto.Sha3(sharedSecret)
|
||||
// aes-secret = crypto.Sha3(ecdhe-shared-secret || shared-secret)
|
||||
var aesSecret = crypto.Sha3(append(dhSharedSecret, sharedSecret...))
|
||||
// # destroy shared-secret
|
||||
// mac-secret = crypto.Sha3(ecdhe-shared-secret || aes-secret)
|
||||
var macSecret = crypto.Sha3(append(dhSharedSecret, aesSecret...))
|
||||
// # destroy ecdhe-shared-secret
|
||||
var egressMac, ingressMac []byte
|
||||
if initiator {
|
||||
egressMac = Xor(macSecret, respNonce)
|
||||
|
|
|
@ -78,40 +78,35 @@ func TestCryptoHandshake(t *testing.T) {
|
|||
prv1, _ := crypto.GenerateKey()
|
||||
pub1 := &prv1.PublicKey
|
||||
|
||||
var initiator, receiver *cryptoId
|
||||
if initiator, err = newCryptoId(&peerId{crypto.FromECDSA(prv0), crypto.FromECDSAPub(pub0)}); err != nil {
|
||||
return
|
||||
}
|
||||
if receiver, err = newCryptoId(&peerId{crypto.FromECDSA(prv1), crypto.FromECDSAPub(pub1)}); err != nil {
|
||||
return
|
||||
}
|
||||
pub0s := crypto.FromECDSAPub(pub0)
|
||||
pub1s := crypto.FromECDSAPub(pub1)
|
||||
|
||||
// simulate handshake by feeding output to input
|
||||
// initiator sends handshake 'auth'
|
||||
auth, initNonce, randomPrivKey, _, err := initiator.startHandshake(receiver.pubKeyS, sessionToken)
|
||||
auth, initNonce, randomPrivKey, _, err := startHandshake(prv0, pub1s, sessionToken)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
|
||||
// receiver reads auth and responds with response
|
||||
response, remoteRecNonce, remoteInitNonce, remoteRandomPrivKey, remoteInitRandomPubKey, err := receiver.respondToHandshake(auth, crypto.FromECDSAPub(pub0), sessionToken)
|
||||
response, remoteRecNonce, remoteInitNonce, remoteRandomPrivKey, remoteInitRandomPubKey, err := respondToHandshake(auth, prv1, pub0s, sessionToken)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
|
||||
// initiator reads receiver's response and the key exchange completes
|
||||
recNonce, remoteRandomPubKey, _, err := initiator.completeHandshake(response)
|
||||
recNonce, remoteRandomPubKey, _, err := completeHandshake(response, prv0)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
|
||||
// now both parties should have the same session parameters
|
||||
initSessionToken, initSecretRW, err := initiator.newSession(true, initNonce, recNonce, auth, randomPrivKey, remoteRandomPubKey)
|
||||
initSessionToken, initSecretRW, err := newSession(true, initNonce, recNonce, auth, randomPrivKey, remoteRandomPubKey)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
|
||||
recSessionToken, recSecretRW, err := receiver.newSession(false, remoteInitNonce, remoteRecNonce, auth, remoteRandomPrivKey, remoteInitRandomPubKey)
|
||||
recSessionToken, recSecretRW, err := newSession(false, remoteInitNonce, remoteRecNonce, auth, remoteRandomPrivKey, remoteInitRandomPubKey)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
|
@ -163,12 +158,12 @@ func TestPeersHandshake(t *testing.T) {
|
|||
initiator := newPeer(conn1, []Protocol{}, nil)
|
||||
receiver := newPeer(conn2, []Protocol{}, nil)
|
||||
initiator.dialAddr = &peerAddr{IP: net.ParseIP("1.2.3.4"), Port: 2222, Pubkey: pub1s[1:]}
|
||||
initiator.ourID = &peerId{prv0s, pub0s}
|
||||
initiator.privateKey = prv0s
|
||||
|
||||
// this is cheating. identity of initiator/dialler not available to listener/receiver
|
||||
// its public key should be looked up based on IP address
|
||||
receiver.identity = initiator.ourID
|
||||
receiver.ourID = &peerId{prv1s, pub1s}
|
||||
receiver.identity = &peerId{nil, pub0s}
|
||||
receiver.privateKey = prv1s
|
||||
|
||||
initiator.pubkeyHook = func(*peerAddr) error { return nil }
|
||||
receiver.pubkeyHook = func(*peerAddr) error { return nil }
|
||||
|
|
27
p2p/peer.go
27
p2p/peer.go
|
@ -3,6 +3,7 @@ package p2p
|
|||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/ecdsa"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"io"
|
||||
|
@ -12,6 +13,8 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/logger"
|
||||
)
|
||||
|
@ -73,6 +76,7 @@ type Peer struct {
|
|||
runBaseProtocol bool // for testing
|
||||
cryptoHandshake bool // for testing
|
||||
cryptoReady chan struct{}
|
||||
privateKey []byte
|
||||
|
||||
runlock sync.RWMutex // protects running
|
||||
running map[string]*proto
|
||||
|
@ -338,6 +342,13 @@ func (p *Peer) dispatch(msg Msg, protoDone chan struct{}) (wait bool, err error)
|
|||
|
||||
type readLoop func(chan<- Msg, chan<- error, <-chan bool)
|
||||
|
||||
func (p *Peer) PrivateKey() (prv *ecdsa.PrivateKey, err error) {
|
||||
if prv = crypto.ToECDSA(p.privateKey); prv == nil {
|
||||
err = fmt.Errorf("invalid private key")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (p *Peer) handleCryptoHandshake() (loop readLoop, err error) {
|
||||
// cryptoId is just created for the lifecycle of the handshake
|
||||
// it is survived by an encrypted readwriter
|
||||
|
@ -350,17 +361,17 @@ func (p *Peer) handleCryptoHandshake() (loop readLoop, err error) {
|
|||
if p.dialAddr != nil { // this should have its own method Outgoing() bool
|
||||
initiator = true
|
||||
}
|
||||
// create crypto layer
|
||||
// this could in principle run only once but maybe we want to allow
|
||||
// identity switching
|
||||
var crypto *cryptoId
|
||||
if crypto, err = newCryptoId(p.ourID); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// run on peer
|
||||
// this bit handles the handshake and creates a secure communications channel with
|
||||
// var rw *secretRW
|
||||
if sessionToken, _, err = crypto.Run(p.conn, p.Pubkey(), sessionToken, initiator); err != nil {
|
||||
var prvKey *ecdsa.PrivateKey
|
||||
if prvKey, err = p.PrivateKey(); err != nil {
|
||||
err = fmt.Errorf("unable to access private key for client: %v", err)
|
||||
return
|
||||
}
|
||||
// initialise a new secure session
|
||||
if sessionToken, _, err = NewSecureSession(p.conn, prvKey, p.Pubkey(), sessionToken, initiator); err != nil {
|
||||
p.Debugf("unable to setup secure session: %v", err)
|
||||
return
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue