2
2
mirror of https://github.com/octoleo/restic.git synced 2024-11-15 01:34:06 +00:00
restic/internal/repository/key.go

313 lines
7.3 KiB
Go
Raw Normal View History

package repository
2014-09-23 20:39:12 +00:00
import (
2017-06-04 09:16:55 +00:00
"context"
2014-09-23 20:39:12 +00:00
"encoding/json"
"fmt"
"os"
"os/user"
"time"
2017-07-23 12:21:03 +00:00
"github.com/restic/restic/internal/errors"
2017-07-24 15:42:25 +00:00
"github.com/restic/restic/internal/restic"
2017-07-23 12:21:03 +00:00
"github.com/restic/restic/internal/backend"
"github.com/restic/restic/internal/crypto"
"github.com/restic/restic/internal/debug"
2014-09-23 20:39:12 +00:00
)
var (
2014-11-15 16:17:24 +00:00
// ErrNoKeyFound is returned when no key for the repository could be decrypted.
ErrNoKeyFound = errors.New("wrong password or no key found")
// ErrMaxKeysReached is returned when the maximum number of keys was checked and no key could be found.
ErrMaxKeysReached = errors.New("maximum number of keys reached")
2014-09-23 20:39:12 +00:00
)
2014-11-15 16:17:24 +00:00
// Key represents an encrypted master key for a repository.
2014-09-23 20:39:12 +00:00
type Key struct {
Created time.Time `json:"created"`
Username string `json:"username"`
Hostname string `json:"hostname"`
KDF string `json:"kdf"`
N int `json:"N"`
R int `json:"r"`
P int `json:"p"`
Salt []byte `json:"salt"`
Data []byte `json:"data"`
2015-04-12 07:41:47 +00:00
user *crypto.Key
master *crypto.Key
2014-11-25 22:18:02 +00:00
2022-10-15 14:01:38 +00:00
id restic.ID
2014-09-23 20:39:12 +00:00
}
2017-10-28 08:28:29 +00:00
// Params tracks the parameters used for the KDF. If not set, it will be
// calibrated on the first run of AddKey().
2017-10-28 08:28:29 +00:00
var Params *crypto.Params
const (
2016-08-21 11:13:05 +00:00
// KDFTimeout specifies the maximum runtime for the KDF.
KDFTimeout = 500 * time.Millisecond
// KDFMemory limits the memory the KDF is allowed to use.
KDFMemory = 60
)
2015-05-03 15:37:12 +00:00
// createMasterKey creates a new master key in the given backend and encrypts
// it with the password.
func createMasterKey(ctx context.Context, s *Repository, password string) (*Key, error) {
return AddKey(ctx, s, password, "", "", nil)
2014-09-23 20:39:12 +00:00
}
2015-03-28 10:50:23 +00:00
// OpenKey tries do decrypt the key specified by name with the given password.
2022-10-15 14:01:38 +00:00
func OpenKey(ctx context.Context, s *Repository, id restic.ID, password string) (*Key, error) {
k, err := LoadKey(ctx, s, id)
2014-09-23 20:39:12 +00:00
if err != nil {
2022-10-15 14:01:38 +00:00
debug.Log("LoadKey(%v) returned error %v", id.String(), err)
2014-09-23 20:39:12 +00:00
return nil, err
}
// check KDF
if k.KDF != "scrypt" {
return nil, errors.New("only supported KDF is scrypt()")
}
// derive user key
2017-10-28 08:28:29 +00:00
params := crypto.Params{
N: k.N,
R: k.R,
P: k.P,
}
k.user, err = crypto.KDF(params, k.Salt, password)
2014-09-23 20:39:12 +00:00
if err != nil {
2016-08-29 20:16:58 +00:00
return nil, errors.Wrap(err, "crypto.KDF")
2014-09-23 20:39:12 +00:00
}
// decrypt master keys
2017-10-29 10:33:57 +00:00
nonce, ciphertext := k.Data[:k.user.NonceSize()], k.Data[k.user.NonceSize():]
2017-11-01 08:58:59 +00:00
buf, err := k.user.Open(nil, nonce, ciphertext, nil)
2014-09-23 20:39:12 +00:00
if err != nil {
return nil, err
}
// restore json
2015-04-12 07:41:47 +00:00
k.master = &crypto.Key{}
2014-09-23 20:39:12 +00:00
err = json.Unmarshal(buf, k.master)
if err != nil {
2016-09-27 20:35:08 +00:00
debug.Log("Unmarshal() returned error %v", err)
2016-08-29 20:16:58 +00:00
return nil, errors.Wrap(err, "Unmarshal")
2014-09-23 20:39:12 +00:00
}
2022-10-15 14:01:38 +00:00
k.id = id
2014-09-23 20:39:12 +00:00
if !k.Valid() {
return nil, errors.New("Invalid key for repository")
}
2014-09-23 20:39:12 +00:00
return k, nil
}
// SearchKey tries to decrypt at most maxKeys keys in the backend with the
// given password. If none could be found, ErrNoKeyFound is returned. When
// maxKeys is reached, ErrMaxKeysReached is returned. When setting maxKeys to
// zero, all keys in the repo are checked.
2018-11-25 14:10:45 +00:00
func SearchKey(ctx context.Context, s *Repository, password string, maxKeys int, keyHint string) (k *Key, err error) {
checked := 0
2018-11-25 14:10:45 +00:00
if len(keyHint) > 0 {
id, err := restic.Find(ctx, s, restic.KeyFile, keyHint)
2018-11-25 14:10:45 +00:00
if err == nil {
2022-10-15 14:01:38 +00:00
key, err := OpenKey(ctx, s, id, password)
2018-11-25 14:10:45 +00:00
if err == nil {
debug.Log("successfully opened hinted key %v", id)
return key, nil
}
debug.Log("could not open hinted key %v", id)
} else {
debug.Log("Could not find hinted key %v", keyHint)
}
}
listCtx, cancel := context.WithCancel(ctx)
defer cancel()
// try at most maxKeys keys in repo
2024-02-10 21:58:10 +00:00
err = s.List(listCtx, restic.KeyFile, func(id restic.ID, _ int64) error {
2022-05-30 07:54:32 +00:00
checked++
if maxKeys > 0 && checked > maxKeys {
return ErrMaxKeysReached
}
2022-10-15 14:01:38 +00:00
debug.Log("trying key %q", id.String())
key, err := OpenKey(ctx, s, id, password)
if err != nil {
2022-10-15 14:01:38 +00:00
debug.Log("key %v returned error %v", id.String(), err)
// ErrUnauthenticated means the password is wrong, try the next key
if errors.Is(err, crypto.ErrUnauthenticated) {
return nil
}
return err
2014-09-23 20:39:12 +00:00
}
2022-10-15 14:01:38 +00:00
debug.Log("successfully opened key %v", id.String())
k = key
cancel()
return nil
})
if err == context.Canceled {
err = nil
}
if err != nil {
return nil, err
2014-09-23 20:39:12 +00:00
}
if k == nil {
return nil, ErrNoKeyFound
}
return k, nil
2014-09-23 20:39:12 +00:00
}
2015-02-17 22:05:23 +00:00
// LoadKey loads a key from the backend.
2022-10-15 14:01:38 +00:00
func LoadKey(ctx context.Context, s *Repository, id restic.ID) (k *Key, err error) {
h := backend.Handle{Type: restic.KeyFile, Name: id.String()}
data, err := backend.LoadAll(ctx, nil, s.be, h)
2015-02-17 22:05:23 +00:00
if err != nil {
return nil, err
}
k = &Key{}
err = json.Unmarshal(data, k)
2015-02-17 22:05:23 +00:00
if err != nil {
2016-08-29 20:16:58 +00:00
return nil, errors.Wrap(err, "Unmarshal")
2015-02-17 22:05:23 +00:00
}
2015-11-18 19:33:20 +00:00
return k, nil
2015-02-17 22:05:23 +00:00
}
2014-11-25 22:07:00 +00:00
// AddKey adds a new key to an already existing repository.
func AddKey(ctx context.Context, s *Repository, password, username, hostname string, template *crypto.Key) (*Key, error) {
// make sure we have valid KDF parameters
2017-10-28 08:28:29 +00:00
if Params == nil {
p, err := crypto.Calibrate(KDFTimeout, KDFMemory)
if err != nil {
2016-08-29 20:16:58 +00:00
return nil, errors.Wrap(err, "Calibrate")
}
2017-10-28 08:28:29 +00:00
Params = &p
2016-09-27 20:35:08 +00:00
debug.Log("calibrated KDF parameters are %v", p)
}
2014-11-25 22:07:00 +00:00
// fill meta data about key
newkey := &Key{
Created: time.Now(),
Username: username,
Hostname: hostname,
KDF: "scrypt",
N: Params.N,
R: Params.R,
P: Params.P,
2014-11-25 22:07:00 +00:00
}
if newkey.Hostname == "" {
newkey.Hostname, _ = os.Hostname()
2014-11-25 22:07:00 +00:00
}
if newkey.Username == "" {
usr, err := user.Current()
if err == nil {
newkey.Username = usr.Username
}
2014-11-25 22:07:00 +00:00
}
// generate random salt
var err error
newkey.Salt, err = crypto.NewSalt()
if err != nil {
panic("unable to read enough random bytes for salt: " + err.Error())
2014-11-25 22:07:00 +00:00
}
Refactor crypto layer, switch HMAC for Poyl1305-AES HMAC-SHA256 calls SHA256() twice which is very expensive. Therefore, this commit uses Poly1305-AES instead of HMAC-SHA256. benchcmp: benchmark old ns/op new ns/op delta BenchmarkChunkEncrypt 261033772 195114818 -25.25% BenchmarkChunkEncryptParallel 260973195 195787368 -24.98% BenchmarkArchiveDirectory 1050500651 1002615884 -4.56% BenchmarkPreload 23544286 24994508 +6.16% BenchmarkLoadTree 350065 427665 +22.17% BenchmarkEncryptWriter 87789753 31069126 -64.61% BenchmarkEncrypt 88283197 38259043 -56.66% BenchmarkDecryptReader 90478843 40714818 -55.00% BenchmarkEncryptDecryptReader 179917626 81231730 -54.85% BenchmarkDecrypt 87871591 37784207 -57.00% BenchmarkSaveJSON 52481 56861 +8.35% BenchmarkSaveFrom 75404085 51108596 -32.22% BenchmarkLoadJSONID 90545437 82696805 -8.67% benchmark old MB/s new MB/s speedup BenchmarkChunkEncrypt 40.17 53.74 1.34x BenchmarkChunkEncryptParallel 40.18 53.56 1.33x BenchmarkEncryptWriter 95.55 270.00 2.83x BenchmarkEncrypt 95.02 219.26 2.31x BenchmarkDecryptReader 92.71 206.03 2.22x BenchmarkEncryptDecryptReader 46.62 103.27 2.22x BenchmarkDecrypt 95.46 222.01 2.33x BenchmarkSaveFrom 55.62 82.07 1.48x benchmark old allocs new allocs delta BenchmarkChunkEncrypt 112 110 -1.79% BenchmarkChunkEncryptParallel 103 100 -2.91% BenchmarkArchiveDirectory 383704 392083 +2.18% BenchmarkPreload 21765 21874 +0.50% BenchmarkLoadTree 341 436 +27.86% BenchmarkEncryptWriter 20 17 -15.00% BenchmarkEncrypt 14 13 -7.14% BenchmarkDecryptReader 18 15 -16.67% BenchmarkEncryptDecryptReader 46 39 -15.22% BenchmarkDecrypt 16 12 -25.00% BenchmarkSaveJSON 81 86 +6.17% BenchmarkSaveFrom 117 121 +3.42% BenchmarkLoadJSONID 80525 80264 -0.32% benchmark old bytes new bytes delta BenchmarkChunkEncrypt 118956 64697 -45.61% BenchmarkChunkEncryptParallel 118972 64681 -45.63% BenchmarkArchiveDirectory 160236600 177498232 +10.77% BenchmarkPreload 2772488 3302992 +19.13% BenchmarkLoadTree 49102 46484 -5.33% BenchmarkEncryptWriter 28927 8388146 +28897.64% BenchmarkEncrypt 2473 1950 -21.15% BenchmarkDecryptReader 527827 2774 -99.47% BenchmarkEncryptDecryptReader 4100875 1528036 -62.74% BenchmarkDecrypt 2509 2154 -14.15% BenchmarkSaveJSON 4971 5892 +18.53% BenchmarkSaveFrom 40117 31742 -20.88% BenchmarkLoadJSONID 9444217 9442106 -0.02% This closes #102.
2015-03-14 18:53:51 +00:00
// call KDF to derive user key
2017-10-28 08:28:29 +00:00
newkey.user, err = crypto.KDF(*Params, newkey.Salt, password)
2014-11-25 22:07:00 +00:00
if err != nil {
return nil, err
}
if template == nil {
// generate new random master keys
2015-04-30 02:28:34 +00:00
newkey.master = crypto.NewRandomKey()
} else {
// copy master keys from old key
newkey.master = template
}
2014-11-25 22:07:00 +00:00
// encrypt master keys (as json) with user key
buf, err := json.Marshal(newkey.master)
if err != nil {
2016-08-29 20:16:58 +00:00
return nil, errors.Wrap(err, "Marshal")
2014-11-25 22:07:00 +00:00
}
2017-10-29 10:33:57 +00:00
nonce := crypto.NewRandomNonce()
2022-06-12 12:48:30 +00:00
ciphertext := make([]byte, 0, crypto.CiphertextLength(len(buf)))
2017-10-29 10:33:57 +00:00
ciphertext = append(ciphertext, nonce...)
ciphertext = newkey.user.Seal(ciphertext, nonce, buf, nil)
newkey.Data = ciphertext
2014-11-25 22:07:00 +00:00
// dump as json
buf, err = json.Marshal(newkey)
if err != nil {
2016-08-29 20:16:58 +00:00
return nil, errors.Wrap(err, "Marshal")
2014-11-25 22:07:00 +00:00
}
2022-10-15 14:01:38 +00:00
id := restic.Hash(buf)
2014-11-25 22:07:00 +00:00
// store in repository and return
h := backend.Handle{
2016-09-01 19:19:30 +00:00
Type: restic.KeyFile,
2022-10-15 14:01:38 +00:00
Name: id.String(),
}
err = s.be.Save(ctx, h, backend.NewByteReader(buf, s.be.Hasher()))
if err != nil {
return nil, err
}
2022-10-15 14:01:38 +00:00
newkey.id = id
2014-11-25 22:07:00 +00:00
return newkey, nil
2014-11-25 22:07:00 +00:00
}
func RemoveKey(ctx context.Context, repo *Repository, id restic.ID) error {
if id == repo.KeyID() {
return errors.New("refusing to remove key currently used to access repository")
}
h := backend.Handle{Type: restic.KeyFile, Name: id.String()}
return repo.be.Remove(ctx, h)
}
2014-09-23 20:39:12 +00:00
func (k *Key) String() string {
if k == nil {
return "<Key nil>"
}
return fmt.Sprintf("<Key of %s@%s, created on %s>", k.Username, k.Hostname, k.Created)
}
2014-11-25 22:18:02 +00:00
2022-10-15 14:01:38 +00:00
// ID returns an identifier for the key.
func (k Key) ID() restic.ID {
return k.id
2014-11-25 22:18:02 +00:00
}
// Valid tests whether the mac and encryption keys are valid (i.e. not zero)
func (k *Key) Valid() bool {
return k.user.Valid() && k.master.Valid()
}