@ -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,13 +13,14 @@ 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
}
@ -27,7 +28,7 @@ type secretRW struct {
type cryptoId struct {
prvKey * ecdsa . PrivateKey
pubKey * ecdsa . PublicKey
pubKeyR io . ReaderAt
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 . pubKeyDE R = id . Pubkey ( )
return
}
//
func ( self * cryptoId ) setup Auth( remotePubKeyDER , sessionToken [ ] byte ) ( auth [ ] byte , nonce [ ] byte , sharedKnowledge [ ] byte , err error ) {
// initAuth is called by peer if it initiated the connection
func ( self * cryptoId ) init Auth( remotePubKeyDER , sessionToken [ ] byte ) ( auth [ ] byte , i nitN once [ ] 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
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
sharedSecret , err = ecies . ImportECDSA ( self . prvKey ) . GenerateShared ( ecies . ImportECDSAPublic ( remotePubKey ) , skLen , skLen )
if err != nil {
if sessionToken , err = ecies . ImportECDSA ( self . prvKey ) . GenerateShared ( ecies . ImportECDSAPublic ( remotePubKey ) , sskLen , sskLen ) ; err != nil {
return
}
// check previous session token
if sessionToken == nil {
err = fmt . Errorf ( "no session token for peer" )
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 ) , ss kLen , s 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 ... ) ) ... ) )
var sharedSecret = crypto . Sha3 ( append ( dhSharedSecret , crypto . Sha3 ( append ( respNonce , init Nonce... ) ) ... ) )
// 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 , n once) , auth ... ) )
var egressMac = crypto . Sha3 ( append ( Xor ( macSecret , respN once) , auth ... ) )
// # destroy nonce
// ingress-mac = crypto.Sha3(mac-secret^initiator-nonce || auth),
var ingressMac = crypto . Sha3 ( append ( Xor ( macSecret , remote Nonce) , auth ... ) )
var ingressMac = crypto . Sha3 ( append ( Xor ( macSecret , init Nonce) , 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 ]
}