aboutsummaryrefslogtreecommitdiffstats
path: root/plumbing
diff options
context:
space:
mode:
Diffstat (limited to 'plumbing')
-rw-r--r--plumbing/hash/hash.go6
-rw-r--r--plumbing/object/signature.go101
-rw-r--r--plumbing/object/signature_test.go180
-rw-r--r--plumbing/object/tag.go37
-rw-r--r--plumbing/object/tag_test.go21
-rw-r--r--plumbing/transport/internal/common/common.go2
-rw-r--r--plumbing/transport/internal/common/common_test.go14
-rw-r--r--plumbing/transport/ssh/auth_method.go28
-rw-r--r--plumbing/transport/ssh/common.go33
-rw-r--r--plumbing/transport/ssh/common_test.go79
-rw-r--r--plumbing/transport/ssh/upload_pack_test.go4
11 files changed, 425 insertions, 80 deletions
diff --git a/plumbing/hash/hash.go b/plumbing/hash/hash.go
index fe3bf76..80e4b5f 100644
--- a/plumbing/hash/hash.go
+++ b/plumbing/hash/hash.go
@@ -7,7 +7,7 @@ import (
"fmt"
"hash"
- "github.com/pjbgf/sha1cd/cgo"
+ "github.com/pjbgf/sha1cd"
)
// algos is a map of hash algorithms.
@@ -20,9 +20,7 @@ func init() {
// reset resets the default algos value. Can be used after running tests
// that registers new algorithms to avoid side effects.
func reset() {
- // For performance reasons the cgo version of the collision
- // detection algorithm is being used.
- algos[crypto.SHA1] = cgo.New
+ algos[crypto.SHA1] = sha1cd.New
}
// RegisterHash allows for the hash algorithm used to be overriden.
diff --git a/plumbing/object/signature.go b/plumbing/object/signature.go
new file mode 100644
index 0000000..91cf371
--- /dev/null
+++ b/plumbing/object/signature.go
@@ -0,0 +1,101 @@
+package object
+
+import "bytes"
+
+const (
+ signatureTypeUnknown signatureType = iota
+ signatureTypeOpenPGP
+ signatureTypeX509
+ signatureTypeSSH
+)
+
+var (
+ // openPGPSignatureFormat is the format of an OpenPGP signature.
+ openPGPSignatureFormat = signatureFormat{
+ []byte("-----BEGIN PGP SIGNATURE-----"),
+ []byte("-----BEGIN PGP MESSAGE-----"),
+ }
+ // x509SignatureFormat is the format of an X509 signature, which is
+ // a PKCS#7 (S/MIME) signature.
+ x509SignatureFormat = signatureFormat{
+ []byte("-----BEGIN CERTIFICATE-----"),
+ }
+
+ // sshSignatureFormat is the format of an SSH signature.
+ sshSignatureFormat = signatureFormat{
+ []byte("-----BEGIN SSH SIGNATURE-----"),
+ }
+)
+
+var (
+ // knownSignatureFormats is a map of known signature formats, indexed by
+ // their signatureType.
+ knownSignatureFormats = map[signatureType]signatureFormat{
+ signatureTypeOpenPGP: openPGPSignatureFormat,
+ signatureTypeX509: x509SignatureFormat,
+ signatureTypeSSH: sshSignatureFormat,
+ }
+)
+
+// signatureType represents the type of the signature.
+type signatureType int8
+
+// signatureFormat represents the beginning of a signature.
+type signatureFormat [][]byte
+
+// typeForSignature returns the type of the signature based on its format.
+func typeForSignature(b []byte) signatureType {
+ for t, i := range knownSignatureFormats {
+ for _, begin := range i {
+ if bytes.HasPrefix(b, begin) {
+ return t
+ }
+ }
+ }
+ return signatureTypeUnknown
+}
+
+// parseSignedBytes returns the position of the last signature block found in
+// the given bytes. If no signature block is found, it returns -1.
+//
+// When multiple signature blocks are found, the position of the last one is
+// returned. Any tailing bytes after this signature block start should be
+// considered part of the signature.
+//
+// Given this, it would be safe to use the returned position to split the bytes
+// into two parts: the first part containing the message, the second part
+// containing the signature.
+//
+// Example:
+//
+// message := []byte(`Message with signature
+//
+// -----BEGIN SSH SIGNATURE-----
+// ...`)
+//
+// var signature string
+// if pos, _ := parseSignedBytes(message); pos != -1 {
+// signature = string(message[pos:])
+// message = message[:pos]
+// }
+//
+// This logic is on par with git's gpg-interface.c:parse_signed_buffer().
+// https://github.com/git/git/blob/7c2ef319c52c4997256f5807564523dfd4acdfc7/gpg-interface.c#L668
+func parseSignedBytes(b []byte) (int, signatureType) {
+ var n, match = 0, -1
+ var t signatureType
+ for n < len(b) {
+ var i = b[n:]
+ if st := typeForSignature(i); st != signatureTypeUnknown {
+ match = n
+ t = st
+ }
+ if eol := bytes.IndexByte(i, '\n'); eol >= 0 {
+ n += eol + 1
+ continue
+ }
+ // If we reach this point, we've reached the end.
+ break
+ }
+ return match, t
+}
diff --git a/plumbing/object/signature_test.go b/plumbing/object/signature_test.go
new file mode 100644
index 0000000..1bdb1d1
--- /dev/null
+++ b/plumbing/object/signature_test.go
@@ -0,0 +1,180 @@
+package object
+
+import (
+ "bytes"
+ "testing"
+)
+
+func Test_typeForSignature(t *testing.T) {
+ tests := []struct {
+ name string
+ b []byte
+ want signatureType
+ }{
+ {
+ name: "known signature format (PGP)",
+ b: []byte(`-----BEGIN PGP SIGNATURE-----
+
+iHUEABYKAB0WIQTMqU0ycQ3f6g3PMoWMmmmF4LuV8QUCYGebVwAKCRCMmmmF4LuV
+8VtyAP9LbuXAhtK6FQqOjKybBwlV70rLcXVP24ubDuz88VVwSgD+LuObsasWq6/U
+TssDKHUR2taa53bQYjkZQBpvvwOrLgc=
+=YQUf
+-----END PGP SIGNATURE-----`),
+ want: signatureTypeOpenPGP,
+ },
+ {
+ name: "known signature format (SSH)",
+ b: []byte(`-----BEGIN SSH SIGNATURE-----
+U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgij/EfHS8tCjolj5uEANXgKzFfp
+0D7wOhjWVbYZH6KugAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5
+AAAAQIYHMhSVV9L2xwJuV8eWMLjThya8yXgCHDzw3p01D19KirrabW0veiichPB5m+Ihtr
+MKEQruIQWJb+8HVXwssA4=
+-----END SSH SIGNATURE-----`),
+ want: signatureTypeSSH,
+ },
+ {
+ name: "known signature format (X509)",
+ b: []byte(`-----BEGIN CERTIFICATE-----
+MIIDZjCCAk6gAwIBAgIJALZ9Z3Z9Z3Z9MA0GCSqGSIb3DQEBCwUAMIGIMQswCQYD
+VQQGEwJTRTEOMAwGA1UECAwFVGV4YXMxDjAMBgNVBAcMBVRleGFzMQ4wDAYDVQQK
+DAVUZXhhczEOMAwGA1UECwwFVGV4YXMxGDAWBgNVBAMMD1RleGFzIENlcnRpZmlj
+YXRlMB4XDTE3MDUyNjE3MjY0MloXDTI3MDUyNDE3MjY0MlowgYgxCzAJBgNVBAYT
+AlNFMQ4wDAYDVQQIDAVUZXhhczEOMAwGA1UEBwwFVGV4YXMxDjAMBgNVBAoMBVRl
+eGFzMQ4wDAYDVQQLDAVUZXhhczEYMBYGA1UEAwwPVGV4YXMgQ2VydGlmaWNhdGUw
+ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDQZ9Z3Z9Z3Z9Z3Z9Z3Z9Z3
+-----END CERTIFICATE-----`),
+ want: signatureTypeX509,
+ },
+ {
+ name: "unknown signature format",
+ b: []byte(`-----BEGIN ARBITRARY SIGNATURE-----
+U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgij/EfHS8tCjolj5uEANXgKzFfp
+-----END UNKNOWN SIGNATURE-----`),
+ want: signatureTypeUnknown,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := typeForSignature(tt.b); got != tt.want {
+ t.Errorf("typeForSignature() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
+
+func Test_parseSignedBytes(t *testing.T) {
+ tests := []struct {
+ name string
+ b []byte
+ wantSignature []byte
+ wantType signatureType
+ }{
+ {
+ name: "detects signature and type",
+ b: []byte(`signed tag
+-----BEGIN PGP SIGNATURE-----
+
+iQGzBAABCAAdFiEE/h5sbbqJFh9j1AdUSqtFFGopTmwFAmB5XFkACgkQSqtFFGop
+TmxvgAv+IPjX5WCLFUIMx8hquMZp1VkhQrseE7rljUYaYpga8gZ9s4kseTGhy7Un
+61U3Ro6cTPEiQF/FkAGzSdPuGqv0ARBqHDX2tUI9+Zs/K8aG8tN+JTaof0gBcTyI
+BLbZVYDTxbS9whxSDewQd0OvBG1m9ISLUhjXo6mbaVvrKXNXTHg40MPZ8ZxjR/vN
+hxXXoUVnFyEDo+v6nK56mYtapThDaQQHHzD6D3VaCq3Msog7qAh9/ZNBmgb88aQ3
+FoK8PHMyr5elsV3mE9bciZBUc+dtzjOvp94uQ5ZKUXaPusXaYXnKpVnzhyer6RBI
+gJLWtPwAinqmN41rGJ8jDAGrpPNjaRrMhGtbyVUPUf19OxuUIroe77sIIKTP0X2o
+Wgp56dYpTst0JcGv/FYCeau/4pTRDfwHAOcDiBQ/0ag9IrZp9P8P9zlKmzNPEraV
+pAe1/EFuhv2UDLucAiWM8iDZIcw8iN0OYMOGUmnk0WuGIo7dzLeqMGY+ND5n5Z8J
+sZC//k6m
+=VhHy
+-----END PGP SIGNATURE-----`),
+ wantSignature: []byte(`-----BEGIN PGP SIGNATURE-----
+
+iQGzBAABCAAdFiEE/h5sbbqJFh9j1AdUSqtFFGopTmwFAmB5XFkACgkQSqtFFGop
+TmxvgAv+IPjX5WCLFUIMx8hquMZp1VkhQrseE7rljUYaYpga8gZ9s4kseTGhy7Un
+61U3Ro6cTPEiQF/FkAGzSdPuGqv0ARBqHDX2tUI9+Zs/K8aG8tN+JTaof0gBcTyI
+BLbZVYDTxbS9whxSDewQd0OvBG1m9ISLUhjXo6mbaVvrKXNXTHg40MPZ8ZxjR/vN
+hxXXoUVnFyEDo+v6nK56mYtapThDaQQHHzD6D3VaCq3Msog7qAh9/ZNBmgb88aQ3
+FoK8PHMyr5elsV3mE9bciZBUc+dtzjOvp94uQ5ZKUXaPusXaYXnKpVnzhyer6RBI
+gJLWtPwAinqmN41rGJ8jDAGrpPNjaRrMhGtbyVUPUf19OxuUIroe77sIIKTP0X2o
+Wgp56dYpTst0JcGv/FYCeau/4pTRDfwHAOcDiBQ/0ag9IrZp9P8P9zlKmzNPEraV
+pAe1/EFuhv2UDLucAiWM8iDZIcw8iN0OYMOGUmnk0WuGIo7dzLeqMGY+ND5n5Z8J
+sZC//k6m
+=VhHy
+-----END PGP SIGNATURE-----`),
+ wantType: signatureTypeOpenPGP,
+ },
+ {
+ name: "last signature for multiple signatures",
+ b: []byte(`signed tag
+-----BEGIN PGP SIGNATURE-----
+
+iQGzBAABCAAdFiEE/h5sbbqJFh9j1AdUSqtFFGopTmwFAmB5XFkACgkQSqtFFGop
+TmxvgAv+IPjX5WCLFUIMx8hquMZp1VkhQrseE7rljUYaYpga8gZ9s4kseTGhy7Un
+61U3Ro6cTPEiQF/FkAGzSdPuGqv0ARBqHDX2tUI9+Zs/K8aG8tN+JTaof0gBcTyI
+BLbZVYDTxbS9whxSDewQd0OvBG1m9ISLUhjXo6mbaVvrKXNXTHg40MPZ8ZxjR/vN
+hxXXoUVnFyEDo+v6nK56mYtapThDaQQHHzD6D3VaCq3Msog7qAh9/ZNBmgb88aQ3
+FoK8PHMyr5elsV3mE9bciZBUc+dtzjOvp94uQ5ZKUXaPusXaYXnKpVnzhyer6RBI
+gJLWtPwAinqmN41rGJ8jDAGrpPNjaRrMhGtbyVUPUf19OxuUIroe77sIIKTP0X2o
+Wgp56dYpTst0JcGv/FYCeau/4pTRDfwHAOcDiBQ/0ag9IrZp9P8P9zlKmzNPEraV
+pAe1/EFuhv2UDLucAiWM8iDZIcw8iN0OYMOGUmnk0WuGIo7dzLeqMGY+ND5n5Z8J
+sZC//k6m
+=VhHy
+-----END PGP SIGNATURE-----
+-----BEGIN SSH SIGNATURE-----
+U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgij/EfHS8tCjolj5uEANXgKzFfp
+0D7wOhjWVbYZH6KugAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5
+AAAAQIYHMhSVV9L2xwJuV8eWMLjThya8yXgCHDzw3p01D19KirrabW0veiichPB5m+Ihtr
+MKEQruIQWJb+8HVXwssA4=
+-----END SSH SIGNATURE-----`),
+ wantSignature: []byte(`-----BEGIN SSH SIGNATURE-----
+U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgij/EfHS8tCjolj5uEANXgKzFfp
+0D7wOhjWVbYZH6KugAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5
+AAAAQIYHMhSVV9L2xwJuV8eWMLjThya8yXgCHDzw3p01D19KirrabW0veiichPB5m+Ihtr
+MKEQruIQWJb+8HVXwssA4=
+-----END SSH SIGNATURE-----`),
+ wantType: signatureTypeSSH,
+ },
+ {
+ name: "signature with trailing data",
+ b: []byte(`An invalid
+
+-----BEGIN SSH SIGNATURE-----
+U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgij/EfHS8tCjolj5uEANXgKzFfp
+0D7wOhjWVbYZH6KugAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5
+AAAAQIYHMhSVV9L2xwJuV8eWMLjThya8yXgCHDzw3p01D19KirrabW0veiichPB5m+Ihtr
+MKEQruIQWJb+8HVXwssA4=
+-----END SSH SIGNATURE-----
+
+signed tag`),
+ wantSignature: []byte(`-----BEGIN SSH SIGNATURE-----
+U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgij/EfHS8tCjolj5uEANXgKzFfp
+0D7wOhjWVbYZH6KugAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5
+AAAAQIYHMhSVV9L2xwJuV8eWMLjThya8yXgCHDzw3p01D19KirrabW0veiichPB5m+Ihtr
+MKEQruIQWJb+8HVXwssA4=
+-----END SSH SIGNATURE-----
+
+signed tag`),
+ wantType: signatureTypeSSH,
+ },
+ {
+ name: "data without signature",
+ b: []byte(`Some message`),
+ wantSignature: []byte(``),
+ wantType: signatureTypeUnknown,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ pos, st := parseSignedBytes(tt.b)
+ var signature []byte
+ if pos >= 0 {
+ signature = tt.b[pos:]
+ }
+ if !bytes.Equal(signature, tt.wantSignature) {
+ t.Errorf("parseSignedBytes() got = %s for pos = %v, want %s", signature, pos, tt.wantSignature)
+ }
+ if st != tt.wantType {
+ t.Errorf("parseSignedBytes() got1 = %v, want %v", st, tt.wantType)
+ }
+ })
+ }
+}
diff --git a/plumbing/object/tag.go b/plumbing/object/tag.go
index 84066f7..cf46c08 100644
--- a/plumbing/object/tag.go
+++ b/plumbing/object/tag.go
@@ -4,11 +4,9 @@ import (
"bytes"
"fmt"
"io"
- stdioutil "io/ioutil"
"strings"
"github.com/ProtonMail/go-crypto/openpgp"
-
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/storer"
"github.com/go-git/go-git/v5/utils/ioutil"
@@ -128,40 +126,15 @@ func (t *Tag) Decode(o plumbing.EncodedObject) (err error) {
}
}
- data, err := stdioutil.ReadAll(r)
+ data, err := io.ReadAll(r)
if err != nil {
return err
}
-
- var pgpsig bool
- // Check if data contains PGP signature.
- if bytes.Contains(data, []byte(beginpgp)) {
- // Split the lines at newline.
- messageAndSig := bytes.Split(data, []byte("\n"))
-
- for _, l := range messageAndSig {
- if pgpsig {
- if bytes.Contains(l, []byte(endpgp)) {
- t.PGPSignature += endpgp + "\n"
- break
- } else {
- t.PGPSignature += string(l) + "\n"
- }
- continue
- }
-
- // Check if it's the beginning of a PGP signature.
- if bytes.Contains(l, []byte(beginpgp)) {
- t.PGPSignature += beginpgp + "\n"
- pgpsig = true
- continue
- }
-
- t.Message += string(l) + "\n"
- }
- } else {
- t.Message = string(data)
+ if sm, _ := parseSignedBytes(data); sm >= 0 {
+ t.PGPSignature = string(data[sm:])
+ data = data[:sm]
}
+ t.Message = string(data)
return nil
}
diff --git a/plumbing/object/tag_test.go b/plumbing/object/tag_test.go
index cd1d15d..15b943e 100644
--- a/plumbing/object/tag_test.go
+++ b/plumbing/object/tag_test.go
@@ -312,6 +312,27 @@ RUysgqjcpT8+iQM1PblGfHR4XAhuOqN5Fx06PSaFZhqvWFezJ28/CLyX5q+oIVk=
c.Assert(decoded.PGPSignature, Equals, pgpsignature)
}
+func (s *TagSuite) TestSSHSignatureSerialization(c *C) {
+ encoded := &plumbing.MemoryObject{}
+ decoded := &Tag{}
+ tag := s.tag(c, plumbing.NewHash("b742a2a9fa0afcfa9a6fad080980fbc26b007c69"))
+
+ signature := `-----BEGIN SSH SIGNATURE-----
+U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgij/EfHS8tCjolj5uEANXgKzFfp
+0D7wOhjWVbYZH6KugAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5
+AAAAQIYHMhSVV9L2xwJuV8eWMLjThya8yXgCHDzw3p01D19KirrabW0veiichPB5m+Ihtr
+MKEQruIQWJb+8HVXwssA4=
+-----END SSH SIGNATURE-----`
+ tag.PGPSignature = signature
+
+ err := tag.Encode(encoded)
+ c.Assert(err, IsNil)
+
+ err = decoded.Decode(encoded)
+ c.Assert(err, IsNil)
+ c.Assert(decoded.PGPSignature, Equals, signature)
+}
+
func (s *TagSuite) TestVerify(c *C) {
ts := time.Unix(1617403017, 0)
loc, _ := time.LoadLocation("UTC")
diff --git a/plumbing/transport/internal/common/common.go b/plumbing/transport/internal/common/common.go
index d0e9a29..b2c2fee 100644
--- a/plumbing/transport/internal/common/common.go
+++ b/plumbing/transport/internal/common/common.go
@@ -374,7 +374,7 @@ func (s *session) checkNotFoundError() error {
case <-t.C:
return ErrTimeoutExceeded
case line, ok := <-s.firstErrLine:
- if !ok {
+ if !ok || len(line) == 0 {
return nil
}
diff --git a/plumbing/transport/internal/common/common_test.go b/plumbing/transport/internal/common/common_test.go
index c60ef3b..affa787 100644
--- a/plumbing/transport/internal/common/common_test.go
+++ b/plumbing/transport/internal/common/common_test.go
@@ -76,3 +76,17 @@ func (s *CommonSuite) TestIsRepoNotFoundErrorForGogsAccessDenied(c *C) {
c.Assert(isRepoNotFound, Equals, true)
}
+
+func (s *CommonSuite) TestCheckNotFoundError(c *C) {
+ firstErrLine := make(chan string, 1)
+
+ session := session{
+ firstErrLine: firstErrLine,
+ }
+
+ firstErrLine <- ""
+
+ err := session.checkNotFoundError()
+
+ c.Assert(err, IsNil)
+}
diff --git a/plumbing/transport/ssh/auth_method.go b/plumbing/transport/ssh/auth_method.go
index 9d3bcd3..e89ce4b 100644
--- a/plumbing/transport/ssh/auth_method.go
+++ b/plumbing/transport/ssh/auth_method.go
@@ -43,6 +43,7 @@ const (
type KeyboardInteractive struct {
User string
Challenge ssh.KeyboardInteractiveChallenge
+ HostKeyCallbackHelper
}
func (a *KeyboardInteractive) Name() string {
@@ -54,18 +55,19 @@ func (a *KeyboardInteractive) String() string {
}
func (a *KeyboardInteractive) ClientConfig() (*ssh.ClientConfig, error) {
- return &ssh.ClientConfig{
+ return a.SetHostKeyCallback(&ssh.ClientConfig{
User: a.User,
Auth: []ssh.AuthMethod{
a.Challenge,
},
- }, nil
+ })
}
// Password implements AuthMethod by using the given password.
type Password struct {
User string
Password string
+ HostKeyCallbackHelper
}
func (a *Password) Name() string {
@@ -77,10 +79,10 @@ func (a *Password) String() string {
}
func (a *Password) ClientConfig() (*ssh.ClientConfig, error) {
- return &ssh.ClientConfig{
+ return a.SetHostKeyCallback(&ssh.ClientConfig{
User: a.User,
Auth: []ssh.AuthMethod{ssh.Password(a.Password)},
- }, nil
+ })
}
// PasswordCallback implements AuthMethod by using a callback
@@ -88,6 +90,7 @@ func (a *Password) ClientConfig() (*ssh.ClientConfig, error) {
type PasswordCallback struct {
User string
Callback func() (pass string, err error)
+ HostKeyCallbackHelper
}
func (a *PasswordCallback) Name() string {
@@ -99,16 +102,17 @@ func (a *PasswordCallback) String() string {
}
func (a *PasswordCallback) ClientConfig() (*ssh.ClientConfig, error) {
- return &ssh.ClientConfig{
+ return a.SetHostKeyCallback(&ssh.ClientConfig{
User: a.User,
Auth: []ssh.AuthMethod{ssh.PasswordCallback(a.Callback)},
- }, nil
+ })
}
// PublicKeys implements AuthMethod by using the given key pairs.
type PublicKeys struct {
User string
Signer ssh.Signer
+ HostKeyCallbackHelper
}
// NewPublicKeys returns a PublicKeys from a PEM encoded private key. An
@@ -147,10 +151,10 @@ func (a *PublicKeys) String() string {
}
func (a *PublicKeys) ClientConfig() (*ssh.ClientConfig, error) {
- return &ssh.ClientConfig{
+ return a.SetHostKeyCallback(&ssh.ClientConfig{
User: a.User,
Auth: []ssh.AuthMethod{ssh.PublicKeys(a.Signer)},
- }, nil
+ })
}
func username() (string, error) {
@@ -173,6 +177,7 @@ func username() (string, error) {
type PublicKeysCallback struct {
User string
Callback func() (signers []ssh.Signer, err error)
+ HostKeyCallbackHelper
}
// NewSSHAgentAuth returns a PublicKeysCallback based on a SSH agent, it opens
@@ -207,10 +212,10 @@ func (a *PublicKeysCallback) String() string {
}
func (a *PublicKeysCallback) ClientConfig() (*ssh.ClientConfig, error) {
- return &ssh.ClientConfig{
+ return a.SetHostKeyCallback(&ssh.ClientConfig{
User: a.User,
Auth: []ssh.AuthMethod{ssh.PublicKeysCallback(a.Callback)},
- }, nil
+ })
}
// NewKnownHostsCallback returns ssh.HostKeyCallback based on a file based on a
@@ -286,9 +291,6 @@ func filterKnownHostsFiles(files ...string) ([]string, error) {
// HostKeyCallbackHelper is a helper that provides common functionality to
// configure HostKeyCallback into a ssh.ClientConfig.
-// Deprecated in favor of SetConfigHostKeyFields (see common.go) which provides
-// a mechanism for also setting ClientConfig.HostKeyAlgorithms for a specific
-// host.
type HostKeyCallbackHelper struct {
// HostKeyCallback is the function type used for verifying server keys.
// If nil default callback will be create using NewKnownHostsCallback
diff --git a/plumbing/transport/ssh/common.go b/plumbing/transport/ssh/common.go
index 4b9ac07..e06958a 100644
--- a/plumbing/transport/ssh/common.go
+++ b/plumbing/transport/ssh/common.go
@@ -10,6 +10,7 @@ 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"
@@ -122,9 +123,18 @@ func (c *command) connect() error {
return err
}
hostWithPort := c.getHostWithPort()
- config, err = SetConfigHostKeyFields(config, hostWithPort)
- if err != nil {
- return err
+ if config.HostKeyCallback == nil {
+ kh, err := newKnownHosts()
+ 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)
}
overrideConfig(c.config, config)
@@ -167,23 +177,6 @@ func dial(network, addr string, config *ssh.ClientConfig) (*ssh.Client, error) {
return ssh.NewClient(c, chans, reqs), nil
}
-// SetConfigHostKeyFields sets cfg.HostKeyCallback and cfg.HostKeyAlgorithms
-// based on OpenSSH known_hosts. cfg is modified in-place. hostWithPort must be
-// supplied, since the algorithms will be set based on the known host keys for
-// that specific host. Otherwise, golang.org/x/crypto/ssh can return an error
-// upon connecting to a host whose *first* key is not known, even though other
-// keys (of different types) are known and match properly.
-// 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.
-func SetConfigHostKeyFields(cfg *ssh.ClientConfig, hostWithPort string) (*ssh.ClientConfig, error) {
- kh, err := newKnownHosts()
- if err == nil {
- cfg.HostKeyCallback = kh.HostKeyCallback()
- cfg.HostKeyAlgorithms = kh.HostKeyAlgorithms(hostWithPort)
- }
- return cfg, err
-}
-
func (c *command) getHostWithPort() string {
if addr, found := c.doGetHostWithPortFromSSHConfig(); found {
return addr
diff --git a/plumbing/transport/ssh/common_test.go b/plumbing/transport/ssh/common_test.go
index 6d634d5..496e82d 100644
--- a/plumbing/transport/ssh/common_test.go
+++ b/plumbing/transport/ssh/common_test.go
@@ -5,23 +5,25 @@ import (
"github.com/go-git/go-git/v5/plumbing/transport"
+ "github.com/gliderlabs/ssh"
"github.com/kevinburke/ssh_config"
- "golang.org/x/crypto/ssh"
+ stdssh "golang.org/x/crypto/ssh"
+ "golang.org/x/crypto/ssh/testdata"
. "gopkg.in/check.v1"
)
func Test(t *testing.T) { TestingT(t) }
func (s *SuiteCommon) TestOverrideConfig(c *C) {
- config := &ssh.ClientConfig{
+ config := &stdssh.ClientConfig{
User: "foo",
- Auth: []ssh.AuthMethod{
- ssh.Password("yourpassword"),
+ Auth: []stdssh.AuthMethod{
+ stdssh.Password("yourpassword"),
},
- HostKeyCallback: ssh.FixedHostKey(nil),
+ HostKeyCallback: stdssh.FixedHostKey(nil),
}
- target := &ssh.ClientConfig{}
+ target := &stdssh.ClientConfig{}
overrideConfig(config, target)
c.Assert(target.User, Equals, "foo")
@@ -30,11 +32,11 @@ func (s *SuiteCommon) TestOverrideConfig(c *C) {
}
func (s *SuiteCommon) TestOverrideConfigKeep(c *C) {
- config := &ssh.ClientConfig{
+ config := &stdssh.ClientConfig{
User: "foo",
}
- target := &ssh.ClientConfig{
+ target := &stdssh.ClientConfig{
User: "bar",
}
@@ -93,12 +95,69 @@ func (s *SuiteCommon) TestDefaultSSHConfigWildcard(c *C) {
c.Assert(cmd.getHostWithPort(), Equals, "github.com:22")
}
+func (s *SuiteCommon) TestIgnoreHostKeyCallback(c *C) {
+ uploadPack := &UploadPackSuite{
+ opts: []ssh.Option{
+ ssh.HostKeyPEM(testdata.PEMBytes["ed25519"]),
+ },
+ }
+ 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.InsecureIgnoreHostKey()
+ ep := uploadPack.newEndpoint(c, "bar.git")
+ ps, err := uploadPack.Client.NewUploadPackSession(ep, auth)
+ c.Assert(err, IsNil)
+ c.Assert(ps, NotNil)
+}
+
+func (s *SuiteCommon) TestFixedHostKeyCallback(c *C) {
+ hostKey, err := stdssh.ParsePrivateKey(testdata.PEMBytes["ed25519"])
+ c.Assert(err, IsNil)
+ uploadPack := &UploadPackSuite{
+ opts: []ssh.Option{
+ ssh.HostKeyPEM(testdata.PEMBytes["ed25519"]),
+ },
+ }
+ 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())
+ ep := uploadPack.newEndpoint(c, "bar.git")
+ ps, err := uploadPack.Client.NewUploadPackSession(ep, auth)
+ c.Assert(err, IsNil)
+ c.Assert(ps, NotNil)
+}
+
+func (s *SuiteCommon) TestFailHostKeyCallback(c *C) {
+ uploadPack := &UploadPackSuite{
+ opts: []ssh.Option{
+ ssh.HostKeyPEM(testdata.PEMBytes["ed25519"]),
+ },
+ }
+ 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)
+ ep := uploadPack.newEndpoint(c, "bar.git")
+ _, err = uploadPack.Client.NewUploadPackSession(ep, auth)
+ c.Assert(err, NotNil)
+}
+
func (s *SuiteCommon) TestIssue70(c *C) {
uploadPack := &UploadPackSuite{}
uploadPack.SetUpSuite(c)
- config := &ssh.ClientConfig{
- HostKeyCallback: ssh.InsecureIgnoreHostKey(),
+ config := &stdssh.ClientConfig{
+ HostKeyCallback: stdssh.InsecureIgnoreHostKey(),
}
r := &runner{
config: config,
diff --git a/plumbing/transport/ssh/upload_pack_test.go b/plumbing/transport/ssh/upload_pack_test.go
index e65e04a..f172fee 100644
--- a/plumbing/transport/ssh/upload_pack_test.go
+++ b/plumbing/transport/ssh/upload_pack_test.go
@@ -25,6 +25,7 @@ import (
type UploadPackSuite struct {
test.UploadPackSuite
fixtures.Suite
+ opts []ssh.Option
port int
base string
@@ -57,6 +58,9 @@ func (s *UploadPackSuite) SetUpSuite(c *C) {
s.UploadPackSuite.NonExistentEndpoint = s.newEndpoint(c, "non-existent.git")
server := &ssh.Server{Handler: handlerSSH}
+ for _, opt := range s.opts {
+ opt(server)
+ }
go func() {
log.Fatal(server.Serve(l))
}()