Repository: go-git/go-git

Issue:
Re-add support for SSH @cert-authority to v5
> The recently released skeema/knownhosts v1.3.0 introduced a `HostKeyDB` type that extends the `HostKeyCallback` functionality to support @cert-authority algorithms in its key `HostKeyAlgorithms` discovery functionality.

> `known_hosts` files may contain lines with @cert-authority markers to represent a certificate instead of a key. If a git remote uses cert authorities as the preferred host identification mechanism, the functionality added in skeema/knownhosts v1.3.0 is needed so that go-git can interact with this remote and verify it.

This was previously implemented on https://github.com/go-git/go-git/pull/1157 and reverted on https://github.com/go-git/go-git/pull/1346 due to https://github.com/go-git/go-git/issues/1341.

We can re-add this to v5, provided that we have enough regression testing and affected users confirm the regression is no longer in place.

cc: @Javier-varez @evanelias 

Discussion:
@Javier-varez: I will try to find time to look at this issue over the weekend. Thank you @evanelias, I did not get notified about this issue before your mentions, but I would definitely be interested to bring this functionality back into go-git.@Javier-varez: I finally managed to get some time to investigate issue #1341 so that I can figure out the right way to bring this feature back.

At this point I am relatively confident I have found the root cause of the issue, unfortunately it is more complex than I anticipated, so the fix will also be less straight forward than I was hoping for. I will describe the situation here, as well as a couple of potential alternatives to fix it.

The problem described in #1341 is related to the usage of the [`ssh.FixedHostKey`](https://github.com/golang/crypto/blob/d0a798f774735c176ed0d3500ac986957a02660f/ssh/client.go#L269) `HostKeyCallback` together with the [`PublicKeys`](https://github.com/go-git/go-git/blob/ec133065ea1201a4488bbc793ccd9337b6c06877/plumbing/transport/ssh/auth_method.go#L121) `AuthMethod` of `go-git`. I will examine how this is handled both without the implementation of #1157 and with it.

# Using the `PublicKeys` auth method with a custom `HostKeyCallback` before the introduction of #1157 

The following code overrides the configuration of the `HostKeyCallback` in `PublicKeys`.

```go 
	pk, err := ssh2.NewPublicKeys("git", []byte(privateKey), "")
	if err != nil {
		return "", fmt.Errorf("unable to create git public key: %s", err)
	}
	pk.HostKeyCallback = ssh.FixedHostKey(hostKey)
	deployBranch := gitplum.NewBranchReferenceName("main")
	r, err := git.PlainClone(dir, false, &git.CloneOptions{
		URL: url,
		Auth:          pk,
		ReferenceName: deployBranch,
		SingleBranch:  true,
		Depth:         1,
	})
```

Here, `pk.HostKeyCallback` is setting the field `HostKeyCallback` of the `HostKeyCallbackHelper`. In this configuration, the `PublicKeys` do not attempt to populate the `HostKeyCallback` field anymore when [`ClientConfig`](https://github.com/go-git/go-git/blob/ec133065ea1201a4488bbc793ccd9337b6c06877/plumbing/transport/ssh/auth_method.go#L152) is invoked. this is the right behaviour because otherwise it would have mistakenly replaced the user's custom HostKeyCallback.

However, by the time the request reaches the `connect` method, because the HostKeyAlgorithms were never set (and cannot be in `PublicKeys`), it calls the [`HostKeyAlgorithms`](https://github.com/skeema/knownhosts/blob/9485bdec8ed521b32ab0e9310619effa071a99d6/knownhosts.go#L349) function of `skeema/knownhosts` with the `ssh.FixedHostKey` callback to obtain the list of valid host key algos.

The problem is that `skeema/knownhosts` relies on the original known host `HostKeyCallback` implementation returning a [`KeyErr`] when the given key is not found (See [here](https://github.com/golang/crypto/blob/d0a798f774735c176ed0d3500ac986957a02660f/ssh/knownhosts/knownhosts.go#L368)). It does this as a way to query the supported host key algorithms of the server, but certainly this will not work with this custom HostKeyCallback.

So what ends up happening is that the `skeema/knownhost` is unable to find any host key algorithms, and therefore the ssh connection falls back to the [`supportedHostKeyAlgos `](https://github.com/golang/crypto/blob/d0a798f774735c176ed0d3500ac986957a02660f/ssh/common.go#L73), which as indicated in #1341, prioritises `ecdsa-sha2-nistp256`.

In my understanding this is as broken as the new behaviour after #1157, because it does not matter which key the user indicated, and what type of key it is, it will always attempt to use the `ecdsa` key.

# Using the `PublicKeys` auth method with a custom `HostKeyCallback` after the introduction of #1157 

With the change to support cert authorities, the change in behaviour manifested in this line:
https://github.com/go-git/go-git/blob/20b556be6a825f0255a71376a2bce4c2c8393b89/plumbing/transport/ssh/common.go#L147

Here, we are no longer using the `HostKeyCallback` provided by the user to call into `skeema/knownhosts` in order to figure out the supported host key algorithms. In this case, Skeema works well and returns the list of supported host keys, which is ordered in the same way as the user's `~/.ssh/knownhosts` file. I cannot know for sure, but I believe the author of #1341 had the `ssh-ed25519` before other host keys for the server in the file. That would explain the difference in behaviour.

However, this is also not the desired solution, because even though we get the supported host key algorithms of the server, we are not filtering them based on what the `HostKeyCallback` wants (for this example it is a `FixedHostKey`).

# Potential solutions

Solving the disagreement between `skeema/knownhosts` and the user-specified `HostKeyCallback` is not trivial. I came up with a couple of possible alternatives:

1. My preferred alternative for this issue would be to keep the behaviour before the introduction of #1157. This is not very difficult, as I can attempt to detect when the HostKeyCallback was constructed using `skeema/knownhosts` in order to only call `HostKeyAlgorithms` on the `HostKeyDB` in this case. The case of a custom `HostKeyCallback` would still exhibit the same behaviour of release `v5.12`. I have prototyped this in https://github.com/Javier-varez/go-git/commit/3aac6a2d70e413f3b321ee2fa4dd3f88a4afb1d1.
2. It is in theory possible to update `skeema/knownhosts` so that `HostKeyAlgorithms` doesn't return all algorithms supported by the server, but only those whose keys were filtered by the `HostKeyCallback` the user is planning to use. Then, we can use this in go-git. I prototyped this in https://github.com/Javier-varez/go-git/commit/befeafeaf750861372de2dfd6aa765f8b69ba554 and https://github.com/Javier-varez/knownhosts/commit/341aa9bf3a042386d6f49eb0290edceb273818d0.

@pjbgf, @evanelias, I'd appreciate it if you could take a look and share your thoughts on the topic. I'm very much open to feedback and discussion on the best way forward.
@evanelias: I believe the ideal behavior with a `FixedHostKey` would be to skip the skeema/knownhosts interaction entirely. Or equivalently, ensure it returns an empty algorithm list and no error — if I understand correctly, that's what would happen prior to the merge of #1157, as you've summarized above: since the FixedHostKey's callback is not ever returning a `KeyErr`, skeema/knownhosts then returns an empty algorithm list, which means Go's `supportedHostKeyAlgos` default gets used instead.

With a `FixedHostKey`, the client is saying they want to use that specific key, so ideally we shouldn't care whether that key is in the `known_hosts` file, and we also shouldn't care if the `known_hosts` file even exists or not. So with that in mind, I'm not sure if solution 2 is safe. If I'm understanding it correctly, solution 2 assumes that the `known_hosts` file exists *and* already contains the FixedHostKey, but neither of those might be true, depending on the user's situation.

That said, honestly with a `FixedHostKey` and a Git server that reports multiple host keys, it reasonably should be the caller's responsibility to set `ssh.ClientConfig.HostKeyAlgorithms` accurately to match the desired key's type anyway. Otherwise you end up with a situation where 1 fixed key works and the other ones don't, which is confusing, and matches @jgoodall's findings in #1341 even before the merge of #1157.

But I must add a disclaimer here that I haven't personally used `FixedHostKey` when interacting with x/crypto/ssh, and I haven't used go-git in several years, so I'm not totally certain that my understanding is correct.@Javier-varez: Thanks for your comments. I agree that it would be best to leave the HostKeyAlgorithms untouched when FixedHostKey is used. To that end, I have made sure that if a custom host key callback is provided in the AuthMethods supported by go-git, one can also set the HostKeyAlgorithms. The default behaviour is not overriding any of HostKeyCallback or HostKeyAlgorithms, in which case knownhosts is used with support for the cert authorities. 

A user may still customise the behaviour further by providing their own `AuthMethod` implementation if the default authentication methods provided by go-git are not sufficient. 

I created #1482 to add the functionality back to `v5.x`. If we agree on this behaviour, I will also port it to `main`@pjbgf: @Javier-varez thank you very much for looking into this. I'd echo's @evanelias thoughts on this, specially as in some cases, folks use `FixedHostKey` when relying on `go-git` to interact with Git repositories programmatically whilst not relying on any `known_hosts` files present on the given server. The PR LGTM and I will merge it shortly.

I'd hold on the forward porting of this to `main` for the time being, as first we need some [ironing around SSH dependencies](https://github.com/go-git/go-git/issues/1503) for v6. But I created a [new issue](https://github.com/go-git/go-git/issues/1504) to track the forward porting once that API has been defined.@pjbgf: @jgoodall this change is now merged on `releases/v5.x`, and I'm aiming to cut a release with it next week. If you have time, would you mind confirming this is not breaking your setup anymore?@jgoodall: @pjbgf  - tested with `releases/v5.x` and seems to work fine - thanks!
@pjbgf: Great stuff, thank you for the quick turn around @jgoodall.

Thank you @Javier-varez and @evanelias for working on this, I have just released [v5.15.0](https://github.com/go-git/go-git/releases/tag/v5.15.0) with these changes.@pjbgf: For future reference, this introduced [two regressions](https://github.com/go-git/go-git/pull/1482#issuecomment-2800866150). A solution is being proposed on #1515.

Solution patch:
diff --git a/plumbing/transport/ssh/auth_method.go b/plumbing/transport/ssh/auth_method.go
index ac4e3583..6882e51b 100644
--- a/plumbing/transport/ssh/auth_method.go
+++ b/plumbing/transport/ssh/auth_method.go
@@ -54,7 +54,7 @@ func (a *KeyboardInteractive) String() string {
 }
 
 func (a *KeyboardInteractive) ClientConfig() (*ssh.ClientConfig, error) {
-	return a.SetHostKeyCallback(&ssh.ClientConfig{
+	return a.SetHostKeyCallbackAndAlgorithms(&ssh.ClientConfig{
 		User: a.User,
 		Auth: []ssh.AuthMethod{
 			a.Challenge,
@@ -78,7 +78,7 @@ func (a *Password) String() string {
 }
 
 func (a *Password) ClientConfig() (*ssh.ClientConfig, error) {
-	return a.SetHostKeyCallback(&ssh.ClientConfig{
+	return a.SetHostKeyCallbackAndAlgorithms(&ssh.ClientConfig{
 		User: a.User,
 		Auth: []ssh.AuthMethod{ssh.Password(a.Password)},
 	})
@@ -101,7 +101,7 @@ func (a *PasswordCallback) String() string {
 }
 
 func (a *PasswordCallback) ClientConfig() (*ssh.ClientConfig, error) {
-	return a.SetHostKeyCallback(&ssh.ClientConfig{
+	return a.SetHostKeyCallbackAndAlgorithms(&ssh.ClientConfig{
 		User: a.User,
 		Auth: []ssh.AuthMethod{ssh.PasswordCallback(a.Callback)},
 	})
@@ -150,7 +150,7 @@ func (a *PublicKeys) String() string {
 }
 
 func (a *PublicKeys) ClientConfig() (*ssh.ClientConfig, error) {
-	return a.SetHostKeyCallback(&ssh.ClientConfig{
+	return a.SetHostKeyCallbackAndAlgorithms(&ssh.ClientConfig{
 		User: a.User,
 		Auth: []ssh.AuthMethod{ssh.PublicKeys(a.Signer)},
 	})
@@ -211,7 +211,7 @@ func (a *PublicKeysCallback) String() string {
 }
 
 func (a *PublicKeysCallback) ClientConfig() (*ssh.ClientConfig, error) {
-	return a.SetHostKeyCallback(&ssh.ClientConfig{
+	return a.SetHostKeyCallbackAndAlgorithms(&ssh.ClientConfig{
 		User: a.User,
 		Auth: []ssh.AuthMethod{ssh.PublicKeysCallback(a.Callback)},
 	})
@@ -230,11 +230,23 @@ func (a *PublicKeysCallback) ClientConfig() (*ssh.ClientConfig, error) {
 //	~/.ssh/known_hosts
 //	/etc/ssh/ssh_known_hosts
 func NewKnownHostsCallback(files ...string) (ssh.HostKeyCallback, error) {
-	kh, err := newKnownHosts(files...)
-	return ssh.HostKeyCallback(kh), err
+	kh, err := NewKnownHostsDb(files...)
+	return kh.HostKeyCallback(), err
 }
 
-func newKnownHosts(files ...string) (knownhosts.HostKeyCallback, error) {
+// NewKnownHostsDb returns knownhosts.HostKeyDB based on a file based on a
+// known_hosts file. http://man.openbsd.org/sshd#SSH_KNOWN_HOSTS_FILE_FORMAT
+//
+// If list of files is empty, then it will be read from the SSH_KNOWN_HOSTS
+// environment variable, example:
+//
+//	/home/foo/custom_known_hosts_file:/etc/custom_known/hosts_file
+//
+// If SSH_KNOWN_HOSTS is not set the following file locations will be used:
+//
+//	~/.ssh/known_hosts
+//	/etc/ssh/ssh_known_hosts
+func NewKnownHostsDb(files ...string) (*knownhosts.HostKeyDB, error) {
 	var err error
 
 	if len(files) == 0 {
@@ -247,7 +259,7 @@ func newKnownHosts(files ...string) (knownhosts.HostKeyCallback, error) {
 		return nil, err
 	}
 
-	return knownhosts.New(files...)
+	return knownhosts.NewDB(files...)
 }
 
 func getDefaultKnownHostsFiles() ([]string, error) {
@@ -289,25 +301,23 @@ func filterKnownHostsFiles(files ...string) ([]string, error) {
 }
 
 // HostKeyCallbackHelper is a helper that provides common functionality to
-// configure HostKeyCallback into a ssh.ClientConfig.
+// configure HostKeyCallback and HostKeyAlgorithms into a ssh.ClientConfig.
 type HostKeyCallbackHelper struct {
 	// HostKeyCallback is the function type used for verifying server keys.
-	// If nil default callback will be create using NewKnownHostsCallback
+	// If nil, a default callback will be created using NewKnownHostsDb
 	// without argument.
 	HostKeyCallback ssh.HostKeyCallback
-}
 
-// SetHostKeyCallback sets the field HostKeyCallback in the given cfg. If
-// HostKeyCallback is empty a default callback is created using
-// NewKnownHostsCallback.
-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
-		}
-	}
+	// HostKeyAlgorithms is a list of supported host key algorithms that will
+	// be used for host key verification.
+	HostKeyAlgorithms []string
+}
 
+// SetHostKeyCallbackAndAlgorithms sets the field HostKeyCallback and HostKeyAlgorithms in the given cfg.
+// If the host key callback or algorithms is empty it is left empty. It will be handled by the dial method,
+// falling back to knownhosts.
+func (m *HostKeyCallbackHelper) SetHostKeyCallbackAndAlgorithms(cfg *ssh.ClientConfig) (*ssh.ClientConfig, error) {
 	cfg.HostKeyCallback = m.HostKeyCallback
+	cfg.HostKeyAlgorithms = m.HostKeyAlgorithms
 	return cfg, nil
 }
diff --git a/plumbing/transport/ssh/common.go b/plumbing/transport/ssh/common.go
index 05dea448..ae6f2174 100644
--- a/plumbing/transport/ssh/common.go
+++ b/plumbing/transport/ssh/common.go
@@ -11,7 +11,6 @@ import (
 
 	"github.com/go-git/go-git/v5/plumbing/transport"
 	"github.com/go-git/go-git/v5/plumbing/transport/internal/common"
-	"github.com/skeema/knownhosts"
 
 	"github.com/kevinburke/ssh_config"
 	"golang.org/x/crypto/ssh"
@@ -127,17 +126,17 @@ func (c *command) connect() error {
 	}
 	hostWithPort := c.getHostWithPort()
 	if config.HostKeyCallback == nil {
-		kh, err := newKnownHosts()
+		db, err := NewKnownHostsDb()
 		if err != nil {
 			return err
 		}
-		config.HostKeyCallback = kh.HostKeyCallback()
-		config.HostKeyAlgorithms = kh.HostKeyAlgorithms(hostWithPort)
-	} else if len(config.HostKeyAlgorithms) == 0 {
-		// Set the HostKeyAlgorithms based on HostKeyCallback.
-		// For background see https://github.com/go-git/go-git/issues/411 as well as
-		// https://github.com/golang/go/issues/29286 for root cause.
-		config.HostKeyAlgorithms = knownhosts.HostKeyAlgorithms(config.HostKeyCallback, hostWithPort)
+		config.HostKeyCallback = db.HostKeyCallback()
+		config.HostKeyAlgorithms = db.HostKeyAlgorithms(hostWithPort)
+	} else {
+		// If the user gave a custom HostKeyCallback, we do not try to detect host key algorithms
+		// based on knownhosts functionality, as the user may be requesting a FixedKey or using a
+		// different key approval strategy. In that case, the user is responsible for populating
+		// HostKeyAlgorithms appropriately
 	}
 
 	overrideConfig(c.config, config)



Test patch:
diff --git a/plumbing/transport/ssh/auth_method_test.go b/plumbing/transport/ssh/auth_method_test.go
index b275018a..5eaafafb 100644
--- a/plumbing/transport/ssh/auth_method_test.go
+++ b/plumbing/transport/ssh/auth_method_test.go
@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"os"
 	"runtime"
+	"slices"
 	"strings"
 
 	"github.com/go-git/go-billy/v5/osfs"
@@ -18,7 +19,8 @@ import (
 type (
 	SuiteCommon struct{}
 
-	mockKnownHosts struct{}
+	mockKnownHosts         struct{}
+	mockKnownHostsWithCert struct{}
 )
 
 func (mockKnownHosts) host() string { return "github.com" }
@@ -27,6 +29,19 @@ func (mockKnownHosts) knownHosts() []byte {
 }
 func (mockKnownHosts) Network() string { return "tcp" }
 func (mockKnownHosts) String() string  { return "github.com:22" }
+func (mockKnownHosts) Algorithms() []string {
+	return []string{ssh.KeyAlgoRSA, ssh.KeyAlgoRSASHA256, ssh.KeyAlgoRSASHA512}
+}
+
+func (mockKnownHostsWithCert) host() string { return "github.com" }
+func (mockKnownHostsWithCert) knownHosts() []byte {
+	return []byte(`@cert-authority github.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==`)
+}
+func (mockKnownHostsWithCert) Network() string { return "tcp" }
+func (mockKnownHostsWithCert) String() string  { return "github.com:22" }
+func (mockKnownHostsWithCert) Algorithms() []string {
+	return []string{ssh.CertAlgoRSASHA512v01, ssh.CertAlgoRSASHA256v01, ssh.CertAlgoRSAv01}
+}
 
 var _ = Suite(&SuiteCommon{})
 
@@ -230,3 +245,75 @@ func (*SuiteCommon) TestNewKnownHostsCallback(c *C) {
 	err = clb(mock.String(), mock, hostKey)
 	c.Assert(err, IsNil)
 }
+
+func (*SuiteCommon) TestNewKnownHostsDbWithoutCert(c *C) {
+	if runtime.GOOS == "js" {
+		c.Skip("not available in wasm")
+	}
+
+	var mock = mockKnownHosts{}
+
+	f, err := util.TempFile(osfs.Default, "", "known-hosts")
+	c.Assert(err, IsNil)
+
+	_, err = f.Write(mock.knownHosts())
+	c.Assert(err, IsNil)
+
+	err = f.Close()
+	c.Assert(err, IsNil)
+
+	defer util.RemoveAll(osfs.Default, f.Name())
+
+	f, err = osfs.Default.Open(f.Name())
+	c.Assert(err, IsNil)
+
+	defer f.Close()
+
+	db, err := NewKnownHostsDb(f.Name())
+	c.Assert(err, IsNil)
+
+	algos := db.HostKeyAlgorithms(mock.String())
+	c.Assert(algos, HasLen, len(mock.Algorithms()))
+
+	for _, algorithm := range mock.Algorithms() {
+		if !slices.Contains(algos, algorithm) {
+			c.Error("algos does not contain ", algorithm)
+		}
+	}
+}
+
+func (*SuiteCommon) TestNewKnownHostsDbWithCert(c *C) {
+	if runtime.GOOS == "js" {
+		c.Skip("not available in wasm")
+	}
+
+	var mock = mockKnownHostsWithCert{}
+
+	f, err := util.TempFile(osfs.Default, "", "known-hosts")
+	c.Assert(err, IsNil)
+
+	_, err = f.Write(mock.knownHosts())
+	c.Assert(err, IsNil)
+
+	err = f.Close()
+	c.Assert(err, IsNil)
+
+	defer util.RemoveAll(osfs.Default, f.Name())
+
+	f, err = osfs.Default.Open(f.Name())
+	c.Assert(err, IsNil)
+
+	defer f.Close()
+
+	db, err := NewKnownHostsDb(f.Name())
+	c.Assert(err, IsNil)
+
+	algos := db.HostKeyAlgorithms(mock.String())
+	c.Assert(algos, HasLen, len(mock.Algorithms()))
+
+	for _, algorithm := range mock.Algorithms() {
+		if !slices.Contains(algos, algorithm) {
+			c.Error("algos does not contain ", algorithm)
+		}
+	}
+}
diff --git a/plumbing/transport/ssh/common_test.go b/plumbing/transport/ssh/common_test.go
index a7249368..a4e29bef 100644
--- a/plumbing/transport/ssh/common_test.go
+++ b/plumbing/transport/ssh/common_test.go
@@ -129,12 +129,35 @@ func (s *SuiteCommon) TestFixedHostKeyCallback(c *C) {
 	c.Assert(err, IsNil)
 	c.Assert(auth, NotNil)
 	auth.HostKeyCallback = stdssh.FixedHostKey(hostKey.PublicKey())
+	auth.HostKeyAlgorithms = []string{"ssh-ed25519"}
 	ep := uploadPack.newEndpoint(c, "bar.git")
 	ps, err := uploadPack.Client.NewUploadPackSession(ep, auth)
 	c.Assert(err, IsNil)
 	c.Assert(ps, NotNil)
 }
 
+func (s *SuiteCommon) TestFixedHostKeyCallbackUnexpectedAlgorithm(c *C) {
+	hostKey, err := stdssh.ParsePrivateKey(testdata.PEMBytes["ed25519"])
+	c.Assert(err, IsNil)
+	uploadPack := &UploadPackSuite{
+		opts: []ssh.Option{
+			ssh.HostKeyPEM(testdata.PEMBytes["rsa"]),
+		},
+	}
+	uploadPack.SetUpSuite(c)
+	// Use the default client, which does not have a host key callback
+	uploadPack.Client = DefaultClient
+	auth, err := NewPublicKeys("foo", testdata.PEMBytes["rsa"], "")
+	c.Assert(err, IsNil)
+	c.Assert(auth, NotNil)
+	auth.HostKeyCallback = stdssh.FixedHostKey(hostKey.PublicKey())
+	auth.HostKeyAlgorithms = []string{"ssh-ed25519"}
+	ep := uploadPack.newEndpoint(c, "bar.git")
+	ps, err := uploadPack.Client.NewUploadPackSession(ep, auth)
+	c.Assert(err, NotNil)
+	c.Assert(ps, IsNil)
+}
+
 func (s *SuiteCommon) TestFailHostKeyCallback(c *C) {
 	uploadPack := &UploadPackSuite{
 		opts: []ssh.Option{