Skip to content

Commit

Permalink
WIP for touchid support based on 99designs/aws-vault#131
Browse files Browse the repository at this point in the history
  • Loading branch information
milesbxf committed Sep 15, 2022
1 parent 81fed19 commit 9eca1df
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 0 deletions.
1 change: 1 addition & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,5 @@ type Config struct {

// WinCredPrefix is a string prefix to prepend to the key name
WinCredPrefix string
UseBiometrics bool
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ require (
github.com/dvsekhvalnov/jose2go v1.5.0
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c
github.com/lox/go-touchid v0.0.0-20170712105233-619cc8e578d0
github.com/mtibben/percent v0.2.1
github.com/stretchr/testify v1.7.0
golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NM
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lox/go-touchid v0.0.0-20170712105233-619cc8e578d0 h1:m81erW+1MD5vl3lKQ/+TYPHJ6Y9/C1COqxXPE51FkDk=
github.com/lox/go-touchid v0.0.0-20170712105233-619cc8e578d0/go.mod h1:EHbIQzfC3kdWFI81pLOFjssnolF+ALfmVf8PUdWBxo4=
github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs=
github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
Expand Down
101 changes: 101 additions & 0 deletions keychain.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ import (
"fmt"

gokeychain "github.com/99designs/go-keychain"

"github.com/lox/go-touchid"
)

const (
biometricsAccount = "com.99designs.aws-vault.biometrics"
biometricsService = "aws-vault"
biometricsLabel = "Passphrase for %s"
)

type keychain struct {
Expand All @@ -19,6 +27,9 @@ type keychain struct {
isSynchronizable bool
isAccessibleWhenUnlocked bool
isTrusted bool

isTouchIDAuthenticated bool
useTouchID bool
}

func init() {
Expand All @@ -31,6 +42,9 @@ func init() {
// KeychainAccessibleWhenUnlocked is a shorthand for setting the accessibility value.
// See: https://developer.apple.com/documentation/security/ksecattraccessiblewhenunlocked
isAccessibleWhenUnlocked: cfg.KeychainAccessibleWhenUnlocked,

isTouchIDAuthenticated: false,
useTouchID: cfg.UseBiometrics,
}
if cfg.KeychainName != "" {
kc.path = cfg.KeychainName + ".keychain"
Expand Down Expand Up @@ -271,6 +285,9 @@ func (k *keychain) createOrOpen() (gokeychain.Keychain, error) {
debugf("Checking keychain status")
err := kc.Status()
if err == nil {
if k.useTouchID {
return k.openWithTouchID()
}
debugf("Keychain status returned nil, keychain exists")
return kc, nil
}
Expand All @@ -294,3 +311,87 @@ func (k *keychain) createOrOpen() (gokeychain.Keychain, error) {
debugf("Creating keychain %s with provided password", k.path)
return gokeychain.NewKeychain(k.path, passphrase)
}

func (k *keychain) openWithTouchID() (gokeychain.Keychain, error) {
if k.isTouchIDAuthenticated {
return gokeychain.NewWithPath(k.path), nil
}

debugf("checking with touchid")
ok, err := touchid.Authenticate("unlock " + k.path)
if err != nil {
return gokeychain.Keychain{}, fmt.Errorf("failed to authenticate with biometrics: %v", err)
}
if !ok {
return gokeychain.Keychain{}, fmt.Errorf("failed to authenticate with biometrics")
}

k.isTouchIDAuthenticated = true

debugf("looking up password in login.keychain")
query := gokeychain.NewItem()
query.SetSecClass(gokeychain.SecClassGenericPassword)
query.SetService(biometricsService)
query.SetAccount(biometricsAccount)
query.SetLabel(fmt.Sprintf(biometricsLabel, k.path))
query.SetMatchLimit(gokeychain.MatchLimitOne)
query.SetReturnData(true)

results, err := gokeychain.QueryItem(query)
if err != nil {
return gokeychain.Keychain{}, fmt.Errorf("failed to query keychain: %v", err)
}

if len(results) != 1 {
kc, err := k.setupTouchID()
if err != nil {
return gokeychain.Keychain{}, fmt.Errorf("failed to setup touchid: %v", err)
}
return kc, nil
} else {
debugf("found password in login.keychain, unlocking %s with stored password", k.path)
if err := gokeychain.UnlockAtPath(k.path, string(results[0].Data)); err != nil {
return gokeychain.Keychain{}, fmt.Errorf("failed to unlock keychain: %v", err)
}
passPhrase := string(results[0].Data)
return gokeychain.NewKeychain(k.path, passPhrase)
}

return gokeychain.NewWithPath(k.path), nil
}

func (k *keychain) setupTouchID() (gokeychain.Keychain, error) {
fmt.Println("\nTo use Touch ID for authentication, your keychain password needs to be stored in your login keychain.\n" +
"You will be prompted for your password.\n")
passphrase, err := k.passwordFunc(fmt.Sprintf("Enter passphrase for %q", k.path))
if err != nil {
return gokeychain.Keychain{}, fmt.Errorf("failed to get password: %v", err)
}

fmt.Println()
debugf("locking keychain %s", k.path)
if err := gokeychain.LockAtPath(k.path); err != nil {
return gokeychain.Keychain{}, fmt.Errorf("failed to lock keychain: %v", err)
}

debugf("unlocking keychain %s", k.path)
if err := gokeychain.UnlockAtPath(k.path, passphrase); err != nil {
return gokeychain.Keychain{}, fmt.Errorf("failed to unlock keychain: %v", err)
}

item := gokeychain.NewItem()
item.SetSecClass(gokeychain.SecClassGenericPassword)
item.SetService(biometricsService)
item.SetAccount(biometricsAccount)
item.SetLabel(fmt.Sprintf(biometricsLabel, k.path))
item.SetData([]byte(passphrase))
item.SetSynchronizable(gokeychain.SynchronizableNo)
item.SetAccessible(gokeychain.AccessibleWhenUnlocked)

debugf("Adding service=%q, account=%q to osx keychain %s", biometricsService, biometricsAccount, k.path)
if err := gokeychain.AddItem(item); err != nil {
return gokeychain.Keychain{}, fmt.Errorf("failed to add item to keychain: %v", err)
}

return gokeychain.NewKeychain(k.path, passphrase)
}

0 comments on commit 9eca1df

Please sign in to comment.