package ssh import ( "bytes" "crypto/rand" "errors" "fmt" "net" "os" "github.com/quexten/goldwarden/agent/sockets" "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" "golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh/agent" ) var log = logging.GetLogger("Goldwarden", "SSH") 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) if approved, err := pinentry.GetApproval("SSH Key Signing Request", message); err != nil || !approved { log.Info("Sign Request for key: %s denied", sshKey.Name) return nil, errors.New("Approval not given") } if !biometrics.CheckBiometrics(biometrics.SSHKey) { 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) } }