From e82d0aa1cf0e9d7d526962649c6e1eb56d8013d2 Mon Sep 17 00:00:00 2001 From: zelig Date: Sun, 18 Jan 2015 07:59:54 +0000 Subject: [PATCH 01/25] initial hook for crypto handshake (void, off by default) --- p2p/peer.go | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/p2p/peer.go b/p2p/peer.go index 2380a3285b8d..886b95a80b16 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -70,6 +70,7 @@ type Peer struct { // These fields maintain the running protocols. protocols []Protocol runBaseProtocol bool // for testing + cryptoHandshake bool // for testing runlock sync.RWMutex // protects running running map[string]*proto @@ -141,6 +142,20 @@ func (p *Peer) Identity() ClientIdentity { return p.identity } +func (self *Peer) Pubkey() (pubkey []byte) { + self.infolock.Lock() + defer self.infolock.Unlock() + switch { + case self.identity != nil: + pubkey = self.identity.Pubkey() + case self.dialAddr != nil: + pubkey = self.dialAddr.Pubkey + case self.listenAddr != nil: + pubkey = self.listenAddr.Pubkey + } + return +} + // Caps returns the capabilities (supported subprotocols) of the remote peer. func (p *Peer) Caps() []Cap { p.infolock.Lock() @@ -207,6 +222,12 @@ func (p *Peer) loop() (reason DiscReason, err error) { defer close(p.closed) defer p.conn.Close() + if p.cryptoHandshake { + if err := p.handleCryptoHandshake(); err != nil { + return DiscProtocolError, err // no graceful disconnect + } + } + // read loop readMsg := make(chan Msg) readErr := make(chan error) @@ -307,6 +328,11 @@ func (p *Peer) dispatch(msg Msg, protoDone chan struct{}) (wait bool, err error) return wait, nil } +func (p *Peer) handleCryptoHandshake() (err error) { + + return nil +} + func (p *Peer) startBaseProtocol() { p.runlock.Lock() defer p.runlock.Unlock() From a325b457af52999b29d750d8ea73f64fe37d07ca Mon Sep 17 00:00:00 2001 From: zelig Date: Sun, 18 Jan 2015 08:03:39 +0000 Subject: [PATCH 02/25] add privkey to clientIdentity + tests --- p2p/client_identity.go | 15 +++++++++++---- p2p/client_identity_test.go | 11 ++++++++++- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/p2p/client_identity.go b/p2p/client_identity.go index f15fd01bfc6d..fca2756bd641 100644 --- a/p2p/client_identity.go +++ b/p2p/client_identity.go @@ -7,8 +7,9 @@ import ( // ClientIdentity represents the identity of a peer. type ClientIdentity interface { - String() string // human readable identity - Pubkey() []byte // 512-bit public key + String() string // human readable identity + Pubkey() []byte // 512-bit public key + PrivKey() []byte // 512-bit private key } type SimpleClientIdentity struct { @@ -17,10 +18,11 @@ type SimpleClientIdentity struct { customIdentifier string os string implementation string + privkey []byte pubkey []byte } -func NewSimpleClientIdentity(clientIdentifier string, version string, customIdentifier string, pubkey []byte) *SimpleClientIdentity { +func NewSimpleClientIdentity(clientIdentifier string, version string, customIdentifier string, privkey []byte, pubkey []byte) *SimpleClientIdentity { clientIdentity := &SimpleClientIdentity{ clientIdentifier: clientIdentifier, version: version, @@ -28,6 +30,7 @@ func NewSimpleClientIdentity(clientIdentifier string, version string, customIden os: runtime.GOOS, implementation: runtime.Version(), pubkey: pubkey, + privkey: privkey, } return clientIdentity @@ -50,8 +53,12 @@ func (c *SimpleClientIdentity) String() string { c.implementation) } +func (c *SimpleClientIdentity) Privkey() []byte { + return c.privkey +} + func (c *SimpleClientIdentity) Pubkey() []byte { - return []byte(c.pubkey) + return c.pubkey } func (c *SimpleClientIdentity) SetCustomIdentifier(customIdentifier string) { diff --git a/p2p/client_identity_test.go b/p2p/client_identity_test.go index 7248a7b1a7e2..61c34fbf1167 100644 --- a/p2p/client_identity_test.go +++ b/p2p/client_identity_test.go @@ -1,13 +1,22 @@ package p2p import ( + "bytes" "fmt" "runtime" "testing" ) func TestClientIdentity(t *testing.T) { - clientIdentity := NewSimpleClientIdentity("Ethereum(G)", "0.5.16", "test", []byte("pubkey")) + clientIdentity := NewSimpleClientIdentity("Ethereum(G)", "0.5.16", "test", []byte("privkey"), []byte("pubkey")) + key := clientIdentity.Privkey() + if !bytes.Equal(key, []byte("privkey")) { + t.Errorf("Expected Privkey to be %x, got %x", key, []byte("privkey")) + } + key = clientIdentity.Pubkey() + if !bytes.Equal(key, []byte("pubkey")) { + t.Errorf("Expected Pubkey to be %x, got %x", key, []byte("pubkey")) + } clientString := clientIdentity.String() expected := fmt.Sprintf("Ethereum(G)/v0.5.16/test/%s/%s", runtime.GOOS, runtime.Version()) if clientString != expected { From ac4e71f0512344d31be19f057da0173bb9729a5c Mon Sep 17 00:00:00 2001 From: zelig Date: Sun, 18 Jan 2015 09:44:49 +0000 Subject: [PATCH 03/25] fix protocol to accomodate privkey --- p2p/protocol.go | 4 ++++ p2p/protocol_test.go | 11 ++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/p2p/protocol.go b/p2p/protocol.go index 1d121a885579..62ada929d70c 100644 --- a/p2p/protocol.go +++ b/p2p/protocol.go @@ -64,6 +64,10 @@ func (h *handshake) Pubkey() []byte { return h.NodeID } +func (h *handshake) PrivKey() []byte { + return nil +} + // Cap is the structure of a peer capability. type Cap struct { Name string diff --git a/p2p/protocol_test.go b/p2p/protocol_test.go index b1d10ac5360f..6804a9d40bf4 100644 --- a/p2p/protocol_test.go +++ b/p2p/protocol_test.go @@ -11,7 +11,7 @@ import ( ) type peerId struct { - pubkey []byte + privKey, pubkey []byte } func (self *peerId) String() string { @@ -27,6 +27,15 @@ func (self *peerId) Pubkey() (pubkey []byte) { return } +func (self *peerId) PrivKey() (privKey []byte) { + privKey = self.privKey + if len(privKey) == 0 { + privKey = crypto.GenerateNewKeyPair().PublicKey + self.privKey = privKey + } + return +} + func newTestPeer() (peer *Peer) { peer = NewPeer(&peerId{}, []Cap{}) peer.pubkeyHook = func(*peerAddr) error { return nil } From b8f0620af0c78a83f6b8b007188b15903a2a55f3 Mon Sep 17 00:00:00 2001 From: zelig Date: Sun, 18 Jan 2015 09:46:08 +0000 Subject: [PATCH 04/25] add crypto auth logic to p2p --- p2p/crypto.go | 174 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 p2p/crypto.go diff --git a/p2p/crypto.go b/p2p/crypto.go new file mode 100644 index 000000000000..9204fa9d0f59 --- /dev/null +++ b/p2p/crypto.go @@ -0,0 +1,174 @@ +package p2p + +import ( + "bytes" + "crypto/ecdsa" + "crypto/rand" + "fmt" + "io" + + "github.com/ethereum/go-ethereum/crypto" + "github.com/obscuren/ecies" + "github.com/obscuren/secp256k1-go" +) + +var ( + skLen int = 32 // ecies.MaxSharedKeyLength(pubKey) / 2 + sigLen int = 32 // elliptic S256 + pubKeyLen int = 32 // ECDSA + msgLen int = sigLen + 1 + pubKeyLen + skLen // 97 +) + +//, aesSecret, macSecret, egressMac, ingress +type secretRW struct { + aesSecret, macSecret, egressMac, ingressMac []byte +} + +type cryptoId struct { + prvKey *ecdsa.PrivateKey + pubKey *ecdsa.PublicKey + pubKeyR io.ReaderAt +} + +func newCryptoId(id ClientIdentity) (self *cryptoId, err error) { + // will be at server init + var prvKeyDER []byte = id.PrivKey() + if prvKeyDER == nil { + err = fmt.Errorf("no private key for client") + return + } + // initialise ecies private key via importing DER encoded keys (known via our own clientIdentity) + var prvKey = crypto.ToECDSA(prvKeyDER) + 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.pubKeyR = bytes.NewReader(id.Pubkey()) + return +} + +// +func (self *cryptoId) setupAuth(remotePubKeyDER, sessionToken []byte) (auth []byte, nonce []byte, sharedKnowledge []byte, err error) { + // session init, common to both parties + var remotePubKey = crypto.ToECDSAPub(remotePubKeyDER) + if remotePubKey == nil { + err = fmt.Errorf("invalid remote public key") + return + } + var sharedSecret []byte + // generate shared key from prv and remote pubkey + sharedSecret, err = ecies.ImportECDSA(self.prvKey).GenerateShared(ecies.ImportECDSAPublic(remotePubKey), skLen, skLen) + if err != nil { + return + } + // check previous session token + if sessionToken == nil { + err = fmt.Errorf("no session token for peer") + return + } + // allocate msgLen long message + var msg []byte = make([]byte, msgLen) + // generate skLen long nonce at the end + nonce = msg[msgLen-skLen:] + if _, err = rand.Read(nonce); err != nil { + return + } + // create known message + // should use + // cipher.xorBytes from crypto/cipher/xor.go for fast xor + sharedKnowledge = Xor(sharedSecret, sessionToken) + var signedMsg = Xor(sharedKnowledge, nonce) + + // generate random keypair to use for signing + var ecdsaRandomPrvKey *ecdsa.PrivateKey + if ecdsaRandomPrvKey, err = crypto.GenerateKey(); err != nil { + return + } + // var ecdsaRandomPubKey *ecdsa.PublicKey + // ecdsaRandomPubKey= &ecdsaRandomPrvKey.PublicKey + + // message known to both parties ecdh-shared-secret^nonce^token + var signature []byte + // signature = sign(ecdhe-random, ecdh-shared-secret^nonce^token) + // uses secp256k1.Sign + if signature, err = crypto.Sign(signedMsg, ecdsaRandomPrvKey); err != nil { + return + } + // msg = signature || 0x80 || pubk || nonce + copy(msg, signature) + msg[sigLen] = 0x80 + self.pubKeyR.ReadAt(msg[sigLen+1:], int64(pubKeyLen)) // gives pubKeyLen, io.EOF (since we dont read onto the nonce) + + // auth = eciesEncrypt(remote-pubk, msg) + if auth, err = crypto.Encrypt(remotePubKey, msg); err != nil { + return + } + return +} + +func (self *cryptoId) verifyAuth(auth, nonce, sharedKnowledge []byte) (sessionToken []byte, rw *secretRW, 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 { + return + } + + var remoteNonce []byte = msg[msgLen-skLen:] + // I prove that i possess prv key (to derive shared secret, and read nonce off encrypted msg) and that I posessed the earlier one , our shared history + // they prove they possess their private key to derive the same shared secret, plus the same shared history (previous session token) + var signedMsg = Xor(sharedKnowledge, remoteNonce) + var remoteRandomPubKeyDER []byte + if remoteRandomPubKeyDER, err = secp256k1.RecoverPubkey(signedMsg, msg[:32]); err != nil { + return + } + var remoteRandomPubKey = crypto.ToECDSAPub(remoteRandomPubKeyDER) + if remoteRandomPubKey == nil { + err = fmt.Errorf("invalid remote public key") + return + } + // 3) Now we can trust ecdhe-random-pubk to derive keys + //ecdhe-shared-secret = ecdh.agree(ecdhe-random, remote-ecdhe-random-pubk) + var dhSharedSecret []byte + dhSharedSecret, err = ecies.ImportECDSA(self.prvKey).GenerateShared(ecies.ImportECDSAPublic(remoteRandomPubKey), skLen, skLen) + if err != nil { + return + } + // shared-secret = crypto.Sha3(ecdhe-shared-secret || crypto.Sha3(nonce || initiator-nonce)) + var sharedSecret []byte = crypto.Sha3(append(dhSharedSecret, crypto.Sha3(append(nonce, remoteNonce...))...)) + // 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 + // egress-mac = crypto.Sha3(mac-secret^nonce || auth) + var egressMac = crypto.Sha3(append(Xor(macSecret, nonce), auth...)) + // # destroy nonce + // ingress-mac = crypto.Sha3(mac-secret^initiator-nonce || auth), + var ingressMac = crypto.Sha3(append(Xor(macSecret, remoteNonce), auth...)) + // # destroy remote-nonce + rw = &secretRW{ + aesSecret: aesSecret, + macSecret: macSecret, + egressMac: egressMac, + ingressMac: ingressMac, + } + return +} + +func Xor(one, other []byte) (xor []byte) { + for i := 0; i < len(one); i++ { + xor[i] = one[i] ^ other[i] + } + return +} From e2e0849cacee60e2911409c9cd51ab41ff8d8ac2 Mon Sep 17 00:00:00 2001 From: zelig Date: Sun, 18 Jan 2015 23:53:45 +0000 Subject: [PATCH 05/25] rewrite to comply with latest spec - correct sizes for the blocks : sec signature 65, ecies sklen 16, keylength 32 - added allocation to Xor (should be optimized later) - no pubkey reader needed, just do with copy - restructuring now into INITIATE, RESPOND, COMPLETE -> newSession initialises the encryption/authentication layer - crypto identity can be part of client identity, some initialisation when server created --- p2p/crypto.go | 191 ++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 138 insertions(+), 53 deletions(-) diff --git a/p2p/crypto.go b/p2p/crypto.go index 9204fa9d0f59..6e3f360d925d 100644 --- a/p2p/crypto.go +++ b/p2p/crypto.go @@ -1,11 +1,11 @@ package p2p import ( - "bytes" + // "bytes" "crypto/ecdsa" "crypto/rand" "fmt" - "io" + // "io" "github.com/ethereum/go-ethereum/crypto" "github.com/obscuren/ecies" @@ -13,21 +13,22 @@ import ( ) var ( - skLen int = 32 // ecies.MaxSharedKeyLength(pubKey) / 2 - sigLen int = 32 // elliptic S256 - pubKeyLen int = 32 // ECDSA - msgLen int = sigLen + 1 + pubKeyLen + skLen // 97 + sskLen int = 16 // ecies.MaxSharedKeyLength(pubKey) / 2 + sigLen int = 65 // elliptic S256 + keyLen int = 32 // ECDSA + msgLen int = sigLen + 3*keyLen + 1 // 162 + resLen int = 65 ) -//, aesSecret, macSecret, egressMac, ingress +// aesSecret, macSecret, egressMac, ingress type secretRW struct { aesSecret, macSecret, egressMac, ingressMac []byte } type cryptoId struct { - prvKey *ecdsa.PrivateKey - pubKey *ecdsa.PublicKey - pubKeyR io.ReaderAt + prvKey *ecdsa.PrivateKey + pubKey *ecdsa.PublicKey + pubKeyDER []byte } func newCryptoId(id ClientIdentity) (self *cryptoId, err error) { @@ -50,99 +51,181 @@ func newCryptoId(id ClientIdentity) (self *cryptoId, err error) { // to be created at server init shared between peers and sessions // for reuse, call wth ReadAt, no reset seek needed } - self.pubKeyR = bytes.NewReader(id.Pubkey()) + self.pubKeyDER = id.Pubkey() return } -// -func (self *cryptoId) setupAuth(remotePubKeyDER, sessionToken []byte) (auth []byte, nonce []byte, sharedKnowledge []byte, err error) { +// initAuth is called by peer if it initiated the connection +func (self *cryptoId) initAuth(remotePubKeyDER, sessionToken []byte) (auth []byte, initNonce []byte, remotePubKey *ecdsa.PublicKey, err error) { // session init, common to both parties - var remotePubKey = crypto.ToECDSAPub(remotePubKeyDER) + remotePubKey = crypto.ToECDSAPub(remotePubKeyDER) if remotePubKey == nil { err = fmt.Errorf("invalid remote public key") return } - var sharedSecret []byte - // generate shared key from prv and remote pubkey - sharedSecret, err = ecies.ImportECDSA(self.prvKey).GenerateShared(ecies.ImportECDSAPublic(remotePubKey), skLen, skLen) - if err != nil { - return - } - // check previous session token + + var tokenFlag byte if sessionToken == nil { - err = fmt.Errorf("no session token for peer") - return + // 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 { + return + } + fmt.Printf("secret generated: %v %x", len(sessionToken), sessionToken) + // tokenFlag = 0x00 // redundant + } else { + // for known peers, we use stored token from the previous session + tokenFlag = 0x01 } - // allocate msgLen long message + + //E(remote-pubk, S(ecdhe-random, ecdh-shared-secret^nonce) || H(ecdhe-random-pubk) || pubk || nonce || 0x0) + // E(remote-pubk, S(ecdhe-random, token^nonce) || H(ecdhe-random-pubk) || pubk || nonce || 0x1) + // allocate msgLen long message, var msg []byte = make([]byte, msgLen) - // generate skLen long nonce at the end - nonce = msg[msgLen-skLen:] - if _, err = rand.Read(nonce); err != nil { + // generate sskLen long nonce + initNonce = msg[msgLen-keyLen-1 : msgLen-1] + // nonce = msg[msgLen-sskLen-1 : msgLen-1] + if _, err = rand.Read(initNonce); err != nil { return } // create known message - // should use - // cipher.xorBytes from crypto/cipher/xor.go for fast xor - sharedKnowledge = Xor(sharedSecret, sessionToken) - var signedMsg = Xor(sharedKnowledge, nonce) + // ecdh-shared-secret^nonce for new peers + // token^nonce for old peers + var sharedSecret = Xor(sessionToken, initNonce) // generate random keypair to use for signing var ecdsaRandomPrvKey *ecdsa.PrivateKey if ecdsaRandomPrvKey, err = crypto.GenerateKey(); err != nil { return } - // var ecdsaRandomPubKey *ecdsa.PublicKey - // ecdsaRandomPubKey= &ecdsaRandomPrvKey.PublicKey - - // message known to both parties ecdh-shared-secret^nonce^token + // sign shared secret (message known to both parties): shared-secret var signature []byte - // signature = sign(ecdhe-random, ecdh-shared-secret^nonce^token) + // signature = sign(ecdhe-random, shared-secret) // uses secp256k1.Sign - if signature, err = crypto.Sign(signedMsg, ecdsaRandomPrvKey); err != nil { + if signature, err = crypto.Sign(sharedSecret, ecdsaRandomPrvKey); err != nil { return } - // msg = signature || 0x80 || pubk || nonce - copy(msg, signature) - msg[sigLen] = 0x80 - self.pubKeyR.ReadAt(msg[sigLen+1:], int64(pubKeyLen)) // gives pubKeyLen, io.EOF (since we dont read onto the nonce) + fmt.Printf("signature generated: %v %x", len(signature), signature) + // message + // signed-shared-secret || H(ecdhe-random-pubk) || pubk || nonce || 0x0 + copy(msg, signature) // copy signed-shared-secret + // H(ecdhe-random-pubk) + copy(msg[sigLen:sigLen+keyLen], crypto.Sha3(crypto.FromECDSAPub(&ecdsaRandomPrvKey.PublicKey))) + // pubkey copied to the correct segment. + copy(msg[sigLen+keyLen:sigLen+2*keyLen], self.pubKeyDER) + // nonce is already in the slice + // stick tokenFlag byte to the end + msg[msgLen-1] = tokenFlag + + fmt.Printf("plaintext message generated: %v %x", len(msg), msg) + + // encrypt using remote-pubk // auth = eciesEncrypt(remote-pubk, msg) + if auth, err = crypto.Encrypt(remotePubKey, msg); err != nil { return } + fmt.Printf("encrypted message generated: %v %x\n used pubkey: %x\n", len(auth), auth, crypto.FromECDSAPub(remotePubKey)) + return } -func (self *cryptoId) verifyAuth(auth, nonce, sharedKnowledge []byte) (sessionToken []byte, rw *secretRW, err error) { +// verifyAuth is called by peer if it accepted (but not initiated) the connection +func (self *cryptoId) verifyAuth(auth, sharedSecret []byte, remotePubKey *ecdsa.PublicKey) (authResp []byte, respNonce []byte, initNonce []byte, remoteRandomPubKey *ecdsa.PublicKey, err error) { var msg []byte + fmt.Printf("encrypted message received: %v %x\n used pubkey: %x\n", len(auth), auth, crypto.FromECDSAPub(self.pubKey)) // 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 { return } - var remoteNonce []byte = msg[msgLen-skLen:] - // I prove that i possess prv key (to derive shared secret, and read nonce off encrypted msg) and that I posessed the earlier one , our shared history - // they prove they possess their private key to derive the same shared secret, plus the same shared history (previous session token) - var signedMsg = Xor(sharedKnowledge, remoteNonce) + // var remoteNonce []byte = msg[msgLen-skLen-1 : msgLen-1] + initNonce = msg[msgLen-keyLen-1 : msgLen-1] + // I prove that i own prv key (to derive shared secret, and read nonce off encrypted msg) and that I own shared secret + // they prove they own the private key belonging to ecdhe-random-pubk + var signedMsg = Xor(sharedSecret, initNonce) var remoteRandomPubKeyDER []byte - if remoteRandomPubKeyDER, err = secp256k1.RecoverPubkey(signedMsg, msg[:32]); err != nil { + if remoteRandomPubKeyDER, err = secp256k1.RecoverPubkey(signedMsg, msg[:sigLen]); err != nil { return } - var remoteRandomPubKey = crypto.ToECDSAPub(remoteRandomPubKeyDER) + remoteRandomPubKey = crypto.ToECDSAPub(remoteRandomPubKeyDER) if remoteRandomPubKey == nil { err = fmt.Errorf("invalid remote public key") return } - // 3) Now we can trust ecdhe-random-pubk to derive keys + + var resp = make([]byte, 2*keyLen+1) + // generate sskLen long nonce + respNonce = msg[msgLen-keyLen-1 : msgLen-1] + if _, err = rand.Read(respNonce); err != nil { + return + } + // generate random keypair + var ecdsaRandomPrvKey *ecdsa.PrivateKey + if ecdsaRandomPrvKey, err = crypto.GenerateKey(); err != nil { + return + } + // var ecdsaRandomPubKey *ecdsa.PublicKey + // ecdsaRandomPubKey= &ecdsaRandomPrvKey.PublicKey + + // message + // E(remote-pubk, ecdhe-random-pubk || nonce || 0x0) + copy(resp[:keyLen], crypto.FromECDSAPub(&ecdsaRandomPrvKey.PublicKey)) + // pubkey copied to the correct segment. + copy(resp[keyLen:2*keyLen], self.pubKeyDER) + // nonce is already in the slice + // stick tokenFlag byte to the end + var tokenFlag byte + if sharedSecret == nil { + } else { + // for known peers, we use stored token from the previous session + tokenFlag = 0x01 + } + resp[resLen] = tokenFlag + + // encrypt using remote-pubk + // auth = eciesEncrypt(remote-pubk, msg) + // why not encrypt with ecdhe-random-remote + if authResp, err = crypto.Encrypt(remotePubKey, resp); err != nil { + return + } + return +} + +func (self *cryptoId) verifyAuthResp(auth []byte) (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 { + return + } + + respNonce = msg[resLen-keyLen-1 : resLen-1] + var remoteRandomPubKeyDER = msg[:keyLen] + remoteRandomPubKey = crypto.ToECDSAPub(remoteRandomPubKeyDER) + if remoteRandomPubKey == nil { + err = fmt.Errorf("invalid ecdh random remote public key") + return + } + if msg[resLen-1] == 0x01 { + tokenFlag = true + } + return +} + +func (self *cryptoId) newSession(initNonce, respNonce, auth []byte, 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 - dhSharedSecret, err = ecies.ImportECDSA(self.prvKey).GenerateShared(ecies.ImportECDSAPublic(remoteRandomPubKey), skLen, skLen) + dhSharedSecret, err = ecies.ImportECDSA(self.prvKey).GenerateShared(ecies.ImportECDSAPublic(remoteRandomPubKey), sskLen, sskLen) if err != nil { return } // shared-secret = crypto.Sha3(ecdhe-shared-secret || crypto.Sha3(nonce || initiator-nonce)) - var sharedSecret []byte = crypto.Sha3(append(dhSharedSecret, crypto.Sha3(append(nonce, remoteNonce...))...)) + 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) @@ -152,10 +235,10 @@ func (self *cryptoId) verifyAuth(auth, nonce, sharedKnowledge []byte) (sessionTo var macSecret = crypto.Sha3(append(dhSharedSecret, aesSecret...)) // # destroy ecdhe-shared-secret // egress-mac = crypto.Sha3(mac-secret^nonce || auth) - var egressMac = crypto.Sha3(append(Xor(macSecret, nonce), auth...)) + var egressMac = crypto.Sha3(append(Xor(macSecret, respNonce), auth...)) // # destroy nonce // ingress-mac = crypto.Sha3(mac-secret^initiator-nonce || auth), - var ingressMac = crypto.Sha3(append(Xor(macSecret, remoteNonce), auth...)) + var ingressMac = crypto.Sha3(append(Xor(macSecret, initNonce), auth...)) // # destroy remote-nonce rw = &secretRW{ aesSecret: aesSecret, @@ -166,7 +249,9 @@ func (self *cryptoId) verifyAuth(auth, nonce, sharedKnowledge []byte) (sessionTo return } +// should use cipher.xorBytes from crypto/cipher/xor.go for fast xor func Xor(one, other []byte) (xor []byte) { + xor = make([]byte, len(one)) for i := 0; i < len(one); i++ { xor[i] = one[i] ^ other[i] } From 248ab7185a05bceaadc3343f16cc052a320d359e Mon Sep 17 00:00:00 2001 From: zelig Date: Mon, 19 Jan 2015 00:55:24 +0000 Subject: [PATCH 06/25] fix crash - add session token check and fallback to shared secret in responder call too - use explicit length for the types of new messages - fix typo resp[resLen-1] = tokenFlag --- p2p/crypto.go | 51 +++++++++++++++++++++++++++++---------------------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/p2p/crypto.go b/p2p/crypto.go index 6e3f360d925d..643bd431e4bb 100644 --- a/p2p/crypto.go +++ b/p2p/crypto.go @@ -17,7 +17,7 @@ var ( sigLen int = 65 // elliptic S256 keyLen int = 32 // ECDSA msgLen int = sigLen + 3*keyLen + 1 // 162 - resLen int = 65 + resLen int = 65 // ) // aesSecret, macSecret, egressMac, ingress @@ -133,7 +133,7 @@ func (self *cryptoId) initAuth(remotePubKeyDER, sessionToken []byte) (auth []byt } // verifyAuth is called by peer if it accepted (but not initiated) the connection -func (self *cryptoId) verifyAuth(auth, sharedSecret []byte, remotePubKey *ecdsa.PublicKey) (authResp []byte, respNonce []byte, initNonce []byte, remoteRandomPubKey *ecdsa.PublicKey, err error) { +func (self *cryptoId) verifyAuth(auth, sessionToken []byte, remotePubKey *ecdsa.PublicKey) (authResp []byte, respNonce []byte, initNonce []byte, remoteRandomPubKey *ecdsa.PublicKey, err error) { var msg []byte fmt.Printf("encrypted message received: %v %x\n used pubkey: %x\n", len(auth), auth, crypto.FromECDSAPub(self.pubKey)) // they prove that msg is meant for me, @@ -141,50 +141,57 @@ func (self *cryptoId) verifyAuth(auth, sharedSecret []byte, remotePubKey *ecdsa. if msg, err = crypto.Decrypt(self.prvKey, auth); err != nil { return } + fmt.Printf("\nplaintext message retrieved: %v %x\n", len(msg), msg) - // var remoteNonce []byte = msg[msgLen-skLen-1 : msgLen-1] + var tokenFlag byte + if sessionToken == nil { + // 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 { + return + } + fmt.Printf("secret generated: %v %x", len(sessionToken), sessionToken) + // tokenFlag = 0x00 // redundant + } else { + // for known peers, we use stored token from the previous session + tokenFlag = 0x01 + } + + // the initiator nonce is read off the end of the message initNonce = msg[msgLen-keyLen-1 : msgLen-1] // I prove that i own prv key (to derive shared secret, and read nonce off encrypted msg) and that I own shared secret // they prove they own the private key belonging to ecdhe-random-pubk - var signedMsg = Xor(sharedSecret, initNonce) + // we can now reconstruct the signed message and recover the peers pubkey + var signedMsg = Xor(sessionToken, initNonce) var remoteRandomPubKeyDER []byte if remoteRandomPubKeyDER, err = secp256k1.RecoverPubkey(signedMsg, msg[:sigLen]); err != nil { return } + // convert to ECDSA standard remoteRandomPubKey = crypto.ToECDSAPub(remoteRandomPubKeyDER) if remoteRandomPubKey == nil { err = fmt.Errorf("invalid remote public key") return } - var resp = make([]byte, 2*keyLen+1) - // generate sskLen long nonce - respNonce = msg[msgLen-keyLen-1 : msgLen-1] + // now we find ourselves a long task too, fill it random + var resp = make([]byte, resLen) + // generate keyLen long nonce + respNonce = msg[resLen-keyLen-1 : msgLen-1] if _, err = rand.Read(respNonce); err != nil { return } - // generate random keypair + // generate random keypair for session var ecdsaRandomPrvKey *ecdsa.PrivateKey if ecdsaRandomPrvKey, err = crypto.GenerateKey(); err != nil { return } - // var ecdsaRandomPubKey *ecdsa.PublicKey - // ecdsaRandomPubKey= &ecdsaRandomPrvKey.PublicKey - - // message + // responder auth message // E(remote-pubk, ecdhe-random-pubk || nonce || 0x0) copy(resp[:keyLen], crypto.FromECDSAPub(&ecdsaRandomPrvKey.PublicKey)) - // pubkey copied to the correct segment. - copy(resp[keyLen:2*keyLen], self.pubKeyDER) // nonce is already in the slice - // stick tokenFlag byte to the end - var tokenFlag byte - if sharedSecret == nil { - } else { - // for known peers, we use stored token from the previous session - tokenFlag = 0x01 - } - resp[resLen] = tokenFlag + resp[resLen-1] = tokenFlag // encrypt using remote-pubk // auth = eciesEncrypt(remote-pubk, msg) From 605ee4d2e8194e0471ed6d1cf400789c116ceae4 Mon Sep 17 00:00:00 2001 From: zelig Date: Mon, 19 Jan 2015 01:24:09 +0000 Subject: [PATCH 07/25] handshake test to crypto --- p2p/crypto.go | 2 -- p2p/crypto_test.go | 54 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 p2p/crypto_test.go diff --git a/p2p/crypto.go b/p2p/crypto.go index 643bd431e4bb..10c82d3a11a2 100644 --- a/p2p/crypto.go +++ b/p2p/crypto.go @@ -1,11 +1,9 @@ package p2p import ( - // "bytes" "crypto/ecdsa" "crypto/rand" "fmt" - // "io" "github.com/ethereum/go-ethereum/crypto" "github.com/obscuren/ecies" diff --git a/p2p/crypto_test.go b/p2p/crypto_test.go new file mode 100644 index 000000000000..6b4afb16a456 --- /dev/null +++ b/p2p/crypto_test.go @@ -0,0 +1,54 @@ +package p2p + +import ( + // "bytes" + "fmt" + "testing" + + "github.com/ethereum/go-ethereum/crypto" +) + +func TestCryptoHandshake(t *testing.T) { + var err error + var sessionToken []byte + prvInit, _ := crypto.GenerateKey() + pubInit := &prvInit.PublicKey + prvResp, _ := crypto.GenerateKey() + pubResp := &prvResp.PublicKey + + var initiator, responder *cryptoId + if initiator, err = newCryptoId(&peerId{crypto.FromECDSA(prvInit), crypto.FromECDSAPub(pubInit)}); err != nil { + return + } + if responder, err = newCryptoId(&peerId{crypto.FromECDSA(prvResp), crypto.FromECDSAPub(pubResp)}); err != nil { + return + } + + auth, initNonce, _, _ := initiator.initAuth(responder.pubKeyDER, sessionToken) + + response, remoteRespNonce, remoteInitNonce, remoteRandomPubKey, _ := responder.verifyAuth(auth, sessionToken, pubInit) + + respNonce, randomPubKey, _, _ := initiator.verifyAuthResp(response) + + fmt.Printf("%x\n%x\n%x\n%x\n%x\n%x\n%x\n%x\n", auth, initNonce, response, remoteRespNonce, remoteInitNonce, remoteRandomPubKey, respNonce, randomPubKey) + // initSessionToken, initSecretRW, _ := initiator.newSession(initNonce, respNonce, auth, randomPubKey) + // respSessionToken, respSecretRW, _ := responder.newSession(remoteInitNonce, remoteRespNonce, auth, remoteRandomPubKey) + + // if !bytes.Equal(initSessionToken, respSessionToken) { + // t.Errorf("session tokens do not match") + // } + // // aesSecret, macSecret, egressMac, ingressMac + // if !bytes.Equal(initSecretRW.aesSecret, respSecretRW.aesSecret) { + // t.Errorf("AES secrets do not match") + // } + // if !bytes.Equal(initSecretRW.macSecret, respSecretRW.macSecret) { + // t.Errorf("macSecrets do not match") + // } + // if !bytes.Equal(initSecretRW.egressMac, respSecretRW.egressMac) { + // t.Errorf("egressMacs do not match") + // } + // if !bytes.Equal(initSecretRW.ingressMac, respSecretRW.ingressMac) { + // t.Errorf("ingressMacs do not match") + // } + +} From a314bbffaba05862ed7f6e60a9010ab75481b9b0 Mon Sep 17 00:00:00 2001 From: zelig Date: Mon, 19 Jan 2015 01:24:28 +0000 Subject: [PATCH 08/25] handshake test to crypto --- p2p/crypto_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/p2p/crypto_test.go b/p2p/crypto_test.go index 6b4afb16a456..1785b5c458a7 100644 --- a/p2p/crypto_test.go +++ b/p2p/crypto_test.go @@ -31,7 +31,7 @@ func TestCryptoHandshake(t *testing.T) { respNonce, randomPubKey, _, _ := initiator.verifyAuthResp(response) fmt.Printf("%x\n%x\n%x\n%x\n%x\n%x\n%x\n%x\n", auth, initNonce, response, remoteRespNonce, remoteInitNonce, remoteRandomPubKey, respNonce, randomPubKey) - // initSessionToken, initSecretRW, _ := initiator.newSession(initNonce, respNonce, auth, randomPubKey) + initSessionToken, initSecretRW, _ := initiator.newSession(initNonce, respNonce, auth, randomPubKey) // respSessionToken, respSecretRW, _ := responder.newSession(remoteInitNonce, remoteRespNonce, auth, remoteRandomPubKey) // if !bytes.Equal(initSessionToken, respSessionToken) { From b5734f3cce8779edbb410578868b4cd44b333d4e Mon Sep 17 00:00:00 2001 From: zelig Date: Mon, 19 Jan 2015 04:53:48 +0000 Subject: [PATCH 09/25] completed the test. FAIL now. it crashes at diffie-hellman. ECIES -> secp256k1-go panics --- p2p/crypto.go | 45 +++++++++++++++++++++++-------------- p2p/crypto_test.go | 55 +++++++++++++++++++++++----------------------- 2 files changed, 57 insertions(+), 43 deletions(-) diff --git a/p2p/crypto.go b/p2p/crypto.go index 10c82d3a11a2..37c6e1fc98f9 100644 --- a/p2p/crypto.go +++ b/p2p/crypto.go @@ -53,10 +53,24 @@ func newCryptoId(id ClientIdentity) (self *cryptoId, err error) { return } -// initAuth is called by peer if it initiated the connection -func (self *cryptoId) initAuth(remotePubKeyDER, sessionToken []byte) (auth []byte, initNonce []byte, remotePubKey *ecdsa.PublicKey, err error) { +/* startHandshake is called by peer if it initiated the connection. + By protocol spec, the party who initiates the connection (initiator) will send an 'auth' packet +New: authInitiator -> E(remote-pubk, S(ecdhe-random, ecdh-shared-secret^nonce) || H(ecdhe-random-pubk) || pubk || nonce || 0x0) + authRecipient -> E(remote-pubk, ecdhe-random-pubk || nonce || 0x0) + +Known: authInitiator = E(remote-pubk, S(ecdhe-random, token^nonce) || H(ecdhe-random-pubk) || pubk || nonce || 0x1) + authRecipient = E(remote-pubk, ecdhe-random-pubk || nonce || 0x1) // token found + authRecipient = E(remote-pubk, ecdhe-random-pubk || nonce || 0x0) // token not found + +The caller provides the public key of the peer as conjuctured from lookup based on IP:port, given as user input or proven by signatures. The caller must have access to persistant information about the peers, and pass the previous session token as an argument to cryptoId. + +The handshake is the process by which the peers establish their connection for a session. + +*/ + +func (self *cryptoId) startHandshake(remotePubKeyDER, sessionToken []byte) (auth []byte, initNonce []byte, randomPrvKey *ecdsa.PrivateKey, randomPubKey *ecdsa.PublicKey, err error) { // session init, common to both parties - remotePubKey = crypto.ToECDSAPub(remotePubKeyDER) + remotePubKey := crypto.ToECDSAPub(remotePubKeyDER) if remotePubKey == nil { err = fmt.Errorf("invalid remote public key") return @@ -70,6 +84,7 @@ func (self *cryptoId) initAuth(remotePubKeyDER, sessionToken []byte) (auth []byt if sessionToken, err = ecies.ImportECDSA(self.prvKey).GenerateShared(ecies.ImportECDSAPublic(remotePubKey), sskLen, sskLen); err != nil { return } + // this will not stay here ;) fmt.Printf("secret generated: %v %x", len(sessionToken), sessionToken) // tokenFlag = 0x00 // redundant } else { @@ -93,15 +108,14 @@ func (self *cryptoId) initAuth(remotePubKeyDER, sessionToken []byte) (auth []byt var sharedSecret = Xor(sessionToken, initNonce) // generate random keypair to use for signing - var ecdsaRandomPrvKey *ecdsa.PrivateKey - if ecdsaRandomPrvKey, err = crypto.GenerateKey(); err != nil { + if randomPrvKey, err = crypto.GenerateKey(); err != nil { return } // sign shared secret (message known to both parties): shared-secret var signature []byte // signature = sign(ecdhe-random, shared-secret) // uses secp256k1.Sign - if signature, err = crypto.Sign(sharedSecret, ecdsaRandomPrvKey); err != nil { + if signature, err = crypto.Sign(sharedSecret, randomPrvKey); err != nil { return } fmt.Printf("signature generated: %v %x", len(signature), signature) @@ -110,7 +124,7 @@ func (self *cryptoId) initAuth(remotePubKeyDER, sessionToken []byte) (auth []byt // signed-shared-secret || H(ecdhe-random-pubk) || pubk || nonce || 0x0 copy(msg, signature) // copy signed-shared-secret // H(ecdhe-random-pubk) - copy(msg[sigLen:sigLen+keyLen], crypto.Sha3(crypto.FromECDSAPub(&ecdsaRandomPrvKey.PublicKey))) + copy(msg[sigLen:sigLen+keyLen], crypto.Sha3(crypto.FromECDSAPub(&randomPrvKey.PublicKey))) // pubkey copied to the correct segment. copy(msg[sigLen+keyLen:sigLen+2*keyLen], self.pubKeyDER) // nonce is already in the slice @@ -131,7 +145,7 @@ func (self *cryptoId) initAuth(remotePubKeyDER, sessionToken []byte) (auth []byt } // verifyAuth is called by peer if it accepted (but not initiated) the connection -func (self *cryptoId) verifyAuth(auth, sessionToken []byte, remotePubKey *ecdsa.PublicKey) (authResp []byte, respNonce []byte, initNonce []byte, remoteRandomPubKey *ecdsa.PublicKey, err error) { +func (self *cryptoId) respondToHandshake(auth, sessionToken []byte, remotePubKey *ecdsa.PublicKey) (authResp []byte, respNonce []byte, initNonce []byte, randomPrvKey *ecdsa.PrivateKey, err error) { var msg []byte fmt.Printf("encrypted message received: %v %x\n used pubkey: %x\n", len(auth), auth, crypto.FromECDSAPub(self.pubKey)) // they prove that msg is meant for me, @@ -167,7 +181,7 @@ func (self *cryptoId) verifyAuth(auth, sessionToken []byte, remotePubKey *ecdsa. return } // convert to ECDSA standard - remoteRandomPubKey = crypto.ToECDSAPub(remoteRandomPubKeyDER) + remoteRandomPubKey := crypto.ToECDSAPub(remoteRandomPubKeyDER) if remoteRandomPubKey == nil { err = fmt.Errorf("invalid remote public key") return @@ -181,13 +195,12 @@ func (self *cryptoId) verifyAuth(auth, sessionToken []byte, remotePubKey *ecdsa. return } // generate random keypair for session - var ecdsaRandomPrvKey *ecdsa.PrivateKey - if ecdsaRandomPrvKey, err = crypto.GenerateKey(); err != nil { + if randomPrvKey, err = crypto.GenerateKey(); err != nil { return } // responder auth message // E(remote-pubk, ecdhe-random-pubk || nonce || 0x0) - copy(resp[:keyLen], crypto.FromECDSAPub(&ecdsaRandomPrvKey.PublicKey)) + copy(resp[:keyLen], crypto.FromECDSAPub(&randomPrvKey.PublicKey)) // nonce is already in the slice resp[resLen-1] = tokenFlag @@ -200,7 +213,7 @@ func (self *cryptoId) verifyAuth(auth, sessionToken []byte, remotePubKey *ecdsa. return } -func (self *cryptoId) verifyAuthResp(auth []byte) (respNonce []byte, remoteRandomPubKey *ecdsa.PublicKey, tokenFlag bool, err error) { +func (self *cryptoId) completeHandshake(auth []byte) (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 @@ -221,12 +234,12 @@ func (self *cryptoId) verifyAuthResp(auth []byte) (respNonce []byte, remoteRando return } -func (self *cryptoId) newSession(initNonce, respNonce, auth []byte, remoteRandomPubKey *ecdsa.PublicKey) (sessionToken []byte, rw *secretRW, err error) { +func (self *cryptoId) newSession(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 - dhSharedSecret, err = ecies.ImportECDSA(self.prvKey).GenerateShared(ecies.ImportECDSAPublic(remoteRandomPubKey), sskLen, sskLen) - if err != nil { + pubKey := ecies.ImportECDSAPublic(remoteRandomPubKey) + 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)) diff --git a/p2p/crypto_test.go b/p2p/crypto_test.go index 1785b5c458a7..cfb2d19d1a9d 100644 --- a/p2p/crypto_test.go +++ b/p2p/crypto_test.go @@ -1,7 +1,7 @@ package p2p import ( - // "bytes" + "bytes" "fmt" "testing" @@ -24,31 +24,32 @@ func TestCryptoHandshake(t *testing.T) { return } - auth, initNonce, _, _ := initiator.initAuth(responder.pubKeyDER, sessionToken) - - response, remoteRespNonce, remoteInitNonce, remoteRandomPubKey, _ := responder.verifyAuth(auth, sessionToken, pubInit) - - respNonce, randomPubKey, _, _ := initiator.verifyAuthResp(response) - - fmt.Printf("%x\n%x\n%x\n%x\n%x\n%x\n%x\n%x\n", auth, initNonce, response, remoteRespNonce, remoteInitNonce, remoteRandomPubKey, respNonce, randomPubKey) - initSessionToken, initSecretRW, _ := initiator.newSession(initNonce, respNonce, auth, randomPubKey) - // respSessionToken, respSecretRW, _ := responder.newSession(remoteInitNonce, remoteRespNonce, auth, remoteRandomPubKey) - - // if !bytes.Equal(initSessionToken, respSessionToken) { - // t.Errorf("session tokens do not match") - // } - // // aesSecret, macSecret, egressMac, ingressMac - // if !bytes.Equal(initSecretRW.aesSecret, respSecretRW.aesSecret) { - // t.Errorf("AES secrets do not match") - // } - // if !bytes.Equal(initSecretRW.macSecret, respSecretRW.macSecret) { - // t.Errorf("macSecrets do not match") - // } - // if !bytes.Equal(initSecretRW.egressMac, respSecretRW.egressMac) { - // t.Errorf("egressMacs do not match") - // } - // if !bytes.Equal(initSecretRW.ingressMac, respSecretRW.ingressMac) { - // t.Errorf("ingressMacs do not match") - // } + auth, initNonce, randomPrvKey, randomPubKey, _ := initiator.initAuth(responder.pubKeyDER, sessionToken) + + response, remoteRespNonce, remoteInitNonce, remoteRandomPrivKey, _ := responder.verifyAuth(auth, sessionToken, pubInit) + + respNonce, remoteRandomPubKey, _, _ := initiator.verifyAuthResp(response) + + initSessionToken, initSecretRW, _ := initiator.newSession(initNonce, respNonce, auth, randomPrvKey, remoteRandomPubKey) + respSessionToken, respSecretRW, _ := responder.newSession(remoteInitNonce, remoteRespNonce, auth, remoteRandomPrivKey, randomPubKey) + + fmt.Printf("%x\n%x\n%x\n%x\n%x\n%x\n%x\n%x\n%x\n%x\n", auth, initNonce, response, remoteRespNonce, remoteInitNonce, remoteRandomPubKey, respNonce, randomPubKey, initSessionToken, initSecretRW) + + if !bytes.Equal(initSessionToken, respSessionToken) { + t.Errorf("session tokens do not match") + } + // aesSecret, macSecret, egressMac, ingressMac + if !bytes.Equal(initSecretRW.aesSecret, respSecretRW.aesSecret) { + t.Errorf("AES secrets do not match") + } + if !bytes.Equal(initSecretRW.macSecret, respSecretRW.macSecret) { + t.Errorf("macSecrets do not match") + } + if !bytes.Equal(initSecretRW.egressMac, respSecretRW.egressMac) { + t.Errorf("egressMacs do not match") + } + if !bytes.Equal(initSecretRW.ingressMac, respSecretRW.ingressMac) { + t.Errorf("ingressMacs do not match") + } } From 44779d854a97ec58d2c68491eff7b64ffbfd9abb Mon Sep 17 00:00:00 2001 From: zelig Date: Mon, 19 Jan 2015 11:21:13 +0000 Subject: [PATCH 10/25] integrate cryptoId into peer and connection lifecycle --- p2p/crypto.go | 15 +++++++++++++++ p2p/peer.go | 21 ++++++++++++++++++--- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/p2p/crypto.go b/p2p/crypto.go index 37c6e1fc98f9..728b8e884ec1 100644 --- a/p2p/crypto.go +++ b/p2p/crypto.go @@ -53,6 +53,21 @@ func newCryptoId(id ClientIdentity) (self *cryptoId, err error) { return } +func (self *cryptoId) Run(remotePubKeyDER []byte) (rw *secretRW) { + if self.initiator { + auth, initNonce, randomPrvKey, randomPubKey, err := initiator.initAuth(remotePubKeyDER, sessionToken) + + respNonce, remoteRandomPubKey, _, _ := initiator.verifyAuthResp(response) + } else { + // we are listening connection. we are responders in the haandshake. + // Extract info from the authentication. The initiator starts by sending us a handshake that we need to respond to. + response, remoteRespNonce, remoteInitNonce, remoteRandomPrivKey, _ := responder.verifyAuth(auth, sessionToken, pubInit) + + } + initSessionToken, initSecretRW, _ := initiator.newSession(initNonce, respNonce, auth, randomPrvKey, remoteRandomPubKey) + respSessionToken, respSecretRW, _ := responder.newSession(remoteInitNonce, remoteRespNonce, auth, remoteRandomPrivKey, randomPubKey) +} + /* startHandshake is called by peer if it initiated the connection. By protocol spec, the party who initiates the connection (initiator) will send an 'auth' packet New: authInitiator -> E(remote-pubk, S(ecdhe-random, ecdh-shared-secret^nonce) || H(ecdhe-random-pubk) || pubk || nonce || 0x0) diff --git a/p2p/peer.go b/p2p/peer.go index 886b95a80b16..e98c3d5608e5 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -222,10 +222,14 @@ func (p *Peer) loop() (reason DiscReason, err error) { defer close(p.closed) defer p.conn.Close() + var readLoop func(chan Msg, chan error, chan bool) if p.cryptoHandshake { - if err := p.handleCryptoHandshake(); err != nil { + if readLoop, err := p.handleCryptoHandshake(); err != nil { + // from here on everything can be encrypted, authenticated return DiscProtocolError, err // no graceful disconnect } + } else { + readLoop = p.readLoop } // read loop @@ -233,7 +237,7 @@ func (p *Peer) loop() (reason DiscReason, err error) { readErr := make(chan error) readNext := make(chan bool, 1) protoDone := make(chan struct{}, 1) - go p.readLoop(readMsg, readErr, readNext) + go readLoop(readMsg, readErr, readNext) readNext <- true if p.runBaseProtocol { @@ -329,8 +333,19 @@ func (p *Peer) dispatch(msg Msg, protoDone chan struct{}) (wait bool, err error) } func (p *Peer) handleCryptoHandshake() (err error) { + // cryptoId is just created for the lifecycle of the handshake + // it is survived by an encrypted readwriter + if p.dialAddr != 0 { // this should have its own method Outgoing() bool + initiator = true + } + // create crypto layer + cryptoId := newCryptoId(p.identity, initiator, sessionToken) + // run on peer + if rw, err := cryptoId.Run(p.Pubkey()); err != nil { + return err + } + p.conn = rw.Run(p.conn) - return nil } func (p *Peer) startBaseProtocol() { From a8b3c03fbd51a0c1456e48dc11a0c3989f80cef6 Mon Sep 17 00:00:00 2001 From: zelig Date: Mon, 19 Jan 2015 23:42:13 +0000 Subject: [PATCH 11/25] first stab at integrating crypto in our p2p - abstract the entire handshake logic in cryptoId.Run() taking session-relevant parameters - changes in peer to accomodate how the encryption layer would be switched on - modify arguments of handshake components - fixed test getting the wrong pubkey but it till crashes on DH in newSession() --- p2p/crypto.go | 53 +++++++++++++++++++++++++++++++++------------- p2p/crypto_test.go | 39 +++++++++++++++++----------------- p2p/peer.go | 31 +++++++++++++++++++-------- 3 files changed, 79 insertions(+), 44 deletions(-) diff --git a/p2p/crypto.go b/p2p/crypto.go index 728b8e884ec1..b6d600826f71 100644 --- a/p2p/crypto.go +++ b/p2p/crypto.go @@ -4,6 +4,7 @@ import ( "crypto/ecdsa" "crypto/rand" "fmt" + "io" "github.com/ethereum/go-ethereum/crypto" "github.com/obscuren/ecies" @@ -53,19 +54,35 @@ func newCryptoId(id ClientIdentity) (self *cryptoId, err error) { return } -func (self *cryptoId) Run(remotePubKeyDER []byte) (rw *secretRW) { - if self.initiator { - auth, initNonce, randomPrvKey, randomPubKey, err := initiator.initAuth(remotePubKeyDER, sessionToken) - - respNonce, remoteRandomPubKey, _, _ := initiator.verifyAuthResp(response) +func (self *cryptoId) Run(conn io.ReadWriter, remotePubKeyDER []byte, sessionToken []byte, initiator bool) (token []byte, rw *secretRW, err error) { + var auth, initNonce, recNonce []byte + var randomPrivKey *ecdsa.PrivateKey + var remoteRandomPubKey *ecdsa.PublicKey + if initiator { + if auth, initNonce, randomPrivKey, _, err = self.startHandshake(remotePubKeyDER, sessionToken); err != nil { + return + } + conn.Write(auth) + var response []byte + conn.Read(response) + // write out auth message + // wait for response, then call complete + if recNonce, remoteRandomPubKey, _, err = self.completeHandshake(response); err != nil { + return + } } else { - // we are listening connection. we are responders in the haandshake. + conn.Read(auth) + // we are listening connection. we are responders in the handshake. // Extract info from the authentication. The initiator starts by sending us a handshake that we need to respond to. - response, remoteRespNonce, remoteInitNonce, remoteRandomPrivKey, _ := responder.verifyAuth(auth, sessionToken, pubInit) - + // so we read auth message first, then respond + var response []byte + if response, recNonce, initNonce, randomPrivKey, err = self.respondToHandshake(auth, remotePubKeyDER, sessionToken); err != nil { + return + } + remoteRandomPubKey = &randomPrivKey.PublicKey + conn.Write(response) } - initSessionToken, initSecretRW, _ := initiator.newSession(initNonce, respNonce, auth, randomPrvKey, remoteRandomPubKey) - respSessionToken, respSecretRW, _ := responder.newSession(remoteInitNonce, remoteRespNonce, auth, remoteRandomPrivKey, randomPubKey) + return self.newSession(initNonce, recNonce, auth, randomPrivKey, remoteRandomPubKey) } /* startHandshake is called by peer if it initiated the connection. @@ -83,9 +100,9 @@ The handshake is the process by which the peers establish their connection for a */ -func (self *cryptoId) startHandshake(remotePubKeyDER, sessionToken []byte) (auth []byte, initNonce []byte, randomPrvKey *ecdsa.PrivateKey, randomPubKey *ecdsa.PublicKey, err error) { +func (self *cryptoId) startHandshake(remotePubKeyDER, sessionToken []byte) (auth []byte, initNonce []byte, randomPrvKey *ecdsa.PrivateKey, remotePubKey *ecdsa.PublicKey, err error) { // session init, common to both parties - remotePubKey := crypto.ToECDSAPub(remotePubKeyDER) + remotePubKey = crypto.ToECDSAPub(remotePubKeyDER) if remotePubKey == nil { err = fmt.Errorf("invalid remote public key") return @@ -160,8 +177,14 @@ func (self *cryptoId) startHandshake(remotePubKeyDER, sessionToken []byte) (auth } // verifyAuth is called by peer if it accepted (but not initiated) the connection -func (self *cryptoId) respondToHandshake(auth, sessionToken []byte, remotePubKey *ecdsa.PublicKey) (authResp []byte, respNonce []byte, initNonce []byte, randomPrvKey *ecdsa.PrivateKey, err error) { +func (self *cryptoId) respondToHandshake(auth, remotePubKeyDER, sessionToken []byte) (authResp []byte, respNonce []byte, initNonce []byte, randomPrivKey *ecdsa.PrivateKey, err error) { var msg []byte + remotePubKey := crypto.ToECDSAPub(remotePubKeyDER) + if remotePubKey == nil { + err = fmt.Errorf("invalid public key") + return + } + fmt.Printf("encrypted message received: %v %x\n used pubkey: %x\n", len(auth), auth, crypto.FromECDSAPub(self.pubKey)) // they prove that msg is meant for me, // I prove I possess private key if i can read it @@ -210,12 +233,12 @@ func (self *cryptoId) respondToHandshake(auth, sessionToken []byte, remotePubKey return } // generate random keypair for session - if randomPrvKey, err = crypto.GenerateKey(); err != nil { + if randomPrivKey, err = crypto.GenerateKey(); err != nil { return } // responder auth message // E(remote-pubk, ecdhe-random-pubk || nonce || 0x0) - copy(resp[:keyLen], crypto.FromECDSAPub(&randomPrvKey.PublicKey)) + copy(resp[:keyLen], crypto.FromECDSAPub(&randomPrivKey.PublicKey)) // nonce is already in the slice resp[resLen-1] = tokenFlag diff --git a/p2p/crypto_test.go b/p2p/crypto_test.go index cfb2d19d1a9d..fb7df6b50ef8 100644 --- a/p2p/crypto_test.go +++ b/p2p/crypto_test.go @@ -11,44 +11,43 @@ import ( func TestCryptoHandshake(t *testing.T) { var err error var sessionToken []byte - prvInit, _ := crypto.GenerateKey() - pubInit := &prvInit.PublicKey - prvResp, _ := crypto.GenerateKey() - pubResp := &prvResp.PublicKey + prv0, _ := crypto.GenerateKey() + pub0 := &prv0.PublicKey + prv1, _ := crypto.GenerateKey() + pub1 := &prv1.PublicKey - var initiator, responder *cryptoId - if initiator, err = newCryptoId(&peerId{crypto.FromECDSA(prvInit), crypto.FromECDSAPub(pubInit)}); err != nil { + var initiator, receiver *cryptoId + if initiator, err = newCryptoId(&peerId{crypto.FromECDSA(prv0), crypto.FromECDSAPub(pub0)}); err != nil { return } - if responder, err = newCryptoId(&peerId{crypto.FromECDSA(prvResp), crypto.FromECDSAPub(pubResp)}); err != nil { + if receiver, err = newCryptoId(&peerId{crypto.FromECDSA(prv1), crypto.FromECDSAPub(pub1)}); err != nil { return } - auth, initNonce, randomPrvKey, randomPubKey, _ := initiator.initAuth(responder.pubKeyDER, sessionToken) + // simulate handshake by feeding output to input + auth, initNonce, randomPrivKey, _, _ := initiator.startHandshake(receiver.pubKeyDER, sessionToken) + response, remoteRecNonce, remoteInitNonce, remoteRandomPrivKey, _ := receiver.respondToHandshake(auth, crypto.FromECDSAPub(pub0), sessionToken) + recNonce, remoteRandomPubKey, _, _ := initiator.completeHandshake(response) - response, remoteRespNonce, remoteInitNonce, remoteRandomPrivKey, _ := responder.verifyAuth(auth, sessionToken, pubInit) + initSessionToken, initSecretRW, _ := initiator.newSession(initNonce, recNonce, auth, randomPrivKey, remoteRandomPubKey) + recSessionToken, recSecretRW, _ := receiver.newSession(remoteInitNonce, remoteRecNonce, auth, remoteRandomPrivKey, &randomPrivKey.PublicKey) - respNonce, remoteRandomPubKey, _, _ := initiator.verifyAuthResp(response) + fmt.Printf("%x\n%x\n%x\n%x\n%x\n%x\n%x\n%x\n%x\n%x\n", auth, initNonce, response, remoteRecNonce, remoteInitNonce, remoteRandomPubKey, recNonce, &randomPrivKey.PublicKey, initSessionToken, initSecretRW) - initSessionToken, initSecretRW, _ := initiator.newSession(initNonce, respNonce, auth, randomPrvKey, remoteRandomPubKey) - respSessionToken, respSecretRW, _ := responder.newSession(remoteInitNonce, remoteRespNonce, auth, remoteRandomPrivKey, randomPubKey) - - fmt.Printf("%x\n%x\n%x\n%x\n%x\n%x\n%x\n%x\n%x\n%x\n", auth, initNonce, response, remoteRespNonce, remoteInitNonce, remoteRandomPubKey, respNonce, randomPubKey, initSessionToken, initSecretRW) - - if !bytes.Equal(initSessionToken, respSessionToken) { + if !bytes.Equal(initSessionToken, recSessionToken) { t.Errorf("session tokens do not match") } // aesSecret, macSecret, egressMac, ingressMac - if !bytes.Equal(initSecretRW.aesSecret, respSecretRW.aesSecret) { + if !bytes.Equal(initSecretRW.aesSecret, recSecretRW.aesSecret) { t.Errorf("AES secrets do not match") } - if !bytes.Equal(initSecretRW.macSecret, respSecretRW.macSecret) { + if !bytes.Equal(initSecretRW.macSecret, recSecretRW.macSecret) { t.Errorf("macSecrets do not match") } - if !bytes.Equal(initSecretRW.egressMac, respSecretRW.egressMac) { + if !bytes.Equal(initSecretRW.egressMac, recSecretRW.egressMac) { t.Errorf("egressMacs do not match") } - if !bytes.Equal(initSecretRW.ingressMac, respSecretRW.ingressMac) { + if !bytes.Equal(initSecretRW.ingressMac, recSecretRW.ingressMac) { t.Errorf("ingressMacs do not match") } diff --git a/p2p/peer.go b/p2p/peer.go index e98c3d5608e5..e3e04ee6527e 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -222,9 +222,9 @@ func (p *Peer) loop() (reason DiscReason, err error) { defer close(p.closed) defer p.conn.Close() - var readLoop func(chan Msg, chan error, chan bool) + var readLoop func(chan<- Msg, chan<- error, <-chan bool) if p.cryptoHandshake { - if readLoop, err := p.handleCryptoHandshake(); err != nil { + if readLoop, err = p.handleCryptoHandshake(); err != nil { // from here on everything can be encrypted, authenticated return DiscProtocolError, err // no graceful disconnect } @@ -332,20 +332,33 @@ func (p *Peer) dispatch(msg Msg, protoDone chan struct{}) (wait bool, err error) return wait, nil } -func (p *Peer) handleCryptoHandshake() (err error) { +type readLoop func(chan<- Msg, chan<- error, <-chan bool) + +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 - if p.dialAddr != 0 { // this should have its own method Outgoing() bool + var initiator bool + var sessionToken []byte + if p.dialAddr != nil { // this should have its own method Outgoing() bool initiator = true } // create crypto layer - cryptoId := newCryptoId(p.identity, initiator, sessionToken) + // 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 - if rw, err := cryptoId.Run(p.Pubkey()); err != nil { - return err + // 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 { + return } - p.conn = rw.Run(p.conn) - + loop = func(msg chan<- Msg, err chan<- error, next <-chan bool) { + // this is the readloop :) + } + return } func (p *Peer) startBaseProtocol() { From 6bb203d52348a9c7a31f6444662f12126c763abe Mon Sep 17 00:00:00 2001 From: zelig Date: Tue, 20 Jan 2015 00:23:10 +0000 Subject: [PATCH 12/25] add minor comments to the test --- p2p/crypto_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/p2p/crypto_test.go b/p2p/crypto_test.go index fb7df6b50ef8..33cdb3f83fd5 100644 --- a/p2p/crypto_test.go +++ b/p2p/crypto_test.go @@ -25,10 +25,14 @@ func TestCryptoHandshake(t *testing.T) { } // simulate handshake by feeding output to input + // initiator sends handshake 'auth' auth, initNonce, randomPrivKey, _, _ := initiator.startHandshake(receiver.pubKeyDER, sessionToken) + // receiver reads auth and responds with response response, remoteRecNonce, remoteInitNonce, remoteRandomPrivKey, _ := receiver.respondToHandshake(auth, crypto.FromECDSAPub(pub0), sessionToken) + // initiator reads receiver's response and the key exchange completes recNonce, remoteRandomPubKey, _, _ := initiator.completeHandshake(response) + // now both parties should have the same session parameters initSessionToken, initSecretRW, _ := initiator.newSession(initNonce, recNonce, auth, randomPrivKey, remoteRandomPubKey) recSessionToken, recSecretRW, _ := receiver.newSession(remoteInitNonce, remoteRecNonce, auth, remoteRandomPrivKey, &randomPrivKey.PublicKey) From 20ac18751efce1365d90f81098c499134ab79c87 Mon Sep 17 00:00:00 2001 From: zelig Date: Tue, 20 Jan 2015 00:41:45 +0000 Subject: [PATCH 13/25] add equality check for nonce and remote nonce --- p2p/crypto_test.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/p2p/crypto_test.go b/p2p/crypto_test.go index 33cdb3f83fd5..89baca084445 100644 --- a/p2p/crypto_test.go +++ b/p2p/crypto_test.go @@ -38,6 +38,12 @@ func TestCryptoHandshake(t *testing.T) { fmt.Printf("%x\n%x\n%x\n%x\n%x\n%x\n%x\n%x\n%x\n%x\n", auth, initNonce, response, remoteRecNonce, remoteInitNonce, remoteRandomPubKey, recNonce, &randomPrivKey.PublicKey, initSessionToken, initSecretRW) + if !bytes.Equal(initNonce, remoteInitNonce) { + t.Errorf("nonces do not match") + } + if !bytes.Equal(recNonce, remoteRecNonce) { + t.Errorf("receiver nonces do not match") + } if !bytes.Equal(initSessionToken, recSessionToken) { t.Errorf("session tokens do not match") } From 2d4f5985c00324073e666d57f7fbd4d60b9592e4 Mon Sep 17 00:00:00 2001 From: zelig Date: Tue, 20 Jan 2015 15:20:18 +0000 Subject: [PATCH 14/25] important fix for peer pubkey. when taken from identity, chop first format byte! --- p2p/peer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/p2p/peer.go b/p2p/peer.go index e3e04ee6527e..e44eaab34336 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -147,7 +147,7 @@ func (self *Peer) Pubkey() (pubkey []byte) { defer self.infolock.Unlock() switch { case self.identity != nil: - pubkey = self.identity.Pubkey() + pubkey = self.identity.Pubkey()[1:] case self.dialAddr != nil: pubkey = self.dialAddr.Pubkey case self.listenAddr != nil: From 0433cae5a632b5065d0aef1c6281a960c06a42e2 Mon Sep 17 00:00:00 2001 From: zelig Date: Tue, 20 Jan 2015 16:47:46 +0000 Subject: [PATCH 15/25] changes that fix it all: - set proper public key serialisation length in pubLen = 64 - reset all sizes and offsets - rename from DER to S (we are not using DER encoding) - add remoteInitRandomPubKey as return value to respondToHandshake - add ImportPublicKey with error return to read both EC golang.elliptic style 65 byte encoding and 64 byte one - add ExportPublicKey falling back to go-ethereum/crypto.FromECDSAPub() chopping off the first byte - add Import - Export tests - all tests pass --- p2p/crypto.go | 111 +++++++++++++++++++++++++-------------------- p2p/crypto_test.go | 92 ++++++++++++++++++++++++++++++++++--- 2 files changed, 146 insertions(+), 57 deletions(-) diff --git a/p2p/crypto.go b/p2p/crypto.go index b6d600826f71..dbef022ccde4 100644 --- a/p2p/crypto.go +++ b/p2p/crypto.go @@ -12,11 +12,12 @@ import ( ) var ( - sskLen int = 16 // ecies.MaxSharedKeyLength(pubKey) / 2 - sigLen int = 65 // elliptic S256 - keyLen int = 32 // ECDSA - msgLen int = sigLen + 3*keyLen + 1 // 162 - resLen int = 65 // + sskLen int = 16 // ecies.MaxSharedKeyLength(pubKey) / 2 + sigLen int = 65 // elliptic S256 + pubLen int = 64 // 512 bit pubkey in uncompressed representation without format byte + keyLen int = 32 // ECDSA + msgLen int = 194 // sigLen + keyLen + pubLen + keyLen + 1 = 194 + resLen int = 97 // pubLen + keyLen + 1 ) // aesSecret, macSecret, egressMac, ingress @@ -25,20 +26,21 @@ type secretRW struct { } type cryptoId struct { - prvKey *ecdsa.PrivateKey - pubKey *ecdsa.PublicKey - pubKeyDER []byte + prvKey *ecdsa.PrivateKey + pubKey *ecdsa.PublicKey + pubKeyS []byte } func newCryptoId(id ClientIdentity) (self *cryptoId, err error) { // will be at server init - var prvKeyDER []byte = id.PrivKey() - if prvKeyDER == nil { + var prvKeyS []byte = id.PrivKey() + if prvKeyS == nil { err = fmt.Errorf("no private key for client") return } - // initialise ecies private key via importing DER encoded keys (known via our own clientIdentity) - var prvKey = crypto.ToECDSA(prvKeyDER) + // 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 @@ -50,16 +52,16 @@ func newCryptoId(id ClientIdentity) (self *cryptoId, err error) { // to be created at server init shared between peers and sessions // for reuse, call wth ReadAt, no reset seek needed } - self.pubKeyDER = id.Pubkey() + self.pubKeyS = id.Pubkey() return } -func (self *cryptoId) Run(conn io.ReadWriter, remotePubKeyDER []byte, sessionToken []byte, initiator bool) (token []byte, rw *secretRW, err error) { +func (self *cryptoId) Run(conn io.ReadWriter, remotePubKeyS []byte, sessionToken []byte, initiator bool) (token []byte, rw *secretRW, err error) { var auth, initNonce, recNonce []byte var randomPrivKey *ecdsa.PrivateKey var remoteRandomPubKey *ecdsa.PublicKey if initiator { - if auth, initNonce, randomPrivKey, _, err = self.startHandshake(remotePubKeyDER, sessionToken); err != nil { + if auth, initNonce, randomPrivKey, _, err = self.startHandshake(remotePubKeyS, sessionToken); err != nil { return } conn.Write(auth) @@ -76,10 +78,9 @@ func (self *cryptoId) Run(conn io.ReadWriter, remotePubKeyDER []byte, sessionTok // 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, err = self.respondToHandshake(auth, remotePubKeyDER, sessionToken); err != nil { + if response, recNonce, initNonce, randomPrivKey, remoteRandomPubKey, err = self.respondToHandshake(auth, remotePubKeyS, sessionToken); err != nil { return } - remoteRandomPubKey = &randomPrivKey.PublicKey conn.Write(response) } return self.newSession(initNonce, recNonce, auth, randomPrivKey, remoteRandomPubKey) @@ -100,11 +101,29 @@ The handshake is the process by which the peers establish their connection for a */ -func (self *cryptoId) startHandshake(remotePubKeyDER, sessionToken []byte) (auth []byte, initNonce []byte, randomPrvKey *ecdsa.PrivateKey, remotePubKey *ecdsa.PublicKey, err error) { +func ImportPublicKey(pubKey []byte) (pubKeyEC *ecdsa.PublicKey, err error) { + var pubKey65 []byte + switch len(pubKey) { + case 64: + pubKey65 = append([]byte{0x04}, pubKey...) + case 65: + pubKey65 = pubKey + default: + return nil, fmt.Errorf("invalid public key length %v (expect 64/65)", len(pubKey)) + } + return crypto.ToECDSAPub(pubKey65), nil +} + +func ExportPublicKey(pubKeyEC *ecdsa.PublicKey) (pubKey []byte, err error) { + if pubKeyEC == nil { + return nil, fmt.Errorf("no ECDSA public key given") + } + return crypto.FromECDSAPub(pubKeyEC)[1:], nil +} + +func (self *cryptoId) startHandshake(remotePubKeyS, sessionToken []byte) (auth []byte, initNonce []byte, randomPrvKey *ecdsa.PrivateKey, remotePubKey *ecdsa.PublicKey, err error) { // session init, common to both parties - remotePubKey = crypto.ToECDSAPub(remotePubKeyDER) - if remotePubKey == nil { - err = fmt.Errorf("invalid remote public key") + if remotePubKey, err = ImportPublicKey(remotePubKeyS); err != nil { return } @@ -116,8 +135,6 @@ func (self *cryptoId) startHandshake(remotePubKeyDER, sessionToken []byte) (auth if sessionToken, err = ecies.ImportECDSA(self.prvKey).GenerateShared(ecies.ImportECDSAPublic(remotePubKey), sskLen, sskLen); err != nil { return } - // this will not stay here ;) - fmt.Printf("secret generated: %v %x", len(sessionToken), sessionToken) // tokenFlag = 0x00 // redundant } else { // for known peers, we use stored token from the previous session @@ -128,9 +145,7 @@ func (self *cryptoId) startHandshake(remotePubKeyDER, sessionToken []byte) (auth // E(remote-pubk, S(ecdhe-random, token^nonce) || H(ecdhe-random-pubk) || pubk || nonce || 0x1) // allocate msgLen long message, var msg []byte = make([]byte, msgLen) - // generate sskLen long nonce initNonce = msg[msgLen-keyLen-1 : msgLen-1] - // nonce = msg[msgLen-sskLen-1 : msgLen-1] if _, err = rand.Read(initNonce); err != nil { return } @@ -150,48 +165,45 @@ func (self *cryptoId) startHandshake(remotePubKeyDER, sessionToken []byte) (auth if signature, err = crypto.Sign(sharedSecret, randomPrvKey); err != nil { return } - fmt.Printf("signature generated: %v %x", len(signature), signature) // message // signed-shared-secret || H(ecdhe-random-pubk) || pubk || nonce || 0x0 copy(msg, signature) // copy signed-shared-secret // H(ecdhe-random-pubk) - copy(msg[sigLen:sigLen+keyLen], crypto.Sha3(crypto.FromECDSAPub(&randomPrvKey.PublicKey))) + var randomPubKey64 []byte + if randomPubKey64, err = ExportPublicKey(&randomPrvKey.PublicKey); err != nil { + return + } + copy(msg[sigLen:sigLen+keyLen], crypto.Sha3(randomPubKey64)) // pubkey copied to the correct segment. - copy(msg[sigLen+keyLen:sigLen+2*keyLen], self.pubKeyDER) + copy(msg[sigLen+keyLen:sigLen+keyLen+pubLen], self.pubKeyS) // nonce is already in the slice // stick tokenFlag byte to the end msg[msgLen-1] = tokenFlag - fmt.Printf("plaintext message generated: %v %x", len(msg), msg) - // encrypt using remote-pubk // auth = eciesEncrypt(remote-pubk, msg) if auth, err = crypto.Encrypt(remotePubKey, msg); err != nil { return } - fmt.Printf("encrypted message generated: %v %x\n used pubkey: %x\n", len(auth), auth, crypto.FromECDSAPub(remotePubKey)) return } // verifyAuth is called by peer if it accepted (but not initiated) the connection -func (self *cryptoId) respondToHandshake(auth, remotePubKeyDER, sessionToken []byte) (authResp []byte, respNonce []byte, initNonce []byte, randomPrivKey *ecdsa.PrivateKey, err error) { +func (self *cryptoId) respondToHandshake(auth, remotePubKeyS, sessionToken []byte) (authResp []byte, respNonce []byte, initNonce []byte, randomPrivKey *ecdsa.PrivateKey, remoteRandomPubKey *ecdsa.PublicKey, err error) { var msg []byte - remotePubKey := crypto.ToECDSAPub(remotePubKeyDER) - if remotePubKey == nil { - err = fmt.Errorf("invalid public key") + var remotePubKey *ecdsa.PublicKey + if remotePubKey, err = ImportPublicKey(remotePubKeyS); err != nil { return } - fmt.Printf("encrypted message received: %v %x\n used pubkey: %x\n", len(auth), auth, crypto.FromECDSAPub(self.pubKey)) // 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 { return } - fmt.Printf("\nplaintext message retrieved: %v %x\n", len(msg), msg) var tokenFlag byte if sessionToken == nil { @@ -201,7 +213,6 @@ func (self *cryptoId) respondToHandshake(auth, remotePubKeyDER, sessionToken []b if sessionToken, err = ecies.ImportECDSA(self.prvKey).GenerateShared(ecies.ImportECDSAPublic(remotePubKey), sskLen, sskLen); err != nil { return } - fmt.Printf("secret generated: %v %x", len(sessionToken), sessionToken) // tokenFlag = 0x00 // redundant } else { // for known peers, we use stored token from the previous session @@ -214,21 +225,19 @@ func (self *cryptoId) respondToHandshake(auth, remotePubKeyDER, sessionToken []b // they prove they own the private key belonging to ecdhe-random-pubk // we can now reconstruct the signed message and recover the peers pubkey var signedMsg = Xor(sessionToken, initNonce) - var remoteRandomPubKeyDER []byte - if remoteRandomPubKeyDER, err = secp256k1.RecoverPubkey(signedMsg, msg[:sigLen]); err != nil { + var remoteRandomPubKeyS []byte + if remoteRandomPubKeyS, err = secp256k1.RecoverPubkey(signedMsg, msg[:sigLen]); err != nil { return } // convert to ECDSA standard - remoteRandomPubKey := crypto.ToECDSAPub(remoteRandomPubKeyDER) - if remoteRandomPubKey == nil { - err = fmt.Errorf("invalid remote public key") + if remoteRandomPubKey, err = ImportPublicKey(remoteRandomPubKeyS); err != nil { return } // now we find ourselves a long task too, fill it random var resp = make([]byte, resLen) // generate keyLen long nonce - respNonce = msg[resLen-keyLen-1 : msgLen-1] + respNonce = resp[pubLen : pubLen+keyLen] if _, err = rand.Read(respNonce); err != nil { return } @@ -238,7 +247,11 @@ func (self *cryptoId) respondToHandshake(auth, remotePubKeyDER, sessionToken []b } // responder auth message // E(remote-pubk, ecdhe-random-pubk || nonce || 0x0) - copy(resp[:keyLen], crypto.FromECDSAPub(&randomPrivKey.PublicKey)) + var randomPubKeyS []byte + if randomPubKeyS, err = ExportPublicKey(&randomPrivKey.PublicKey); err != nil { + return + } + copy(resp[:pubLen], randomPubKeyS) // nonce is already in the slice resp[resLen-1] = tokenFlag @@ -259,11 +272,9 @@ func (self *cryptoId) completeHandshake(auth []byte) (respNonce []byte, remoteRa return } - respNonce = msg[resLen-keyLen-1 : resLen-1] - var remoteRandomPubKeyDER = msg[:keyLen] - remoteRandomPubKey = crypto.ToECDSAPub(remoteRandomPubKeyDER) - if remoteRandomPubKey == nil { - err = fmt.Errorf("invalid ecdh random remote public key") + respNonce = msg[pubLen : pubLen+keyLen] + var remoteRandomPubKeyS = msg[:pubLen] + if remoteRandomPubKey, err = ImportPublicKey(remoteRandomPubKeyS); err != nil { return } if msg[resLen-1] == 0x01 { diff --git a/p2p/crypto_test.go b/p2p/crypto_test.go index 89baca084445..8000efaf199b 100644 --- a/p2p/crypto_test.go +++ b/p2p/crypto_test.go @@ -2,16 +2,76 @@ package p2p import ( "bytes" + // "crypto/ecdsa" + // "crypto/elliptic" + // "crypto/rand" "fmt" "testing" "github.com/ethereum/go-ethereum/crypto" + "github.com/obscuren/ecies" ) +func TestPublicKeyEncoding(t *testing.T) { + prv0, _ := crypto.GenerateKey() // = ecdsa.GenerateKey(crypto.S256(), rand.Reader) + pub0 := &prv0.PublicKey + pub0s := crypto.FromECDSAPub(pub0) + pub1, err := ImportPublicKey(pub0s) + if err != nil { + t.Errorf("%v", err) + } + eciesPub1 := ecies.ImportECDSAPublic(pub1) + if eciesPub1 == nil { + t.Errorf("invalid ecdsa public key") + } + pub1s, err := ExportPublicKey(pub1) + if err != nil { + t.Errorf("%v", err) + } + if len(pub1s) != 64 { + t.Errorf("wrong length expect 64, got", len(pub1s)) + } + pub2, err := ImportPublicKey(pub1s) + if err != nil { + t.Errorf("%v", err) + } + pub2s, err := ExportPublicKey(pub2) + if err != nil { + t.Errorf("%v", err) + } + if !bytes.Equal(pub1s, pub2s) { + t.Errorf("exports dont match") + } + pub2sEC := crypto.FromECDSAPub(pub2) + if !bytes.Equal(pub0s, pub2sEC) { + t.Errorf("exports dont match") + } +} + +func TestSharedSecret(t *testing.T) { + prv0, _ := crypto.GenerateKey() // = ecdsa.GenerateKey(crypto.S256(), rand.Reader) + pub0 := &prv0.PublicKey + prv1, _ := crypto.GenerateKey() + pub1 := &prv1.PublicKey + + ss0, err := ecies.ImportECDSA(prv0).GenerateShared(ecies.ImportECDSAPublic(pub1), sskLen, sskLen) + if err != nil { + return + } + ss1, err := ecies.ImportECDSA(prv1).GenerateShared(ecies.ImportECDSAPublic(pub0), sskLen, sskLen) + if err != nil { + return + } + t.Logf("Secret:\n%v %x\n%v %x", len(ss0), ss0, len(ss0), ss1) + if !bytes.Equal(ss0, ss1) { + t.Errorf("dont match :(") + } +} + func TestCryptoHandshake(t *testing.T) { var err error var sessionToken []byte - prv0, _ := crypto.GenerateKey() + prv0, _ := crypto.GenerateKey() // = ecdsa.GenerateKey(crypto.S256(), rand.Reader) pub0 := &prv0.PublicKey prv1, _ := crypto.GenerateKey() pub1 := &prv1.PublicKey @@ -26,17 +86,35 @@ func TestCryptoHandshake(t *testing.T) { // simulate handshake by feeding output to input // initiator sends handshake 'auth' - auth, initNonce, randomPrivKey, _, _ := initiator.startHandshake(receiver.pubKeyDER, sessionToken) + auth, initNonce, randomPrivKey, _, err := initiator.startHandshake(receiver.pubKeyS, sessionToken) + if err != nil { + t.Errorf("%v", err) + } + // receiver reads auth and responds with response - response, remoteRecNonce, remoteInitNonce, remoteRandomPrivKey, _ := receiver.respondToHandshake(auth, crypto.FromECDSAPub(pub0), sessionToken) + response, remoteRecNonce, remoteInitNonce, remoteRandomPrivKey, remoteInitRandomPubKey, err := receiver.respondToHandshake(auth, crypto.FromECDSAPub(pub0), sessionToken) + if err != nil { + t.Errorf("%v", err) + } + // initiator reads receiver's response and the key exchange completes - recNonce, remoteRandomPubKey, _, _ := initiator.completeHandshake(response) + recNonce, remoteRandomPubKey, _, err := initiator.completeHandshake(response) + if err != nil { + t.Errorf("%v", err) + } // now both parties should have the same session parameters - initSessionToken, initSecretRW, _ := initiator.newSession(initNonce, recNonce, auth, randomPrivKey, remoteRandomPubKey) - recSessionToken, recSecretRW, _ := receiver.newSession(remoteInitNonce, remoteRecNonce, auth, remoteRandomPrivKey, &randomPrivKey.PublicKey) + initSessionToken, initSecretRW, err := initiator.newSession(initNonce, recNonce, auth, randomPrivKey, remoteRandomPubKey) + if err != nil { + t.Errorf("%v", err) + } + + recSessionToken, recSecretRW, err := receiver.newSession(remoteInitNonce, remoteRecNonce, auth, remoteRandomPrivKey, remoteInitRandomPubKey) + if err != nil { + t.Errorf("%v", err) + } - fmt.Printf("%x\n%x\n%x\n%x\n%x\n%x\n%x\n%x\n%x\n%x\n", auth, initNonce, response, remoteRecNonce, remoteInitNonce, remoteRandomPubKey, recNonce, &randomPrivKey.PublicKey, initSessionToken, initSecretRW) + fmt.Printf("\nauth %x\ninitNonce %x\nresponse%x\nremoteRecNonce %x\nremoteInitNonce %x\nremoteRandomPubKey %x\nrecNonce %x\nremoteInitRandomPubKey %x\ninitSessionToken %x\n\n", auth, initNonce, response, remoteRecNonce, remoteInitNonce, remoteRandomPubKey, recNonce, remoteInitRandomPubKey, initSessionToken) if !bytes.Equal(initNonce, remoteInitNonce) { t.Errorf("nonces do not match") From 8f938c7aa43977f83338fe953252d6d8c297ab8d Mon Sep 17 00:00:00 2001 From: zelig Date: Wed, 21 Jan 2015 10:22:07 +0000 Subject: [PATCH 16/25] add code documentation --- p2p/crypto.go | 62 ++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 47 insertions(+), 15 deletions(-) diff --git a/p2p/crypto.go b/p2p/crypto.go index dbef022ccde4..8551e317c7c6 100644 --- a/p2p/crypto.go +++ b/p2p/crypto.go @@ -20,17 +20,27 @@ var ( resLen int = 97 // pubLen + keyLen + 1 ) +// secretRW implements a message read writer with encryption and authentication +// it is initialised by cryptoId.Run() after a successful crypto handshake // aesSecret, macSecret, egressMac, ingress 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() @@ -56,6 +66,20 @@ func newCryptoId(id ClientIdentity) (self *cryptoId, err error) { return } +/* +Run(connection, remotePublicKey, sessionToken) 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. + + 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. + + 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) { var auth, initNonce, recNonce []byte var randomPrivKey *ecdsa.PrivateKey @@ -86,21 +110,9 @@ func (self *cryptoId) Run(conn io.ReadWriter, remotePubKeyS []byte, sessionToken return self.newSession(initNonce, recNonce, auth, randomPrivKey, remoteRandomPubKey) } -/* startHandshake is called by peer if it initiated the connection. - By protocol spec, the party who initiates the connection (initiator) will send an 'auth' packet -New: authInitiator -> E(remote-pubk, S(ecdhe-random, ecdh-shared-secret^nonce) || H(ecdhe-random-pubk) || pubk || nonce || 0x0) - authRecipient -> E(remote-pubk, ecdhe-random-pubk || nonce || 0x0) - -Known: authInitiator = E(remote-pubk, S(ecdhe-random, token^nonce) || H(ecdhe-random-pubk) || pubk || nonce || 0x1) - authRecipient = E(remote-pubk, ecdhe-random-pubk || nonce || 0x1) // token found - authRecipient = E(remote-pubk, ecdhe-random-pubk || nonce || 0x0) // token not found - -The caller provides the public key of the peer as conjuctured from lookup based on IP:port, given as user input or proven by signatures. The caller must have access to persistant information about the peers, and pass the previous session token as an argument to cryptoId. - -The handshake is the process by which the peers establish their connection for a session. - +/* +ImportPublicKey creates a 512 bit *ecsda.PublicKey from a byte slice. It accepts the simple 64 byte uncompressed format or the 65 byte format given by calling elliptic.Marshal on the EC point represented by the key. Any other length will result in an invalid public key error. */ - func ImportPublicKey(pubKey []byte) (pubKeyEC *ecdsa.PublicKey, err error) { var pubKey65 []byte switch len(pubKey) { @@ -114,6 +126,9 @@ func ImportPublicKey(pubKey []byte) (pubKeyEC *ecdsa.PublicKey, err error) { return crypto.ToECDSAPub(pubKey65), nil } +/* +ExportPublicKey exports a *ecdsa.PublicKey into a byte slice using a simple 64-byte format. and is used for simple serialisation in network communication +*/ func ExportPublicKey(pubKeyEC *ecdsa.PublicKey) (pubKey []byte, err error) { if pubKeyEC == nil { return nil, fmt.Errorf("no ECDSA public key given") @@ -121,6 +136,12 @@ func ExportPublicKey(pubKeyEC *ecdsa.PublicKey) (pubKey []byte, err error) { return crypto.FromECDSAPub(pubKeyEC)[1:], nil } +/* startHandshake is called by if the node is the initiator of the connection. + +The caller provides the public key of the peer as conjuctured from lookup based on IP:port, given as user input or proven by signatures. The caller must have access to persistant information about the peers, and pass the previous session token as an argument to cryptoId. + +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) { // session init, common to both parties if remotePubKey, err = ImportPublicKey(remotePubKeyS); err != nil { @@ -191,7 +212,11 @@ func (self *cryptoId) startHandshake(remotePubKeyS, sessionToken []byte) (auth [ return } -// verifyAuth is called by peer if it accepted (but not initiated) the connection +/* +respondToHandshake is called by peer if it accepted (but not initiated) the connection from the remote. It is passed the initiator handshake received, the public key and session token belonging to the remote initiator. + +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) { var msg []byte var remotePubKey *ecdsa.PublicKey @@ -264,6 +289,9 @@ func (self *cryptoId) respondToHandshake(auth, remotePubKeyS, sessionToken []byt return } +/* +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) { var msg []byte // they prove that msg is meant for me, @@ -283,6 +311,9 @@ func (self *cryptoId) completeHandshake(auth []byte) (respNonce []byte, remoteRa return } +/* +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(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) @@ -316,6 +347,7 @@ func (self *cryptoId) newSession(initNonce, respNonce, auth []byte, privKey *ecd return } +// TODO: optimisation // should use cipher.xorBytes from crypto/cipher/xor.go for fast xor func Xor(one, other []byte) (xor []byte) { xor = make([]byte, len(one)) From 012d9e1e827ca2fb502b64d9ca9038497172f2cf Mon Sep 17 00:00:00 2001 From: zelig Date: Wed, 21 Jan 2015 14:42:12 +0000 Subject: [PATCH 17/25] add initial peer level test (failing) --- p2p/crypto_test.go | 53 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/p2p/crypto_test.go b/p2p/crypto_test.go index 8000efaf199b..5fbdc61e3eec 100644 --- a/p2p/crypto_test.go +++ b/p2p/crypto_test.go @@ -6,6 +6,7 @@ import ( // "crypto/elliptic" // "crypto/rand" "fmt" + "net" "testing" "github.com/ethereum/go-ethereum/crypto" @@ -114,7 +115,9 @@ func TestCryptoHandshake(t *testing.T) { t.Errorf("%v", err) } - fmt.Printf("\nauth %x\ninitNonce %x\nresponse%x\nremoteRecNonce %x\nremoteInitNonce %x\nremoteRandomPubKey %x\nrecNonce %x\nremoteInitRandomPubKey %x\ninitSessionToken %x\n\n", auth, initNonce, response, remoteRecNonce, remoteInitNonce, remoteRandomPubKey, recNonce, remoteInitRandomPubKey, initSessionToken) + fmt.Printf("\nauth (%v) %x\n\nresp (%v) %x\n\n", len(auth), auth, len(response), response) + + // fmt.Printf("\nauth %x\ninitNonce %x\nresponse%x\nremoteRecNonce %x\nremoteInitNonce %x\nremoteRandomPubKey %x\nrecNonce %x\nremoteInitRandomPubKey %x\ninitSessionToken %x\n\n", auth, initNonce, response, remoteRecNonce, remoteInitNonce, remoteRandomPubKey, recNonce, remoteInitRandomPubKey, initSessionToken) if !bytes.Equal(initNonce, remoteInitNonce) { t.Errorf("nonces do not match") @@ -140,3 +143,51 @@ func TestCryptoHandshake(t *testing.T) { } } + +func TestPeersHandshake(t *testing.T) { + defer testlog(t).detach() + var err error + // var sessionToken []byte + prv0, _ := crypto.GenerateKey() // = ecdsa.GenerateKey(crypto.S256(), rand.Reader) + pub0 := &prv0.PublicKey + prv1, _ := crypto.GenerateKey() + pub1 := &prv1.PublicKey + + prv0s := crypto.FromECDSA(prv0) + pub0s := crypto.FromECDSAPub(pub0) + prv1s := crypto.FromECDSA(prv1) + pub1s := crypto.FromECDSAPub(pub1) + + conn1, conn2 := net.Pipe() + 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} + + // 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} + + initiator.pubkeyHook = func(*peerAddr) error { return nil } + receiver.pubkeyHook = func(*peerAddr) error { return nil } + + initiator.cryptoHandshake = true + receiver.cryptoHandshake = true + errc0 := make(chan error, 1) + errc1 := make(chan error, 1) + go func() { + _, err := initiator.loop() + errc0 <- err + }() + go func() { + _, err := receiver.loop() + errc1 <- err + }() + select { + case err = <-errc0: + t.Errorf("peer 0 quit with error: %v", err) + case err = <-errc1: + t.Errorf("peer 1 quit with error: %v", err) + } +} From 1dda555230bc97b03cc4dd6f565d8ee88a430208 Mon Sep 17 00:00:00 2001 From: zelig Date: Wed, 21 Jan 2015 14:45:53 +0000 Subject: [PATCH 18/25] chop first byte when cryptoid.PubKeyS is set from identity.Pubkey() since this is directly copied in the auth message --- p2p/crypto.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/p2p/crypto.go b/p2p/crypto.go index 8551e317c7c6..91d60aa7e384 100644 --- a/p2p/crypto.go +++ b/p2p/crypto.go @@ -7,10 +7,13 @@ import ( "io" "github.com/ethereum/go-ethereum/crypto" + ethlogger "github.com/ethereum/go-ethereum/logger" "github.com/obscuren/ecies" "github.com/obscuren/secp256k1-go" ) +var clogger = ethlogger.NewLogger("CRYPTOID") + var ( sskLen int = 16 // ecies.MaxSharedKeyLength(pubKey) / 2 sigLen int = 65 // elliptic S256 @@ -62,10 +65,17 @@ func newCryptoId(id ClientIdentity) (self *cryptoId, err error) { // to be created at server init shared between peers and sessions // for reuse, call wth ReadAt, no reset seek needed } - self.pubKeyS = id.Pubkey() + self.pubKeyS = id.Pubkey()[1:] + clogger.Debugf("crytoid starting for %v", hexkey(self.pubKeyS)) return } +type hexkey []byte + +func (self hexkey) String() string { + return fmt.Sprintf("(%d) %x", len(self), []byte(self)) +} + /* Run(connection, remotePublicKey, sessionToken) is called when the peer connection starts to set up a secure session by performing a crypto handshake. From 92bd2532c98e7f03d8a549ad4c70816ee51c040d Mon Sep 17 00:00:00 2001 From: zelig Date: Wed, 21 Jan 2015 16:22:49 +0000 Subject: [PATCH 19/25] peer-level integration test for crypto handshake - add const length params for handshake messages - add length check to fail early - add debug logs to help interop testing (!ABSOLUTELY SHOULD BE DELETED LATER) - wrap connection read/writes in error check - add cryptoReady channel in peer to signal when secure session setup is finished - wait for cryptoReady or timeout in TestPeersHandshake --- p2p/crypto.go | 53 ++++++++++++++++++++++++++++++++++++++++------ p2p/crypto_test.go | 11 ++++++++++ p2p/peer.go | 22 +++++++++++-------- 3 files changed, 71 insertions(+), 15 deletions(-) diff --git a/p2p/crypto.go b/p2p/crypto.go index 91d60aa7e384..e8f4d551bd2b 100644 --- a/p2p/crypto.go +++ b/p2p/crypto.go @@ -21,6 +21,8 @@ var ( keyLen int = 32 // ECDSA msgLen int = 194 // sigLen + keyLen + pubLen + keyLen + 1 = 194 resLen int = 97 // pubLen + keyLen + 1 + iHSLen int = 307 // size of the final ECIES payload sent as initiator's handshake + rHSLen int = 210 // size of the final ECIES payload sent as receiver's handshake ) // secretRW implements a message read writer with encryption and authentication @@ -66,7 +68,8 @@ func newCryptoId(id ClientIdentity) (self *cryptoId, err error) { // for reuse, call wth ReadAt, no reset seek needed } self.pubKeyS = id.Pubkey()[1:] - clogger.Debugf("crytoid starting for %v", hexkey(self.pubKeyS)) + clogger.Debugf("initialise crypto for NodeId %v", hexkey(self.pubKeyS)) + clogger.Debugf("private-key %v\npublic key %v", hexkey(prvKeyS), hexkey(self.pubKeyS)) return } @@ -92,22 +95,51 @@ Run(connection, remotePublicKey, sessionToken) is called when the peer connectio func (self *cryptoId) Run(conn io.ReadWriter, 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 { return } - conn.Write(auth) - var response []byte - conn.Read(response) + clogger.Debugf("initiator-nonce: %v", hexkey(initNonce)) + clogger.Debugf("initiator-random-private-key: %v", hexkey(crypto.FromECDSA(randomPrivKey))) + randomPublicKeyS, _ := ExportPublicKey(&randomPrivKey.PublicKey) + clogger.Debugf("initiator-random-public-key: %v", hexkey(randomPublicKeyS)) + + if _, err = conn.Write(auth); err != nil { + return + } + clogger.Debugf("initiator handshake (sent to %v):\n%v", hexkey(remotePubKeyS), hexkey(auth)) + var response []byte = make([]byte, rHSLen) + if read, err = conn.Read(response); err != nil || read == 0 { + return + } + if read != rHSLen { + err = fmt.Errorf("remote receiver's handshake has invalid length. expect %v, got %v", rHSLen, read) + return + } // write out auth message // wait for response, then call complete if recNonce, remoteRandomPubKey, _, err = self.completeHandshake(response); err != nil { return } + clogger.Debugf("receiver-nonce: %v", hexkey(recNonce)) + remoteRandomPubKeyS, _ := ExportPublicKey(remoteRandomPubKey) + clogger.Debugf("receiver-random-public-key: %v", hexkey(remoteRandomPubKeyS)) + } else { - conn.Read(auth) + auth = make([]byte, iHSLen) + clogger.Debugf("waiting for initiator handshake (from %v)", hexkey(remotePubKeyS)) + if read, err = conn.Read(auth); err != nil { + return + } + if read != iHSLen { + err = fmt.Errorf("remote initiator's handshake has invalid length. expect %v, got %v", iHSLen, read) + return + } + clogger.Debugf("received initiator handshake (from %v):\n%v", hexkey(remotePubKeyS), hexkey(auth)) // we are listening connection. we are responders in the handshake. // 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 @@ -115,7 +147,12 @@ func (self *cryptoId) Run(conn io.ReadWriter, remotePubKeyS []byte, sessionToken if response, recNonce, initNonce, randomPrivKey, remoteRandomPubKey, err = self.respondToHandshake(auth, remotePubKeyS, sessionToken); err != nil { return } - conn.Write(response) + clogger.Debugf("receiver-nonce: %v", hexkey(recNonce)) + clogger.Debugf("receiver-random-priv-key: %v", hexkey(crypto.FromECDSA(randomPrivKey))) + if _, err = conn.Write(response); err != nil { + return + } + clogger.Debugf("receiver handshake (sent to %v):\n%v", hexkey(remotePubKeyS), hexkey(response)) } return self.newSession(initNonce, recNonce, auth, randomPrivKey, remoteRandomPubKey) } @@ -354,6 +391,10 @@ func (self *cryptoId) newSession(initNonce, respNonce, auth []byte, privKey *ecd egressMac: egressMac, ingressMac: ingressMac, } + clogger.Debugf("aes-secret: %v", hexkey(aesSecret)) + clogger.Debugf("mac-secret: %v", hexkey(macSecret)) + clogger.Debugf("egress-mac: %v", hexkey(egressMac)) + clogger.Debugf("ingress-mac: %v", hexkey(ingressMac)) return } diff --git a/p2p/crypto_test.go b/p2p/crypto_test.go index 5fbdc61e3eec..47b16040ae5c 100644 --- a/p2p/crypto_test.go +++ b/p2p/crypto_test.go @@ -8,6 +8,7 @@ import ( "fmt" "net" "testing" + "time" "github.com/ethereum/go-ethereum/crypto" "github.com/obscuren/ecies" @@ -184,7 +185,17 @@ func TestPeersHandshake(t *testing.T) { _, err := receiver.loop() errc1 <- err }() + ready := make(chan bool) + go func() { + <-initiator.cryptoReady + <-receiver.cryptoReady + close(ready) + }() + timeout := time.After(1 * time.Second) select { + case <-ready: + case <-timeout: + t.Errorf("crypto handshake hanging for too long") case err = <-errc0: t.Errorf("peer 0 quit with error: %v", err) case err = <-errc1: diff --git a/p2p/peer.go b/p2p/peer.go index e44eaab34336..818f80580269 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -71,6 +71,7 @@ type Peer struct { protocols []Protocol runBaseProtocol bool // for testing cryptoHandshake bool // for testing + cryptoReady chan struct{} runlock sync.RWMutex // protects running running map[string]*proto @@ -120,15 +121,16 @@ func newServerPeer(server *Server, conn net.Conn, dialAddr *peerAddr) *Peer { func newPeer(conn net.Conn, protocols []Protocol, dialAddr *peerAddr) *Peer { p := &Peer{ - Logger: logger.NewLogger("P2P " + conn.RemoteAddr().String()), - conn: conn, - dialAddr: dialAddr, - bufconn: bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn)), - protocols: protocols, - running: make(map[string]*proto), - disc: make(chan DiscReason), - protoErr: make(chan error), - closed: make(chan struct{}), + Logger: logger.NewLogger("P2P " + conn.RemoteAddr().String()), + conn: conn, + dialAddr: dialAddr, + bufconn: bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn)), + protocols: protocols, + running: make(map[string]*proto), + disc: make(chan DiscReason), + protoErr: make(chan error), + closed: make(chan struct{}), + cryptoReady: make(chan struct{}), } return p } @@ -240,6 +242,7 @@ func (p *Peer) loop() (reason DiscReason, err error) { go readLoop(readMsg, readErr, readNext) readNext <- true + close(p.cryptoReady) if p.runBaseProtocol { p.startBaseProtocol() } @@ -353,6 +356,7 @@ func (p *Peer) handleCryptoHandshake() (loop readLoop, err error) { // 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 { + p.Debugf("unable to setup secure session: %v", err) return } loop = func(msg chan<- Msg, err chan<- error, next <-chan bool) { From c5001a4722ab5e1dbc9710a0fc44da8bb1314aae Mon Sep 17 00:00:00 2001 From: zelig Date: Wed, 21 Jan 2015 16:53:13 +0000 Subject: [PATCH 20/25] add temporary forced session token generation --- p2p/crypto.go | 3 +++ p2p/peer.go | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/p2p/crypto.go b/p2p/crypto.go index e8f4d551bd2b..f5307cd5a763 100644 --- a/p2p/crypto.go +++ b/p2p/crypto.go @@ -103,6 +103,9 @@ func (self *cryptoId) Run(conn io.ReadWriter, remotePubKeyS []byte, sessionToken if auth, initNonce, randomPrivKey, _, err = self.startHandshake(remotePubKeyS, sessionToken); err != nil { return } + if sessionToken != nil { + clogger.Debugf("session-token: %v", hexkey(sessionToken)) + } clogger.Debugf("initiator-nonce: %v", hexkey(initNonce)) clogger.Debugf("initiator-random-private-key: %v", hexkey(crypto.FromECDSA(randomPrivKey))) randomPublicKeyS, _ := ExportPublicKey(&randomPrivKey.PublicKey) diff --git a/p2p/peer.go b/p2p/peer.go index 818f80580269..99f1a61d38a0 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -3,6 +3,7 @@ package p2p import ( "bufio" "bytes" + "crypto/rand" "fmt" "io" "io/ioutil" @@ -342,6 +343,10 @@ func (p *Peer) handleCryptoHandshake() (loop readLoop, err error) { // it is survived by an encrypted readwriter var initiator bool var sessionToken []byte + sessionToken = make([]byte, keyLen) + if _, err = rand.Read(sessionToken); err != nil { + return + } if p.dialAddr != nil { // this should have its own method Outgoing() bool initiator = true } From 122644028de66183095b6ec5be4bd63e32501f7e Mon Sep 17 00:00:00 2001 From: zelig Date: Mon, 26 Jan 2015 14:50:12 +0000 Subject: [PATCH 21/25] apply handshake related improvements from p2p.crypto branch --- p2p/crypto.go | 44 +++++++++++++++++++++++--------------------- p2p/crypto_test.go | 14 +++++++------- p2p/peer.go | 2 +- 3 files changed, 31 insertions(+), 29 deletions(-) diff --git a/p2p/crypto.go b/p2p/crypto.go index f5307cd5a763..361012743867 100644 --- a/p2p/crypto.go +++ b/p2p/crypto.go @@ -7,20 +7,20 @@ import ( "io" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/crypto/secp256k1" ethlogger "github.com/ethereum/go-ethereum/logger" "github.com/obscuren/ecies" - "github.com/obscuren/secp256k1-go" ) var clogger = ethlogger.NewLogger("CRYPTOID") -var ( +const ( sskLen int = 16 // ecies.MaxSharedKeyLength(pubKey) / 2 sigLen int = 65 // elliptic S256 pubLen int = 64 // 512 bit pubkey in uncompressed representation without format byte - keyLen int = 32 // ECDSA - msgLen int = 194 // sigLen + keyLen + pubLen + keyLen + 1 = 194 - resLen int = 97 // pubLen + keyLen + 1 + shaLen int = 32 // hash length (for nonce etc) + msgLen int = 194 // sigLen + shaLen + pubLen + shaLen + 1 = 194 + resLen int = 97 // pubLen + shaLen + 1 iHSLen int = 307 // size of the final ECIES payload sent as initiator's handshake rHSLen int = 210 // size of the final ECIES payload sent as receiver's handshake ) @@ -157,7 +157,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(initNonce, recNonce, auth, randomPrivKey, remoteRandomPubKey) + return self.newSession(initiator, initNonce, recNonce, auth, randomPrivKey, remoteRandomPubKey) } /* @@ -198,7 +198,7 @@ func (self *cryptoId) startHandshake(remotePubKeyS, sessionToken []byte) (auth [ return } - var tokenFlag byte + var tokenFlag byte // = 0x00 if sessionToken == nil { // no session token found means we need to generate shared secret. // ecies shared secret is used as initial session token for new peers @@ -216,7 +216,7 @@ func (self *cryptoId) startHandshake(remotePubKeyS, sessionToken []byte) (auth [ // E(remote-pubk, S(ecdhe-random, token^nonce) || H(ecdhe-random-pubk) || pubk || nonce || 0x1) // allocate msgLen long message, var msg []byte = make([]byte, msgLen) - initNonce = msg[msgLen-keyLen-1 : msgLen-1] + initNonce = msg[msgLen-shaLen-1 : msgLen-1] if _, err = rand.Read(initNonce); err != nil { return } @@ -245,9 +245,9 @@ func (self *cryptoId) startHandshake(remotePubKeyS, sessionToken []byte) (auth [ if randomPubKey64, err = ExportPublicKey(&randomPrvKey.PublicKey); err != nil { return } - copy(msg[sigLen:sigLen+keyLen], crypto.Sha3(randomPubKey64)) + copy(msg[sigLen:sigLen+shaLen], crypto.Sha3(randomPubKey64)) // pubkey copied to the correct segment. - copy(msg[sigLen+keyLen:sigLen+keyLen+pubLen], self.pubKeyS) + copy(msg[sigLen+shaLen:sigLen+shaLen+pubLen], self.pubKeyS) // nonce is already in the slice // stick tokenFlag byte to the end msg[msgLen-1] = tokenFlag @@ -295,7 +295,7 @@ func (self *cryptoId) respondToHandshake(auth, remotePubKeyS, sessionToken []byt } // the initiator nonce is read off the end of the message - initNonce = msg[msgLen-keyLen-1 : msgLen-1] + initNonce = msg[msgLen-shaLen-1 : msgLen-1] // I prove that i own prv key (to derive shared secret, and read nonce off encrypted msg) and that I own shared secret // they prove they own the private key belonging to ecdhe-random-pubk // we can now reconstruct the signed message and recover the peers pubkey @@ -311,8 +311,8 @@ func (self *cryptoId) respondToHandshake(auth, remotePubKeyS, sessionToken []byt // now we find ourselves a long task too, fill it random var resp = make([]byte, resLen) - // generate keyLen long nonce - respNonce = resp[pubLen : pubLen+keyLen] + // generate shaLen long nonce + respNonce = resp[pubLen : pubLen+shaLen] if _, err = rand.Read(respNonce); err != nil { return } @@ -350,7 +350,7 @@ func (self *cryptoId) completeHandshake(auth []byte) (respNonce []byte, remoteRa return } - respNonce = msg[pubLen : pubLen+keyLen] + respNonce = msg[pubLen : pubLen+shaLen] var remoteRandomPubKeyS = msg[:pubLen] if remoteRandomPubKey, err = ImportPublicKey(remoteRandomPubKeyS); err != nil { return @@ -364,7 +364,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(initNonce, respNonce, auth []byte, privKey *ecdsa.PrivateKey, remoteRandomPubKey *ecdsa.PublicKey) (sessionToken []byte, rw *secretRW, err error) { +func (self *cryptoId) 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 @@ -382,12 +382,14 @@ func (self *cryptoId) newSession(initNonce, respNonce, auth []byte, privKey *ecd // mac-secret = crypto.Sha3(ecdhe-shared-secret || aes-secret) var macSecret = crypto.Sha3(append(dhSharedSecret, aesSecret...)) // # destroy ecdhe-shared-secret - // egress-mac = crypto.Sha3(mac-secret^nonce || auth) - var egressMac = crypto.Sha3(append(Xor(macSecret, respNonce), auth...)) - // # destroy nonce - // ingress-mac = crypto.Sha3(mac-secret^initiator-nonce || auth), - var ingressMac = crypto.Sha3(append(Xor(macSecret, initNonce), auth...)) - // # destroy remote-nonce + var egressMac, ingressMac []byte + if initiator { + egressMac = Xor(macSecret, respNonce) + ingressMac = Xor(macSecret, initNonce) + } else { + egressMac = Xor(macSecret, initNonce) + ingressMac = Xor(macSecret, respNonce) + } rw = &secretRW{ aesSecret: aesSecret, macSecret: macSecret, diff --git a/p2p/crypto_test.go b/p2p/crypto_test.go index 47b16040ae5c..919d38df6937 100644 --- a/p2p/crypto_test.go +++ b/p2p/crypto_test.go @@ -106,12 +106,12 @@ func TestCryptoHandshake(t *testing.T) { } // now both parties should have the same session parameters - initSessionToken, initSecretRW, err := initiator.newSession(initNonce, recNonce, auth, randomPrivKey, remoteRandomPubKey) + initSessionToken, initSecretRW, err := initiator.newSession(true, initNonce, recNonce, auth, randomPrivKey, remoteRandomPubKey) if err != nil { t.Errorf("%v", err) } - recSessionToken, recSecretRW, err := receiver.newSession(remoteInitNonce, remoteRecNonce, auth, remoteRandomPrivKey, remoteInitRandomPubKey) + recSessionToken, recSecretRW, err := receiver.newSession(false, remoteInitNonce, remoteRecNonce, auth, remoteRandomPrivKey, remoteInitRandomPubKey) if err != nil { t.Errorf("%v", err) } @@ -136,11 +136,11 @@ func TestCryptoHandshake(t *testing.T) { if !bytes.Equal(initSecretRW.macSecret, recSecretRW.macSecret) { t.Errorf("macSecrets do not match") } - if !bytes.Equal(initSecretRW.egressMac, recSecretRW.egressMac) { - t.Errorf("egressMacs do not match") + if !bytes.Equal(initSecretRW.egressMac, recSecretRW.ingressMac) { + t.Errorf("initiator's egressMac do not match receiver's ingressMac") } - if !bytes.Equal(initSecretRW.ingressMac, recSecretRW.ingressMac) { - t.Errorf("ingressMacs do not match") + if !bytes.Equal(initSecretRW.ingressMac, recSecretRW.egressMac) { + t.Errorf("initiator's inressMac do not match receiver's egressMac") } } @@ -191,7 +191,7 @@ func TestPeersHandshake(t *testing.T) { <-receiver.cryptoReady close(ready) }() - timeout := time.After(1 * time.Second) + timeout := time.After(10 * time.Second) select { case <-ready: case <-timeout: diff --git a/p2p/peer.go b/p2p/peer.go index 99f1a61d38a0..62df58f8deb9 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -343,7 +343,7 @@ func (p *Peer) handleCryptoHandshake() (loop readLoop, err error) { // it is survived by an encrypted readwriter var initiator bool var sessionToken []byte - sessionToken = make([]byte, keyLen) + sessionToken = make([]byte, shaLen) if _, err = rand.Read(sessionToken); err != nil { return } From 2cd375cefd787af4f4e44ebdba4cc8877a1edae5 Mon Sep 17 00:00:00 2001 From: zelig Date: Mon, 26 Jan 2015 16:16:23 +0000 Subject: [PATCH 22/25] make crypto handshake calls package level, store privateKey on peer + tests ok --- p2p/crypto.go | 87 ++++++++++++---------------------------------- p2p/crypto_test.go | 25 ++++++------- p2p/peer.go | 27 +++++++++----- 3 files changed, 52 insertions(+), 87 deletions(-) diff --git a/p2p/crypto.go b/p2p/crypto.go index 361012743867..6a2b99e9377d 100644 --- a/p2p/crypto.go +++ b/p2p/crypto.go @@ -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) diff --git a/p2p/crypto_test.go b/p2p/crypto_test.go index 919d38df6937..f55588ce2b37 100644 --- a/p2p/crypto_test.go +++ b/p2p/crypto_test.go @@ -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 } diff --git a/p2p/peer.go b/p2p/peer.go index 62df58f8deb9..e82bca222b6d 100644 --- a/p2p/peer.go +++ b/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 } From 295b59c87edb84888b72e21475c43c1fa946912f Mon Sep 17 00:00:00 2001 From: zelig Date: Mon, 26 Jan 2015 16:58:58 +0000 Subject: [PATCH 23/25] get rid of Private Key in ClientIdentity --- p2p/client_identity.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/p2p/client_identity.go b/p2p/client_identity.go index fca2756bd641..ef01f1ad7827 100644 --- a/p2p/client_identity.go +++ b/p2p/client_identity.go @@ -7,9 +7,8 @@ import ( // ClientIdentity represents the identity of a peer. type ClientIdentity interface { - String() string // human readable identity - Pubkey() []byte // 512-bit public key - PrivKey() []byte // 512-bit private key + String() string // human readable identity + Pubkey() []byte // 512-bit public key } type SimpleClientIdentity struct { @@ -22,7 +21,7 @@ type SimpleClientIdentity struct { pubkey []byte } -func NewSimpleClientIdentity(clientIdentifier string, version string, customIdentifier string, privkey []byte, pubkey []byte) *SimpleClientIdentity { +func NewSimpleClientIdentity(clientIdentifier string, version string, customIdentifier string, pubkey []byte) *SimpleClientIdentity { clientIdentity := &SimpleClientIdentity{ clientIdentifier: clientIdentifier, version: version, @@ -30,7 +29,6 @@ func NewSimpleClientIdentity(clientIdentifier string, version string, customIden os: runtime.GOOS, implementation: runtime.Version(), pubkey: pubkey, - privkey: privkey, } return clientIdentity From 154fcbff5e110c7e2a3a1bab6b68030b2c99c3d9 Mon Sep 17 00:00:00 2001 From: zelig Date: Thu, 29 Jan 2015 01:49:50 +0000 Subject: [PATCH 24/25] fix clientidentity test after privkey removed --- p2p/client_identity_test.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/p2p/client_identity_test.go b/p2p/client_identity_test.go index 61c34fbf1167..7b62f05ef02e 100644 --- a/p2p/client_identity_test.go +++ b/p2p/client_identity_test.go @@ -8,12 +8,8 @@ import ( ) func TestClientIdentity(t *testing.T) { - clientIdentity := NewSimpleClientIdentity("Ethereum(G)", "0.5.16", "test", []byte("privkey"), []byte("pubkey")) - key := clientIdentity.Privkey() - if !bytes.Equal(key, []byte("privkey")) { - t.Errorf("Expected Privkey to be %x, got %x", key, []byte("privkey")) - } - key = clientIdentity.Pubkey() + clientIdentity := NewSimpleClientIdentity("Ethereum(G)", "0.5.16", "test", []byte("pubkey")) + key := clientIdentity.Pubkey() if !bytes.Equal(key, []byte("pubkey")) { t.Errorf("Expected Pubkey to be %x, got %x", key, []byte("pubkey")) } From 55ea1a7bc9d471565ead5eead42b8f118cdea6c5 Mon Sep 17 00:00:00 2001 From: zelig Date: Thu, 29 Jan 2015 03:16:10 +0000 Subject: [PATCH 25/25] key generation abstracted out, for testing with deterministic keys --- p2p/crypto.go | 41 ++++++++++++++++++++++++++---- p2p/crypto_test.go | 63 ++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 92 insertions(+), 12 deletions(-) diff --git a/p2p/crypto.go b/p2p/crypto.go index 6a2b99e9377d..cb0534cba092 100644 --- a/p2p/crypto.go +++ b/p2p/crypto.go @@ -1,6 +1,7 @@ package p2p import ( + // "binary" "crypto/ecdsa" "crypto/rand" "fmt" @@ -38,6 +39,33 @@ func (self hexkey) String() string { return fmt.Sprintf("(%d) %x", len(self), []byte(self)) } +var nonceF = func(b []byte) (n int, err error) { + return rand.Read(b) +} + +var step = 0 +var detnonceF = func(b []byte) (n int, err error) { + step++ + copy(b, crypto.Sha3([]byte("privacy"+string(step)))) + fmt.Printf("detkey %v: %v\n", step, hexkey(b)) + return +} + +var keyF = func() (priv *ecdsa.PrivateKey, err error) { + priv, err = ecdsa.GenerateKey(crypto.S256(), rand.Reader) + if err != nil { + return + } + return +} + +var detkeyF = func() (priv *ecdsa.PrivateKey, err error) { + s := make([]byte, 32) + detnonceF(s) + priv = crypto.ToECDSA(s) + return +} + /* NewSecureSession(connection, privateKey, remotePublicKey, sessionToken, initiator) is called when the peer connection starts to set up a secure session by performing a crypto handshake. @@ -53,7 +81,6 @@ NewSecureSession(connection, privateKey, remotePublicKey, sessionToken, initiato It returns a secretRW which implements the MsgReadWriter interface. */ - 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 @@ -178,7 +205,8 @@ func startHandshake(prvKey *ecdsa.PrivateKey, remotePubKeyS, sessionToken []byte // allocate msgLen long message, var msg []byte = make([]byte, msgLen) initNonce = msg[msgLen-shaLen-1 : msgLen-1] - if _, err = rand.Read(initNonce); err != nil { + fmt.Printf("init-nonce: ") + if _, err = nonceF(initNonce); err != nil { return } // create known message @@ -187,7 +215,8 @@ func startHandshake(prvKey *ecdsa.PrivateKey, remotePubKeyS, sessionToken []byte var sharedSecret = Xor(sessionToken, initNonce) // generate random keypair to use for signing - if randomPrvKey, err = crypto.GenerateKey(); err != nil { + fmt.Printf("init-random-ecdhe-private-key: ") + if randomPrvKey, err = keyF(); err != nil { return } // sign shared secret (message known to both parties): shared-secret @@ -278,11 +307,13 @@ func respondToHandshake(auth []byte, prvKey *ecdsa.PrivateKey, remotePubKeyS, se var resp = make([]byte, resLen) // generate shaLen long nonce respNonce = resp[pubLen : pubLen+shaLen] - if _, err = rand.Read(respNonce); err != nil { + fmt.Printf("rec-nonce: ") + if _, err = nonceF(respNonce); err != nil { return } // generate random keypair for session - if randomPrivKey, err = crypto.GenerateKey(); err != nil { + fmt.Printf("rec-random-ecdhe-private-key: ") + if randomPrivKey, err = keyF(); err != nil { return } // responder auth message diff --git a/p2p/crypto_test.go b/p2p/crypto_test.go index f55588ce2b37..a4bf14ba64da 100644 --- a/p2p/crypto_test.go +++ b/p2p/crypto_test.go @@ -2,9 +2,7 @@ package p2p import ( "bytes" - // "crypto/ecdsa" - // "crypto/elliptic" - // "crypto/rand" + "crypto/ecdsa" "fmt" "net" "testing" @@ -71,11 +69,60 @@ func TestSharedSecret(t *testing.T) { } func TestCryptoHandshake(t *testing.T) { + testCryptoHandshakeWithGen(false, t) +} + +func TestTokenCryptoHandshake(t *testing.T) { + testCryptoHandshakeWithGen(true, t) +} + +func TestDetCryptoHandshake(t *testing.T) { + defer testlog(t).detach() + tmpkeyF := keyF + keyF = detkeyF + tmpnonceF := nonceF + nonceF = detnonceF + testCryptoHandshakeWithGen(false, t) + keyF = tmpkeyF + nonceF = tmpnonceF +} + +func TestDetTokenCryptoHandshake(t *testing.T) { + defer testlog(t).detach() + tmpkeyF := keyF + keyF = detkeyF + tmpnonceF := nonceF + nonceF = detnonceF + testCryptoHandshakeWithGen(true, t) + keyF = tmpkeyF + nonceF = tmpnonceF +} + +func testCryptoHandshakeWithGen(token bool, t *testing.T) { + fmt.Printf("init-private-key: ") + prv0, err := keyF() + if err != nil { + t.Errorf("%v", err) + return + } + fmt.Printf("rec-private-key: ") + prv1, err := keyF() + if err != nil { + t.Errorf("%v", err) + return + } + var nonce []byte + if token { + fmt.Printf("session-token: ") + nonce = make([]byte, shaLen) + nonceF(nonce) + } + testCryptoHandshake(prv0, prv1, nonce, t) +} + +func testCryptoHandshake(prv0, prv1 *ecdsa.PrivateKey, sessionToken []byte, t *testing.T) { var err error - var sessionToken []byte - prv0, _ := crypto.GenerateKey() // = ecdsa.GenerateKey(crypto.S256(), rand.Reader) pub0 := &prv0.PublicKey - prv1, _ := crypto.GenerateKey() pub1 := &prv1.PublicKey pub0s := crypto.FromECDSAPub(pub0) @@ -87,12 +134,14 @@ func TestCryptoHandshake(t *testing.T) { if err != nil { t.Errorf("%v", err) } + fmt.Printf("-> %v\n", hexkey(auth)) // receiver reads auth and responds with response response, remoteRecNonce, remoteInitNonce, remoteRandomPrivKey, remoteInitRandomPubKey, err := respondToHandshake(auth, prv1, pub0s, sessionToken) if err != nil { t.Errorf("%v", err) } + fmt.Printf("<- %v\n", hexkey(response)) // initiator reads receiver's response and the key exchange completes recNonce, remoteRandomPubKey, _, err := completeHandshake(response, prv0) @@ -111,7 +160,7 @@ func TestCryptoHandshake(t *testing.T) { t.Errorf("%v", err) } - fmt.Printf("\nauth (%v) %x\n\nresp (%v) %x\n\n", len(auth), auth, len(response), response) + // fmt.Printf("\nauth (%v) %x\n\nresp (%v) %x\n\n", len(auth), auth, len(response), response) // fmt.Printf("\nauth %x\ninitNonce %x\nresponse%x\nremoteRecNonce %x\nremoteInitNonce %x\nremoteRandomPubKey %x\nrecNonce %x\nremoteInitRandomPubKey %x\ninitSessionToken %x\n\n", auth, initNonce, response, remoteRecNonce, remoteInitNonce, remoteRandomPubKey, recNonce, remoteInitRandomPubKey, initSessionToken)