Skip to content

Commit

Permalink
reencrypt functionality (#21)
Browse files Browse the repository at this point in the history
  • Loading branch information
aviau authored May 10, 2020
1 parent 758383d commit 2443cfd
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 26 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ This section was just added so that I could get an idea of where I am at.
- [X] Creates a folder and a .gpg-id file
- [X] Support ``--path`` option
- [X] Support multiple GPG ids
- [ ] Re-encryption functionality
- [X] Re-encryption functionality
- [ ] Should output: ``Password store initialized for [gpg-id].``
- [ ] ``--clone <url>`` allows to init from an existing repo

Expand Down
33 changes: 30 additions & 3 deletions cmd/gopass/internal/cli/command/init/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"fmt"
"io/ioutil"
"path/filepath"
"strings"

"github.com/aviau/gopass"
"github.com/aviau/gopass/cmd/gopass/internal/cli/command"
Expand Down Expand Up @@ -71,10 +72,36 @@ func ExecInit(cfg command.Config, args []string) error {
gpgIDs := fs.Args()

store := gopass.NewPasswordStore(path)
if err := store.Init(gpgIDs); err != nil {
return err

// There is no existing store, create one.
if len(store.GPGIDs) == 0 {
if err := store.Init(gpgIDs); err != nil {
return err
}
fmt.Fprintf(cfg.WriterOutput(), "Successfully created Password Store at \"%s\".\n", path)
} else {
// The store already exists, reencrypt it.

// First, set the GPG ids...
store.SetGPGIDs(gpgIDs)

// Now, reencrypt every password
passwords := store.GetPasswordsList()
for _, password := range passwords {
fmt.Printf("%s: reencrypting to %s\n", password, strings.Join(gpgIDs, ", "))
if err := store.ReencryptPassword(password); err != nil {
return err
}
}

// Commit
if err := store.AddAndCommit(
"Reencrypt password store using new GPG id "+strings.Join(gpgIDs, ", "),
"*",
); err != nil {
return err
}
}

fmt.Fprintf(cfg.WriterOutput(), "Successfully created Password Store at \"%s\".\n", path)
return nil
}
3 changes: 3 additions & 0 deletions man/gopass.1
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ Initialize new password storage and use
for encryption.
Multiple gpg-ids may be specified, in order to encrypt each password with multiple ids.
This command must be run first before a password store can be used.
If the specified
.I gpg-id
is different from the key used in any existing files, these files will be reencrypted to use the new id.
Note that use of
.BR gpg-agent (1)
is recommended so that the batch decryption does not require as much user
Expand Down
113 changes: 98 additions & 15 deletions password_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"bufio"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"path"
Expand Down Expand Up @@ -129,25 +130,37 @@ func (store *PasswordStore) Init(gpgIDs []string) error {
return store.AddAndCommit("initial commit", ".gpg-id")
}

// InsertPassword inserts a new password or overwrites an existing one
func (store *PasswordStore) InsertPassword(pwname, pwtext string) error {
containsPassword, passwordPath := store.ContainsPassword(pwname)
//SetGPGIDs will set the store's GPG ids
func (store *PasswordStore) SetGPGIDs(gpgIDs []string) error {
gpgIDFile, err := os.OpenFile(
path.Join(store.Path, ".gpg-id"),
os.O_WRONLY|os.O_CREATE|os.O_TRUNC,
0644,
)
if err != nil {
return err
}
defer gpgIDFile.Close()

// Check if password already exists
var gitAction string
if containsPassword {
gitAction = "edited"
} else {
gitAction = "added"
for _, gpgID := range gpgIDs {
gpgIDFile.WriteString(gpgID + "\n")
}
store.GPGIDs = gpgIDs

return store.AddAndCommit(
fmt.Sprintf("Set GPG id to %s", strings.Join(gpgIDs, ", ")),
".gpg-id",
)
}

func (store *PasswordStore) getGPGEncryptArgs(destinationPath string) []string {
gpgArgs := []string{
"--encrypt",
"--batch",
"--use-agent",
"--no-tty",
"--yes",
"--output", passwordPath,
"--output", destinationPath,
}

for _, recipient := range store.GPGIDs {
Expand All @@ -158,7 +171,76 @@ func (store *PasswordStore) InsertPassword(pwname, pwtext string) error {
)
}

gpg := exec.Command(store.GPGBin, gpgArgs...)
return gpgArgs
}

func (store *PasswordStore) getGPGDecryptArgs(passwordPath string) []string {
return []string{
"--quiet",
"--batch",
"--use-agent",
"--decrypt",
passwordPath,
}
}

// ReencryptPassword will reencrypt a password to the current GPG ids
func (store *PasswordStore) ReencryptPassword(pwname string) error {
containsPassword, passwordPath := store.ContainsPassword(pwname)

// Error if the password does not exist
if containsPassword == false {
return fmt.Errorf("could not find password \"%s\" at path \"%s\"", pwname, passwordPath)
}

// Create a directory to temporarily hold the reencrypted password
tempDir, err := ioutil.TempDir("", "gopass")
if err != nil {
return err
}
defer os.RemoveAll(tempDir)

tempFile := filepath.Join(tempDir, "temp.gpg")

// Pipe gpg decrypt to gpg encrypt
gpgDecrypt := exec.Command(store.GPGBin, store.getGPGDecryptArgs(passwordPath)...)
gpgEncrypt := exec.Command(store.GPGBin, store.getGPGEncryptArgs(tempFile)...)

gpgDecrypt.Stderr = os.Stderr

gpgEncrypt.Stdin, _ = gpgDecrypt.StdoutPipe()
gpgEncrypt.Stderr = os.Stderr
gpgEncrypt.Start()

if err := gpgDecrypt.Run(); err != nil {
return err
}

if err := gpgEncrypt.Wait(); err != nil {
return err
}

// Move the newly encrypted password to the password store
if err := os.Rename(tempFile, passwordPath); err != nil {
return err
}

return nil
}

// InsertPassword inserts a new password or overwrites an existing one
func (store *PasswordStore) InsertPassword(pwname, pwtext string) error {
containsPassword, passwordPath := store.ContainsPassword(pwname)

// Check if password already exists
var gitAction string
if containsPassword {
gitAction = "edited"
} else {
gitAction = "added"
}

gpg := exec.Command(store.GPGBin, store.getGPGEncryptArgs(passwordPath)...)

stdin, _ := gpg.StdinPipe()
io.WriteString(stdin, pwtext)
Expand Down Expand Up @@ -319,8 +401,7 @@ func (store *PasswordStore) GetPassword(pwname string) (string, error) {
return "", fmt.Errorf("could not find password \"%s\" at path \"%s\"", pwname, passwordPath)
}

// TODO: Use GPG lib instead
show := exec.Command(store.GPGBin, "--quiet", "--batch", "--use-agent", "-d", passwordPath)
show := exec.Command(store.GPGBin, store.getGPGDecryptArgs(passwordPath)...)
output, err := show.CombinedOutput()

if err != nil {
Expand Down Expand Up @@ -360,8 +441,10 @@ func (store *PasswordStore) GetPasswordsList() []string {

var scan = func(path string, fileInfo os.FileInfo, inpErr error) (err error) {
if strings.HasSuffix(path, ".gpg") {
_, file := filepath.Split(path)
password := strings.TrimSuffix(file, ".gpg")
password := strings.TrimSuffix(
strings.TrimPrefix(path, store.Path+"/"),
".gpg",
)
list = append(list, password)
}
return
Expand Down
20 changes: 13 additions & 7 deletions password_store_get_password_list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,26 +31,32 @@ func TestGetPasswordsList(t *testing.T) {
st := gopasstest.NewPasswordStoreTest(t)
defer st.Close()

_, err := os.Create(filepath.Join(st.PasswordStore.Path, "test.com.gpg"))
if err != nil {
if _, err := os.Create(filepath.Join(st.PasswordStore.Path, "test.com.gpg")); err != nil {
t.Fatal(err)
}

_, err = os.Create(filepath.Join(st.PasswordStore.Path, "test2.com.gpg"))
if err != nil {
if _, err := os.Create(filepath.Join(st.PasswordStore.Path, "test2.com.gpg")); err != nil {
t.Fatal(err)
}

_, err = os.Create(filepath.Join(st.PasswordStore.Path, "test3"))
if err != nil {
if _, err := os.Create(filepath.Join(st.PasswordStore.Path, "test3")); err != nil {
t.Fatal(err)
}

dirPath := filepath.Join(st.PasswordStore.Path, "dir")
if err := os.Mkdir(dirPath, os.ModePerm); err != nil {
t.Fatal(err)
}

if _, err := os.Create(filepath.Join(dirPath, "test3.com.gpg")); err != nil {
t.Fatal(err)
}

passwords := st.PasswordStore.GetPasswordsList()
assert.Equal(
t,
passwords,
[]string{"test.com", "test2.com"},
[]string{"dir/test3.com", "test.com", "test2.com"},
"Password list should contain test.com and test2.com",
)
}

0 comments on commit 2443cfd

Please sign in to comment.