rabaead: Go Coverage Report

not tracked not covered covered

    package rabaead

    import (
            "bytes"
            "crypto/cipher"
            "encoding/binary"
            "io"

            "snix.ir/rabbitio"
    )

    const cmrk = 0x08 // chunk size indicator,
    // without this reader cannot calculate actual size of plaintext

    // additional data func, return value is used as AD in Seal and Open
    // nil AdFunc is harmless and equal to func()[]byte{return nil}
    type AdditionalFunc func() []byte

    type chunkReader struct {
            aead  cipher.AEAD
            csize int
            rader io.Reader
            buff  []byte
            nonce []byte
            adexe AdditionalFunc
    }

    type chunkWriter struct {
            aead   cipher.AEAD
            csize  int
            writer io.Writer
            buff   []byte
            nonce  []byte
            adexe  AdditionalFunc
    }

    // NewChunkReader returns a chunkReader data type, this reader reads and open() aead
    // ciphertext, each chunk has its own tag and cmrk value.
    // this reader has a chunk size in-memory buffer, large chunk size can make application to runs
    // out of memory, thus is most suitable for sliced data, like network data transmit and so..
    func NewChunkReader(r io.Reader, chnk int, a cipher.AEAD, nonce []byte, f AdditionalFunc) (*chunkReader, error) {

            if len(nonce) != rabbitio.IVXLen && len(nonce) != 0 {
                    return nil, rabbitio.ErrInvalidIVX
            }

            s := &chunkReader{
                    aead:  a,
                    buff:  []byte{},
                    nonce: make([]byte, len(nonce)),
                    csize: chnk,
                    rader: r,
                    adexe: f,
            }

            if s.adexe == nil {
                    s.adexe = func() []byte { return nil }
            }
            copy(s.nonce, nonce)
            return s, nil
    }

    // NewChunkWriter returns a chunkWriter data type, this writer sale() and write aead
    // plaintext, each chunk has its own tag and cmrk value.
    // this writer has a chunk size in-memory buffer, large chunk size can make application to
    // runs out of memory, thus is most suitable for sliced data, like network data transmit and so..
    func NewChunkWriter(w io.Writer, chnk int, a cipher.AEAD, nonce []byte, f AdditionalFunc) (*chunkWriter, error) {

            if len(nonce) != rabbitio.IVXLen && len(nonce) != 0 {
                    return nil, rabbitio.ErrInvalidIVX
            }
            s := &chunkWriter{
                    aead:   a,
                    buff:   []byte{},
                    nonce:  make([]byte, len(nonce)),
                    csize:  chnk,
                    writer: w,
                    adexe:  f,
            }

            if s.adexe == nil {
                    s.adexe = func() []byte { return nil }
            }

            copy(s.nonce, nonce)
            return s, nil
    }

    // Close method, if there is any
    func (w *chunkWriter) Close() error {
            if c, ok := w.writer.(io.Closer); ok {
                    return c.Close()
            }
            return nil
    }

    // Write writes plaintext chunk into the sale() and underlying writer
    // write would not report overhead data (chunk size marker and poly1305 tag) in
    // written return value. for each chunk there is 8+16 byte overhead data.
    // AdFunc will be triggered for each chunk of data
    func (w *chunkWriter) Write(b []byte) (n int, err error) {
            w.buff = b
            for len(w.buff) > 0 {
                    s, err := w.write()
                    if err != nil {
                            return n, err
                    }
                    n += s
            }
            return
    }

    func (w *chunkWriter) write() (int, error) {
            size := cmrk + w.csize + w.aead.Overhead()
            chnk := make([]byte, size)
            var n int
            var err error

            if len(w.buff) > 0 {
                    s := copy(chnk[cmrk:len(chnk)-w.aead.Overhead()], w.buff)
                    w.buff = w.buff[s:]
                    copy(chnk[0:cmrk], uint64Little(uint64(s)))

                    w.aead.Seal(chnk[:0], w.nonce, chnk[:cmrk+w.csize], w.adexe())
                    _, err = w.writer.Write(chnk)
                    if err != nil {
                            return n, err
                    }
                    n += s
            }

            return n, err
    }

    // Read reads and open() ciphertext chunk from underlying reader
    // read would not report overhead data (chunk size marker and poly1305 tag) in its
    // return value. if the read data from underlying reader is corrupted, ErrAuthMsg
    // error will be returned. for each chunk there is 8+16 byte overhead data.
    // AdFunc will be triggered for each chunk of data

    func (r *chunkReader) Read(b []byte) (int, error) {
            if len(b) <= r.csize {
                    return r.readTo(b)
            }
            n := 0
            for {
                    if n+r.csize > len(b) {
                            sr, err := r.readTo(b[n:])
                            n += sr
                            if err != nil {
                                    return n, err
                            }
                            break
                    }
                    sr, err := r.readTo(b[n : n+r.csize])
                    n += sr
                    if err != nil {
                            return n, err
                    }
            }

            return n, nil
    }

    func (r *chunkReader) readTo(b []byte) (int, error) {
            var n int
            if len(r.buff) > 0 {
                    n = copy(b, r.buff)
                    r.buff = r.buff[n:]
                    return n, nil
            }

            sr, err := r.read()
            n = copy(b, r.buff[:sr])
            r.buff = r.buff[n:]
            return n, err
    }

    func (r *chunkReader) read() (int, error) {

            var n int
            size := cmrk + r.csize + r.aead.Overhead()
            chnk := make([]byte, size)
            chLE := uint64Little(uint64(r.csize))

            si, err := io.ReadFull(r.rader, chnk)
            if err != nil {
                    return n, err
            }

            if si > 0 {
                    _, err = r.aead.Open(chnk[:0], r.nonce, chnk, r.adexe())
                    if err != nil {
                            return n, err
                    }

                    if bytes.Equal(chnk[0:cmrk], chLE) {
                            n += r.csize
                            r.buff = append(r.buff, chnk[cmrk:cmrk+r.csize]...)
                    } else {
                            f := binary.LittleEndian.Uint64(chnk[0:cmrk])
                            n += int(f)
                            r.buff = append(r.buff, chnk[cmrk:cmrk+f]...)
                    }
            }

            return n, err
    }

    func uint64Little(n uint64) []byte {
            b := make([]byte, cmrk)
            binary.LittleEndian.PutUint64(b, n)
            return b
    }

    package rabaead

    import (
            "crypto/cipher"
            "errors"

            "snix.ir/poly1305"
            "snix.ir/rabbitio"
    )

    const polykeylen = 0x20 // poly1305 key len: 32byte

    var ErrAuthMsg = errors.New("rabaead: message authentication failed")
    var erroverlap = errors.New("rabaead: invalid buffer memory overlap")

    type rabbitPoly1305 struct {
            key       []byte // rabbit cipher key
            noncesize int    // rabbit iv size
    }

    // NewAEAD returns a rabbit aead data-type
    // key must be 16 byte len
    func NewAEAD(key []byte) (cipher.AEAD, error) { return newRabbitAead(key) }

    func newRabbitAead(key []byte) (cipher.AEAD, error) {
            if len(key) != rabbitio.KeyLen {
                    return nil, rabbitio.ErrInvalidKey
            }

            rabbitAead := &rabbitPoly1305{
                    noncesize: rabbitio.IVXLen,
                    key:       make([]byte, rabbitio.KeyLen),
            }
            copy(rabbitAead.key[:], key)
            return rabbitAead, nil

    }

    // Overhead returns poly1305 tag size: 16byte
    func (c *rabbitPoly1305) Overhead() int { return poly1305.TagSize }

    // NonceSize returns rabbit iv len: 8byte
    func (c *rabbitPoly1305) NonceSize() int { return c.noncesize }

    func (c *rabbitPoly1305) sealRabbit(dst, nonce, plaintext, ad []byte) []byte {
            ret, out := headtail(dst, len(plaintext)+poly1305.TagSize)
            ciphertext, tag := out[:len(plaintext)], out[len(plaintext):]
            if inexactOverlap(out, plaintext) {
                    panic(erroverlap) //should never happen
            }

            var polyKey [polykeylen]byte
            s, err := rabbitio.NewCipher(c.key, nonce)
            if err != nil {
                    panic(err)
            }
            s.XORKeyStream(polyKey[:], polyKey[:])
            p := poly1305.New(&polyKey)
            writePadding(p, ad)

            s, err = rabbitio.NewCipher(c.key, nonce)
            if err != nil {
                    panic(err)
            }
            s.XORKeyStream(ciphertext, plaintext)

            writePadding(p, ciphertext)

            writeUint64(p, len(ad))
            writeUint64(p, len(plaintext))
            p.Sum(tag[:0x00])

            return ret
    }

    func (c *rabbitPoly1305) openRabbit(dst, nonce, ciphertext, ad []byte) ([]byte, error) {
            tag := ciphertext[len(ciphertext)-poly1305.TagSize:]
            ciphertext = ciphertext[:len(ciphertext)-poly1305.TagSize]

            var polyKey [polykeylen]byte
            s, err := rabbitio.NewCipher(c.key, nonce)
            if err != nil {
                    panic(err)
            }
            s.XORKeyStream(polyKey[:], polyKey[:])

            p := poly1305.New(&polyKey)
            writePadding(p, ad)
            writePadding(p, ciphertext)

            writeUint64(p, len(ad))
            writeUint64(p, len(ciphertext))

            ret, out := headtail(dst, len(ciphertext))
            if inexactOverlap(out, ciphertext) {
                    panic(erroverlap) //should never happen
            }

            // check data integrity
            if !p.Verify(tag) {
                    return nil, ErrAuthMsg
            }

            s, err = rabbitio.NewCipher(c.key, nonce)
            if err != nil {
                    panic(err)
            }
            s.XORKeyStream(out, ciphertext)
            return ret, nil
    }

    // Open opens a rabbit aead ciphertext.
    // panic occurs if nonce len is not equal to IVXLen (8byte) or zero
    // if data is not verified, ErrAuthMsg will be returned
    func (c *rabbitPoly1305) Open(dst, nonce, ciphertext, ad []byte) ([]byte, error) {

            if len(ciphertext) < poly1305.TagSize {
                    return nil, ErrAuthMsg
            }

            return c.openRabbit(dst, nonce, ciphertext, ad)
    }

    // Seal seals a plaintext into the rabbit aead ciphertext.
    // panic occurs if nonce len is not equal to IVXLen (8byte) or zero
    func (c *rabbitPoly1305) Seal(dst, nonce, plaintext, ad []byte) []byte {
            return c.sealRabbit(dst, nonce, plaintext, ad)
    }

    //go:build !appengine

    package rabaead

    import "unsafe"

    func anyOverlap(x, y []byte) bool {
            return len(x) > 0 && len(y) > 0 &&
                    uintptr(unsafe.Pointer(&x[0])) <= uintptr(unsafe.Pointer(&y[len(y)-1])) &&
                    uintptr(unsafe.Pointer(&y[0])) <= uintptr(unsafe.Pointer(&x[len(x)-1]))
    }

    func inexactOverlap(x, y []byte) bool {
            if len(x) == 0 || len(y) == 0 || &x[0] == &y[0] {
                    return false
            }
            return anyOverlap(x, y)
    }

    package rabaead

    import (
            "encoding/binary"

            "snix.ir/poly1305"
    )

    func headtail(in []byte, n int) (head, tail []byte) {
            total := len(in) + n

            if cap(in) >= total {
                    head = in[:total]
            } else {
                    head = make([]byte, total)
                    copy(head, in)
            }
            tail = head[len(in):]
            return
    }

    func writePadding(p *poly1305.MAC, b []byte) {
            p.Write(b)
            if rem := len(b) % 16; rem != 0 {
                    var buf [16]byte
                    padLen := 16 - rem
                    p.Write(buf[:padLen])
            }
    }

    func writeUint64(p *poly1305.MAC, n int) {
            var buf [8]byte
            binary.LittleEndian.PutUint64(buf[:], uint64(n))
            p.Write(buf[:])
    }

    package rabaead

    import (
            "crypto/cipher"
            "errors"
            "io"

            "snix.ir/poly1305"
            "snix.ir/rabbitio"
    )

    type streamReader struct {
            ie        *ioaead
            cip       cipher.Stream
            firstRead bool
            nwr       int
            read      io.Reader
            buff      []byte
            temp      []byte
            tagc      []byte
    }

    type ioaead struct {
            key            []byte // rabbit cipher key
            nonce          []byte
            poly           *poly1305.MAC
            adlen          int
            additionalData AdditionalFunc
    }

    type streamWriter struct {
            ie          *ioaead
            writer      io.Writer
            plainWriter io.Writer
            nwr         int
            firstWrite  bool
    }

    var errunderio = errors.New("underlying io reader returns wrong read value, which is not supposed to happen")

    func makeioaead(key, iv []byte, adfunc AdditionalFunc) *ioaead {
            if adfunc == nil {
                    adfunc = func() []byte { return nil }
            }
            str := &ioaead{
                    key:   make([]byte, rabbitio.KeyLen),
                    nonce: make([]byte, len(iv)),
            }

            str.additionalData = adfunc
            copy(str.key, key)
            copy(str.nonce, iv)
            var poly [polykeylen]byte
            cph, _ := rabbitio.NewCipher(str.key, str.nonce)
            cph.XORKeyStream(poly[:], poly[:])
            str.poly = poly1305.New(&poly)
            return str
    }

    func (s *ioaead) execAdFunc() {
            additionalData := s.additionalData()
            s.adlen = len(additionalData)
            writePadding(s.poly, additionalData)
    }

    func newCipherReader(r io.Reader, key, nonce []byte, f AdditionalFunc) (*streamReader, error) {
            if len(key) != rabbitio.KeyLen {
                    return nil, rabbitio.ErrInvalidKey
            }

            if len(nonce) != rabbitio.IVXLen && len(nonce) != 0 {
                    return nil, rabbitio.ErrInvalidIVX
            }

            v := &streamReader{
                    ie:   makeioaead(key, nonce, f),
                    read: r,
                    buff: []byte{},
                    tagc: make([]byte, 16),
                    temp: make([]byte, 16),
            }

            v.cip, _ = rabbitio.NewCipher(v.ie.key, v.ie.nonce)
            return v, nil
    }

    // NewStreamReader returns streamReader data type, this reader open() and read aead
    // ciphertext which have 16-byte poly1305 tag overhead.
    // read data cannot be authenticated until underlying reader returns EOF
    // so you should use this reader only if you can undo your read.
    // AdFunc will be triggered at first call to read method
    func NewStreamReader(r io.Reader, key, nonce []byte, f AdditionalFunc) (*streamReader, error) {
            return newCipherReader(r, key, nonce, f)
    }

    // NewStreamWriter returns streamWriter data type, this writer sale() and write aead
    // plaintext which have 16-byte poly1305 tag overhead, running Close() is necessary
    // in order to calculate and write tag at the end of the write.
    // AdFunc will be triggered at first call to write method
    func NewStreamWriter(w io.Writer, key, nonce []byte, f AdditionalFunc) (*streamWriter, error) {
            return newChipherWriter(w, key, nonce, f)
    }

    func newChipherWriter(w io.Writer, key, nonce []byte, f AdditionalFunc) (*streamWriter, error) {
            if len(key) != rabbitio.KeyLen {
                    return nil, rabbitio.ErrInvalidKey
            }
            if len(nonce) != rabbitio.IVXLen && len(nonce) != 0 {
                    return nil, rabbitio.ErrInvalidIVX
            }
            v := &streamWriter{
                    ie:          makeioaead(key, nonce, f),
                    plainWriter: w,
            }

            v.writer, _ = rabbitio.NewWriterCipher(
                    v.ie.key, v.ie.nonce,
                    io.MultiWriter(w, v.ie.poly),
            )

            return v, nil
    }

    func (r *streamReader) readTo(b []byte) (int, error) {
            var n int
            if len(r.buff) > 0 {
                    return r.copyBuff(b), nil
            }

            sr, err := r.readBuff()
            if err != nil {
                    if err == io.EOF {
                            n = r.copyUntil(b, sr)
                            return n, r.verify()
                    }
                    return n, err
            }
            return r.copyUntil(b, sr), err
    }

    // Read reads and open ciphertext.
    // read data is unreliable until underlying reader returns EOF
    // after that Read return EOF or ErrAuthMsg if integrity of data has been compromised.
    // in such a case, you need to unread data. a simple demonstration would be to delete
    // or truncate the file if ErrAuthMsg is returned
    func (r *streamReader) Read(b []byte) (int, error) {
            if len(b) <= 16 {
                    return r.readTo(b)
            }
            n := 0
            for {
                    if n+16 > len(b) {
                            sr, err := r.readTo(b[n:])
                            n += sr
                            if err != nil {
                                    return n, err
                            }
                            break
                    }

                    sr, err := r.readTo(b[n : n+16])
                    n += sr
                    if err != nil {
                            return n, err
                    }
            }
            return n, nil
    }

    func (r *streamReader) verify() error {
            r.ie.ioPaddingTo(r.nwr)
            if r.ie.poly.Verify(r.tagc) {
                    return io.EOF
            }
            return ErrAuthMsg
    }

    func (r *streamReader) copyUntil(b []byte, sr int) int {
            n := copy(b, r.buff[:sr])
            r.buff = r.buff[n:]
            r.nwr += n
            return n
    }

    func (r *streamReader) copyBuff(b []byte) int {
            n := copy(b, r.buff)
            r.buff = r.buff[n:]
            r.nwr += n
            return n
    }

    func (r *streamReader) readBuff() (int, error) {
            if !r.firstRead {
                    r.ie.execAdFunc()
                    _, err := io.ReadFull(r.read, r.temp)
                    if err != nil {
                            return 0, err
                    }
                    r.firstRead = true
            }

            var buff = make([]byte, 16)
            n, err := r.read.Read(buff)
            if err != nil {
                    return 0, err
            }
            if n > len(buff) {
                    return 0, errunderio
            }

            copy(r.tagc, append(r.temp[n:], buff[:n]...))
            r.buff = append(r.buff, r.temp[:n]...)
            r.buffAndXor()

            if n < 16 {
                    return n, err
            }

            copy(r.temp, buff)
            return n, err
    }

    func (r *streamReader) buffAndXor() {
            r.ie.poly.Write(r.buff)
            r.cip.XORKeyStream(r.buff, r.buff)
    }

    // Write writes plaintext data, in order to calculate and write tag
    // at the end of the write, running Close() is necessary
    func (w *streamWriter) Write(b []byte) (int, error) {
            if !w.firstWrite {
                    w.ie.execAdFunc()
                    w.firstWrite = true
            }
            n, err := w.writer.Write(b)
            if err != nil {
                    return n, err
            }

            w.nwr += n
            return n, err
    }

    func (p *ioaead) ioPaddingTo(nb int) {
            if rem := nb % 16; rem != 0 {
                    var buf [16]byte
                    padLen := 16 - rem
                    p.poly.Write(buf[:padLen])
            }

            writeUint64(p.poly, p.adlen)
            writeUint64(p.poly, nb)
    }

    // Close calculate and write poly1305 tag before closing the writer
    // if underlying writer does not have a Close() method, Close only
    // calculate and write poly1305 tag
    func (w *streamWriter) Close() error {
            w.ie.ioPaddingTo(w.nwr)
            if _, err := w.plainWriter.Write(w.ie.poly.Sum(nil)); err != nil {
                    return err
            }

            if c, ok := w.plainWriter.(io.Closer); ok {
                    return c.Close()
            }
            return nil
    }