mirror of
https://github.com/taigrr/arc
synced 2025-01-18 04:33:13 -08:00
initial import
This commit is contained in:
76
archive/archive.go
Normal file
76
archive/archive.go
Normal file
@@ -0,0 +1,76 @@
|
||||
// Copyright (C) 2016 - Will Glozer. All rights reserved.
|
||||
|
||||
package archive
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/subtle"
|
||||
"io"
|
||||
|
||||
"github.com/wg/ecies/xchacha20poly1305"
|
||||
)
|
||||
|
||||
const KeySize = xchacha20poly1305.KeySize
|
||||
|
||||
type Archive struct {
|
||||
xchacha20poly1305.XChaCha20Poly1305
|
||||
tag [xchacha20poly1305.TagSize]byte
|
||||
io.Reader
|
||||
io.Writer
|
||||
}
|
||||
|
||||
func NewArchiveFromReader(r io.Reader, key []byte) (*Archive, error) {
|
||||
var nonce [xchacha20poly1305.NonceSize]byte
|
||||
a := &Archive{Reader: r}
|
||||
|
||||
if _, err := io.ReadFull(r, a.tag[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err := io.ReadFull(r, nonce[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err := a.Init(key, nonce[:])
|
||||
return a, err
|
||||
}
|
||||
|
||||
func NewArchiveForWriter(w io.Writer, key []byte) (*Archive, error) {
|
||||
var nonce [xchacha20poly1305.NonceSize]byte
|
||||
a := &Archive{Writer: w}
|
||||
|
||||
if _, err := rand.Read(nonce[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := a.Init(key, nonce[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err := w.Write(a.tag[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err := w.Write(nonce[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func (a *Archive) Read(b []byte) (int, error) {
|
||||
n, err := a.Reader.Read(b)
|
||||
a.Decrypt(b[:n], b[:n])
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (a *Archive) Write(b []byte) (int, error) {
|
||||
a.Encrypt(b, b)
|
||||
return a.Writer.Write(b)
|
||||
}
|
||||
|
||||
func (a *Archive) Verify() bool {
|
||||
var tag [xchacha20poly1305.TagSize]byte
|
||||
a.Tag(tag[:0])
|
||||
return subtle.ConstantTimeCompare(a.tag[:], tag[:]) == 1
|
||||
}
|
||||
170
archive/archive_test.go
Normal file
170
archive/archive_test.go
Normal file
@@ -0,0 +1,170 @@
|
||||
// Copyright (C) 2016 - Will Glozer. All rights reserved.
|
||||
|
||||
package archive
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCreateArchive(t *testing.T) {
|
||||
entries := []*tar.Header{
|
||||
{Name: "foo", Size: 0},
|
||||
{Name: "bar", Size: 1<<16 - 1},
|
||||
{Name: "baz", Size: 64},
|
||||
}
|
||||
key := randomKey()
|
||||
|
||||
buf, dat, err := createArchive(key, entries)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
r, err := NewReader(buf, key)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for i, e := range entries {
|
||||
switch next, err := r.Next(); {
|
||||
case err != nil:
|
||||
t.Fatal(err)
|
||||
case e.Name != next.Name:
|
||||
t.Fatalf("expected entry name %s got %s", e.Name, next.Name)
|
||||
case e.Size != next.Size:
|
||||
t.Fatalf("expected entry size %d got %d", e.Size, next.Size)
|
||||
}
|
||||
|
||||
switch b, err := ioutil.ReadAll(r); {
|
||||
case err != nil:
|
||||
t.Fatal(err)
|
||||
case int(e.Size) != len(b):
|
||||
t.Fatalf("expected to read %d bytes got %d", e.Size, len(b))
|
||||
case !bytes.Equal(b, dat[i]):
|
||||
t.Fatalf("expected content '%v' got '%v'", b, dat[i])
|
||||
}
|
||||
}
|
||||
|
||||
if !r.Verify() {
|
||||
t.Fatal("archive verify failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyArchive(t *testing.T) {
|
||||
entries := []*tar.Header{
|
||||
{Name: "foo", Size: 32},
|
||||
{Name: "bar", Size: 64},
|
||||
}
|
||||
key := randomKey()
|
||||
|
||||
buf, _, err := createArchive(key, entries)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if valid, _ := Verify(buf, key); !valid {
|
||||
t.Fatal("archive verify failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyFailWrongKey(t *testing.T) {
|
||||
entries := []*tar.Header{
|
||||
{Name: "foo", Size: 32},
|
||||
{Name: "bar", Size: 64},
|
||||
}
|
||||
key := randomKey()
|
||||
|
||||
buf, _, err := createArchive(key, entries)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
key[0] = ^key[0]
|
||||
|
||||
if valid, _ := Verify(buf, key); valid {
|
||||
t.Fatal("verified invalid archive")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyFailByteFlip(t *testing.T) {
|
||||
entries := []*tar.Header{
|
||||
{Name: "foo", Size: 32},
|
||||
{Name: "bar", Size: 64},
|
||||
}
|
||||
key := randomKey()
|
||||
|
||||
buf, _, err := createArchive(key, entries)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
archive := buf.Bytes()
|
||||
for i, b := range archive {
|
||||
archive[i] = ^archive[i]
|
||||
|
||||
r := bytes.NewReader(archive)
|
||||
if valid, _ := Verify(r, key); valid {
|
||||
t.Fatal("verified invalid archive at", i)
|
||||
}
|
||||
|
||||
archive[i] = b
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriterInvariants(t *testing.T) {
|
||||
_, _, err := createArchive(make([]byte, 31), nil)
|
||||
if err == nil {
|
||||
t.Fatalf("created archive with 31 byte key")
|
||||
}
|
||||
}
|
||||
|
||||
func createArchive(key []byte, entries []*tar.Header) (*Buffer, [][]byte, error) {
|
||||
buf := &Buffer{}
|
||||
|
||||
arc, err := NewWriter(buf, key)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
dat := make([][]byte, len(entries))
|
||||
|
||||
for i, e := range entries {
|
||||
err := arc.Add(e)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
dat[i] = make([]byte, e.Size)
|
||||
_, err = rand.Read(dat[i])
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
err = arc.Copy(bytes.NewReader(dat[i]), e.Size)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
tag, _ := arc.Finish()
|
||||
copy(buf.Bytes()[0:16], tag)
|
||||
|
||||
return buf, dat, nil
|
||||
}
|
||||
|
||||
type Buffer struct {
|
||||
bytes.Buffer
|
||||
}
|
||||
|
||||
func randomKey() []byte {
|
||||
key := make([]byte, 32)
|
||||
_, err := io.ReadFull(rand.Reader, key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return key
|
||||
}
|
||||
47
archive/reader.go
Normal file
47
archive/reader.go
Normal file
@@ -0,0 +1,47 @@
|
||||
// Copyright (C) 2016 - Will Glozer. All rights reserved.
|
||||
|
||||
package archive
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"compress/gzip"
|
||||
"io"
|
||||
)
|
||||
|
||||
type Reader struct {
|
||||
archiver *tar.Reader
|
||||
compressor *gzip.Reader
|
||||
archive *Archive
|
||||
}
|
||||
|
||||
func NewReader(r io.Reader, key []byte) (*Reader, error) {
|
||||
archive, err := NewArchiveFromReader(r, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
compressor, err := gzip.NewReader(archive)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
archiver := tar.NewReader(compressor)
|
||||
|
||||
return &Reader{
|
||||
archiver: archiver,
|
||||
compressor: compressor,
|
||||
archive: archive,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *Reader) Next() (*tar.Header, error) {
|
||||
return r.archiver.Next()
|
||||
}
|
||||
|
||||
func (r *Reader) Read(b []byte) (int, error) {
|
||||
return r.archiver.Read(b)
|
||||
}
|
||||
|
||||
func (r *Reader) Verify() bool {
|
||||
return r.archive.Verify()
|
||||
}
|
||||
22
archive/verify.go
Normal file
22
archive/verify.go
Normal file
@@ -0,0 +1,22 @@
|
||||
// Copyright (C) 2016 - Will Glozer. All rights reserved.
|
||||
|
||||
package archive
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
func Verify(r io.Reader, key []byte) (bool, error) {
|
||||
archive, err := NewArchiveFromReader(r, key)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
_, err = io.Copy(ioutil.Discard, archive)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return archive.Verify(), nil
|
||||
}
|
||||
63
archive/writer.go
Normal file
63
archive/writer.go
Normal file
@@ -0,0 +1,63 @@
|
||||
// Copyright (C) 2016 - Will Glozer. All rights reserved.
|
||||
|
||||
package archive
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"github.com/klauspost/compress/gzip"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrShortCopy = errors.New("archive: short copy")
|
||||
)
|
||||
|
||||
type Writer struct {
|
||||
archiver *tar.Writer
|
||||
compressor *gzip.Writer
|
||||
archive *Archive
|
||||
}
|
||||
|
||||
func NewWriter(w io.Writer, key []byte) (*Writer, error) {
|
||||
archive, err := NewArchiveForWriter(w, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
compressor := gzip.NewWriter(archive)
|
||||
archiver := tar.NewWriter(compressor)
|
||||
|
||||
return &Writer{
|
||||
archiver: archiver,
|
||||
compressor: compressor,
|
||||
archive: archive,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (w *Writer) Add(header *tar.Header) error {
|
||||
return w.archiver.WriteHeader(header)
|
||||
}
|
||||
|
||||
func (w *Writer) Copy(r io.Reader, size int64) error {
|
||||
switch n, err := io.Copy(w.archiver, r); {
|
||||
case err != nil:
|
||||
return err
|
||||
case n < size:
|
||||
return ErrShortCopy
|
||||
}
|
||||
return w.archiver.Flush()
|
||||
}
|
||||
|
||||
func (w *Writer) Finish() ([]byte, error) {
|
||||
if err := w.archiver.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := w.compressor.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return w.archive.Tag(nil), nil
|
||||
}
|
||||
Reference in New Issue
Block a user