handshake.go 9.18 KB
Newer Older
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
1 2 3 4 5 6 7
// Package spipe handles establishing secure communication between two peers.

package spipe

import (
	"bytes"
	"errors"
8
	"fmt"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
9 10 11 12 13 14 15 16 17
	"strings"

	"crypto/aes"
	"crypto/cipher"
	"crypto/hmac"
	"crypto/rand"
	"crypto/sha1"
	"crypto/sha256"
	"crypto/sha512"
18
	bfish "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.crypto/blowfish"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
19 20 21
	"hash"

	proto "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
22

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
23
	ci "github.com/jbenet/go-ipfs/crypto"
24
	pb "github.com/jbenet/go-ipfs/crypto/spipe/internal/pb"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
25 26 27 28
	peer "github.com/jbenet/go-ipfs/peer"
	u "github.com/jbenet/go-ipfs/util"
)

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
29 30
var log = u.Logger("handshake")

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
31 32 33 34
// List of supported ECDH curves
var SupportedExchanges = "P-256,P-224,P-384,P-521"

// List of supported Ciphers
35
var SupportedCiphers = "AES-256,AES-128,Blowfish"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56

// List of supported Hashes
var SupportedHashes = "SHA256,SHA512,SHA1"

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

// ErrClosed signals the closing of a connection.
var ErrClosed = errors.New("connection closed")

// handsahke performs initial communication over insecure channel to share
// keys, IDs, and initiate communication.
func (s *SecurePipe) handshake() error {
	// Generate and send Hello packet.
	// Hello = (rand, PublicKey, Supported)
	nonce := make([]byte, 16)
	_, err := rand.Read(nonce)
	if err != nil {
		return err
	}

Jeromy's avatar
Jeromy committed
57
	log.Debugf("handshake: %s <--> %s", s.local, s.remote)
58
	myPubKey, err := s.local.PubKey().Bytes()
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
59 60 61 62
	if err != nil {
		return err
	}

63
	proposeMsg := new(pb.Propose)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
64 65 66 67 68 69 70 71 72 73 74
	proposeMsg.Rand = nonce
	proposeMsg.Pubkey = myPubKey
	proposeMsg.Exchanges = &SupportedExchanges
	proposeMsg.Ciphers = &SupportedCiphers
	proposeMsg.Hashes = &SupportedHashes

	encoded, err := proto.Marshal(proposeMsg)
	if err != nil {
		return err
	}

75 76 77 78 79 80
	// Send our Propose packet
	select {
	case s.insecure.Out <- encoded:
	case <-s.ctx.Done():
		return ErrClosed
	}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
81 82 83 84 85 86 87

	// Parse their Propose packet and generate an Exchange packet.
	// Exchange = (EphemeralPubKey, Signature)
	var resp []byte
	select {
	case <-s.ctx.Done():
		return ErrClosed
88
	case resp = <-s.insecure.In:
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
89 90
	}

91
	// u.POut("received encoded handshake\n")
92
	proposeResp := new(pb.Propose)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
93 94 95 96 97
	err = proto.Unmarshal(resp, proposeResp)
	if err != nil {
		return err
	}

98 99
	// get remote identity
	remotePubKey, err := ci.UnmarshalPublicKey(proposeResp.GetPubkey())
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
100 101 102 103
	if err != nil {
		return err
	}

104 105
	// get or construct peer
	s.remote, err = getOrConstructPeer(s.peers, remotePubKey)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
106 107 108
	if err != nil {
		return err
	}
Jeromy's avatar
Jeromy committed
109
	log.Debugf("%s Remote Peer Identified as %s", s.local, s.remote)
110

111
	exchange, err := SelectBest(SupportedExchanges, proposeResp.GetExchanges())
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
112 113 114 115
	if err != nil {
		return err
	}

116
	cipherType, err := SelectBest(SupportedCiphers, proposeResp.GetCiphers())
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
117 118 119 120
	if err != nil {
		return err
	}

121
	hashType, err := SelectBest(SupportedHashes, proposeResp.GetHashes())
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
122 123 124 125
	if err != nil {
		return err
	}

126
	// u.POut("Selected %s %s %s\n", exchange, cipherType, hashType)
127
	epubkey, genSharedKey, err := ci.GenerateEKeyPair(exchange) // Generate EphemeralPubKey
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
128 129 130 131 132 133

	var handshake bytes.Buffer // Gather corpus to sign.
	handshake.Write(encoded)
	handshake.Write(resp)
	handshake.Write(epubkey)

134
	exPacket := new(pb.Exchange)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
135 136

	exPacket.Epubkey = epubkey
137
	exPacket.Signature, err = s.local.PrivKey().Sign(handshake.Bytes())
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
138 139 140 141 142 143
	if err != nil {
		return err
	}

	exEncoded, err := proto.Marshal(exPacket)

144 145 146 147 148 149
	// send out Exchange packet
	select {
	case s.insecure.Out <- exEncoded:
	case <-s.ctx.Done():
		return ErrClosed
	}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
150 151 152 153 154 155 156 157 158 159

	// Parse their Exchange packet and generate a Finish packet.
	// Finish = E('Finish')
	var resp1 []byte
	select {
	case <-s.ctx.Done():
		return ErrClosed
	case resp1 = <-s.insecure.In:
	}

160
	exchangeResp := new(pb.Exchange)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
161 162 163 164 165 166 167 168 169 170
	err = proto.Unmarshal(resp1, exchangeResp)
	if err != nil {
		return err
	}

	var theirHandshake bytes.Buffer
	theirHandshake.Write(resp)
	theirHandshake.Write(encoded)
	theirHandshake.Write(exchangeResp.GetEpubkey())

171
	// u.POut("Remote Peer Identified as %s\n", s.remote)
172
	ok, err := s.remote.PubKey().Verify(theirHandshake.Bytes(), exchangeResp.GetSignature())
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
173 174 175 176 177 178 179 180
	if err != nil {
		return err
	}

	if !ok {
		return errors.New("Bad signature!")
	}

181
	secret, err := genSharedKey(exchangeResp.GetEpubkey())
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
182 183 184 185 186
	if err != nil {
		return err
	}

	cmp := bytes.Compare(myPubKey, proposeResp.GetPubkey())
Jeromy's avatar
Jeromy committed
187 188 189 190 191 192 193 194 195 196 197 198
	mIV, tIV, mCKey, tCKey, mMKey, tMKey := ci.KeyStretcher(cmp, cipherType, hashType, secret)
	//ci.KeyStretcher(cmp, cipherType, hashType, secret)

	go s.handleSecureIn(hashType, cipherType, tIV, tCKey, tMKey)
	go s.handleSecureOut(hashType, cipherType, mIV, mCKey, mMKey)

	/*
		// Disable Secure Channel
		go func(sp *SecurePipe) {
			for {
				select {
				case <-sp.ctx.Done():
Jeromy's avatar
Jeromy committed
199
					return
Jeromy's avatar
Jeromy committed
200 201 202 203 204 205
				case m, ok := <-sp.insecure.In:
					if !ok {
						sp.cancel()
						return
					}
					sp.In <- m
Jeromy's avatar
Jeromy committed
206 207
				}
			}
Jeromy's avatar
Jeromy committed
208 209 210 211 212
		}(s)
		go func(sp *SecurePipe) {
			for {
				select {
				case <-sp.ctx.Done():
Jeromy's avatar
Jeromy committed
213
					return
Jeromy's avatar
Jeromy committed
214 215 216 217 218 219
				case m, ok := <-sp.Out:
					if !ok {
						sp.cancel()
						return
					}
					sp.insecure.Out <- m
Jeromy's avatar
Jeromy committed
220 221
				}
			}
Jeromy's avatar
Jeromy committed
222 223
		}(s)
	*/
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
224 225 226

	finished := []byte("Finished")

227 228 229 230 231 232 233 234
	// send finished msg
	select {
	case <-s.ctx.Done():
		return ErrClosed
	case s.Out <- finished:
	}

	// recv finished msg
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
235 236 237 238
	var resp2 []byte
	select {
	case <-s.ctx.Done():
		return ErrClosed
239
	case resp2 = <-s.In:
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
240 241 242
	}

	if bytes.Compare(resp2, finished) != 0 {
243
		return fmt.Errorf("Negotiation failed, got: %s", resp2)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
244 245
	}

Jeromy's avatar
Jeromy committed
246
	log.Debugf("%s handshake: Got node id: %s", s.local, s.remote)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
247 248 249 250 251 252 253 254 255 256 257 258 259 260
	return 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
	}
}

261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278
func makeCipher(cipherType string, CKey []byte) (cipher.Block, error) {
	switch cipherType {
	case "AES-128", "AES-256":
		return aes.NewCipher(CKey)
	case "Blowfish":
		return bfish.NewCipher(CKey)
	default:
		return nil, fmt.Errorf("Unrecognized cipher string: %s", cipherType)
	}
}

func (s *SecurePipe) handleSecureIn(hashType, cipherType string, tIV, tCKey, tMKey []byte) {
	theirBlock, err := makeCipher(cipherType, tCKey)
	if err != nil {
		log.Criticalf("Invalid Cipher: %s", err)
		s.cancel()
		return
	}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
279 280 281 282 283
	theirCipher := cipher.NewCTR(theirBlock, tIV)

	theirMac, macSize := makeMac(hashType, tMKey)

	for {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
284 285 286 287 288 289 290 291 292
		var data []byte
		ok := true

		select {
		case <-s.ctx.Done():
			ok = false // return out
		case data, ok = <-s.insecure.In:
		}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
293
		if !ok {
294
			close(s.Duplex.In)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
295 296 297
			return
		}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
298
		// log.Debug("[peer %s] secure in [from = %s] %d", s.local, s.remote, len(data))
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321
		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 {
			s.Duplex.In <- buff
		} else {
			s.Duplex.In <- nil
		}
	}
}

322 323 324 325 326 327 328
func (s *SecurePipe) handleSecureOut(hashType, cipherType string, mIV, mCKey, mMKey []byte) {
	myBlock, err := makeCipher(cipherType, mCKey)
	if err != nil {
		log.Criticalf("Invalid Cipher: %s", err)
		s.cancel()
		return
	}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
329 330 331 332 333
	myCipher := cipher.NewCTR(myBlock, mIV)

	myMac, macSize := makeMac(hashType, mMKey)

	for {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
334 335 336 337 338 339 340 341 342
		var data []byte
		ok := true

		select {
		case <-s.ctx.Done():
			ok = false // return out
		case data, ok = <-s.Out:
		}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
343
		if !ok {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
344
			close(s.insecure.Out)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
345 346 347 348 349 350 351
			return
		}

		if len(data) == 0 {
			continue
		}

Jeromy's avatar
fix bug  
Jeromy committed
352
		buff := make([]byte, len(data)+macSize)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
353

Jeromy's avatar
fix bug  
Jeromy committed
354 355 356 357 358
		myCipher.XORKeyStream(buff, data)

		myMac.Write(buff[0:len(data)])
		copy(buff[len(data):], myMac.Sum(nil))
		myMac.Reset()
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
359

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
360
		// log.Debug("[peer %s] secure out [to = %s] %d", s.local, s.remote, len(buff))
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
361 362 363 364 365
		s.insecure.Out <- buff
	}
}

// Determines which algorithm to use.  Note:  f(a, b) = f(b, a)
366
func SelectBest(myPrefs, theirPrefs string) (string, error) {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
367
	// Person with greatest hash gets first choice.
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
368 369
	myHash := u.Hash([]byte(myPrefs))
	theirHash := u.Hash([]byte(theirPrefs))
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394

	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!")
}
395 396 397 398

// getOrConstructPeer attempts to fetch a peer from a peerstore.
// if succeeds, verify ID and PubKey match.
// else, construct it.
399
func getOrConstructPeer(peers peer.Peerstore, rpk ci.PubKey) (peer.Peer, error) {
400

401
	rid, err := peer.IDFromPubKey(rpk)
402 403 404 405 406
	if err != nil {
		return nil, err
	}

	npeer, err := peers.Get(rid)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
407 408
	if err != nil {
		return nil, err // unexpected error happened.
409 410
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
411 412 413
	// public key verification happens in Peer.VerifyAndSetPubKey
	if err := npeer.VerifyAndSetPubKey(rpk); err != nil {
		return nil, err // pubkey mismatch or other problem
414 415 416
	}
	return npeer, nil
}