From ec6cd1c6115089972c1106c4ae09de316a85f28d Mon Sep 17 00:00:00 2001 From: jb0n Date: Wed, 30 Sep 2015 11:30:55 -0700 Subject: [PATCH 01/13] hacking in support for SNI callback. It's messy, needs cleanup, need to get some types changed from raw C types back to go... but it's working --- ctx.go | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/ctx.go b/ctx.go index 7db505ec..86202725 100644 --- a/ctx.go +++ b/ctx.go @@ -70,6 +70,29 @@ static long SSL_CTX_set_tmp_ecdh_not_a_macro(SSL_CTX* ctx, EC_KEY *key) { return SSL_CTX_set_tmp_ecdh(ctx, key); } +static long SSL_CTX_set_tlsext_servername_callback_not_a_macro(SSL_CTX* ctx, void (*fp)()) { + return SSL_CTX_set_tlsext_servername_callback(ctx, fp); +} + +typedef struct CtxWrapper { + void *go_ctx; + SSL_CTX *ctx; +} CtxWrapper; + +extern int call_servername_cb(SSL* ssl, int ad, void* arg); + +static int call_go_servername(SSL* ssl, int ad, void* arg) { + return call_servername_cb(ssl, ad, arg); +} + +static int servername_gateway(CtxWrapper* cw) { + SSL_CTX* ctx = cw->ctx; + //TODO: figure out what to do with return codes. The first isn't 0 + SSL_CTX_set_tlsext_servername_callback(ctx, call_go_servername); + SSL_CTX_set_tlsext_servername_arg(ctx, cw->go_ctx); + return 0; +} + #ifndef SSL_MODE_RELEASE_BUFFERS #define SSL_MODE_RELEASE_BUFFERS 0 #endif @@ -122,6 +145,8 @@ type Ctx struct { chain []*Certificate key PrivateKey verify_cb VerifyCallback + //servername_cb ServerNameCallback + servername_cb func(ssl unsafe.Pointer, ad int, arg unsafe.Pointer) int } //export get_ssl_ctx_idx @@ -605,3 +630,24 @@ func (c *Ctx) SessSetCacheSize(t int) int { func (c *Ctx) SessGetCacheSize() int { return int(C.SSL_CTX_sess_get_cache_size_not_a_macro(c.ctx)) } + +// Set SSL_CTX_set_tlsext_servername_callback +// https://www.openssl.org/docs/manmaster/ssl/??? +//type ServerNameCallback func(ssl *C.SSL, ad C.int, arg unsafe.Pointer) int + +//export call_servername_cb +func call_servername_cb(ssl *C.SSL, ad C.int, arg unsafe.Pointer) C.int { + var c *Ctx = (*Ctx)(arg) + ret := c.servername_cb(unsafe.Pointer(ssl), int(ad), arg) + return C.int(ret) +} + +//func (c *Ctx) SetTlsExtServerNameCallback(cb ServerNameCallback) int { +func (c *Ctx) SetTlsExtServerNameCallback(cb func(ssl unsafe.Pointer, ad int, arg unsafe.Pointer) int) int { + c.servername_cb = cb + cw := C.CtxWrapper{ + go_ctx: unsafe.Pointer(c), + ctx: c.ctx, + } + return int(C.servername_gateway(&cw)) +} From b00fc5712c49ce911077abd4a3a7a84dce48aa93 Mon Sep 17 00:00:00 2001 From: jb0n Date: Wed, 30 Sep 2015 12:06:44 -0700 Subject: [PATCH 02/13] pass (haf formed) openssl.Conn to user callback. Expose GetServerName to openssl.Conn --- conn.go | 4 ++++ ctx.go | 13 ++++++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/conn.go b/conn.go index 9837ce3a..5cb98308 100644 --- a/conn.go +++ b/conn.go @@ -566,3 +566,7 @@ func (c *Conn) SetTlsExtHostName(name string) error { func (c *Conn) VerifyResult() VerifyResult { return VerifyResult(C.SSL_get_verify_result(c.ssl)) } + +func (c *Conn) GetServerName() string { + return C.GoString(C.SSL_get_servername(c.ssl, C.TLSEXT_NAMETYPE_host_name)) +} diff --git a/ctx.go b/ctx.go index 86202725..69a8e281 100644 --- a/ctx.go +++ b/ctx.go @@ -146,7 +146,7 @@ type Ctx struct { key PrivateKey verify_cb VerifyCallback //servername_cb ServerNameCallback - servername_cb func(ssl unsafe.Pointer, ad int, arg unsafe.Pointer) int + servername_cb func(ssl Conn, ad int, arg unsafe.Pointer) int } //export get_ssl_ctx_idx @@ -638,12 +638,19 @@ func (c *Ctx) SessGetCacheSize() int { //export call_servername_cb func call_servername_cb(ssl *C.SSL, ad C.int, arg unsafe.Pointer) C.int { var c *Ctx = (*Ctx)(arg) - ret := c.servername_cb(unsafe.Pointer(ssl), int(ad), arg) + + //setup a dummy Conn so we can associate a SSL_CTX from user callback + conn := Conn{ + ssl: ssl, + ctx: c, + } + + ret := c.servername_cb(conn, int(ad), arg) return C.int(ret) } //func (c *Ctx) SetTlsExtServerNameCallback(cb ServerNameCallback) int { -func (c *Ctx) SetTlsExtServerNameCallback(cb func(ssl unsafe.Pointer, ad int, arg unsafe.Pointer) int) int { +func (c *Ctx) SetTlsExtServerNameCallback(cb func(ssl Conn, ad int, arg unsafe.Pointer) int) int { c.servername_cb = cb cw := C.CtxWrapper{ go_ctx: unsafe.Pointer(c), From b45bbf1553f5be0fca641a1a77591fd66dbbb7a0 Mon Sep 17 00:00:00 2001 From: jb0n Date: Wed, 30 Sep 2015 12:30:05 -0700 Subject: [PATCH 03/13] flip the ctx (no idea if this works yet) --- conn.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/conn.go b/conn.go index 5cb98308..07ccb7bf 100644 --- a/conn.go +++ b/conn.go @@ -548,6 +548,11 @@ func (c *Conn) SetWriteDeadline(t time.Time) error { return c.conn.SetWriteDeadline(t) } +func (c *Conn) SetCtx(ctx *Ctx) { + c.ctx = ctx + C.SSL_set_SSL_CTX(c.ssl, ctx.ctx) +} + func (c *Conn) UnderlyingConn() net.Conn { return c.conn } From f756cf3e9a56dd7816757fc3402d19322b95f41e Mon Sep 17 00:00:00 2001 From: jb0n Date: Wed, 30 Sep 2015 15:28:53 -0700 Subject: [PATCH 04/13] use an idiomatic go name for callback function --- ctx.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ctx.go b/ctx.go index 69a8e281..72da005f 100644 --- a/ctx.go +++ b/ctx.go @@ -79,10 +79,10 @@ typedef struct CtxWrapper { SSL_CTX *ctx; } CtxWrapper; -extern int call_servername_cb(SSL* ssl, int ad, void* arg); +extern int callServerNameCb(SSL* ssl, int ad, void* arg); static int call_go_servername(SSL* ssl, int ad, void* arg) { - return call_servername_cb(ssl, ad, arg); + return callServerNameCb(ssl, ad, arg); } static int servername_gateway(CtxWrapper* cw) { @@ -635,8 +635,8 @@ func (c *Ctx) SessGetCacheSize() int { // https://www.openssl.org/docs/manmaster/ssl/??? //type ServerNameCallback func(ssl *C.SSL, ad C.int, arg unsafe.Pointer) int -//export call_servername_cb -func call_servername_cb(ssl *C.SSL, ad C.int, arg unsafe.Pointer) C.int { +//export callServerNameCb +func callServerNameCb(ssl *C.SSL, ad C.int, arg unsafe.Pointer) C.int { var c *Ctx = (*Ctx)(arg) //setup a dummy Conn so we can associate a SSL_CTX from user callback From 6325b7cd36b83b20d66097fb71b3930b5cec2201 Mon Sep 17 00:00:00 2001 From: jb0n Date: Wed, 30 Sep 2015 17:01:48 -0700 Subject: [PATCH 05/13] cleaned up a small bug (was pointing a void* to the wrongthing, but it worked). Fixing some names. Allowing user defined callback to pass data back to themselves --- ctx.go | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/ctx.go b/ctx.go index 72da005f..230bd792 100644 --- a/ctx.go +++ b/ctx.go @@ -74,10 +74,11 @@ static long SSL_CTX_set_tlsext_servername_callback_not_a_macro(SSL_CTX* ctx, voi return SSL_CTX_set_tlsext_servername_callback(ctx, fp); } -typedef struct CtxWrapper { +typedef struct TlsServernameData { void *go_ctx; SSL_CTX *ctx; -} CtxWrapper; + void *arg; +} TlsServernameData; extern int callServerNameCb(SSL* ssl, int ad, void* arg); @@ -85,11 +86,11 @@ static int call_go_servername(SSL* ssl, int ad, void* arg) { return callServerNameCb(ssl, ad, arg); } -static int servername_gateway(CtxWrapper* cw) { +static int servername_gateway(TlsServernameData* cw) { SSL_CTX* ctx = cw->ctx; //TODO: figure out what to do with return codes. The first isn't 0 SSL_CTX_set_tlsext_servername_callback(ctx, call_go_servername); - SSL_CTX_set_tlsext_servername_arg(ctx, cw->go_ctx); + SSL_CTX_set_tlsext_servername_arg(ctx, cw); return 0; } @@ -147,6 +148,7 @@ type Ctx struct { verify_cb VerifyCallback //servername_cb ServerNameCallback servername_cb func(ssl Conn, ad int, arg unsafe.Pointer) int + ted C.TlsServernameData } //export get_ssl_ctx_idx @@ -637,24 +639,25 @@ func (c *Ctx) SessGetCacheSize() int { //export callServerNameCb func callServerNameCb(ssl *C.SSL, ad C.int, arg unsafe.Pointer) C.int { - var c *Ctx = (*Ctx)(arg) + var ted *C.TlsServernameData = (*C.TlsServernameData)(arg) + goCtx := (*Ctx)(ted.go_ctx) //setup a dummy Conn so we can associate a SSL_CTX from user callback conn := Conn{ ssl: ssl, - ctx: c, + ctx: goCtx, } - - ret := c.servername_cb(conn, int(ad), arg) + ret := goCtx.servername_cb(conn, int(ad), ted.arg) return C.int(ret) } //func (c *Ctx) SetTlsExtServerNameCallback(cb ServerNameCallback) int { -func (c *Ctx) SetTlsExtServerNameCallback(cb func(ssl Conn, ad int, arg unsafe.Pointer) int) int { +func (c *Ctx) SetTlsExtServerNameCallback(cb func(ssl Conn, ad int, arg unsafe.Pointer) int, arg unsafe.Pointer) int { c.servername_cb = cb - cw := C.CtxWrapper{ + c.ted = C.TlsServernameData{ go_ctx: unsafe.Pointer(c), ctx: c.ctx, + arg: arg, } - return int(C.servername_gateway(&cw)) + return int(C.servername_gateway(&c.ted)) } From ab76d8aa1d049afdc76e0d25c948462554aafbeb Mon Sep 17 00:00:00 2001 From: jb0n Date: Tue, 13 Oct 2015 15:13:27 -0700 Subject: [PATCH 06/13] adding a test for setting SNI --- tls_ext_test.go | 120 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 tls_ext_test.go diff --git a/tls_ext_test.go b/tls_ext_test.go new file mode 100644 index 00000000..e6015da1 --- /dev/null +++ b/tls_ext_test.go @@ -0,0 +1,120 @@ +// Copyright (C) 2014 Space Monkey, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package openssl + +import ( + "bytes" + "io" + "sync" + "testing" + "unsafe" +) + +var gFoundServerName bool = false +var gServerName string +var gCallbackData string = "some callback data" + +func passThroughServername() func(ssl Conn, ad int, arg unsafe.Pointer) int { + x := func(ssl Conn, ad int, arg unsafe.Pointer) int { + cbData := (*string)(arg) + if *cbData != gCallbackData { //we should getthe callback data we set on the CTX + return 1 + } + name := ssl.GetServerName() + if name == gServerName { + gFoundServerName = true + //here we'd normally do soemthing like get a CTX for the specific server name and + //set it on the conn. + } else { + gFoundServerName = false + } + return 0 + } + return x +} + +func TestTLSExtSNI(t *testing.T) { + //setup SNI On the CTX + server_conn, client_conn := NetPipe(t) + defer server_conn.Close() + defer client_conn.Close() + + server, client := OpenSSLConstructor(t, server_conn, client_conn) + cconn := client.(*Conn) + sconn := server.(*Conn) + ctx := (*sconn).ctx + //setup SNI On the CTX + rc := ctx.SetTlsExtServerNameCallback(passThroughServername(), unsafe.Pointer(&gCallbackData)) + if rc != 0 { + t.Fatal("Expected 0 from ctx.SetTlsExtServerNameCallback, but got %d", rc) + } + data := "first test string\n" + host := "test-host" + + var wg sync.WaitGroup + wg.Add(2) + go func() { + defer wg.Done() + gServerName = host + err := cconn.SetTlsExtHostName(host) + if err != nil { + t.Fatal(err) + } + + 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() + if gFoundServerName == false { + t.Fatal("Expected gFoundServerName to be set to true") + } + if gServerName != host { + t.Fatal("Expected gServerName to be '%s', but it was '%s'", host, gServerName) + } +} From 435a2e6bea384d7db0ef1318b68a9e663654d510 Mon Sep 17 00:00:00 2001 From: jb0n Date: Tue, 13 Oct 2015 15:53:37 -0700 Subject: [PATCH 07/13] sticking to typedef where possible --- ctx.go | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/ctx.go b/ctx.go index 230bd792..6afa5374 100644 --- a/ctx.go +++ b/ctx.go @@ -141,13 +141,12 @@ var ( ) type Ctx struct { - ctx *C.SSL_CTX - cert *Certificate - chain []*Certificate - key PrivateKey - verify_cb VerifyCallback - //servername_cb ServerNameCallback - servername_cb func(ssl Conn, ad int, arg unsafe.Pointer) int + ctx *C.SSL_CTX + cert *Certificate + chain []*Certificate + key PrivateKey + verify_cb VerifyCallback + servername_cb ServerNameCallback ted C.TlsServernameData } @@ -635,7 +634,7 @@ func (c *Ctx) SessGetCacheSize() int { // Set SSL_CTX_set_tlsext_servername_callback // https://www.openssl.org/docs/manmaster/ssl/??? -//type ServerNameCallback func(ssl *C.SSL, ad C.int, arg unsafe.Pointer) int +type ServerNameCallback func(ssl Conn, ad int, arg unsafe.Pointer) int //export callServerNameCb func callServerNameCb(ssl *C.SSL, ad C.int, arg unsafe.Pointer) C.int { @@ -651,8 +650,8 @@ func callServerNameCb(ssl *C.SSL, ad C.int, arg unsafe.Pointer) C.int { return C.int(ret) } -//func (c *Ctx) SetTlsExtServerNameCallback(cb ServerNameCallback) int { -func (c *Ctx) SetTlsExtServerNameCallback(cb func(ssl Conn, ad int, arg unsafe.Pointer) int, arg unsafe.Pointer) int { +func (c *Ctx) SetTlsExtServerNameCallback(cb func(ssl Conn, ad int, arg unsafe.Pointer) int, + arg unsafe.Pointer) int { c.servername_cb = cb c.ted = C.TlsServernameData{ go_ctx: unsafe.Pointer(c), From 86eef808fefa18c5e4e1005541d1b7fcd05e98ef Mon Sep 17 00:00:00 2001 From: jb0n Date: Wed, 21 Oct 2015 17:29:29 -0700 Subject: [PATCH 08/13] adding Peek to openssl.Conn --- conn.go | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/conn.go b/conn.go index 07ccb7bf..db75458a 100644 --- a/conn.go +++ b/conn.go @@ -476,6 +476,43 @@ func (c *Conn) Read(b []byte) (n int, err error) { return 0, err } +func (c *Conn) peek(b []byte) (int, func() error) { + if len(b) == 0 { + return 0, nil + } + c.mtx.Lock() + defer c.mtx.Unlock() + if c.is_shutdown { + return 0, func() error { return io.EOF } + } + runtime.LockOSThread() + defer runtime.UnlockOSThread() + rv, errno := C.SSL_peek(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) Peek(b []byte) (n int, err error) { + if len(b) == 0 { + return 0, nil + } + err = tryAgain + for err == tryAgain { + n, errcb := c.peek(b) + err = c.handleError(errcb) + if err == nil { + go c.flushOutputBuffer() + return n, nil + } + if err == io.ErrUnexpectedEOF { + err = io.EOF + } + } + return 0, err +} + func (c *Conn) write(b []byte) (int, func() error) { if len(b) == 0 { return 0, nil From 9934c101bae47d00a1a2e5e996a040f9a26ea530 Mon Sep 17 00:00:00 2001 From: jb0n Date: Mon, 26 Oct 2015 16:21:46 -0700 Subject: [PATCH 09/13] needed to add libcrypto for newer openssl installs --- build.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.go b/build.go index 27c71b1b..e831a210 100644 --- a/build.go +++ b/build.go @@ -16,7 +16,7 @@ package openssl -// #cgo pkg-config: libssl +// #cgo pkg-config: libssl libcrypto // #cgo windows CFLAGS: -DWIN32_LEAN_AND_MEAN // #cgo darwin CFLAGS: -Wno-deprecated-declarations import "C" From a0b486691fab0fd55d4a41e2f8ea36f562d5e994 Mon Sep 17 00:00:00 2001 From: jb0n Date: Fri, 2 Sep 2016 18:08:06 -0700 Subject: [PATCH 10/13] this is the commit I tried to get in a little while ago, but I had some weird stuff going on working in different dirs (go get vs an actual checkout). This takes care of the go1.6+ pointer ownership/passing rules we were in violation of --- ctx.go | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/ctx.go b/ctx.go index 6afa5374..fffc47d6 100644 --- a/ctx.go +++ b/ctx.go @@ -80,6 +80,16 @@ typedef struct TlsServernameData { void *arg; } TlsServernameData; +static TlsServernameData* new_TlsServernameData() { + return malloc(sizeof(TlsServernameData)); +} + +//UNUSED: openssl doesn't have a way to unset SNI callback or arg. So we just leak whatever +//the function above allocates +//static void del_TlsServernameData(TlsServernameData *tsd) { +// free(tds); +//} + extern int callServerNameCb(SSL* ssl, int ad, void* arg); static int call_go_servername(SSL* ssl, int ad, void* arg) { @@ -147,7 +157,7 @@ type Ctx struct { key PrivateKey verify_cb VerifyCallback servername_cb ServerNameCallback - ted C.TlsServernameData + ted *C.TlsServernameData } //export get_ssl_ctx_idx @@ -653,10 +663,10 @@ func callServerNameCb(ssl *C.SSL, ad C.int, arg unsafe.Pointer) C.int { func (c *Ctx) SetTlsExtServerNameCallback(cb func(ssl Conn, ad int, arg unsafe.Pointer) int, arg unsafe.Pointer) int { c.servername_cb = cb - c.ted = C.TlsServernameData{ - go_ctx: unsafe.Pointer(c), - ctx: c.ctx, - arg: arg, - } - return int(C.servername_gateway(&c.ted)) + ted := C.new_TlsServernameData() + ted.go_ctx = unsafe.Pointer(c) + ted.ctx = c.ctx + ted.arg = arg + c.ted = ted + return int(C.servername_gateway(c.ted)) } From f6fb917a5901e7d1252e3f308b2fd5a43e3256e0 Mon Sep 17 00:00:00 2001 From: jb0n Date: Fri, 2 Sep 2016 18:15:42 -0700 Subject: [PATCH 11/13] let's not deref potential NULL pointers --- ctx.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ctx.go b/ctx.go index fffc47d6..ff11e7fb 100644 --- a/ctx.go +++ b/ctx.go @@ -664,6 +664,9 @@ func (c *Ctx) SetTlsExtServerNameCallback(cb func(ssl Conn, ad int, arg unsafe.P arg unsafe.Pointer) int { c.servername_cb = cb ted := C.new_TlsServernameData() + if ted == nil { + return 1 + } ted.go_ctx = unsafe.Pointer(c) ted.ctx = c.ctx ted.arg = arg From 4b21cb3b7d7693b988194f70e492a6be3e07399f Mon Sep 17 00:00:00 2001 From: jb0n Date: Tue, 6 Sep 2016 14:17:47 -0700 Subject: [PATCH 12/13] may as well use calloc there since we're handing it back to go and the go way is to aero initialize everything --- ctx.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ctx.go b/ctx.go index ff11e7fb..1c0c1793 100644 --- a/ctx.go +++ b/ctx.go @@ -81,7 +81,7 @@ typedef struct TlsServernameData { } TlsServernameData; static TlsServernameData* new_TlsServernameData() { - return malloc(sizeof(TlsServernameData)); + return calloc(1, sizeof(TlsServernameData)); } //UNUSED: openssl doesn't have a way to unset SNI callback or arg. So we just leak whatever From d822f9404e96ced3676c8a79bd861d2577d99593 Mon Sep 17 00:00:00 2001 From: jgould Date: Wed, 19 Jul 2023 16:30:13 -0700 Subject: [PATCH 13/13] hooking up SSL_version to the golang side --- conn.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/conn.go b/conn.go index db75458a..ee69974c 100644 --- a/conn.go +++ b/conn.go @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:build cgo // +build cgo package openssl @@ -31,6 +32,9 @@ package openssl // const char * SSL_get_cipher_name_not_a_macro(const SSL *ssl) { // return SSL_get_cipher_name(ssl); // } +// int SSL_version_not_a_macro(const SSL *ssl) { +// return SSL_version(ssl); +// } import "C" import ( @@ -612,3 +616,7 @@ func (c *Conn) VerifyResult() VerifyResult { func (c *Conn) GetServerName() string { return C.GoString(C.SSL_get_servername(c.ssl, C.TLSEXT_NAMETYPE_host_name)) } + +func (c *Conn) Version() int { + return int(C.SSL_version_not_a_macro(c.ssl)) +}