Add an initial regress test for libressl, which calls ressl from Go and
authorjsing <jsing@openbsd.org>
Sat, 12 Jul 2014 16:01:28 +0000 (16:01 +0000)
committerjsing <jsing@openbsd.org>
Sat, 12 Jul 2014 16:01:28 +0000 (16:01 +0000)
makes it talk to a Go TLS server.

regress/lib/libressl/Makefile [new file with mode: 0644]
regress/lib/libressl/goressl/Makefile [new file with mode: 0644]
regress/lib/libressl/goressl/ressl.go [new file with mode: 0644]
regress/lib/libressl/goressl/ressl_test.go [new file with mode: 0644]

diff --git a/regress/lib/libressl/Makefile b/regress/lib/libressl/Makefile
new file mode 100644 (file)
index 0000000..563753a
--- /dev/null
@@ -0,0 +1,8 @@
+#      $OpenBSD: Makefile,v 1.1 2014/07/12 16:01:28 jsing Exp $
+
+SUBDIR= \
+       goressl
+
+install:
+
+.include <bsd.subdir.mk>
diff --git a/regress/lib/libressl/goressl/Makefile b/regress/lib/libressl/goressl/Makefile
new file mode 100644 (file)
index 0000000..d938db3
--- /dev/null
@@ -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 <bsd.regress.mk>
diff --git a/regress/lib/libressl/goressl/ressl.go b/regress/lib/libressl/goressl/ressl.go
new file mode 100644 (file)
index 0000000..b27a193
--- /dev/null
@@ -0,0 +1,157 @@
+// Package ressl provides a Go interface to the libressl library.
+package ressl
+
+/*
+#cgo LDFLAGS: -lressl -lssl -lcrypto
+
+#include <stdlib.h>
+
+#include <ressl/ressl.h>
+
+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 (file)
index 0000000..665d4e0
--- /dev/null
@@ -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)
+       }
+}