From 3a2525809c9e07b301709a618c00f1ed062c3b77 Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Sun, 12 Apr 2015 09:36:14 +0200 Subject: [PATCH] Reorganize crypto code Move all crypto functions to package "crypto", move random generators for tests into helper package. --- archiver_test.go | 20 +-- crypto/buffer_pool.go | 19 +++ crypto.go => crypto/crypto.go | 45 +++++-- .../crypto_int_test.go | 28 ++-- crypto_test.go => crypto/crypto_test.go | 122 ++++++++---------- key.go | 90 +++---------- key_test.go | 1 - pools.go | 3 + server.go | 5 +- test/helpers.go | 20 +++ 10 files changed, 166 insertions(+), 187 deletions(-) create mode 100644 crypto/buffer_pool.go rename crypto.go => crypto/crypto.go (87%) rename crypto_int_test.go => crypto/crypto_int_test.go (87%) rename crypto_test.go => crypto/crypto_test.go (64%) diff --git a/archiver_test.go b/archiver_test.go index da53025a6..370c0590b 100644 --- a/archiver_test.go +++ b/archiver_test.go @@ -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") diff --git a/crypto/buffer_pool.go b/crypto/buffer_pool.go new file mode 100644 index 000000000..e9b86e94f --- /dev/null +++ b/crypto/buffer_pool.go @@ -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) +} diff --git a/crypto.go b/crypto/crypto.go similarity index 87% rename from crypto.go rename to crypto/crypto.go index 743e34a05..182e0a33a 100644 --- a/crypto.go +++ b/crypto/crypto.go @@ -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) diff --git a/crypto_int_test.go b/crypto/crypto_int_test.go similarity index 87% rename from crypto_int_test.go rename to crypto/crypto_int_test.go index e1d4d76c4..d626844ae 100644 --- a/crypto_int_test.go +++ b/crypto/crypto_int_test.go @@ -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) } diff --git a/crypto_test.go b/crypto/crypto_test.go similarity index 64% rename from crypto_test.go rename to crypto/crypto_test.go index 8586dbe7d..7da3e57d7 100644 --- a/crypto_test.go +++ b/crypto/crypto_test.go @@ -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", diff --git a/key.go b/key.go index 7f8de363f..7a42088ef 100644 --- a/key.go +++ b/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 } diff --git a/key_test.go b/key_test.go index 7072be028..1048a0cf8 100644 --- a/key_test.go +++ b/key_test.go @@ -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 { diff --git a/pools.go b/pools.go index 10e573a88..cc7e02a20 100644 --- a/pools.go +++ b/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() diff --git a/server.go b/server.go index e4ebbeea6..6891907b7 100644 --- a/server.go +++ b/server.go @@ -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) diff --git a/test/helpers.go b/test/helpers.go index 3394b0818..d7fe0eb16 100644 --- a/test/helpers.go +++ b/test/helpers.go @@ -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)) +}