mirror of
https://gitlab.com/pulsechaincom/go-pulse.git
synced 2024-12-23 11:57:17 +00:00
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
This commit is contained in:
parent
58fc2c679b
commit
364b783281
111
p2p/crypto.go
111
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 {
|
||||
|
@ -2,16 +2,76 @@ package p2p
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
// "crypto/ecdsa"
|
||||
// "crypto/elliptic"
|
||||
// "crypto/rand"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/obscuren/ecies"
|
||||
)
|
||||
|
||||
func TestPublicKeyEncoding(t *testing.T) {
|
||||
prv0, _ := crypto.GenerateKey() // = ecdsa.GenerateKey(crypto.S256(), rand.Reader)
|
||||
pub0 := &prv0.PublicKey
|
||||
pub0s := crypto.FromECDSAPub(pub0)
|
||||
pub1, err := ImportPublicKey(pub0s)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
eciesPub1 := ecies.ImportECDSAPublic(pub1)
|
||||
if eciesPub1 == nil {
|
||||
t.Errorf("invalid ecdsa public key")
|
||||
}
|
||||
pub1s, err := ExportPublicKey(pub1)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
if len(pub1s) != 64 {
|
||||
t.Errorf("wrong length expect 64, got", len(pub1s))
|
||||
}
|
||||
pub2, err := ImportPublicKey(pub1s)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
pub2s, err := ExportPublicKey(pub2)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
if !bytes.Equal(pub1s, pub2s) {
|
||||
t.Errorf("exports dont match")
|
||||
}
|
||||
pub2sEC := crypto.FromECDSAPub(pub2)
|
||||
if !bytes.Equal(pub0s, pub2sEC) {
|
||||
t.Errorf("exports dont match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSharedSecret(t *testing.T) {
|
||||
prv0, _ := crypto.GenerateKey() // = ecdsa.GenerateKey(crypto.S256(), rand.Reader)
|
||||
pub0 := &prv0.PublicKey
|
||||
prv1, _ := crypto.GenerateKey()
|
||||
pub1 := &prv1.PublicKey
|
||||
|
||||
ss0, err := ecies.ImportECDSA(prv0).GenerateShared(ecies.ImportECDSAPublic(pub1), sskLen, sskLen)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ss1, err := ecies.ImportECDSA(prv1).GenerateShared(ecies.ImportECDSAPublic(pub0), sskLen, sskLen)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
t.Logf("Secret:\n%v %x\n%v %x", len(ss0), ss0, len(ss0), ss1)
|
||||
if !bytes.Equal(ss0, ss1) {
|
||||
t.Errorf("dont match :(")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCryptoHandshake(t *testing.T) {
|
||||
var err error
|
||||
var sessionToken []byte
|
||||
prv0, _ := crypto.GenerateKey()
|
||||
prv0, _ := crypto.GenerateKey() // = ecdsa.GenerateKey(crypto.S256(), rand.Reader)
|
||||
pub0 := &prv0.PublicKey
|
||||
prv1, _ := crypto.GenerateKey()
|
||||
pub1 := &prv1.PublicKey
|
||||
@ -26,17 +86,35 @@ func TestCryptoHandshake(t *testing.T) {
|
||||
|
||||
// simulate handshake by feeding output to input
|
||||
// initiator sends handshake 'auth'
|
||||
auth, initNonce, randomPrivKey, _, _ := initiator.startHandshake(receiver.pubKeyDER, sessionToken)
|
||||
auth, initNonce, randomPrivKey, _, err := initiator.startHandshake(receiver.pubKeyS, sessionToken)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
|
||||
// receiver reads auth and responds with response
|
||||
response, remoteRecNonce, remoteInitNonce, remoteRandomPrivKey, _ := receiver.respondToHandshake(auth, crypto.FromECDSAPub(pub0), sessionToken)
|
||||
response, remoteRecNonce, remoteInitNonce, remoteRandomPrivKey, remoteInitRandomPubKey, err := receiver.respondToHandshake(auth, crypto.FromECDSAPub(pub0), sessionToken)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
|
||||
// initiator reads receiver's response and the key exchange completes
|
||||
recNonce, remoteRandomPubKey, _, _ := initiator.completeHandshake(response)
|
||||
recNonce, remoteRandomPubKey, _, err := initiator.completeHandshake(response)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
|
||||
// now both parties should have the same session parameters
|
||||
initSessionToken, initSecretRW, _ := initiator.newSession(initNonce, recNonce, auth, randomPrivKey, remoteRandomPubKey)
|
||||
recSessionToken, recSecretRW, _ := receiver.newSession(remoteInitNonce, remoteRecNonce, auth, remoteRandomPrivKey, &randomPrivKey.PublicKey)
|
||||
initSessionToken, initSecretRW, err := initiator.newSession(initNonce, recNonce, auth, randomPrivKey, remoteRandomPubKey)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("%x\n%x\n%x\n%x\n%x\n%x\n%x\n%x\n%x\n%x\n", auth, initNonce, response, remoteRecNonce, remoteInitNonce, remoteRandomPubKey, recNonce, &randomPrivKey.PublicKey, initSessionToken, initSecretRW)
|
||||
recSessionToken, recSecretRW, err := receiver.newSession(remoteInitNonce, remoteRecNonce, auth, remoteRandomPrivKey, remoteInitRandomPubKey)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("\nauth %x\ninitNonce %x\nresponse%x\nremoteRecNonce %x\nremoteInitNonce %x\nremoteRandomPubKey %x\nrecNonce %x\nremoteInitRandomPubKey %x\ninitSessionToken %x\n\n", auth, initNonce, response, remoteRecNonce, remoteInitNonce, remoteRandomPubKey, recNonce, remoteInitRandomPubKey, initSessionToken)
|
||||
|
||||
if !bytes.Equal(initNonce, remoteInitNonce) {
|
||||
t.Errorf("nonces do not match")
|
||||
|
Loading…
Reference in New Issue
Block a user