Rework systemauth

This commit is contained in:
Bernd Schoolmann 2023-09-19 21:49:56 +02:00
parent 368fe2bae6
commit e53a49b47a
No known key found for this signature in database
15 changed files with 166 additions and 78 deletions

View File

@ -31,4 +31,5 @@ package() {
cd "$pkgname-$pkgver"
install -Dm755 build/$pkgname "$pkgdir"/usr/bin/$pkgname
install -Dm644 "$srcdir/$pkgname-$pkgver/resources/com.quexten.goldwarden.policy" "$pkgdir/usr/share/polkit-1/actions/com.quexten.goldwarden.policy"
chown root:root "$pkgdir/usr/share/polkit-1/actions/com.quexten.goldwarden.policy"
}

View File

@ -8,7 +8,6 @@ import (
"github.com/quexten/goldwarden/agent/config"
"github.com/quexten/goldwarden/agent/sockets"
"github.com/quexten/goldwarden/agent/systemauth"
"github.com/quexten/goldwarden/agent/systemauth/biometrics"
"github.com/quexten/goldwarden/agent/vault"
"github.com/quexten/goldwarden/ipc"
)
@ -81,16 +80,16 @@ func ensureIsNotLocked(action Action) Action {
})
}
systemauth.CreateSession(*ctx)
systemauth.CreatePinSession(*ctx)
}
return action(request, cfg, vault, ctx)
}
}
func ensureBiometricsAuthorized(approvalType biometrics.Approval, action Action) Action {
func ensureBiometricsAuthorized(approvalType systemauth.SessionType, action Action) Action {
return func(request ipc.IPCMessage, cfg *config.Config, vault *vault.Vault, ctx *sockets.CallingContext) (ipc.IPCMessage, error) {
if !systemauth.CheckBiometrics(ctx, approvalType) {
if permission, err := systemauth.GetPermission(approvalType, *ctx, cfg); err != nil || !permission {
return ipc.IPCMessageFromPayload(ipc.ActionResponse{
Success: false,
Message: "Polkit authorization failed required",
@ -101,6 +100,6 @@ func ensureBiometricsAuthorized(approvalType biometrics.Approval, action Action)
}
}
func ensureEverything(approvalType biometrics.Approval, action Action) Action {
func ensureEverything(approvalType systemauth.SessionType, action Action) Action {
return ensureIsNotLocked(ensureIsLoggedIn(ensureBiometricsAuthorized(approvalType, action)))
}

View File

@ -6,6 +6,7 @@ import (
"github.com/quexten/goldwarden/agent/config"
"github.com/quexten/goldwarden/agent/sockets"
"github.com/quexten/goldwarden/agent/systemauth"
"github.com/quexten/goldwarden/agent/systemauth/biometrics"
"github.com/quexten/goldwarden/agent/systemauth/pinentry"
"github.com/quexten/goldwarden/agent/vault"
@ -13,6 +14,17 @@ import (
)
func handleGetBiometricsKey(request ipc.IPCMessage, cfg *config.Config, vault *vault.Vault, ctx *sockets.CallingContext) (response ipc.IPCMessage, err error) {
if !(systemauth.VerifyPinSession(*ctx) || biometrics.CheckBiometrics(biometrics.BrowserBiometrics)) {
response, err = ipc.IPCMessageFromPayload(ipc.ActionResponse{
Success: false,
Message: "not approved",
})
if err != nil {
return ipc.IPCMessage{}, err
}
return response, nil
}
if approved, err := pinentry.GetApproval("Approve Credential Access", fmt.Sprintf("%s on %s>%s>%s is trying to access your vault encryption key for browser biometric unlock.", ctx.UserName, ctx.GrandParentProcessName, ctx.ParentProcessName, ctx.ProcessName)); err != nil || !approved {
response, err = ipc.IPCMessageFromPayload(ipc.ActionResponse{
Success: false,
@ -25,6 +37,9 @@ func handleGetBiometricsKey(request ipc.IPCMessage, cfg *config.Config, vault *v
}
masterKey, err := cfg.GetMasterKey()
if err != nil {
return ipc.IPCMessage{}, err
}
masterKeyB64 := base64.StdEncoding.EncodeToString(masterKey)
response, err = ipc.IPCMessageFromPayload(ipc.GetBiometricsKeyResponse{
Key: masterKeyB64,
@ -33,5 +48,5 @@ func handleGetBiometricsKey(request ipc.IPCMessage, cfg *config.Config, vault *v
}
func init() {
AgentActionsRegistry.Register(ipc.IPCMessageTypeGetBiometricsKeyRequest, ensureEverything(biometrics.BrowserBiometrics, handleGetBiometricsKey))
AgentActionsRegistry.Register(ipc.IPCMessageTypeGetBiometricsKeyRequest, ensureIsNotLocked(ensureIsLoggedIn(handleGetBiometricsKey)))
}

View File

@ -5,7 +5,7 @@ import (
"github.com/quexten/goldwarden/agent/config"
"github.com/quexten/goldwarden/agent/sockets"
"github.com/quexten/goldwarden/agent/systemauth/biometrics"
"github.com/quexten/goldwarden/agent/systemauth"
"github.com/quexten/goldwarden/agent/systemauth/pinentry"
"github.com/quexten/goldwarden/agent/vault"
"github.com/quexten/goldwarden/ipc"
@ -45,5 +45,5 @@ func handleGetCliCredentials(request ipc.IPCMessage, cfg *config.Config, vault *
}
func init() {
AgentActionsRegistry.Register(ipc.IPCMessageTypeGetCLICredentialsRequest, ensureEverything(biometrics.AccessCredential, handleGetCliCredentials))
AgentActionsRegistry.Register(ipc.IPCMessageTypeGetCLICredentialsRequest, ensureEverything(systemauth.AccessVault, handleGetCliCredentials))
}

View File

@ -10,7 +10,7 @@ import (
"github.com/quexten/goldwarden/agent/bitwarden/crypto"
"github.com/quexten/goldwarden/agent/config"
"github.com/quexten/goldwarden/agent/sockets"
"github.com/quexten/goldwarden/agent/systemauth/biometrics"
"github.com/quexten/goldwarden/agent/systemauth"
"github.com/quexten/goldwarden/agent/systemauth/pinentry"
"github.com/quexten/goldwarden/agent/vault"
"github.com/quexten/goldwarden/ipc"
@ -157,6 +157,6 @@ func handleListLoginsRequest(request ipc.IPCMessage, cfg *config.Config, vault *
}
func init() {
AgentActionsRegistry.Register(ipc.IPCMessageGetLoginRequest, ensureEverything(biometrics.AccessCredential, handleGetLoginCipher))
AgentActionsRegistry.Register(ipc.IPCMessageListLoginsRequest, ensureEverything(biometrics.AccessCredential, handleListLoginsRequest))
AgentActionsRegistry.Register(ipc.IPCMessageGetLoginRequest, ensureEverything(systemauth.AccessVault, handleGetLoginCipher))
AgentActionsRegistry.Register(ipc.IPCMessageListLoginsRequest, ensureEverything(systemauth.AccessVault, handleListLoginsRequest))
}

View File

@ -8,7 +8,7 @@ import (
"github.com/quexten/goldwarden/agent/config"
"github.com/quexten/goldwarden/agent/sockets"
"github.com/quexten/goldwarden/agent/ssh"
"github.com/quexten/goldwarden/agent/systemauth/biometrics"
"github.com/quexten/goldwarden/agent/systemauth"
"github.com/quexten/goldwarden/agent/vault"
"github.com/quexten/goldwarden/ipc"
"github.com/quexten/goldwarden/logging"
@ -57,6 +57,6 @@ func handleListSSH(msg ipc.IPCMessage, cfg *config.Config, vault *vault.Vault, c
}
func init() {
AgentActionsRegistry.Register(ipc.IPCMessageTypeCreateSSHKeyRequest, ensureEverything(biometrics.SSHKey, handleAddSSH))
AgentActionsRegistry.Register(ipc.IPCMessageTypeCreateSSHKeyRequest, ensureEverything(systemauth.SSHKey, handleAddSSH))
AgentActionsRegistry.Register(ipc.IPCMessageTypeGetSSHKeysRequest, ensureIsNotLocked(ensureIsLoggedIn(handleListSSH)))
}

View File

@ -8,7 +8,7 @@ import (
"github.com/quexten/goldwarden/agent/bitwarden/crypto"
"github.com/quexten/goldwarden/agent/config"
"github.com/quexten/goldwarden/agent/sockets"
"github.com/quexten/goldwarden/agent/systemauth/biometrics"
"github.com/quexten/goldwarden/agent/systemauth"
"github.com/quexten/goldwarden/agent/systemauth/pinentry"
"github.com/quexten/goldwarden/agent/vault"
"github.com/quexten/goldwarden/ipc"
@ -181,6 +181,6 @@ func init() {
AgentActionsRegistry.Register(ipc.IPCMessageTypeUnlockVaultRequest, handleUnlockVault)
AgentActionsRegistry.Register(ipc.IPCMessageTypeLockVaultRequest, handleLockVault)
AgentActionsRegistry.Register(ipc.IPCMessageTypeWipeVaultRequest, handleWipeVault)
AgentActionsRegistry.Register(ipc.IPCMessageTypeUpdateVaultPINRequest, ensureBiometricsAuthorized(biometrics.ChangePin, handleUpdateVaultPin))
AgentActionsRegistry.Register(ipc.IPCMessageTypeUpdateVaultPINRequest, ensureBiometricsAuthorized(systemauth.AccessVault, handleUpdateVaultPin))
AgentActionsRegistry.Register(ipc.IPCMessageTypeGetVaultPINStatusRequest, handlePinStatus)
}

View File

@ -179,11 +179,12 @@ func connectToWebsocket(ctx context.Context, vault *vault.Vault, cfg *config.Con
}
websocketLog.Info("AuthRequest details " + authRequest.RequestIpAddress + " " + authRequest.RequestDeviceType)
if approved, err := pinentry.GetApproval("Paswordless Login Request", "Do you want to allow "+authRequest.RequestIpAddress+" ("+authRequest.RequestDeviceType+") to login to your account?"); err != nil || !approved {
var message = "Do you want to allow " + authRequest.RequestIpAddress + " (" + authRequest.RequestDeviceType + ") to login to your account?"
if approved, err := pinentry.GetApproval("Paswordless Login Request", message); err != nil || !approved {
websocketLog.Info("AuthRequest denied")
break
}
if !biometrics.CheckBiometrics(biometrics.AccessCredential) {
if !biometrics.CheckBiometrics(biometrics.AccessVault) {
websocketLog.Info("AuthRequest denied - biometrics required")
break
}

View File

@ -43,7 +43,6 @@ type RuntimeConfig struct {
User string
Password string
Pin string
SessionToken string
}
type ConfigFile struct {
@ -121,6 +120,18 @@ func (c *Config) Unlock(password string) bool {
return true
}
func (c *Config) VerifyPin(password string) bool {
key := argon2.Key([]byte(password), []byte(c.ConfigFile.DeviceUUID), KDFIterations, KDFMemory, KDFThreads, 32)
debug.FreeOSMemory()
keyHash := sha3.Sum256(key)
configKeyHash := hex.EncodeToString(keyHash[:])
if cryptoSubtle.ConstantTimeCompare([]byte(configKeyHash), []byte(c.ConfigFile.ConfigKeyHash)) != 1 {
return false
} else {
return true
}
}
func (c *Config) Lock() {
c.mu.Lock()
defer c.mu.Unlock()

View File

@ -8,9 +8,9 @@ import (
"net"
"os"
"github.com/quexten/goldwarden/agent/config"
"github.com/quexten/goldwarden/agent/sockets"
"github.com/quexten/goldwarden/agent/systemauth"
"github.com/quexten/goldwarden/agent/systemauth/biometrics"
"github.com/quexten/goldwarden/agent/systemauth/pinentry"
"github.com/quexten/goldwarden/agent/vault"
"github.com/quexten/goldwarden/logging"
@ -22,6 +22,7 @@ var log = logging.GetLogger("Goldwarden", "SSH")
type vaultAgent struct {
vault *vault.Vault
config *config.Config
unlockRequestAction func() bool
context sockets.CallingContext
}
@ -77,7 +78,7 @@ func (vaultAgent vaultAgent) Sign(key ssh.PublicKey, data []byte) (*ssh.Signatur
return nil, errors.New("vault is locked")
}
systemauth.CreateSession(vaultAgent.context)
systemauth.CreatePinSession(vaultAgent.context)
}
var signer ssh.Signer
@ -103,7 +104,7 @@ func (vaultAgent vaultAgent) Sign(key ssh.PublicKey, data []byte) (*ssh.Signatur
return nil, errors.New("Approval not given")
}
if !systemauth.CheckBiometrics(&vaultAgent.context, biometrics.SSHKey) {
if permission, err := systemauth.GetPermission(systemauth.SSHKey, vaultAgent.context, vaultAgent.config); err != nil || !permission {
log.Info("Sign Request for key: %s denied", key.Marshal())
return nil, errors.New("Biometrics not checked")
}
@ -124,6 +125,7 @@ func (vaultAgent) Unlock(passphrase []byte) error {
type SSHAgentServer struct {
vault *vault.Vault
config *config.Config
unlockRequestAction func() bool
}
@ -131,9 +133,10 @@ func (v *SSHAgentServer) SetUnlockRequestAction(action func() bool) {
v.unlockRequestAction = action
}
func NewVaultAgent(vault *vault.Vault) SSHAgentServer {
func NewVaultAgent(vault *vault.Vault, config *config.Config) SSHAgentServer {
return SSHAgentServer{
vault: vault,
vault: vault,
config: config,
unlockRequestAction: func() bool {
log.Info("Unlock Request, but no action defined")
return false
@ -175,6 +178,7 @@ func (v SSHAgentServer) Serve() {
go agent.ServeAgent(vaultAgent{
vault: v.vault,
config: v.config,
unlockRequestAction: v.unlockRequestAction,
context: callingContext,
}, conn)

View File

@ -19,10 +19,8 @@ func init() {
type Approval string
const (
AccessCredential Approval = "com.quexten.goldwarden.accesscredential"
ChangePin Approval = "com.quexten.goldwarden.changepin"
AccessVault Approval = "com.quexten.goldwarden.accessvault"
SSHKey Approval = "com.quexten.goldwarden.usesshkey"
ModifyVault Approval = "com.quexten.goldwarden.modifyvault"
BrowserBiometrics Approval = "com.quexten.goldwarden.browserbiometrics"
)

View File

@ -12,18 +12,9 @@ const POLICY = `<?xml version="1.0" encoding="UTF-8"?>
"http://www.freedesktop.org/standards/PolicyKit/1.0/policyconfig.dtd">
<policyconfig>
<action id="com.quexten.goldwarden.accesscredential">
<description>Allow Credential Access</description>
<message>Authenticate to allow access to a single credential</message>
<defaults>
<allow_any>auth_self</allow_any>
<allow_inactive>auth_self</allow_inactive>
<allow_active>auth_self</allow_active>
</defaults>
</action>
<action id="com.quexten.goldwarden.changepin">
<description>Approve Pin Change</description>
<message>Authenticate to change your Goldwarden PIN.</message>
<action id="com.quexten.goldwarden.accessvault">
<description>Allow access to the vault</description>
<message>Allows access to the vault entries</message>
<defaults>
<allow_any>auth_self</allow_any>
<allow_inactive>auth_self</allow_inactive>
@ -31,7 +22,7 @@ const POLICY = `<?xml version="1.0" encoding="UTF-8"?>
</defaults>
</action>
<action id="com.quexten.goldwarden.usesshkey">
<description>Use Bitwarden SSH Key</description>
<description>Use SSH Key</description>
<message>Authenticate to use an SSH Key from your vault</message>
<defaults>
<allow_any>auth_self</allow_any>
@ -39,15 +30,6 @@ const POLICY = `<?xml version="1.0" encoding="UTF-8"?>
<allow_active>auth_self</allow_active>
</defaults>
</action>
<action id="com.quexten.goldwarden.modifyvault">
<description>Modify Bitwarden Vault</description>
<message>Authenticate to allow modification of your Bitvarden vault in Goldwarden</message>
<defaults>
<allow_any>auth_self</allow_any>
<allow_inactive>auth_self</allow_inactive>
<allow_active>auth_self</allow_active>
</defaults>
</action>
<action id="com.quexten.goldwarden.browserbiometrics">
<description>Browser Biometrics</description>
<message>Authenticate to allow Goldwarden to unlock your browser.</message>
@ -68,16 +50,18 @@ func CheckBiometrics(approvalType Approval) bool {
authority, err := polkit.NewAuthority()
if err != nil {
log.Error("Failed to create polkit authority: %s", err.Error())
return false
}
result, err := authority.CheckAuthorization(
approvalType.String(),
nil,
polkit.CheckAuthorizationAllowUserInteraction, "",
uint32(polkit.AuthenticationRequiredRetained), "",
)
if err != nil {
log.Error("Failed to create polkit authority: %s", err.Error())
return false
}
@ -85,3 +69,32 @@ func CheckBiometrics(approvalType Approval) bool {
return result.IsAuthorized
}
func BiometricsWorking() bool {
if biometricsDisabled {
return false
}
authority, err := polkit.NewAuthority()
if err != nil {
return false
}
result, err := authority.EnumerateActions("en")
if err != nil {
return false
}
if len(result) == 0 {
return false
}
testFor := AccessVault
for _, action := range result {
if Approval(action.ActionID) == testFor {
return true
}
}
return false
}

View File

@ -1,15 +1,29 @@
package systemauth
import (
"fmt"
"math"
"time"
"github.com/quexten/goldwarden/agent/config"
"github.com/quexten/goldwarden/agent/sockets"
"github.com/quexten/goldwarden/agent/systemauth/biometrics"
"github.com/quexten/goldwarden/agent/systemauth/pinentry"
"github.com/quexten/goldwarden/logging"
)
var log = logging.GetLogger("Goldwarden", "Systemauth")
const tokenExpiry = 10 * time.Minute
type SessionType string
const (
AccessVault SessionType = "com.quexten.goldwarden.accessvault"
SSHKey SessionType = "com.quexten.goldwarden.usesshkey"
Pin SessionType = "com.quexten.goldwarden.pin" // this counts as all other permissions
)
var sessionStore = SessionStore{
Store: []Session{},
}
@ -19,26 +33,28 @@ type Session struct {
ParentPid int
GrandParentPid int
Expires time.Time
sessionType SessionType
}
type SessionStore struct {
Store []Session
}
func (s *SessionStore) CreateSession(pid int, parentpid int, grandparentpid int) Session {
func (s *SessionStore) CreateSession(pid int, parentpid int, grandparentpid int, sessionType SessionType) Session {
var session = Session{
Pid: pid,
ParentPid: parentpid,
GrandParentPid: grandparentpid,
Expires: time.Now().Add(tokenExpiry),
sessionType: sessionType,
}
s.Store = append(s.Store, session)
return session
}
func (s *SessionStore) VerifySession(ctx sockets.CallingContext) bool {
func (s *SessionStore) verifySession(ctx sockets.CallingContext, sessionType SessionType) bool {
for _, session := range s.Store {
if session.ParentPid == ctx.ParentProcessPid && session.GrandParentPid == ctx.GrandParentProcessPid {
if session.ParentPid == ctx.ParentProcessPid && session.GrandParentPid == ctx.GrandParentProcessPid && (session.sessionType == sessionType || session.sessionType == Pin) {
if session.Expires.After(time.Now()) {
return true
}
@ -47,34 +63,70 @@ func (s *SessionStore) VerifySession(ctx sockets.CallingContext) bool {
return false
}
func GetApproval(title string, description string, requriesBiometrics bool) (bool, error) {
approval, err := pinentry.GetApproval(title, description)
if err != nil {
return false, err
// with session
func GetPermission(sessionType SessionType, ctx sockets.CallingContext, config *config.Config) (bool, error) {
log.Info("Checking permission for " + ctx.ProcessName + " with session type " + string(sessionType))
var actionDescription = ""
biometricsApprovalType := biometrics.AccessVault
switch sessionType {
case AccessVault:
actionDescription = "access the vault"
biometricsApprovalType = biometrics.AccessVault
case SSHKey:
actionDescription = "use an SSH key for signing"
biometricsApprovalType = biometrics.SSHKey
}
if requriesBiometrics {
biometricsApproval := biometrics.CheckBiometrics(biometrics.AccessCredential)
if !biometricsApproval {
return false, nil
var message = fmt.Sprintf("Do you want to authorize %s>%s>%s to %s? (This choice will be remembered for %d minutes)", ctx.GrandParentProcessName, ctx.ParentProcessName, ctx.ProcessName, actionDescription, int(math.Floor(tokenExpiry.Minutes())))
if sessionStore.verifySession(ctx, sessionType) {
log.Info("Permission granted from cached session")
} else {
if biometrics.BiometricsWorking() {
biometricsApproval := biometrics.CheckBiometrics(biometricsApprovalType)
if !biometricsApproval {
return false, nil
}
} else {
pin, err := pinentry.GetPassword("Enter PIN", "Biometrics is not available. Enter your pin to authorize this action. "+message)
if err != nil {
return false, err
}
if !config.VerifyPin(pin) {
return false, nil
}
}
approval, err := pinentry.GetApproval("Goldwarden authorization", message)
if err != nil || !approval {
return false, err
}
log.Info("Permission granted, creating session")
sessionStore.CreateSession(ctx.ProcessPid, ctx.ParentProcessPid, ctx.GrandParentProcessPid, sessionType)
}
return approval, nil
return true, nil
}
// no session
func CheckBiometrics(callingContext *sockets.CallingContext, approvalType biometrics.Approval) bool {
if sessionStore.VerifySession(*callingContext) {
return true
}
var approval = biometrics.CheckBiometrics(approvalType)
if !approval {
var message = fmt.Sprintf("Do you want to grant %s>%s>%s one-time access your vault?", callingContext.GrandParentProcessName, callingContext.ParentProcessName, callingContext.ProcessName)
var bioApproval = biometrics.CheckBiometrics(approvalType)
if !bioApproval {
return false
}
sessionStore.CreateSession(callingContext.ProcessPid, callingContext.ParentProcessPid, callingContext.GrandParentProcessPid)
return true
approval, err := pinentry.GetApproval("Goldwarden authorization", message)
if err != nil {
log.Error(err.Error())
}
return approval
}
func CreateSession(ctx sockets.CallingContext) {
sessionStore.CreateSession(ctx.ProcessPid, ctx.ParentProcessPid, ctx.GrandParentProcessPid)
func CreatePinSession(ctx sockets.CallingContext) {
sessionStore.CreateSession(ctx.ProcessPid, ctx.ParentProcessPid, ctx.GrandParentProcessPid, Pin)
}
func VerifyPinSession(ctx sockets.CallingContext) bool {
return sessionStore.verifySession(ctx, Pin)
}

View File

@ -150,7 +150,7 @@ func StartUnixAgent(path string, runtimeConfig config.RuntimeConfig) error {
}
if !runtimeConfig.DisableSSHAgent {
vaultAgent := ssh.NewVaultAgent(vault)
vaultAgent := ssh.NewVaultAgent(vault, &cfg)
vaultAgent.SetUnlockRequestAction(func() bool {
err := cfg.TryUnlock(vault)
if err == nil {

View File

@ -6,7 +6,6 @@ import (
"github.com/quexten/goldwarden/agent/config"
"github.com/quexten/goldwarden/browserbiometrics"
"github.com/quexten/goldwarden/client/setup"
"github.com/quexten/goldwarden/cmd"
)
@ -39,7 +38,6 @@ func main() {
User: os.Getenv("GOLDWARDEN_AUTH_USER"),
Password: os.Getenv("GOLDWARDEN_AUTH_PASSWORD"),
Pin: os.Getenv("GOLDWARDEN_PIN"),
SessionToken: os.Getenv("GOLDWARDEN_SESSION_TOKEN"),
ConfigDirectory: configPath,
}
@ -57,9 +55,5 @@ func main() {
os.Setenv("GOLDWARDEN_SYSTEM_AUTH_DISABLED", "true")
}
if !setup.VerifySetup(runtimeConfig) {
return
}
cmd.Execute(runtimeConfig)
}