diff --git a/crypto/crypto.go b/crypto/crypto.go index 482a5a748..6bca758ac 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -1,16 +1,12 @@ package crypto import ( - "bytes" "crypto/aes" "crypto/cipher" "crypto/rand" "encoding/json" "errors" "fmt" - "io" - "io/ioutil" - "sync" "github.com/restic/restic/chunker" "golang.org/x/crypto/poly1305" @@ -300,194 +296,3 @@ func KDF(N, R, P int, salt []byte, password string) (*Key, error) { return derKeys, nil } - -type encryptWriter struct { - iv iv - wroteIV bool - data *bytes.Buffer - key *Key - s cipher.Stream - w io.Writer - origWr io.Writer - err error // remember error writing iv -} - -func (e *encryptWriter) Close() error { - // write mac - mac := poly1305_sign(e.data.Bytes()[ivSize:], e.data.Bytes()[:ivSize], &e.key.Sign) - _, err := e.origWr.Write(mac) - if err != nil { - return err - } - - // return buffer - bufPool.Put(e.data.Bytes()) - - return nil -} - -const encryptWriterChunkSize = 512 * 1024 // 512 KiB -var encryptWriterBufPool = sync.Pool{ - New: func() interface{} { - return make([]byte, encryptWriterChunkSize) - }, -} - -func (e *encryptWriter) Write(p []byte) (int, error) { - // write iv first - if !e.wroteIV { - _, e.err = e.origWr.Write(e.iv[:]) - e.wroteIV = true - } - - if e.err != nil { - return 0, e.err - } - - buf := encryptWriterBufPool.Get().([]byte) - defer encryptWriterBufPool.Put(buf) - - written := 0 - for len(p) > 0 { - max := len(p) - if max > encryptWriterChunkSize { - max = encryptWriterChunkSize - } - - e.s.XORKeyStream(buf, p[:max]) - n, err := e.w.Write(buf[:max]) - if n != max { - if err == nil { // should never happen - err = io.ErrShortWrite - } - } - - written += n - p = p[n:] - - if err != nil { - e.err = err - return written, err - } - } - - return written, nil -} - -// EncryptTo buffers data written to the returned io.WriteCloser. When Close() -// is called, the data is encrypted an written to the underlying writer. -func EncryptTo(ks *Key, wr io.Writer) io.WriteCloser { - ew := &encryptWriter{ - iv: newIV(), - data: bytes.NewBuffer(getBuffer()[:0]), - key: ks, - origWr: wr, - } - - // buffer iv for mac - _, err := ew.data.Write(ew.iv[:]) - if err != nil { - panic(err) - } - - c, err := aes.NewCipher(ks.Encrypt[:]) - if err != nil { - panic(fmt.Sprintf("unable to create cipher: %v", err)) - } - - ew.s = cipher.NewCTR(c, ew.iv[:]) - ew.w = io.MultiWriter(ew.data, wr) - - return ew -} - -type decryptReader struct { - buf []byte - pos int -} - -func (d *decryptReader) Read(dst []byte) (int, error) { - if d.buf == nil { - return 0, io.EOF - } - - if len(dst) == 0 { - return 0, nil - } - - remaining := len(d.buf) - d.pos - if len(dst) >= remaining { - n := copy(dst, d.buf[d.pos:]) - d.Close() - return n, io.EOF - } - - n := copy(dst, d.buf[d.pos:d.pos+len(dst)]) - d.pos += n - - return n, nil -} - -func (d *decryptReader) ReadByte() (c byte, err error) { - if d.buf == nil { - return 0, io.EOF - } - - remaining := len(d.buf) - d.pos - if remaining == 1 { - c = d.buf[d.pos] - d.Close() - return c, io.EOF - } - - c = d.buf[d.pos] - d.pos++ - - return -} - -func (d *decryptReader) Close() error { - if d.buf == nil { - return nil - } - - freeBuffer(d.buf) - d.buf = nil - return nil -} - -// DecryptFrom verifies and decrypts the ciphertext read from rd with ks and -// makes it available on the returned Reader. Ciphertext must be in the form IV -// || Ciphertext || MAC. In order to correctly verify the ciphertext, rd is -// drained, locally buffered and made available on the returned Reader -// afterwards. If a MAC verification failure is observed, it is returned -// immediately. -func DecryptFrom(ks *Key, rd io.Reader) (io.ReadCloser, error) { - ciphertext := getBuffer() - - ciphertext = ciphertext[0:cap(ciphertext)] - n, err := io.ReadFull(rd, ciphertext) - if err != io.ErrUnexpectedEOF { - // read remaining data - buf, e := ioutil.ReadAll(rd) - ciphertext = append(ciphertext, buf...) - n += len(buf) - err = e - } else { - err = nil - } - - if err != nil { - return nil, err - } - - ciphertext = ciphertext[:n] - - // decrypt - ciphertext, err = Decrypt(ks, ciphertext, ciphertext) - if err != nil { - return nil, err - } - - return &decryptReader{buf: ciphertext}, nil -} diff --git a/crypto/reader.go b/crypto/reader.go new file mode 100644 index 000000000..87856ef0a --- /dev/null +++ b/crypto/reader.go @@ -0,0 +1,97 @@ +package crypto + +import ( + "io" + "io/ioutil" +) + +type decryptReader struct { + buf []byte + pos int +} + +func (d *decryptReader) Read(dst []byte) (int, error) { + if d.buf == nil { + return 0, io.EOF + } + + if len(dst) == 0 { + return 0, nil + } + + remaining := len(d.buf) - d.pos + if len(dst) >= remaining { + n := copy(dst, d.buf[d.pos:]) + d.Close() + return n, io.EOF + } + + n := copy(dst, d.buf[d.pos:d.pos+len(dst)]) + d.pos += n + + return n, nil +} + +func (d *decryptReader) ReadByte() (c byte, err error) { + if d.buf == nil { + return 0, io.EOF + } + + remaining := len(d.buf) - d.pos + if remaining == 1 { + c = d.buf[d.pos] + d.Close() + return c, io.EOF + } + + c = d.buf[d.pos] + d.pos++ + + return +} + +func (d *decryptReader) Close() error { + if d.buf == nil { + return nil + } + + freeBuffer(d.buf) + d.buf = nil + return nil +} + +// DecryptFrom verifies and decrypts the ciphertext read from rd with ks and +// makes it available on the returned Reader. Ciphertext must be in the form IV +// || Ciphertext || MAC. In order to correctly verify the ciphertext, rd is +// drained, locally buffered and made available on the returned Reader +// afterwards. If a MAC verification failure is observed, it is returned +// immediately. +func DecryptFrom(ks *Key, rd io.Reader) (io.ReadCloser, error) { + ciphertext := getBuffer() + + ciphertext = ciphertext[0:cap(ciphertext)] + n, err := io.ReadFull(rd, ciphertext) + if err != io.ErrUnexpectedEOF { + // read remaining data + buf, e := ioutil.ReadAll(rd) + ciphertext = append(ciphertext, buf...) + n += len(buf) + err = e + } else { + err = nil + } + + if err != nil { + return nil, err + } + + ciphertext = ciphertext[:n] + + // decrypt + ciphertext, err = Decrypt(ks, ciphertext, ciphertext) + if err != nil { + return nil, err + } + + return &decryptReader{buf: ciphertext}, nil +} diff --git a/crypto/writer.go b/crypto/writer.go new file mode 100644 index 000000000..856fb5fff --- /dev/null +++ b/crypto/writer.go @@ -0,0 +1,110 @@ +package crypto + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "fmt" + "io" + "sync" +) + +type encryptWriter struct { + iv iv + wroteIV bool + data *bytes.Buffer + key *Key + s cipher.Stream + w io.Writer + origWr io.Writer + err error // remember error writing iv +} + +func (e *encryptWriter) Close() error { + // write mac + mac := poly1305_sign(e.data.Bytes()[ivSize:], e.data.Bytes()[:ivSize], &e.key.Sign) + _, err := e.origWr.Write(mac) + if err != nil { + return err + } + + // return buffer + bufPool.Put(e.data.Bytes()) + + return nil +} + +const encryptWriterChunkSize = 512 * 1024 // 512 KiB +var encryptWriterBufPool = sync.Pool{ + New: func() interface{} { + return make([]byte, encryptWriterChunkSize) + }, +} + +func (e *encryptWriter) Write(p []byte) (int, error) { + // write iv first + if !e.wroteIV { + _, e.err = e.origWr.Write(e.iv[:]) + e.wroteIV = true + } + + if e.err != nil { + return 0, e.err + } + + buf := encryptWriterBufPool.Get().([]byte) + defer encryptWriterBufPool.Put(buf) + + written := 0 + for len(p) > 0 { + max := len(p) + if max > encryptWriterChunkSize { + max = encryptWriterChunkSize + } + + e.s.XORKeyStream(buf, p[:max]) + n, err := e.w.Write(buf[:max]) + if n != max { + if err == nil { // should never happen + err = io.ErrShortWrite + } + } + + written += n + p = p[n:] + + if err != nil { + e.err = err + return written, err + } + } + + return written, nil +} + +// EncryptTo buffers data written to the returned io.WriteCloser. When Close() +// is called, the data is encrypted an written to the underlying writer. +func EncryptTo(ks *Key, wr io.Writer) io.WriteCloser { + ew := &encryptWriter{ + iv: newIV(), + data: bytes.NewBuffer(getBuffer()[:0]), + key: ks, + origWr: wr, + } + + // buffer iv for mac + _, err := ew.data.Write(ew.iv[:]) + if err != nil { + panic(err) + } + + c, err := aes.NewCipher(ks.Encrypt[:]) + if err != nil { + panic(fmt.Sprintf("unable to create cipher: %v", err)) + } + + ew.s = cipher.NewCTR(c, ew.iv[:]) + ew.w = io.MultiWriter(ew.data, wr) + + return ew +}