From 3dca1d688d6f1072e130a80c75a728c083acebfc Mon Sep 17 00:00:00 2001 From: jsing Date: Sat, 12 Jul 2014 16:01:28 +0000 Subject: [PATCH] Add an initial regress test for libressl, which calls ressl from Go and makes it talk to a Go TLS server. --- regress/lib/libressl/Makefile | 8 ++ regress/lib/libressl/goressl/Makefile | 15 ++ regress/lib/libressl/goressl/ressl.go | 157 +++++++++++++++++++++ regress/lib/libressl/goressl/ressl_test.go | 100 +++++++++++++ 4 files changed, 280 insertions(+) create mode 100644 regress/lib/libressl/Makefile create mode 100644 regress/lib/libressl/goressl/Makefile create mode 100644 regress/lib/libressl/goressl/ressl.go create mode 100644 regress/lib/libressl/goressl/ressl_test.go diff --git a/regress/lib/libressl/Makefile b/regress/lib/libressl/Makefile new file mode 100644 index 00000000000..563753a1f07 --- /dev/null +++ b/regress/lib/libressl/Makefile @@ -0,0 +1,8 @@ +# $OpenBSD: Makefile,v 1.1 2014/07/12 16:01:28 jsing Exp $ + +SUBDIR= \ + goressl + +install: + +.include diff --git a/regress/lib/libressl/goressl/Makefile b/regress/lib/libressl/goressl/Makefile new file mode 100644 index 00000000000..d938db3f370 --- /dev/null +++ b/regress/lib/libressl/goressl/Makefile @@ -0,0 +1,15 @@ +# $OpenBSD: Makefile,v 1.1 2014/07/12 16:01:28 jsing Exp $ + +GO_VERSION != sh -c "(go version) 2>/dev/null || true" + +.if empty(GO_VERSION) +regress: + @echo golang is required for this regress... skipping +.endif + +REGRESS_TARGETS=regress-goressl + +regress-goressl: + cd ${.CURDIR} && go test -test.v . + +.include diff --git a/regress/lib/libressl/goressl/ressl.go b/regress/lib/libressl/goressl/ressl.go new file mode 100644 index 00000000000..b27a193814b --- /dev/null +++ b/regress/lib/libressl/goressl/ressl.go @@ -0,0 +1,157 @@ +// Package ressl provides a Go interface to the libressl library. +package ressl + +/* +#cgo LDFLAGS: -lressl -lssl -lcrypto + +#include + +#include + +typedef void *ressl; +*/ +import "C" + +import ( + "errors" + "fmt" + "unsafe" +) + +// ResslConfig provides configuration options for a Ressl context. +type ResslConfig struct { + caFile *C.char + resslCfg *C.struct_ressl_config +} + +// Ressl encapsulates the context for ressl. +type Ressl struct { + cfg *ResslConfig + ctx *C.struct_ressl +} + +// Init initialises the ressl library. +func Init() error { + if C.ressl_init() != 0 { + return errors.New("initialisation failed") + } + return nil +} + +// NewConfig returns a new ressl configuration. +func NewConfig() (*ResslConfig, error) { + cfg := C.ressl_config_new() + if cfg == nil { + return nil, errors.New("failed to allocate config") + } + return &ResslConfig{ + resslCfg: cfg, + }, nil +} + +// SetCAFile sets the CA file to be used for connections. +func (c *ResslConfig) SetCAFile(filename string) { + if c.caFile != nil { + C.free(unsafe.Pointer(c.caFile)) + } + c.caFile = C.CString(filename) + C.ressl_config_ca_file(c.resslCfg, c.caFile) +} + +// SetInsecure disables verification for the connection. +func (c *ResslConfig) SetInsecure() { + C.ressl_config_insecure(c.resslCfg) +} + +// SetSecure enables verification for the connection. +func (c *ResslConfig) SetSecure() { + C.ressl_config_secure(c.resslCfg) +} + +// Free frees resources associated with the ressl configuration. +func (c *ResslConfig) Free() { + if c.resslCfg == nil { + return + } + C.ressl_config_free(c.resslCfg) + c.resslCfg = nil +} + +// New returns a new ressl context, using the optional configuration. If no +// configuration is specified the defaults will be used. +func New(config *ResslConfig) (*Ressl, error) { + var sslCfg *C.struct_ressl_config + if config != nil { + sslCfg = config.resslCfg + } + ctx := C.ressl_new(sslCfg) + if ctx == nil { + return nil, errors.New("ressl new failed") + } + return &Ressl{ + cfg: config, + ctx: ctx, + }, nil +} + +// Error returns the error message from the ressl context. +func (r *Ressl) Error() string { + if msg := C.ressl_error(r.ctx); msg != nil { + return C.GoString(msg) + } + return "" +} + +// Connect attempts to establish an SSL connection to the specified host on +// the given port. The host may optionally contain a colon separated port +// value if the port string is specified as an empty string. +func (r *Ressl) Connect(host, port string) error { + h := C.CString(host) + var p *C.char + if port != "" { + p = C.CString(port) + } + defer C.free(unsafe.Pointer(h)) + defer C.free(unsafe.Pointer(p)) + if C.ressl_connect(r.ctx, h, p) != 0 { + return fmt.Errorf("connect failed: %v", r.Error()) + } + return nil +} + +// Read reads data the SSL connection into the given buffer. +func (r *Ressl) Read(buf []byte) (int, error) { + var inlen C.size_t + if C.ressl_read(r.ctx, (*C.char)(unsafe.Pointer(&buf[0])), C.size_t(len(buf)), (*C.size_t)(unsafe.Pointer(&inlen))) != 0 { + return -1, fmt.Errorf("read failed: %v", r.Error()) + } + return int(inlen), nil +} + +// Write writes the given data to the SSL connection. +func (r *Ressl) Write(buf []byte) (int, error) { + var outlen C.size_t + p := C.CString(string(buf)) + defer C.free(unsafe.Pointer(p)) + if C.ressl_write(r.ctx, p, C.size_t(len(buf)), (*C.size_t)(unsafe.Pointer(&outlen))) != 0 { + return -1, fmt.Errorf("write failed: %v", r.Error()) + } + return int(outlen), nil +} + +// Close closes the SSL connection. +func (r *Ressl) Close() error { + if C.ressl_close(r.ctx) != 0 { + return fmt.Errorf("close failed: %v", r.Error()) + } + return nil +} + +// Free frees resources associated with the ressl context. +func (r *Ressl) Free() { + if r.ctx == nil { + return + } + C.ressl_free(r.ctx) + r.ctx = nil +} diff --git a/regress/lib/libressl/goressl/ressl_test.go b/regress/lib/libressl/goressl/ressl_test.go new file mode 100644 index 00000000000..665d4e0cefb --- /dev/null +++ b/regress/lib/libressl/goressl/ressl_test.go @@ -0,0 +1,100 @@ +package ressl + +import ( + "encoding/pem" + "fmt" + "io/ioutil" + "net/http" + "net/http/httptest" + "net/url" + "os" + "strings" + "testing" +) + +// createCAFile writes a PEM encoded version of the certificate out to a +// temporary file, for use by ressl. +func createCAFile(cert []byte) (string, error) { + f, err := ioutil.TempFile("", "ressl") + if err != nil { + return "", fmt.Errorf("failed to create file: %v", err) + } + defer f.Close() + block := &pem.Block{ + Type: "CERTIFICATE", + Bytes: cert, + } + if err := pem.Encode(f, block); err != nil { + return "", fmt.Errorf("failed to encode certificate: %v", err) + } + return f.Name(), nil +} + +const httpContent = "Hello, ressl!" + +func TestResslBasic(t *testing.T) { + ts := httptest.NewTLSServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w, httpContent) + }, + ), + ) + defer ts.Close() + + u, err := url.Parse(ts.URL) + if err != nil { + t.Fatalf("Failed to parse URL %q: %v", ts.URL, err) + } + + caFile, err := createCAFile(ts.TLS.Certificates[0].Certificate[0]) + if err != nil { + t.Fatalf("Failed to create CA file: %v", err) + } + defer os.Remove(caFile) + + if err := Init(); err != nil { + t.Fatal(err) + } + + cfg, err := NewConfig() + if err != nil { + t.Fatal(err) + } + defer cfg.Free() + cfg.SetCAFile(caFile) + + ssl, err := New(cfg) + if err != nil { + t.Fatal(err) + } + defer ssl.Free() + + t.Logf("Connecting to %s", u.Host) + + if err := ssl.Connect(u.Host, ""); err != nil { + t.Fatal(err) + } + defer ssl.Close() + + n, err := ssl.Write([]byte("GET / HTTP/1.0\n\n")) + if err != nil { + t.Fatal(err) + } + t.Logf("Wrote %d bytes...", n) + + buf := make([]byte, 1024) + n, err = ssl.Read(buf) + if err != nil { + t.Fatal(err) + } + t.Logf("Read %d bytes...", n) + + if !strings.Contains(string(buf), httpContent) { + t.Errorf("Response does not contain %q", httpContent) + } + + if err := ssl.Close(); err != nil { + t.Fatal(err) + } +} -- 2.20.1