rw.go 5.41 KB
Newer Older
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
1 2 3 4
package secio

import (
	"crypto/cipher"
Jeromy's avatar
Jeromy committed
5
	"crypto/hmac"
6
	"encoding/binary"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
7 8 9 10 11
	"errors"
	"fmt"
	"io"
	"sync"

tavit ohanian's avatar
tavit ohanian committed
12 13
	pool "gitlab.dms3.io/p2p/go-buffer-pool"
	msgio "gitlab.dms3.io/p2p/go-msgio"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
14 15 16 17 18 19
)

// ErrMACInvalid signals that a MAC verification failed
var ErrMACInvalid = errors.New("MAC verification failed")

type etmWriter struct {
Steven Allen's avatar
Steven Allen committed
20 21 22
	str cipher.Stream // the stream cipher to encrypt with
	mac HMAC          // the mac to authenticate data with
	w   io.Writer
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
23 24 25 26 27 28

	sync.Mutex
}

// NewETMWriter Encrypt-Then-MAC
func NewETMWriter(w io.Writer, s cipher.Stream, mac HMAC) msgio.WriteCloser {
Steven Allen's avatar
Steven Allen committed
29
	return &etmWriter{w: w, str: s, mac: mac}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
}

// Write writes passed in buffer as a single message.
func (w *etmWriter) Write(b []byte) (int, error) {
	if err := w.WriteMsg(b); err != nil {
		return 0, err
	}
	return len(b), nil
}

// WriteMsg writes the msg in the passed in buffer.
func (w *etmWriter) WriteMsg(b []byte) error {
	w.Lock()
	defer w.Unlock()

	// encrypt.
46 47 48
	buf := pool.Get(4 + len(b) + w.mac.Size())
	defer pool.Put(buf)
	data := buf[4 : 4+len(b)]
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
49 50 51 52 53 54 55 56 57 58 59 60 61
	w.str.XORKeyStream(data, b)

	// log.Debugf("ENC plaintext (%d): %s %v", len(b), b, b)
	// log.Debugf("ENC ciphertext (%d): %s %v", len(data), data, data)

	// then, mac.
	if _, err := w.mac.Write(data); err != nil {
		return err
	}

	// Sum appends.
	data = w.mac.Sum(data)
	w.mac.Reset()
62
	binary.BigEndian.PutUint32(buf[:4], uint32(len(data)))
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
63

64
	_, err := w.w.Write(buf)
65
	return err
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
66 67 68
}

func (w *etmWriter) Close() error {
69 70 71 72
	if c, ok := w.w.(io.Closer); ok {
		return c.Close()
	}
	return nil
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
73 74 75 76 77 78
}

type etmReader struct {
	msgio.Reader
	io.Closer

79
	// internal buffer returned from the msgio
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
80 81
	buf []byte

82 83 84 85
	// low and high watermark for the buffered data
	lowat int
	hiwat int

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
86 87 88 89 90
	// params
	msg msgio.ReadCloser // msgio for knowing where boundaries lie
	str cipher.Stream    // the stream cipher to encrypt with
	mac HMAC             // the mac to authenticate data with

91 92 93 94
	// internal buffer used for checking MACs, this saves us quite a few
	// allocations and should be quite small.
	macBuf []byte

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
95 96 97 98 99 100 101 102 103 104 105 106
	sync.Mutex
}

// NewETMReader Encrypt-Then-MAC
func NewETMReader(r io.Reader, s cipher.Stream, mac HMAC) msgio.ReadCloser {
	return &etmReader{msg: msgio.NewReader(r), str: s, mac: mac}
}

func (r *etmReader) NextMsgLen() (int, error) {
	return r.msg.NextMsgLen()
}

107 108 109
func (r *etmReader) drain(buf []byte) int {
	// Return zero if there is no data remaining in the internal buffer.
	if r.lowat == r.hiwat {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
110 111 112
		return 0
	}

113 114 115 116 117 118 119 120 121 122 123 124 125 126
	// Copy data to the output buffer.
	n := copy(buf, r.buf[r.lowat:r.hiwat])

	// Update the low watermark.
	r.lowat += n

	// Release the buffer and reset the watermarks if it has been fully read.
	if r.lowat == r.hiwat {
		r.msg.ReleaseMsg(r.buf)
		r.buf = nil
		r.lowat = 0
		r.hiwat = 0
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
127 128 129
	return n
}

130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
func (r *etmReader) fill() error {
	// Read a message from the underlying msgio.
	msg, err := r.msg.ReadMsg()
	if err != nil {
		return err
	}

	// Check the MAC.
	n, err := r.macCheckThenDecrypt(msg)
	if err != nil {
		r.msg.ReleaseMsg(msg)
		return err
	}

	// Retain the buffer so it can be drained from and later released.
	r.buf = msg
	r.lowat = 0
	r.hiwat = n

	return nil
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
152 153 154 155
func (r *etmReader) Read(buf []byte) (int, error) {
	r.Lock()
	defer r.Unlock()

156 157
	// Return buffered data without reading more, if possible.
	copied := r.drain(buf)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
158 159 160 161
	if copied > 0 {
		return copied, nil
	}

162
	// Check the length of the next message.
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
163 164 165 166 167
	fullLen, err := r.msg.NextMsgLen()
	if err != nil {
		return 0, err
	}

168 169
	// If the destination buffer is too short, fill an internal buffer and then
	// drain as much of that into the output buffer as will fit.
vyzo's avatar
vyzo committed
170
	if len(buf) < fullLen {
171 172 173 174 175 176 177
		err := r.fill()
		if err != nil {
			return 0, err
		}

		copied := r.drain(buf)
		return copied, nil
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
178 179
	}

180 181
	// Otherwise, read directly into the destination buffer.
	n, err := io.ReadFull(r.msg, buf[:fullLen])
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
182
	if err != nil {
183
		return 0, err
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
184 185
	}

186
	m, err := r.macCheckThenDecrypt(buf[:n])
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
187 188 189 190
	if err != nil {
		return 0, err
	}

191
	return m, nil
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
192 193 194 195 196 197 198 199 200 201 202 203 204
}

func (r *etmReader) ReadMsg() ([]byte, error) {
	r.Lock()
	defer r.Unlock()

	msg, err := r.msg.ReadMsg()
	if err != nil {
		return nil, err
	}

	n, err := r.macCheckThenDecrypt(msg)
	if err != nil {
Steven Allen's avatar
Steven Allen committed
205
		r.msg.ReleaseMsg(msg)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221
		return nil, err
	}
	return msg[:n], nil
}

func (r *etmReader) macCheckThenDecrypt(m []byte) (int, error) {
	l := len(m)
	if l < r.mac.size {
		return 0, fmt.Errorf("buffer (%d) shorter than MAC size (%d)", l, r.mac.size)
	}

	mark := l - r.mac.size
	data := m[:mark]
	macd := m[mark:]

	r.mac.Write(data)
222
	r.macBuf = r.mac.Sum(r.macBuf[:0])
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
223 224 225
	r.mac.Reset()

	// check mac. if failed, return error.
226 227
	if !hmac.Equal(macd, r.macBuf) {
		log.Debug("MAC Invalid:", r.macBuf, "!=", macd)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
228 229 230 231 232 233 234 235 236 237 238
		return 0, ErrMACInvalid
	}

	// ok seems good. decrypt. (can decrypt in place, yay!)
	// log.Debugf("DEC ciphertext (%d): %s %v", len(data), data, data)
	r.str.XORKeyStream(data, data)
	// log.Debugf("DEC plaintext (%d): %s %v", len(data), data, data)

	return mark, nil
}

239 240
func (r *etmReader) Close() error {
	return r.msg.Close()
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
241 242 243 244 245 246 247
}

// ReleaseMsg signals a buffer can be reused.
func (r *etmReader) ReleaseMsg(b []byte) {
	r.msg.ReleaseMsg(b)
}

Steven Allen's avatar
Steven Allen committed
248 249 250
// read and write a message at the same time.
func readWriteMsg(c msgio.ReadWriter, out []byte) ([]byte, error) {
	wresult := make(chan error)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
251
	go func() {
Steven Allen's avatar
Steven Allen committed
252
		wresult <- c.WriteMsg(out)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
253 254
	}()

Steven Allen's avatar
Steven Allen committed
255 256 257 258
	msg, err1 := c.ReadMsg()

	// Always wait for the read to finish.
	err2 := <-wresult
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
259

Steven Allen's avatar
Steven Allen committed
260 261 262 263 264 265 266 267
	if err1 != nil {
		return nil, err1
	}
	if err2 != nil {
		c.ReleaseMsg(msg)
		return nil, err2
	}
	return msg, nil
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
268
}