diff --git a/cmd/kopia/command_create.go b/cmd/kopia/command_create.go index d6bb43275..e03ad7ccc 100644 --- a/cmd/kopia/command_create.go +++ b/cmd/kopia/command_create.go @@ -100,12 +100,11 @@ func runCreateCommand(context *kingpin.ParseContext) error { repoFormat.ObjectFormat, repoFormat.MaxBlobSize) - masterKey, password, err := getKeyOrPassword(true) + creds, err := getVaultCredentials(true) if err != nil { return fmt.Errorf("unable to get credentials: %v", err) } - var vlt *vault.Vault vf, err := vaultFormat() if err != nil { return fmt.Errorf("unable to initialize vault format: %v", err) @@ -115,11 +114,7 @@ func runCreateCommand(context *kingpin.ParseContext) error { "Initializing vault in '%s' with encryption '%v'.\n", vaultStorage.Configuration().Config.ToURL().String(), vf.Encryption) - if masterKey != nil { - vlt, err = vault.CreateWithMasterKey(vaultStorage, vf, masterKey) - } else { - vlt, err = vault.CreateWithPassword(vaultStorage, vf, password) - } + vlt, err := vault.Create(vaultStorage, vf, creds) if err != nil { return fmt.Errorf("cannot create vault: %v", err) } diff --git a/cmd/kopia/config.go b/cmd/kopia/config.go index 5893bbd1a..755c56f8b 100644 --- a/cmd/kopia/config.go +++ b/cmd/kopia/config.go @@ -118,50 +118,46 @@ func openVaultSpecifiedByFlag() (*vault.Vault, error) { return nil, err } - masterKey, password, err := getKeyOrPassword(false) + creds, err := getVaultCredentials(false) if err != nil { return nil, err } - if masterKey != nil { - return vault.OpenWithMasterKey(storage, masterKey) - } - - return vault.OpenWithPassword(storage, password) + return vault.Open(storage, creds) } var errPasswordTooShort = errors.New("password too short") -func getKeyOrPassword(isNew bool) ([]byte, string, error) { +func getVaultCredentials(isNew bool) (vault.Credentials, error) { if *key != "" { k, err := hex.DecodeString(*key) if err != nil { - return nil, "", fmt.Errorf("invalid key format: %v", err) + return nil, fmt.Errorf("invalid key format: %v", err) } - return k, "", nil + return vault.MasterKey(k) } if *password != "" { - return nil, strings.TrimSpace(*password), nil + return vault.Password(strings.TrimSpace(*password)) } if *keyFile != "" { key, err := ioutil.ReadFile(*keyFile) if err != nil { - return nil, "", fmt.Errorf("unable to read key file: %v", err) + return nil, fmt.Errorf("unable to read key file: %v", err) } - return key, "", nil + return vault.MasterKey(key) } if *passwordFile != "" { f, err := ioutil.ReadFile(*passwordFile) if err != nil { - return nil, "", fmt.Errorf("unable to read password file: %v", err) + return nil, fmt.Errorf("unable to read password file: %v", err) } - return nil, strings.TrimSpace(string(f)), nil + return vault.Password(strings.TrimSpace(string(f))) } if isNew { for { @@ -174,28 +170,28 @@ func getKeyOrPassword(isNew bool) ([]byte, string, error) { continue } if err != nil { - return nil, "", err + return nil, err } fmt.Printf("Re-enter password for verification: ") p2, err := askPass() if err != nil { - return nil, "", err + return nil, err } fmt.Println() if p1 != p2 { fmt.Println("Passwords don't match!") } else { - return nil, p1, nil + return vault.Password(p1) } } } else { fmt.Printf("Enter password to open vault: ") p1, err := askPass() if err != nil { - return nil, "", err + return nil, err } fmt.Println() - return nil, p1, nil + return vault.Password(p1) } } diff --git a/vault/creds.go b/vault/creds.go new file mode 100644 index 000000000..e514f4c03 --- /dev/null +++ b/vault/creds.go @@ -0,0 +1,57 @@ +package vault + +import ( + "crypto/sha256" + "fmt" + + "golang.org/x/crypto/pbkdf2" +) + +const ( + passwordBasedKeySize = 32 + + pbkdf2Rounds = 10000 + + // MinPasswordLength is the minimum allowed length of a password in charcters. + MinPasswordLength = 12 + + // MinMasterKeyLength is the minimum allowed length of a master key, in bytes. + MinMasterKeyLength = 16 +) + +// Credentials em +type Credentials interface { + getMasterKey(salt []byte) []byte +} + +type masterKeyCredentials struct { + key []byte +} + +func (mkc *masterKeyCredentials) getMasterKey(salt []byte) []byte { + return mkc.key +} + +func MasterKey(key []byte) (Credentials, error) { + if len(key) < MinMasterKeyLength { + return nil, fmt.Errorf("master key too short") + } + + return &masterKeyCredentials{key}, nil +} + +type passwordCredentials struct { + password string +} + +func (pc *passwordCredentials) getMasterKey(salt []byte) []byte { + return pbkdf2.Key([]byte(pc.password), salt, pbkdf2Rounds, passwordBasedKeySize, sha256.New) +} + +func Password(password string) (Credentials, error) { + if len(password) < MinPasswordLength { + return nil, fmt.Errorf("password too short") + } + + return &passwordCredentials{password}, nil +} diff --git a/vault/keys.go b/vault/keys.go deleted file mode 100644 index a6e5dd6c8..000000000 --- a/vault/keys.go +++ /dev/null @@ -1,47 +0,0 @@ -package vault - -import ( - "errors" - - "golang.org/x/crypto/curve25519" -) - -const ( - pbkdf2Rounds = 10000 - masterKeySize = 32 -) - -// UserPrivateKey encapsulates secret key belonging to a user. -type UserPrivateKey struct { - key [32]byte -} - -// UserPublicKey encapsulates public key belonging to a user. -type UserPublicKey struct { - key [32]byte -} - -// Bytes returns the private key bytes. -func (prv UserPrivateKey) Bytes() []byte { - r := make([]byte, 32) - copy(r, prv.key[:]) - return r -} - -// PublicKey returns public key associated with the private key. -func (prv UserPrivateKey) PublicKey() *UserPublicKey { - pub := &UserPublicKey{} - - curve25519.ScalarBaseMult(&pub.key, &prv.key) - return pub -} - -func newPrivateKey(key []byte) (*UserPrivateKey, error) { - if len(key) != 32 { - return nil, errors.New("unsupported key length") - } - - k := &UserPrivateKey{} - copy(k.key[:], key) - return k, nil -} diff --git a/vault/vault.go b/vault/vault.go index ba39e303f..6624c1c26 100644 --- a/vault/vault.go +++ b/vault/vault.go @@ -17,19 +17,12 @@ "github.com/kopia/kopia/repo" "golang.org/x/crypto/hkdf" - "golang.org/x/crypto/pbkdf2" ) const ( formatBlock = "format" checksumBlock = "checksum" repositoryConfigBlock = "repo" - - // MinPasswordLength is the minimum allowed length of a password in charcters. - MinPasswordLength = 12 - - // MinMasterKeyLength is the minimum allowed length of a master key, in bytes. - MinMasterKeyLength = 16 ) var ( @@ -216,35 +209,23 @@ func (v *Vault) List(prefix string) ([]string, error) { return result, nil } -// CreateWithPassword creates a password-protected Vault in the specified storage. -func CreateWithPassword(storage blob.Storage, format *Format, password string) (*Vault, error) { +// Create creates a Vault in the specified storage. +func Create(storage blob.Storage, format *Format, creds Credentials) (*Vault, error) { if err := format.ensureUniqueID(); err != nil { return nil, err } - if len(password) < MinPasswordLength { - return nil, fmt.Errorf("password too short, must be at least %v characters, got %v", MinPasswordLength, len(password)) - } - masterKey := pbkdf2.Key([]byte(password), format.UniqueID, pbkdf2Rounds, masterKeySize, sha256.New) - return CreateWithMasterKey(storage, format, masterKey) -} - -// CreateWithMasterKey creates a master key-protected Vault in the specified storage. -func CreateWithMasterKey(storage blob.Storage, format *Format, masterKey []byte) (*Vault, error) { - if len(masterKey) < MinMasterKeyLength { - return nil, fmt.Errorf("key too short, must be at least %v bytes, got %v", MinMasterKeyLength, len(masterKey)) - } - v := Vault{ - Storage: storage, - MasterKey: masterKey, - Format: *format, + Storage: storage, + Format: *format, } v.Format.Version = "1" if err := v.Format.ensureUniqueID(); err != nil { return nil, err } + v.MasterKey = creds.getMasterKey(v.Format.UniqueID) + formatBytes, err := json.Marshal(&v.Format) if err != nil { return nil, err @@ -264,11 +245,11 @@ func CreateWithMasterKey(storage blob.Storage, format *Format, masterKey []byte) return nil, err } - return OpenWithMasterKey(storage, masterKey) + return Open(storage, creds) } -// OpenWithPassword opens a password-protected vault. -func OpenWithPassword(storage blob.Storage, password string) (*Vault, error) { +// OpenWithPassword opens a vault. +func Open(storage blob.Storage, creds Credentials) (*Vault, error) { v := Vault{ Storage: storage, } @@ -283,7 +264,7 @@ func OpenWithPassword(storage blob.Storage, password string) (*Vault, error) { return nil, err } - v.MasterKey = pbkdf2.Key([]byte(password), v.Format.UniqueID, pbkdf2Rounds, masterKeySize, sha256.New) + v.MasterKey = creds.getMasterKey(v.Format.UniqueID) if _, err := v.readEncryptedBlock(checksumBlock); err != nil { return nil, err