From 4e52adb84a2cda50877aa92584c9d1675cf51b62 Mon Sep 17 00:00:00 2001 From: zelig Date: Sun, 18 Jan 2015 09:46:08 +0000 Subject: add crypto auth logic to p2p --- p2p/crypto.go | 174 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 p2p/crypto.go (limited to 'p2p/crypto.go') diff --git a/p2p/crypto.go b/p2p/crypto.go new file mode 100644 index 000000000..9204fa9d0 --- /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 +} -- cgit v1.2.3 From b855f671a508a7e8160cbdd27197ba83310b264c Mon Sep 17 00:00:00 2001 From: zelig Date: Sun, 18 Jan 2015 23:53:45 +0000 Subject: 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(-) (limited to 'p2p/crypto.go') diff --git a/p2p/crypto.go b/p2p/crypto.go index 9204fa9d0..6e3f360d9 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] } -- cgit v1.2.3 From 714b955d6e03a822af80b566b0fa73098761f57a Mon Sep 17 00:00:00 2001 From: zelig Date: Mon, 19 Jan 2015 00:55:24 +0000 Subject: 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(-) (limited to 'p2p/crypto.go') diff --git a/p2p/crypto.go b/p2p/crypto.go index 6e3f360d9..643bd431e 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) -- cgit v1.2.3 From 3b6385b14624faee26445a3d98fa94efdb30d29a Mon Sep 17 00:00:00 2001 From: zelig Date: Mon, 19 Jan 2015 01:24:09 +0000 Subject: handshake test to crypto --- p2p/crypto.go | 2 -- 1 file changed, 2 deletions(-) (limited to 'p2p/crypto.go') diff --git a/p2p/crypto.go b/p2p/crypto.go index 643bd431e..10c82d3a1 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" -- cgit v1.2.3 From 489d956283390b701473edd4a597afea2c426d41 Mon Sep 17 00:00:00 2001 From: zelig Date: Mon, 19 Jan 2015 04:53:48 +0000 Subject: completed the test. FAIL now. it crashes at diffie-hellman. ECIES -> secp256k1-go panics --- p2p/crypto.go | 45 +++++++++++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 16 deletions(-) (limited to 'p2p/crypto.go') diff --git a/p2p/crypto.go b/p2p/crypto.go index 10c82d3a1..37c6e1fc9 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)) -- cgit v1.2.3 From 1803c65e4097b9d6cb83f72a8a09aeddcc01f685 Mon Sep 17 00:00:00 2001 From: zelig Date: Mon, 19 Jan 2015 11:21:13 +0000 Subject: integrate cryptoId into peer and connection lifecycle --- p2p/crypto.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'p2p/crypto.go') diff --git a/p2p/crypto.go b/p2p/crypto.go index 37c6e1fc9..728b8e884 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) -- cgit v1.2.3 From e252c634cb40c8ef7f9bcd542f5418a937929620 Mon Sep 17 00:00:00 2001 From: zelig Date: Mon, 19 Jan 2015 23:42:13 +0000 Subject: 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 ++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 38 insertions(+), 15 deletions(-) (limited to 'p2p/crypto.go') diff --git a/p2p/crypto.go b/p2p/crypto.go index 728b8e884..b6d600826 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 -- cgit v1.2.3 From 364b7832811c19106456b3b30a34c7097a0b526e Mon Sep 17 00:00:00 2001 From: zelig Date: Tue, 20 Jan 2015 16:47:46 +0000 Subject: 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 ++++++++++++++++++++++++++++++++-------------------------- 1 file changed, 61 insertions(+), 50 deletions(-) (limited to 'p2p/crypto.go') diff --git a/p2p/crypto.go b/p2p/crypto.go index b6d600826..dbef022cc 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 { -- cgit v1.2.3 From 4afde4e738e3981e086d6e8710c3d711d0e8531d Mon Sep 17 00:00:00 2001 From: zelig Date: Wed, 21 Jan 2015 10:22:07 +0000 Subject: add code documentation --- p2p/crypto.go | 62 ++++++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 47 insertions(+), 15 deletions(-) (limited to 'p2p/crypto.go') diff --git a/p2p/crypto.go b/p2p/crypto.go index dbef022cc..8551e317c 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)) -- cgit v1.2.3 From 20aade56c3057a221d7fa7152a4969d5f8f980d5 Mon Sep 17 00:00:00 2001 From: zelig Date: Wed, 21 Jan 2015 14:45:53 +0000 Subject: 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(-) (limited to 'p2p/crypto.go') diff --git a/p2p/crypto.go b/p2p/crypto.go index 8551e317c..91d60aa7e 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. -- cgit v1.2.3 From faa069a126da29a246193713568634e5be6edd2d Mon Sep 17 00:00:00 2001 From: zelig Date: Wed, 21 Jan 2015 16:22:49 +0000 Subject: 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 +++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 47 insertions(+), 6 deletions(-) (limited to 'p2p/crypto.go') diff --git a/p2p/crypto.go b/p2p/crypto.go index 91d60aa7e..e8f4d551b 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 } -- cgit v1.2.3 From 54252ede3177cb169fbb9e4824a31ce58cb0316c Mon Sep 17 00:00:00 2001 From: zelig Date: Wed, 21 Jan 2015 16:53:13 +0000 Subject: add temporary forced session token generation --- p2p/crypto.go | 3 +++ 1 file changed, 3 insertions(+) (limited to 'p2p/crypto.go') diff --git a/p2p/crypto.go b/p2p/crypto.go index e8f4d551b..f5307cd5a 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) -- cgit v1.2.3 From 4499743522d32990614c7d900d746e998a1b81ed Mon Sep 17 00:00:00 2001 From: zelig Date: Mon, 26 Jan 2015 14:50:12 +0000 Subject: apply handshake related improvements from p2p.crypto branch --- p2p/crypto.go | 44 +++++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 21 deletions(-) (limited to 'p2p/crypto.go') diff --git a/p2p/crypto.go b/p2p/crypto.go index f5307cd5a..361012743 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, -- cgit v1.2.3 From 68205dec9ff8ab7d16c61f5e32b104d7aa20b352 Mon Sep 17 00:00:00 2001 From: zelig Date: Mon, 26 Jan 2015 16:16:23 +0000 Subject: make crypto handshake calls package level, store privateKey on peer + tests ok --- p2p/crypto.go | 87 ++++++++++++++++------------------------------------------- 1 file changed, 23 insertions(+), 64 deletions(-) (limited to 'p2p/crypto.go') diff --git a/p2p/crypto.go b/p2p/crypto.go index 361012743..6a2b99e93 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) -- cgit v1.2.3 From 2e48d39fc7fc9b8d65e9b6e0ce6863b9374f2233 Mon Sep 17 00:00:00 2001 From: zelig Date: Thu, 29 Jan 2015 03:16:10 +0000 Subject: key generation abstracted out, for testing with deterministic keys --- p2p/crypto.go | 41 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 36 insertions(+), 5 deletions(-) (limited to 'p2p/crypto.go') diff --git a/p2p/crypto.go b/p2p/crypto.go index 6a2b99e93..cb0534cba 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 -- cgit v1.2.3 From 5bdc1159433138d92ed6fefb253e3c6ed3a43995 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 5 Feb 2015 03:07:58 +0100 Subject: p2p: integrate p2p/discover Overview of changes: - ClientIdentity has been removed, use discover.NodeID - Server now requires a private key to be set (instead of public key) - Server performs the encryption handshake before launching Peer - Dial logic takes peers from discover table - Encryption handshake code has been cleaned up a bit - baseProtocol is gone because we don't exchange peers anymore - Some parts of baseProtocol have moved into Peer instead --- p2p/crypto.go | 429 ++++++++++++++++++++++++++-------------------------------- 1 file changed, 194 insertions(+), 235 deletions(-) (limited to 'p2p/crypto.go') diff --git a/p2p/crypto.go b/p2p/crypto.go index cb0534cba..2692d708c 100644 --- a/p2p/crypto.go +++ b/p2p/crypto.go @@ -10,28 +10,25 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto/secp256k1" ethlogger "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/p2p/discover" "github.com/obscuren/ecies" ) var clogger = ethlogger.NewLogger("CRYPTOID") 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 - 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 -) + sskLen = 16 // ecies.MaxSharedKeyLength(pubKey) / 2 + sigLen = 65 // elliptic S256 + pubLen = 64 // 512 bit pubkey in uncompressed representation without format byte + shaLen = 32 // hash length (for nonce etc) -// 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 -} + authMsgLen = sigLen + shaLen + pubLen + shaLen + 1 + authRespLen = pubLen + shaLen + 1 + + eciesBytes = 65 + 16 + 32 + iHSLen = authMsgLen + eciesBytes // size of the final ECIES payload sent as initiator's handshake + rHSLen = authRespLen + eciesBytes // size of the final ECIES payload sent as receiver's handshake +) type hexkey []byte @@ -39,150 +36,73 @@ 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 +func encHandshake(conn io.ReadWriter, prv *ecdsa.PrivateKey, dial *discover.Node) ( + remoteID discover.NodeID, + sessionToken []byte, + err error, +) { + if dial == nil { + var remotePubkey []byte + sessionToken, remotePubkey, err = inboundEncHandshake(conn, prv, nil) + copy(remoteID[:], remotePubkey) + } else { + remoteID = dial.ID + sessionToken, err = outboundEncHandshake(conn, prv, remoteID[:], nil) + } + return remoteID, sessionToken, err } -var keyF = func() (priv *ecdsa.PrivateKey, err error) { - priv, err = ecdsa.GenerateKey(crypto.S256(), rand.Reader) +// outboundEncHandshake negotiates a session token on conn. +// it should be called on the dialing side of the connection. +// +// privateKey is the local client's private key +// remotePublicKey is the remote peer's node ID +// sessionToken is the token from a previous session with this node. +func outboundEncHandshake(conn io.ReadWriter, prvKey *ecdsa.PrivateKey, remotePublicKey []byte, sessionToken []byte) ( + newSessionToken []byte, + err error, +) { + auth, initNonce, randomPrivKey, err := authMsg(prvKey, remotePublicKey, sessionToken) if err != nil { - return + return nil, err } - 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. - - connection is (a buffered) network connection. - - 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 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 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 = startHandshake(prvKey, 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) - 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 = completeHandshake(response, prvKey); err != nil { - return - } - clogger.Debugf("receiver-nonce: %v", hexkey(recNonce)) - remoteRandomPubKeyS, _ := ExportPublicKey(remoteRandomPubKey) - clogger.Debugf("receiver-random-public-key: %v", hexkey(remoteRandomPubKeyS)) - - } else { - 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 - var response []byte - if response, recNonce, initNonce, randomPrivKey, remoteRandomPubKey, err = respondToHandshake(auth, prvKey, remotePubKeyS, sessionToken); err != nil { - return - } - 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)) + if sessionToken != nil { + clogger.Debugf("session-token: %v", hexkey(sessionToken)) } - return newSession(initiator, initNonce, recNonce, auth, randomPrivKey, remoteRandomPubKey) -} -/* -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) { - 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)) + 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 nil, err } - return crypto.ToECDSAPub(pubKey65), nil -} + clogger.Debugf("initiator handshake: %v", hexkey(auth)) -/* -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") + response := make([]byte, rHSLen) + if _, err = io.ReadFull(conn, response); err != nil { + return nil, err + } + recNonce, remoteRandomPubKey, _, err := completeHandshake(response, prvKey) + if err != nil { + return nil, err } - 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. + clogger.Debugf("receiver-nonce: %v", hexkey(recNonce)) + remoteRandomPubKeyS, _ := exportPublicKey(remoteRandomPubKey) + clogger.Debugf("receiver-random-public-key: %v", hexkey(remoteRandomPubKeyS)) + return newSession(initNonce, recNonce, randomPrivKey, remoteRandomPubKey) +} -The first return value is the auth message that is to be sent out to the remote receiver. -*/ -func startHandshake(prvKey *ecdsa.PrivateKey, remotePubKeyS, sessionToken []byte) (auth []byte, initNonce []byte, randomPrvKey *ecdsa.PrivateKey, remotePubKey *ecdsa.PublicKey, err error) { +// authMsg creates the initiator handshake. +func authMsg(prvKey *ecdsa.PrivateKey, remotePubKeyS, sessionToken []byte) ( + auth, initNonce []byte, + randomPrvKey *ecdsa.PrivateKey, + err error, +) { // session init, common to both parties - if remotePubKey, err = ImportPublicKey(remotePubKeyS); err != nil { + remotePubKey, err := importPublicKey(remotePubKeyS) + if err != nil { return } @@ -203,20 +123,18 @@ func startHandshake(prvKey *ecdsa.PrivateKey, remotePubKeyS, sessionToken []byte //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) - initNonce = msg[msgLen-shaLen-1 : msgLen-1] - fmt.Printf("init-nonce: ") - if _, err = nonceF(initNonce); err != nil { + var msg []byte = make([]byte, authMsgLen) + initNonce = msg[authMsgLen-shaLen-1 : authMsgLen-1] + if _, err = rand.Read(initNonce); err != nil { return } // create known message // ecdh-shared-secret^nonce for new peers // token^nonce for old peers - var sharedSecret = Xor(sessionToken, initNonce) + var sharedSecret = xor(sessionToken, initNonce) // generate random keypair to use for signing - fmt.Printf("init-random-ecdhe-private-key: ") - if randomPrvKey, err = keyF(); err != nil { + if randomPrvKey, err = crypto.GenerateKey(); err != nil { return } // sign shared secret (message known to both parties): shared-secret @@ -232,11 +150,11 @@ func startHandshake(prvKey *ecdsa.PrivateKey, remotePubKeyS, sessionToken []byte copy(msg, signature) // copy signed-shared-secret // H(ecdhe-random-pubk) var randomPubKey64 []byte - if randomPubKey64, err = ExportPublicKey(&randomPrvKey.PublicKey); err != nil { + if randomPubKey64, err = exportPublicKey(&randomPrvKey.PublicKey); err != nil { return } var pubKey64 []byte - if pubKey64, err = ExportPublicKey(&prvKey.PublicKey); err != nil { + if pubKey64, err = exportPublicKey(&prvKey.PublicKey); err != nil { return } copy(msg[sigLen:sigLen+shaLen], crypto.Sha3(randomPubKey64)) @@ -244,36 +162,98 @@ func startHandshake(prvKey *ecdsa.PrivateKey, remotePubKeyS, sessionToken []byte copy(msg[sigLen+shaLen:sigLen+shaLen+pubLen], pubKey64) // nonce is already in the slice // stick tokenFlag byte to the end - msg[msgLen-1] = tokenFlag + msg[authMsgLen-1] = tokenFlag // encrypt using remote-pubk // auth = eciesEncrypt(remote-pubk, msg) - if auth, err = crypto.Encrypt(remotePubKey, msg); err != nil { return } - return } -/* -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 respondToHandshake(auth []byte, prvKey *ecdsa.PrivateKey, remotePubKeyS, sessionToken []byte) (authResp []byte, respNonce []byte, initNonce []byte, randomPrivKey *ecdsa.PrivateKey, remoteRandomPubKey *ecdsa.PublicKey, err error) { +// 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 completeHandshake(auth []byte, prvKey *ecdsa.PrivateKey) ( + respNonce []byte, + remoteRandomPubKey *ecdsa.PublicKey, + tokenFlag bool, + err error, +) { var msg []byte - var remotePubKey *ecdsa.PublicKey - if remotePubKey, err = ImportPublicKey(remotePubKeyS); err != nil { + // they prove that msg is meant for me, + // I prove I possess private key if i can read it + if msg, err = crypto.Decrypt(prvKey, auth); err != nil { return } + respNonce = msg[pubLen : pubLen+shaLen] + var remoteRandomPubKeyS = msg[:pubLen] + if remoteRandomPubKey, err = importPublicKey(remoteRandomPubKeyS); err != nil { + return + } + if msg[authRespLen-1] == 0x01 { + tokenFlag = true + } + return +} + +// inboundEncHandshake negotiates a session token on conn. +// it should be called on the listening side of the connection. +// +// privateKey is the local client's private key +// sessionToken is the token from a previous session with this node. +func inboundEncHandshake(conn io.ReadWriter, prvKey *ecdsa.PrivateKey, sessionToken []byte) ( + token, remotePubKey []byte, + err error, +) { + // 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. + auth := make([]byte, iHSLen) + if _, err := io.ReadFull(conn, auth); err != nil { + return nil, nil, err + } + response, recNonce, initNonce, remotePubKey, randomPrivKey, remoteRandomPubKey, err := authResp(auth, sessionToken, prvKey) + if err != nil { + return nil, nil, err + } + 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 nil, nil, err + } + clogger.Debugf("receiver handshake:\n%v", hexkey(response)) + token, err = newSession(initNonce, recNonce, randomPrivKey, remoteRandomPubKey) + return token, remotePubKey, err +} + +// authResp is called by peer if it accepted (but not +// initiated) the connection from the remote. It is passed the initiator +// handshake received and the 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 authResp(auth, sessionToken []byte, prvKey *ecdsa.PrivateKey) ( + authResp, respNonce, initNonce, remotePubKeyS []byte, + randomPrivKey *ecdsa.PrivateKey, + remoteRandomPubKey *ecdsa.PublicKey, + err error, +) { // they prove that msg is meant for me, // I prove I possess private key if i can read it - if msg, err = crypto.Decrypt(prvKey, auth); err != nil { + msg, err := crypto.Decrypt(prvKey, auth) + if err != nil { return } + remotePubKeyS = msg[sigLen+shaLen : sigLen+shaLen+pubLen] + remotePubKey, _ := importPublicKey(remotePubKeyS) + var tokenFlag byte if sessionToken == nil { // no session token found means we need to generate shared secret. @@ -289,42 +269,42 @@ func respondToHandshake(auth []byte, prvKey *ecdsa.PrivateKey, remotePubKeyS, se } // the initiator nonce is read off the end of the message - 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 - var signedMsg = Xor(sessionToken, initNonce) + initNonce = msg[authMsgLen-shaLen-1 : authMsgLen-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 + var signedMsg = xor(sessionToken, initNonce) var remoteRandomPubKeyS []byte if remoteRandomPubKeyS, err = secp256k1.RecoverPubkey(signedMsg, msg[:sigLen]); err != nil { return } // convert to ECDSA standard - if remoteRandomPubKey, err = ImportPublicKey(remoteRandomPubKeyS); err != nil { + if remoteRandomPubKey, err = importPublicKey(remoteRandomPubKeyS); err != nil { return } // now we find ourselves a long task too, fill it random - var resp = make([]byte, resLen) + var resp = make([]byte, authRespLen) // generate shaLen long nonce respNonce = resp[pubLen : pubLen+shaLen] - fmt.Printf("rec-nonce: ") - if _, err = nonceF(respNonce); err != nil { + if _, err = rand.Read(respNonce); err != nil { return } // generate random keypair for session - fmt.Printf("rec-random-ecdhe-private-key: ") - if randomPrivKey, err = keyF(); err != nil { + if randomPrivKey, err = crypto.GenerateKey(); err != nil { return } // responder auth message // E(remote-pubk, ecdhe-random-pubk || nonce || 0x0) var randomPubKeyS []byte - if randomPubKeyS, err = ExportPublicKey(&randomPrivKey.PublicKey); err != nil { + if randomPubKeyS, err = exportPublicKey(&randomPrivKey.PublicKey); err != nil { return } copy(resp[:pubLen], randomPubKeyS) // nonce is already in the slice - resp[resLen-1] = tokenFlag + resp[authRespLen-1] = tokenFlag // encrypt using remote-pubk // auth = eciesEncrypt(remote-pubk, msg) @@ -335,70 +315,49 @@ func respondToHandshake(auth []byte, prvKey *ecdsa.PrivateKey, remotePubKeyS, se 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 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(prvKey, auth); err != nil { - return +// newSession is called after the handshake is completed. The +// arguments are values negotiated in the handshake. The return value +// is a new session Token to be remembered for the next time we +// connect with this peer. +func newSession(initNonce, respNonce []byte, privKey *ecdsa.PrivateKey, remoteRandomPubKey *ecdsa.PublicKey) ([]byte, error) { + // 3) Now we can trust ecdhe-random-pubk to derive new keys + //ecdhe-shared-secret = ecdh.agree(ecdhe-random, remote-ecdhe-random-pubk) + pubKey := ecies.ImportECDSAPublic(remoteRandomPubKey) + dhSharedSecret, err := ecies.ImportECDSA(privKey).GenerateShared(pubKey, sskLen, sskLen) + if err != nil { + return nil, err } + sharedSecret := crypto.Sha3(dhSharedSecret, crypto.Sha3(respNonce, initNonce)) + sessionToken := crypto.Sha3(sharedSecret) + return sessionToken, nil +} - respNonce = msg[pubLen : pubLen+shaLen] - var remoteRandomPubKeyS = msg[:pubLen] - if remoteRandomPubKey, err = ImportPublicKey(remoteRandomPubKeyS); err != nil { - return - } - if msg[resLen-1] == 0x01 { - tokenFlag = true +// importPublicKey unmarshals 512 bit public keys. +func importPublicKey(pubKey []byte) (pubKeyEC *ecdsa.PublicKey, err error) { + var pubKey65 []byte + switch len(pubKey) { + case 64: + // add 'uncompressed key' flag + 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 + return crypto.ToECDSAPub(pubKey65), nil } -/* -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 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 - pubKey := ecies.ImportECDSAPublic(remoteRandomPubKey) - if dhSharedSecret, err = ecies.ImportECDSA(privKey).GenerateShared(pubKey, sskLen, sskLen); err != nil { - return +func exportPublicKey(pubKeyEC *ecdsa.PublicKey) (pubKey []byte, err error) { + if pubKeyEC == nil { + return nil, fmt.Errorf("no ECDSA public key given") } - var sharedSecret = crypto.Sha3(append(dhSharedSecret, crypto.Sha3(append(respNonce, initNonce...))...)) - sessionToken = crypto.Sha3(sharedSecret) - var aesSecret = crypto.Sha3(append(dhSharedSecret, sharedSecret...)) - var macSecret = crypto.Sha3(append(dhSharedSecret, aesSecret...)) - 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, - 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 + return crypto.FromECDSAPub(pubKeyEC)[1:], nil } -// TODO: optimisation -// should use cipher.xorBytes from crypto/cipher/xor.go for fast xor -func Xor(one, other []byte) (xor []byte) { +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] } - return + return xor } -- cgit v1.2.3