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
-}