From 33a9e147d62a5fc8147364f2db49ee7c78cd2a23 Mon Sep 17 00:00:00 2001 From: Brendan Mc Date: Wed, 3 Sep 2014 21:09:02 -0400 Subject: [PATCH 1/5] Implemented a basic version of TLS. --- identify/identify.go | 468 +++++++++++++++++++++++++++++++++++------ identify/message.pb.go | 71 ++++++- identify/message.proto | 12 +- 3 files changed, 476 insertions(+), 75 deletions(-) diff --git a/identify/identify.go b/identify/identify.go index b145add2e..cdf86cca7 100644 --- a/identify/identify.go +++ b/identify/identify.go @@ -6,100 +6,230 @@ import ( "bytes" "errors" + "crypto/aes" + "crypto/cipher" + "crypto/elliptic" + "crypto/hmac" + "crypto/rand" + "crypto/sha1" + "crypto/sha256" + "crypto/sha512" + "hash" + "math/big" + "strings" + proto "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") // Perform initial communication with this peer to share node ID's and // initiate communication -func Handshake(self, remote *peer.Peer, in, out chan []byte) error { - encoded, err := buildHandshake(self) +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 err + 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 - pbresp := new(Identify) - err = proto.Unmarshal(resp, pbresp) + helloResp := new(Hello) + err = proto.Unmarshal(resp, helloResp) if err != nil { - return err + return nil, nil, err } - // Verify that the given ID matches their given public key - if verifyErr := verifyID(peer.ID(pbresp.GetId()), pbresp.GetPubkey()); verifyErr != nil { - return verifyErr - } - - pubkey, err := ci.UnmarshalPublicKey(pbresp.GetPubkey()) + remote.PubKey, err = ci.UnmarshalPublicKey(helloResp.GetPubkey()) if err != nil { - return err + return nil, nil, err } - // Challenge peer to ensure they own the given pubkey - secret := self.PrivKey.GenSecret() - encrypted, err := pubkey.Encrypt(secret) + remote.ID, err = IdFromPubKey(remote.PubKey) if err != nil { - //... this is odd - return err + return nil, nil, err } - out <- encrypted - challenge := <-in - - // Decrypt challenge and send plaintext to partner - plain, err := self.PrivKey.Decrypt(challenge) + exchange, err := selectBest(SupportedExchanges, helloResp.GetExchanges()) if err != nil { - return err + return nil, nil, err } - out <- plain - chalResp := <-in - if !bytes.Equal(chalResp, secret) { - return errors.New("Recieved incorrect challenge response!") + cipherType, err := selectBest(SupportedExchanges, helloResp.GetCiphers()) + if err != nil { + return nil, nil, err } - remote.ID = peer.ID(pbresp.GetId()) - remote.PubKey = pubkey + hashType, err := selectBest(SupportedExchanges, helloResp.GetHashes()) + if err != nil { + return nil, nil, err + } + + epubkey, done, err := generateEPubKey(exchange) // Generate EphemeralPubKey + + 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) + + 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 + theirHandshake.Write(resp) + theirHandshake.Write(encoded) + theirHandshake.Write(exchangeResp.GetEpubkey()) + + 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 := keyGenerator(cmp, cipherType, hashType, secret) + + secureIn := make(chan []byte) + secureOut := make(chan []byte) + + go func() { + myBlock, _ := aes.NewCipher(mCKey) + myCipher := cipher.NewCTR(myBlock, mIV) + + theirBlock, _ := aes.NewCipher(tCKey) + theirCipher := cipher.NewCTR(theirBlock, tIV) + + var myMac, theirMac hash.Hash + var macSize int + + switch hashType { + case "SHA1": + myMac = hmac.New(sha1.New, mMKey) + theirMac = hmac.New(sha1.New, tMKey) + macSize = 20 + + case "SHA256": + myMac = hmac.New(sha256.New, mMKey) + theirMac = hmac.New(sha256.New, tMKey) + macSize = 32 + + case "SHA512": + myMac = hmac.New(sha512.New, mMKey) + theirMac = hmac.New(sha512.New, tMKey) + macSize = 64 + } + + for { + select { + case data, ok := <-secureOut: + if !ok { + 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 + + case data, ok := <-in: + if !ok { + 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 + } + } + } + }() + u.DOut("[%s] identify: Got node id: %s\n", self.ID.Pretty(), remote.ID.Pretty()) - return nil -} - -func buildHandshake(self *peer.Peer) ([]byte, error) { - pkb, err := self.PubKey.Bytes() - if err != nil { - return nil, err - } - - pmes := new(Identify) - pmes.Id = []byte(self.ID) - pmes.Pubkey = pkb - - encoded, err := proto.Marshal(pmes) - if err != nil { - return nil, err - } - - return encoded, nil -} - -func verifyID(id peer.ID, pubkey []byte) error { - hash, err := u.Hash(pubkey) - if err != nil { - return err - } - - if id.Equal(peer.ID(hash)) { - return nil - } - - return errors.New("ID did not match public key!") + return secureIn, secureOut, nil } func IdFromPubKey(pk ci.PubKey) (peer.ID, error) { @@ -113,3 +243,217 @@ func IdFromPubKey(pk ci.PubKey) (peer.ID, error) { } return peer.ID(hash), nil } + +// Generates a set of keys for each party by stretching the shared key. +// (myIV, theirIV, myCipherKey, theirCipherKey, myMACKey, theirMACKey) +func keyGenerator(cmp int, cipherType string, hashType string, secret []byte) ([]byte, []byte, []byte, []byte, []byte, []byte) { + var cipherKeySize int + switch cipherType { + case "AES128": + cipherKeySize = 2 * 16 + case "AES256": + cipherKeySize = 2 * 32 + } + + ivSize := 16 + hmacKeySize := 20 + + seed := []byte("key expansion") + + result := make([]byte, 2*(ivSize+cipherKeySize+hmacKeySize)) + + var h func() hash.Hash + + switch hashType { + case "SHA1": + h = sha1.New + case "SHA256": + h = sha256.New + case "SHA512": + h = sha512.New + } + + m := hmac.New(h, secret) + m.Write(seed) + + a := m.Sum(nil) + + j := 0 + for j < len(result) { + m.Reset() + m.Write(a) + m.Write(seed) + b := m.Sum(nil) + + todo := len(b) + + if j+todo > len(result) { + todo = len(result) - j + } + + copy(result[j:j+todo], b) + + j += todo + + m.Reset() + m.Write(a) + a = m.Sum(nil) + } + + myResult := make([]byte, ivSize+cipherKeySize+hmacKeySize) + theirResult := make([]byte, ivSize+cipherKeySize+hmacKeySize) + + half := len(result) / 2 + + if cmp == 1 { + copy(myResult, result[:half]) + copy(theirResult, result[half:]) + } else if cmp == -1 { + copy(myResult, result[half:]) + copy(theirResult, result[:half]) + } else { // Shouldn't happen, but oh well. + copy(myResult, result[half:]) + copy(theirResult, result[half:]) + } + + myIV := myResult[0:ivSize] + myCKey := myResult[ivSize : ivSize+cipherKeySize] + myMKey := myResult[ivSize+cipherKeySize:] + + theirIV := theirResult[0:ivSize] + theirCKey := theirResult[ivSize : ivSize+cipherKeySize] + theirMKey := theirResult[ivSize+cipherKeySize:] + + return myIV, theirIV, myCKey, theirCKey, myMKey, theirMKey +} + +// 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!") +} + +// Generates an ephemeral public key and returns a function that will compute +// the shared secret key. +// +// Focuses only on ECDH now, but can be made more general in the future. +func generateEPubKey(exchange string) ([]byte, func([]byte) ([]byte, error), error) { + genKeyPair := func(curve elliptic.Curve) ([]byte, []byte, error) { + priv, x, y, err := elliptic.GenerateKey(curve, rand.Reader) + if err != nil { + return nil, nil, err + } + + var pubKey bytes.Buffer + pubKey.Write(x.Bytes()) + pubKey.Write(y.Bytes()) + + return pubKey.Bytes(), priv, nil + } + + genSec := func(curve elliptic.Curve, theirPub []byte, myPriv []byte) ([]byte, error) { + // Verify and unpack node's public key. + curveSize := curve.Params().BitSize + + if len(theirPub) != (curveSize / 2) { + return nil, errors.New("Malformed public key.") + } + + bound := (curveSize / 8) + x := big.NewInt(0) + y := big.NewInt(0) + + x.SetBytes(theirPub[0:bound]) + y.SetBytes(theirPub[bound : bound*2]) + + if !curve.IsOnCurve(x, y) { + return nil, errors.New("Invalid public key.") + } + + // Generate shared secret. + secret, _ := curve.ScalarMult(x, y, myPriv) + + return secret.Bytes(), nil + } + + switch exchange { + case "P-224": + curve := elliptic.P224() + pub, priv, err := genKeyPair(curve) + if err != nil { + return nil, nil, err + } + + done := func(theirs []byte) ([]byte, error) { return genSec(curve, theirs, priv) } + + return pub, done, nil + + case "P-256": + curve := elliptic.P256() + pub, priv, err := genKeyPair(curve) + if err != nil { + return nil, nil, err + } + + done := func(theirs []byte) ([]byte, error) { return genSec(curve, theirs, priv) } + + return pub, done, nil + + case "P-384": + curve := elliptic.P384() + pub, priv, err := genKeyPair(curve) + if err != nil { + return nil, nil, err + } + + done := func(theirs []byte) ([]byte, error) { return genSec(curve, theirs, priv) } + + return pub, done, nil + + case "P-521": + curve := elliptic.P521() + pub, priv, err := genKeyPair(curve) + if err != nil { + return nil, nil, err + } + + done := func(theirs []byte) ([]byte, error) { return genSec(curve, theirs, priv) } + + return pub, done, nil + + } + + return nil, nil, errors.New("Something silly happened.") +} diff --git a/identify/message.pb.go b/identify/message.pb.go index 0e5c45627..9313fc1c6 100644 --- a/identify/message.pb.go +++ b/identify/message.pb.go @@ -9,7 +9,8 @@ It is generated from these files: message.proto It has these top-level messages: - Identify + Hello + Exchange */ package identify @@ -20,29 +21,77 @@ import math "math" var _ = proto.Marshal var _ = math.Inf -type Identify struct { - Id []byte `protobuf:"bytes,1,req,name=id" json:"id,omitempty"` - Pubkey []byte `protobuf:"bytes,2,req,name=pubkey" json:"pubkey,omitempty"` - XXX_unrecognized []byte `json:"-"` +type Hello struct { + Rand []byte `protobuf:"bytes,1,req,name=rand" json:"rand,omitempty"` + Pubkey []byte `protobuf:"bytes,2,req,name=pubkey" json:"pubkey,omitempty"` + Exchanges *string `protobuf:"bytes,3,req,name=exchanges" json:"exchanges,omitempty"` + Ciphers *string `protobuf:"bytes,4,req,name=ciphers" json:"ciphers,omitempty"` + Hashes *string `protobuf:"bytes,5,req,name=hashes" json:"hashes,omitempty"` + XXX_unrecognized []byte `json:"-"` } -func (m *Identify) Reset() { *m = Identify{} } -func (m *Identify) String() string { return proto.CompactTextString(m) } -func (*Identify) ProtoMessage() {} +func (m *Hello) Reset() { *m = Hello{} } +func (m *Hello) String() string { return proto.CompactTextString(m) } +func (*Hello) ProtoMessage() {} -func (m *Identify) GetId() []byte { +func (m *Hello) GetRand() []byte { if m != nil { - return m.Id + return m.Rand } return nil } -func (m *Identify) GetPubkey() []byte { +func (m *Hello) GetPubkey() []byte { if m != nil { return m.Pubkey } return nil } +func (m *Hello) GetExchanges() string { + if m != nil && m.Exchanges != nil { + return *m.Exchanges + } + return "" +} + +func (m *Hello) GetCiphers() string { + if m != nil && m.Ciphers != nil { + return *m.Ciphers + } + return "" +} + +func (m *Hello) GetHashes() string { + if m != nil && m.Hashes != nil { + return *m.Hashes + } + return "" +} + +type Exchange struct { + Epubkey []byte `protobuf:"bytes,1,req,name=epubkey" json:"epubkey,omitempty"` + Signature []byte `protobuf:"bytes,2,req,name=signature" json:"signature,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *Exchange) Reset() { *m = Exchange{} } +func (m *Exchange) String() string { return proto.CompactTextString(m) } +func (*Exchange) ProtoMessage() {} + +func (m *Exchange) GetEpubkey() []byte { + if m != nil { + return m.Epubkey + } + return nil +} + +func (m *Exchange) GetSignature() []byte { + if m != nil { + return m.Signature + } + return nil +} + func init() { } diff --git a/identify/message.proto b/identify/message.proto index df9ff515f..4c3e032e5 100644 --- a/identify/message.proto +++ b/identify/message.proto @@ -1,6 +1,14 @@ package identify; -message Identify { - required bytes id = 1; +message Hello { + required bytes rand = 1; required bytes pubkey = 2; + required string exchanges = 3; + required string ciphers = 4; + required string hashes = 5; +} + +message Exchange { + required bytes epubkey = 1; + required bytes signature = 2; } From ca8b7d48e017ecff998957219645c8f4077b0939 Mon Sep 17 00:00:00 2001 From: Brendan Mc Date: Wed, 3 Sep 2014 21:09:25 -0400 Subject: [PATCH 2/5] Apparently I changed something. --- identify/identify.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/identify/identify.go b/identify/identify.go index cdf86cca7..3879f61cc 100644 --- a/identify/identify.go +++ b/identify/identify.go @@ -34,7 +34,7 @@ var SupportedHashes = "SHA256,SHA512,SHA1" var ErrUnsupportedKeyType = errors.New("unsupported key type") // Perform initial communication with this peer to share node ID's and -// initiate communication +// initiate communication. (secureIn, secureOut, error) func Handshake(self, remote *peer.Peer, in, out chan []byte) (chan []byte, chan []byte, error) { // Generate and send Hello packet. // Hello = (rand, PublicKey, Supported) From 0e185788fcb99f68b54f135efb08fbb05c1377d2 Mon Sep 17 00:00:00 2001 From: Brendan Mc Date: Wed, 3 Sep 2014 21:28:11 -0400 Subject: [PATCH 3/5] Updated tests. --- identify/identify.go | 17 ++++++++--------- identify/identify_test.go | 4 ++-- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/identify/identify.go b/identify/identify.go index 3879f61cc..e80fb5c7e 100644 --- a/identify/identify.go +++ b/identify/identify.go @@ -4,8 +4,6 @@ package identify import ( "bytes" - "errors" - "crypto/aes" "crypto/cipher" "crypto/elliptic" @@ -14,6 +12,7 @@ import ( "crypto/sha1" "crypto/sha256" "crypto/sha512" + "errors" "hash" "math/big" "strings" @@ -86,12 +85,12 @@ func Handshake(self, remote *peer.Peer, in, out chan []byte) (chan []byte, chan return nil, nil, err } - cipherType, err := selectBest(SupportedExchanges, helloResp.GetCiphers()) + cipherType, err := selectBest(SupportedCiphers, helloResp.GetCiphers()) if err != nil { return nil, nil, err } - hashType, err := selectBest(SupportedExchanges, helloResp.GetHashes()) + hashType, err := selectBest(SupportedHashes, helloResp.GetHashes()) if err != nil { return nil, nil, err } @@ -249,10 +248,10 @@ func IdFromPubKey(pk ci.PubKey) (peer.ID, error) { func keyGenerator(cmp int, cipherType string, hashType string, secret []byte) ([]byte, []byte, []byte, []byte, []byte, []byte) { var cipherKeySize int switch cipherType { - case "AES128": - cipherKeySize = 2 * 16 - case "AES256": - cipherKeySize = 2 * 32 + case "AES-128": + cipherKeySize = 16 + case "AES-256": + cipherKeySize = 32 } ivSize := 16 @@ -387,7 +386,7 @@ func generateEPubKey(exchange string) ([]byte, func([]byte) ([]byte, error), err // Verify and unpack node's public key. curveSize := curve.Params().BitSize - if len(theirPub) != (curveSize / 2) { + if len(theirPub) != (curveSize / 4) { return nil, errors.New("Malformed public key.") } diff --git a/identify/identify_test.go b/identify/identify_test.go index 13cd7dfa0..6ac33039f 100644 --- a/identify/identify_test.go +++ b/identify/identify_test.go @@ -41,13 +41,13 @@ func TestHandshake(t *testing.T) { } go func() { - err := Handshake(pa, pb, cha, chb) + _, _, err := Handshake(pa, pb, cha, chb) if err != nil { t.Fatal(err) } }() - err = Handshake(pb, pa, chb, cha) + _, _, err = Handshake(pb, pa, chb, cha) if err != nil { t.Fatal(err) } From 80ec51f12942e8f21ab4e5f7aec3757993e58e2e Mon Sep 17 00:00:00 2001 From: Brendan Mc Date: Wed, 3 Sep 2014 21:38:29 -0400 Subject: [PATCH 4/5] Added last step of negotiation. --- identify/identify.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/identify/identify.go b/identify/identify.go index e80fb5c7e..0bc99f311 100644 --- a/identify/identify.go +++ b/identify/identify.go @@ -226,6 +226,15 @@ func Handshake(self, remote *peer.Peer, in, out chan []byte) (chan []byte, chan } }() + 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 From c6823ac6e40ecefd59a87fc93f694f4533d498c1 Mon Sep 17 00:00:00 2001 From: Brendan Mc Date: Thu, 4 Sep 2014 13:15:22 -0400 Subject: [PATCH 5/5] Cleaned up code some. --- crypto/key.go | 155 +++++++++++++++++++- crypto/rsa.go | 8 - identify/identify.go | 339 +++++++++++-------------------------------- 3 files changed, 230 insertions(+), 272 deletions(-) diff --git a/crypto/key.go b/crypto/key.go index f28ecf1c0..7e5027750 100644 --- a/crypto/key.go +++ b/crypto/key.go @@ -1,10 +1,18 @@ package crypto import ( + "bytes" "errors" + "crypto/elliptic" + "crypto/hmac" "crypto/rand" "crypto/rsa" + "crypto/sha1" + "crypto/sha256" + "crypto/sha512" + "hash" + "math/big" "code.google.com/p/goprotobuf/proto" ) @@ -19,9 +27,6 @@ type PrivKey interface { // Cryptographically sign the given bytes Sign([]byte) ([]byte, error) - // Decrypt a message encrypted with this keys public key - Decrypt([]byte) ([]byte, error) - // Return a public key paired with this private key GetPublic() PubKey @@ -36,13 +41,13 @@ type PubKey interface { // Verify that 'sig' is the signed hash of 'data' Verify(data []byte, sig []byte) (bool, error) - // Encrypt the given data with the public key - Encrypt([]byte) ([]byte, error) - // Bytes returns a serialized, storeable representation of this key Bytes() ([]byte, error) } +// Given a public key, generates the shared key. +type GenSharedKey func([]byte) ([]byte, error) + func GenerateKeyPair(typ, bits int) (PrivKey, PubKey, error) { switch typ { case RSA: @@ -57,6 +62,144 @@ func GenerateKeyPair(typ, bits int) (PrivKey, PubKey, error) { } } +// Generates an ephemeral public key and returns a function that will compute +// the shared secret key. Used in the identify module. +// +// Focuses only on ECDH now, but can be made more general in the future. +func GenerateEKeyPair(curveName string) ([]byte, GenSharedKey, error) { + var curve elliptic.Curve + + switch curveName { + case "P-224": + curve = elliptic.P224() + case "P-256": + curve = elliptic.P256() + case "P-384": + curve = elliptic.P384() + case "P-521": + curve = elliptic.P521() + } + + priv, x, y, err := elliptic.GenerateKey(curve, rand.Reader) + if err != nil { + return nil, nil, err + } + + var pubKey bytes.Buffer + pubKey.Write(x.Bytes()) + pubKey.Write(y.Bytes()) + + done := func(theirPub []byte) ([]byte, error) { + // Verify and unpack node's public key. + curveSize := curve.Params().BitSize + + if len(theirPub) != (curveSize / 4) { + return nil, errors.New("Malformed public key.") + } + + bound := (curveSize / 8) + x := big.NewInt(0) + y := big.NewInt(0) + + x.SetBytes(theirPub[0:bound]) + y.SetBytes(theirPub[bound : bound*2]) + + if !curve.IsOnCurve(x, y) { + return nil, errors.New("Invalid public key.") + } + + // Generate shared secret. + secret, _ := curve.ScalarMult(x, y, priv) + + return secret.Bytes(), nil + } + + return pubKey.Bytes(), done, nil +} + +// Generates a set of keys for each party by stretching the shared key. +// (myIV, theirIV, myCipherKey, theirCipherKey, myMACKey, theirMACKey) +func KeyStretcher(cmp int, cipherType string, hashType string, secret []byte) ([]byte, []byte, []byte, []byte, []byte, []byte) { + var cipherKeySize int + switch cipherType { + case "AES-128": + cipherKeySize = 16 + case "AES-256": + cipherKeySize = 32 + } + + ivSize := 16 + hmacKeySize := 20 + + seed := []byte("key expansion") + + result := make([]byte, 2*(ivSize+cipherKeySize+hmacKeySize)) + + var h func() hash.Hash + + switch hashType { + case "SHA1": + h = sha1.New + case "SHA256": + h = sha256.New + case "SHA512": + h = sha512.New + } + + m := hmac.New(h, secret) + m.Write(seed) + + a := m.Sum(nil) + + j := 0 + for j < len(result) { + m.Reset() + m.Write(a) + m.Write(seed) + b := m.Sum(nil) + + todo := len(b) + + if j+todo > len(result) { + todo = len(result) - j + } + + copy(result[j:j+todo], b) + + j += todo + + m.Reset() + m.Write(a) + a = m.Sum(nil) + } + + myResult := make([]byte, ivSize+cipherKeySize+hmacKeySize) + theirResult := make([]byte, ivSize+cipherKeySize+hmacKeySize) + + half := len(result) / 2 + + if cmp == 1 { + copy(myResult, result[:half]) + copy(theirResult, result[half:]) + } else if cmp == -1 { + copy(myResult, result[half:]) + copy(theirResult, result[:half]) + } else { // Shouldn't happen, but oh well. + copy(myResult, result[half:]) + copy(theirResult, result[half:]) + } + + myIV := myResult[0:ivSize] + myCKey := myResult[ivSize : ivSize+cipherKeySize] + myMKey := myResult[ivSize+cipherKeySize:] + + theirIV := theirResult[0:ivSize] + theirCKey := theirResult[ivSize : ivSize+cipherKeySize] + theirMKey := theirResult[ivSize+cipherKeySize:] + + return myIV, theirIV, myCKey, theirCKey, myMKey, theirMKey +} + func UnmarshalPublicKey(data []byte) (PubKey, error) { pmes := new(PBPublicKey) err := proto.Unmarshal(data, pmes) diff --git a/crypto/rsa.go b/crypto/rsa.go index fda3bcf58..31aa69596 100644 --- a/crypto/rsa.go +++ b/crypto/rsa.go @@ -28,10 +28,6 @@ func (pk *RsaPublicKey) Verify(data, sig []byte) (bool, error) { return true, nil } -func (pk *RsaPublicKey) Encrypt(message []byte) ([]byte, error) { - return rsa.EncryptPKCS1v15(rand.Reader, pk.k, message) -} - func (pk *RsaPublicKey) Bytes() ([]byte, error) { b, err := x509.MarshalPKIXPublicKey(pk.k) if err != nil { @@ -56,10 +52,6 @@ func (sk *RsaPrivateKey) Sign(message []byte) ([]byte, error) { return rsa.SignPKCS1v15(rand.Reader, sk.k, crypto.SHA256, hashed[:]) } -func (sk *RsaPrivateKey) Decrypt(ciphertext []byte) ([]byte, error) { - return rsa.DecryptPKCS1v15(rand.Reader, sk.k, ciphertext) -} - func (sk *RsaPrivateKey) GetPublic() PubKey { return &RsaPublicKey{&sk.k.PublicKey} } diff --git a/identify/identify.go b/identify/identify.go index 0bc99f311..017b03c0f 100644 --- a/identify/identify.go +++ b/identify/identify.go @@ -4,18 +4,17 @@ package identify import ( "bytes" + "errors" + "strings" + "crypto/aes" "crypto/cipher" - "crypto/elliptic" "crypto/hmac" "crypto/rand" "crypto/sha1" "crypto/sha256" "crypto/sha512" - "errors" "hash" - "math/big" - "strings" proto "code.google.com/p/goprotobuf/proto" ci "github.com/jbenet/go-ipfs/crypto" @@ -95,7 +94,7 @@ func Handshake(self, remote *peer.Peer, in, out chan []byte) (chan []byte, chan return nil, nil, err } - epubkey, done, err := generateEPubKey(exchange) // Generate EphemeralPubKey + epubkey, done, err := ci.GenerateEKeyPair(exchange) // Generate EphemeralPubKey var handshake bytes.Buffer // Gather corpus to sign. handshake.Write(encoded) @@ -144,87 +143,13 @@ func Handshake(self, remote *peer.Peer, in, out chan []byte) (chan []byte, chan } cmp := bytes.Compare(myPubKey, helloResp.GetPubkey()) - mIV, tIV, mCKey, tCKey, mMKey, tMKey := keyGenerator(cmp, cipherType, hashType, secret) + mIV, tIV, mCKey, tCKey, mMKey, tMKey := ci.KeyStretcher(cmp, cipherType, hashType, secret) secureIn := make(chan []byte) secureOut := make(chan []byte) - go func() { - myBlock, _ := aes.NewCipher(mCKey) - myCipher := cipher.NewCTR(myBlock, mIV) - - theirBlock, _ := aes.NewCipher(tCKey) - theirCipher := cipher.NewCTR(theirBlock, tIV) - - var myMac, theirMac hash.Hash - var macSize int - - switch hashType { - case "SHA1": - myMac = hmac.New(sha1.New, mMKey) - theirMac = hmac.New(sha1.New, tMKey) - macSize = 20 - - case "SHA256": - myMac = hmac.New(sha256.New, mMKey) - theirMac = hmac.New(sha256.New, tMKey) - macSize = 32 - - case "SHA512": - myMac = hmac.New(sha512.New, mMKey) - theirMac = hmac.New(sha512.New, tMKey) - macSize = 64 - } - - for { - select { - case data, ok := <-secureOut: - if !ok { - 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 - - case data, ok := <-in: - if !ok { - 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 - } - } - } - }() + go secureInProxy(in, secureIn, hashType, tIV, tCKey, tMKey) + go secureOutProxy(out, secureOut, hashType, mIV, mCKey, mMKey) finished := []byte("Finished") @@ -240,6 +165,80 @@ func Handshake(self, remote *peer.Peer, in, out chan []byte) (chan []byte, chan 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, 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 { + 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, 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 { + 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 + } +} + func IdFromPubKey(pk ci.PubKey) (peer.ID, error) { b, err := pk.Bytes() if err != nil { @@ -252,89 +251,6 @@ func IdFromPubKey(pk ci.PubKey) (peer.ID, error) { return peer.ID(hash), nil } -// Generates a set of keys for each party by stretching the shared key. -// (myIV, theirIV, myCipherKey, theirCipherKey, myMACKey, theirMACKey) -func keyGenerator(cmp int, cipherType string, hashType string, secret []byte) ([]byte, []byte, []byte, []byte, []byte, []byte) { - var cipherKeySize int - switch cipherType { - case "AES-128": - cipherKeySize = 16 - case "AES-256": - cipherKeySize = 32 - } - - ivSize := 16 - hmacKeySize := 20 - - seed := []byte("key expansion") - - result := make([]byte, 2*(ivSize+cipherKeySize+hmacKeySize)) - - var h func() hash.Hash - - switch hashType { - case "SHA1": - h = sha1.New - case "SHA256": - h = sha256.New - case "SHA512": - h = sha512.New - } - - m := hmac.New(h, secret) - m.Write(seed) - - a := m.Sum(nil) - - j := 0 - for j < len(result) { - m.Reset() - m.Write(a) - m.Write(seed) - b := m.Sum(nil) - - todo := len(b) - - if j+todo > len(result) { - todo = len(result) - j - } - - copy(result[j:j+todo], b) - - j += todo - - m.Reset() - m.Write(a) - a = m.Sum(nil) - } - - myResult := make([]byte, ivSize+cipherKeySize+hmacKeySize) - theirResult := make([]byte, ivSize+cipherKeySize+hmacKeySize) - - half := len(result) / 2 - - if cmp == 1 { - copy(myResult, result[:half]) - copy(theirResult, result[half:]) - } else if cmp == -1 { - copy(myResult, result[half:]) - copy(theirResult, result[:half]) - } else { // Shouldn't happen, but oh well. - copy(myResult, result[half:]) - copy(theirResult, result[half:]) - } - - myIV := myResult[0:ivSize] - myCKey := myResult[ivSize : ivSize+cipherKeySize] - myMKey := myResult[ivSize+cipherKeySize:] - - theirIV := theirResult[0:ivSize] - theirCKey := theirResult[ivSize : ivSize+cipherKeySize] - theirMKey := theirResult[ivSize+cipherKeySize:] - - return myIV, theirIV, myCKey, theirCKey, myMKey, theirMKey -} - // 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. @@ -372,96 +288,3 @@ func selectBest(myPrefs, theirPrefs string) (string, error) { return "", errors.New("No algorithms in common!") } - -// Generates an ephemeral public key and returns a function that will compute -// the shared secret key. -// -// Focuses only on ECDH now, but can be made more general in the future. -func generateEPubKey(exchange string) ([]byte, func([]byte) ([]byte, error), error) { - genKeyPair := func(curve elliptic.Curve) ([]byte, []byte, error) { - priv, x, y, err := elliptic.GenerateKey(curve, rand.Reader) - if err != nil { - return nil, nil, err - } - - var pubKey bytes.Buffer - pubKey.Write(x.Bytes()) - pubKey.Write(y.Bytes()) - - return pubKey.Bytes(), priv, nil - } - - genSec := func(curve elliptic.Curve, theirPub []byte, myPriv []byte) ([]byte, error) { - // Verify and unpack node's public key. - curveSize := curve.Params().BitSize - - if len(theirPub) != (curveSize / 4) { - return nil, errors.New("Malformed public key.") - } - - bound := (curveSize / 8) - x := big.NewInt(0) - y := big.NewInt(0) - - x.SetBytes(theirPub[0:bound]) - y.SetBytes(theirPub[bound : bound*2]) - - if !curve.IsOnCurve(x, y) { - return nil, errors.New("Invalid public key.") - } - - // Generate shared secret. - secret, _ := curve.ScalarMult(x, y, myPriv) - - return secret.Bytes(), nil - } - - switch exchange { - case "P-224": - curve := elliptic.P224() - pub, priv, err := genKeyPair(curve) - if err != nil { - return nil, nil, err - } - - done := func(theirs []byte) ([]byte, error) { return genSec(curve, theirs, priv) } - - return pub, done, nil - - case "P-256": - curve := elliptic.P256() - pub, priv, err := genKeyPair(curve) - if err != nil { - return nil, nil, err - } - - done := func(theirs []byte) ([]byte, error) { return genSec(curve, theirs, priv) } - - return pub, done, nil - - case "P-384": - curve := elliptic.P384() - pub, priv, err := genKeyPair(curve) - if err != nil { - return nil, nil, err - } - - done := func(theirs []byte) ([]byte, error) { return genSec(curve, theirs, priv) } - - return pub, done, nil - - case "P-521": - curve := elliptic.P521() - pub, priv, err := genKeyPair(curve) - if err != nil { - return nil, nil, err - } - - done := func(theirs []byte) ([]byte, error) { return genSec(curve, theirs, priv) } - - return pub, done, nil - - } - - return nil, nil, errors.New("Something silly happened.") -}