@cryptotaxi247 / kubo / commits / c787adaa6

better handshake for all.

Juan Batiz-Benet committed Sep 14, 2014 at 04:15 UTC c787adaa65b78b49b320476257acb33b99680409
7 files changed +433 -37
crypto/spipe/Makefile new
+8
@@ -0,0 +1,8 @@
1 +
2 +all: message.pb.go
3 +
4 +message.pb.go: message.proto
5 + protoc --gogo_out=. --proto_path=../../../../../:/usr/local/opt/protobuf/include:. $<
6 +
7 +clean:
8 + rm message.pb.go
crypto/spipe/handshake.go new
+310
@@ -0,0 +1,310 @@
1 +// Package spipe handles establishing secure communication between two peers.
2 +
3 +package spipe
4 +
5 +import (
6 + "bytes"
7 + "errors"
8 + "strings"
9 +
10 + "crypto/aes"
11 + "crypto/cipher"
12 + "crypto/hmac"
13 + "crypto/rand"
14 + "crypto/sha1"
15 + "crypto/sha256"
16 + "crypto/sha512"
17 + "hash"
18 +
19 + proto "github.com/jbenet/go-ipfs/Godeps/_workspace/src/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 +// List of supported ECDH curves
26 +var SupportedExchanges = "P-256,P-224,P-384,P-521"
27 +
28 +// List of supported Ciphers
29 +var SupportedCiphers = "AES-256,AES-128"
30 +
31 +// List of supported Hashes
32 +var SupportedHashes = "SHA256,SHA512,SHA1"
33 +
34 +// ErrUnsupportedKeyType is returned when a private key cast/type switch fails.
35 +var ErrUnsupportedKeyType = errors.New("unsupported key type")
36 +
37 +// ErrClosed signals the closing of a connection.
38 +var ErrClosed = errors.New("connection closed")
39 +
40 +// handsahke performs initial communication over insecure channel to share
41 +// keys, IDs, and initiate communication.
42 +func (s *SecurePipe) handshake() error {
43 + // Generate and send Hello packet.
44 + // Hello = (rand, PublicKey, Supported)
45 + nonce := make([]byte, 16)
46 + _, err := rand.Read(nonce)
47 + if err != nil {
48 + return err
49 + }
50 +
51 + myPubKey, err := s.local.PubKey.Bytes()
52 + if err != nil {
53 + return err
54 + }
55 +
56 + proposeMsg := new(Propose)
57 + proposeMsg.Rand = nonce
58 + proposeMsg.Pubkey = myPubKey
59 + proposeMsg.Exchanges = &SupportedExchanges
60 + proposeMsg.Ciphers = &SupportedCiphers
61 + proposeMsg.Hashes = &SupportedHashes
62 +
63 + encoded, err := proto.Marshal(proposeMsg)
64 + if err != nil {
65 + return err
66 + }
67 +
68 + s.insecure.Out <- encoded
69 +
70 + // Parse their Propose packet and generate an Exchange packet.
71 + // Exchange = (EphemeralPubKey, Signature)
72 + var resp []byte
73 + select {
74 + case <-s.ctx.Done():
75 + return ErrClosed
76 + case resp = <-s.Duplex.In:
77 + }
78 +
79 + proposeResp := new(Propose)
80 + err = proto.Unmarshal(resp, proposeResp)
81 + if err != nil {
82 + return err
83 + }
84 +
85 + s.remote.PubKey, err = ci.UnmarshalPublicKey(proposeResp.GetPubkey())
86 + if err != nil {
87 + return err
88 + }
89 +
90 + s.remote.ID, err = IDFromPubKey(s.remote.PubKey)
91 + if err != nil {
92 + return err
93 + }
94 +
95 + exchange, err := selectBest(SupportedExchanges, proposeResp.GetExchanges())
96 + if err != nil {
97 + return err
98 + }
99 +
100 + cipherType, err := selectBest(SupportedCiphers, proposeResp.GetCiphers())
101 + if err != nil {
102 + return err
103 + }
104 +
105 + hashType, err := selectBest(SupportedHashes, proposeResp.GetHashes())
106 + if err != nil {
107 + return err
108 + }
109 +
110 + epubkey, done, err := ci.GenerateEKeyPair(exchange) // Generate EphemeralPubKey
111 +
112 + var handshake bytes.Buffer // Gather corpus to sign.
113 + handshake.Write(encoded)
114 + handshake.Write(resp)
115 + handshake.Write(epubkey)
116 +
117 + exPacket := new(Exchange)
118 +
119 + exPacket.Epubkey = epubkey
120 + exPacket.Signature, err = s.local.PrivKey.Sign(handshake.Bytes())
121 + if err != nil {
122 + return err
123 + }
124 +
125 + exEncoded, err := proto.Marshal(exPacket)
126 +
127 + s.insecure.Out <- exEncoded
128 +
129 + // Parse their Exchange packet and generate a Finish packet.
130 + // Finish = E('Finish')
131 + var resp1 []byte
132 + select {
133 + case <-s.ctx.Done():
134 + return ErrClosed
135 + case resp1 = <-s.insecure.In:
136 + }
137 +
138 + exchangeResp := new(Exchange)
139 + err = proto.Unmarshal(resp1, exchangeResp)
140 + if err != nil {
141 + return err
142 + }
143 +
144 + var theirHandshake bytes.Buffer
145 + theirHandshake.Write(resp)
146 + theirHandshake.Write(encoded)
147 + theirHandshake.Write(exchangeResp.GetEpubkey())
148 +
149 + ok, err := s.remote.PubKey.Verify(theirHandshake.Bytes(), exchangeResp.GetSignature())
150 + if err != nil {
151 + return err
152 + }
153 +
154 + if !ok {
155 + return errors.New("Bad signature!")
156 + }
157 +
158 + secret, err := done(exchangeResp.GetEpubkey())
159 + if err != nil {
160 + return err
161 + }
162 +
163 + cmp := bytes.Compare(myPubKey, proposeResp.GetPubkey())
164 + mIV, tIV, mCKey, tCKey, mMKey, tMKey := ci.KeyStretcher(cmp, cipherType, hashType, secret)
165 +
166 + go s.handleSecureIn(hashType, tIV, tCKey, tMKey)
167 + go s.handleSecureOut(hashType, mIV, mCKey, mMKey)
168 +
169 + finished := []byte("Finished")
170 +
171 + s.Out <- finished
172 + var resp2 []byte
173 + select {
174 + case <-s.ctx.Done():
175 + return ErrClosed
176 + case resp2 = <-s.Duplex.In:
177 + }
178 +
179 + if bytes.Compare(resp2, finished) != 0 {
180 + return errors.New("Negotiation failed.")
181 + }
182 +
183 + u.DOut("[%s] identify: Got node id: %s\n", s.local.ID.Pretty(), s.remote.ID.Pretty())
184 + return nil
185 +}
186 +
187 +func makeMac(hashType string, key []byte) (hash.Hash, int) {
188 + switch hashType {
189 + case "SHA1":
190 + return hmac.New(sha1.New, key), sha1.Size
191 + case "SHA512":
192 + return hmac.New(sha512.New, key), sha512.Size
193 + default:
194 + return hmac.New(sha256.New, key), sha256.Size
195 + }
196 +}
197 +
198 +func (s *SecurePipe) handleSecureIn(hashType string, tIV, tCKey, tMKey []byte) {
199 + theirBlock, _ := aes.NewCipher(tCKey)
200 + theirCipher := cipher.NewCTR(theirBlock, tIV)
201 +
202 + theirMac, macSize := makeMac(hashType, tMKey)
203 +
204 + for {
205 + data, ok := <-s.insecure.In
206 + if !ok {
207 + return
208 + }
209 +
210 + if len(data) <= macSize {
211 + continue
212 + }
213 +
214 + mark := len(data) - macSize
215 + buff := make([]byte, mark)
216 +
217 + theirCipher.XORKeyStream(buff, data[0:mark])
218 +
219 + theirMac.Write(data[0:mark])
220 + expected := theirMac.Sum(nil)
221 + theirMac.Reset()
222 +
223 + hmacOk := hmac.Equal(data[mark:], expected)
224 +
225 + if hmacOk {
226 + s.Duplex.In <- buff
227 + } else {
228 + s.Duplex.In <- nil
229 + }
230 + }
231 +}
232 +
233 +func (s *SecurePipe) handleSecureOut(hashType string, mIV, mCKey, mMKey []byte) {
234 + myBlock, _ := aes.NewCipher(mCKey)
235 + myCipher := cipher.NewCTR(myBlock, mIV)
236 +
237 + myMac, macSize := makeMac(hashType, mMKey)
238 +
239 + for {
240 + data, ok := <-s.Out
241 + if !ok {
242 + return
243 + }
244 +
245 + if len(data) == 0 {
246 + continue
247 + }
248 +
249 + buff := make([]byte, len(data)+macSize)
250 +
251 + myCipher.XORKeyStream(buff, data)
252 +
253 + myMac.Write(buff[0:len(data)])
254 + copy(buff[len(data):], myMac.Sum(nil))
255 + myMac.Reset()
256 +
257 + s.insecure.Out <- buff
258 + }
259 +}
260 +
261 +// IDFromPubKey retrieves a Public Key from the peer given by pk
262 +func IDFromPubKey(pk ci.PubKey) (peer.ID, error) {
263 + b, err := pk.Bytes()
264 + if err != nil {
265 + return nil, err
266 + }
267 + hash, err := u.Hash(b)
268 + if err != nil {
269 + return nil, err
270 + }
271 + return peer.ID(hash), nil
272 +}
273 +
274 +// Determines which algorithm to use. Note: f(a, b) = f(b, a)
275 +func selectBest(myPrefs, theirPrefs string) (string, error) {
276 + // Person with greatest hash gets first choice.
277 + myHash, err := u.Hash([]byte(myPrefs))
278 + if err != nil {
279 + return "", err
280 + }
281 +
282 + theirHash, err := u.Hash([]byte(theirPrefs))
283 + if err != nil {
284 + return "", err
285 + }
286 +
287 + cmp := bytes.Compare(myHash, theirHash)
288 + var firstChoiceArr, secChoiceArr []string
289 +
290 + if cmp == -1 {
291 + firstChoiceArr = strings.Split(theirPrefs, ",")
292 + secChoiceArr = strings.Split(myPrefs, ",")
293 + } else if cmp == 1 {
294 + firstChoiceArr = strings.Split(myPrefs, ",")
295 + secChoiceArr = strings.Split(theirPrefs, ",")
296 + } else { // Exact same preferences.
297 + myPrefsArr := strings.Split(myPrefs, ",")
298 + return myPrefsArr[0], nil
299 + }
300 +
301 + for _, secChoice := range secChoiceArr {
302 + for _, firstChoice := range firstChoiceArr {
303 + if firstChoice == secChoice {
304 + return firstChoice, nil
305 + }
306 + }
307 + }
308 +
309 + return "", errors.New("No algorithms in common!")
310 +}
crypto/spipe/identify_test.go renamed
+1 -1
@@ -1,4 +1,4 @@
1 -package identify
1 +package spipe
2
3 import (
4 "testing"
crypto/spipe/message.pb.go renamed
+24 -22
@@ -1,68 +1,70 @@
1 -// Code generated by protoc-gen-go.
1 +// Code generated by protoc-gen-gogo.
2 // source: message.proto
3 // DO NOT EDIT!
4
5 /*
6 -Package identify is a generated protocol buffer package.
6 +Package spipe is a generated protocol buffer package.
7
8 It is generated from these files:
9 message.proto
10
11 It has these top-level messages:
12 - Hello
12 + Propose
13 Exchange
14 */
15 -package identify
15 +package spipe
16
17 -import proto "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto"
17 +import proto "code.google.com/p/gogoprotobuf/proto"
18 +import json "encoding/json"
19 import math "math"
20
20 -// Reference imports to suppress errors if they are not otherwise used.
21 +// Reference proto, json, and math imports to suppress error if they are not otherwise used.
22 var _ = proto.Marshal
23 +var _ = &json.SyntaxError{}
24 var _ = math.Inf
25
24 -type Hello struct {
25 - Rand []byte `protobuf:"bytes,1,req,name=rand" json:"rand,omitempty"`
26 - Pubkey []byte `protobuf:"bytes,2,req,name=pubkey" json:"pubkey,omitempty"`
27 - Exchanges *string `protobuf:"bytes,3,req,name=exchanges" json:"exchanges,omitempty"`
28 - Ciphers *string `protobuf:"bytes,4,req,name=ciphers" json:"ciphers,omitempty"`
29 - Hashes *string `protobuf:"bytes,5,req,name=hashes" json:"hashes,omitempty"`
26 +type Propose struct {
27 + Rand []byte `protobuf:"bytes,1,opt,name=rand" json:"rand,omitempty"`
28 + Pubkey []byte `protobuf:"bytes,2,opt,name=pubkey" json:"pubkey,omitempty"`
29 + Exchanges *string `protobuf:"bytes,3,opt,name=exchanges" json:"exchanges,omitempty"`
30 + Ciphers *string `protobuf:"bytes,4,opt,name=ciphers" json:"ciphers,omitempty"`
31 + Hashes *string `protobuf:"bytes,5,opt,name=hashes" json:"hashes,omitempty"`
32 XXX_unrecognized []byte `json:"-"`
33 }
34
33 -func (m *Hello) Reset() { *m = Hello{} }
34 -func (m *Hello) String() string { return proto.CompactTextString(m) }
35 -func (*Hello) ProtoMessage() {}
35 +func (m *Propose) Reset() { *m = Propose{} }
36 +func (m *Propose) String() string { return proto.CompactTextString(m) }
37 +func (*Propose) ProtoMessage() {}
38
37 -func (m *Hello) GetRand() []byte {
39 +func (m *Propose) GetRand() []byte {
40 if m != nil {
41 return m.Rand
42 }
43 return nil
44 }
45
44 -func (m *Hello) GetPubkey() []byte {
46 +func (m *Propose) GetPubkey() []byte {
47 if m != nil {
48 return m.Pubkey
49 }
50 return nil
51 }
52
51 -func (m *Hello) GetExchanges() string {
53 +func (m *Propose) GetExchanges() string {
54 if m != nil && m.Exchanges != nil {
55 return *m.Exchanges
56 }
57 return ""
58 }
59
58 -func (m *Hello) GetCiphers() string {
60 +func (m *Propose) GetCiphers() string {
61 if m != nil && m.Ciphers != nil {
62 return *m.Ciphers
63 }
64 return ""
65 }
66
65 -func (m *Hello) GetHashes() string {
67 +func (m *Propose) GetHashes() string {
68 if m != nil && m.Hashes != nil {
69 return *m.Hashes
70 }
@@ -70,8 +72,8 @@ func (m *Hello) GetHashes() string {
72 }
73
74 type Exchange struct {
73 - Epubkey []byte `protobuf:"bytes,1,req,name=epubkey" json:"epubkey,omitempty"`
74 - Signature []byte `protobuf:"bytes,2,req,name=signature" json:"signature,omitempty"`
75 + Epubkey []byte `protobuf:"bytes,1,opt,name=epubkey" json:"epubkey,omitempty"`
76 + Signature []byte `protobuf:"bytes,2,opt,name=signature" json:"signature,omitempty"`
77 XXX_unrecognized []byte `json:"-"`
78 }
79
crypto/spipe/message.proto new
+14
@@ -0,0 +1,14 @@
1 +package spipe;
2 +
3 +message Propose {
4 + optional bytes rand = 1;
5 + optional bytes pubkey = 2;
6 + optional string exchanges = 3;
7 + optional string ciphers = 4;
8 + optional string hashes = 5;
9 +}
10 +
11 +message Exchange {
12 + optional bytes epubkey = 1;
13 + optional bytes signature = 2;
14 +}
crypto/spipe/pipe.go new
+76
@@ -0,0 +1,76 @@
1 +package spipe
2 +
3 +import (
4 + "errors"
5 +
6 + context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context"
7 + peer "github.com/jbenet/go-ipfs/peer"
8 +)
9 +
10 +// Duplex is a simple duplex channel
11 +type Duplex struct {
12 + In chan []byte
13 + Out chan []byte
14 +}
15 +
16 +// SecurePipe objects represent a bi-directional message channel.
17 +type SecurePipe struct {
18 + Duplex
19 + insecure Duplex
20 +
21 + local *peer.Peer
22 + remote *peer.Peer
23 +
24 + params params
25 +
26 + ctx context.Context
27 + cancel context.CancelFunc
28 +}
29 +
30 +// options in a secure pipe
31 +type params struct {
32 +}
33 +
34 +// NewSecurePipe constructs a pipe with channels of a given buffer size.
35 +func NewSecurePipe(ctx context.Context, bufsize int, local,
36 + remote *peer.Peer) (*SecurePipe, error) {
37 +
38 + sp := &SecurePipe{
39 + Duplex: Duplex{
40 + In: make(chan []byte, bufsize),
41 + Out: make(chan []byte, bufsize),
42 + },
43 + local: local,
44 + remote: remote,
45 + }
46 + return sp, nil
47 +}
48 +
49 +// Wrap creates a secure connection on top of an insecure duplex channel.
50 +func (s *SecurePipe) Wrap(ctx context.Context, insecure Duplex) error {
51 + if s.ctx != nil {
52 + return errors.New("Pipe in use")
53 + }
54 +
55 + s.insecure = insecure
56 + s.ctx, s.cancel = context.WithCancel(ctx)
57 +
58 + if err := s.handshake(); err != nil {
59 + s.cancel()
60 + return err
61 + }
62 +
63 + return nil
64 +}
65 +
66 +// Close closes the secure pipe
67 +func (s *SecurePipe) Close() error {
68 + if s.cancel == nil {
69 + return errors.New("pipe already closed")
70 + }
71 +
72 + s.cancel()
73 + s.cancel = nil
74 + close(s.In)
75 + return nil
76 +}
identify/message.proto deleted
-14
@@ -1,14 +0,0 @@
1 -package identify;
2 -
3 -message Hello {
4 - required bytes rand = 1;
5 - required bytes pubkey = 2;
6 - required string exchanges = 3;
7 - required string ciphers = 4;
8 - required string hashes = 5;
9 -}
10 -
11 -message Exchange {
12 - required bytes epubkey = 1;
13 - required bytes signature = 2;
14 -}