secio -- spipe v2
This commit introduces secio, the next generation spipe.
Juan Batiz-Benet committed
Dec 11, 2014 at 01:50 UTC
5aab08fe4fdc94aeb74013d7db87da380c53ad37
8 files changed
+791
crypto/secio/al.go
new
+116
@@ -0,0 +1,116 @@
1
+package secio
2
+
3
+import (
4
+ "errors"
5
+ "fmt"
6
+ "strings"
7
+
8
+ "crypto/aes"
9
+ "crypto/cipher"
10
+ "crypto/hmac"
11
+ "crypto/sha1"
12
+ "crypto/sha256"
13
+ "crypto/sha512"
14
+ "hash"
15
+
16
+ bfish "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.crypto/blowfish"
17
+
18
+ ci "github.com/jbenet/go-ipfs/crypto"
19
+)
20
+
21
+// List of supported ECDH curves
22
+var SupportedExchanges = "P-256,P-224,P-384,P-521"
23
+
24
+// List of supported Ciphers
25
+var SupportedCiphers = "AES-256,AES-128,Blowfish"
26
+
27
+// List of supported Hashes
28
+var SupportedHashes = "SHA256,SHA512"
29
+
30
+type HMAC struct {
31
+ hash.Hash
32
+ size int
33
+}
34
+
35
+// encParams represent encryption parameters
36
+type encParams struct {
37
+ // keys
38
+ permanentPubKey ci.PubKey
39
+ ephemeralPubKey []byte
40
+ keys ci.StretchedKeys
41
+
42
+ // selections
43
+ curveT string
44
+ cipherT string
45
+ hashT string
46
+
47
+ // cipher + mac
48
+ cipher cipher.Stream
49
+ mac HMAC
50
+}
51
+
52
+func (e *encParams) makeMacAndCipher() error {
53
+ m, err := newMac(e.hashT, e.keys.MacKey)
54
+ if err != nil {
55
+ return err
56
+ }
57
+
58
+ bc, err := newBlockCipher(e.cipherT, e.keys.CipherKey)
59
+ if err != nil {
60
+ return err
61
+ }
62
+
63
+ e.cipher = cipher.NewCTR(bc, e.keys.IV)
64
+ e.mac = m
65
+ return nil
66
+}
67
+
68
+func newMac(hashType string, key []byte) (HMAC, error) {
69
+ switch hashType {
70
+ case "SHA1":
71
+ return HMAC{hmac.New(sha1.New, key), sha1.Size}, nil
72
+ case "SHA512":
73
+ return HMAC{hmac.New(sha512.New, key), sha512.Size}, nil
74
+ case "SHA256":
75
+ return HMAC{hmac.New(sha256.New, key), sha256.Size}, nil
76
+ default:
77
+ return HMAC{}, fmt.Errorf("Unrecognized hash type: %s", hashType)
78
+ }
79
+}
80
+
81
+func newBlockCipher(cipherT string, key []byte) (cipher.Block, error) {
82
+ switch cipherT {
83
+ case "AES-128", "AES-256":
84
+ return aes.NewCipher(key)
85
+ case "Blowfish":
86
+ return bfish.NewCipher(key)
87
+ default:
88
+ return nil, fmt.Errorf("Unrecognized cipher type: %s", cipherT)
89
+ }
90
+}
91
+
92
+// Determines which algorithm to use. Note: f(a, b) = f(b, a)
93
+func selectBest(order int, p1, p2 string) (string, error) {
94
+ var f, s []string
95
+ switch order {
96
+ case -1:
97
+ f = strings.Split(p2, ",")
98
+ s = strings.Split(p1, ",")
99
+ case 1:
100
+ f = strings.Split(p1, ",")
101
+ s = strings.Split(p2, ",")
102
+ default: // Exact same preferences.
103
+ p := strings.Split(p1, ",")
104
+ return p[0], nil
105
+ }
106
+
107
+ for _, fc := range f {
108
+ for _, sc := range s {
109
+ if fc == sc {
110
+ return fc, nil
111
+ }
112
+ }
113
+ }
114
+
115
+ return "", errors.New("No algorithms in common!")
116
+}
crypto/secio/interface.go
new
+72
@@ -0,0 +1,72 @@
1
+// package secio handles establishing secure communication between two peers.
2
+package secio
3
+
4
+import (
5
+ "io"
6
+
7
+ context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context"
8
+ msgio "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-msgio"
9
+
10
+ peer "github.com/jbenet/go-ipfs/peer"
11
+)
12
+
13
+// SessionGenerator constructs secure communication sessions for a peer.
14
+type SessionGenerator struct {
15
+ Local peer.Peer
16
+ Peerstore peer.Peerstore
17
+}
18
+
19
+// NewSession takes an insecure io.ReadWriter, performs a TLS-like
20
+// handshake with the other side, and returns a secure session.
21
+// See the source for the protocol details and security implementation.
22
+// The provided Context is only needed for the duration of this function.
23
+func (sg *SessionGenerator) NewSession(ctx context.Context,
24
+ insecure io.ReadWriter) (Session, error) {
25
+
26
+ if ctx == nil {
27
+ ctx = context.Background()
28
+ }
29
+ ctx, cancel := context.WithCancel(ctx)
30
+
31
+ ss := newSecureSession(sg.Local, sg.Peerstore)
32
+ if err := ss.handshake(ctx, insecure); err != nil {
33
+ cancel()
34
+ return nil, err
35
+ }
36
+
37
+ return ss, nil
38
+}
39
+
40
+type Session interface {
41
+ // ReadWriter returns the encrypted communication channel
42
+ ReadWriter() msgio.ReadWriteCloser
43
+
44
+ // LocalPeer retrieves the local peer.
45
+ LocalPeer() peer.Peer
46
+
47
+ // RemotePeer retrieves the local peer.
48
+ RemotePeer() peer.Peer
49
+
50
+ // Close closes the secure session
51
+ Close() error
52
+}
53
+
54
+// SecureReadWriter returns the encrypted communication channel
55
+func (s *secureSession) ReadWriter() msgio.ReadWriteCloser {
56
+ return s.secure
57
+}
58
+
59
+// LocalPeer retrieves the local peer.
60
+func (s *secureSession) LocalPeer() peer.Peer {
61
+ return s.localPeer
62
+}
63
+
64
+// RemotePeer retrieves the local peer.
65
+func (s *secureSession) RemotePeer() peer.Peer {
66
+ return s.remotePeer
67
+}
68
+
69
+// Close closes the secure session
70
+func (s *secureSession) Close() error {
71
+ return s.secure.Close()
72
+}
crypto/secio/internal/pb/Makefile
new
+10
@@ -0,0 +1,10 @@
1
+PB = $(wildcard *.proto)
2
+GO = $(PB:.proto=.pb.go)
3
+
4
+all: $(GO)
5
+
6
+%.pb.go: %.proto
7
+ protoc --gogo_out=. --proto_path=../../../../../../:/usr/local/opt/protobuf/include:. $<
8
+
9
+clean:
10
+ rm *.pb.go
crypto/secio/internal/pb/spipe.pb.go
new
+99
@@ -0,0 +1,99 @@
1
+// Code generated by protoc-gen-gogo.
2
+// source: spipe.proto
3
+// DO NOT EDIT!
4
+
5
+/*
6
+Package spipe_pb is a generated protocol buffer package.
7
+
8
+It is generated from these files:
9
+ spipe.proto
10
+
11
+It has these top-level messages:
12
+ Propose
13
+ Exchange
14
+*/
15
+package spipe_pb
16
+
17
+import proto "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/gogoprotobuf/proto"
18
+import json "encoding/json"
19
+import math "math"
20
+
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
+
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
+
35
+func (m *Propose) Reset() { *m = Propose{} }
36
+func (m *Propose) String() string { return proto.CompactTextString(m) }
37
+func (*Propose) ProtoMessage() {}
38
+
39
+func (m *Propose) GetRand() []byte {
40
+ if m != nil {
41
+ return m.Rand
42
+ }
43
+ return nil
44
+}
45
+
46
+func (m *Propose) GetPubkey() []byte {
47
+ if m != nil {
48
+ return m.Pubkey
49
+ }
50
+ return nil
51
+}
52
+
53
+func (m *Propose) GetExchanges() string {
54
+ if m != nil && m.Exchanges != nil {
55
+ return *m.Exchanges
56
+ }
57
+ return ""
58
+}
59
+
60
+func (m *Propose) GetCiphers() string {
61
+ if m != nil && m.Ciphers != nil {
62
+ return *m.Ciphers
63
+ }
64
+ return ""
65
+}
66
+
67
+func (m *Propose) GetHashes() string {
68
+ if m != nil && m.Hashes != nil {
69
+ return *m.Hashes
70
+ }
71
+ return ""
72
+}
73
+
74
+type Exchange struct {
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
+
80
+func (m *Exchange) Reset() { *m = Exchange{} }
81
+func (m *Exchange) String() string { return proto.CompactTextString(m) }
82
+func (*Exchange) ProtoMessage() {}
83
+
84
+func (m *Exchange) GetEpubkey() []byte {
85
+ if m != nil {
86
+ return m.Epubkey
87
+ }
88
+ return nil
89
+}
90
+
91
+func (m *Exchange) GetSignature() []byte {
92
+ if m != nil {
93
+ return m.Signature
94
+ }
95
+ return nil
96
+}
97
+
98
+func init() {
99
+}
crypto/secio/internal/pb/spipe.proto
new
+14
@@ -0,0 +1,14 @@
1
+package spipe.pb;
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/secio/io_test.go
new
+1
@@ -0,0 +1 @@
1
+package secio
crypto/secio/protocol.go
new
+282
@@ -0,0 +1,282 @@
1
+package secio
2
+
3
+import (
4
+ "bytes"
5
+ "crypto/rand"
6
+ "errors"
7
+ "fmt"
8
+ "io"
9
+
10
+ context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context"
11
+ msgio "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-msgio"
12
+
13
+ ci "github.com/jbenet/go-ipfs/crypto"
14
+ pb "github.com/jbenet/go-ipfs/crypto/spipe/internal/pb"
15
+ peer "github.com/jbenet/go-ipfs/peer"
16
+ u "github.com/jbenet/go-ipfs/util"
17
+)
18
+
19
+var log = u.Logger("spipe")
20
+
21
+// ErrUnsupportedKeyType is returned when a private key cast/type switch fails.
22
+var ErrUnsupportedKeyType = errors.New("unsupported key type")
23
+
24
+// ErrClosed signals the closing of a connection.
25
+var ErrClosed = errors.New("connection closed")
26
+
27
+// nonceSize is the size of our nonces (in bytes)
28
+const nonceSize = 16
29
+
30
+// secureSession encapsulates all the parameters needed for encrypting
31
+// and decrypting traffic from an insecure channel.
32
+type secureSession struct {
33
+ secure msgio.ReadWriteCloser
34
+
35
+ insecure io.ReadWriter
36
+ insecureM msgio.ReadWriter
37
+
38
+ peers peer.Peerstore
39
+ localPeer peer.Peer
40
+ remotePeer peer.Peer
41
+
42
+ local encParams
43
+ remote encParams
44
+
45
+ sharedSecret []byte
46
+}
47
+
48
+func newSecureSession(local peer.Peer, peers peer.Peerstore) *secureSession {
49
+ return &secureSession{peers: peers, localPeer: local}
50
+}
51
+
52
+// handsahke performs initial communication over insecure channel to share
53
+// keys, IDs, and initiate communication, assigning all necessary params.
54
+// requires the duplex channel to be a msgio.ReadWriter (for framed messaging)
55
+func (s *secureSession) handshake(ctx context.Context, insecure io.ReadWriter) error {
56
+
57
+ s.insecure = insecure
58
+ s.insecureM = msgio.NewReadWriter(insecure)
59
+
60
+ // =============================================================================
61
+ // step 1. Propose -- propose cipher suite + send pubkeys + nonce
62
+
63
+ // Generate and send Hello packet.
64
+ // Hello = (rand, PublicKey, Supported)
65
+ nonceOut := make([]byte, nonceSize)
66
+ _, err := rand.Read(nonceOut)
67
+ if err != nil {
68
+ return err
69
+ }
70
+
71
+ log.Debugf("handshake: %s <--start--> %s", s.localPeer, s.remotePeer)
72
+ s.local.permanentPubKey = s.localPeer.PubKey()
73
+ myPubKeyBytes, err := s.local.permanentPubKey.Bytes()
74
+ if err != nil {
75
+ return err
76
+ }
77
+
78
+ proposeOut := new(pb.Propose)
79
+ proposeOut.Rand = nonceOut
80
+ proposeOut.Pubkey = myPubKeyBytes
81
+ proposeOut.Exchanges = &SupportedExchanges
82
+ proposeOut.Ciphers = &SupportedCiphers
83
+ proposeOut.Hashes = &SupportedHashes
84
+
85
+ // Send Propose packet (respects ctx)
86
+ proposeOutBytes, err := writeMsgCtx(ctx, s.insecureM, proposeOut)
87
+ if err != nil {
88
+ return err
89
+ }
90
+
91
+ // Receive + Parse their Propose packet and generate an Exchange packet.
92
+ proposeIn := new(pb.Propose)
93
+ proposeInBytes, err := readMsgCtx(ctx, s.insecureM, proposeIn)
94
+ if err != nil {
95
+ return err
96
+ }
97
+
98
+ // =============================================================================
99
+ // step 1.1 Identify -- get identity from their key
100
+
101
+ // get remote identity
102
+ s.remote.permanentPubKey, err = ci.UnmarshalPublicKey(proposeIn.GetPubkey())
103
+ if err != nil {
104
+ return err
105
+ }
106
+
107
+ // get or construct peer
108
+ s.remotePeer, err = getOrConstructPeer(s.peers, s.remote.permanentPubKey)
109
+ if err != nil {
110
+ return err
111
+ }
112
+ // log.Debugf("%s Remote Peer Identified as %s", s.localPeer, s.remotePeer)
113
+
114
+ // =============================================================================
115
+ // step 1.2 Selection -- select/agree on best encryption parameters
116
+
117
+ // to determine order, use cmp(H(lr||rpk), H(rr||lpk)).
118
+ oh1 := u.Hash(append(proposeIn.GetPubkey(), nonceOut...))
119
+ oh2 := u.Hash(append(myPubKeyBytes, proposeIn.GetRand()...))
120
+ order := bytes.Compare(oh1, oh2)
121
+ s.local.curveT, err = selectBest(order, SupportedExchanges, proposeIn.GetExchanges())
122
+ if err != nil {
123
+ return err
124
+ }
125
+
126
+ s.local.cipherT, err = selectBest(order, SupportedCiphers, proposeIn.GetCiphers())
127
+ if err != nil {
128
+ return err
129
+ }
130
+
131
+ s.local.hashT, err = selectBest(order, SupportedHashes, proposeIn.GetHashes())
132
+ if err != nil {
133
+ return err
134
+ }
135
+
136
+ // we use the same params for both directions (must choose same curve)
137
+ // WARNING: if they dont SelectBest the same way, this won't work...
138
+ s.remote.curveT = s.local.curveT
139
+ s.remote.cipherT = s.local.cipherT
140
+ s.remote.hashT = s.local.hashT
141
+
142
+ // =============================================================================
143
+ // step 2. Exchange -- exchange (signed) ephemeral keys. verify signatures.
144
+
145
+ // Generate EphemeralPubKey
146
+ var genSharedKey ci.GenSharedKey
147
+ s.local.ephemeralPubKey, genSharedKey, err = ci.GenerateEKeyPair(s.local.curveT)
148
+
149
+ // Gather corpus to sign.
150
+ var selectionOut bytes.Buffer
151
+ selectionOut.Write(proposeOutBytes)
152
+ selectionOut.Write(proposeInBytes)
153
+ selectionOut.Write(s.local.ephemeralPubKey)
154
+ selectionOutBytes := selectionOut.Bytes()
155
+
156
+ exchangeOut := new(pb.Exchange)
157
+ exchangeOut.Epubkey = s.local.ephemeralPubKey
158
+ exchangeOut.Signature, err = s.localPeer.PrivKey().Sign(selectionOutBytes)
159
+ if err != nil {
160
+ return err
161
+ }
162
+
163
+ // Send Propose packet (respects ctx)
164
+ if _, err := writeMsgCtx(ctx, s.insecureM, exchangeOut); err != nil {
165
+ return err
166
+ }
167
+
168
+ // Receive + Parse their Propose packet and generate an Exchange packet.
169
+ exchangeIn := new(pb.Exchange)
170
+ if _, err := readMsgCtx(ctx, s.insecureM, exchangeIn); err != nil {
171
+ return err
172
+ }
173
+
174
+ // =============================================================================
175
+ // step 2.1. Verify -- verify their exchange packet is good.
176
+
177
+ // get their ephemeral pub key
178
+ s.remote.ephemeralPubKey = exchangeIn.GetEpubkey()
179
+
180
+ var selectionIn bytes.Buffer
181
+ selectionIn.Write(proposeInBytes)
182
+ selectionIn.Write(proposeOutBytes)
183
+ selectionIn.Write(s.remote.ephemeralPubKey)
184
+ selectionInBytes := selectionIn.Bytes()
185
+
186
+ // u.POut("Remote Peer Identified as %s\n", s.remote)
187
+ sigOK, err := s.remotePeer.PubKey().Verify(selectionInBytes, exchangeIn.GetSignature())
188
+ if err != nil {
189
+ return err
190
+ }
191
+
192
+ if !sigOK {
193
+ return errors.New("Bad signature!")
194
+ }
195
+
196
+ // =============================================================================
197
+ // step 2.2. Keys -- generate keys for mac + encryption
198
+
199
+ // OK! seems like we're good to go.
200
+ s.sharedSecret, err = genSharedKey(exchangeIn.GetEpubkey())
201
+ if err != nil {
202
+ return err
203
+ }
204
+
205
+ // generate two sets of keys (stretching)
206
+ k1, k2 := ci.KeyStretcher(s.local.cipherT, s.local.hashT, s.sharedSecret)
207
+
208
+ // use random nonces to decide order.
209
+ switch order {
210
+ case 1:
211
+ case -1:
212
+ k1, k2 = k2, k1 // swap
213
+ default:
214
+ log.Error("WOAH: same keys (AND same nonce: 1/(2^128) chance!).")
215
+ // this shouldn't happen. must determine order another way.
216
+ // use the same keys but, make sure to copy underlying data!
217
+ copy(k2.IV, k1.IV)
218
+ copy(k2.MacKey, k1.MacKey)
219
+ copy(k2.CipherKey, k1.CipherKey)
220
+ }
221
+ s.local.keys = k1
222
+ s.remote.keys = k2
223
+
224
+ // =============================================================================
225
+ // step 2.3. MAC + Cipher -- prepare MAC + cipher
226
+
227
+ if err := s.local.makeMacAndCipher(); err != nil {
228
+ return err
229
+ }
230
+
231
+ if err := s.remote.makeMacAndCipher(); err != nil {
232
+ return err
233
+ }
234
+
235
+ // =============================================================================
236
+ // step 3. Finish -- send expected message (the nonces), verify encryption works
237
+
238
+ // setup ETM ReadWriter
239
+ w := NewETMWriter(s.insecure, s.local.cipher, s.local.mac)
240
+ r := NewETMReader(s.insecure, s.remote.cipher, s.remote.mac)
241
+ s.secure = msgio.Combine(w, r).(msgio.ReadWriteCloser)
242
+
243
+ // send their Nonce.
244
+ if _, err := s.secure.Write(proposeIn.GetRand()); err != nil {
245
+ return fmt.Errorf("Failed to write Finish nonce: %s", err)
246
+ }
247
+
248
+ // read our Nonce
249
+ nonceOut2 := make([]byte, len(nonceOut))
250
+ if _, err := io.ReadFull(s.secure, nonceOut2); err != nil {
251
+ return fmt.Errorf("Failed to read Finish nonce: %s", err)
252
+ }
253
+ if !bytes.Equal(nonceOut, nonceOut2) {
254
+ return fmt.Errorf("Failed to read our encrypted nonce, go: %s", nonceOut2)
255
+ }
256
+
257
+ // Whew! ok, that's all folks.
258
+ log.Debugf("handshake: %s <--finish--> %s", s.localPeer, s.remotePeer)
259
+ return nil
260
+}
261
+
262
+// getOrConstructPeer attempts to fetch a peer from a peerstore.
263
+// if succeeds, verify ID and PubKey match.
264
+// else, construct it.
265
+func getOrConstructPeer(peers peer.Peerstore, rpk ci.PubKey) (peer.Peer, error) {
266
+
267
+ rid, err := peer.IDFromPubKey(rpk)
268
+ if err != nil {
269
+ return nil, err
270
+ }
271
+
272
+ npeer, err := peers.FindOrCreate(rid)
273
+ if err != nil {
274
+ return nil, err // unexpected error happened.
275
+ }
276
+
277
+ // public key verification happens in Peer.VerifyAndSetPubKey
278
+ if err := npeer.VerifyAndSetPubKey(rpk); err != nil {
279
+ return nil, err // pubkey mismatch or other problem
280
+ }
281
+ return npeer, nil
282
+}
crypto/secio/rw.go
new
+197
@@ -0,0 +1,197 @@
1
+package secio
2
+
3
+import (
4
+ "crypto/cipher"
5
+ "errors"
6
+ "fmt"
7
+ "io"
8
+
9
+ "crypto/hmac"
10
+
11
+ context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context"
12
+ proto "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto"
13
+ msgio "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-msgio"
14
+)
15
+
16
+// ErrMACInvalid signals that a MAC verification failed
17
+var ErrMACInvalid = errors.New("MAC verification failed")
18
+
19
+type etmWriter struct {
20
+ // params
21
+ msg msgio.WriteCloser
22
+ str cipher.Stream
23
+ mac HMAC
24
+}
25
+
26
+// NewETMWriter Encrypt-Then-MAC
27
+func NewETMWriter(w io.Writer, s cipher.Stream, mac HMAC) msgio.WriteCloser {
28
+ return &etmWriter{msg: msgio.NewWriter(w), str: s, mac: mac}
29
+}
30
+
31
+// Write writes passed in buffer as a single message.
32
+func (w *etmWriter) Write(b []byte) (int, error) {
33
+ if err := w.WriteMsg(b); err != nil {
34
+ return 0, err
35
+ }
36
+ return len(b), nil
37
+}
38
+
39
+// WriteMsg writes the msg in the passed in buffer.
40
+func (w *etmWriter) WriteMsg(b []byte) error {
41
+
42
+ // encrypt.
43
+ w.str.XORKeyStream(b, b)
44
+
45
+ // then, mac.
46
+ if _, err := w.mac.Write(b); err != nil {
47
+ return err
48
+ }
49
+
50
+ // Sum appends.
51
+ b = w.mac.Sum(b)
52
+ w.mac.Reset()
53
+ // it's sad to append here. our buffers are -- hopefully -- coming from
54
+ // a shared buffer pool, so the append may not actually cause allocation
55
+ // one can only hope. i guess we'll see.
56
+
57
+ return w.msg.WriteMsg(b)
58
+}
59
+
60
+func (w *etmWriter) Close() error {
61
+ return w.msg.Close()
62
+}
63
+
64
+type etmReader struct {
65
+ msgio.Reader
66
+ io.Closer
67
+
68
+ // params
69
+ msg msgio.ReadCloser
70
+ str cipher.Stream
71
+ mac HMAC
72
+}
73
+
74
+// NewETMReader Encrypt-Then-MAC
75
+func NewETMReader(r io.Reader, s cipher.Stream, mac HMAC) msgio.ReadCloser {
76
+ return &etmReader{msg: msgio.NewReader(r), str: s, mac: mac}
77
+}
78
+
79
+func (r *etmReader) Read(buf []byte) (int, error) {
80
+ buf2 := buf
81
+ changed := false
82
+ if cap(buf2) < (len(buf) + r.mac.size) {
83
+ buf2 = make([]byte, len(buf)+r.mac.size)
84
+ changed = true
85
+ }
86
+
87
+ // WARNING: assumes msg.Read will only read _one_ message. this is what
88
+ // msgio is supposed to do. but msgio may change in the future. may this
89
+ // comment be your guiding light.
90
+ n, err := r.msg.Read(buf2)
91
+ if err != nil {
92
+ return n, err
93
+ }
94
+ buf2 = buf2[:n]
95
+
96
+ m, err := r.macCheckThenDecrypt(buf2)
97
+ if err != nil {
98
+ return 0, err
99
+ }
100
+ buf2 = buf2[:m]
101
+ if changed {
102
+ return copy(buf, buf2), nil
103
+ }
104
+ return m, nil
105
+}
106
+
107
+func (r *etmReader) ReadMsg() ([]byte, error) {
108
+ msg, err := r.msg.ReadMsg()
109
+ if err != nil {
110
+ return nil, err
111
+ }
112
+
113
+ n, err := r.macCheckThenDecrypt(msg)
114
+ if err != nil {
115
+ return nil, err
116
+ }
117
+ return msg[:n], nil
118
+}
119
+
120
+func (r *etmReader) macCheckThenDecrypt(m []byte) (int, error) {
121
+ l := len(m)
122
+ if l < r.mac.size {
123
+ return 0, fmt.Errorf("buffer (%d) shorter than MAC size (%d)", l, r.mac.size)
124
+ }
125
+
126
+ mark := l - r.mac.size
127
+ data := m[:mark]
128
+ macd := m[mark:]
129
+
130
+ r.mac.Write(data)
131
+ expected := r.mac.Sum(nil)
132
+ r.mac.Reset()
133
+
134
+ // check mac. if failed, return error.
135
+ if !hmac.Equal(macd, expected) {
136
+ log.Error("MAC Invalid:", expected, "!=", macd)
137
+ return 0, ErrMACInvalid
138
+ }
139
+
140
+ // ok seems good. decrypt.
141
+ r.str.XORKeyStream(data, data)
142
+ return mark, nil
143
+}
144
+
145
+func (w *etmReader) Close() error {
146
+ return w.msg.Close()
147
+}
148
+
149
+// ReleaseMsg signals a buffer can be reused.
150
+func (r *etmReader) ReleaseMsg(b []byte) {
151
+ r.msg.ReleaseMsg(b)
152
+}
153
+
154
+// writeMsgCtx is used by the
155
+func writeMsgCtx(ctx context.Context, w msgio.Writer, msg proto.Message) ([]byte, error) {
156
+ enc, err := proto.Marshal(msg)
157
+ if err != nil {
158
+ return nil, err
159
+ }
160
+
161
+ // write in a goroutine so we can exit when our context is cancelled.
162
+ done := make(chan error)
163
+ go func(m []byte) {
164
+ err := w.WriteMsg(m)
165
+ done <- err
166
+ }(enc)
167
+
168
+ select {
169
+ case <-ctx.Done():
170
+ return nil, ctx.Err()
171
+ case e := <-done:
172
+ return enc, e
173
+ }
174
+}
175
+
176
+func readMsgCtx(ctx context.Context, r msgio.Reader, p proto.Message) ([]byte, error) {
177
+ var msg []byte
178
+
179
+ // read in a goroutine so we can exit when our context is cancelled.
180
+ done := make(chan error)
181
+ go func() {
182
+ var err error
183
+ msg, err = r.ReadMsg()
184
+ done <- err
185
+ }()
186
+
187
+ select {
188
+ case <-ctx.Done():
189
+ return nil, ctx.Err()
190
+ case e := <-done:
191
+ if e != nil {
192
+ return nil, e
193
+ }
194
+ }
195
+
196
+ return msg, proto.Unmarshal(msg, p)
197
+}