diff --git a/pkg/git/libgit2/transport.go b/pkg/git/libgit2/transport.go index 74fd317e4..f53273567 100644 --- a/pkg/git/libgit2/transport.go +++ b/pkg/git/libgit2/transport.go @@ -19,9 +19,12 @@ package libgit2 import ( "bufio" "bytes" + "crypto/md5" "crypto/sha1" + "crypto/sha256" "crypto/x509" "fmt" + "hash" "net" "net/url" "strings" @@ -157,7 +160,7 @@ func (s *PublicKeyAuth) Method(secret corev1.Secret) (*git.Auth, error) { // is an entry for the hostname _and_ port. host = knownhosts.Normalize(s.host) for _, k := range kk { - if k.matches(host, cert.Hostkey.HashSHA1[:]) { + if k.matches(host, cert.Hostkey) { return git2go.ErrOk } } @@ -195,17 +198,28 @@ func parseKnownHosts(s string) ([]knownKey, error) { return knownHosts, nil } -func (k knownKey) matches(host string, key []byte) bool { +func (k knownKey) matches(host string, hostkey git2go.HostkeyCertificate) bool { if !containsHost(k.hosts, host) { return false } - hash := sha1.Sum(k.key.Marshal()) - if bytes.Compare(hash[:], key) != 0 { + var fingerprint []byte + var hasher hash.Hash + switch { + case hostkey.Kind&git2go.HostkeySHA256 > 0: + fingerprint = hostkey.HashSHA256[:] + hasher = sha256.New() + case hostkey.Kind&git2go.HostkeySHA1 > 0: + fingerprint = hostkey.HashSHA1[:] + hasher = sha1.New() + case hostkey.Kind&git2go.HostkeyMD5 > 0: + fingerprint = hostkey.HashMD5[:] + hasher = md5.New() + default: return false } - - return true + hasher.Write(k.key.Marshal()) + return bytes.Compare(hasher.Sum(nil), fingerprint) == 0 } func containsHost(hosts []string, host string) bool { diff --git a/pkg/git/libgit2/transport_test.go b/pkg/git/libgit2/transport_test.go index 7a2dcd310..2a1387c1d 100644 --- a/pkg/git/libgit2/transport_test.go +++ b/pkg/git/libgit2/transport_test.go @@ -17,9 +17,11 @@ limitations under the License. package libgit2 import ( + "encoding/base64" "reflect" "testing" + git2go "github.com/libgit2/git2go/v31" corev1 "k8s.io/api/core/v1" "github.com/fluxcd/source-controller/pkg/git" @@ -145,3 +147,60 @@ func TestPublicKeyStrategy_Method(t *testing.T) { }) } } + +func TestKnownKeyHash(t *testing.T) { + tests := []struct { + name string + hostkey git2go.HostkeyCertificate + wantMatches bool + }{ + {"good sha256 hostkey", git2go.HostkeyCertificate{Kind: git2go.HostkeySHA256 | git2go.HostkeySHA1 | git2go.HostkeyMD5, HashSHA256: sha256Fingerprint("nThbg6kXUpJWGl7E1IGOCspRomTxdCARLviKw6E5SY8")}, true}, + {"bad sha256 hostkey", git2go.HostkeyCertificate{Kind: git2go.HostkeySHA256 | git2go.HostkeySHA1 | git2go.HostkeyMD5, HashSHA256: sha256Fingerprint("ROQFvPThGrW4RuWLoL9tq9I9zJ42fK4XywyRtbOz/EQ")}, false}, + {"good sha1 hostkey", git2go.HostkeyCertificate{Kind: git2go.HostkeySHA1 | git2go.HostkeyMD5, HashSHA1: sha1Fingerprint("v2toJdKXfFEaR1u++4iq1UqSrHM")}, true}, + {"bad sha1 hostkey", git2go.HostkeyCertificate{Kind: git2go.HostkeySHA1 | git2go.HostkeyMD5, HashSHA1: sha1Fingerprint("tfpLlQhDDFP3yGdewTvHNxWmAdk")}, false}, + {"good md5 hostkey", git2go.HostkeyCertificate{Kind: git2go.HostkeyMD5, HashMD5: md5Fingerprint("\x16\x27\xac\xa5\x76\x28\x2d\x36\x63\x1b\x56\x4d\xeb\xdf\xa6\x48")}, true}, + {"bad md5 hostkey", git2go.HostkeyCertificate{Kind: git2go.HostkeyMD5, HashMD5: md5Fingerprint("\xb6\x03\x0e\x39\x97\x9e\xd0\xe7\x24\xce\xa3\x77\x3e\x01\x42\x09")}, false}, + {"invalid hostkey", git2go.HostkeyCertificate{}, false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + knownKeys, err := parseKnownHosts(knownHostsFixture) + if err != nil { + t.Error(err) + return + } + + matches := knownKeys[0].matches("github.com", tt.hostkey) + if matches != tt.wantMatches { + t.Errorf("Method() matches = %v, wantMatches %v", matches, tt.wantMatches) + return + } + }) + } +} + +func md5Fingerprint(in string) [16]byte { + var out [16]byte + copy(out[:], []byte(in)) + return out +} + +func sha1Fingerprint(in string) [20]byte { + d, err := base64.RawStdEncoding.DecodeString(in) + if err != nil { + panic(err) + } + var out [20]byte + copy(out[:], d) + return out +} + +func sha256Fingerprint(in string) [32]byte { + d, err := base64.RawStdEncoding.DecodeString(in) + if err != nil { + panic(err) + } + var out [32]byte + copy(out[:], d) + return out +}