2023-07-17 03:23:26 +02:00
package ssh
import (
"bytes"
"crypto/rand"
"errors"
"fmt"
"net"
"os"
2023-09-19 21:49:56 +02:00
"github.com/quexten/goldwarden/agent/config"
2023-12-28 12:58:02 +01:00
"github.com/quexten/goldwarden/agent/notify"
2023-07-17 03:23:26 +02:00
"github.com/quexten/goldwarden/agent/sockets"
2023-09-12 18:56:35 +02:00
"github.com/quexten/goldwarden/agent/systemauth"
2023-09-12 02:54:46 +02:00
"github.com/quexten/goldwarden/agent/systemauth/pinentry"
2023-07-17 03:23:26 +02:00
"github.com/quexten/goldwarden/agent/vault"
2023-08-21 18:37:34 +02:00
"github.com/quexten/goldwarden/logging"
2023-07-17 03:23:26 +02:00
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/agent"
)
2023-08-21 18:37:34 +02:00
var log = logging . GetLogger ( "Goldwarden" , "SSH" )
2023-07-17 03:23:26 +02:00
type vaultAgent struct {
vault * vault . Vault
2023-09-19 21:49:56 +02:00
config * config . Config
2023-07-17 03:23:26 +02:00
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" )
}
2023-09-19 22:13:11 +02:00
systemauth . CreatePinSession ( vaultAgent . context )
2023-07-17 03:23:26 +02:00
}
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" )
}
2023-09-12 18:56:35 +02:00
2023-09-19 21:49:56 +02:00
systemauth . CreatePinSession ( vaultAgent . context )
2023-07-17 03:23:26 +02:00
}
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
}
}
2023-12-28 13:11:00 +01:00
isGit := false
magicHeader := [ ] byte ( "SSHSIG\x00\x00\x00\x03git" )
if bytes . HasPrefix ( data , magicHeader ) {
isGit = true
}
requestTemplate := "%s on %s>%s>%s is requesting ssh signage with key %s"
if isGit {
requestTemplate = "%s on %s>%s>%s is requesting git signage with key %s"
}
message := fmt . Sprintf ( requestTemplate , vaultAgent . context . UserName , vaultAgent . context . GrandParentProcessName , vaultAgent . context . ParentProcessName , vaultAgent . context . ProcessName , sshKey . Name )
2023-07-17 03:23:26 +02:00
2023-09-12 02:54:46 +02:00
if approved , err := pinentry . GetApproval ( "SSH Key Signing Request" , message ) ; err != nil || ! approved {
2023-07-17 03:23:26 +02:00
log . Info ( "Sign Request for key: %s denied" , sshKey . Name )
return nil , errors . New ( "Approval not given" )
}
2023-09-19 21:49:56 +02:00
if permission , err := systemauth . GetPermission ( systemauth . SSHKey , vaultAgent . context , vaultAgent . config ) ; err != nil || ! permission {
2023-07-17 03:23:26 +02: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 )
2023-12-28 13:11:00 +01:00
if isGit {
notify . Notify ( "Goldwarden" , fmt . Sprintf ( "Git Signing Request Approved for %s" , sshKey . Name ) , "" , func ( ) { } )
} else {
notify . Notify ( "Goldwarden" , fmt . Sprintf ( "SSH Signing Request Approved for %s" , sshKey . Name ) , "" , func ( ) { } )
}
2023-07-17 03:23:26 +02:00
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
2023-09-19 21:49:56 +02:00
config * config . Config
2023-12-30 18:53:01 +01:00
runtimeConfig * config . RuntimeConfig
2023-07-17 03:23:26 +02:00
unlockRequestAction func ( ) bool
}
func ( v * SSHAgentServer ) SetUnlockRequestAction ( action func ( ) bool ) {
v . unlockRequestAction = action
}
2023-12-30 18:53:01 +01:00
func NewVaultAgent ( vault * vault . Vault , config * config . Config , runtimeConfig * config . RuntimeConfig ) SSHAgentServer {
2023-07-17 03:23:26 +02:00
return SSHAgentServer {
2023-12-30 18:53:01 +01:00
vault : vault ,
config : config ,
runtimeConfig : runtimeConfig ,
2023-07-17 03:23:26 +02:00
unlockRequestAction : func ( ) bool {
log . Info ( "Unlock Request, but no action defined" )
return false
} ,
}
}
func ( v SSHAgentServer ) Serve ( ) {
2023-12-30 18:53:01 +01:00
path := v . runtimeConfig . SSHAgentSocketPath
2023-07-17 03:23:26 +02:00
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 ,
2023-09-19 21:49:56 +02:00
config : v . config ,
2023-07-17 03:23:26 +02:00
unlockRequestAction : v . unlockRequestAction ,
context : callingContext ,
} , conn )
}
}