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.
This commit is contained in:
parent
4a14260153
commit
528e14aa4f
4
git.go
4
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
|
||||
|
|
18
remote.go
18
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()
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
23
wrapper.c
23
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 */
|
||||
|
|
Loading…
Reference in New Issue