feat: create naive ui for ssh key passphrases

This isn't quite as reactive as the other methods, but it does attempt
to use publickey without a passphrase, then attempt to use the password
as the passphrase, and finally prompting the user for a passphrase. The
problem with this approach is that if multiple keys are used and they
all have passphrases, they need to all be checked up front. In practice,
this will not happen often, but it is something to be aware of.
This commit is contained in:
Sylvia Crowe 2024-02-08 02:53:37 -08:00
parent 747a2b784a
commit 89906d21d4

View File

@ -6,9 +6,10 @@ package remote
import (
"bytes"
"context"
"crypto/x509"
"encoding/base64"
"errors"
"fmt"
"log"
"net"
"os"
"os/user"
@ -34,23 +35,39 @@ func (uice UserInputCancelError) Error() string {
return uice.Err.Error()
}
func createPublicKeyAuth(identityFile string, passphrase string) (ssh.AuthMethod, error) {
func createPublicKeyAuth(identityFile string, passphrase string) (ssh.Signer, error) {
privateKey, err := os.ReadFile(base.ExpandHomeDir(identityFile))
if err != nil {
return nil, fmt.Errorf("failed to read ssh key file. err: %+v", err)
}
signer, err := ssh.ParsePrivateKey(privateKey)
if err != nil {
if errors.Is(err, &ssh.PassphraseMissingError{}) {
signer, err = ssh.ParsePrivateKeyWithPassphrase(privateKey, []byte(passphrase))
if err != nil {
return nil, fmt.Errorf("failed to parse private ssh key with passphrase. err: %+v", err)
if err == nil {
return signer, err
}
} else {
if _, ok := err.(*ssh.PassphraseMissingError); !ok {
return nil, fmt.Errorf("failed to parse private ssh key. err: %+v", err)
}
signer, err = ssh.ParsePrivateKeyWithPassphrase(privateKey, []byte(passphrase))
if err == nil {
return signer, err
}
return ssh.PublicKeys(signer), nil
if err != x509.IncorrectPasswordError && err.Error() != "bcrypt_pbkdf: empty password" {
log.Printf("qwerty: %+v", err)
return nil, fmt.Errorf("failed to parse private ssh key. err: %+v", err)
}
request := &sstore.UserInputRequestType{
ResponseType: "text",
QueryText: fmt.Sprintf("Enter passphrase for the SSH key: %s", identityFile),
Title: "Publickey Auth + Passphrase",
}
ctx, cancelFn := context.WithTimeout(context.Background(), 15*time.Second)
defer cancelFn()
response, err := sstore.MainBus.GetUserInput(ctx, request)
if err != nil {
return nil, UserInputCancelError{Err: err}
}
return ssh.ParsePrivateKeyWithPassphrase(privateKey, []byte(response.Text))
}
func createDefaultPasswordCallbackPrompt(password string) func() (secret string, err error) {
@ -406,9 +423,9 @@ func ConnectToClient(opts *sstore.SSHOpts) (*ssh.Client, error) {
return nil, err
}
var authMethods []ssh.AuthMethod
publicKeyAuth, err := createPublicKeyAuth(identityFile, opts.SSHPassword)
publicKeySigner, err := createPublicKeyAuth(identityFile, opts.SSHPassword)
if err == nil {
authMethods = append(authMethods, publicKeyAuth)
authMethods = append(authMethods, ssh.PublicKeys(publicKeySigner))
}
authMethods = append(authMethods, ssh.RetryableAuthMethod(ssh.KeyboardInteractive(createCombinedKbdInteractiveChallenge(opts.SSHPassword)), 2))
authMethods = append(authMethods, ssh.RetryableAuthMethod(ssh.PasswordCallback(createCombinedPasswordCallbackPrompt(opts.SSHPassword)), 2))