Skip to content

Commit

Permalink
Add secret config store (gopasspw#708)
Browse files Browse the repository at this point in the history
  • Loading branch information
dominikschulz authored Mar 15, 2018
1 parent d8a229e commit 1a239b0
Show file tree
Hide file tree
Showing 45 changed files with 713 additions and 234 deletions.
3 changes: 3 additions & 0 deletions action/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/blang/semver"
"github.com/justwatchcom/gopass/config"
"github.com/justwatchcom/gopass/store/root"
"github.com/justwatchcom/gopass/utils/out"
)

var (
Expand Down Expand Up @@ -42,6 +43,8 @@ func newAction(ctx context.Context, cfg *config.Config, sv semver.Version) (*Act
version: sv,
}

ctx = out.AddPrefix(ctx, "[action] ")

store, err := root.New(ctx, cfg)
if err != nil {
return nil, exitError(ctx, ExitUnknown, err, "failed to init root store: %s", err)
Expand Down
9 changes: 8 additions & 1 deletion action/action_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,14 @@ func newMock(ctx context.Context, u *gptest.Unit) (*Action, error) {
ctx = backend.WithRCSBackend(ctx, backend.Noop)
ctx = backend.WithCryptoBackend(ctx, backend.Plain)
ctx = backend.WithStorageBackend(ctx, backend.FS)
return newAction(ctx, cfg, semver.Version{})
act, err := newAction(ctx, cfg, semver.Version{})
if err != nil {
return nil, err
}
if err := act.Initialized(ctx, nil); err != nil {
return nil, err
}
return act, nil
}

func TestAction(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion action/clihelper.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ func (s *Action) askForPrivateKey(ctx context.Context, name, prompt string) (str

fmt.Fprintln(stdout, prompt)
for i, k := range kl {
fmt.Fprintf(stdout, "[%d] %s\n", i, crypto.FormatKey(ctx, k))
fmt.Fprintf(stdout, "[%d] %s - %s\n", i, crypto.Name(), crypto.FormatKey(ctx, k))
}
iv, err := termio.AskForInt(ctx, fmt.Sprintf("Please enter the number of a key (0-%d, [q]uit)", len(kl)-1), 0)
if err != nil {
Expand Down
5 changes: 5 additions & 0 deletions action/history_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ func TestHistory(t *testing.T) {
defer u.Remove()

ctx := context.Background()
ctx = ctxutil.WithDebug(ctx, true)
ctx = ctxutil.WithAlwaysYes(ctx, true)
ctx = backend.WithRCSBackend(ctx, backend.GitCLI)
ctx = backend.WithCryptoBackend(ctx, backend.Plain)
Expand All @@ -30,6 +31,7 @@ func TestHistory(t *testing.T) {
cfg.Root.Path = backend.FromPath(u.StoreDir(""))
act, err := newAction(ctx, cfg, semver.Version{})
assert.NoError(t, err)
assert.NoError(t, act.Initialized(ctx, nil))

buf := &bytes.Buffer{}
out.Stdout = buf
Expand All @@ -41,18 +43,21 @@ func TestHistory(t *testing.T) {

// init git
assert.NoError(t, act.gitInit(ctx, "", "foo bar", "foo.bar@example.org"))
buf.Reset()

// insert bar
fs := flag.NewFlagSet("default", flag.ContinueOnError)
assert.NoError(t, fs.Parse([]string{"bar"}))
c := cli.NewContext(app, fs, nil)

assert.NoError(t, act.Insert(ctx, c))
buf.Reset()

// history bar
fs = flag.NewFlagSet("default", flag.ContinueOnError)
assert.NoError(t, fs.Parse([]string{"bar"}))
c = cli.NewContext(app, fs, nil)

assert.NoError(t, act.History(ctx, c))
buf.Reset()
}
46 changes: 36 additions & 10 deletions action/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"github.com/fatih/color"
"github.com/justwatchcom/gopass/backend"
"github.com/justwatchcom/gopass/config"
"github.com/justwatchcom/gopass/store/sub"
"github.com/justwatchcom/gopass/utils/agent/client"
"github.com/justwatchcom/gopass/utils/ctxutil"
"github.com/justwatchcom/gopass/utils/cui"
"github.com/justwatchcom/gopass/utils/out"
Expand All @@ -21,6 +23,7 @@ import (
// prepared.
func (s *Action) Initialized(ctx context.Context, c *cli.Context) error {
if !s.Store.Initialized(ctx) {
out.Debug(ctx, "Store needs to be initialized")
if !ctxutil.IsInteractive(ctx) {
return exitError(ctx, ExitNotInitialized, nil, "password-store is not initialized. Try '%s init'", s.Name)
}
Expand All @@ -31,6 +34,7 @@ func (s *Action) Initialized(ctx context.Context, c *cli.Context) error {
return nil
}
}
out.Debug(ctx, "Store is already initialized")
return nil
}

Expand Down Expand Up @@ -59,7 +63,9 @@ func (s *Action) init(ctx context.Context, alias, path string, nogit bool, keys
path = s.Store.Path()
}
}
out.Debug(ctx, "init(%s, %s, %t, %+v)", alias, path, nogit, keys)

out.Debug(ctx, "Checking private keys ...")
if len(keys) < 1 {
nk, err := s.askForPrivateKey(ctx, alias, color.CyanString("Please select a private key for encrypting secrets:"))
if err != nil {
Expand All @@ -68,17 +74,20 @@ func (s *Action) init(ctx context.Context, alias, path string, nogit bool, keys
keys = []string{nk}
}

out.Debug(ctx, "Initializing sub store - Alias: %s - Path: %s - Keys: %+v", alias, path, keys)
if err := s.Store.Init(ctx, alias, path, keys...); err != nil {
return errors.Wrapf(err, "failed to init store '%s' at '%s'", alias, path)
}

if alias != "" && path != "" {
out.Debug(ctx, "Mounting sub store %s -> %s", alias, path)
if err := s.Store.AddMount(ctx, alias, path); err != nil {
return errors.Wrapf(err, "failed to add mount '%s'", alias)
}
}

if !nogit {
out.Debug(ctx, "Initializing RCS ...")
if err := s.gitInit(ctx, alias, "", ""); err != nil {
out.Debug(ctx, "Stacktrace: %+v\n", err)
out.Red(ctx, "Failed to init git: %s", err)
Expand Down Expand Up @@ -116,19 +125,32 @@ func (s *Action) InitOnboarding(ctx context.Context, c *cli.Context) error {
email := c.String("email")

ctx = out.AddPrefix(ctx, "[init] ")
ctx = backend.WithRCSBackend(ctx, backend.GitCLI)
out.Debug(ctx, "Starting Onboarding Wizard - remote: %s - team: %s - create: %t - name: %s - email: %s", remote, team, create, name, email)

crypto := s.Store.Crypto(ctx, name)
if crypto == nil {
c, err := sub.GetCryptoBackend(ctx, backend.GetCryptoBackend(ctx), config.Directory(), client.New(config.Directory()))
if err != nil {
return errors.Wrapf(err, "failed to init crypto backend")
}
crypto = c
}

out.Debug(ctx, "Crypto Backend initialized as: %s", crypto.Name())

// check for existing GPG keypairs (private/secret keys). We need at least
// one useable key pair. If none exists try to create one
if !s.initHasUseablePrivateKeys(ctx, team) {
out.Yellow(ctx, "No useable GPG keys. Generating new key pair")
ctx := out.AddPrefix(ctx, "[gpg] ")
if !s.initHasUseablePrivateKeys(ctx, crypto, team) {
out.Yellow(ctx, "No useable crypto keys. Generating new key pair")
ctx := out.AddPrefix(ctx, "[crypto] ")
out.Print(ctx, "Key generation may take up to a few minutes")
if err := s.initCreatePrivateKey(ctx, team, name, email); err != nil {
if err := s.initCreatePrivateKey(ctx, crypto, team, name, email); err != nil {
return errors.Wrapf(err, "failed to create new private key")
}
}

out.Debug(ctx, "Has useable private keys")

// if a git remote and a team name are given attempt unattended team setup
if remote != "" && team != "" {
if create {
Expand Down Expand Up @@ -162,8 +184,7 @@ func (s *Action) InitOnboarding(ctx context.Context, c *cli.Context) error {
return nil
}

func (s *Action) initCreatePrivateKey(ctx context.Context, mount, name, email string) error {
crypto := s.Store.Crypto(ctx, mount)
func (s *Action) initCreatePrivateKey(ctx context.Context, crypto backend.Crypto, mount, name, email string) error {
out.Green(ctx, "Creating key pair ...")
out.Yellow(ctx, "WARNING: We are about to generate some GPG keys.")
out.Print(ctx, `However, the GPG program can sometimes lock up, displaying the following:
Expand Down Expand Up @@ -213,8 +234,8 @@ https://github.com/justwatchcom/gopass/blob/master/docs/entropy.md`)
return nil
}

func (s *Action) initHasUseablePrivateKeys(ctx context.Context, mount string) bool {
kl, err := s.Store.Crypto(ctx, mount).ListPrivateKeyIDs(ctx)
func (s *Action) initHasUseablePrivateKeys(ctx context.Context, crypto backend.Crypto, mount string) bool {
kl, err := crypto.ListPrivateKeyIDs(ctx)
if err != nil {
return false
}
Expand Down Expand Up @@ -248,8 +269,13 @@ func (s *Action) initSetupGitRemote(ctx context.Context, team, remote string) er
func (s *Action) initLocal(ctx context.Context, c *cli.Context) error {
ctx = out.AddPrefix(ctx, "[local] ")

path := ""
if s.Store != nil {
path = s.Store.URL()
}

out.Print(ctx, "Initializing your local store ...")
if err := s.init(out.WithHidden(ctx, true), "", "", false); err != nil {
if err := s.init(out.WithHidden(ctx, true), "", path, false); err != nil {
return errors.Wrapf(err, "failed to init local store")
}
out.Green(ctx, " -> OK")
Expand Down
5 changes: 3 additions & 2 deletions action/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,9 @@ func TestInit(t *testing.T) {
assert.NoError(t, act.Initialized(ctx, c))
assert.Error(t, act.Init(ctx, c))
assert.Error(t, act.InitOnboarding(ctx, c))
assert.Equal(t, true, act.initHasUseablePrivateKeys(ctx, ""))
assert.Error(t, act.initCreatePrivateKey(ctx, "", "foo bar", "foo.bar@example.org"))
crypto := act.Store.Crypto(ctx, "")
assert.Equal(t, true, act.initHasUseablePrivateKeys(ctx, crypto, ""))
assert.Error(t, act.initCreatePrivateKey(ctx, crypto, "", "foo bar", "foo.bar@example.org"))
buf.Reset()

// un-initialize the store
Expand Down
1 change: 1 addition & 0 deletions app.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ func setupApp(ctx context.Context, sv semver.Version) *cli.App {
}
}

// initialize action handlers
action, err := ap.New(ctx, cfg, sv)
if err != nil {
out.Red(ctx, "No gpg binary found: %s", err)
Expand Down
12 changes: 6 additions & 6 deletions backend/crypto/xc/decrypt.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func (x *XC) Decrypt(ctx context.Context, buf []byte) ([]byte, error) {
}

// try to find a suiteable decryption key in the header
sk, err := x.decryptSessionKey(msg.Header)
sk, err := x.decryptSessionKey(ctx, msg.Header)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -87,12 +87,12 @@ func (x *XC) findPublicKey(needle string) (*keyring.PublicKey, error) {
}

// decryptPrivateKey will ask the agent to unlock the private key
func (x *XC) decryptPrivateKey(recp *keyring.PrivateKey) error {
func (x *XC) decryptPrivateKey(ctx context.Context, recp *keyring.PrivateKey) error {
fp := recp.Fingerprint()

for i := 0; i < maxUnlockAttempts; i++ {
// retry asking for key in case it's wrong
passphrase, err := x.client.Passphrase(fp, fmt.Sprintf("Unlock private key %s", recp.Fingerprint()))
passphrase, err := x.client.Passphrase(ctx, fp, fmt.Sprintf("Unlock private key %s", recp.Fingerprint()))
if err != nil {
return errors.Wrapf(err, "failed to get passphrase from agent: %s", err)
}
Expand All @@ -103,7 +103,7 @@ func (x *XC) decryptPrivateKey(recp *keyring.PrivateKey) error {
}

// decryption failed, clear cache and wait a moment before trying again
if err := x.client.Remove(fp); err != nil {
if err := x.client.Remove(ctx, fp); err != nil {
return errors.Wrapf(err, "failed to clear cache")
}
time.Sleep(10 * time.Millisecond)
Expand All @@ -114,7 +114,7 @@ func (x *XC) decryptPrivateKey(recp *keyring.PrivateKey) error {

// decryptSessionKey will attempt to find a readable recipient entry in the
// header and decrypt it's session key
func (x *XC) decryptSessionKey(hdr *xcpb.Header) ([]byte, error) {
func (x *XC) decryptSessionKey(ctx context.Context, hdr *xcpb.Header) ([]byte, error) {
// find a suiteable decryption key, i.e. a recipient entry which was encrypted
// for one of our private keys
recp, err := x.findDecryptionKey(hdr)
Expand All @@ -130,7 +130,7 @@ func (x *XC) decryptSessionKey(hdr *xcpb.Header) ([]byte, error) {
}

// unlock recipient key
if err := x.decryptPrivateKey(recp); err != nil {
if err := x.decryptPrivateKey(ctx, recp); err != nil {
return nil, err
}

Expand Down
10 changes: 5 additions & 5 deletions backend/crypto/xc/encrypt.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func (x *XC) Encrypt(ctx context.Context, plaintext []byte, recipients []string)
}

// encrypt the session key per recipient
header, err := x.encryptHeader(privKey, sk, recipients)
header, err := x.encryptHeader(ctx, privKey, sk, recipients)
if err != nil {
return nil, errors.Wrapf(err, "failed to encrypt header: %s", err)
}
Expand All @@ -60,7 +60,7 @@ func (x *XC) Encrypt(ctx context.Context, plaintext []byte, recipients []string)

// encrypt header creates and populates a header struct with the nonce (plain)
// and the session key encrypted per recipient
func (x *XC) encryptHeader(signKey *keyring.PrivateKey, sk []byte, recipients []string) (*xcpb.Header, error) {
func (x *XC) encryptHeader(ctx context.Context, signKey *keyring.PrivateKey, sk []byte, recipients []string) (*xcpb.Header, error) {
hdr := &xcpb.Header{
Sender: signKey.Fingerprint(),
Recipients: make(map[string][]byte, len(recipients)),
Expand All @@ -75,7 +75,7 @@ func (x *XC) encryptHeader(signKey *keyring.PrivateKey, sk []byte, recipients []
continue
}

r, err := x.encryptForRecipient(signKey, sk, recp)
r, err := x.encryptForRecipient(ctx, signKey, sk, recp)
if err != nil {
return nil, errors.Wrapf(err, "failed to encrypt session key for recipient %s: %s", recp, err)
}
Expand All @@ -87,7 +87,7 @@ func (x *XC) encryptHeader(signKey *keyring.PrivateKey, sk []byte, recipients []
}

// encryptForRecipients encrypts the given session key for the given recipient
func (x *XC) encryptForRecipient(sender *keyring.PrivateKey, sk []byte, recipient string) ([]byte, error) {
func (x *XC) encryptForRecipient(ctx context.Context, sender *keyring.PrivateKey, sk []byte, recipient string) ([]byte, error) {
recp := x.pubring.Get(recipient)
if recp == nil {
return nil, fmt.Errorf("recipient public key not available for %s", recipient)
Expand All @@ -97,7 +97,7 @@ func (x *XC) encryptForRecipient(sender *keyring.PrivateKey, sk []byte, recipien
copy(recipientPublicKey[:], recp.PublicKey[:])

// unlock sender key
if err := x.decryptPrivateKey(sender); err != nil {
if err := x.decryptPrivateKey(ctx, sender); err != nil {
return nil, err
}

Expand Down
6 changes: 3 additions & 3 deletions backend/crypto/xc/encrypt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@ type fakeAgent struct {
pw string
}

func (f *fakeAgent) Ping() error {
func (f *fakeAgent) Ping(context.Context) error {
return nil
}

func (f *fakeAgent) Remove(string) error {
func (f *fakeAgent) Remove(context.Context, string) error {
return nil
}

func (f *fakeAgent) Passphrase(string, string) (string, error) {
func (f *fakeAgent) Passphrase(context.Context, string, string) (string, error) {
return f.pw, nil
}

Expand Down
Loading

0 comments on commit 1a239b0

Please sign in to comment.