mirror of
https://github.com/libp2p/go-openssl.git
synced 2025-01-29 05:10:10 +08:00
space monkey internal commit export
[katamari commit: fbadad69fa95d38c389d6dbf9607599c51353e78]
This commit is contained in:
parent
94414970bf
commit
f3fa51fc61
300
bio.go
Normal file
300
bio.go
Normal file
@ -0,0 +1,300 @@
|
||||
// Copyright (C) 2014 Space Monkey, Inc.
|
||||
|
||||
package openssl
|
||||
|
||||
/*
|
||||
#cgo pkg-config: openssl
|
||||
#include <string.h>
|
||||
#include <openssl/bio.h>
|
||||
|
||||
extern int cbioNew(BIO *b);
|
||||
extern int cbioFree(BIO *b);
|
||||
|
||||
extern int writeBioWrite(BIO *b, char *buf, int size);
|
||||
extern long writeBioCtrl(BIO *b, int cmd, long arg1, void *arg2);
|
||||
static int writeBioPuts(BIO *b, const char *str) {
|
||||
return writeBioWrite(b, (char*)str, (int)strlen(str));
|
||||
}
|
||||
|
||||
extern int readBioRead(BIO *b, char *buf, int size);
|
||||
extern long readBioCtrl(BIO *b, int cmd, long arg1, void *arg2);
|
||||
|
||||
static BIO_METHOD writeBioMethod = {
|
||||
BIO_TYPE_SOURCE_SINK,
|
||||
"Go Write BIO",
|
||||
(int (*)(BIO *, const char *, int))writeBioWrite,
|
||||
NULL,
|
||||
writeBioPuts,
|
||||
NULL,
|
||||
writeBioCtrl,
|
||||
cbioNew,
|
||||
cbioFree,
|
||||
NULL};
|
||||
|
||||
static BIO_METHOD* BIO_s_writeBio() { return &writeBioMethod; }
|
||||
|
||||
static BIO_METHOD readBioMethod = {
|
||||
BIO_TYPE_SOURCE_SINK,
|
||||
"Go Read BIO",
|
||||
NULL,
|
||||
readBioRead,
|
||||
NULL,
|
||||
NULL,
|
||||
readBioCtrl,
|
||||
cbioNew,
|
||||
cbioFree,
|
||||
NULL};
|
||||
|
||||
static BIO_METHOD* BIO_s_readBio() { return &readBioMethod; }
|
||||
|
||||
static void BIO_clear_retry_flags_not_a_macro(BIO *b) {
|
||||
BIO_clear_retry_flags(b);
|
||||
}
|
||||
|
||||
static void BIO_set_retry_read_not_a_macro(BIO *b) { BIO_set_retry_read(b); }
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"io"
|
||||
"reflect"
|
||||
"sync"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
sslMaxRecord = 16 * 1024
|
||||
)
|
||||
|
||||
func nonCopyGoBytes(ptr uintptr, length int) []byte {
|
||||
var slice []byte
|
||||
header := (*reflect.SliceHeader)(unsafe.Pointer(&slice))
|
||||
header.Cap = length
|
||||
header.Len = length
|
||||
header.Data = ptr
|
||||
return slice
|
||||
}
|
||||
|
||||
func nonCopyCString(data *C.char, size C.int) []byte {
|
||||
return nonCopyGoBytes(uintptr(unsafe.Pointer(data)), int(size))
|
||||
}
|
||||
|
||||
//export cbioNew
|
||||
func cbioNew(b *C.BIO) C.int {
|
||||
b.shutdown = 1
|
||||
b.init = 1
|
||||
b.num = -1
|
||||
b.ptr = nil
|
||||
b.flags = 0
|
||||
return 1
|
||||
}
|
||||
|
||||
//export cbioFree
|
||||
func cbioFree(b *C.BIO) C.int {
|
||||
return 1
|
||||
}
|
||||
|
||||
type writeBio struct {
|
||||
data_mtx sync.Mutex
|
||||
op_mtx sync.Mutex
|
||||
buf []byte
|
||||
}
|
||||
|
||||
func loadWritePtr(b *C.BIO) *writeBio {
|
||||
return (*writeBio)(unsafe.Pointer(b.ptr))
|
||||
}
|
||||
|
||||
//export writeBioWrite
|
||||
func writeBioWrite(b *C.BIO, data *C.char, size C.int) C.int {
|
||||
ptr := loadWritePtr(b)
|
||||
if ptr == nil || data == nil || size < 0 {
|
||||
return -1
|
||||
}
|
||||
ptr.data_mtx.Lock()
|
||||
defer ptr.data_mtx.Unlock()
|
||||
C.BIO_clear_retry_flags_not_a_macro(b)
|
||||
ptr.buf = append(ptr.buf, nonCopyCString(data, size)...)
|
||||
return size
|
||||
}
|
||||
|
||||
//export writeBioCtrl
|
||||
func writeBioCtrl(b *C.BIO, cmd C.int, arg1 C.long, arg2 unsafe.Pointer) C.long {
|
||||
switch cmd {
|
||||
case C.BIO_CTRL_WPENDING:
|
||||
return writeBioPending(b)
|
||||
case C.BIO_CTRL_DUP, C.BIO_CTRL_FLUSH:
|
||||
return 1
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func writeBioPending(b *C.BIO) C.long {
|
||||
ptr := loadWritePtr(b)
|
||||
if ptr == nil {
|
||||
return 0
|
||||
}
|
||||
ptr.data_mtx.Lock()
|
||||
defer ptr.data_mtx.Unlock()
|
||||
return C.long(len(ptr.buf))
|
||||
}
|
||||
|
||||
func (b *writeBio) WriteTo(w io.Writer) (rv int64, err error) {
|
||||
b.op_mtx.Lock()
|
||||
defer b.op_mtx.Unlock()
|
||||
|
||||
// write whatever data we currently have
|
||||
b.data_mtx.Lock()
|
||||
data := b.buf
|
||||
b.data_mtx.Unlock()
|
||||
total := int64(0)
|
||||
|
||||
for {
|
||||
if len(data) == 0 {
|
||||
return total, nil
|
||||
}
|
||||
written, err := w.Write(data)
|
||||
total += int64(written)
|
||||
|
||||
// subtract however much data we wrote from the buffer
|
||||
b.data_mtx.Lock()
|
||||
n := copy(b.buf, b.buf[written:])
|
||||
b.buf = b.buf[:n]
|
||||
data = b.buf
|
||||
b.data_mtx.Unlock()
|
||||
if err != nil {
|
||||
return total, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (self *writeBio) Disconnect(b *C.BIO) {
|
||||
if loadWritePtr(b) == self {
|
||||
b.ptr = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (b *writeBio) MakeCBIO() *C.BIO {
|
||||
rv := C.BIO_new(C.BIO_s_writeBio())
|
||||
rv.ptr = unsafe.Pointer(b)
|
||||
return rv
|
||||
}
|
||||
|
||||
type readBio struct {
|
||||
data_mtx sync.Mutex
|
||||
op_mtx sync.Mutex
|
||||
buf []byte
|
||||
eof bool
|
||||
}
|
||||
|
||||
func loadReadPtr(b *C.BIO) *readBio {
|
||||
return (*readBio)(unsafe.Pointer(b.ptr))
|
||||
}
|
||||
|
||||
//export readBioRead
|
||||
func readBioRead(b *C.BIO, data *C.char, size C.int) C.int {
|
||||
ptr := loadReadPtr(b)
|
||||
if ptr == nil || size < 0 {
|
||||
return -1
|
||||
}
|
||||
ptr.data_mtx.Lock()
|
||||
defer ptr.data_mtx.Unlock()
|
||||
C.BIO_clear_retry_flags_not_a_macro(b)
|
||||
if len(ptr.buf) == 0 {
|
||||
if ptr.eof {
|
||||
return 0
|
||||
}
|
||||
C.BIO_set_retry_read_not_a_macro(b)
|
||||
return -1
|
||||
}
|
||||
if size == 0 || data == nil {
|
||||
return C.int(len(ptr.buf))
|
||||
}
|
||||
n := copy(nonCopyCString(data, size), ptr.buf)
|
||||
ptr.buf = ptr.buf[:copy(ptr.buf, ptr.buf[n:])]
|
||||
return C.int(n)
|
||||
}
|
||||
|
||||
//export readBioCtrl
|
||||
func readBioCtrl(b *C.BIO, cmd C.int, arg1 C.long, arg2 unsafe.Pointer) C.long {
|
||||
switch cmd {
|
||||
case C.BIO_CTRL_PENDING:
|
||||
return readBioPending(b)
|
||||
case C.BIO_CTRL_DUP, C.BIO_CTRL_FLUSH:
|
||||
return 1
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func readBioPending(b *C.BIO) C.long {
|
||||
ptr := loadReadPtr(b)
|
||||
if ptr == nil {
|
||||
return 0
|
||||
}
|
||||
ptr.data_mtx.Lock()
|
||||
defer ptr.data_mtx.Unlock()
|
||||
return C.long(len(ptr.buf))
|
||||
}
|
||||
|
||||
func (b *readBio) ReadFromOnce(r io.Reader) (n int, err error) {
|
||||
b.op_mtx.Lock()
|
||||
defer b.op_mtx.Unlock()
|
||||
|
||||
// make sure we have a destination that fits at least one SSL record
|
||||
b.data_mtx.Lock()
|
||||
if cap(b.buf) < len(b.buf)+sslMaxRecord {
|
||||
new_buf := make([]byte, len(b.buf), len(b.buf)+sslMaxRecord)
|
||||
copy(new_buf, b.buf)
|
||||
b.buf = new_buf
|
||||
}
|
||||
dst := b.buf[len(b.buf):cap(b.buf)]
|
||||
b.data_mtx.Unlock()
|
||||
|
||||
n, err = r.Read(dst)
|
||||
b.data_mtx.Lock()
|
||||
defer b.data_mtx.Unlock()
|
||||
if n > 0 {
|
||||
b.buf = b.buf[:len(b.buf)+n]
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (b *readBio) MakeCBIO() *C.BIO {
|
||||
rv := C.BIO_new(C.BIO_s_readBio())
|
||||
rv.ptr = unsafe.Pointer(b)
|
||||
return rv
|
||||
}
|
||||
|
||||
func (self *readBio) Disconnect(b *C.BIO) {
|
||||
if loadReadPtr(b) == self {
|
||||
b.ptr = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (b *readBio) MarkEOF() {
|
||||
b.data_mtx.Lock()
|
||||
defer b.data_mtx.Unlock()
|
||||
b.eof = true
|
||||
}
|
||||
|
||||
type anyBio C.BIO
|
||||
|
||||
func asAnyBio(b *C.BIO) *anyBio { return (*anyBio)(b) }
|
||||
|
||||
func (b *anyBio) Read(buf []byte) (n int, err error) {
|
||||
n = int(C.BIO_read((*C.BIO)(b), unsafe.Pointer(&buf[0]), C.int(len(buf))))
|
||||
if n <= 0 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (b *anyBio) Write(buf []byte) (written int, err error) {
|
||||
n := int(C.BIO_write((*C.BIO)(b), unsafe.Pointer(&buf[0]),
|
||||
C.int(len(buf))))
|
||||
if n != len(buf) {
|
||||
return n, SSLError.New("BIO write failed")
|
||||
}
|
||||
return n, nil
|
||||
}
|
379
conn.go
Normal file
379
conn.go
Normal file
@ -0,0 +1,379 @@
|
||||
// Copyright (C) 2014 Space Monkey, Inc.
|
||||
|
||||
package openssl
|
||||
|
||||
// #cgo pkg-config: openssl
|
||||
// #include <openssl/ssl.h>
|
||||
// #include <openssl/conf.h>
|
||||
// #include <openssl/err.h>
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"code.spacemonkey.com/go/errors"
|
||||
space_sync "code.spacemonkey.com/go/space/sync"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrnoError = errors.New(SSLError, "Errno")
|
||||
|
||||
internalConnError = errors.New(SSLError, "Unhandled internal error")
|
||||
zeroReturn = internalConnError.New("zero return")
|
||||
wantRead = internalConnError.New("want read")
|
||||
wantWrite = internalConnError.New("want write")
|
||||
tryAgain = internalConnError.New("try again")
|
||||
)
|
||||
|
||||
type Conn struct {
|
||||
conn net.Conn
|
||||
ssl *C.SSL
|
||||
into_ssl *readBio
|
||||
from_ssl *writeBio
|
||||
is_shutdown bool
|
||||
mtx sync.Mutex
|
||||
want_read_future *space_sync.Future
|
||||
}
|
||||
|
||||
func newSSL(ctx *C.SSL_CTX) (*C.SSL, error) {
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
ssl := C.SSL_new(ctx)
|
||||
if ssl == nil {
|
||||
return nil, errorFromErrorQueue()
|
||||
}
|
||||
return ssl, nil
|
||||
}
|
||||
|
||||
func newConn(conn net.Conn, ctx *Ctx) (*Conn, error) {
|
||||
ssl, err := newSSL(ctx.ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
into_ssl := &readBio{}
|
||||
from_ssl := &writeBio{}
|
||||
|
||||
into_ssl_cbio := into_ssl.MakeCBIO()
|
||||
from_ssl_cbio := from_ssl.MakeCBIO()
|
||||
if into_ssl_cbio == nil || from_ssl_cbio == nil {
|
||||
// these frees are null safe
|
||||
C.BIO_free(into_ssl_cbio)
|
||||
C.BIO_free(from_ssl_cbio)
|
||||
C.SSL_free(ssl)
|
||||
return nil, SSLError.New("failed to allocate memory BIO")
|
||||
}
|
||||
|
||||
// the ssl object takes ownership of these objects now
|
||||
C.SSL_set_bio(ssl, into_ssl_cbio, from_ssl_cbio)
|
||||
|
||||
c := &Conn{
|
||||
conn: conn,
|
||||
ssl: ssl,
|
||||
into_ssl: into_ssl,
|
||||
from_ssl: from_ssl}
|
||||
runtime.SetFinalizer(c, func(c *Conn) {
|
||||
c.into_ssl.Disconnect(into_ssl_cbio)
|
||||
c.from_ssl.Disconnect(from_ssl_cbio)
|
||||
C.SSL_free(c.ssl)
|
||||
})
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func Client(conn net.Conn, ctx *Ctx) (*Conn, error) {
|
||||
c, err := newConn(conn, ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
C.SSL_set_connect_state(c.ssl)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func Server(conn net.Conn, ctx *Ctx) (*Conn, error) {
|
||||
c, err := newConn(conn, ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
C.SSL_set_accept_state(c.ssl)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *Conn) fillInputBuffer() error {
|
||||
for {
|
||||
n, err := c.into_ssl.ReadFromOnce(c.conn)
|
||||
if n == 0 && err == nil {
|
||||
continue
|
||||
}
|
||||
if err == io.EOF {
|
||||
c.into_ssl.MarkEOF()
|
||||
return c.Close()
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Conn) flushOutputBuffer() error {
|
||||
_, err := c.from_ssl.WriteTo(c.conn)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Conn) getErrorHandler(rv C.int, errno error) func() error {
|
||||
errcode := C.SSL_get_error(c.ssl, rv)
|
||||
switch errcode {
|
||||
case C.SSL_ERROR_ZERO_RETURN:
|
||||
return func() error {
|
||||
c.Close()
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
case C.SSL_ERROR_WANT_READ:
|
||||
if c.want_read_future != nil {
|
||||
want_read_future := c.want_read_future
|
||||
return func() error {
|
||||
_, err := want_read_future.Get()
|
||||
return err
|
||||
}
|
||||
}
|
||||
c.want_read_future = space_sync.NewFuture()
|
||||
want_read_future := c.want_read_future
|
||||
return func() (err error) {
|
||||
defer func() {
|
||||
c.mtx.Lock()
|
||||
c.want_read_future = nil
|
||||
c.mtx.Unlock()
|
||||
want_read_future.Set(nil, err)
|
||||
}()
|
||||
err = c.flushOutputBuffer()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = c.fillInputBuffer()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return tryAgain
|
||||
}
|
||||
case C.SSL_ERROR_WANT_WRITE:
|
||||
return func() error {
|
||||
err := c.flushOutputBuffer()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return tryAgain
|
||||
}
|
||||
case C.SSL_ERROR_SYSCALL:
|
||||
var err error
|
||||
if C.ERR_peek_error() == 0 {
|
||||
switch rv {
|
||||
case 0:
|
||||
err = SSLError.New("Unexpected EOF")
|
||||
case -1:
|
||||
err = ErrnoError.Wrap(errno)
|
||||
default:
|
||||
err = errorFromErrorQueue()
|
||||
}
|
||||
} else {
|
||||
err = errorFromErrorQueue()
|
||||
}
|
||||
return func() error { return err }
|
||||
default:
|
||||
err := errorFromErrorQueue()
|
||||
return func() error { return err }
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Conn) handleError(errcb func() error) error {
|
||||
if errcb != nil {
|
||||
return errcb()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Conn) handshake() func() error {
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
c.mtx.Lock()
|
||||
defer c.mtx.Unlock()
|
||||
if c.is_shutdown {
|
||||
return func() error { return io.ErrUnexpectedEOF }
|
||||
}
|
||||
rv, errno := C.SSL_do_handshake(c.ssl)
|
||||
if rv > 0 {
|
||||
return nil
|
||||
}
|
||||
return c.getErrorHandler(rv, errno)
|
||||
}
|
||||
|
||||
func (c *Conn) Handshake() error {
|
||||
err := tryAgain
|
||||
for err == tryAgain {
|
||||
err = c.handleError(c.handshake())
|
||||
if err == nil {
|
||||
return c.flushOutputBuffer()
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Conn) PeerCertificate() (*Certificate, error) {
|
||||
c.mtx.Lock()
|
||||
if c.is_shutdown {
|
||||
return nil, SSLError.New("connection closed")
|
||||
}
|
||||
x := C.SSL_get_peer_certificate(c.ssl)
|
||||
c.mtx.Unlock()
|
||||
if x == nil {
|
||||
return nil, SSLError.New("no peer certificate found")
|
||||
}
|
||||
cert := &Certificate{x: x}
|
||||
runtime.SetFinalizer(cert, func(cert *Certificate) {
|
||||
C.X509_free(cert.x)
|
||||
})
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
func (c *Conn) shutdown() func() error {
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
c.mtx.Lock()
|
||||
defer c.mtx.Unlock()
|
||||
rv, errno := C.SSL_shutdown(c.ssl)
|
||||
if rv > 0 {
|
||||
return nil
|
||||
}
|
||||
if rv == 0 {
|
||||
// The OpenSSL docs say that in this case, the shutdown is not
|
||||
// finished, and we should call SSL_shutdown() a second time, if a
|
||||
// bidirectional shutdown is going to be performed. Further, the
|
||||
// output of SSL_get_error may be misleading, as an erroneous
|
||||
// SSL_ERROR_SYSCALL may be flagged even though no error occurred.
|
||||
// So, TODO: revisit bidrectional shutdown, possibly trying again.
|
||||
// Note: some broken clients won't engage in bidirectional shutdown
|
||||
// without tickling them to close by sending a TCP_FIN packet, or
|
||||
// shutting down the write-side of the connection.
|
||||
return nil
|
||||
} else {
|
||||
return c.getErrorHandler(rv, errno)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Conn) shutdownLoop() error {
|
||||
err := tryAgain
|
||||
shutdown_tries := 0
|
||||
for err == tryAgain {
|
||||
shutdown_tries = shutdown_tries + 1
|
||||
err = c.handleError(c.shutdown())
|
||||
if err == nil {
|
||||
return c.flushOutputBuffer()
|
||||
}
|
||||
if err == tryAgain && shutdown_tries >= 2 {
|
||||
return SSLError.New("shutdown requested a third time?")
|
||||
}
|
||||
}
|
||||
if err == io.ErrUnexpectedEOF {
|
||||
err = nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Conn) Close() error {
|
||||
c.mtx.Lock()
|
||||
if c.is_shutdown {
|
||||
c.mtx.Unlock()
|
||||
return nil
|
||||
}
|
||||
c.is_shutdown = true
|
||||
c.mtx.Unlock()
|
||||
errs := errors.NewErrorGroup()
|
||||
errs.Add(c.shutdownLoop())
|
||||
errs.Add(c.conn.Close())
|
||||
return errs.Finalize()
|
||||
}
|
||||
|
||||
func (c *Conn) read(b []byte) (int, func() error) {
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
c.mtx.Lock()
|
||||
defer c.mtx.Unlock()
|
||||
if c.is_shutdown {
|
||||
return 0, func() error { return io.EOF }
|
||||
}
|
||||
rv, errno := C.SSL_read(c.ssl, unsafe.Pointer(&b[0]), C.int(len(b)))
|
||||
if rv > 0 {
|
||||
return int(rv), nil
|
||||
}
|
||||
return 0, c.getErrorHandler(rv, errno)
|
||||
}
|
||||
|
||||
func (c *Conn) Read(b []byte) (n int, err error) {
|
||||
if len(b) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
err = tryAgain
|
||||
for err == tryAgain {
|
||||
n, errcb := c.read(b)
|
||||
err = c.handleError(errcb)
|
||||
if err == nil {
|
||||
return n, c.flushOutputBuffer()
|
||||
}
|
||||
if err == io.ErrUnexpectedEOF {
|
||||
err = io.EOF
|
||||
}
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
|
||||
func (c *Conn) write(b []byte) (int, func() error) {
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
c.mtx.Lock()
|
||||
defer c.mtx.Unlock()
|
||||
if c.is_shutdown {
|
||||
err := SSLError.New("connection closed")
|
||||
return 0, func() error { return err }
|
||||
}
|
||||
rv, errno := C.SSL_write(c.ssl, unsafe.Pointer(&b[0]), C.int(len(b)))
|
||||
if rv > 0 {
|
||||
return int(rv), nil
|
||||
}
|
||||
return 0, c.getErrorHandler(rv, errno)
|
||||
}
|
||||
|
||||
func (c *Conn) Write(b []byte) (written int, err error) {
|
||||
if len(b) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
err = tryAgain
|
||||
for err == tryAgain {
|
||||
n, errcb := c.write(b)
|
||||
err = c.handleError(errcb)
|
||||
if err == nil {
|
||||
return n, c.flushOutputBuffer()
|
||||
}
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
|
||||
func (c *Conn) LocalAddr() net.Addr {
|
||||
return c.conn.LocalAddr()
|
||||
}
|
||||
|
||||
func (c *Conn) RemoteAddr() net.Addr {
|
||||
return c.conn.RemoteAddr()
|
||||
}
|
||||
|
||||
func (c *Conn) SetDeadline(t time.Time) error {
|
||||
return c.conn.SetDeadline(t)
|
||||
}
|
||||
|
||||
func (c *Conn) SetReadDeadline(t time.Time) error {
|
||||
return c.conn.SetReadDeadline(t)
|
||||
}
|
||||
|
||||
func (c *Conn) SetWriteDeadline(t time.Time) error {
|
||||
return c.conn.SetWriteDeadline(t)
|
||||
}
|
155
ctx.go
Normal file
155
ctx.go
Normal file
@ -0,0 +1,155 @@
|
||||
// Copyright (C) 2014 Space Monkey, Inc.
|
||||
|
||||
package openssl
|
||||
|
||||
// #cgo pkg-config: openssl
|
||||
// #include <openssl/ssl.h>
|
||||
// #include <openssl/conf.h>
|
||||
//
|
||||
// long SSL_CTX_set_options_not_a_macro(SSL_CTX* ctx, long options) {
|
||||
// return SSL_CTX_set_options(ctx, options);
|
||||
// }
|
||||
//
|
||||
// long SSL_CTX_set_mode_not_a_macro(SSL_CTX* ctx, long modes) {
|
||||
// return SSL_CTX_set_mode(ctx, modes);
|
||||
// }
|
||||
//
|
||||
// long SSL_CTX_set_session_cache_mode_not_a_macro(SSL_CTX* ctx, long modes) {
|
||||
// return SSL_CTX_set_session_cache_mode(ctx, modes);
|
||||
// }
|
||||
//
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type Ctx struct {
|
||||
ctx *C.SSL_CTX
|
||||
}
|
||||
|
||||
func NewCtx() (*Ctx, error) {
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
ctx := C.SSL_CTX_new(C.SSLv23_method())
|
||||
if ctx == nil {
|
||||
return nil, errorFromErrorQueue()
|
||||
}
|
||||
c := &Ctx{ctx: ctx}
|
||||
runtime.SetFinalizer(c, func(c *Ctx) {
|
||||
C.SSL_CTX_free(c.ctx)
|
||||
})
|
||||
c.SetOptions(NoSSLv2 | NoSSLv3)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *Ctx) UseCertificate(cert *Certificate) error {
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
if int(C.SSL_CTX_use_certificate(c.ctx, cert.x)) != 1 {
|
||||
return errorFromErrorQueue()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Ctx) UsePrivateKey(key PrivateKey) error {
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
if int(C.SSL_CTX_use_PrivateKey(c.ctx, key.evpPKey())) != 1 {
|
||||
return errorFromErrorQueue()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type CertificateStore struct {
|
||||
store *C.X509_STORE
|
||||
ctx *Ctx // for gc
|
||||
}
|
||||
|
||||
func (c *Ctx) GetCertificateStore() *CertificateStore {
|
||||
// we don't need to dealloc the cert store pointer here, because it points
|
||||
// to a ctx internal. so we do need to keep the ctx around
|
||||
return &CertificateStore{
|
||||
store: C.SSL_CTX_get_cert_store(c.ctx),
|
||||
ctx: c}
|
||||
}
|
||||
|
||||
func (s *CertificateStore) AddCertificate(cert *Certificate) error {
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
if int(C.X509_STORE_add_cert(s.store, cert.x)) == 0 {
|
||||
return errorFromErrorQueue()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Options int
|
||||
|
||||
const (
|
||||
NoCompression Options = C.SSL_OP_NO_COMPRESSION
|
||||
NoSSLv2 = C.SSL_OP_NO_SSLv2
|
||||
NoSSLv3 = C.SSL_OP_NO_SSLv3
|
||||
// TODO: fill in all the others
|
||||
)
|
||||
|
||||
func (c *Ctx) SetOptions(options Options) Options {
|
||||
return Options(C.SSL_CTX_set_options_not_a_macro(
|
||||
c.ctx, C.long(options)))
|
||||
}
|
||||
|
||||
type Modes int
|
||||
|
||||
const (
|
||||
ReleaseBuffers Modes = C.SSL_MODE_RELEASE_BUFFERS
|
||||
// TODO: fill in all the others
|
||||
)
|
||||
|
||||
func (c *Ctx) SetMode(modes Modes) Modes {
|
||||
return Modes(C.SSL_CTX_set_mode_not_a_macro(c.ctx, C.long(modes)))
|
||||
}
|
||||
|
||||
type VerifyOptions int
|
||||
|
||||
const (
|
||||
VerifyPeer VerifyOptions = C.SSL_VERIFY_PEER
|
||||
VerifyFailIfNoPeerCert VerifyOptions = C.SSL_VERIFY_FAIL_IF_NO_PEER_CERT
|
||||
// TODO: fill in all the others
|
||||
)
|
||||
|
||||
func (c *Ctx) SetVerify(options VerifyOptions) {
|
||||
// TODO: take a callback
|
||||
C.SSL_CTX_set_verify(c.ctx, C.int(options), nil)
|
||||
}
|
||||
|
||||
func (c *Ctx) SetSessionId(session_id []byte) error {
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
if int(C.SSL_CTX_set_session_id_context(c.ctx,
|
||||
(*C.uchar)(unsafe.Pointer(&session_id[0])),
|
||||
C.uint(len(session_id)))) == 0 {
|
||||
return errorFromErrorQueue()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Ctx) SetCipherList(list string) error {
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
if int(C.SSL_CTX_set_cipher_list(c.ctx, C.CString(list))) == 0 {
|
||||
return errorFromErrorQueue()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type SessionCacheModes int
|
||||
|
||||
const (
|
||||
Off SessionCacheModes = C.SSL_SESS_CACHE_OFF
|
||||
// TODO: fill in all the others
|
||||
)
|
||||
|
||||
func (c *Ctx) SetSessionCacheMode(modes SessionCacheModes) SessionCacheModes {
|
||||
return SessionCacheModes(
|
||||
C.SSL_CTX_set_session_cache_mode_not_a_macro(c.ctx, C.long(modes)))
|
||||
}
|
78
init.go
Normal file
78
init.go
Normal file
@ -0,0 +1,78 @@
|
||||
// Copyright (C) 2014 Space Monkey, Inc.
|
||||
|
||||
package openssl
|
||||
|
||||
/*
|
||||
#cgo pkg-config: openssl
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/conf.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/engine.h>
|
||||
|
||||
extern void sslThreadId(CRYPTO_THREADID *id);
|
||||
extern void sslMutexOp(int mode, int n, char *file, int line);
|
||||
|
||||
static void OpenSSL_add_all_algorithms_not_a_macro() {
|
||||
OpenSSL_add_all_algorithms();
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"code.spacemonkey.com/go/errors"
|
||||
"code.spacemonkey.com/go/openssl/thread_id"
|
||||
)
|
||||
|
||||
var (
|
||||
SSLError = errors.New(nil, "SSL Error")
|
||||
sslMutexes []sync.Mutex
|
||||
)
|
||||
|
||||
func init() {
|
||||
C.OPENSSL_config(nil)
|
||||
C.ENGINE_load_builtin_engines()
|
||||
C.SSL_load_error_strings()
|
||||
C.SSL_library_init()
|
||||
C.OpenSSL_add_all_algorithms_not_a_macro()
|
||||
sslMutexes = make([]sync.Mutex, int(C.CRYPTO_num_locks()))
|
||||
C.CRYPTO_THREADID_set_callback((*[0]byte)(C.sslThreadId))
|
||||
C.CRYPTO_set_locking_callback((*[0]byte)(C.sslMutexOp))
|
||||
|
||||
// TODO: support dynlock callbacks
|
||||
}
|
||||
|
||||
// errorFromErrorQueue needs to run in the same OS thread as the operation
|
||||
// that caused the possible error
|
||||
func errorFromErrorQueue() error {
|
||||
var errors []string
|
||||
for {
|
||||
err := C.ERR_get_error()
|
||||
if err == 0 {
|
||||
break
|
||||
}
|
||||
errors = append(errors, fmt.Sprintf("%s:%s:%s",
|
||||
C.GoString(C.ERR_lib_error_string(err)),
|
||||
C.GoString(C.ERR_func_error_string(err)),
|
||||
C.GoString(C.ERR_reason_error_string(err))))
|
||||
}
|
||||
return SSLError.New("errors: %s", strings.Join(errors, "\n"))
|
||||
}
|
||||
|
||||
//export sslMutexOp
|
||||
func sslMutexOp(mode, n C.int, file *C.char, line C.int) {
|
||||
if mode&C.CRYPTO_LOCK > 0 {
|
||||
sslMutexes[n].Lock()
|
||||
} else {
|
||||
sslMutexes[n].Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
//export sslThreadId
|
||||
func sslThreadId(id *C.CRYPTO_THREADID) {
|
||||
C.CRYPTO_THREADID_set_pointer(id, thread_id.Id())
|
||||
}
|
26
listener.go
Normal file
26
listener.go
Normal file
@ -0,0 +1,26 @@
|
||||
// Copyright (C) 2014 Space Monkey, Inc.
|
||||
|
||||
package openssl
|
||||
|
||||
import (
|
||||
"net"
|
||||
)
|
||||
|
||||
type listener struct {
|
||||
net.Listener
|
||||
ctx *Ctx
|
||||
}
|
||||
|
||||
func (l *listener) Accept() (c net.Conn, err error) {
|
||||
c, err = l.Listener.Accept()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return Server(c, l.ctx)
|
||||
}
|
||||
|
||||
func NewListener(inner net.Listener, ctx *Ctx) net.Listener {
|
||||
return &listener{
|
||||
Listener: inner,
|
||||
ctx: ctx}
|
||||
}
|
204
pem.go
Normal file
204
pem.go
Normal file
@ -0,0 +1,204 @@
|
||||
// Copyright (C) 2014 Space Monkey, Inc.
|
||||
|
||||
package openssl
|
||||
|
||||
// #cgo pkg-config: openssl
|
||||
// #include <openssl/ssl.h>
|
||||
// #include <openssl/conf.h>
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"io/ioutil"
|
||||
"runtime"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type PublicKey interface {
|
||||
MarshalPKIXPublicKeyPEM() (pem_block []byte, err error)
|
||||
MarshalPKIXPublicKeyDER() (der_block []byte, err error)
|
||||
StdlibPublicKey() (*rsa.PublicKey, error)
|
||||
|
||||
evpPKey() *C.EVP_PKEY
|
||||
}
|
||||
|
||||
type PrivateKey interface {
|
||||
PublicKey
|
||||
|
||||
MarshalPKCS1PrivateKeyPEM() (pem_block []byte, err error)
|
||||
MarshalPKCS1PrivateKeyDER() (der_block []byte, err error)
|
||||
StdlibPrivateKey() (*rsa.PrivateKey, error)
|
||||
}
|
||||
|
||||
type pKey struct {
|
||||
key *C.EVP_PKEY
|
||||
}
|
||||
|
||||
func (key *pKey) evpPKey() *C.EVP_PKEY { return key.key }
|
||||
|
||||
func (key *pKey) MarshalPKCS1PrivateKeyPEM() (pem_block []byte,
|
||||
err error) {
|
||||
bio := C.BIO_new(C.BIO_s_mem())
|
||||
if bio == nil {
|
||||
return nil, SSLError.New("failed to allocate memory BIO")
|
||||
}
|
||||
defer C.BIO_free(bio)
|
||||
if int(C.PEM_write_bio_PrivateKey(bio, key.key, nil, nil, C.int(0), nil,
|
||||
nil)) != 1 {
|
||||
return nil, SSLError.New("failed dumping private key")
|
||||
}
|
||||
return ioutil.ReadAll(asAnyBio(bio))
|
||||
}
|
||||
|
||||
func (key *pKey) MarshalPKIXPublicKeyPEM() (pem_block []byte,
|
||||
err error) {
|
||||
bio := C.BIO_new(C.BIO_s_mem())
|
||||
if bio == nil {
|
||||
return nil, SSLError.New("failed to allocate memory BIO")
|
||||
}
|
||||
defer C.BIO_free(bio)
|
||||
if int(C.PEM_write_bio_PUBKEY(bio, key.key)) != 1 {
|
||||
return nil, SSLError.New("failed dumping public key")
|
||||
}
|
||||
return ioutil.ReadAll(asAnyBio(bio))
|
||||
}
|
||||
|
||||
func (key *pKey) MarshalPKCS1PrivateKeyDER() (der_block []byte,
|
||||
err error) {
|
||||
// TODO: i can't decipher how to get a generic PKCS1 Private Key in DER
|
||||
// format out of the openssl docs, so until someone who knows better
|
||||
// can chastise me for this, we'll do it this way.
|
||||
pem_block, err := key.MarshalPKCS1PrivateKeyPEM()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var p *pem.Block
|
||||
p, pem_block = pem.Decode(pem_block)
|
||||
if len(pem_block) > 0 || p == nil {
|
||||
return nil, SSLError.New("something went wrong with PEM generation")
|
||||
}
|
||||
return p.Bytes, nil
|
||||
}
|
||||
|
||||
func (key *pKey) MarshalPKIXPublicKeyDER() (der_block []byte,
|
||||
err error) {
|
||||
// TODO: i can't decipher how to get a generic PKIX Public Key in DER
|
||||
// format out of the openssl docs, so until someone who knows better
|
||||
// can chastise me for this, we'll do it this way.
|
||||
pem_block, err := key.MarshalPKIXPublicKeyPEM()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var p *pem.Block
|
||||
p, pem_block = pem.Decode(pem_block)
|
||||
if len(pem_block) > 0 || p == nil {
|
||||
return nil, SSLError.New("something went wrong with PEM generation")
|
||||
}
|
||||
return p.Bytes, nil
|
||||
}
|
||||
|
||||
func (key *pKey) StdlibPrivateKey() (*rsa.PrivateKey, error) {
|
||||
der_block, err := key.MarshalPKCS1PrivateKeyDER()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return x509.ParsePKCS1PrivateKey(der_block)
|
||||
}
|
||||
|
||||
func (key *pKey) StdlibPublicKey() (*rsa.PublicKey, error) {
|
||||
der_block, err := key.MarshalPKIXPublicKeyDER()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
k, err := x509.ParsePKIXPublicKey(der_block)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rk, ok := k.(*rsa.PublicKey)
|
||||
if !ok {
|
||||
return nil, SSLError.New("not an rsa public key")
|
||||
}
|
||||
return rk, nil
|
||||
}
|
||||
|
||||
func LoadPrivateKey(pem_block []byte) (PrivateKey, error) {
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
bio := C.BIO_new_mem_buf(unsafe.Pointer(&pem_block[0]),
|
||||
C.int(len(pem_block)))
|
||||
key := C.PEM_read_bio_PrivateKey(bio, nil, nil, nil)
|
||||
C.BIO_free(bio)
|
||||
if key == nil {
|
||||
return nil, errorFromErrorQueue()
|
||||
}
|
||||
p := &pKey{key: key}
|
||||
runtime.SetFinalizer(p, func(p *pKey) {
|
||||
C.EVP_PKEY_free(p.key)
|
||||
})
|
||||
return p, nil
|
||||
}
|
||||
|
||||
type Certificate struct {
|
||||
x *C.X509
|
||||
}
|
||||
|
||||
func LoadCertificate(pem_block []byte) (*Certificate, error) {
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
bio := C.BIO_new_mem_buf(unsafe.Pointer(&pem_block[0]),
|
||||
C.int(len(pem_block)))
|
||||
cert := C.PEM_read_bio_X509(bio, nil, nil, nil)
|
||||
C.BIO_free(bio)
|
||||
if cert == nil {
|
||||
return nil, errorFromErrorQueue()
|
||||
}
|
||||
x := &Certificate{x: cert}
|
||||
runtime.SetFinalizer(x, func(x *Certificate) {
|
||||
C.X509_free(x.x)
|
||||
})
|
||||
return x, nil
|
||||
}
|
||||
|
||||
func (c *Certificate) MarshalPEM() (pem_block []byte, err error) {
|
||||
bio := C.BIO_new(C.BIO_s_mem())
|
||||
if bio == nil {
|
||||
return nil, SSLError.New("failed to allocate memory BIO")
|
||||
}
|
||||
defer C.BIO_free(bio)
|
||||
if int(C.PEM_write_bio_X509(bio, c.x)) != 1 {
|
||||
return nil, SSLError.New("failed dumping certificate")
|
||||
}
|
||||
return ioutil.ReadAll(asAnyBio(bio))
|
||||
}
|
||||
|
||||
func (c *Certificate) PublicKey() (PublicKey, error) {
|
||||
pkey := C.X509_get_pubkey(c.x)
|
||||
if pkey == nil {
|
||||
return nil, SSLError.New("no public key found")
|
||||
}
|
||||
key := &pKey{key: pkey}
|
||||
runtime.SetFinalizer(key, func(key *pKey) {
|
||||
C.EVP_PKEY_free(key.key)
|
||||
})
|
||||
return key, nil
|
||||
}
|
||||
|
||||
type KeyPair struct {
|
||||
Certificate *Certificate
|
||||
PrivateKey PrivateKey
|
||||
}
|
||||
|
||||
func X509KeyPair(key PrivateKey, cert *Certificate) (tls.Certificate, error) {
|
||||
key_pem_bytes, err := key.MarshalPKCS1PrivateKeyPEM()
|
||||
if err != nil {
|
||||
return tls.Certificate{}, err
|
||||
}
|
||||
cert_pem_bytes, err := cert.MarshalPEM()
|
||||
if err != nil {
|
||||
return tls.Certificate{}, err
|
||||
}
|
||||
return tls.X509KeyPair(cert_pem_bytes, key_pem_bytes)
|
||||
}
|
521
ssl_test.go
Normal file
521
ssl_test.go
Normal file
@ -0,0 +1,521 @@
|
||||
// Copyright (C) 2014 Space Monkey, Inc.
|
||||
|
||||
package openssl
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"code.spacemonkey.com/go/errors"
|
||||
space_sync "code.spacemonkey.com/go/space/sync"
|
||||
)
|
||||
|
||||
var (
|
||||
certBytes = []byte(`
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDxDCCAqygAwIBAgIVAMcK/0VWQr2O3MNfJCydqR7oVELcMA0GCSqGSIb3DQEB
|
||||
BQUAMIGQMUkwRwYDVQQDE0A1NjdjZGRmYzRjOWZiNTYwZTk1M2ZlZjA1N2M0NGFm
|
||||
MDdiYjc4MDIzODIxYTA5NThiY2RmMGMwNzJhOTdiMThhMQswCQYDVQQGEwJVUzEN
|
||||
MAsGA1UECBMEVXRhaDEQMA4GA1UEBxMHTWlkdmFsZTEVMBMGA1UEChMMU3BhY2Ug
|
||||
TW9ua2V5MB4XDTEzMTIxNzE4MzgyMloXDTIzMTIxNTE4MzgyMlowgZAxSTBHBgNV
|
||||
BAMTQDM4NTg3ODRkMjU1NTdiNTM1MWZmNjRmMmQzMTQ1ZjkwYTJlMTIzMDM4Y2Yz
|
||||
Mjc1Yzg1OTM1MjcxYWIzMmNiMDkxCzAJBgNVBAYTAlVTMQ0wCwYDVQQIEwRVdGFo
|
||||
MRAwDgYDVQQHEwdNaWR2YWxlMRUwEwYDVQQKEwxTcGFjZSBNb25rZXkwggEiMA0G
|
||||
CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDdf3icNvFsrlrnNLi8SocscqlSbFq+
|
||||
pEvmhcSoqgDLqebnqu8Ld73HJJ74MGXEgRX8xZT5FinOML31CR6t9E/j3dqV6p+G
|
||||
fdlFLe3IqtC0/bPVnCDBirBygBI4uCrMq+1VhAxPWclrDo7l9QRYbsExH9lfn+Ry
|
||||
vxeNMZiOASasvVZNncY8E9usBGRdH17EfDL/TPwXqWOLyxSN5o54GTztjjy9w9CG
|
||||
QP7jcCueKYyQJQCtEmnwc6P/q6/EPv5R6drBkX6loAPtmCUAkHqxkWOJrRq/v7Pw
|
||||
zRYhfY+ZpVHGc7WEkDnLzRiUypr1C9oxvLKS10etZEIwEdKyOkSg2fdPAgMBAAGj
|
||||
EzARMA8GA1UdEwEB/wQFMAMCAQAwDQYJKoZIhvcNAQEFBQADggEBAEcz0RTTJ99l
|
||||
HTK/zTyfV5VZEhtwqu6bwre/hD7lhI+1ji0DZYGIgCbJLKuZhj+cHn2h5nPhN7zE
|
||||
M9tc4pn0TgeVS0SVFSe6TGnIFipNogvP17E+vXpDZcW/xn9kPKeVCZc1hlDt1W4Z
|
||||
5q+ub3aUwuMwYs7bcArtDrumCmciJ3LFyNhebPi4mntb5ooeLFLaujEmVYyrQnpo
|
||||
tWKC9sMlJmLm4yAso64Sv9KLS2T9ivJBNn0ZtougozBCCTqrqgZVjha+B2yjHe9f
|
||||
sRkg/uxcJf7wC5Y0BLlp1+aPwdmZD87T3a1uQ1Ij93jmHG+2T9U20MklHAePOl0q
|
||||
yTqdSPnSH1c=
|
||||
-----END CERTIFICATE-----
|
||||
`)
|
||||
keyBytes = []byte(`
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpQIBAAKCAQEA3X94nDbxbK5a5zS4vEqHLHKpUmxavqRL5oXEqKoAy6nm56rv
|
||||
C3e9xySe+DBlxIEV/MWU+RYpzjC99QkerfRP493aleqfhn3ZRS3tyKrQtP2z1Zwg
|
||||
wYqwcoASOLgqzKvtVYQMT1nJaw6O5fUEWG7BMR/ZX5/kcr8XjTGYjgEmrL1WTZ3G
|
||||
PBPbrARkXR9exHwy/0z8F6lji8sUjeaOeBk87Y48vcPQhkD+43ArnimMkCUArRJp
|
||||
8HOj/6uvxD7+UenawZF+paAD7ZglAJB6sZFjia0av7+z8M0WIX2PmaVRxnO1hJA5
|
||||
y80YlMqa9QvaMbyyktdHrWRCMBHSsjpEoNn3TwIDAQABAoIBAQCwgp6YzmgCFce3
|
||||
LBpzYmjqEM3CMzr1ZXRe1gbr6d4Mbu7leyBX4SpJAnP0kIzo1X2yG7ol7XWPLOST
|
||||
2pqqQWFQ00EX6wsJYEy+hmVRXl5HfU3MUkkAMwd9l3Xt4UWqKPBPD5XHvmN2fvl9
|
||||
Y4388vXdseXGAGNK1eFs0TMjJuOtDxDyrmJcnxpJ7y/77y/Hb5rUa9DCvj8tkKHg
|
||||
HmeIwQE0HhIFofj+qCYbqeVyjbPAaYZMrISXb2HmcyULKEOGRbMH24IzInKA0NxV
|
||||
kdP9qmV8Y2bJ609Fft/y8Vpj31iEdq/OFXyobdVvnXMnaVyAetoaWy7AOTIQ2Cnw
|
||||
wGbJ/F8BAoGBAN/pCnLQrWREeVMuFjf+MgYgCtRRaQ8EOVvjYcXXi0PhtOMFTAb7
|
||||
djqhlgmBOFsmeXcb8YRZsF+pNtu1xk5RJOquyKfK8j1rUdAJfoxGHiaUFI2/1i9E
|
||||
zuXX/Ao0xNRkWMxMKuwYBmmt1fMuVo+1M8UEwFMdHRtgxe+/+eOV1J2PAoGBAP09
|
||||
7GLOYSYAI1OO3BN/bEVNau6tAxP5YShGmX2Qxy0+ooxHZ1V3D8yo6C0hSg+H+fPT
|
||||
mjMgGcvaW6K+QyCdHDjgbk2hfdZ+Beq92JApPrH9gMV7MPhwHzgwjzDDio9OFxYY
|
||||
3vjBQ2yX+9jvz9lkvq2NM3fqFqbsG6Et+5mCc6pBAoGBAI62bxVtEgbladrtdfXs
|
||||
S6ABzkUzOl362EBL9iZuUnJKqstDtgiBQALwuLuIJA5cwHB9W/t6WuMt7CwveJy0
|
||||
NW5rRrNDtBAXlgad9o2bp135ZfxO+EoadjCi8B7lMUsaRkq4hWcDjRrQVJxxvXRN
|
||||
DxkVBSw0Uzf+/0nnN3OqLODbAoGACCY+/isAC1YDzQOS53m5RT2pjEa7C6CB1Ob4
|
||||
t4a6MiWK25LMq35qXr6swg8JMBjDHWqY0r5ctievvTv8Mwd7SgVG526j+wwRKq2z
|
||||
U2hQYS/0Peap+8S37Hn7kakpQ1VS/t4MBttJTSxS6XdGLAvG6xTZLCm3UuXUOcqe
|
||||
ByGgkUECgYEAmop45kRi974g4MPvyLplcE4syb19ifrHj76gPRBi94Cp8jZosY1T
|
||||
ucCCa4lOGgPtXJ0Qf1c8yq5vh4yqkQjrgUTkr+CFDGR6y4CxmNDQxEMYIajaIiSY
|
||||
qmgvgyRayemfO2zR0CPgC6wSoGBth+xW6g+WA8y0z76ZSaWpFi8lVM4=
|
||||
-----END RSA PRIVATE KEY-----
|
||||
`)
|
||||
)
|
||||
|
||||
func NetPipe(t testing.TB) (net.Conn, net.Conn) {
|
||||
l, err := net.Listen("tcp", ":0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer l.Close()
|
||||
client_future := space_sync.NewFuture()
|
||||
go func() {
|
||||
client_future.Set(net.Dial(l.Addr().Network(), l.Addr().String()))
|
||||
}()
|
||||
errs := errors.NewErrorGroup()
|
||||
server_conn, err := l.Accept()
|
||||
errs.Add(err)
|
||||
client_conn, err := client_future.Get()
|
||||
errs.Add(err)
|
||||
err = errs.Finalize()
|
||||
if err != nil {
|
||||
if server_conn != nil {
|
||||
server_conn.Close()
|
||||
}
|
||||
if client_conn != nil {
|
||||
client_conn.(net.Conn).Close()
|
||||
}
|
||||
t.Fatal(err)
|
||||
}
|
||||
return server_conn, client_conn.(net.Conn)
|
||||
}
|
||||
|
||||
type HandshakingConn interface {
|
||||
net.Conn
|
||||
Handshake() error
|
||||
}
|
||||
|
||||
func SimpleConnTest(t testing.TB, constructor func(
|
||||
t testing.TB, conn1, conn2 net.Conn) (sslconn1, sslconn2 HandshakingConn)) {
|
||||
server_conn, client_conn := NetPipe(t)
|
||||
defer server_conn.Close()
|
||||
defer client_conn.Close()
|
||||
|
||||
data := "first test string\n"
|
||||
|
||||
server, client := constructor(t, server_conn, client_conn)
|
||||
defer close_both(server, client)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
err := client.Handshake()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = io.Copy(client, bytes.NewReader([]byte(data)))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = client.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
err := server.Handshake()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(make([]byte, 0, len(data)))
|
||||
_, err = io.CopyN(buf, server, int64(len(data)))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(buf.Bytes()) != data {
|
||||
t.Fatal("mismatched data")
|
||||
}
|
||||
|
||||
err = server.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func close_both(closer1, closer2 io.Closer) {
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
closer1.Close()
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
closer2.Close()
|
||||
}()
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func ClosingTest(t testing.TB, constructor func(
|
||||
t testing.TB, conn1, conn2 net.Conn) (sslconn1, sslconn2 HandshakingConn)) {
|
||||
|
||||
run_test := func(close_tcp bool, server_writes bool) {
|
||||
server_conn, client_conn := NetPipe(t)
|
||||
defer server_conn.Close()
|
||||
defer client_conn.Close()
|
||||
server, client := constructor(t, server_conn, client_conn)
|
||||
defer close_both(server, client)
|
||||
|
||||
var sslconn1, sslconn2 HandshakingConn
|
||||
var conn1 net.Conn
|
||||
if server_writes {
|
||||
sslconn1 = server
|
||||
conn1 = server_conn
|
||||
sslconn2 = client
|
||||
} else {
|
||||
sslconn1 = client
|
||||
conn1 = client_conn
|
||||
sslconn2 = server
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
_, err := sslconn1.Write([]byte("hello"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if close_tcp {
|
||||
err = conn1.Close()
|
||||
} else {
|
||||
err = sslconn1.Close()
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
data, err := ioutil.ReadAll(sslconn2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !bytes.Equal(data, []byte("hello")) {
|
||||
t.Fatal("bytes don't match")
|
||||
}
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
run_test(true, false)
|
||||
run_test(false, false)
|
||||
run_test(true, true)
|
||||
run_test(false, true)
|
||||
}
|
||||
|
||||
func ThroughputBenchmark(b *testing.B, constructor func(
|
||||
t testing.TB, conn1, conn2 net.Conn) (sslconn1, sslconn2 HandshakingConn)) {
|
||||
server_conn, client_conn := NetPipe(b)
|
||||
defer server_conn.Close()
|
||||
defer client_conn.Close()
|
||||
|
||||
server, client := constructor(b, server_conn, client_conn)
|
||||
defer close_both(server, client)
|
||||
|
||||
b.SetBytes(1024)
|
||||
data := make([]byte, b.N*1024)
|
||||
_, err := io.ReadFull(rand.Reader, data[:])
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
_, err = io.Copy(client, bytes.NewReader([]byte(data)))
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
_, err = io.CopyN(buf, server, int64(len(data)))
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
if !bytes.Equal(buf.Bytes(), data) {
|
||||
b.Fatal("mismatched data")
|
||||
}
|
||||
}()
|
||||
wg.Wait()
|
||||
b.StopTimer()
|
||||
}
|
||||
|
||||
func StdlibConstructor(t testing.TB, server_conn, client_conn net.Conn) (
|
||||
server, client HandshakingConn) {
|
||||
cert, err := tls.X509KeyPair(certBytes, keyBytes)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
config := &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
InsecureSkipVerify: true}
|
||||
server = tls.Server(server_conn, config)
|
||||
client = tls.Client(client_conn, config)
|
||||
return server, client
|
||||
}
|
||||
|
||||
func OpenSSLConstructor(t testing.TB, server_conn, client_conn net.Conn) (
|
||||
server, client HandshakingConn) {
|
||||
ctx, err := NewCtx()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
key, err := LoadPrivateKey(keyBytes)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = ctx.UsePrivateKey(key)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cert, err := LoadCertificate(certBytes)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = ctx.UseCertificate(cert)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
server, err = Server(server_conn, ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
client, err = Client(client_conn, ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return server, client
|
||||
}
|
||||
|
||||
func StdlibOpenSSLConstructor(t testing.TB, server_conn, client_conn net.Conn) (
|
||||
server, client HandshakingConn) {
|
||||
server_std, _ := StdlibConstructor(t, server_conn, client_conn)
|
||||
_, client_ssl := OpenSSLConstructor(t, server_conn, client_conn)
|
||||
return server_std, client_ssl
|
||||
}
|
||||
|
||||
func OpenSSLStdlibConstructor(t testing.TB, server_conn, client_conn net.Conn) (
|
||||
server, client HandshakingConn) {
|
||||
_, client_std := StdlibConstructor(t, server_conn, client_conn)
|
||||
server_ssl, _ := OpenSSLConstructor(t, server_conn, client_conn)
|
||||
return server_ssl, client_std
|
||||
}
|
||||
|
||||
func TestStdlibSimple(t *testing.T) {
|
||||
SimpleConnTest(t, StdlibConstructor)
|
||||
}
|
||||
|
||||
func TestOpenSSLSimple(t *testing.T) {
|
||||
SimpleConnTest(t, OpenSSLConstructor)
|
||||
}
|
||||
|
||||
func TestStdlibClosing(t *testing.T) {
|
||||
ClosingTest(t, StdlibConstructor)
|
||||
}
|
||||
|
||||
func TestOpenSSLClosing(t *testing.T) {
|
||||
ClosingTest(t, OpenSSLConstructor)
|
||||
}
|
||||
|
||||
func BenchmarkStdlibThroughput(b *testing.B) {
|
||||
ThroughputBenchmark(b, StdlibConstructor)
|
||||
}
|
||||
|
||||
func BenchmarkOpenSSLThroughput(b *testing.B) {
|
||||
ThroughputBenchmark(b, OpenSSLConstructor)
|
||||
}
|
||||
|
||||
func TestStdlibOpenSSLSimple(t *testing.T) {
|
||||
SimpleConnTest(t, StdlibOpenSSLConstructor)
|
||||
}
|
||||
|
||||
func TestOpenSSLStdlibSimple(t *testing.T) {
|
||||
SimpleConnTest(t, OpenSSLStdlibConstructor)
|
||||
}
|
||||
|
||||
func TestStdlibOpenSSLClosing(t *testing.T) {
|
||||
ClosingTest(t, StdlibOpenSSLConstructor)
|
||||
}
|
||||
|
||||
func TestOpenSSLStdlibClosing(t *testing.T) {
|
||||
ClosingTest(t, OpenSSLStdlibConstructor)
|
||||
}
|
||||
|
||||
func BenchmarkStdlibOpenSSLThroughput(b *testing.B) {
|
||||
ThroughputBenchmark(b, StdlibOpenSSLConstructor)
|
||||
}
|
||||
|
||||
func BenchmarkOpenSSLStdlibThroughput(b *testing.B) {
|
||||
ThroughputBenchmark(b, OpenSSLStdlibConstructor)
|
||||
}
|
||||
|
||||
func LotsOfConns(t *testing.T, payload_size int64, loops, clients int,
|
||||
sleep time.Duration, newListener func(net.Listener) net.Listener,
|
||||
newClient func(net.Conn) (net.Conn, error)) {
|
||||
tcp_listener, err := net.Listen("tcp", ":0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ssl_listener := newListener(tcp_listener)
|
||||
go func() {
|
||||
for {
|
||||
conn, err := ssl_listener.Accept()
|
||||
if err != nil {
|
||||
t.Fatalf("failed accept: %s", err)
|
||||
continue
|
||||
}
|
||||
go func() {
|
||||
defer func() {
|
||||
err = conn.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("failed closing: %s", err)
|
||||
}
|
||||
}()
|
||||
for i := 0; i < loops; i++ {
|
||||
_, err := io.Copy(ioutil.Discard,
|
||||
io.LimitReader(conn, payload_size))
|
||||
if err != nil {
|
||||
t.Fatalf("failed reading: %s", err)
|
||||
return
|
||||
}
|
||||
_, err = io.Copy(conn, io.LimitReader(rand.Reader,
|
||||
payload_size))
|
||||
if err != nil {
|
||||
t.Fatalf("failed writing: %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
time.Sleep(sleep)
|
||||
}()
|
||||
}
|
||||
}()
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < clients; i++ {
|
||||
tcp_client, err := net.Dial(tcp_listener.Addr().Network(),
|
||||
tcp_listener.Addr().String())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ssl_client, err := newClient(tcp_client)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer func() {
|
||||
err = ssl_client.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("failed closing: %s", err)
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
for i := 0; i < loops; i++ {
|
||||
_, err := io.Copy(ssl_client, io.LimitReader(rand.Reader,
|
||||
payload_size))
|
||||
if err != nil {
|
||||
t.Fatalf("failed writing: %s", err)
|
||||
return
|
||||
}
|
||||
_, err = io.Copy(ioutil.Discard,
|
||||
io.LimitReader(ssl_client, payload_size))
|
||||
if err != nil {
|
||||
t.Fatalf("failed reading: %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
time.Sleep(sleep)
|
||||
}(i)
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestStdlibLotsOfConns(t *testing.T) {
|
||||
tls_cert, err := tls.X509KeyPair(certBytes, keyBytes)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tls_config := &tls.Config{
|
||||
Certificates: []tls.Certificate{tls_cert},
|
||||
InsecureSkipVerify: true}
|
||||
LotsOfConns(t, 1024*64, 10, 100, 0*time.Second,
|
||||
func(l net.Listener) net.Listener {
|
||||
return tls.NewListener(l, tls_config)
|
||||
}, func(c net.Conn) (net.Conn, error) {
|
||||
return tls.Client(c, tls_config), nil
|
||||
})
|
||||
}
|
||||
|
||||
func TestOpenSSLLotsOfConns(t *testing.T) {
|
||||
ctx, err := NewCtx()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
key, err := LoadPrivateKey(keyBytes)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = ctx.UsePrivateKey(key)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cert, err := LoadCertificate(certBytes)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = ctx.UseCertificate(cert)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ctx.SetOptions(NoCompression)
|
||||
ctx.SetMode(ReleaseBuffers)
|
||||
ctx.SetSessionCacheMode(Off)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
LotsOfConns(t, 1024*64, 10, 100, 0*time.Second,
|
||||
func(l net.Listener) net.Listener {
|
||||
return NewListener(l, ctx)
|
||||
}, func(c net.Conn) (net.Conn, error) {
|
||||
return Client(c, ctx)
|
||||
})
|
||||
}
|
8
thread_id/thread_id.c
Normal file
8
thread_id/thread_id.c
Normal file
@ -0,0 +1,8 @@
|
||||
// Copyright (C) 2014 Space Monkey, Inc.
|
||||
|
||||
#include "runtime.h"
|
||||
|
||||
void ·Id(void *ref) {
|
||||
ref = (void *)m;
|
||||
FLUSH(&ref);
|
||||
}
|
9
thread_id/thread_id.go
Normal file
9
thread_id/thread_id.go
Normal file
@ -0,0 +1,9 @@
|
||||
// Copyright (C) 2014 Space Monkey, Inc.
|
||||
|
||||
package thread_id
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func Id() unsafe.Pointer
|
Loading…
Reference in New Issue
Block a user