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
This commit is contained in:
parent
4e52adb84a
commit
b855f671a5
201
p2p/crypto.go
201
p2p/crypto.go
@ -1,11 +1,11 @@
|
|||||||
package p2p
|
package p2p
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
// "bytes"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
// "io"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/obscuren/ecies"
|
"github.com/obscuren/ecies"
|
||||||
@ -13,21 +13,22 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
skLen int = 32 // ecies.MaxSharedKeyLength(pubKey) / 2
|
sskLen int = 16 // ecies.MaxSharedKeyLength(pubKey) / 2
|
||||||
sigLen int = 32 // elliptic S256
|
sigLen int = 65 // elliptic S256
|
||||||
pubKeyLen int = 32 // ECDSA
|
keyLen int = 32 // ECDSA
|
||||||
msgLen int = sigLen + 1 + pubKeyLen + skLen // 97
|
msgLen int = sigLen + 3*keyLen + 1 // 162
|
||||||
|
resLen int = 65
|
||||||
)
|
)
|
||||||
|
|
||||||
//, aesSecret, macSecret, egressMac, ingress
|
// aesSecret, macSecret, egressMac, ingress
|
||||||
type secretRW struct {
|
type secretRW struct {
|
||||||
aesSecret, macSecret, egressMac, ingressMac []byte
|
aesSecret, macSecret, egressMac, ingressMac []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
type cryptoId struct {
|
type cryptoId struct {
|
||||||
prvKey *ecdsa.PrivateKey
|
prvKey *ecdsa.PrivateKey
|
||||||
pubKey *ecdsa.PublicKey
|
pubKey *ecdsa.PublicKey
|
||||||
pubKeyR io.ReaderAt
|
pubKeyDER []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func newCryptoId(id ClientIdentity) (self *cryptoId, err error) {
|
func newCryptoId(id ClientIdentity) (self *cryptoId, err error) {
|
||||||
@ -50,70 +51,151 @@ func newCryptoId(id ClientIdentity) (self *cryptoId, err error) {
|
|||||||
// to be created at server init shared between peers and sessions
|
// to be created at server init shared between peers and sessions
|
||||||
// for reuse, call wth ReadAt, no reset seek needed
|
// for reuse, call wth ReadAt, no reset seek needed
|
||||||
}
|
}
|
||||||
self.pubKeyR = bytes.NewReader(id.Pubkey())
|
self.pubKeyDER = id.Pubkey()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
// initAuth is called by peer if it initiated the connection
|
||||||
func (self *cryptoId) setupAuth(remotePubKeyDER, sessionToken []byte) (auth []byte, nonce []byte, sharedKnowledge []byte, err error) {
|
func (self *cryptoId) initAuth(remotePubKeyDER, sessionToken []byte) (auth []byte, initNonce []byte, remotePubKey *ecdsa.PublicKey, err error) {
|
||||||
// session init, common to both parties
|
// session init, common to both parties
|
||||||
var remotePubKey = crypto.ToECDSAPub(remotePubKeyDER)
|
remotePubKey = crypto.ToECDSAPub(remotePubKeyDER)
|
||||||
if remotePubKey == nil {
|
if remotePubKey == nil {
|
||||||
err = fmt.Errorf("invalid remote public key")
|
err = fmt.Errorf("invalid remote public key")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var sharedSecret []byte
|
|
||||||
// generate shared key from prv and remote pubkey
|
var tokenFlag byte
|
||||||
sharedSecret, err = ecies.ImportECDSA(self.prvKey).GenerateShared(ecies.ImportECDSAPublic(remotePubKey), skLen, skLen)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// check previous session token
|
|
||||||
if sessionToken == nil {
|
if sessionToken == nil {
|
||||||
err = fmt.Errorf("no session token for peer")
|
// no session token found means we need to generate shared secret.
|
||||||
return
|
// 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)
|
var msg []byte = make([]byte, msgLen)
|
||||||
// generate skLen long nonce at the end
|
// generate sskLen long nonce
|
||||||
nonce = msg[msgLen-skLen:]
|
initNonce = msg[msgLen-keyLen-1 : msgLen-1]
|
||||||
if _, err = rand.Read(nonce); err != nil {
|
// nonce = msg[msgLen-sskLen-1 : msgLen-1]
|
||||||
|
if _, err = rand.Read(initNonce); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// create known message
|
// create known message
|
||||||
// should use
|
// ecdh-shared-secret^nonce for new peers
|
||||||
// cipher.xorBytes from crypto/cipher/xor.go for fast xor
|
// token^nonce for old peers
|
||||||
sharedKnowledge = Xor(sharedSecret, sessionToken)
|
var sharedSecret = Xor(sessionToken, initNonce)
|
||||||
var signedMsg = Xor(sharedKnowledge, nonce)
|
|
||||||
|
|
||||||
// generate random keypair to use for signing
|
// generate random keypair to use for signing
|
||||||
var ecdsaRandomPrvKey *ecdsa.PrivateKey
|
var ecdsaRandomPrvKey *ecdsa.PrivateKey
|
||||||
if ecdsaRandomPrvKey, err = crypto.GenerateKey(); err != nil {
|
if ecdsaRandomPrvKey, err = crypto.GenerateKey(); err != nil {
|
||||||
return
|
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 {
|
||||||
|
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(&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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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-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[:sigLen]); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
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]
|
||||||
|
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
|
// var ecdsaRandomPubKey *ecdsa.PublicKey
|
||||||
// ecdsaRandomPubKey= &ecdsaRandomPrvKey.PublicKey
|
// ecdsaRandomPubKey= &ecdsaRandomPrvKey.PublicKey
|
||||||
|
|
||||||
// message known to both parties ecdh-shared-secret^nonce^token
|
// message
|
||||||
var signature []byte
|
// E(remote-pubk, ecdhe-random-pubk || nonce || 0x0)
|
||||||
// signature = sign(ecdhe-random, ecdh-shared-secret^nonce^token)
|
copy(resp[:keyLen], crypto.FromECDSAPub(&ecdsaRandomPrvKey.PublicKey))
|
||||||
// uses secp256k1.Sign
|
// pubkey copied to the correct segment.
|
||||||
if signature, err = crypto.Sign(signedMsg, ecdsaRandomPrvKey); err != nil {
|
copy(resp[keyLen:2*keyLen], self.pubKeyDER)
|
||||||
return
|
// 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
|
||||||
}
|
}
|
||||||
// msg = signature || 0x80 || pubk || nonce
|
resp[resLen] = tokenFlag
|
||||||
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)
|
|
||||||
|
|
||||||
|
// encrypt using remote-pubk
|
||||||
// auth = eciesEncrypt(remote-pubk, msg)
|
// auth = eciesEncrypt(remote-pubk, msg)
|
||||||
if auth, err = crypto.Encrypt(remotePubKey, msg); err != nil {
|
// why not encrypt with ecdhe-random-remote
|
||||||
|
if authResp, err = crypto.Encrypt(remotePubKey, resp); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *cryptoId) verifyAuth(auth, nonce, sharedKnowledge []byte) (sessionToken []byte, rw *secretRW, err error) {
|
func (self *cryptoId) verifyAuthResp(auth []byte) (respNonce []byte, remoteRandomPubKey *ecdsa.PublicKey, tokenFlag bool, err error) {
|
||||||
var msg []byte
|
var msg []byte
|
||||||
// they prove that msg is meant for me,
|
// they prove that msg is meant for me,
|
||||||
// I prove I possess private key if i can read it
|
// I prove I possess private key if i can read it
|
||||||
@ -121,28 +203,29 @@ func (self *cryptoId) verifyAuth(auth, nonce, sharedKnowledge []byte) (sessionTo
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var remoteNonce []byte = msg[msgLen-skLen:]
|
respNonce = msg[resLen-keyLen-1 : resLen-1]
|
||||||
// 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
|
var remoteRandomPubKeyDER = msg[:keyLen]
|
||||||
// they prove they possess their private key to derive the same shared secret, plus the same shared history (previous session token)
|
remoteRandomPubKey = crypto.ToECDSAPub(remoteRandomPubKeyDER)
|
||||||
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 {
|
if remoteRandomPubKey == nil {
|
||||||
err = fmt.Errorf("invalid remote public key")
|
err = fmt.Errorf("invalid ecdh random remote public key")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// 3) Now we can trust ecdhe-random-pubk to derive keys
|
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)
|
//ecdhe-shared-secret = ecdh.agree(ecdhe-random, remote-ecdhe-random-pubk)
|
||||||
var dhSharedSecret []byte
|
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 {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// shared-secret = crypto.Sha3(ecdhe-shared-secret || crypto.Sha3(nonce || initiator-nonce))
|
// 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)
|
// token = crypto.Sha3(shared-secret)
|
||||||
sessionToken = crypto.Sha3(sharedSecret)
|
sessionToken = crypto.Sha3(sharedSecret)
|
||||||
// aes-secret = crypto.Sha3(ecdhe-shared-secret || shared-secret)
|
// 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...))
|
var macSecret = crypto.Sha3(append(dhSharedSecret, aesSecret...))
|
||||||
// # destroy ecdhe-shared-secret
|
// # destroy ecdhe-shared-secret
|
||||||
// egress-mac = crypto.Sha3(mac-secret^nonce || auth)
|
// 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
|
// # destroy nonce
|
||||||
// ingress-mac = crypto.Sha3(mac-secret^initiator-nonce || auth),
|
// 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
|
// # destroy remote-nonce
|
||||||
rw = &secretRW{
|
rw = &secretRW{
|
||||||
aesSecret: aesSecret,
|
aesSecret: aesSecret,
|
||||||
@ -166,7 +249,9 @@ func (self *cryptoId) verifyAuth(auth, nonce, sharedKnowledge []byte) (sessionTo
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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++ {
|
for i := 0; i < len(one); i++ {
|
||||||
xor[i] = one[i] ^ other[i]
|
xor[i] = one[i] ^ other[i]
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user