mirror of
https://github.com/ipfs/kubo.git
synced 2026-02-23 11:27:42 +08:00
312 lines
6.9 KiB
Go
312 lines
6.9 KiB
Go
// Package identify handles how peers identify with eachother upon
|
|
// connection to the network
|
|
package identify
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"strings"
|
|
|
|
"crypto/aes"
|
|
"crypto/cipher"
|
|
"crypto/hmac"
|
|
"crypto/rand"
|
|
"crypto/sha1"
|
|
"crypto/sha256"
|
|
"crypto/sha512"
|
|
"hash"
|
|
|
|
proto "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto"
|
|
ci "github.com/jbenet/go-ipfs/crypto"
|
|
peer "github.com/jbenet/go-ipfs/peer"
|
|
u "github.com/jbenet/go-ipfs/util"
|
|
)
|
|
|
|
// List of supported protocols--each section in order of preference.
|
|
// Takes the form: ECDH curves : Ciphers : Hashes
|
|
var SupportedExchanges = "P-256,P-224,P-384,P-521"
|
|
var SupportedCiphers = "AES-256,AES-128"
|
|
var SupportedHashes = "SHA256,SHA512,SHA1"
|
|
|
|
// ErrUnsupportedKeyType is returned when a private key cast/type switch fails.
|
|
var ErrUnsupportedKeyType = errors.New("unsupported key type")
|
|
|
|
// Performs initial communication with this peer to share node ID's and
|
|
// initiate communication. (secureIn, secureOut, error)
|
|
func Handshake(self, remote *peer.Peer, in <-chan []byte, out chan<- []byte) (<-chan []byte, chan<- []byte, error) {
|
|
// Generate and send Hello packet.
|
|
// Hello = (rand, PublicKey, Supported)
|
|
nonce := make([]byte, 16)
|
|
_, err := rand.Read(nonce)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
hello := new(Hello)
|
|
|
|
myPubKey, err := self.PubKey.Bytes()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
hello.Rand = nonce
|
|
hello.Pubkey = myPubKey
|
|
hello.Exchanges = &SupportedExchanges
|
|
hello.Ciphers = &SupportedCiphers
|
|
hello.Hashes = &SupportedHashes
|
|
|
|
encoded, err := proto.Marshal(hello)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
out <- encoded
|
|
|
|
// Parse their Hello packet and generate an Exchange packet.
|
|
// Exchange = (EphemeralPubKey, Signature)
|
|
resp := <-in
|
|
|
|
helloResp := new(Hello)
|
|
err = proto.Unmarshal(resp, helloResp)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
remote.PubKey, err = ci.UnmarshalPublicKey(helloResp.GetPubkey())
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
remote.ID, err = IDFromPubKey(remote.PubKey)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
exchange, err := selectBest(SupportedExchanges, helloResp.GetExchanges())
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
cipherType, err := selectBest(SupportedCiphers, helloResp.GetCiphers())
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
hashType, err := selectBest(SupportedHashes, helloResp.GetHashes())
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
epubkey, done, err := ci.GenerateEKeyPair(exchange) // Generate EphemeralPubKey
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
var handshake bytes.Buffer // Gather corpus to sign.
|
|
handshake.Write(encoded)
|
|
handshake.Write(resp)
|
|
handshake.Write(epubkey)
|
|
|
|
exPacket := new(Exchange)
|
|
|
|
exPacket.Epubkey = epubkey
|
|
exPacket.Signature, err = self.PrivKey.Sign(handshake.Bytes())
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
exEncoded, err := proto.Marshal(exPacket)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
out <- exEncoded
|
|
|
|
// Parse their Exchange packet and generate a Finish packet.
|
|
// Finish = E('Finish')
|
|
resp1 := <-in
|
|
|
|
exchangeResp := new(Exchange)
|
|
err = proto.Unmarshal(resp1, exchangeResp)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
var theirHandshake bytes.Buffer
|
|
_, err = theirHandshake.Write(resp)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
_, err = theirHandshake.Write(encoded)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
_, err = theirHandshake.Write(exchangeResp.GetEpubkey())
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
ok, err := remote.PubKey.Verify(theirHandshake.Bytes(), exchangeResp.GetSignature())
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
if !ok {
|
|
return nil, nil, errors.New("Bad signature!")
|
|
}
|
|
|
|
secret, err := done(exchangeResp.GetEpubkey())
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
cmp := bytes.Compare(myPubKey, helloResp.GetPubkey())
|
|
mIV, tIV, mCKey, tCKey, mMKey, tMKey := ci.KeyStretcher(cmp, cipherType, hashType, secret)
|
|
|
|
secureIn := make(chan []byte)
|
|
secureOut := make(chan []byte)
|
|
|
|
go secureInProxy(in, secureIn, hashType, tIV, tCKey, tMKey)
|
|
go secureOutProxy(out, secureOut, hashType, mIV, mCKey, mMKey)
|
|
|
|
finished := []byte("Finished")
|
|
|
|
secureOut <- finished
|
|
resp2 := <-secureIn
|
|
|
|
if bytes.Compare(resp2, finished) != 0 {
|
|
return nil, nil, errors.New("Negotiation failed.")
|
|
}
|
|
|
|
u.DOut("[%s] identify: Got node id: %s\n", self.ID.Pretty(), remote.ID.Pretty())
|
|
|
|
return secureIn, secureOut, nil
|
|
}
|
|
|
|
func makeMac(hashType string, key []byte) (hash.Hash, int) {
|
|
switch hashType {
|
|
case "SHA1":
|
|
return hmac.New(sha1.New, key), sha1.Size
|
|
case "SHA512":
|
|
return hmac.New(sha512.New, key), sha512.Size
|
|
default:
|
|
return hmac.New(sha256.New, key), sha256.Size
|
|
}
|
|
}
|
|
|
|
func secureInProxy(in <-chan []byte, secureIn chan<- []byte, hashType string, tIV, tCKey, tMKey []byte) {
|
|
theirBlock, _ := aes.NewCipher(tCKey)
|
|
theirCipher := cipher.NewCTR(theirBlock, tIV)
|
|
|
|
theirMac, macSize := makeMac(hashType, tMKey)
|
|
|
|
for {
|
|
data, ok := <-in
|
|
if !ok {
|
|
close(secureIn)
|
|
return
|
|
}
|
|
|
|
if len(data) <= macSize {
|
|
continue
|
|
}
|
|
|
|
mark := len(data) - macSize
|
|
buff := make([]byte, mark)
|
|
|
|
theirCipher.XORKeyStream(buff, data[0:mark])
|
|
|
|
theirMac.Write(data[0:mark])
|
|
expected := theirMac.Sum(nil)
|
|
theirMac.Reset()
|
|
|
|
hmacOk := hmac.Equal(data[mark:], expected)
|
|
|
|
if hmacOk {
|
|
secureIn <- buff
|
|
} else {
|
|
secureIn <- nil
|
|
}
|
|
}
|
|
}
|
|
|
|
func secureOutProxy(out chan<- []byte, secureOut <-chan []byte, hashType string, mIV, mCKey, mMKey []byte) {
|
|
myBlock, _ := aes.NewCipher(mCKey)
|
|
myCipher := cipher.NewCTR(myBlock, mIV)
|
|
|
|
myMac, macSize := makeMac(hashType, mMKey)
|
|
|
|
for {
|
|
data, ok := <-secureOut
|
|
if !ok {
|
|
close(out)
|
|
return
|
|
}
|
|
|
|
if len(data) == 0 {
|
|
continue
|
|
}
|
|
|
|
buff := make([]byte, len(data)+macSize)
|
|
|
|
myCipher.XORKeyStream(buff, data)
|
|
|
|
myMac.Write(buff[0:len(data)])
|
|
copy(buff[len(data):], myMac.Sum(nil))
|
|
myMac.Reset()
|
|
|
|
out <- buff
|
|
}
|
|
}
|
|
|
|
// IDFromPubKey returns Nodes ID given its public key
|
|
func IDFromPubKey(pk ci.PubKey) (peer.ID, error) {
|
|
b, err := pk.Bytes()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
hash, err := u.Hash(b)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return peer.ID(hash), nil
|
|
}
|
|
|
|
// Determines which algorithm to use. Note: f(a, b) = f(b, a)
|
|
func selectBest(myPrefs, theirPrefs string) (string, error) {
|
|
// Person with greatest hash gets first choice.
|
|
myHash, err := u.Hash([]byte(myPrefs))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
theirHash, err := u.Hash([]byte(theirPrefs))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
cmp := bytes.Compare(myHash, theirHash)
|
|
var firstChoiceArr, secChoiceArr []string
|
|
|
|
if cmp == -1 {
|
|
firstChoiceArr = strings.Split(theirPrefs, ",")
|
|
secChoiceArr = strings.Split(myPrefs, ",")
|
|
} else if cmp == 1 {
|
|
firstChoiceArr = strings.Split(myPrefs, ",")
|
|
secChoiceArr = strings.Split(theirPrefs, ",")
|
|
} else { // Exact same preferences.
|
|
myPrefsArr := strings.Split(myPrefs, ",")
|
|
return myPrefsArr[0], nil
|
|
}
|
|
|
|
for _, secChoice := range secChoiceArr {
|
|
for _, firstChoice := range firstChoiceArr {
|
|
if firstChoice == secChoice {
|
|
return firstChoice, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
return "", errors.New("No algorithms in common!")
|
|
}
|