Skip to content

Commit

Permalink
Use OS Keyring to cache age passphrases (#2351)
Browse files Browse the repository at this point in the history
* Use OS Keyring to cache age passphrases
* Add askpass for age

Fixes #2350

RELEASE_NOTES=[ENHANCEMENT] Use OS keychain for age passphrase caching (new config option, off by default).
  • Loading branch information
dominikschulz authored Sep 27, 2022
1 parent ce745ca commit ce95ddf
Show file tree
Hide file tree
Showing 11 changed files with 110 additions and 12 deletions.
4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,13 @@ require (

require (
filippo.io/edwards25519 v1.0.0 // indirect
github.com/alessio/shellescape v1.4.1 // indirect
github.com/boombuler/barcode v1.0.1 // indirect
github.com/cloudflare/circl v1.2.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/danieljoos/wincred v1.1.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/godbus/dbus/v5 v5.0.6 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
Expand All @@ -60,6 +63,7 @@ require (
github.com/rs/zerolog v1.28.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
github.com/zalando/go-keyring v0.2.1 // indirect
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/multierr v1.8.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
Expand Down
11 changes: 11 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5E
github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/ProtonMail/go-crypto v0.0.0-20220824120805-4b6e5c587895 h1:NsReiLpErIPzRrnogAXYwSoU7txA977LjDGrbkewJbg=
github.com/ProtonMail/go-crypto v0.0.0-20220824120805-4b6e5c587895/go.mod h1:UBYPn8k0D56RtnR8RFQMjmh4KrZzWJ5o7Z9SYjossQ8=
github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0=
github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
Expand All @@ -33,6 +35,8 @@ github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/danieljoos/wincred v1.1.0 h1:3RNcEpBg4IhIChZdFRSdlQt1QjCp1sMAPIrOnm7Yf8g=
github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7hqDjlFjiygg=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand All @@ -43,6 +47,8 @@ github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYF
github.com/godbus/dbus v0.0.0-20190623212516-8a1682060722 h1:NNKZiuNXd6lpZRyoFM/uhssj5W9Ps1DbhGHxT49Pm9I=
github.com/godbus/dbus v0.0.0-20190623212516-8a1682060722/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.0.6 h1:mkgN1ofwASrYnJ5W6U/BxG15eXXXjirgZc7CLqkcaro=
github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gokyle/twofactor v1.0.1 h1:uRhvx0S4Hb82RPIDALnf7QxbmPL49LyyaCkJDpWx+Ek=
github.com/gokyle/twofactor v1.0.1/go.mod h1:4gxzH1eaE/F3Pct/sCDNOylP0ClofUO5j4XZN9tKtLE=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
Expand Down Expand Up @@ -131,6 +137,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
Expand All @@ -142,6 +149,8 @@ github.com/urfave/cli/v2 v2.16.3/go.mod h1:1CNUng3PtjQMtRzJO4FMXBQvkGtuYRxxiR9xM
github.com/xrash/smetrics v0.0.0-20170218160415-a3153f7040e9/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
github.com/zalando/go-keyring v0.2.1 h1:MBRN/Z8H4U5wEKXiD67YbDAr5cj/DOStmSga70/2qKc=
github.com/zalando/go-keyring v0.2.1/go.mod h1:g63M2PPn0w5vjmEbwAX3ib5I+41zdm4esSETOn9Y6Dw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
Expand Down Expand Up @@ -203,6 +212,8 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200121175148-a6ecf24a6d71/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Expand Down
3 changes: 3 additions & 0 deletions internal/action/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ func TestConfig(t *testing.T) { //nolint:paralleltest
autoimport: true
cliptimeout: 45
exportkeys: true
keychain: false
nopager: false
notifications: true
parsing: true
Expand Down Expand Up @@ -81,6 +82,7 @@ parsing: true
autoimport: true
cliptimeout: 45
exportkeys: true
keychain: false
nopager: true
notifications: true
parsing: true
Expand Down Expand Up @@ -116,6 +118,7 @@ parsing: true
autoimport
cliptimeout
exportkeys
keychain
nopager
notifications
parsing
Expand Down
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
59 changes: 54 additions & 5 deletions internal/backend/crypto/age/askpass.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/gopasspw/gopass/pkg/pinentry/cli"
"github.com/nbutton23/zxcvbn-go"
"github.com/twpayne/go-pinentry"
"github.com/zalando/go-keyring"
)

type cacher interface {
Expand All @@ -20,18 +21,66 @@ type cacher interface {
Purge()
}

type osKeyring struct {
knownKeys map[string]bool
}

func (o *osKeyring) Get(key string) (string, bool) {
sec, err := keyring.Get("gopass", key)
if err != nil {
debug.Log("failed to get %s from OS keyring: %w", key, err)

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

return sec, true
}

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) {
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() {
// 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 {
return &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 && IsUseKeychain(ctx) {
a.cache = &osKeyring{}
}

return a
}

func (a *askPass) Ping(_ context.Context) error {
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
25 changes: 25 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,27 @@ func IsOnlyNative(ctx context.Context) bool {

return bv
}

// WithUseKeychain returns a context with the value of use keychain
// set.
func WithUseKeychain(ctx context.Context, bv bool) context.Context {
return context.WithValue(ctx, ctxKeyUseKeychain, bv)
}

// IsUseKeychain returns the value of use keychain.
func IsUseKeychain(ctx context.Context) bool {
bv, ok := ctx.Value(ctxKeyUseKeychain).(bool)
if !ok {
return false
}

return bv
}

// HasUseKeychain returns true if a value for use keychain
// was set in the context.
func HasUseKeychain(ctx context.Context) bool {
_, ok := ctx.Value(ctxKeyUseKeychain).(bool)

return ok
}
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
5 changes: 5 additions & 0 deletions internal/config/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package config
import (
"context"

"github.com/gopasspw/gopass/internal/backend/crypto/age"
"github.com/gopasspw/gopass/pkg/ctxutil"
)

Expand Down Expand Up @@ -33,5 +34,9 @@ func (c *Config) WithContext(ctx context.Context) context.Context {
ctx = ctxutil.WithShowParsing(ctx, c.Parsing)
}

if !age.HasUseKeychain(ctx) {
ctx = age.WithUseKeychain(ctx, c.UseKeychain)
}

return ctx
}

0 comments on commit ce95ddf

Please sign in to comment.