2023-07-17 04:23:26 +03:00
package ssh
import (
"bytes"
"crypto/rand"
"errors"
"fmt"
"net"
"os"
"github.com/quexten/goldwarden/agent/sockets"
2023-09-12 02:22:48 +03:00
"github.com/quexten/goldwarden/agent/systemauth/biometrics"
2023-09-12 03:54:46 +03:00
"github.com/quexten/goldwarden/agent/systemauth/pinentry"
2023-07-17 04:23:26 +03:00
"github.com/quexten/goldwarden/agent/vault"
2023-08-21 19:37:34 +03:00
"github.com/quexten/goldwarden/logging"
2023-07-17 04:23:26 +03:00
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/agent"
)
2023-08-21 19:37:34 +03:00
var log = logging . GetLogger ( "Goldwarden" , "SSH" )
2023-07-17 04:23:26 +03:00
type vaultAgent struct {
vault * vault . Vault
unlockRequestAction func ( ) bool
context sockets . CallingContext
}
func ( vaultAgent ) Add ( key agent . AddedKey ) error {
return nil
}
func ( vaultAgent vaultAgent ) List ( ) ( [ ] * agent . Key , error ) {
if vaultAgent . vault . Keyring . IsLocked ( ) {
if ! vaultAgent . unlockRequestAction ( ) {
return nil , errors . New ( "vault is locked" )
}
}
vaultSSHKeys := ( * vaultAgent . vault ) . GetSSHKeys ( )
var sshKeys [ ] * agent . Key
for _ , vaultSSHKey := range vaultSSHKeys {
signer , err := ssh . ParsePrivateKey ( [ ] byte ( vaultSSHKey . Key ) )
if err != nil {
continue
}
pub := signer . PublicKey ( )
sshKeys = append ( sshKeys , & agent . Key {
Format : pub . Type ( ) ,
Blob : pub . Marshal ( ) ,
Comment : vaultSSHKey . Name } )
}
return sshKeys , nil
}
func ( vaultAgent ) Lock ( passphrase [ ] byte ) error {
return nil
}
func ( vaultAgent ) Remove ( key ssh . PublicKey ) error {
return nil
}
func ( vaultAgent ) RemoveAll ( ) error {
return nil
}
func Eq ( a , b ssh . PublicKey ) bool {
return 0 == bytes . Compare ( a . Marshal ( ) , b . Marshal ( ) )
}
func ( vaultAgent vaultAgent ) Sign ( key ssh . PublicKey , data [ ] byte ) ( * ssh . Signature , error ) {
log . Info ( "Sign Request for key: %s" , ssh . FingerprintSHA256 ( key ) )
if vaultAgent . vault . Keyring . IsLocked ( ) {
if ! vaultAgent . unlockRequestAction ( ) {
return nil , errors . New ( "vault is locked" )
}
}
var signer ssh . Signer
var sshKey * vault . SSHKey
vaultSSHKeys := ( * vaultAgent . vault ) . GetSSHKeys ( )
for _ , vaultSSHKey := range vaultSSHKeys {
sg , err := ssh . ParsePrivateKey ( [ ] byte ( vaultSSHKey . Key ) )
if err != nil {
return nil , err
}
if Eq ( sg . PublicKey ( ) , key ) {
signer = sg
sshKey = & vaultSSHKey
break
}
}
message := fmt . Sprintf ( "%s on %s>%s>%s is requesting signage with key %s" , vaultAgent . context . UserName , vaultAgent . context . GrandParentProcessName , vaultAgent . context . ParentProcessName , vaultAgent . context . ProcessName , sshKey . Name )
2023-09-12 03:54:46 +03:00
if approved , err := pinentry . GetApproval ( "SSH Key Signing Request" , message ) ; err != nil || ! approved {
2023-07-17 04:23:26 +03:00
log . Info ( "Sign Request for key: %s denied" , sshKey . Name )
return nil , errors . New ( "Approval not given" )
}
2023-09-12 02:22:48 +03:00
if ! biometrics . CheckBiometrics ( biometrics . SSHKey ) {
2023-07-17 04:23:26 +03:00
log . Info ( "Sign Request for key: %s denied" , key . Marshal ( ) )
return nil , errors . New ( "Biometrics not checked" )
}
var rand = rand . Reader
log . Info ( "Sign Request for key: %s %s accepted" , ssh . FingerprintSHA256 ( key ) , sshKey . Name )
return signer . Sign ( rand , data )
}
func ( vaultAgent ) Signers ( ) ( [ ] ssh . Signer , error ) {
return [ ] ssh . Signer { } , nil
}
func ( vaultAgent ) Unlock ( passphrase [ ] byte ) error {
return nil
}
type SSHAgentServer struct {
vault * vault . Vault
unlockRequestAction func ( ) bool
}
func ( v * SSHAgentServer ) SetUnlockRequestAction ( action func ( ) bool ) {
v . unlockRequestAction = action
}
func NewVaultAgent ( vault * vault . Vault ) SSHAgentServer {
return SSHAgentServer {
vault : vault ,
unlockRequestAction : func ( ) bool {
log . Info ( "Unlock Request, but no action defined" )
return false
} ,
}
}
func ( v SSHAgentServer ) Serve ( ) {
home , err := os . UserHomeDir ( )
if err != nil {
panic ( err )
}
path := home + "/.goldwarden-ssh-agent.sock"
if _ , err := os . Stat ( path ) ; err == nil {
if err := os . Remove ( path ) ; err != nil {
log . Error ( "Could not remove old socket file: %s" , err )
return
}
}
listener , err := net . Listen ( "unix" , path )
if err != nil {
panic ( err )
}
log . Info ( "SSH Agent listening on %s" , path )
for {
var conn , err = listener . Accept ( )
if err != nil {
panic ( err )
}
callingContext := sockets . GetCallingContext ( conn )
log . Info ( "SSH Agent connection from %s>%s>%s \nby user %s" , callingContext . GrandParentProcessName , callingContext . ParentProcessName , callingContext . ProcessName , callingContext . UserName )
log . Info ( "SSH Agent connection accepted" )
go agent . ServeAgent ( vaultAgent {
vault : v . vault ,
unlockRequestAction : v . unlockRequestAction ,
context : callingContext ,
} , conn )
}
}