Skip to content

Commit

Permalink
support multiple gpg ids (#19)
Browse files Browse the repository at this point in the history
  • Loading branch information
aviau authored May 10, 2020
1 parent 62f50d9 commit 758383d
Show file tree
Hide file tree
Showing 7 changed files with 115 additions and 26 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,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
- [ ] Should output: ``Password store initialized for [gpg-id].``
- [ ] ``--clone <url>`` allows to init from an existing repo
Expand Down
8 changes: 4 additions & 4 deletions cmd/gopass/internal/cli/command/init/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func ExecInit(cfg command.Config, args []string) error {
fs.StringVar(&p, "p", "", "")

fs.Usage = func() {
fmt.Fprintln(cfg.WriterOutput(), `Usage: gopass init [ --path=sub-folder, -p sub-folder ] gpg-id`)
fmt.Fprintln(cfg.WriterOutput(), `Usage: gopass init [--path=subfolder,-p subfolder] gpg-id...`)
}

if err := fs.Parse(args); err != nil {
Expand All @@ -63,15 +63,15 @@ func ExecInit(cfg command.Config, args []string) error {
return err
}

if fs.NArg() != 1 {
if fs.NArg() < 1 {
fs.Usage()
return nil
}

gpgID := fs.Arg(0)
gpgIDs := fs.Args()

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

Expand Down
4 changes: 2 additions & 2 deletions cmd/gopass/internal/cli/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func TestInitDashDashHelp(t *testing.T) {

assert.Nil(t, err)
assert.Equal(t, "", cliTest.ErrorWriter.String())
assert.True(t, strings.Contains(cliTest.OutputWriter.String(), "Usage: gopass init"))
assert.True(t, strings.Contains(cliTest.OutputWriter.String(), "Usage: gopass init [--path=subfolder,-p subfolder] gpg-id..."))
}

func TestInitDashH(t *testing.T) {
Expand All @@ -43,5 +43,5 @@ func TestInitDashH(t *testing.T) {

assert.Nil(t, err)
assert.Equal(t, "", cliTest.ErrorWriter.String())
assert.True(t, strings.Contains(cliTest.OutputWriter.String(), "Usage: gopass init"))
assert.True(t, strings.Contains(cliTest.OutputWriter.String(), "Usage: gopass init [--path=subfolder,-p subfolder] gpg-id..."))
}
2 changes: 1 addition & 1 deletion internal/gopasstest/gopasstest.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func NewPasswordStoreTest(t *testing.T) *PasswordStoreTest {
passwordStore := gopass.NewPasswordStore(storePath)
passwordStore.UsesGit = false

if err := passwordStore.Init("test"); err != nil {
if err := passwordStore.Init([]string{"test"}); err != nil {
t.Fatal(err)
}

Expand Down
6 changes: 4 additions & 2 deletions man/gopass.1
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,10 @@ password names in
\fBinit\fP [ \fI--path=path\fP, \fI-p path\fP ] \fIgpg-id...\fP
Initialize new password storage and use
.I gpg-id
for encryption. This command must be run first before a password
store can be used. Note that use of
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.
Note that use of
.BR gpg-agent (1)
is recommended so that the batch decryption does not require as much user
intervention.
Expand Down
64 changes: 47 additions & 17 deletions password_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@
package gopass

import (
"bufio"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"path"
Expand All @@ -32,11 +32,31 @@ import (

// PasswordStore represents a password store.
type PasswordStore struct {
Path string // path of the store
GitDir string // The path of the git directory
GPGBin string // The GPG binary to use
GPGID string // The GPG ID used to encrypt the passwords
UsesGit bool // Whether or not the store uses git
Path string // path of the store
GitDir string // The path of the git directory
GPGBin string // The GPG binary to use
GPGIDs []string // The GPG IDs used for the store
UsesGit bool // Whether or not the store uses git
}

// Returns the GPG ids for a given directory
func loadGPGIDs(directory string) ([]string, error) {
gpgIDPath := path.Join(directory, ".gpg-id")

file, err := os.Open(gpgIDPath)
if err != nil {
return nil, err
}

var gpgIDs []string

fscanner := bufio.NewScanner(file)
for fscanner.Scan() {
gpgID := strings.TrimSpace(fscanner.Text())
gpgIDs = append(gpgIDs, gpgID)
}

return gpgIDs, nil
}

// NewPasswordStore returns a new password store.
Expand All @@ -55,15 +75,14 @@ func NewPasswordStore(storePath string) *PasswordStore {
}

//Read the .gpg-id file
gpgIDPath := path.Join(s.Path, ".gpg-id")
content, _ := ioutil.ReadFile(gpgIDPath)
s.GPGID = strings.TrimSpace(string(content))
gpgIDs, _ := loadGPGIDs(s.Path)
s.GPGIDs = gpgIDs

return &s
}

// Init creates a Password Store at the Path
func (store *PasswordStore) Init(gpgID string) error {
func (store *PasswordStore) Init(gpgIDs []string) error {
// Check if the password path already exists
fi, err := os.Stat(store.Path)
if err == nil {
Expand Down Expand Up @@ -98,8 +117,10 @@ func (store *PasswordStore) Init(gpgID string) error {
return err
}
defer gpgIDFile.Close()
gpgIDFile.WriteString(gpgID + "\n")
store.GPGID = gpgID
for _, gpgID := range gpgIDs {
gpgIDFile.WriteString(gpgID + "\n")
}
store.GPGIDs = gpgIDs

if err := store.git("init"); err != nil {
return err
Expand All @@ -120,15 +141,24 @@ func (store *PasswordStore) InsertPassword(pwname, pwtext string) error {
gitAction = "added"
}

gpg := exec.Command(
store.GPGBin,
"-e",
"-r", store.GPGID,
gpgArgs := []string{
"--encrypt",
"--batch",
"--use-agent",
"--no-tty",
"--yes",
"-o", passwordPath)
"--output", passwordPath,
}

for _, recipient := range store.GPGIDs {
gpgArgs = append(
gpgArgs,
"--recipient",
recipient,
)
}

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

stdin, _ := gpg.StdinPipe()
io.WriteString(stdin, pwtext)
Expand Down
56 changes: 56 additions & 0 deletions password_store_init_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright (C) 2017 Alexandre Viau <[email protected]>
//
// This file is part of gopass.
//
// gopass is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// gopass is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with gopass. If not, see <http://www.gnu.org/licenses/>.

package gopass_test

import (
"io/ioutil"
"os"
"path"

"testing"

"github.com/stretchr/testify/assert"

"github.com/aviau/gopass"
)

func TestNewPasswordStoreGPGIds(t *testing.T) {
storePath, err := ioutil.TempDir("", "gopass")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(storePath)

passwordStore := gopass.NewPasswordStore(storePath)
passwordStore.UsesGit = false

if err := passwordStore.Init([]string{"test", "test2"}); err != nil {
t.Fatal(err)
}

assert.Equal(t, passwordStore.GPGIDs, []string{"test", "test2"})

gpgIdContent, _ := ioutil.ReadFile(
path.Join(
passwordStore.Path,
".gpg-id",
),
)
assert.Equal(t, string(gpgIdContent), "test\ntest2\n")

}

0 comments on commit 758383d

Please sign in to comment.