fix: add keys to agent if requested

This will add the keys to the ssh agent if requested by the ssh config
file. It also adds the IdentityAgent keyword to override the environment
variable.
This commit is contained in:
Sylvia Crowe 2024-06-10 13:52:19 -07:00
parent a3de9768f2
commit c46c2a926c

View File

@ -66,7 +66,7 @@ func createDummySigner() ([]ssh.Signer, error) {
// they were successes. An error in this function prevents any other // they were successes. An error in this function prevents any other
// keys from being attempted. But if there's an error because of a dummy // keys from being attempted. But if there's an error because of a dummy
// file, the library can still try again with a new key. // file, the library can still try again with a new key.
func createPublicKeyCallback(connCtx context.Context, sshKeywords *SshKeywords, passphrase string, authSockSignersExt []ssh.Signer) func() ([]ssh.Signer, error) { func createPublicKeyCallback(connCtx context.Context, sshKeywords *SshKeywords, passphrase string, authSockSignersExt []ssh.Signer, agentClient agent.ExtendedAgent) func() ([]ssh.Signer, error) {
var identityFiles []string var identityFiles []string
existingKeys := make(map[string][]byte) existingKeys := make(map[string][]byte)
@ -108,6 +108,25 @@ func createPublicKeyCallback(connCtx context.Context, sshKeywords *SshKeywords,
// skip this key and try with the next // skip this key and try with the next
return createDummySigner() return createDummySigner()
} }
unencryptedPrivateKey, err := ssh.ParseRawPrivateKey(privateKey)
if _, ok := err.(*ssh.PassphraseMissingError); !ok {
// skip this key and try with the next
return createDummySigner()
}
if err == nil {
signer, err := ssh.NewSignerFromKey(unencryptedPrivateKey)
if err == nil {
if sshKeywords.AddKeysToAgent && agentClient != nil {
agentClient.Add(agent.AddedKey{
PrivateKey: unencryptedPrivateKey,
})
}
return []ssh.Signer{signer}, err
}
}
signer, err := ssh.ParsePrivateKey(privateKey) signer, err := ssh.ParsePrivateKey(privateKey)
if err == nil { if err == nil {
return []ssh.Signer{signer}, err return []ssh.Signer{signer}, err
@ -117,14 +136,34 @@ func createPublicKeyCallback(connCtx context.Context, sshKeywords *SshKeywords,
return createDummySigner() return createDummySigner()
} }
unencryptedPrivateKey, err = ssh.ParseRawPrivateKeyWithPassphrase(privateKey, []byte(passphrase))
if err != x509.IncorrectPasswordError && err.Error() != "bcrypt_pbkdf: empty password" {
// skip this key and try with the next
return createDummySigner()
}
if err == nil {
signer, err := ssh.NewSignerFromKey(unencryptedPrivateKey)
if err == nil {
if sshKeywords.AddKeysToAgent && agentClient != nil {
agentClient.Add(agent.AddedKey{
PrivateKey: unencryptedPrivateKey,
})
}
return []ssh.Signer{signer}, err
}
}
/*
signer, err = ssh.ParsePrivateKeyWithPassphrase(privateKey, []byte(passphrase)) signer, err = ssh.ParsePrivateKeyWithPassphrase(privateKey, []byte(passphrase))
if err == nil { if err == nil {
log.Printf("with passphrase %v\n", signer.PublicKey().Marshal())
return []ssh.Signer{signer}, err return []ssh.Signer{signer}, err
} }
if err != x509.IncorrectPasswordError && err.Error() != "bcrypt_pbkdf: empty password" { if err != x509.IncorrectPasswordError && err.Error() != "bcrypt_pbkdf: empty password" {
// skip this key and try with the next // skip this key and try with the next
return createDummySigner() return createDummySigner()
} }
*/
// batch mode deactivates user input // batch mode deactivates user input
if sshKeywords.BatchMode { if sshKeywords.BatchMode {
@ -145,12 +184,33 @@ func createPublicKeyCallback(connCtx context.Context, sshKeywords *SshKeywords,
// trying keys // trying keys
return nil, UserInputCancelError{Err: err} return nil, UserInputCancelError{Err: err}
} }
unencryptedPrivateKey, err = ssh.ParseRawPrivateKeyWithPassphrase(privateKey, []byte([]byte(response.Text)))
if err != nil {
// skip this key and try with the next
return createDummySigner()
}
signer, err = ssh.NewSignerFromKey(unencryptedPrivateKey)
if err != nil {
// skip this key and try with the next
return createDummySigner()
}
if sshKeywords.AddKeysToAgent && agentClient != nil {
agentClient.Add(agent.AddedKey{
PrivateKey: unencryptedPrivateKey,
})
}
return []ssh.Signer{signer}, err
/*
signer, err = ssh.ParsePrivateKeyWithPassphrase(privateKey, []byte(response.Text)) signer, err = ssh.ParsePrivateKeyWithPassphrase(privateKey, []byte(response.Text))
if err != nil { if err != nil {
// skip this key and try with the next // skip this key and try with the next
return createDummySigner() return createDummySigner()
} }
log.Printf("with passphrase %v\n", signer.PublicKey().Marshal())
return []ssh.Signer{signer}, err return []ssh.Signer{signer}, err
*/
} }
} }
@ -540,7 +600,7 @@ func DialContext(ctx context.Context, network string, addr string, config *ssh.C
} }
func ConnectToClient(connCtx context.Context, opts *sstore.SSHOpts, remoteDisplayName string, sshAuthSock string) (*ssh.Client, error) { func ConnectToClient(connCtx context.Context, opts *sstore.SSHOpts, remoteDisplayName string, sshAuthSock string) (*ssh.Client, error) {
sshConfigKeywords, err := findSshConfigKeywords(opts.SSHHost) sshConfigKeywords, err := findSshConfigKeywords(opts.SSHHost, sshAuthSock)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -550,16 +610,17 @@ func ConnectToClient(connCtx context.Context, opts *sstore.SSHOpts, remoteDispla
return nil, err return nil, err
} }
conn, err := net.Dial("unix", sshAuthSock) conn, err := net.Dial("unix", sshKeywords.IdentityAgent)
var authSockSigners []ssh.Signer var authSockSigners []ssh.Signer
var agentClient agent.ExtendedAgent
if err != nil { if err != nil {
log.Printf("Failed to open SSH_AUTH_SOCK: %v", err) log.Printf("Failed to open Identity Agent Socket: %v", err)
} else { } else {
agentClient := agent.NewClient(conn) agentClient = agent.NewClient(conn)
authSockSigners, _ = agentClient.Signers() authSockSigners, _ = agentClient.Signers()
} }
publicKeyCallback := ssh.PublicKeysCallback(createPublicKeyCallback(connCtx, sshKeywords, opts.SSHPassword, authSockSigners)) publicKeyCallback := ssh.PublicKeysCallback(createPublicKeyCallback(connCtx, sshKeywords, opts.SSHPassword, authSockSigners, agentClient))
keyboardInteractive := ssh.KeyboardInteractive(createCombinedKbdInteractiveChallenge(connCtx, opts.SSHPassword, remoteDisplayName)) keyboardInteractive := ssh.KeyboardInteractive(createCombinedKbdInteractiveChallenge(connCtx, opts.SSHPassword, remoteDisplayName))
passwordCallback := ssh.PasswordCallback(createCombinedPasswordCallbackPrompt(connCtx, opts.SSHPassword, remoteDisplayName)) passwordCallback := ssh.PasswordCallback(createCombinedPasswordCallbackPrompt(connCtx, opts.SSHPassword, remoteDisplayName))
@ -622,6 +683,8 @@ type SshKeywords struct {
PasswordAuthentication bool PasswordAuthentication bool
KbdInteractiveAuthentication bool KbdInteractiveAuthentication bool
PreferredAuthentications []string PreferredAuthentications []string
AddKeysToAgent bool
IdentityAgent string
} }
func combineSshKeywords(opts *sstore.SSHOpts, configKeywords *SshKeywords) (*SshKeywords, error) { func combineSshKeywords(opts *sstore.SSHOpts, configKeywords *SshKeywords) (*SshKeywords, error) {
@ -671,6 +734,8 @@ func combineSshKeywords(opts *sstore.SSHOpts, configKeywords *SshKeywords) (*Ssh
sshKeywords.PasswordAuthentication = configKeywords.PasswordAuthentication sshKeywords.PasswordAuthentication = configKeywords.PasswordAuthentication
sshKeywords.KbdInteractiveAuthentication = configKeywords.KbdInteractiveAuthentication sshKeywords.KbdInteractiveAuthentication = configKeywords.KbdInteractiveAuthentication
sshKeywords.PreferredAuthentications = configKeywords.PreferredAuthentications sshKeywords.PreferredAuthentications = configKeywords.PreferredAuthentications
sshKeywords.AddKeysToAgent = configKeywords.AddKeysToAgent
sshKeywords.IdentityAgent = configKeywords.IdentityAgent
return sshKeywords, nil return sshKeywords, nil
} }
@ -678,7 +743,7 @@ func combineSshKeywords(opts *sstore.SSHOpts, configKeywords *SshKeywords) (*Ssh
// note that a `var == "yes"` will default to false // note that a `var == "yes"` will default to false
// but `var != "no"` will default to true // but `var != "no"` will default to true
// when given unexpected strings // when given unexpected strings
func findSshConfigKeywords(hostPattern string) (*SshKeywords, error) { func findSshConfigKeywords(hostPattern string, sshAuthSock string) (*SshKeywords, error) {
ssh_config.ReloadConfigs() ssh_config.ReloadConfigs()
sshKeywords := &SshKeywords{} sshKeywords := &SshKeywords{}
var err error var err error
@ -733,5 +798,21 @@ func findSshConfigKeywords(hostPattern string) (*SshKeywords, error) {
} }
sshKeywords.PreferredAuthentications = strings.Split(preferredAuthenticationsRaw, ",") sshKeywords.PreferredAuthentications = strings.Split(preferredAuthenticationsRaw, ",")
addKeysToAgentRaw, err := ssh_config.GetStrict(hostPattern, "AddKeysToAgent")
if err != nil {
return nil, err
}
sshKeywords.AddKeysToAgent = (strings.ToLower(addKeysToAgentRaw) == "yes")
identityAgentRaw, err := ssh_config.GetStrict(hostPattern, "IdentityAgent")
if err != nil {
return nil, err
}
if identityAgentRaw == "" {
sshKeywords.IdentityAgent = sshAuthSock
} else {
sshKeywords.IdentityAgent = identityAgentRaw
}
return sshKeywords, nil return sshKeywords, nil
} }