forked from TrueCloudLab/restic
Reorganize crypto code
Move all crypto functions to package "crypto", move random generators for tests into helper package.
This commit is contained in:
parent
8e8f31d3fe
commit
3a2525809c
10 changed files with 166 additions and 187 deletions
|
@ -4,7 +4,6 @@ import (
|
|||
"bytes"
|
||||
"flag"
|
||||
"io"
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/restic/restic"
|
||||
|
@ -16,21 +15,6 @@ import (
|
|||
var benchArchiveDirectory = flag.String("test.benchdir", ".", "benchmark archiving a real directory (default: .)")
|
||||
var testPol = chunker.Pol(0x3DA3358B4DC173)
|
||||
|
||||
func get_random(seed, count int) []byte {
|
||||
buf := make([]byte, count)
|
||||
|
||||
rnd := rand.New(rand.NewSource(int64(seed)))
|
||||
for i := 0; i < count; i++ {
|
||||
buf[i] = byte(rnd.Uint32())
|
||||
}
|
||||
|
||||
return buf
|
||||
}
|
||||
|
||||
func randomReader(seed, size int) *bytes.Reader {
|
||||
return bytes.NewReader(get_random(seed, size))
|
||||
}
|
||||
|
||||
const bufSize = chunker.MiB
|
||||
|
||||
type Rdr interface {
|
||||
|
@ -66,7 +50,7 @@ func benchmarkChunkEncrypt(b testing.TB, buf []byte, rd Rdr, key *restic.Key) {
|
|||
}
|
||||
|
||||
func BenchmarkChunkEncrypt(b *testing.B) {
|
||||
data := get_random(23, 10<<20) // 10MiB
|
||||
data := Random(23, 10<<20) // 10MiB
|
||||
rd := bytes.NewReader(data)
|
||||
|
||||
be := setupBackend(b)
|
||||
|
@ -110,7 +94,7 @@ func BenchmarkChunkEncryptParallel(b *testing.B) {
|
|||
defer teardownBackend(b, be)
|
||||
key := setupKey(b, be, "geheim")
|
||||
|
||||
data := get_random(23, 10<<20) // 10MiB
|
||||
data := Random(23, 10<<20) // 10MiB
|
||||
|
||||
buf := restic.GetChunkBuf("BenchmarkChunkEncryptParallel")
|
||||
|
||||
|
|
19
crypto/buffer_pool.go
Normal file
19
crypto/buffer_pool.go
Normal file
|
@ -0,0 +1,19 @@
|
|||
package crypto
|
||||
|
||||
import "sync"
|
||||
|
||||
const defaultBufSize = 2048
|
||||
|
||||
var bufPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
return make([]byte, defaultBufSize)
|
||||
},
|
||||
}
|
||||
|
||||
func getBuffer() []byte {
|
||||
return bufPool.Get().([]byte)
|
||||
}
|
||||
|
||||
func freeBuffer(buf []byte) {
|
||||
bufPool.Put(buf)
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package restic
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
@ -6,11 +6,13 @@ import (
|
|||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"sync"
|
||||
|
||||
"github.com/restic/restic/chunker"
|
||||
"golang.org/x/crypto/poly1305"
|
||||
"golang.org/x/crypto/scrypt"
|
||||
)
|
||||
|
@ -21,8 +23,30 @@ const (
|
|||
MACKeySizeR = 16 // for Poly1305
|
||||
MACKeySize = MACKeySizeK + MACKeySizeR // for Poly1305-AES128
|
||||
ivSize = aes.BlockSize
|
||||
|
||||
macSize = poly1305.TagSize // Poly1305 size is 16 byte
|
||||
CiphertextExtension = ivSize + macSize
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrUnauthenticated is returned when ciphertext verification has failed.
|
||||
ErrUnauthenticated = errors.New("ciphertext verification failed")
|
||||
|
||||
// ErrBufferTooSmall is returned when the destination slice is too small
|
||||
// for the ciphertext.
|
||||
ErrBufferTooSmall = errors.New("destination buffer too small")
|
||||
)
|
||||
|
||||
// MasterKeys holds signing and encryption keys for a repository. It is stored
|
||||
// encrypted and signed as a JSON data structure in the Data field of the Key
|
||||
// structure. For the master key, the secret random polynomial used for content
|
||||
// defined chunking is included.
|
||||
type MasterKeys struct {
|
||||
Sign MACKey `json:"sign"`
|
||||
Encrypt AESKey `json:"encrypt"`
|
||||
ChunkerPolynomial chunker.Pol `json:"chunker_polynomial,omitempty"`
|
||||
}
|
||||
|
||||
type AESKey [32]byte
|
||||
type MACKey struct {
|
||||
K [16]byte // for AES128
|
||||
|
@ -118,7 +142,7 @@ func poly1305_verify(msg []byte, nonce []byte, key *MACKey, mac []byte) bool {
|
|||
}
|
||||
|
||||
// returns new encryption and mac keys. k.MACKey.R is already masked.
|
||||
func generateRandomKeys() (k *MasterKeys) {
|
||||
func GenerateRandomKeys() (k *MasterKeys) {
|
||||
k = &MasterKeys{}
|
||||
n, err := rand.Read(k.Encrypt[:])
|
||||
if n != AESKeySize || err != nil {
|
||||
|
@ -249,16 +273,17 @@ func Decrypt(ks *MasterKeys, plaintext, ciphertext []byte) ([]byte, error) {
|
|||
return plaintext, nil
|
||||
}
|
||||
|
||||
// runs scrypt(password)
|
||||
func kdf(k *Key, password string) (*MasterKeys, error) {
|
||||
if len(k.Salt) == 0 {
|
||||
// KDF derives encryption and signing keys from the password using the supplied
|
||||
// parameters N, R and P and the Salt.
|
||||
func KDF(N, R, P int, salt []byte, password string) (*MasterKeys, error) {
|
||||
if len(salt) == 0 {
|
||||
return nil, fmt.Errorf("scrypt() called with empty salt")
|
||||
}
|
||||
|
||||
derKeys := &MasterKeys{}
|
||||
|
||||
keybytes := MACKeySize + AESKeySize
|
||||
scryptKeys, err := scrypt.Key([]byte(password), k.Salt, k.N, k.R, k.P, keybytes)
|
||||
scryptKeys, err := scrypt.Key([]byte(password), salt, N, R, P, keybytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error deriving keys from password: %v", err)
|
||||
}
|
||||
|
@ -296,7 +321,7 @@ func (e *encryptWriter) Close() error {
|
|||
}
|
||||
|
||||
// return buffer
|
||||
FreeChunkBuf("EncryptWriter", e.data.Bytes())
|
||||
bufPool.Put(e.data.Bytes())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -354,7 +379,7 @@ func (e *encryptWriter) Write(p []byte) (int, error) {
|
|||
func EncryptTo(ks *MasterKeys, wr io.Writer) io.WriteCloser {
|
||||
ew := &encryptWriter{
|
||||
iv: generateRandomIV(),
|
||||
data: bytes.NewBuffer(GetChunkBuf("EncryptWriter")[:0]),
|
||||
data: bytes.NewBuffer(getBuffer()[:0]),
|
||||
key: ks,
|
||||
origWr: wr,
|
||||
}
|
||||
|
@ -426,7 +451,7 @@ func (d *decryptReader) Close() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
FreeChunkBuf("decryptReader", d.buf)
|
||||
freeBuffer(d.buf)
|
||||
d.buf = nil
|
||||
return nil
|
||||
}
|
||||
|
@ -438,7 +463,7 @@ func (d *decryptReader) Close() error {
|
|||
// afterwards. If a MAC verification failure is observed, it is returned
|
||||
// immediately.
|
||||
func DecryptFrom(ks *MasterKeys, rd io.Reader) (io.ReadCloser, error) {
|
||||
ciphertext := GetChunkBuf("decryptReader")
|
||||
ciphertext := getBuffer()
|
||||
|
||||
ciphertext = ciphertext[0:cap(ciphertext)]
|
||||
n, err := io.ReadFull(rd, ciphertext)
|
|
@ -1,4 +1,4 @@
|
|||
package restic
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
@ -98,24 +98,22 @@ func should_panic(f func()) (did_panic bool) {
|
|||
}
|
||||
|
||||
func TestCrypto(t *testing.T) {
|
||||
r := &Key{}
|
||||
|
||||
for _, tv := range test_values {
|
||||
// test encryption
|
||||
r.master = &MasterKeys{
|
||||
k := &MasterKeys{
|
||||
Encrypt: tv.ekey,
|
||||
Sign: tv.skey,
|
||||
}
|
||||
|
||||
msg := make([]byte, maxCiphertextSize)
|
||||
n, err := Encrypt(r.master, msg, tv.plaintext)
|
||||
msg := make([]byte, 0, 8*1024*1024) // use 8MiB for now
|
||||
n, err := Encrypt(k, msg, tv.plaintext)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
msg = msg[:n]
|
||||
|
||||
// decrypt message
|
||||
_, err = Decrypt(r.master, []byte{}, msg)
|
||||
_, err = Decrypt(k, []byte{}, msg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -123,12 +121,22 @@ func TestCrypto(t *testing.T) {
|
|||
// change mac, this must fail
|
||||
msg[len(msg)-8] ^= 0x23
|
||||
|
||||
if _, err = Decrypt(r.master, []byte{}, msg); err != ErrUnauthenticated {
|
||||
t.Fatal("wrong HMAC value not detected")
|
||||
if _, err = Decrypt(k, []byte{}, msg); err != ErrUnauthenticated {
|
||||
t.Fatal("wrong MAC value not detected")
|
||||
}
|
||||
|
||||
// reset mac
|
||||
msg[len(msg)-8] ^= 0x23
|
||||
|
||||
// tamper with message, this must fail
|
||||
msg[16+5] ^= 0x85
|
||||
|
||||
if _, err = Decrypt(k, []byte{}, msg); err != ErrUnauthenticated {
|
||||
t.Fatal("tampered message not detected")
|
||||
}
|
||||
|
||||
// test decryption
|
||||
p, err := Decrypt(r.master, []byte{}, tv.ciphertext)
|
||||
p, err := Decrypt(k, []byte{}, tv.ciphertext)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
|
@ -1,7 +1,8 @@
|
|||
package restic_test
|
||||
package crypto_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
@ -9,13 +10,14 @@ import (
|
|||
|
||||
"github.com/restic/restic"
|
||||
"github.com/restic/restic/chunker"
|
||||
"github.com/restic/restic/crypto"
|
||||
. "github.com/restic/restic/test"
|
||||
)
|
||||
|
||||
var testLargeCrypto = flag.Bool("test.largecrypto", false, "also test crypto functions with large payloads")
|
||||
|
||||
func TestEncryptDecrypt(t *testing.T) {
|
||||
s := setupBackend(t)
|
||||
defer teardownBackend(t, s)
|
||||
k := setupKey(t, s, testPassword)
|
||||
k := crypto.GenerateRandomKeys()
|
||||
|
||||
tests := []int{5, 23, 2<<18 + 23, 1 << 20}
|
||||
if *testLargeCrypto {
|
||||
|
@ -24,14 +26,14 @@ func TestEncryptDecrypt(t *testing.T) {
|
|||
|
||||
for _, size := range tests {
|
||||
data := make([]byte, size)
|
||||
_, err := io.ReadFull(randomReader(42, size), data)
|
||||
_, err := io.ReadFull(RandomReader(42, size), data)
|
||||
OK(t, err)
|
||||
|
||||
ciphertext := restic.GetChunkBuf("TestEncryptDecrypt")
|
||||
n, err := k.Encrypt(ciphertext, data)
|
||||
n, err := crypto.Encrypt(k, ciphertext, data)
|
||||
OK(t, err)
|
||||
|
||||
plaintext, err := k.Decrypt(nil, ciphertext[:n])
|
||||
plaintext, err := crypto.Decrypt(k, nil, ciphertext[:n])
|
||||
OK(t, err)
|
||||
|
||||
restic.FreeChunkBuf("TestEncryptDecrypt", ciphertext)
|
||||
|
@ -41,9 +43,7 @@ func TestEncryptDecrypt(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestSmallBuffer(t *testing.T) {
|
||||
s := setupBackend(t)
|
||||
defer teardownBackend(t, s)
|
||||
k := setupKey(t, s, testPassword)
|
||||
k := crypto.GenerateRandomKeys()
|
||||
|
||||
size := 600
|
||||
data := make([]byte, size)
|
||||
|
@ -54,9 +54,9 @@ func TestSmallBuffer(t *testing.T) {
|
|||
OK(t, err)
|
||||
|
||||
ciphertext := make([]byte, size/2)
|
||||
_, err = k.Encrypt(ciphertext, data)
|
||||
_, err = crypto.Encrypt(k, ciphertext, data)
|
||||
// this must throw an error, since the target slice is too small
|
||||
Assert(t, err != nil && err == restic.ErrBufferTooSmall,
|
||||
Assert(t, err != nil && err == crypto.ErrBufferTooSmall,
|
||||
"expected restic.ErrBufferTooSmall, got %#v", err)
|
||||
}
|
||||
|
||||
|
@ -65,9 +65,7 @@ func TestLargeEncrypt(t *testing.T) {
|
|||
t.SkipNow()
|
||||
}
|
||||
|
||||
s := setupBackend(t)
|
||||
defer teardownBackend(t, s)
|
||||
k := setupKey(t, s, testPassword)
|
||||
k := crypto.GenerateRandomKeys()
|
||||
|
||||
for _, size := range []int{chunker.MaxSize, chunker.MaxSize + 1, chunker.MaxSize + 1<<20} {
|
||||
data := make([]byte, size)
|
||||
|
@ -77,11 +75,11 @@ func TestLargeEncrypt(t *testing.T) {
|
|||
_, err = io.ReadFull(f, data)
|
||||
OK(t, err)
|
||||
|
||||
ciphertext := make([]byte, size+restic.CiphertextExtension)
|
||||
n, err := k.Encrypt(ciphertext, data)
|
||||
ciphertext := make([]byte, size+crypto.CiphertextExtension)
|
||||
n, err := crypto.Encrypt(k, ciphertext, data)
|
||||
OK(t, err)
|
||||
|
||||
plaintext, err := k.Decrypt([]byte{}, ciphertext[:n])
|
||||
plaintext, err := crypto.Decrypt(k, []byte{}, ciphertext[:n])
|
||||
OK(t, err)
|
||||
|
||||
Equals(t, plaintext, data)
|
||||
|
@ -90,18 +88,16 @@ func TestLargeEncrypt(t *testing.T) {
|
|||
|
||||
func BenchmarkEncryptWriter(b *testing.B) {
|
||||
size := 8 << 20 // 8MiB
|
||||
rd := randomReader(23, size)
|
||||
rd := RandomReader(23, size)
|
||||
|
||||
be := setupBackend(b)
|
||||
defer teardownBackend(b, be)
|
||||
k := setupKey(b, be, testPassword)
|
||||
k := crypto.GenerateRandomKeys()
|
||||
|
||||
b.ResetTimer()
|
||||
b.SetBytes(int64(size))
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
rd.Seek(0, 0)
|
||||
wr := k.EncryptTo(ioutil.Discard)
|
||||
wr := crypto.EncryptTo(k, ioutil.Discard)
|
||||
_, err := io.Copy(wr, rd)
|
||||
OK(b, err)
|
||||
OK(b, wr.Close())
|
||||
|
@ -112,31 +108,25 @@ func BenchmarkEncrypt(b *testing.B) {
|
|||
size := 8 << 20 // 8MiB
|
||||
data := make([]byte, size)
|
||||
|
||||
be := setupBackend(b)
|
||||
defer teardownBackend(b, be)
|
||||
k := setupKey(b, be, testPassword)
|
||||
|
||||
buf := make([]byte, len(data)+restic.CiphertextExtension)
|
||||
k := crypto.GenerateRandomKeys()
|
||||
buf := make([]byte, len(data)+crypto.CiphertextExtension)
|
||||
|
||||
b.ResetTimer()
|
||||
b.SetBytes(int64(size))
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := k.Encrypt(buf, data)
|
||||
_, err := crypto.Encrypt(k, buf, data)
|
||||
OK(b, err)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDecryptReader(b *testing.B) {
|
||||
be := setupBackend(b)
|
||||
defer teardownBackend(b, be)
|
||||
k := setupKey(b, be, testPassword)
|
||||
|
||||
size := 8 << 20 // 8MiB
|
||||
buf := get_random(23, size)
|
||||
buf := Random(23, size)
|
||||
k := crypto.GenerateRandomKeys()
|
||||
|
||||
ciphertext := make([]byte, len(buf)+restic.CiphertextExtension)
|
||||
_, err := k.Encrypt(ciphertext, buf)
|
||||
ciphertext := make([]byte, len(buf)+crypto.CiphertextExtension)
|
||||
_, err := crypto.Encrypt(k, ciphertext, buf)
|
||||
OK(b, err)
|
||||
|
||||
rd := bytes.NewReader(ciphertext)
|
||||
|
@ -146,7 +136,7 @@ func BenchmarkDecryptReader(b *testing.B) {
|
|||
|
||||
for i := 0; i < b.N; i++ {
|
||||
rd.Seek(0, 0)
|
||||
decRd, err := k.DecryptFrom(rd)
|
||||
decRd, err := crypto.DecryptFrom(k, rd)
|
||||
OK(b, err)
|
||||
|
||||
_, err = io.Copy(ioutil.Discard, decRd)
|
||||
|
@ -155,12 +145,10 @@ func BenchmarkDecryptReader(b *testing.B) {
|
|||
}
|
||||
|
||||
func BenchmarkEncryptDecryptReader(b *testing.B) {
|
||||
be := setupBackend(b)
|
||||
defer teardownBackend(b, be)
|
||||
k := setupKey(b, be, testPassword)
|
||||
k := crypto.GenerateRandomKeys()
|
||||
|
||||
size := 8 << 20 // 8MiB
|
||||
rd := randomReader(23, size)
|
||||
rd := RandomReader(23, size)
|
||||
|
||||
b.ResetTimer()
|
||||
b.SetBytes(int64(size))
|
||||
|
@ -169,12 +157,12 @@ func BenchmarkEncryptDecryptReader(b *testing.B) {
|
|||
for i := 0; i < b.N; i++ {
|
||||
rd.Seek(0, 0)
|
||||
buf.Reset()
|
||||
wr := k.EncryptTo(buf)
|
||||
wr := crypto.EncryptTo(k, buf)
|
||||
_, err := io.Copy(wr, rd)
|
||||
OK(b, err)
|
||||
OK(b, wr.Close())
|
||||
|
||||
r, err := k.DecryptFrom(buf)
|
||||
r, err := crypto.DecryptFrom(k, buf)
|
||||
OK(b, err)
|
||||
|
||||
_, err = io.Copy(ioutil.Discard, r)
|
||||
|
@ -188,31 +176,27 @@ func BenchmarkDecrypt(b *testing.B) {
|
|||
size := 8 << 20 // 8MiB
|
||||
data := make([]byte, size)
|
||||
|
||||
s := setupBackend(b)
|
||||
defer teardownBackend(b, s)
|
||||
k := setupKey(b, s, testPassword)
|
||||
k := crypto.GenerateRandomKeys()
|
||||
|
||||
ciphertext := restic.GetChunkBuf("BenchmarkDecrypt")
|
||||
defer restic.FreeChunkBuf("BenchmarkDecrypt", ciphertext)
|
||||
plaintext := restic.GetChunkBuf("BenchmarkDecrypt")
|
||||
defer restic.FreeChunkBuf("BenchmarkDecrypt", plaintext)
|
||||
|
||||
n, err := k.Encrypt(ciphertext, data)
|
||||
n, err := crypto.Encrypt(k, ciphertext, data)
|
||||
OK(b, err)
|
||||
|
||||
b.ResetTimer()
|
||||
b.SetBytes(int64(size))
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
plaintext, err = k.Decrypt(plaintext, ciphertext[:n])
|
||||
plaintext, err = crypto.Decrypt(k, plaintext, ciphertext[:n])
|
||||
OK(b, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncryptStreamWriter(t *testing.T) {
|
||||
s := setupBackend(t)
|
||||
defer teardownBackend(t, s)
|
||||
k := setupKey(t, s, testPassword)
|
||||
k := crypto.GenerateRandomKeys()
|
||||
|
||||
tests := []int{5, 23, 2<<18 + 23, 1 << 20}
|
||||
if *testLargeCrypto {
|
||||
|
@ -221,23 +205,23 @@ func TestEncryptStreamWriter(t *testing.T) {
|
|||
|
||||
for _, size := range tests {
|
||||
data := make([]byte, size)
|
||||
_, err := io.ReadFull(randomReader(42, size), data)
|
||||
_, err := io.ReadFull(RandomReader(42, size), data)
|
||||
OK(t, err)
|
||||
|
||||
ciphertext := bytes.NewBuffer(nil)
|
||||
wr := k.EncryptTo(ciphertext)
|
||||
wr := crypto.EncryptTo(k, ciphertext)
|
||||
|
||||
_, err = io.Copy(wr, bytes.NewReader(data))
|
||||
OK(t, err)
|
||||
OK(t, wr.Close())
|
||||
|
||||
l := len(data) + restic.CiphertextExtension
|
||||
l := len(data) + crypto.CiphertextExtension
|
||||
Assert(t, len(ciphertext.Bytes()) == l,
|
||||
"wrong ciphertext length: expected %d, got %d",
|
||||
l, len(ciphertext.Bytes()))
|
||||
|
||||
// decrypt with default function
|
||||
plaintext, err := k.Decrypt([]byte{}, ciphertext.Bytes())
|
||||
plaintext, err := crypto.Decrypt(k, []byte{}, ciphertext.Bytes())
|
||||
OK(t, err)
|
||||
Assert(t, bytes.Equal(data, plaintext),
|
||||
"wrong plaintext after decryption: expected %02x, got %02x",
|
||||
|
@ -246,9 +230,7 @@ func TestEncryptStreamWriter(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestDecryptStreamReader(t *testing.T) {
|
||||
s := setupBackend(t)
|
||||
defer teardownBackend(t, s)
|
||||
k := setupKey(t, s, testPassword)
|
||||
k := crypto.GenerateRandomKeys()
|
||||
|
||||
tests := []int{5, 23, 2<<18 + 23, 1 << 20}
|
||||
if *testLargeCrypto {
|
||||
|
@ -257,19 +239,19 @@ func TestDecryptStreamReader(t *testing.T) {
|
|||
|
||||
for _, size := range tests {
|
||||
data := make([]byte, size)
|
||||
_, err := io.ReadFull(randomReader(42, size), data)
|
||||
_, err := io.ReadFull(RandomReader(42, size), data)
|
||||
OK(t, err)
|
||||
|
||||
ciphertext := make([]byte, size+restic.CiphertextExtension)
|
||||
ciphertext := make([]byte, size+crypto.CiphertextExtension)
|
||||
|
||||
// encrypt with default function
|
||||
n, err := k.Encrypt(ciphertext, data)
|
||||
n, err := crypto.Encrypt(k, ciphertext, data)
|
||||
OK(t, err)
|
||||
Assert(t, n == len(data)+restic.CiphertextExtension,
|
||||
Assert(t, n == len(data)+crypto.CiphertextExtension,
|
||||
"wrong number of bytes returned after encryption: expected %d, got %d",
|
||||
len(data)+restic.CiphertextExtension, n)
|
||||
len(data)+crypto.CiphertextExtension, n)
|
||||
|
||||
rd, err := k.DecryptFrom(bytes.NewReader(ciphertext))
|
||||
rd, err := crypto.DecryptFrom(k, bytes.NewReader(ciphertext))
|
||||
OK(t, err)
|
||||
|
||||
plaintext, err := ioutil.ReadAll(rd)
|
||||
|
@ -282,9 +264,7 @@ func TestDecryptStreamReader(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestEncryptWriter(t *testing.T) {
|
||||
s := setupBackend(t)
|
||||
defer teardownBackend(t, s)
|
||||
k := setupKey(t, s, testPassword)
|
||||
k := crypto.GenerateRandomKeys()
|
||||
|
||||
tests := []int{5, 23, 2<<18 + 23, 1 << 20}
|
||||
if *testLargeCrypto {
|
||||
|
@ -293,11 +273,11 @@ func TestEncryptWriter(t *testing.T) {
|
|||
|
||||
for _, size := range tests {
|
||||
data := make([]byte, size)
|
||||
_, err := io.ReadFull(randomReader(42, size), data)
|
||||
_, err := io.ReadFull(RandomReader(42, size), data)
|
||||
OK(t, err)
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
wr := k.EncryptTo(buf)
|
||||
wr := crypto.EncryptTo(k, buf)
|
||||
|
||||
_, err = io.Copy(wr, bytes.NewReader(data))
|
||||
OK(t, err)
|
||||
|
@ -305,13 +285,13 @@ func TestEncryptWriter(t *testing.T) {
|
|||
|
||||
ciphertext := buf.Bytes()
|
||||
|
||||
l := len(data) + restic.CiphertextExtension
|
||||
l := len(data) + crypto.CiphertextExtension
|
||||
Assert(t, len(ciphertext) == l,
|
||||
"wrong ciphertext length: expected %d, got %d",
|
||||
l, len(ciphertext))
|
||||
|
||||
// decrypt with default function
|
||||
plaintext, err := k.Decrypt([]byte{}, ciphertext)
|
||||
plaintext, err := crypto.Decrypt(k, []byte{}, ciphertext)
|
||||
OK(t, err)
|
||||
Assert(t, bytes.Equal(data, plaintext),
|
||||
"wrong plaintext after decryption: expected %02x, got %02x",
|
90
key.go
90
key.go
|
@ -13,24 +13,13 @@ import (
|
|||
|
||||
"github.com/restic/restic/backend"
|
||||
"github.com/restic/restic/chunker"
|
||||
"github.com/restic/restic/crypto"
|
||||
"github.com/restic/restic/debug"
|
||||
|
||||
"golang.org/x/crypto/poly1305"
|
||||
)
|
||||
|
||||
// max size is 8MiB, defined in chunker
|
||||
const macSize = poly1305.TagSize // Poly1305 size is 16 byte
|
||||
const maxCiphertextSize = ivSize + chunker.MaxSize + macSize
|
||||
const CiphertextExtension = ivSize + macSize
|
||||
|
||||
var (
|
||||
// ErrUnauthenticated is returned when ciphertext verification has failed.
|
||||
ErrUnauthenticated = errors.New("ciphertext verification failed")
|
||||
// ErrNoKeyFound is returned when no key for the repository could be decrypted.
|
||||
ErrNoKeyFound = errors.New("no key could be found")
|
||||
// ErrBufferTooSmall is returned when the destination slice is too small
|
||||
// for the ciphertext.
|
||||
ErrBufferTooSmall = errors.New("destination buffer too small")
|
||||
)
|
||||
|
||||
// TODO: figure out scrypt values on the fly depending on the current
|
||||
|
@ -55,22 +44,12 @@ type Key struct {
|
|||
Salt []byte `json:"salt"`
|
||||
Data []byte `json:"data"`
|
||||
|
||||
user *MasterKeys
|
||||
master *MasterKeys
|
||||
user *crypto.MasterKeys
|
||||
master *crypto.MasterKeys
|
||||
|
||||
name string
|
||||
}
|
||||
|
||||
// MasterKeys holds signing and encryption keys for a repository. It is stored
|
||||
// encrypted and signed as a JSON data structure in the Data field of the Key
|
||||
// structure. For the master key, the secret random polynomial used for content
|
||||
// defined chunking is included.
|
||||
type MasterKeys struct {
|
||||
Sign MACKey `json:"sign"`
|
||||
Encrypt AESKey `json:"encrypt"`
|
||||
ChunkerPolynomial chunker.Pol `json:"chunker_polynomial,omitempty"`
|
||||
}
|
||||
|
||||
// CreateKey initializes a master key in the given backend and encrypts it with
|
||||
// the password.
|
||||
func CreateKey(s Server, password string) (*Key, error) {
|
||||
|
@ -90,19 +69,19 @@ func OpenKey(s Server, name string, password string) (*Key, error) {
|
|||
}
|
||||
|
||||
// derive user key
|
||||
k.user, err = kdf(k, password)
|
||||
k.user, err = crypto.KDF(k.N, k.R, k.P, k.Salt, password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// decrypt master keys
|
||||
buf, err := k.DecryptUser([]byte{}, k.Data)
|
||||
buf, err := crypto.Decrypt(k.user, []byte{}, k.Data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// restore json
|
||||
k.master = &MasterKeys{}
|
||||
k.master = &crypto.MasterKeys{}
|
||||
err = json.Unmarshal(buf, k.master)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -190,14 +169,14 @@ func AddKey(s Server, password string, template *Key) (*Key, error) {
|
|||
}
|
||||
|
||||
// call KDF to derive user key
|
||||
newkey.user, err = kdf(newkey, password)
|
||||
newkey.user, err = crypto.KDF(newkey.N, newkey.R, newkey.P, newkey.Salt, password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if template == nil {
|
||||
// generate new random master keys
|
||||
newkey.master = generateRandomKeys()
|
||||
newkey.master = crypto.GenerateRandomKeys()
|
||||
// generate random polynomial for cdc
|
||||
p, err := chunker.RandomPolynomial()
|
||||
if err != nil {
|
||||
|
@ -218,7 +197,7 @@ func AddKey(s Server, password string, template *Key) (*Key, error) {
|
|||
}
|
||||
|
||||
newkey.Data = GetChunkBuf("key")
|
||||
n, err = newkey.EncryptUser(newkey.Data, buf)
|
||||
n, err = crypto.Encrypt(newkey.user, newkey.Data, buf)
|
||||
newkey.Data = newkey.Data[:n]
|
||||
|
||||
// dump as json
|
||||
|
@ -254,52 +233,23 @@ func AddKey(s Server, password string, template *Key) (*Key, error) {
|
|||
return newkey, nil
|
||||
}
|
||||
|
||||
func (k *Key) newIV(buf []byte) error {
|
||||
_, err := io.ReadFull(rand.Reader, buf[:ivSize])
|
||||
buf = buf[:ivSize]
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// EncryptUser encrypts and signs data with the user key. Stored in ciphertext
|
||||
// is IV || Ciphertext || MAC.
|
||||
func (k *Key) EncryptUser(ciphertext, plaintext []byte) (int, error) {
|
||||
return Encrypt(k.user, ciphertext, plaintext)
|
||||
}
|
||||
|
||||
// Encrypt encrypts and signs data with the master key. Stored in ciphertext is
|
||||
// IV || Ciphertext || MAC. Returns the ciphertext length.
|
||||
func (k *Key) Encrypt(ciphertext, plaintext []byte) (int, error) {
|
||||
return Encrypt(k.master, ciphertext, plaintext)
|
||||
return crypto.Encrypt(k.master, ciphertext, plaintext)
|
||||
}
|
||||
|
||||
// EncryptTo encrypts and signs data with the master key. The returned
|
||||
// io.Writer writes IV || Ciphertext || HMAC. For the hash function, SHA256 is
|
||||
// used.
|
||||
func (k *Key) EncryptTo(wr io.Writer) io.WriteCloser {
|
||||
return EncryptTo(k.master, wr)
|
||||
}
|
||||
|
||||
// EncryptUserTo encrypts and signs data with the user key. The returned
|
||||
// io.Writer writes IV || Ciphertext || HMAC. For the hash function, SHA256 is
|
||||
// used.
|
||||
func (k *Key) EncryptUserTo(wr io.Writer) io.WriteCloser {
|
||||
return EncryptTo(k.user, wr)
|
||||
return crypto.EncryptTo(k.master, wr)
|
||||
}
|
||||
|
||||
// Decrypt verifes and decrypts the ciphertext with the master key. Ciphertext
|
||||
// must be in the form IV || Ciphertext || MAC.
|
||||
func (k *Key) Decrypt(plaintext, ciphertext []byte) ([]byte, error) {
|
||||
return Decrypt(k.master, plaintext, ciphertext)
|
||||
}
|
||||
|
||||
// DecryptUser verifes and decrypts the ciphertext with the user key. Ciphertext
|
||||
// must be in the form IV || Ciphertext || MAC.
|
||||
func (k *Key) DecryptUser(plaintext, ciphertext []byte) ([]byte, error) {
|
||||
return Decrypt(k.user, plaintext, ciphertext)
|
||||
return crypto.Decrypt(k.master, plaintext, ciphertext)
|
||||
}
|
||||
|
||||
// DecryptFrom verifies and decrypts the ciphertext read from rd and makes it
|
||||
|
@ -309,27 +259,17 @@ func (k *Key) DecryptUser(plaintext, ciphertext []byte) ([]byte, error) {
|
|||
// afterwards. If an MAC verification failure is observed, it is returned
|
||||
// immediately.
|
||||
func (k *Key) DecryptFrom(rd io.Reader) (io.ReadCloser, error) {
|
||||
return DecryptFrom(k.master, rd)
|
||||
}
|
||||
|
||||
// DecryptFrom verifies and decrypts the ciphertext read from rd with the user
|
||||
// key 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 an MAC verification failure is observed, it is returned
|
||||
// immediately.
|
||||
func (k *Key) DecryptUserFrom(rd io.Reader) (io.ReadCloser, error) {
|
||||
return DecryptFrom(k.user, rd)
|
||||
return crypto.DecryptFrom(k.master, rd)
|
||||
}
|
||||
|
||||
// Master() returns the master keys for this repository. Only included for
|
||||
// debug purposes.
|
||||
func (k *Key) Master() *MasterKeys {
|
||||
func (k *Key) Master() *crypto.MasterKeys {
|
||||
return k.master
|
||||
}
|
||||
|
||||
// User() returns the user keys for this key. Only included for debug purposes.
|
||||
func (k *Key) User() *MasterKeys {
|
||||
func (k *Key) User() *crypto.MasterKeys {
|
||||
return k.user
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,6 @@ import (
|
|||
|
||||
var testPassword = "foobar"
|
||||
var testCleanup = flag.Bool("test.cleanup", true, "clean up after running tests (remove local backend directory with all content)")
|
||||
var testLargeCrypto = flag.Bool("test.largecrypto", false, "also test crypto functions with large payloads")
|
||||
var testTempDir = flag.String("test.tempdir", "", "use this directory for temporary storage (default: system temp dir)")
|
||||
|
||||
func setupBackend(t testing.TB) restic.Server {
|
||||
|
|
3
pools.go
3
pools.go
|
@ -5,6 +5,7 @@ import (
|
|||
"sync"
|
||||
|
||||
"github.com/restic/restic/chunker"
|
||||
"github.com/restic/restic/crypto"
|
||||
"github.com/restic/restic/debug"
|
||||
)
|
||||
|
||||
|
@ -20,6 +21,8 @@ type poolStats struct {
|
|||
max int
|
||||
}
|
||||
|
||||
const maxCiphertextSize = crypto.CiphertextExtension + chunker.MaxSize
|
||||
|
||||
func (s *poolStats) Get(k string) {
|
||||
s.m.Lock()
|
||||
defer s.m.Unlock()
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
|
||||
"github.com/restic/restic/backend"
|
||||
"github.com/restic/restic/chunker"
|
||||
"github.com/restic/restic/crypto"
|
||||
"github.com/restic/restic/debug"
|
||||
)
|
||||
|
||||
|
@ -158,11 +159,11 @@ func (s Server) Save(t backend.Type, data []byte, id backend.ID) (Blob, error) {
|
|||
var ciphertext []byte
|
||||
|
||||
// if the data is small enough, use a slice from the pool
|
||||
if len(data) <= maxCiphertextSize-ivSize-macSize {
|
||||
if len(data) <= maxCiphertextSize-crypto.CiphertextExtension {
|
||||
ciphertext = GetChunkBuf("ch.Save()")
|
||||
defer FreeChunkBuf("ch.Save()", ciphertext)
|
||||
} else {
|
||||
l := len(data) + ivSize + macSize
|
||||
l := len(data) + crypto.CiphertextExtension
|
||||
|
||||
debug.Log("Server.Save", "create large slice of %d bytes for ciphertext", l)
|
||||
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
package test_helper
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
|
@ -45,3 +47,21 @@ func Str2ID(s string) backend.ID {
|
|||
|
||||
return id
|
||||
}
|
||||
|
||||
// Random returns size bytes of pseudo-random data derived from the seed.
|
||||
func Random(seed, count int) []byte {
|
||||
buf := make([]byte, count)
|
||||
|
||||
rnd := rand.New(rand.NewSource(int64(seed)))
|
||||
for i := 0; i < count; i++ {
|
||||
buf[i] = byte(rnd.Uint32())
|
||||
}
|
||||
|
||||
return buf
|
||||
}
|
||||
|
||||
// RandomReader returns a reader that returns size bytes of pseudo-random data
|
||||
// derived from the seed.
|
||||
func RandomReader(seed, size int) *bytes.Reader {
|
||||
return bytes.NewReader(Random(seed, size))
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue