mirror of
https://github.com/libp2p/go-openssl.git
synced 2025-04-25 17:50:23 +08:00
support session resumption and hooking into the ticket callback
Change-Id: I8e12e4c1f0a8b350853a41636035baf1cfb0c952
This commit is contained in:
parent
0c8dfef3f6
commit
4d3c3b16ef
88
conn.go
88
conn.go
@ -16,25 +16,31 @@
|
||||
|
||||
package openssl
|
||||
|
||||
// #include <stdlib.h>
|
||||
// #include <openssl/ssl.h>
|
||||
// #include <openssl/conf.h>
|
||||
// #include <openssl/err.h>
|
||||
//
|
||||
// int sk_X509_num_not_a_macro(STACK_OF(X509) *sk) { return sk_X509_num(sk); }
|
||||
// X509 *sk_X509_value_not_a_macro(STACK_OF(X509)* sk, int i) {
|
||||
// return sk_X509_value(sk, i);
|
||||
// }
|
||||
// long SSL_set_tlsext_host_name_not_a_macro(SSL *ssl, const char *name) {
|
||||
// return SSL_set_tlsext_host_name(ssl, name);
|
||||
// }
|
||||
// const char * SSL_get_cipher_name_not_a_macro(const SSL *ssl) {
|
||||
// return SSL_get_cipher_name(ssl);
|
||||
// }
|
||||
/*
|
||||
#include <stdlib.h>
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/conf.h>
|
||||
#include <openssl/err.h>
|
||||
|
||||
int sk_X509_num_not_a_macro(STACK_OF(X509) *sk) { return sk_X509_num(sk); }
|
||||
X509 *sk_X509_value_not_a_macro(STACK_OF(X509)* sk, int i) {
|
||||
return sk_X509_value(sk, i);
|
||||
}
|
||||
long SSL_set_tlsext_host_name_not_a_macro(SSL *ssl, const char *name) {
|
||||
return SSL_set_tlsext_host_name(ssl, name);
|
||||
}
|
||||
const char * SSL_get_cipher_name_not_a_macro(const SSL *ssl) {
|
||||
return SSL_get_cipher_name(ssl);
|
||||
}
|
||||
static int SSL_session_reused_not_a_macro(SSL *ssl) {
|
||||
return SSL_session_reused(ssl);
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"runtime"
|
||||
@ -368,11 +374,13 @@ type ConnectionState struct {
|
||||
CertificateError error
|
||||
CertificateChain []*Certificate
|
||||
CertificateChainError error
|
||||
SessionReused bool
|
||||
}
|
||||
|
||||
func (c *Conn) ConnectionState() (rv ConnectionState) {
|
||||
rv.Certificate, rv.CertificateError = c.PeerCertificate()
|
||||
rv.CertificateChain, rv.CertificateChainError = c.PeerCertificateChain()
|
||||
rv.SessionReused = c.SessionReused()
|
||||
return
|
||||
}
|
||||
|
||||
@ -566,3 +574,53 @@ func (c *Conn) SetTlsExtHostName(name string) error {
|
||||
func (c *Conn) VerifyResult() VerifyResult {
|
||||
return VerifyResult(C.SSL_get_verify_result(c.ssl))
|
||||
}
|
||||
|
||||
func (c *Conn) SessionReused() bool {
|
||||
return C.SSL_session_reused_not_a_macro(c.ssl) == 1
|
||||
}
|
||||
|
||||
func (c *Conn) GetSession() ([]byte, error) {
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
|
||||
// get1 increases the refcount of the session, so we have to free it.
|
||||
session := (*C.SSL_SESSION)(C.SSL_get1_session(c.ssl))
|
||||
if session == nil {
|
||||
return nil, errors.New("failed to get session")
|
||||
}
|
||||
defer C.SSL_SESSION_free(session)
|
||||
|
||||
// get the size of the encoding
|
||||
slen := C.i2d_SSL_SESSION(session, nil)
|
||||
|
||||
buf := (*C.uchar)(C.malloc(C.size_t(slen)))
|
||||
defer C.free(unsafe.Pointer(buf))
|
||||
|
||||
// this modifies the value of buf (seriously), so we have to pass in a temp
|
||||
// var so that we can actually read the bytes from buf.
|
||||
tmp := buf
|
||||
slen2 := C.i2d_SSL_SESSION(session, &tmp)
|
||||
if slen != slen2 {
|
||||
return nil, errors.New("session had different lengths")
|
||||
}
|
||||
|
||||
return C.GoBytes(unsafe.Pointer(buf), slen), nil
|
||||
}
|
||||
|
||||
func (c *Conn) setSession(session []byte) error {
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
|
||||
ptr := (*C.uchar)(&session[0])
|
||||
s := C.d2i_SSL_SESSION(nil, &ptr, C.long(len(session)))
|
||||
if s == nil {
|
||||
return fmt.Errorf("unable to load session: %s", errorFromErrorQueue())
|
||||
}
|
||||
defer C.SSL_SESSION_free(s)
|
||||
|
||||
ret := C.SSL_set_session(c.ssl, s)
|
||||
if ret != 1 {
|
||||
return fmt.Errorf("unable to set session: %s", errorFromErrorQueue())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
4
ctx.go
4
ctx.go
@ -104,6 +104,7 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
@ -122,6 +123,9 @@ type Ctx struct {
|
||||
chain []*Certificate
|
||||
key PrivateKey
|
||||
verify_cb VerifyCallback
|
||||
|
||||
ticket_store_mu sync.Mutex
|
||||
ticket_store *TicketStore
|
||||
}
|
||||
|
||||
//export get_ssl_ctx_idx
|
||||
|
53
digest.go
Normal file
53
digest.go
Normal file
@ -0,0 +1,53 @@
|
||||
// Copyright (C) 2015 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.
|
||||
|
||||
// +build cgo
|
||||
|
||||
package openssl
|
||||
|
||||
// #include <openssl/evp.h>
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Digest represents and openssl message digest.
|
||||
type Digest struct {
|
||||
ptr *C.EVP_MD
|
||||
}
|
||||
|
||||
// GetDigestByName returns the Digest with the name or nil and an error if the
|
||||
// digest was not found.
|
||||
func GetDigestByName(name string) (*Digest, error) {
|
||||
cname := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(cname))
|
||||
p := C.EVP_get_digestbyname(cname)
|
||||
if p == nil {
|
||||
return nil, fmt.Errorf("Digest %v not found", name)
|
||||
}
|
||||
// we can consider digests to use static mem; don't need to free
|
||||
return &Digest{ptr: p}, nil
|
||||
}
|
||||
|
||||
// GetDigestByName returns the Digest with the NID or nil and an error if the
|
||||
// digest was not found.
|
||||
func GetDigestByNid(nid NID) (*Digest, error) {
|
||||
sn, err := Nid2ShortName(nid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return GetDigestByName(sn)
|
||||
}
|
26
net.go
26
net.go
@ -77,6 +77,25 @@ const (
|
||||
// This library is not nice enough to use the system certificate store by
|
||||
// default for you yet.
|
||||
func Dial(network, addr string, ctx *Ctx, flags DialFlags) (*Conn, error) {
|
||||
return DialSession(network, addr, ctx, flags, nil)
|
||||
}
|
||||
|
||||
// DialSession will connect to network/address and then wrap the corresponding
|
||||
// underlying connection with an OpenSSL client connection using context ctx.
|
||||
// If flags includes InsecureSkipHostVerification, the server certificate's
|
||||
// hostname will not be checked to match the hostname in addr. Otherwise, flags
|
||||
// should be 0.
|
||||
//
|
||||
// Dial probably won't work for you unless you set a verify location or add
|
||||
// some certs to the certificate store of the client context you're using.
|
||||
// This library is not nice enough to use the system certificate store by
|
||||
// default for you yet.
|
||||
//
|
||||
// If session is not nil it will be used to resume the tls state. The session
|
||||
// can be retrieved from the GetSession method on the Conn.
|
||||
func DialSession(network, addr string, ctx *Ctx, flags DialFlags,
|
||||
session []byte) (*Conn, error) {
|
||||
|
||||
host, _, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -98,6 +117,13 @@ func Dial(network, addr string, ctx *Ctx, flags DialFlags) (*Conn, error) {
|
||||
c.Close()
|
||||
return nil, err
|
||||
}
|
||||
if session != nil {
|
||||
err := conn.setSession(session)
|
||||
if err != nil {
|
||||
c.Close()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if flags&DisableSNI == 0 {
|
||||
err = conn.SetTlsExtHostName(host)
|
||||
if err != nil {
|
||||
|
27
tickets.c
Normal file
27
tickets.c
Normal file
@ -0,0 +1,27 @@
|
||||
// Copyright (C) 2015 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.
|
||||
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/evp.h>
|
||||
#include "_cgo_export.h"
|
||||
|
||||
int ticket_key_cb(SSL *s, unsigned char key_name[16],
|
||||
unsigned char iv[EVP_MAX_IV_LENGTH],
|
||||
EVP_CIPHER_CTX *cctx, HMAC_CTX *hctx, int enc) {
|
||||
|
||||
SSL_CTX* ssl_ctx = SSL_get_SSL_CTX(s);
|
||||
void* p = SSL_CTX_get_ex_data(ssl_ctx, get_ssl_ctx_idx());
|
||||
// get the pointer to the go Ctx object and pass it back into the thunk
|
||||
return ticket_key_cb_thunk(p, s, key_name, iv, cctx, hctx, enc);
|
||||
}
|
239
tickets.go
Normal file
239
tickets.go
Normal file
@ -0,0 +1,239 @@
|
||||
// Copyright (C) 2015 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.
|
||||
|
||||
// +build cgo
|
||||
|
||||
package openssl
|
||||
|
||||
/*
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/evp.h>
|
||||
|
||||
static int SSL_CTX_set_tlsext_ticket_key_cb_not_a_macro(SSL_CTX *sslctx,
|
||||
int (*cb)(SSL *s, unsigned char key_name[16],
|
||||
unsigned char iv[EVP_MAX_IV_LENGTH],
|
||||
EVP_CIPHER_CTX *ctx, HMAC_CTX *hctx, int enc)) {
|
||||
|
||||
return SSL_CTX_set_tlsext_ticket_key_cb(sslctx, cb);
|
||||
}
|
||||
|
||||
extern int ticket_key_cb(SSL *s, unsigned char key_name[16],
|
||||
unsigned char iv[EVP_MAX_IV_LENGTH],
|
||||
EVP_CIPHER_CTX *cctx, HMAC_CTX *hctx, int enc);
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"os"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
KeyNameSize = 16
|
||||
)
|
||||
|
||||
// TicketCipherCtx describes the cipher that will be used by the ticket store
|
||||
// for encrypting the tickets. Engine may be nil if no engine is desired.
|
||||
type TicketCipherCtx struct {
|
||||
Cipher *Cipher
|
||||
Engine *Engine
|
||||
}
|
||||
|
||||
// TicketDigestCtx describes the digest that will be used by the ticket store
|
||||
// to authenticate the data. Engine may be nil if no engine is desired.
|
||||
type TicketDigestCtx struct {
|
||||
Digest *Digest
|
||||
Engine *Engine
|
||||
}
|
||||
|
||||
// TicketName is an identifier for the key material for a ticket.
|
||||
type TicketName [KeyNameSize]byte
|
||||
|
||||
// TicketKey is the key material for a ticket. If this is lost, forward secrecy
|
||||
// is lost as it allows decrypting TLS sessions retroactively.
|
||||
type TicketKey struct {
|
||||
Name TicketName
|
||||
CipherKey []byte
|
||||
HMACKey []byte
|
||||
IV []byte
|
||||
}
|
||||
|
||||
// TicketKeyManager is a manager for TicketKeys. It allows one to control the
|
||||
// lifetime of tickets, causing renewals and expirations for keys that are
|
||||
// created. Calls to the manager are serialized.
|
||||
type TicketKeyManager interface {
|
||||
// New should create a brand new TicketKey with a new name.
|
||||
New() *TicketKey
|
||||
|
||||
// Current should return a key that is still valid.
|
||||
Current() *TicketKey
|
||||
|
||||
// Lookup should return a key with the given name, or nil if no name
|
||||
// exists.
|
||||
Lookup(name TicketName) *TicketKey
|
||||
|
||||
// Expired should return if the key with the given name is expired and
|
||||
// should not be used any more.
|
||||
Expired(name TicketName) bool
|
||||
|
||||
// ShouldRenew should return if the key is still ok to use for the current
|
||||
// session, but we should send a new key for the client.
|
||||
ShouldRenew(name TicketName) bool
|
||||
}
|
||||
|
||||
// TicketStore descibes the encryption and authentication methods the tickets
|
||||
// will use along with a key manager for generating and keeping track of the
|
||||
// secrets.
|
||||
type TicketStore struct {
|
||||
CipherCtx TicketCipherCtx
|
||||
DigestCtx TicketDigestCtx
|
||||
Keys TicketKeyManager
|
||||
}
|
||||
|
||||
func (t *TicketStore) cipherEngine() *C.ENGINE {
|
||||
if t.CipherCtx.Engine == nil {
|
||||
return nil
|
||||
}
|
||||
return t.CipherCtx.Engine.e
|
||||
}
|
||||
|
||||
func (t *TicketStore) digestEngine() *C.ENGINE {
|
||||
if t.DigestCtx.Engine == nil {
|
||||
return nil
|
||||
}
|
||||
return t.DigestCtx.Engine.e
|
||||
}
|
||||
|
||||
const (
|
||||
// instruct to do a handshake
|
||||
ticket_resp_requireHandshake = 0
|
||||
// crypto context is set up correctly
|
||||
ticket_resp_sessionOk = 1
|
||||
// crypto context is ok, but the ticket should be reissued
|
||||
ticket_resp_renewSession = 2
|
||||
// we had a problem that shouldn't fall back to doing a handshake
|
||||
ticket_resp_error = -1
|
||||
|
||||
// asked to create session crypto context
|
||||
ticket_req_newSession = 1
|
||||
// asked to load crypto context for a previous session
|
||||
ticket_req_lookupSession = 0
|
||||
)
|
||||
|
||||
//export ticket_key_cb_thunk
|
||||
func ticket_key_cb_thunk(p unsafe.Pointer, s *C.SSL, key_name *C.uchar,
|
||||
iv *C.uchar, cctx *C.EVP_CIPHER_CTX, hctx *C.HMAC_CTX, enc C.int) C.int {
|
||||
|
||||
// no panic's allowed. it's super hard to guarantee any state at this point
|
||||
// so just abort everything.
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
logger.Critf("openssl: ticket key callback panic'd: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}()
|
||||
|
||||
ctx := (*Ctx)(p)
|
||||
store := ctx.ticket_store
|
||||
if store == nil {
|
||||
// TODO(jeff): should this be an error condition? it doesn't make sense
|
||||
// to be called if we don't have a store I believe, but that's probably
|
||||
// not worth aborting the handshake which is what I believe returning
|
||||
// an error would do.
|
||||
return ticket_resp_requireHandshake
|
||||
}
|
||||
|
||||
ctx.ticket_store_mu.Lock()
|
||||
defer ctx.ticket_store_mu.Unlock()
|
||||
|
||||
switch enc {
|
||||
case ticket_req_newSession:
|
||||
key := store.Keys.Current()
|
||||
if key == nil {
|
||||
key = store.Keys.New()
|
||||
if key == nil {
|
||||
return ticket_resp_requireHandshake
|
||||
}
|
||||
}
|
||||
|
||||
C.memcpy(
|
||||
unsafe.Pointer(key_name),
|
||||
unsafe.Pointer(&key.Name[0]),
|
||||
KeyNameSize)
|
||||
C.EVP_EncryptInit_ex(
|
||||
cctx,
|
||||
store.CipherCtx.Cipher.ptr,
|
||||
store.cipherEngine(),
|
||||
(*C.uchar)(&key.CipherKey[0]),
|
||||
(*C.uchar)(&key.IV[0]))
|
||||
C.HMAC_Init_ex(
|
||||
hctx,
|
||||
unsafe.Pointer(&key.HMACKey[0]),
|
||||
C.int(len(key.HMACKey)),
|
||||
store.DigestCtx.Digest.ptr,
|
||||
store.digestEngine())
|
||||
|
||||
return ticket_resp_sessionOk
|
||||
|
||||
case ticket_req_lookupSession:
|
||||
var name TicketName
|
||||
C.memcpy(
|
||||
unsafe.Pointer(&name[0]),
|
||||
unsafe.Pointer(key_name),
|
||||
KeyNameSize)
|
||||
|
||||
key := store.Keys.Lookup(name)
|
||||
if key == nil {
|
||||
return ticket_resp_requireHandshake
|
||||
}
|
||||
if store.Keys.Expired(name) {
|
||||
return ticket_resp_requireHandshake
|
||||
}
|
||||
|
||||
C.EVP_DecryptInit_ex(
|
||||
cctx,
|
||||
store.CipherCtx.Cipher.ptr,
|
||||
store.cipherEngine(),
|
||||
(*C.uchar)(&key.CipherKey[0]),
|
||||
(*C.uchar)(&key.IV[0]))
|
||||
C.HMAC_Init_ex(
|
||||
hctx,
|
||||
unsafe.Pointer(&key.HMACKey[0]),
|
||||
C.int(len(key.HMACKey)),
|
||||
store.DigestCtx.Digest.ptr,
|
||||
store.digestEngine())
|
||||
|
||||
if store.Keys.ShouldRenew(name) {
|
||||
return ticket_resp_renewSession
|
||||
}
|
||||
|
||||
return ticket_resp_sessionOk
|
||||
|
||||
default:
|
||||
return ticket_resp_error
|
||||
}
|
||||
}
|
||||
|
||||
// SetTicketStore sets the ticket store for the context so that clients can do
|
||||
// ticket based session resumption. If the store is nil, the
|
||||
func (c *Ctx) SetTicketStore(store *TicketStore) {
|
||||
c.ticket_store = store
|
||||
|
||||
if store == nil {
|
||||
C.SSL_CTX_set_tlsext_ticket_key_cb_not_a_macro(c.ctx, nil)
|
||||
} else {
|
||||
C.SSL_CTX_set_tlsext_ticket_key_cb_not_a_macro(c.ctx,
|
||||
(*[0]byte)(C.ticket_key_cb))
|
||||
}
|
||||
}
|
3
verify.c
3
verify.c
@ -14,11 +14,10 @@
|
||||
|
||||
#include <openssl/ssl.h>
|
||||
#include "_cgo_export.h"
|
||||
#include <stdio.h>
|
||||
|
||||
int verify_cb(int ok, X509_STORE_CTX* store) {
|
||||
SSL* ssl = (SSL *)X509_STORE_CTX_get_app_data(store);
|
||||
SSL_CTX* ssl_ctx = ssl_ctx = SSL_get_SSL_CTX(ssl);
|
||||
SSL_CTX* ssl_ctx = SSL_get_SSL_CTX(ssl);
|
||||
void* p = SSL_CTX_get_ex_data(ssl_ctx, get_ssl_ctx_idx());
|
||||
// get the pointer to the go Ctx object and pass it back into the thunk
|
||||
return verify_cb_thunk(p, ok, store);
|
||||
|
Loading…
Reference in New Issue
Block a user