From d9dfc4bce89c711e9b0da865ab5e94f2db47b751 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Wed, 15 Oct 2014 16:57:32 +0200 Subject: [PATCH] Add support for hostkey certificates While they're not exactly certificates, they belong in the same category. --- remote.go | 65 ++++++++++++++++++++++++++++++++++++++++++-------- remote_test.go | 8 ++----- 2 files changed, 57 insertions(+), 16 deletions(-) diff --git a/remote.go b/remote.go index 4e5005f..132c069 100644 --- a/remote.go +++ b/remote.go @@ -2,6 +2,7 @@ package git /* #include +#include extern void _go_git_setup_callbacks(git_remote_callbacks *callbacks); @@ -45,7 +46,7 @@ type CompletionCallback func(RemoteCompletion) int type CredentialsCallback func(url string, username_from_url string, allowed_types CredType) (int, *Cred) type TransferProgressCallback func(stats TransferProgress) int type UpdateTipsCallback func(refname string, a *Oid, b *Oid) int -type CertificateCheckCallback func(cert *x509.Certificate, valid bool, hostname string) int +type CertificateCheckCallback func(cert *Certificate, valid bool, hostname string) int type RemoteCallbacks struct { SidebandProgressCallback TransportMessageCallback @@ -61,6 +62,39 @@ type Remote struct { callbacks RemoteCallbacks } +type CertificateKind uint + +const ( + CertificateX509 CertificateKind = C.GIT_CERT_X509 + CertificateHostkey = C.GIT_CERT_HOSTKEY_LIBSSH2 +) + +// Certificate represents the two possible certificates which libgit2 +// knows it might find. If Kind is CertficateX509 then the X509 field +// will be filled. If Kind is CertificateHostkey then the Hostkey +// field will be fille.d +type Certificate struct { + Kind CertificateKind + X509 *x509.Certificate + Hostkey HostkeyCertificate +} + +type HostkeyKind uint + +const ( + HostkeyMD5 HostkeyKind = C.GIT_CERT_SSH_MD5 + HostkeySHA1 = C.GIT_CERT_SSH_SHA1 +) + +// Server host key information. If Kind is HostkeyMD5 the MD5 field +// will be filled. If Kind is HostkeySHA1, then HashSHA1 will be +// filled. +type HostkeyCertificate struct { + Kind HostkeyKind + HashMD5 [16]byte + HashSHA1 [20]byte +} + func populateRemoteCallbacks(ptr *C.git_remote_callbacks, callbacks *RemoteCallbacks) { C.git_remote_init_callbacks(ptr, C.GIT_REMOTE_CALLBACKS_VERSION) if callbacks == nil { @@ -124,7 +158,7 @@ func updateTipsCallback(_refname *C.char, _a *C.git_oid, _b *C.git_oid, data uns } //export certificateCheckCallback -func certificateCheckCallback(_cert *C.git_cert, _valid C.int, _host *C.char, data unsafe.Pointer) int { +func certificateCheckCallback(_cert *C.git_cert, _valid C.int, _host *C.char, data unsafe.Pointer) int { callbacks := (*RemoteCallbacks)(data) if callbacks.CertificateCheckCallback == nil { return 0 @@ -132,21 +166,32 @@ func certificateCheckCallback(_cert *C.git_cert, _valid C.int, _host *C.char, d host := C.GoString(_host) valid := _valid != 0 + var cert Certificate if _cert.cert_type == C.GIT_CERT_X509 { + cert.Kind = CertificateX509 ccert := (*C.git_cert_x509)(unsafe.Pointer(_cert)) x509_certs, err := x509.ParseCertificates(C.GoBytes(ccert.data, C.int(ccert.len))) if err != nil { - return C.GIT_EUSER; + return C.GIT_EUSER } // we assume there's only one, which should hold true for any web server we want to talk to - return callbacks.CertificateCheckCallback(x509_certs[0], valid, host) + cert.X509 = x509_certs[0] + } else if _cert.cert_type == C.GIT_CERT_HOSTKEY_LIBSSH2 { + cert.Kind = CertificateHostkey + ccert := (*C.git_cert_hostkey)(unsafe.Pointer(_cert)) + cert.Hostkey.Kind = HostkeyKind(ccert._type) + C.memcpy(unsafe.Pointer(&cert.Hostkey.HashMD5[0]), unsafe.Pointer(&ccert.hash_md5[0]), C.size_t(len(cert.Hostkey.HashMD5))) + C.memcpy(unsafe.Pointer(&cert.Hostkey.HashSHA1[0]), unsafe.Pointer(&ccert.hash_sha1[0]), C.size_t(len(cert.Hostkey.HashSHA1))) + } else { + cstr := C.CString("Unsupported certificate type") + C.giterr_set_str(C.GITERR_NET, cstr) + C.free(unsafe.Pointer(cstr)) + return -1 // we don't support anything else atm } - cstr := C.CString("Unsupported certificate type") - C.giterr_set_str(C.GITERR_NET, cstr) - C.free(unsafe.Pointer(cstr)) - return ErrUser // we don't support anything else atm + + return callbacks.CertificateCheckCallback(&cert, valid, host) } func RemoteIsValidName(name string) bool { @@ -462,11 +507,11 @@ func (o *Remote) RefspecCount() uint { } func (o *Remote) SetUpdateFetchHead(val bool) { - C.git_remote_set_update_fetchhead(o.ptr, cbool(val)) + C.git_remote_set_update_fetchhead(o.ptr, cbool(val)) } func (o *Remote) UpdateFetchHead() bool { - return C.git_remote_update_fetchhead(o.ptr) > 0 + return C.git_remote_update_fetchhead(o.ptr) > 0 } // Fetch performs a fetch operation. refspecs specifies which refspecs diff --git a/remote_test.go b/remote_test.go index 8021f71..d1ad1e8 100644 --- a/remote_test.go +++ b/remote_test.go @@ -1,8 +1,6 @@ package git import ( - "fmt" - "crypto/x509" "os" "testing" ) @@ -49,8 +47,7 @@ func TestListRemotes(t *testing.T) { } -func assertHostname(cert *x509.Certificate, valid bool, hostname string, t *testing.T) int { - fmt.Println("hostname", hostname) +func assertHostname(cert *Certificate, valid bool, hostname string, t *testing.T) int { if hostname != "github.com" { t.Fatal("Hostname does not match") return ErrUser @@ -68,7 +65,7 @@ func TestCertificateCheck(t *testing.T) { checkFatal(t, err) callbacks := RemoteCallbacks{ - CertificateCheckCallback: func (cert *x509.Certificate, valid bool, hostname string) int { + CertificateCheckCallback: func (cert *Certificate, valid bool, hostname string) int { return assertHostname(cert, valid, hostname, t) }, } @@ -77,5 +74,4 @@ func TestCertificateCheck(t *testing.T) { checkFatal(t, err) err = remote.Fetch([]string{}, nil, "") checkFatal(t, err) - fmt.Println("after Fetch()") }