2024-02-03 22:55:49 +01:00

118 lines
3.4 KiB
Go

// Copyright 2015 Keybase, Inc. All rights reserved. Use of
// this source code is governed by the included BSD license.
//go:build darwin
// +build darwin
package pinentry
import (
"crypto/rand"
"encoding/hex"
"fmt"
"os"
"github.com/keybase/go-keychain"
)
const (
// pinentryServiceName is the service name that pinentry uses
// when storing into the Keychain.
pinentryServiceName = "GnuPG"
// accountNameByteLength is how many random bytes to use to
// generate the account name. 32 bytes of randomness is more
// than enough to make the account name unpredictable.
accountNameByteLength = 32
)
type pinentrySecretStoreInfo string
func (pi *pinentryInstance) useSecretStore(useSecretStore bool) (pinentrySecretStoreInfo, error) {
if !useSecretStore {
return "", nil
}
// Make account name unpredictable to make it infeasible for
// an attacker to guess (and thus sniff the passphrase). See
// https://github.com/keybase/client/issues/484#issuecomment-114313867
// .
var accountNameBytes [accountNameByteLength]byte
n, err := rand.Read(accountNameBytes[:])
if n != accountNameByteLength {
return "", fmt.Errorf("Unexpected random byte count %d", n)
}
if err != nil {
return "", err
}
accountName := "keybase-" + hex.EncodeToString(accountNameBytes[:])
// This will cause a "Save in Keychain" checkbox to appear in
// the pinentry dialog. If checked, pinentry will then save
// the entered passphrase into the keychain with the service
// name "GnuPG" and the account name equal to the passed-in
// cache-id option value.
pi.Set("OPTION", "cache-id "+accountName, &err)
if err != nil {
// It's possible that the pinentry being used doesn't support
// this option. So just return instead of causing a fatal
// error.
pi.parent.log.Debug("| Error setting pinentry cache-id OPTION: %s", err)
pi.parent.log.Debug("| Not using secret store as a result.")
return "", nil
}
return pinentrySecretStoreInfo(accountName), err
}
func (pi *pinentryInstance) shouldStoreSecret(info pinentrySecretStoreInfo) bool {
if len(info) == 0 {
return false
}
// We just want to know when the user did check the "Save in
// Keychain" checkbox, so remove whatever pinentry put into
// the keychain, and infer the state of the checkbox from the
// error (since there will be no error if an entry was found
// and deleted).
//
// This is a bit of a hack -- this may cause a dialog to pop
// up saying that the client wants to access the user's
// keychain. But this will do for now until we write our own
// pinentry.
query := keychain.NewItem()
query.SetSecClass(keychain.SecClassGenericPassword)
query.SetService(pinentryServiceName)
query.SetAccount(string(info))
query.SetMatchLimit(keychain.MatchLimitOne)
// We need to query and delete by item reference because the
// OSX keychain API only allows us to delete unowned items
// this way.
query.SetReturnRef(true)
ref, err := keychain.QueryItemRef(query)
if err != nil {
// Default to false if there was an error.
return false
}
if ref == nil {
// If not found, return false.
return false
}
defer keychain.Release(ref)
err = keychain.DeleteItemRef(ref)
if err != nil {
// Default to false if there was an error deleting.
return false
}
// Entry was found and deleted.
return true
}
func HasWindows() bool {
// We aren't in an ssh connection, so we can probably spawn a window.
return len(os.Getenv("SSH_CONNECTION")) == 0
}