From 528e14aa4fb801413841ed77fc56ad2fe061d2da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 23 Jan 2017 01:54:28 +0000 Subject: [PATCH] Implement a managed TLS stream and tell libgit2 to use it Instead of having to guess about how to make the TLS stream safe on the given platform, we can use a managed one we implement ourselves and tell libgit2 to use that. --- git.go | 4 + remote.go | 18 +++- stream.go | 251 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ wrapper.c | 23 +++++ 4 files changed, 294 insertions(+), 2 deletions(-) create mode 100644 stream.go diff --git a/git.go b/git.go index cb973ee..377b9ec 100644 --- a/git.go +++ b/git.go @@ -137,6 +137,10 @@ func init() { panic("libgit2 was not built with threading support") } + if err := RegisterManagedTls(); err != nil { + panic(err) + } + // This is not something we should be doing, as we may be // stomping all over someone else's setup. The user should do // this themselves or use some binding/wrapper which does it diff --git a/remote.go b/remote.go index d3f437d..98e35cd 100644 --- a/remote.go +++ b/remote.go @@ -10,6 +10,7 @@ extern void _go_git_setup_callbacks(git_remote_callbacks *callbacks); import "C" import ( "crypto/x509" + "errors" "reflect" "runtime" "strings" @@ -163,6 +164,20 @@ type Certificate struct { Hostkey HostkeyCertificate } +func (self *Certificate) toC() (*C.git_cert, error) { + switch self.Kind { + case CertificateX509: + ccert := (*C.git_cert_x509)(C.calloc(1, C.size_t(unsafe.Sizeof(C.git_cert_x509{})))) + ccert.parent.cert_type = C.GIT_CERT_X509 + rawCert := self.X509.Raw + ccert.len = C.size_t(len(rawCert)) + ccert.data = C.CBytes(rawCert) + return (*C.git_cert)(unsafe.Pointer(ccert)), nil + default: + return nil, errors.New("not supported") + } +} + type HostkeyKind uint const ( @@ -734,13 +749,12 @@ func (o *Remote) Connect(direction ConnectDirection, callbacks *RemoteCallbacks, var cproxy C.git_proxy_options populateProxyOptions(&cproxy, proxyOpts) defer freeProxyOptions(&cproxy) - + cheaders := C.git_strarray{} cheaders.count = C.size_t(len(headers)) cheaders.strings = makeCStringsFromStrings(headers) defer freeStrarray(&cheaders) - runtime.LockOSThread() defer runtime.UnlockOSThread() diff --git a/stream.go b/stream.go new file mode 100644 index 0000000..8fb0b89 --- /dev/null +++ b/stream.go @@ -0,0 +1,251 @@ +package git + +/* +#include +#include + +typedef struct { + git_stream parent; + void *ptr; +} managed_stream; + +extern int _go_git_register_tls(void); +extern void _go_git_setup_stream(managed_stream* s, int encrypted, int proxy_support, void *ptr); + +*/ +import "C" + +import ( + "crypto/tls" + "errors" + "fmt" + "io" + "reflect" + "runtime" + "unsafe" +) + +// Network stream for libgit2 to use +type Stream interface { + Encrypted() bool + ProxySupport() bool + Connect() error + Certificate() (Certificate, error) + SetProxy(ProxyOptions) error + io.ReadWriteCloser +} + +type ManagedStream struct { + host string + port string + conn *tls.Conn +} + +func (self *ManagedStream) Encrypted() bool { + return true +} + +func (self *ManagedStream) ProxySupport() bool { + return false +} + +func (self *ManagedStream) Connect() error { + conn, err := tls.Dial("tcp", fmt.Sprintf("%s:%s", self.host, self.port), nil) + if err != nil { + return err + } + + self.conn = conn + return nil +} + +func (self *ManagedStream) Certificate() (Certificate, error) { + connState := self.conn.ConnectionState() + cert := Certificate{ + Kind: CertificateX509, + X509: connState.PeerCertificates[0], + } + + return cert, nil +} + +func (self *ManagedStream) SetProxy(opts ProxyOptions) error { + return errors.New("proxy not supported") +} + +func (self *ManagedStream) Read(p []byte) (int, error) { + return self.conn.Read(p) +} + +func (self *ManagedStream) Write(p []byte) (int, error) { + return self.conn.Write(p) +} + +func (self *ManagedStream) Close() error { + return self.conn.Close() +} + +var errNotStream = errors.New("passed object does not implement Stream") + +// getStreamInterface extracts the Stream interface from the pointers we passed +// to the C code. +func getStreamInterface(_s *C.git_stream) (Stream, error) { + // For type compatibility we accept C.git_stream but we know we pass + // C.managed_stream so force the casting to that. + wrapperPtr := (*C.managed_stream)(unsafe.Pointer(_s)) + + // Inside we've stored a handle to the actual type, which must implement + // Stream. + stream, ok := pointerHandles.Get(wrapperPtr.ptr).(Stream) + if !ok { + return nil, errNotStream + } + + return stream, nil +} + +//export streamCertificate +func streamCertificate(out **C.git_cert, _s *C.git_stream) C.int { + stream, err := getStreamInterface(_s) + if err != nil { + return setLibgit2Error(err) + } + + cert, err := stream.Certificate() + if err != nil { + return setLibgit2Error(err) + } + + ccert, err := cert.toC() + if err != nil { + return setLibgit2Error(err) + } + + *out = ccert + return 0 +} + +//export streamSetProxy +func streamSetProxy(s *C.git_stream, proxy_opts *C.git_proxy_options) C.int { + setLibgit2Error(errors.New("proxy not supported")) + return -1 +} + +//export streamConnect +func streamConnect(_s *C.git_stream) C.int { + stream, err := getStreamInterface(_s) + if err != nil { + return setLibgit2Error(err) + } + + err = stream.Connect() + if err != nil { + return setLibgit2Error(err) + } + + return 0 +} + +//export streamRead +func streamRead(_s *C.git_stream, data unsafe.Pointer, l C.size_t) C.ssize_t { + stream, err := getStreamInterface(_s) + if err != nil { + setLibgit2Error(err) + return -1 + } + + var p []byte + header := (*reflect.SliceHeader)(unsafe.Pointer(&p)) + header.Cap = int(l) + header.Len = int(l) + header.Data = uintptr(data) + + n, err := stream.Read(p) + if err != nil { + setLibgit2Error(err) + return -1 + } + + return C.ssize_t(n) +} + +//export streamWrite +func streamWrite(_s *C.git_stream, data unsafe.Pointer, l C.size_t, _f C.int) C.ssize_t { + stream, err := getStreamInterface(_s) + if err != nil { + setLibgit2Error(err) + return -1 + } + + var p []byte + header := (*reflect.SliceHeader)(unsafe.Pointer(&p)) + header.Cap = int(l) + header.Len = int(l) + header.Data = uintptr(data) + + n, err := stream.Write(p) + if err != nil { + setLibgit2Error(err) + return -1 + } + + return C.ssize_t(n) +} + +//export streamClose +func streamClose(_s *C.git_stream) C.int { + stream, err := getStreamInterface(_s) + if err != nil { + return setLibgit2Error(err) + } + + err = stream.Close() + if err != nil { + return setLibgit2Error(err) + } + + return 0 +} + +//export streamFree +func streamFree(_s *C.git_stream) { + wrapperPtr := (*C.managed_stream)(unsafe.Pointer(_s)) + pointerHandles.Untrack(wrapperPtr.ptr) +} + +func newManagedStream(host, port string) *ManagedStream { + return &ManagedStream{ + host: host, + port: port, + } +} + +//export streamCallbackCb +func streamCallbackCb(out **C.git_stream, chost, cport *C.char) C.int { + stream := C.calloc(1, C.size_t(unsafe.Sizeof(C.managed_stream{}))) + managed := newManagedStream(C.GoString(chost), C.GoString(cport)) + managedPtr := pointerHandles.Track(managed) + C._go_git_setup_stream(stream, 1, 0, managedPtr) + + *out = (*C.git_stream)(stream) + return 0 +} + +func setLibgit2Error(err error) C.int { + cstr := C.CString(err.Error()) + defer C.free(unsafe.Pointer(cstr)) + C.giterr_set_str(C.GITERR_NET, cstr) + + return -1 +} + +func RegisterManagedTls() error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + if err := C._go_git_register_tls(); err != 0 { + return MakeGitError(err) + } + + return nil +} diff --git a/wrapper.c b/wrapper.c index 11c2f32..746ae45 100644 --- a/wrapper.c +++ b/wrapper.c @@ -180,4 +180,27 @@ void _go_git_writestream_free(git_writestream *stream) stream->free(stream); } +int _go_git_register_tls(void) +{ + return git_stream_register_tls((git_stream_cb) streamCallbackCb); +} + +typedef int (*set_proxy_cb)(struct git_stream *, const git_proxy_options *proxy_opts); +typedef ssize_t (*write_cb)(struct git_stream *, const char *, size_t, int); + +void _go_git_setup_stream(managed_stream* s, int encrypted, int proxy_support, void *ptr) +{ + s->parent.version = GIT_STREAM_VERSION; + s->parent.encrypted = encrypted; + s->parent.proxy_support = proxy_support; + s->parent.connect = streamConnect; + s->parent.certificate = streamCertificate; + s->parent.set_proxy = (set_proxy_cb) streamSetProxy; + s->parent.read = streamRead; + s->parent.write = (write_cb) streamWrite; + s->parent.close = streamClose; + s->parent.free = streamFree; + s->ptr = ptr; +} + /* EOF */ -- 2.45.2