func (m *HostKeyCallbackHelper) SetHostKeyCallback(cfg *ssh.ClientConfig) (*ssh.ClientConfig, error) {
	var err error
	if m.HostKeyCallback == nil {
		if m.HostKeyCallback, err = NewKnownHostsCallback(); err != nil {
			return cfg, err
		}
	}
	cfg.HostKeyCallback = m.HostKeyCallback
	return cfg, nil
}
SetHostKeyCallback sets the field HostKeyCallback in the given cfg. If
HostKeyCallback is empty a default callback is created using
NewKnownHostsCallback.
package ssh
import (
	"crypto/x509"
	"encoding/pem"
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"os/user"
	"path/filepath"
	"gopkg.in/src-d/go-git.v4/plumbing/transport"
	"github.com/mitchellh/go-homedir"
	"github.com/xanzy/ssh-agent"
	"golang.org/x/crypto/ssh"
	"golang.org/x/crypto/ssh/knownhosts"
)
const DefaultUsername = "git"
type AuthMethod interface {
	transport.AuthMethod
	ClientConfig() (*ssh.ClientConfig, error)
}
const (
	KeyboardInteractiveName = "ssh-keyboard-interactive"
	PasswordName            = "ssh-password"
	PasswordCallbackName    = "ssh-password-callback"
	PublicKeysName          = "ssh-public-keys"
	PublicKeysCallbackName  = "ssh-public-key-callback"
)
type KeyboardInteractive struct {
	User      string
	Challenge ssh.KeyboardInteractiveChallenge
	HostKeyCallbackHelper
}
func (a *KeyboardInteractive) Name() string {
	return KeyboardInteractiveName
}
func (a *KeyboardInteractive) String() string {
	return fmt.Sprintf("user: %s, name: %s", a.User, a.Name())
}
func (a *KeyboardInteractive) ClientConfig() (*ssh.ClientConfig, error) {
	return a.SetHostKeyCallback(&ssh.ClientConfig{
		User: a.User,
		Auth: []ssh.AuthMethod{
			ssh.KeyboardInteractiveChallenge(a.Challenge),
		},
	})
}
type Password struct {
	User     string
	Password string
	HostKeyCallbackHelper
}
func (a *Password) Name() string {
	return PasswordName
}
func (a *Password) String() string {
	return fmt.Sprintf("user: %s, name: %s", a.User, a.Name())
}
func (a *Password) ClientConfig() (*ssh.ClientConfig, error) {
	return a.SetHostKeyCallback(&ssh.ClientConfig{
		User: a.User,
		Auth: []ssh.AuthMethod{ssh.Password(a.Password)},
	})
}
type PasswordCallback struct {
	User     string
	Callback func() (pass string, err error)
	HostKeyCallbackHelper
}
func (a *PasswordCallback) Name() string {
	return PasswordCallbackName
}
func (a *PasswordCallback) String() string {
	return fmt.Sprintf("user: %s, name: %s", a.User, a.Name())
}
func (a *PasswordCallback) ClientConfig() (*ssh.ClientConfig, error) {
	return a.SetHostKeyCallback(&ssh.ClientConfig{
		User: a.User,
		Auth: []ssh.AuthMethod{ssh.PasswordCallback(a.Callback)},
	})
}
type PublicKeys struct {
	User   string
	Signer ssh.Signer
	HostKeyCallbackHelper
}
func NewPublicKeys(user string, pemBytes []byte, password string) (*PublicKeys, error) {
	block, _ := pem.Decode(pemBytes)
	if x509.IsEncryptedPEMBlock(block) {
		key, err := x509.DecryptPEMBlock(block, []byte(password))
		if err != nil {
			return nil, err
		}
		block = &pem.Block{Type: block.Type, Bytes: key}
		pemBytes = pem.EncodeToMemory(block)
	}
	signer, err := ssh.ParsePrivateKey(pemBytes)
	if err != nil {
		return nil, err
	}
	return &PublicKeys{User: user, Signer: signer}, nil
}
func NewPublicKeysFromFile(user, pemFile, password string) (*PublicKeys, error) {
	bytes, err := ioutil.ReadFile(pemFile)
	if err != nil {
		return nil, err
	}
	return NewPublicKeys(user, bytes, password)
}
func (a *PublicKeys) Name() string {
	return PublicKeysName
}
func (a *PublicKeys) String() string {
	return fmt.Sprintf("user: %s, name: %s", a.User, a.Name())
}
func (a *PublicKeys) ClientConfig() (*ssh.ClientConfig, error) {
	return a.SetHostKeyCallback(&ssh.ClientConfig{
		User: a.User,
		Auth: []ssh.AuthMethod{ssh.PublicKeys(a.Signer)},
	})
}
func username() (string, error) {
	var username string
	if user, err := user.Current(); err == nil {
		username = user.Username
	} else {
		username = os.Getenv("USER")
	}
	if username == "" {
		return "", errors.New("failed to get username")
	}
	return username, nil
}
type PublicKeysCallback struct {
	User     string
	Callback func() (signers []ssh.Signer, err error)
	HostKeyCallbackHelper
}
func NewSSHAgentAuth(u string) (*PublicKeysCallback, error) {
	var err error
	if u == "" {
		u, err = username()
		if err != nil {
			return nil, err
		}
	}
	a, _, err := sshagent.New()
	if err != nil {
		return nil, fmt.Errorf("error creating SSH agent: %q", err)
	}
	return &PublicKeysCallback{
		User:     u,
		Callback: a.Signers,
	}, nil
}
func (a *PublicKeysCallback) Name() string {
	return PublicKeysCallbackName
}
func (a *PublicKeysCallback) String() string {
	return fmt.Sprintf("user: %s, name: %s", a.User, a.Name())
}
func (a *PublicKeysCallback) ClientConfig() (*ssh.ClientConfig, error) {
	return a.SetHostKeyCallback(&ssh.ClientConfig{
		User: a.User,
		Auth: []ssh.AuthMethod{ssh.PublicKeysCallback(a.Callback)},
	})
}
func NewKnownHostsCallback(files ...string) (ssh.HostKeyCallback, error) {
	files, err := getDefaultKnownHostsFiles()
	if err != nil {
		return nil, err
	}
	files, err = filterKnownHostsFiles(files...)
	if err != nil {
		return nil, err
	}
	return knownhosts.New(files...)
}
func getDefaultKnownHostsFiles() ([]string, error) {
	files := filepath.SplitList(os.Getenv("SSH_KNOWN_HOSTS"))
	if len(files) != 0 {
		return files, nil
	}
	homeDirPath, err := homedir.Dir()
	if err != nil {
		return nil, err
	}
	return []string{
		filepath.Join(homeDirPath, "/.ssh/known_hosts"),
		"/etc/ssh/ssh_known_hosts",
	}, nil
}
func filterKnownHostsFiles(files ...string) ([]string, error) {
	var out []string
	for _, file := range files {
		_, err := os.Stat(file)
		if err == nil {
			out = append(out, file)
			continue
		}
		if !os.IsNotExist(err) {
			return nil, err
		}
	}
	if len(out) == 0 {
		return nil, fmt.Errorf("unable to find any valid know_hosts file, set SSH_KNOWN_HOSTS env variable")
	}
	return out, nil
}
type HostKeyCallbackHelper struct {
	HostKeyCallback ssh.HostKeyCallback
}