Skip to content

Commit

Permalink
Add askpass for age
Browse files Browse the repository at this point in the history
Fixes gopasspw#2350

RELEASE_NOTES=[ENHANCEMENT] Use OS keychain for age passpharse caching
(off by default).

Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>
  • Loading branch information
dominikschulz committed Sep 24, 2022
1 parent 2c80c7b commit e9a144c
Show file tree
Hide file tree
Showing 7 changed files with 45 additions and 15 deletions.
4 changes: 2 additions & 2 deletions internal/backend/crypto/age/age.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ type Age struct {
}

// New creates a new Age backend.
func New() (*Age, error) {
func New(ctx context.Context) (*Age, error) {
ghc, err := ghssh.New()
if err != nil {
return nil, err
Expand All @@ -44,7 +44,7 @@ func New() (*Age, error) {
ghCache: ghc,
recpCache: rc,
identity: filepath.Join(appdir.UserConfig(), "age", "identities"),
askPass: DefaultAskPass,
askPass: newAskPass(ctx),
}, nil
}

Expand Down
31 changes: 23 additions & 8 deletions internal/backend/crypto/age/askpass.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ type cacher interface {
Purge()
}

type osKeyring struct{}
type osKeyring struct {
knownKeys map[string]bool
}

func (o *osKeyring) Get(key string) (string, bool) {
sec, err := keyring.Get("gopass", key)
Expand All @@ -30,6 +32,7 @@ func (o *osKeyring) Get(key string) (string, bool) {

return "", false
}
o.knownKeys[name] = true

return sec, true
}
Expand All @@ -38,30 +41,42 @@ func (o *osKeyring) Set(name, value string) {
if err := keyring.Set("gopass", name, value); err != nil {
debug.Log("failed to set %s: %w", name, err)
}
o.knownKeys[name] = true
}

func (o *osKeyring) Remove(name string) {
keyring.Delete("gopass", name)
if err := keyring.Delete("gopass", name); err != nil {
debug.Log("failed to remove %s from keyring: %s", name, err)

return
}
o.knownKeys[name] = false
}

func (o *osKeyring) Purge() {
debug.Log("not implemented")
// purge all known keys. only useful for the REPL case.
// Does not persist across restarts.
for k, v := range o.knownKeys {
if !v {
continue
}
if err := keyring.Delete("gopass", k); err != nil {
debug.Log("failed to remove %s from keyring: %s", k, err)
}
}
}

type askPass struct {
testing bool
cache cacher
}

// DefaultAskPass is the default password cache.
var DefaultAskPass = newAskPass()

func newAskPass() *askPass {
func newAskPass(ctx context.Context) *askPass {
a := &askPass{
cache: cache.NewInMemTTL[string, string](time.Hour, 24*time.Hour),
}

if err := keyring.Set("gopass", "sentinel", "empty"); err == nil {
if err := keyring.Set("gopass", "sentinel", "empty"); err == nil && IsUseKeychain(ctx) {
a.cache = &osKeyring{}
}

Expand Down
6 changes: 3 additions & 3 deletions internal/backend/crypto/age/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func (l loader) Commands() []*cli.Command {
"List identities",
Action: func(c *cli.Context) error {
ctx := ctxutil.WithGlobalFlags(c)
a, err := New()
a, err := New(ctx)
if err != nil {
return exit.Error(exit.Unknown, err, "failed to create age backend")
}
Expand Down Expand Up @@ -55,7 +55,7 @@ func (l loader) Commands() []*cli.Command {
"Add an identity",
Action: func(c *cli.Context) error {
ctx := ctxutil.WithGlobalFlags(c)
a, err := New()
a, err := New(ctx)
if err != nil {
return exit.Error(exit.Unknown, err, "failed to create age backend")
}
Expand All @@ -74,7 +74,7 @@ func (l loader) Commands() []*cli.Command {
"Remove an identity",
Action: func(c *cli.Context) error {
ctx := ctxutil.WithGlobalFlags(c)
a, err := New()
a, err := New(ctx)
if err != nil {
return exit.Error(exit.Unknown, err, "failed to create age backend")
}
Expand Down
14 changes: 14 additions & 0 deletions internal/backend/crypto/age/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ type contextKey int

const (
ctxKeyOnlyNative contextKey = iota
ctxKeyUseKeychain
)

// WithOnlyNative will return a context with the flag for only native set.
Expand All @@ -23,3 +24,16 @@ func IsOnlyNative(ctx context.Context) bool {

return bv
}

func WithUseKeychain(ctx context.Context, bv bool) context.Context {
return context.WithValue(ctx, ctxKeyUseKeychain, bv)
}

func IsUseKeychain(ctx context.Context) bool {
bv, ok := ctx.Value(ctxKeyUseKeychain).(bool)
if !ok {
return false
}

return bv
}
2 changes: 1 addition & 1 deletion internal/backend/crypto/age/keyring.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func migrate(ctx context.Context, s backend.Storage) error {
}

// create a new instance so we can use decryptFile.
a, err := New()
a, err := New(ctx)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion internal/backend/crypto/age/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ type loader struct{}
func (l loader) New(ctx context.Context) (backend.Crypto, error) {
debug.Log("Using Crypto Backend: %s", name)

return New()
return New(ctx)
}

func (l loader) Handles(ctx context.Context, s backend.Storage) error {
Expand Down
1 change: 1 addition & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type Config struct {
Path string `yaml:"path"`
SafeContent bool `yaml:"safecontent"` // avoid showing passwords in terminal.
Mounts map[string]string `yaml:"mounts"`
UseKeychain bool `yaml:"keychain"` // use OS keychain for age

ConfigPath string `yaml:"-"`

Expand Down

0 comments on commit e9a144c

Please sign in to comment.