identify.go 6.47 KB
Newer Older
1 2 3 4 5
// The identify package handles how peers identify with eachother upon
// connection to the network
package identify

import (
6
	"bytes"
Brendan Mc's avatar
Brendan Mc committed
7 8 9
	"errors"
	"strings"

10 11 12 13 14 15 16 17 18
	"crypto/aes"
	"crypto/cipher"
	"crypto/hmac"
	"crypto/rand"
	"crypto/sha1"
	"crypto/sha256"
	"crypto/sha512"
	"hash"

19
	proto "code.google.com/p/goprotobuf/proto"
20
	ci "github.com/jbenet/go-ipfs/crypto"
21
	peer "github.com/jbenet/go-ipfs/peer"
22
	u "github.com/jbenet/go-ipfs/util"
23 24
)

25 26 27 28 29 30
// 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"

31 32 33
// ErrUnsupportedKeyType is returned when a private key cast/type switch fails.
var ErrUnsupportedKeyType = errors.New("unsupported key type")

34
// Perform initial communication with this peer to share node ID's and
Brendan Mc's avatar
Brendan Mc committed
35
// initiate communication.  (secureIn, secureOut, error)
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
func Handshake(self, remote *peer.Peer, in, out chan []byte) (chan []byte, chan []byte, error) {
	// Generate and send Hello packet.
	// Hello = (rand, PublicKey, Supported)
	nonce := make([]byte, 16)
	rand.Read(nonce)

	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)
56
	if err != nil {
57
		return nil, nil, err
58
	}
59

60
	out <- encoded
61 62 63

	// Parse their Hello packet and generate an Exchange packet.
	// Exchange = (EphemeralPubKey, Signature)
64
	resp := <-in
65

66 67
	helloResp := new(Hello)
	err = proto.Unmarshal(resp, helloResp)
68
	if err != nil {
69
		return nil, nil, err
70 71
	}

72 73 74
	remote.PubKey, err = ci.UnmarshalPublicKey(helloResp.GetPubkey())
	if err != nil {
		return nil, nil, err
75 76
	}

77
	remote.ID, err = IdFromPubKey(remote.PubKey)
78
	if err != nil {
79
		return nil, nil, err
80 81
	}

82
	exchange, err := selectBest(SupportedExchanges, helloResp.GetExchanges())
83
	if err != nil {
84
		return nil, nil, err
85 86
	}

Brendan Mc's avatar
Brendan Mc committed
87
	cipherType, err := selectBest(SupportedCiphers, helloResp.GetCiphers())
88
	if err != nil {
89
		return nil, nil, err
90 91
	}

Brendan Mc's avatar
Brendan Mc committed
92
	hashType, err := selectBest(SupportedHashes, helloResp.GetHashes())
93 94
	if err != nil {
		return nil, nil, err
95 96
	}

Brendan Mc's avatar
Brendan Mc committed
97
	epubkey, done, err := ci.GenerateEKeyPair(exchange) // Generate EphemeralPubKey
98

99 100 101 102
	var handshake bytes.Buffer // Gather corpus to sign.
	handshake.Write(encoded)
	handshake.Write(resp)
	handshake.Write(epubkey)
103

104 105 106 107
	exPacket := new(Exchange)

	exPacket.Epubkey = epubkey
	exPacket.Signature, err = self.PrivKey.Sign(handshake.Bytes())
108
	if err != nil {
109
		return nil, nil, err
110 111
	}

112 113 114 115 116 117 118
	exEncoded, err := proto.Marshal(exPacket)

	out <- exEncoded

	// Parse their Exchange packet and generate a Finish packet.
	// Finish = E('Finish')
	resp1 := <-in
119

120 121
	exchangeResp := new(Exchange)
	err = proto.Unmarshal(resp1, exchangeResp)
122
	if err != nil {
123
		return nil, nil, err
124 125
	}

126 127 128 129
	var theirHandshake bytes.Buffer
	theirHandshake.Write(resp)
	theirHandshake.Write(encoded)
	theirHandshake.Write(exchangeResp.GetEpubkey())
130

131
	ok, err := remote.PubKey.Verify(theirHandshake.Bytes(), exchangeResp.GetSignature())
132
	if err != nil {
133 134 135 136 137
		return nil, nil, err
	}

	if !ok {
		return nil, nil, errors.New("Bad signature!")
138 139
	}

140 141 142
	secret, err := done(exchangeResp.GetEpubkey())
	if err != nil {
		return nil, nil, err
143 144
	}

145
	cmp := bytes.Compare(myPubKey, helloResp.GetPubkey())
Brendan Mc's avatar
Brendan Mc committed
146
	mIV, tIV, mCKey, tCKey, mMKey, tMKey := ci.KeyStretcher(cmp, cipherType, hashType, secret)
147 148 149 150

	secureIn := make(chan []byte)
	secureOut := make(chan []byte)

Brendan Mc's avatar
Brendan Mc committed
151 152
	go secureInProxy(in, secureIn, hashType, tIV, tCKey, tMKey)
	go secureOutProxy(out, secureOut, hashType, mIV, mCKey, mMKey)
153

Brendan Mc's avatar
Brendan Mc committed
154 155 156 157 158 159 160 161 162
	finished := []byte("Finished")

	secureOut <- finished
	resp2 := <-secureIn

	if bytes.Compare(resp2, finished) != 0 {
		return nil, nil, errors.New("Negotiation failed.")
	}

163 164 165
	u.DOut("[%s] identify: Got node id: %s\n", self.ID.Pretty(), remote.ID.Pretty())

	return secureIn, secureOut, nil
166 167
}

Brendan Mc's avatar
Brendan Mc committed
168 169 170 171 172 173 174 175
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
176 177
	}
}
178

Brendan Mc's avatar
Brendan Mc committed
179 180 181
func secureInProxy(in, secureIn chan []byte, hashType string, tIV, tCKey, tMKey []byte) {
	theirBlock, _ := aes.NewCipher(tCKey)
	theirCipher := cipher.NewCTR(theirBlock, tIV)
182

Brendan Mc's avatar
Brendan Mc committed
183
	theirMac, macSize := makeMac(hashType, tMKey)
184

Brendan Mc's avatar
Brendan Mc committed
185 186 187 188 189
	for {
		data, ok := <-in
		if !ok {
			return
		}
190

Brendan Mc's avatar
Brendan Mc committed
191 192 193
		if len(data) <= macSize {
			continue
		}
194

Brendan Mc's avatar
Brendan Mc committed
195 196
		mark := len(data) - macSize
		buff := make([]byte, mark)
197

Brendan Mc's avatar
Brendan Mc committed
198
		theirCipher.XORKeyStream(buff, data[0:mark])
199

Brendan Mc's avatar
Brendan Mc committed
200 201 202
		theirMac.Write(data[0:mark])
		expected := theirMac.Sum(nil)
		theirMac.Reset()
203

Brendan Mc's avatar
Brendan Mc committed
204
		hmacOk := hmac.Equal(data[mark:], expected)
205

Brendan Mc's avatar
Brendan Mc committed
206 207 208 209
		if hmacOk {
			secureIn <- buff
		} else {
			secureIn <- nil
210
		}
Brendan Mc's avatar
Brendan Mc committed
211 212
	}
}
213

Brendan Mc's avatar
Brendan Mc committed
214 215 216
func secureOutProxy(out, secureOut chan []byte, hashType string, mIV, mCKey, mMKey []byte) {
	myBlock, _ := aes.NewCipher(mCKey)
	myCipher := cipher.NewCTR(myBlock, mIV)
217

Brendan Mc's avatar
Brendan Mc committed
218
	myMac, macSize := makeMac(hashType, mMKey)
219

Brendan Mc's avatar
Brendan Mc committed
220 221 222 223 224
	for {
		data, ok := <-secureOut
		if !ok {
			return
		}
225

Brendan Mc's avatar
Brendan Mc committed
226 227 228
		if len(data) == 0 {
			continue
		}
229

Brendan Mc's avatar
Brendan Mc committed
230
		buff := make([]byte, len(data)+macSize)
231

Brendan Mc's avatar
Brendan Mc committed
232
		myCipher.XORKeyStream(buff, data)
233

Brendan Mc's avatar
Brendan Mc committed
234 235 236
		myMac.Write(buff[0:len(data)])
		copy(buff[len(data):], myMac.Sum(nil))
		myMac.Reset()
237

Brendan Mc's avatar
Brendan Mc committed
238 239 240
		out <- buff
	}
}
241

Brendan Mc's avatar
Brendan Mc committed
242 243 244 245 246 247 248 249 250 251
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
252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290
}

// 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!")
}