118 lines
2.1 KiB
Go
118 lines
2.1 KiB
Go
package crypto
|
|
|
|
import (
|
|
"crypto/cipher"
|
|
"errors"
|
|
"io"
|
|
|
|
"golang.org/x/crypto/chacha20poly1305"
|
|
)
|
|
|
|
type cryptoReader interface {
|
|
io.Reader
|
|
}
|
|
|
|
type nonce struct {
|
|
N [NonceSize]byte
|
|
C int
|
|
}
|
|
|
|
type reader struct {
|
|
a cipher.AEAD
|
|
src cryptoReader
|
|
|
|
unread []byte // decrypted but unread data, backed by buf
|
|
buf [FullSize]byte
|
|
off int64
|
|
|
|
err error
|
|
nonce [NonceSize]byte
|
|
cn int64
|
|
outBuf []byte
|
|
}
|
|
|
|
func newReader(key []byte, src cryptoReader) (*reader, error) {
|
|
aead, err := chacha20poly1305.New(key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &reader{
|
|
a: aead,
|
|
src: src,
|
|
cn: -1,
|
|
outBuf: make([]byte, 0, FullSize),
|
|
}, nil
|
|
}
|
|
|
|
func (r *reader) Read(p []byte) (int, error) {
|
|
if len(r.unread) > 0 {
|
|
n := copy(p, r.unread)
|
|
r.unread = r.unread[n:]
|
|
return n, nil
|
|
}
|
|
if r.err != nil {
|
|
return 0, r.err
|
|
}
|
|
if len(p) == 0 {
|
|
return 0, nil
|
|
}
|
|
|
|
last, err := r.readChunk()
|
|
if err != nil {
|
|
r.err = err
|
|
return 0, err
|
|
}
|
|
if r.off > 0 && len(r.unread) >= int(r.off) {
|
|
r.unread = r.unread[r.off:]
|
|
r.off = 0
|
|
}
|
|
|
|
n := copy(p, r.unread)
|
|
r.unread = r.unread[n:]
|
|
|
|
if last {
|
|
r.err = io.EOF
|
|
}
|
|
|
|
return n, nil
|
|
}
|
|
|
|
func (r *reader) readChunk() (last bool, err error) {
|
|
if len(r.unread) != 0 {
|
|
panic("stream: internal error: readChunk called with dirty buffer")
|
|
}
|
|
|
|
in := r.buf[:]
|
|
n, err := io.ReadFull(r.src, in)
|
|
switch {
|
|
case err == io.EOF:
|
|
// A message can't end without a marked chunk. This message is truncated.
|
|
return false, io.ErrUnexpectedEOF
|
|
case err == io.ErrUnexpectedEOF:
|
|
// The last chunk can be short.
|
|
in = in[:n]
|
|
last = true
|
|
setLastChunkFlag(&r.nonce)
|
|
case err != nil:
|
|
return false, err
|
|
}
|
|
|
|
out, err := r.a.Open(r.outBuf, r.nonce[:], in, nil)
|
|
if err != nil && !last {
|
|
// Check if this was a full-length final chunk.
|
|
last = true
|
|
setLastChunkFlag(&r.nonce)
|
|
out, err = r.a.Open(r.outBuf, r.nonce[:], in, nil)
|
|
}
|
|
if err != nil {
|
|
return false, errors.New("failed to decrypt and authenticate payload chunk")
|
|
}
|
|
|
|
incNonce(&r.nonce)
|
|
size := FullSize
|
|
if len(out) < FullSize {
|
|
size = len(out)
|
|
}
|
|
r.unread = out[:size]
|
|
return last, nil
|
|
}
|