diff --git a/cmd/khepri/cmd_key.go b/cmd/khepri/cmd_key.go index 7ef9df1b9..c728d03e3 100644 --- a/cmd/khepri/cmd_key.go +++ b/cmd/khepri/cmd_key.go @@ -36,14 +36,34 @@ func list_keys(be backend.Server, key *khepri.Key) error { return nil } +func add_key(be backend.Server, key *khepri.Key) error { + pw := readPassword("KHEPRI_NEWPASSWORD", "enter password for new key: ") + pw2 := readPassword("KHEPRI_NEWPASSWORD", "enter password again: ") + + if pw != pw2 { + errx(1, "passwords do not match") + } + + id, err := key.AddKey(be, pw) + if err != nil { + return fmt.Errorf("creating new key failed: %v\n", err) + } + + fmt.Printf("saved new key as %s\n", id) + + return nil +} + func commandKey(be backend.Server, key *khepri.Key, args []string) error { if len(args) < 1 { - return errors.New("usage: key [list]") + return errors.New("usage: key [list|add]") } switch args[0] { case "list": return list_keys(be, key) + case "add": + return add_key(be, key) } return nil diff --git a/cmd/khepri/main.go b/cmd/khepri/main.go index 9cadfa559..5df73b881 100644 --- a/cmd/khepri/main.go +++ b/cmd/khepri/main.go @@ -34,10 +34,14 @@ type commandFunc func(backend.Server, *khepri.Key, []string) error var commands map[string]commandFunc -func readPassword(prompt string) string { - p := os.Getenv("KHEPRI_PASSWORD") - if p != "" { - return p +func readPassword(env string, prompt string) string { + + if env != "" { + p := os.Getenv(env) + + if p != "" { + return p + } } fmt.Print(prompt) @@ -51,8 +55,8 @@ func readPassword(prompt string) string { } func commandInit(repo string) error { - pw := readPassword("enter password for new backend: ") - pw2 := readPassword("enter password again: ") + pw := readPassword("KHEPRI_PASSWORD", "enter password for new backend: ") + pw2 := readPassword("KHEPRI_PASSWORD", "enter password again: ") if pw != pw2 { errx(1, "passwords do not match") @@ -188,7 +192,7 @@ func main() { errx(1, "unable to open repo: %v", err) } - key, err := khepri.SearchKey(repo, readPassword("Enter Password for Repository: ")) + key, err := khepri.SearchKey(repo, readPassword("KHEPRI_PASSWORD", "Enter Password for Repository: ")) if err != nil { errx(2, "unable to open repo: %v", err) } diff --git a/key.go b/key.go index d39503f9e..86e0d77ee 100644 --- a/key.go +++ b/key.go @@ -204,6 +204,70 @@ func SearchKey(be backend.Server, password string) (*Key, error) { return nil, ErrNoKeyFound } +// AddKey adds a new key to an already existing repository. +func (oldkey *Key) AddKey(be backend.Server, password string) (backend.ID, error) { + // fill meta data about key + newkey := &Key{ + Created: time.Now(), + KDF: "scrypt", + N: scryptN, + R: scryptR, + P: scryptP, + } + + hn, err := os.Hostname() + if err == nil { + newkey.Hostname = hn + } + + usr, err := user.Current() + if err == nil { + newkey.Username = usr.Username + } + + // generate random salt + newkey.Salt = make([]byte, scryptSaltsize) + n, err := rand.Read(newkey.Salt) + if n != scryptSaltsize || err != nil { + panic("unable to read enough random bytes for salt") + } + + // call scrypt() to derive user key + newkey.user, err = newkey.scrypt(password) + if err != nil { + return nil, err + } + + // copy master keys from oldkey + newkey.master = oldkey.master + + // encrypt master keys (as json) with user key + buf, err := json.Marshal(newkey.master) + if err != nil { + return nil, err + } + + newkey.Data = GetChunkBuf("key") + n, err = newkey.EncryptUser(newkey.Data, buf) + newkey.Data = newkey.Data[:n] + + // dump as json + buf, err = json.Marshal(newkey) + if err != nil { + return nil, err + } + + // store in repository and return + id, err := be.Create(backend.Key, buf) + if err != nil { + return nil, err + } + + FreeChunkBuf("key", newkey.Data) + + return id, nil +} + func (k *Key) scrypt(password string) (*keys, error) { if len(k.Salt) == 0 { return nil, fmt.Errorf("scrypt() called with empty salt")