diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c2aa44 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*.o +ssl.so +samples/certs/*.pem +samples/certs/*.srl +samples/key/*.pem diff --git a/samples/caching/client.lua b/samples/caching/client.lua new file mode 100644 index 0000000..bd86a7a --- /dev/null +++ b/samples/caching/client.lua @@ -0,0 +1,37 @@ +-- +-- Public domain +-- +require("socket") +require("ssl") + +local params = { + mode = "client", + protocol = "sslv3", + key = "../certs/clientAkey.pem", + certificate = "../certs/clientA.pem", + cafile = "../certs/rootA.pem", + verify = {"peer", "fail_if_no_peer_cert"}, + options = {"all", "no_sslv2"}, + cache = "client", +} + +local session + +while true do + local peer = socket.tcp() + assert( peer:connect("127.0.0.1", 8888) ) + + -- [[ SSL wrapper + peer = assert( ssl.wrap(peer, params) ) + if session then + peer:setsession(session) + end + assert( peer:dohandshake() ) + --]] + + session = peer:getsession() + print(peer:reused(),session) + + peer:receive("*l") + peer:close() +end diff --git a/samples/caching/server.lua b/samples/caching/server.lua new file mode 100644 index 0000000..1c162e1 --- /dev/null +++ b/samples/caching/server.lua @@ -0,0 +1,34 @@ +local socket = require("socket") +local ssl = require("ssl") + +local ctx = assert( ssl.newcontext { + mode = "server", + protocol = "sslv3", + key = "../certs/serverAkey.pem", + certificate = "../certs/serverA.pem", + cafile = "../certs/rootA.pem", + verify = {"peer", "fail_if_no_peer_cert"}, + options = {"all", "no_sslv2"}, + cache = "server" ; + cachesize = 1e6 ; + cachecontext = "serversample" ; +} ) + +local server = socket.tcp() +server:setoption('reuseaddr', true) +assert( server:bind("127.0.0.1", 8888) ) +server:listen() + +while true do + local peer = server:accept() + + -- [[ SSL wrapper + peer = assert( ssl.wrap(peer, ctx) ) + assert( peer:dohandshake() ) + --]] + + print(peer:reused(),peer:getsession()) + + peer:send("loop test\n") + peer:close() +end diff --git a/src/Makefile b/src/Makefile index 9b9520d..3adf964 100644 --- a/src/Makefile +++ b/src/Makefile @@ -7,6 +7,7 @@ OBJS= \ io.o \ usocket.o \ context.o \ + session.o \ ssl.o LIBS=-lssl -lcrypto diff --git a/src/context.c b/src/context.c index 53e3d3a..a97e068 100644 --- a/src/context.c +++ b/src/context.c @@ -42,7 +42,7 @@ static int set_option_flag(const char *opt, unsigned long *flag) /** * Find the protocol. */ -static SSL_METHOD* str2method(const char *method) +const static SSL_METHOD* str2method(const char *method) { if (!strcmp(method, "sslv3")) return SSLv3_method(); if (!strcmp(method, "tlsv1")) return TLSv1_method(); @@ -103,7 +103,7 @@ static int passwd_cb(char *buf, int size, int flag, void *udata) static int create(lua_State *L) { p_context ctx; - SSL_METHOD *method; + const SSL_METHOD *method; method = str2method(luaL_checkstring(L, 1)); if (!method) { @@ -124,8 +124,6 @@ static int create(lua_State *L) return 2; } ctx->mode = MD_CTX_INVALID; - /* No session support */ - SSL_CTX_set_session_cache_mode(ctx->context, SSL_SESS_CACHE_OFF); luaL_getmetatable(L, "SSL:Context"); lua_setmetatable(L, -2); return 1; @@ -297,6 +295,148 @@ static int set_mode(lua_State *L) return 1; } +/** + * Set context's session cache timeout + */ +static int set_timeout(lua_State *L) +{ + SSL_CTX *ctx = ctx_getcontext(L, 1); + long t = luaL_checklong(L, 2); + lua_pushinteger(L,SSL_CTX_set_timeout(ctx, t)); + return 1; +} + +/** + * Set context's session id context, see SSL_CTX_set_session_id_context(3) + */ +static int set_session_id_context(lua_State *L) +{ + SSL_CTX *ctx = ctx_getcontext(L, 1); + size_t len; + const unsigned char *str = (const unsigned char*)luaL_checklstring(L,2,&len); + if (SSL_CTX_set_session_id_context(ctx,str,len) == 1) { + lua_pushboolean(L,1); + return 1; + } else { + lua_pushboolean(L,0); + lua_pushfstring(L, "error setting session id (%s)", + ERR_reason_error_string(ERR_get_error())); + return 2; + } +} + +/** + * Set context's session cache mode, see SSL_CTX_set_session_cache_mode(3) + * Takes a vararg of items to be or'd together + */ +static int set_session_cache_mode(lua_State *L) +{ + SSL_CTX *ctx = ctx_getcontext(L, 1); + long mode = 0; + const char *str; + int i; + int top = lua_gettop(L); + for (i=2;i<=top;i++) { + switch(lua_type(L,i)) { + case LUA_TBOOLEAN: + if (lua_toboolean(L,i)) { + mode |= SSL_SESS_CACHE_BOTH; + } else { + mode |= SSL_SESS_CACHE_OFF; + } + break; + case LUA_TSTRING: + str = lua_tostring(L,i); + if (!strcmp("off",str)) { + mode |= SSL_SESS_CACHE_OFF; + break; + } else if (!strcmp("client",str)) { + mode |= SSL_SESS_CACHE_CLIENT; + break; + } else if (!strcmp("server",str)) { + mode |= SSL_SESS_CACHE_SERVER; + break; + } else if (!strcmp("both",str)) { + mode |= SSL_SESS_CACHE_BOTH; + break; + } else if (!strcmp("no_auto_clear",str)) { + mode |= SSL_SESS_CACHE_NO_AUTO_CLEAR; + break; + } else if (!strcmp("no_internal_lookup",str)) { + mode |= SSL_SESS_CACHE_NO_INTERNAL_LOOKUP; + break; + } else if (!strcmp("no_internal_store",str)) { + mode |= SSL_SESS_CACHE_NO_INTERNAL_STORE; + break; + } else if (!strcmp("no_internal",str)) { + mode |= SSL_SESS_CACHE_NO_INTERNAL; + break; + } + default: + return luaL_argerror(L,i,"unknown session cache mode"); + } + } + SSL_CTX_set_session_cache_mode(ctx,mode); + lua_pushboolean(L,1); + return 1; +} + +/* + * Set context's session cache size, see SSL_CTX_sess_set_cache_size(3) + */ +static int set_cache_size(lua_State *L) +{ + SSL_CTX *ctx = ctx_getcontext(L, 1); + long n = luaL_checklong(L, 2); + SSL_CTX_sess_set_cache_size(ctx, n); + lua_pushboolean(L, 1); + return 1; +} + +/* + * Get context's session cache size, see SSL_CTX_sess_set_cache_size(3) + */ +static int get_cache_size(lua_State *L) +{ + SSL_CTX *ctx = ctx_getcontext(L, 1); + lua_pushnumber(L, SSL_CTX_sess_get_cache_size(ctx)); + return 1; +} + +/** + * Return a table of context statistics + */ +static int ctx_stats(lua_State *L) +{ + SSL_CTX *ctx = ctx_getcontext(L, 1); + lua_createtable(L,0,12); + lua_pushnumber(L, SSL_CTX_sess_number(ctx)); + lua_setfield(L,-2,"number"); + lua_pushnumber(L, SSL_CTX_sess_connect(ctx)); + lua_setfield(L,-2,"connect"); + lua_pushnumber(L, SSL_CTX_sess_connect_good(ctx)); + lua_setfield(L,-2,"connect_good"); + lua_pushnumber(L, SSL_CTX_sess_connect_renegotiate(ctx)); + lua_setfield(L,-2,"connect_renegotiate"); + lua_pushnumber(L, SSL_CTX_sess_accept(ctx)); + lua_setfield(L,-2,"accept"); + lua_pushnumber(L, SSL_CTX_sess_accept_good(ctx)); + lua_setfield(L,-2,"accept_good"); + lua_pushnumber(L, SSL_CTX_sess_accept_renegotiate(ctx)); + lua_setfield(L,-2,"accept_renegotiate"); + lua_pushnumber(L, SSL_CTX_sess_hits(ctx)); + lua_setfield(L,-2,"hits"); + lua_pushnumber(L, SSL_CTX_sess_cb_hits(ctx)); + lua_setfield(L,-2,"cb_hits"); + lua_pushnumber(L, SSL_CTX_sess_misses(ctx)); + lua_setfield(L,-2,"misses"); + lua_pushnumber(L, SSL_CTX_sess_timeouts(ctx)); + lua_setfield(L,-2,"timeouts"); + lua_pushnumber(L, SSL_CTX_sess_cache_full(ctx)); + lua_setfield(L,-2,"cache_full"); + return 1; +} + /** * Return a pointer to SSL_CTX structure. */ @@ -312,6 +452,13 @@ static int raw_ctx(lua_State *L) */ static luaL_Reg funcs[] = { {"create", create}, + {NULL, NULL} +}; + +/* + * Context methods + */ +static luaL_Reg methods[] = { {"locations", load_locations}, {"loadcert", load_cert}, {"loadkey", load_key}, @@ -320,6 +467,12 @@ static luaL_Reg funcs[] = { {"setverify", set_verify}, {"setoptions", set_options}, {"setmode", set_mode}, + {"settimeout", set_timeout}, + {"setsessionidcontext", set_session_id_context}, + {"setsessioncachemode", set_session_cache_mode}, + {"setcachesize", set_cache_size}, + {"getcachesize", get_cache_size}, + {"stats", ctx_stats}, {"rawcontext", raw_ctx}, {NULL, NULL} }; @@ -387,7 +540,11 @@ char ctx_getmode(lua_State *L, int idx) int luaopen_ssl_context(lua_State *L) { luaL_newmetatable(L, "SSL:Context"); + lua_newtable(L); + luaL_register(L, NULL, methods); + lua_setfield(L,-2,"__index"); luaL_register(L, NULL, meta); luaL_register(L, "ssl.context", funcs); + luaL_register(L, NULL, methods); /* Add methods to require-returned table (COMPAT) */ return 1; } diff --git a/src/session.c b/src/session.c new file mode 100644 index 0000000..0dba52f --- /dev/null +++ b/src/session.c @@ -0,0 +1,135 @@ +/*-------------------------------------------------------------------------- + * + * Copyright (C) 2013 Daurnimator + * + *--------------------------------------------------------------------------*/ + +#include +#include + +#include "session.h" + +static void check_mt(lua_State *L); + +void pushSSL_SESSION (lua_State *L, SSL_SESSION *p) { + check_mt(L); + *(SSL_SESSION **)lua_newuserdata(L,sizeof(SSL_SESSION *)) = p; + luaL_getmetatable(L, "SSL:Session"); + lua_setmetatable(L, -2); +} + +SSL_SESSION * checkSSL_SESSION (lua_State *L, int narg) { + SSL_SESSION *sess = *(SSL_SESSION **)luaL_checkudata(L, narg, "SSL:Session"); + if(sess == NULL) { + /* Doesn't return */ + luaL_argerror(L, narg, "freed session"); + } + return sess; +} + +/** + * Collect SSL session -- GC metamethod. + */ +static int session_free(lua_State *L) +{ + SSL_SESSION **psess = luaL_checkudata(L, 1, "SSL:Session"); + if (*psess != NULL) { + SSL_SESSION_free(*psess); + *psess = NULL; + } + return 0; +} + +/** + * Maniplate the time a session was established + */ +static int session_get_time (lua_State *L) +{ + SSL_SESSION *sess = checkSSL_SESSION(L, 1); + lua_pushinteger(L,SSL_SESSION_get_time(sess)); + return 1; +} +static int session_set_time (lua_State *L) +{ + SSL_SESSION *sess = checkSSL_SESSION(L, 1); + long t = luaL_checklong(L, 2); + lua_pushinteger(L, SSL_SESSION_set_time(sess, t)); + return 1; +} + +/** + * Maniplate a session's timeout value, this can be used to extend or shorten the lifetime of the session. + */ +static int session_get_timeout (lua_State *L) +{ + SSL_SESSION *sess = checkSSL_SESSION(L, 1); + lua_pushinteger(L, SSL_SESSION_get_timeout(sess)); + return 1; +} +static int session_set_timeout (lua_State *L) +{ + SSL_SESSION *sess = checkSSL_SESSION(L, 1); + long t = luaL_checklong(L, 2); + lua_pushinteger(L, SSL_SESSION_set_timeout(sess, t)); + return 1; +} + +/** + * Get a session's (binary) id + */ +static int session_get_id (lua_State *L) +{ + SSL_SESSION *sess = checkSSL_SESSION(L, 1); + unsigned int len; + const unsigned char *str = SSL_SESSION_get_id(sess, &len); + lua_pushlstring(L, (const char*)str, len); + return 1; +} + +/** + * Returns ASN1 representation of session + */ +static int session_asn1(lua_State *L) +{ + SSL_SESSION *sess = checkSSL_SESSION(L, 1); + int len = i2d_SSL_SESSION(sess , NULL); + /* Allocate room for ASN1 representation on lua stack */ + void* buff = lua_newuserdata(L,len); + i2d_SSL_SESSION(sess , (unsigned char**)&buff); + lua_pushlstring(L, (char*)buff, len); + return 1; +} + +/** + * SSL session -- tostring metamethod. + */ +static int session_tostring(lua_State *L) +{ + lua_pushfstring(L, "SSL session: %p", checkSSL_SESSION(L, 1)); + return 1; +} + +/** + * Session metamethods + */ +static luaL_Reg meta[] = { + {"__gc", session_free}, + {"__tostring", session_tostring}, + {"asn1", session_asn1}, + {"get_time", session_get_time}, + {"set_time", session_set_time}, + {"get_timeout", session_get_timeout}, + {"set_timeout", session_set_timeout}, + {"id", session_get_id}, + {NULL, NULL} +}; + +static void check_mt(lua_State *L) { + if (luaL_newmetatable(L, "SSL:Session")) { + /* meta.__index = meta */ + lua_pushvalue(L,-1); + lua_setfield(L,-2,"__index"); + + luaL_register(L, NULL, meta); + } +} diff --git a/src/session.h b/src/session.h new file mode 100644 index 0000000..dbf9eb2 --- /dev/null +++ b/src/session.h @@ -0,0 +1,20 @@ +#ifndef __SESSION_H__ +#define __SESSION_H__ + +/*-------------------------------------------------------------------------- + * + * Copyright (C) 2013 Daurnimator + * + *--------------------------------------------------------------------------*/ + +#include +#include + +#include "context.h" + +void pushSSL_SESSION (lua_State *L, SSL_SESSION *p); +SSL_SESSION * checkSSL_SESSION (lua_State *L, int narg); + +LUASEC_API int luaopen_ssl_session(lua_State *L); + +#endif diff --git a/src/ssl.c b/src/ssl.c index bb5bbc7..58ee5bb 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -17,6 +17,7 @@ #include "timeout.h" #include "socket.h" #include "ssl.h" +#include "session.h" /** * Map error code into string. @@ -354,6 +355,43 @@ static int meth_rawconn(lua_State *L) return 1; } +/** + * Returns the session used by the SSL object + */ +static int meth_getsession(lua_State *L) +{ + p_ssl ssl = (p_ssl)luaL_checkudata(L, 1, "SSL:Connection"); + pushSSL_SESSION(L,SSL_get1_session(ssl->ssl)); + return 1; +} + +/** + * Returns the session used by the SSL object + */ +static int meth_setsession(lua_State *L) +{ + p_ssl ssl = (p_ssl)luaL_checkudata(L, 1, "SSL:Connection"); + SSL_SESSION *sess = checkSSL_SESSION(L, 2); + if (!SSL_set_session(ssl->ssl, sess)) { + lua_pushnil(L); + lua_pushstring(L,ERR_reason_error_string(ERR_get_error())); + return 2; + } + + lua_pushboolean(L,1); + return 1; +} + +/** + * Return if a reused session was negotiated during handshake + */ +static int meth_session_reused(lua_State *L) +{ + p_ssl ssl = (p_ssl)luaL_checkudata(L, 1, "SSL:Connection"); + lua_pushboolean(L,SSL_session_reused(ssl->ssl)); + return 1; +} + /*---------------------------------------------------------------------------*/ @@ -369,6 +407,9 @@ static luaL_Reg meta[] = { {"send", meth_send}, {"settimeout", meth_settimeout}, {"want", meth_want}, + {"getsession", meth_getsession}, + {"setsession", meth_setsession}, + {"reused", meth_session_reused}, {NULL, NULL} }; diff --git a/src/ssl.lua b/src/ssl.lua index 0170bc8..2644121 100644 --- a/src/ssl.lua +++ b/src/ssl.lua @@ -69,6 +69,19 @@ function newcontext(cfg) succ, msg = context.setdepth(ctx, cfg.depth) if not succ then return nil, msg end end + if cfg.mode == "server" and cfg.cachecontext then + succ, msg = context.setsessionidcontext(ctx, cfg.cachecontext) + if not succ then return nil, msg end + end + if cfg.cache then + context.setsessioncachemode(ctx, cfg.cache) + end + if cfg.cachetimeout then + context.settimeout(ctx, cfg.timeout) + end + if cfg.cachesize then + context.setcachesize(ctx, cfg.cachesize) + end return ctx end