[WIP] Use Go's TLS stream instead of whatever the library ships with #372

Closed
carlosmn wants to merge 2 commits from cmn/tls-stream into main
4 changed files with 294 additions and 2 deletions

4
git.go
View File

@ -129,6 +129,10 @@ func init() {
panic("libgit2 was not built with threading support") 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 // This is not something we should be doing, as we may be
// stomping all over someone else's setup. The user should do // stomping all over someone else's setup. The user should do
// this themselves or use some binding/wrapper which does it // this themselves or use some binding/wrapper which does it

View File

@ -10,6 +10,7 @@ extern void _go_git_setup_callbacks(git_remote_callbacks *callbacks);
import "C" import "C"
import ( import (
"crypto/x509" "crypto/x509"
"errors"
"reflect" "reflect"
"runtime" "runtime"
"strings" "strings"
@ -163,6 +164,20 @@ type Certificate struct {
Hostkey HostkeyCertificate 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 type HostkeyKind uint
const ( const (
@ -734,13 +749,12 @@ func (o *Remote) Connect(direction ConnectDirection, callbacks *RemoteCallbacks,
var cproxy C.git_proxy_options var cproxy C.git_proxy_options
populateProxyOptions(&cproxy, proxyOpts) populateProxyOptions(&cproxy, proxyOpts)
defer freeProxyOptions(&cproxy) defer freeProxyOptions(&cproxy)
cheaders := C.git_strarray{} cheaders := C.git_strarray{}
cheaders.count = C.size_t(len(headers)) cheaders.count = C.size_t(len(headers))
cheaders.strings = makeCStringsFromStrings(headers) cheaders.strings = makeCStringsFromStrings(headers)
defer freeStrarray(&cheaders) defer freeStrarray(&cheaders)
runtime.LockOSThread() runtime.LockOSThread()
defer runtime.UnlockOSThread() defer runtime.UnlockOSThread()

251
stream.go Normal file
View File

@ -0,0 +1,251 @@
package git
/*
#include <git2.h>
#include <git2/sys/stream.h>
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
}

View File

@ -180,4 +180,27 @@ void _go_git_writestream_free(git_writestream *stream)
stream->free(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 */ /* EOF */