@cryptotaxi247 / kubo / commits / 5aab08fe4

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