mirror of
https://github.com/restic/restic.git
synced 2025-03-16 00:00:05 +01:00
357 lines
9.6 KiB
Go
357 lines
9.6 KiB
Go
package crypto_test
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/rand"
|
|
"encoding/json"
|
|
"io"
|
|
"os"
|
|
"testing"
|
|
|
|
"github.com/restic/restic/internal/crypto"
|
|
"github.com/restic/restic/internal/errors"
|
|
rtest "github.com/restic/restic/internal/test"
|
|
"golang.org/x/sync/errgroup"
|
|
|
|
"github.com/restic/chunker"
|
|
)
|
|
|
|
const testLargeCrypto = false
|
|
|
|
func TestEncryptDecrypt(t *testing.T) {
|
|
k := crypto.NewRandomKey()
|
|
|
|
tests := []int{5, 23, 2<<18 + 23, 1 << 20}
|
|
if testLargeCrypto {
|
|
tests = append(tests, 7<<20+123)
|
|
}
|
|
|
|
for _, size := range tests {
|
|
data := rtest.Random(42, size)
|
|
buf := make([]byte, 0, size+crypto.Extension)
|
|
|
|
nonce := crypto.NewRandomNonce()
|
|
ciphertext := k.Seal(buf[:0], nonce, data, nil)
|
|
rtest.Assert(t, len(ciphertext) == len(data)+k.Overhead(),
|
|
"ciphertext length does not match: want %d, got %d",
|
|
len(data)+crypto.Extension, len(ciphertext))
|
|
|
|
plaintext := make([]byte, 0, len(ciphertext))
|
|
plaintext, err := k.Open(plaintext[:0], nonce, ciphertext, nil)
|
|
rtest.OK(t, err)
|
|
rtest.Assert(t, len(plaintext) == len(data),
|
|
"plaintext length does not match: want %d, got %d",
|
|
len(data), len(plaintext))
|
|
|
|
rtest.Equals(t, plaintext, data)
|
|
}
|
|
}
|
|
|
|
func TestSmallBuffer(t *testing.T) {
|
|
k := crypto.NewRandomKey()
|
|
|
|
size := 600
|
|
data := make([]byte, size)
|
|
_, err := io.ReadFull(rand.Reader, data)
|
|
rtest.OK(t, err)
|
|
|
|
ciphertext := make([]byte, 0, size/2)
|
|
nonce := crypto.NewRandomNonce()
|
|
ciphertext = k.Seal(ciphertext[:0], nonce, data, nil)
|
|
// this must extend the slice
|
|
rtest.Assert(t, cap(ciphertext) > size/2,
|
|
"expected extended slice, but capacity is only %d bytes",
|
|
cap(ciphertext))
|
|
|
|
// check for the correct plaintext
|
|
plaintext := make([]byte, len(ciphertext))
|
|
plaintext, err = k.Open(plaintext[:0], nonce, ciphertext, nil)
|
|
rtest.OK(t, err)
|
|
rtest.Assert(t, bytes.Equal(plaintext, data),
|
|
"wrong plaintext returned")
|
|
}
|
|
|
|
func TestSameBuffer(t *testing.T) {
|
|
k := crypto.NewRandomKey()
|
|
|
|
size := 600
|
|
data := make([]byte, size)
|
|
_, err := io.ReadFull(rand.Reader, data)
|
|
rtest.OK(t, err)
|
|
|
|
ciphertext := make([]byte, 0, size+crypto.Extension)
|
|
|
|
nonce := crypto.NewRandomNonce()
|
|
ciphertext = k.Seal(ciphertext, nonce, data, nil)
|
|
|
|
// use the same buffer for decryption
|
|
ciphertext, err = k.Open(ciphertext[:0], nonce, ciphertext, nil)
|
|
rtest.OK(t, err)
|
|
rtest.Assert(t, bytes.Equal(ciphertext, data),
|
|
"wrong plaintext returned")
|
|
}
|
|
|
|
func encrypt(t testing.TB, k *crypto.Key, data, ciphertext, nonce []byte) []byte {
|
|
prefixlen := len(ciphertext)
|
|
ciphertext = k.Seal(ciphertext, nonce, data, nil)
|
|
if len(ciphertext) != len(data)+k.Overhead()+prefixlen {
|
|
t.Fatalf("destination slice has wrong length, want %d, got %d",
|
|
len(data)+k.Overhead(), len(ciphertext))
|
|
}
|
|
|
|
return ciphertext
|
|
}
|
|
|
|
func decryptNewSliceAndCompare(t testing.TB, k *crypto.Key, data, ciphertext, nonce []byte) {
|
|
plaintext := make([]byte, 0, len(ciphertext))
|
|
decryptAndCompare(t, k, data, ciphertext, nonce, plaintext)
|
|
}
|
|
|
|
func decryptAndCompare(t testing.TB, k *crypto.Key, data, ciphertext, nonce, dst []byte) {
|
|
prefix := make([]byte, len(dst))
|
|
copy(prefix, dst)
|
|
|
|
plaintext, err := k.Open(dst, nonce, ciphertext, nil)
|
|
if err != nil {
|
|
t.Fatalf("unable to decrypt ciphertext: %v", err)
|
|
}
|
|
|
|
if len(data)+len(prefix) != len(plaintext) {
|
|
t.Fatalf("wrong plaintext returned, want %d bytes, got %d", len(data)+len(prefix), len(plaintext))
|
|
}
|
|
|
|
if !bytes.Equal(plaintext[:len(prefix)], prefix) {
|
|
t.Fatal("prefix is wrong")
|
|
}
|
|
|
|
if !bytes.Equal(plaintext[len(prefix):], data) {
|
|
t.Fatal("wrong plaintext returned")
|
|
}
|
|
}
|
|
|
|
func TestAppendOpen(t *testing.T) {
|
|
k := crypto.NewRandomKey()
|
|
nonce := crypto.NewRandomNonce()
|
|
|
|
data := make([]byte, 600)
|
|
_, err := io.ReadFull(rand.Reader, data)
|
|
rtest.OK(t, err)
|
|
ciphertext := encrypt(t, k, data, nil, nonce)
|
|
|
|
// we need to test several different cases:
|
|
// * destination slice is nil
|
|
// * destination slice is empty and has enough capacity
|
|
// * destination slice is empty and does not have enough capacity
|
|
// * destination slice contains data and has enough capacity
|
|
// * destination slice contains data and does not have enough capacity
|
|
|
|
// destination slice is nil
|
|
t.Run("nil", func(t *testing.T) {
|
|
var plaintext []byte
|
|
decryptAndCompare(t, k, data, ciphertext, nonce, plaintext)
|
|
})
|
|
|
|
// destination slice is empty and has enough capacity
|
|
t.Run("empty-large", func(t *testing.T) {
|
|
plaintext := make([]byte, 0, len(data)+100)
|
|
decryptAndCompare(t, k, data, ciphertext, nonce, plaintext)
|
|
})
|
|
|
|
// destination slice is empty and does not have enough capacity
|
|
t.Run("empty-small", func(t *testing.T) {
|
|
plaintext := make([]byte, 0, len(data)/2)
|
|
decryptAndCompare(t, k, data, ciphertext, nonce, plaintext)
|
|
})
|
|
|
|
// destination slice contains data and has enough capacity
|
|
t.Run("prefix-large", func(t *testing.T) {
|
|
plaintext := make([]byte, 0, len(data)+100)
|
|
plaintext = append(plaintext, []byte("foobar")...)
|
|
decryptAndCompare(t, k, data, ciphertext, nonce, plaintext)
|
|
})
|
|
|
|
// destination slice contains data and does not have enough capacity
|
|
t.Run("prefix-small", func(t *testing.T) {
|
|
plaintext := make([]byte, 0, len(data)/2)
|
|
plaintext = append(plaintext, []byte("foobar")...)
|
|
decryptAndCompare(t, k, data, ciphertext, nonce, plaintext)
|
|
})
|
|
}
|
|
|
|
func TestAppendSeal(t *testing.T) {
|
|
k := crypto.NewRandomKey()
|
|
|
|
data := make([]byte, 600)
|
|
_, err := io.ReadFull(rand.Reader, data)
|
|
rtest.OK(t, err)
|
|
|
|
// we need to test several different cases:
|
|
// * destination slice is nil
|
|
// * destination slice is empty and has enough capacity
|
|
// * destination slice is empty and does not have enough capacity
|
|
// * destination slice contains data and has enough capacity
|
|
// * destination slice contains data and does not have enough capacity
|
|
|
|
// destination slice is nil
|
|
t.Run("nil", func(t *testing.T) {
|
|
nonce := crypto.NewRandomNonce()
|
|
var ciphertext []byte
|
|
|
|
ciphertext = encrypt(t, k, data, ciphertext, nonce)
|
|
decryptNewSliceAndCompare(t, k, data, ciphertext, nonce)
|
|
})
|
|
|
|
// destination slice is empty and has enough capacity
|
|
t.Run("empty-large", func(t *testing.T) {
|
|
nonce := crypto.NewRandomNonce()
|
|
ciphertext := make([]byte, 0, len(data)+100)
|
|
|
|
ciphertext = encrypt(t, k, data, ciphertext, nonce)
|
|
decryptNewSliceAndCompare(t, k, data, ciphertext, nonce)
|
|
})
|
|
|
|
// destination slice is empty and does not have enough capacity
|
|
t.Run("empty-small", func(t *testing.T) {
|
|
nonce := crypto.NewRandomNonce()
|
|
ciphertext := make([]byte, 0, len(data)/2)
|
|
|
|
ciphertext = encrypt(t, k, data, ciphertext, nonce)
|
|
decryptNewSliceAndCompare(t, k, data, ciphertext, nonce)
|
|
})
|
|
|
|
// destination slice contains data and has enough capacity
|
|
t.Run("prefix-large", func(t *testing.T) {
|
|
nonce := crypto.NewRandomNonce()
|
|
ciphertext := make([]byte, 0, len(data)+100)
|
|
ciphertext = append(ciphertext, []byte("foobar")...)
|
|
|
|
ciphertext = encrypt(t, k, data, ciphertext, nonce)
|
|
if string(ciphertext[:6]) != "foobar" {
|
|
t.Errorf("prefix is missing")
|
|
}
|
|
decryptNewSliceAndCompare(t, k, data, ciphertext[6:], nonce)
|
|
})
|
|
|
|
// destination slice contains data and does not have enough capacity
|
|
t.Run("prefix-small", func(t *testing.T) {
|
|
nonce := crypto.NewRandomNonce()
|
|
ciphertext := make([]byte, 0, len(data)/2)
|
|
ciphertext = append(ciphertext, []byte("foobar")...)
|
|
|
|
ciphertext = encrypt(t, k, data, ciphertext, nonce)
|
|
if string(ciphertext[:6]) != "foobar" {
|
|
t.Errorf("prefix is missing")
|
|
}
|
|
decryptNewSliceAndCompare(t, k, data, ciphertext[6:], nonce)
|
|
})
|
|
}
|
|
|
|
func TestLargeEncrypt(t *testing.T) {
|
|
if !testLargeCrypto {
|
|
t.SkipNow()
|
|
}
|
|
|
|
k := crypto.NewRandomKey()
|
|
|
|
for _, size := range []int{chunker.MaxSize, chunker.MaxSize + 1, chunker.MaxSize + 1<<20} {
|
|
data := make([]byte, size)
|
|
_, err := io.ReadFull(rand.Reader, data)
|
|
rtest.OK(t, err)
|
|
|
|
nonce := crypto.NewRandomNonce()
|
|
ciphertext := k.Seal(make([]byte, size+k.Overhead()), nonce, data, nil)
|
|
plaintext, err := k.Open([]byte{}, nonce, ciphertext, nil)
|
|
rtest.OK(t, err)
|
|
|
|
rtest.Equals(t, plaintext, data)
|
|
}
|
|
}
|
|
|
|
func BenchmarkEncrypt(b *testing.B) {
|
|
size := 8 << 20 // 8MiB
|
|
data := make([]byte, size)
|
|
|
|
k := crypto.NewRandomKey()
|
|
buf := make([]byte, len(data)+crypto.Extension)
|
|
nonce := crypto.NewRandomNonce()
|
|
|
|
b.ResetTimer()
|
|
b.SetBytes(int64(size))
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
_ = k.Seal(buf, nonce, data, nil)
|
|
}
|
|
}
|
|
|
|
func writeTempfile(name string, data []byte) {
|
|
f, err := os.Create("/tmp/" + name)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
_, err = f.Write(data)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
err = f.Close()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
func writeJSON(name string, data interface{}) {
|
|
buf, err := json.Marshal(data)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
writeTempfile(name, buf)
|
|
}
|
|
|
|
func BenchmarkDecrypt(b *testing.B) {
|
|
size := 8 << 20 // 8MiB
|
|
data := rtest.Random(42, size)
|
|
|
|
k := crypto.NewRandomKey()
|
|
|
|
ciphertext := make([]byte, 0, size+crypto.Extension)
|
|
nonce := crypto.NewRandomNonce()
|
|
ciphertext = k.Seal(ciphertext, nonce, data, nil)
|
|
|
|
b.ResetTimer()
|
|
b.SetBytes(int64(size))
|
|
|
|
var wg errgroup.Group
|
|
|
|
for i := 0; i < 5; i++ {
|
|
wg.Go(func() error {
|
|
buf := make([]byte, len(ciphertext))
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
buf = buf[:cap(buf)]
|
|
copy(buf, ciphertext)
|
|
result, err := k.Open(buf[:0], nonce, buf, nil)
|
|
rtest.OK(b, err)
|
|
|
|
if !bytes.Equal(data, result) {
|
|
writeJSON("benchmark-decrypt-encryption-key.raw", k.EncryptionKey)
|
|
writeJSON("benchmark-decrypt-mac-key.raw", k.MACKey)
|
|
writeTempfile("benchmark-decrypt-nonce.raw", nonce)
|
|
writeTempfile("benchmark-decrypt-ciphertext.raw", ciphertext)
|
|
writeTempfile("benchmark-decrypt-plaintext.raw", data)
|
|
writeTempfile("benchmark-decrypt-result.raw", result)
|
|
|
|
return errors.New("wrong plaintext")
|
|
}
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
err := wg.Wait()
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
}
|