diff options
Diffstat (limited to 'p2p/crypto.go')
-rw-r--r-- | p2p/crypto.go | 174 |
1 files changed, 174 insertions, 0 deletions
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 +} |