/*--------------------------------------------------------------------------
* LuaSec 0.4
* Copyright (C) 2006-2009 Bruno Silvestre
*
*--------------------------------------------------------------------------*/
#include <string.h>
#include <openssl/ssl.h>
#include <openssl/x509v3.h>
#include <openssl/err.h>
#include <lua.h>
#include <lauxlib.h>
#include "io.h"
#include "buffer.h"
#include "timeout.h"
#include "socket.h"
#include "ssl.h"
#include "x509.h"
/* index into the SSL storage where the t_ssl is.
* see SSL_get_ex_data().
*/
int luasec_ssl_idx;
/**
* Map error code into string.
*/
static const char *ssl_ioerror(void *ctx, int err)
{
if (err == IO_SSL) {
p_ssl ssl = (p_ssl) ctx;
switch(ssl->error) {
case SSL_ERROR_NONE: return "No error";
case SSL_ERROR_ZERO_RETURN: return "closed";
case SSL_ERROR_WANT_READ: return "wantread";
case SSL_ERROR_WANT_WRITE: return "wantwrite";
case SSL_ERROR_WANT_CONNECT: return "'connect' not completed";
case SSL_ERROR_WANT_ACCEPT: return "'accept' not completed";
case SSL_ERROR_WANT_X509_LOOKUP: return "Waiting for callback";
case SSL_ERROR_SYSCALL: return "System error";
case SSL_ERROR_SSL: return ERR_reason_error_string(ERR_get_error());
default: return "Unknown SSL error";
}
}
return socket_strerror(err);
}
/**
* Close the connection before the GC collect the object.
*/
static int meth_destroy(lua_State *L)
{
p_ssl ssl = (p_ssl) lua_touserdata(L, 1);
if (ssl->ssl) {
socket_setblocking(&ssl->sock);
SSL_shutdown(ssl->ssl);
socket_destroy(&ssl->sock);
SSL_free(ssl->ssl);
ssl->ssl = NULL;
}
luaL_unref(L, LUA_REGISTRYINDEX, ssl->t_cert_errors);
ssl->t_cert_errors = LUA_NOREF;
return 0;
}
/**
* Object information -- tostring metamethod
*/
static int meth_tostring(lua_State *L)
{
p_ssl ssl = (p_ssl)lua_touserdata(L, 1);
lua_pushfstring(L, "SSL connection: %p", ssl);
return 1;
}
/**
* Perform the TLS/SSL handshake
*/
static int handshake(p_ssl ssl)
{
int err;
p_timeout tm = timeout_markstart(&ssl->tm);
if (ssl->state == ST_SSL_CLOSED)
return IO_CLOSED;
for ( ; ; ) {
ERR_clear_error();
err = SSL_do_handshake(ssl->ssl);
ssl->error = SSL_get_error(ssl->ssl, err);
switch(ssl->error) {
case SSL_ERROR_NONE:
ssl->state = ST_SSL_CONNECTED;
return IO_DONE;
case SSL_ERROR_WANT_READ:
err = socket_waitfd(&ssl->sock, WAITFD_R, tm);
if (err == IO_TIMEOUT) return IO_SSL;
if (err != IO_DONE) return err;
break;
case SSL_ERROR_WANT_WRITE:
err = socket_waitfd(&ssl->sock, WAITFD_W, tm);
if (err == IO_TIMEOUT) return IO_SSL;
if (err != IO_DONE) return err;
break;
case SSL_ERROR_SYSCALL:
if (ERR_peek_error()) {
ssl->error = SSL_ERROR_SSL;
return IO_SSL;
}
if (err == 0)
return IO_CLOSED;
return socket_error();
default:
return IO_SSL;
}
}
return IO_UNKNOWN;
}
/**
* Send data
*/
static int ssl_send(void *ctx, const char *data, size_t count, size_t *sent,
p_timeout tm)
{
int err;
p_ssl ssl = (p_ssl) ctx;
if (ssl->state == ST_SSL_CLOSED)
return IO_CLOSED;
*sent = 0;
for ( ; ; ) {
ERR_clear_error();
err = SSL_write(ssl->ssl, data, (int) count);
ssl->error = SSL_get_error(ssl->ssl, err);
switch(ssl->error) {
case SSL_ERROR_NONE:
*sent = err;
return IO_DONE;
case SSL_ERROR_WANT_READ:
err = socket_waitfd(&ssl->sock, WAITFD_R, tm);
if (err == IO_TIMEOUT) return IO_SSL;
if (err != IO_DONE) return err;
break;
case SSL_ERROR_WANT_WRITE:
err = socket_waitfd(&ssl->sock, WAITFD_W, tm);
if (err == IO_TIMEOUT) return IO_SSL;
if (err != IO_DONE) return err;
break;
case SSL_ERROR_SYSCALL:
if (ERR_peek_error()) {
ssl->error = SSL_ERROR_SSL;
return IO_SSL;
}
if (err == 0)
return IO_CLOSED;
return socket_error();
default:
return IO_SSL;
}
}
return IO_UNKNOWN;
}
/**
* Receive data
*/
static int ssl_recv(void *ctx, char *data, size_t count, size_t *got,
p_timeout tm)
{
int err;
p_ssl ssl = (p_ssl) ctx;
if (ssl->state == ST_SSL_CLOSED)
return IO_CLOSED;
*got = 0;
for ( ; ; ) {
ERR_clear_error();
err = SSL_read(ssl->ssl, data, (int) count);
ssl->error = SSL_get_error(ssl->ssl, err);
switch(ssl->error) {
case SSL_ERROR_NONE:
*got = err;
return IO_DONE;
case SSL_ERROR_ZERO_RETURN:
*got = err;
return IO_CLOSED;
case SSL_ERROR_WANT_READ:
err = socket_waitfd(&ssl->sock, WAITFD_R, tm);
if (err == IO_TIMEOUT) return IO_SSL;
if (err != IO_DONE) return err;
break;
case SSL_ERROR_WANT_WRITE:
err = socket_waitfd(&ssl->sock, WAITFD_W, tm);
if (err == IO_TIMEOUT) return IO_SSL;
if (err != IO_DONE) return err;
break;
case SSL_ERROR_SYSCALL:
if (ERR_peek_error()) {
ssl->error = SSL_ERROR_SSL;
return IO_SSL;
}
if (err == 0)
return IO_CLOSED;
return socket_error();
default:
return IO_SSL;
}
}
return IO_UNKNOWN;
}
/**
* Create a new TLS/SSL object and mark it as new.
*/
static int meth_create(lua_State *L)
{
p_ssl ssl;
int mode = ctx_getmode(L, 1);
p_context ctx = checkctx(L, 1);
if (mode == MD_CTX_INVALID) {
lua_pushnil(L);
lua_pushstring(L, "invalid mode");
return 2;
}
if (luasec_ssl_idx == -1) {
luasec_ssl_idx = SSL_get_ex_new_index(0, "luasec ssl context", NULL, NULL, NULL);
if (luasec_ssl_idx == -1) {
lua_pushnil(L);
lua_pushstring(L, "error creating luasec SSL index");
return 2;
}
}
ssl = (p_ssl) lua_newuserdata(L, sizeof(t_ssl));
if (!ssl) {
lua_pushnil(L);
lua_pushstring(L, "error creating SSL object");
return 2;
}
ssl->ssl = SSL_new(ctx->context);
if (!ssl->ssl) {
lua_pushnil(L);
lua_pushstring(L, "error creating SSL object");
return 2;;
}
ssl->state = ST_SSL_NEW;
SSL_set_fd(ssl->ssl, (int) SOCKET_INVALID);
SSL_set_mode(ssl->ssl, SSL_MODE_ENABLE_PARTIAL_WRITE |
SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
#ifdef SSL_MODE_RELEASE_BUFFERS
SSL_set_mode(ssl->ssl, SSL_MODE_RELEASE_BUFFERS);
#endif
SSL_set_ex_data(ssl->ssl, luasec_ssl_idx, ssl);
ssl->t_cert_errors = LUA_NOREF;
if (mode == MD_CTX_SERVER)
SSL_set_accept_state(ssl->ssl);
else
SSL_set_connect_state(ssl->ssl);
io_init(&ssl->io, (p_send) ssl_send, (p_recv) ssl_recv,
(p_error) ssl_ioerror, ssl);
timeout_init(&ssl->tm, -1, -1);
buffer_init(&ssl->buf, &ssl->io, &ssl->tm);
luaL_getmetatable(L, "SSL:Connection");
lua_setmetatable(L, -2);
return 1;
}
/**
* Buffer send function
*/
static int meth_send(lua_State *L) {
p_ssl ssl = (p_ssl) luaL_checkudata(L, 1, "SSL:Connection");
return buffer_meth_send(L, &ssl->buf);
}
/**
* Buffer receive function
*/
static int meth_receive(lua_State *L) {
p_ssl ssl = (p_ssl) luaL_checkudata(L, 1, "SSL:Connection");
return buffer_meth_receive(L, &ssl->buf);
}
/**
* Select support methods
*/
static int meth_getfd(lua_State *L)
{
p_ssl ssl = (p_ssl) luaL_checkudata(L, 1, "SSL:Connection");
lua_pushnumber(L, ssl->sock);
return 1;
}
/**
* Set the TLS/SSL file descriptor.
* This is done *before* the handshake.
*/
static int meth_setfd(lua_State *L)
{
p_ssl ssl = (p_ssl) luaL_checkudata(L, 1, "SSL:Connection");
if (ssl->state != ST_SSL_NEW)
luaL_argerror(L, 1, "invalid SSL object state");
ssl->sock = luaL_checkint(L, 2);
socket_setnonblocking(&ssl->sock);
SSL_set_fd(ssl->ssl, (int)ssl->sock);
return 0;
}
/**
* Lua handshake function.
*/
static int meth_handshake(lua_State *L)
{
p_ssl ssl = (p_ssl) luaL_checkudata(L, 1, "SSL:Connection");
int err = handshake(ssl);
if (err == IO_DONE) {
lua_pushboolean(L, 1);
return 1;
}
lua_pushboolean(L, 0);
lua_pushstring(L, ssl_ioerror((void*)ssl, err));
return 2;
}
/**
* Close the connection.
*/
static int meth_close(lua_State *L)
{
p_ssl ssl = (p_ssl) luaL_checkudata(L, 1, "SSL:Connection");
meth_destroy(L);
ssl->state = ST_SSL_CLOSED;
return 0;
}
/**
* Set timeout.
*/
static int meth_settimeout(lua_State *L)
{
p_ssl ssl = (p_ssl) luaL_checkudata(L, 1, "SSL:Connection");
return timeout_meth_settimeout(L, &ssl->tm);
}
/**
* Check if there is data in the buffer.
*/
static int meth_dirty(lua_State *L)
{
int res = 0;
p_ssl ssl = (p_ssl) luaL_checkudata(L, 1, "SSL:Connection");
if (ssl->state != ST_SSL_CLOSED)
res = !buffer_isempty(&ssl->buf) || SSL_pending(ssl->ssl);
lua_pushboolean(L, res);
return 1;
}
/**
* Return the state information about the SSL object.
*/
static int meth_want(lua_State *L)
{
p_ssl ssl = (p_ssl) luaL_checkudata(L, 1, "SSL:Connection");
int code = (ssl->state == ST_SSL_CLOSED) ? SSL_NOTHING : SSL_want(ssl->ssl);
switch(code) {
case SSL_NOTHING: lua_pushstring(L, "nothing"); break;
case SSL_READING: lua_pushstring(L, "read"); break;
case SSL_WRITING: lua_pushstring(L, "write"); break;
case SSL_X509_LOOKUP: lua_pushstring(L, "x509lookup"); break;
}
return 1;
}
/**
* Return a pointer to SSL structure.
*/
static int meth_rawconn(lua_State *L)
{
p_ssl ssl = (p_ssl)luaL_checkudata(L, 1, "SSL:Connection");
lua_pushlightuserdata(L, (void*)ssl->ssl);
return 1;
}
/**
* Return the compression method used.
*/
static int meth_compression(lua_State *L)
{
const COMP_METHOD *comp;
p_ssl ssl = (p_ssl)luaL_checkudata(L, 1, "SSL:Connection");
comp = SSL_get_current_compression(ssl->ssl);
if (comp) {
lua_pushstring(L, SSL_COMP_get_name(comp));
return 1;
} else {
lua_pushboolean(L, 0);
return 1;
}
}
/**
* Return the validation state of the peer chain
*/
static int meth_getpeerverification(lua_State *L)
{
p_ssl ssl = (p_ssl)luaL_checkudata(L, 1, "SSL:Connection");
lua_pushboolean(L, SSL_get_verify_result(ssl->ssl) == X509_V_OK);
lua_rawgeti(L, LUA_REGISTRYINDEX, ssl->t_cert_errors);
return 2;
}
static void luasec_push_cert(lua_State *L, X509 *cert)
{
if (cert == NULL) {
lua_pushnil(L);
}
else
{
luasec_push_x509(L, cert);
}
}
/**
* Return the nth certificate of the peer's chain.
*/
static int meth_getpeercertificate(lua_State *L)
{
p_ssl ssl = (p_ssl)luaL_checkudata(L, 1, "SSL:Connection");
int n = luaL_optint(L, 2, 1); /* Default to the first cert */
STACK_OF(X509) *certs;
X509 *cert;
/* This function is 1-based, but OpenSSL is 0-based */
--n;
if (n < 0) {
lua_pushnil(L);
lua_pushliteral(L, "n must be positive");
return 2;
}
if (n == 0) {
luasec_push_cert(L, SSL_get_peer_certificate(ssl->ssl));
return 1;
}
/*
* In a server-context, the stack doesn't contain the peer cert, so
* adjust accordingly.
*/
if (ssl->ssl->server)
--n;
certs = SSL_get_peer_cert_chain(ssl->ssl);
if (n >= sk_X509_num(certs)) {
lua_pushnil(L);
lua_pushliteral(L, "no certificate at this index");
return 2;
}
cert = sk_X509_value(certs, n);
/* Locking...the same as in SSL_get_peer_certificate */
CRYPTO_add(&cert->references, 1, CRYPTO_LOCK_X509);
luasec_push_cert(L, cert);
return 1;
}
static int meth_getpeerchain(lua_State *L)
{
p_ssl ssl = (p_ssl)luaL_checkudata(L, 1, "SSL:Connection");
STACK_OF(X509) *certs;
int n_certs, i;
lua_newtable(L);
if (ssl->ssl->server) {
luasec_push_cert(L, SSL_get_peer_certificate(ssl->ssl));
lua_rawseti(L, -2, 1);
}
certs = SSL_get_peer_cert_chain(ssl->ssl);
n_certs = sk_X509_num(certs);
for (i = 0; i < n_certs; ++i) {
X509 *cert = sk_X509_value(certs, i);
/* Locking...the same as in SSL_get_peer_certificate */
CRYPTO_add(&cert->references, 1, CRYPTO_LOCK_X509);
luasec_push_cert(L, cert);
lua_rawseti(L, -2, lua_objlen(L, -2)+1);
}
return 1;
}
static int meth_getfinished(lua_State *L)
{
p_ssl ssl = (p_ssl)luaL_checkudata(L, 1, "SSL:Connection");
SSL *conn = ssl->ssl;
char *buffer = NULL;
size_t len = 0;
if ((len = SSL_get_finished(conn, NULL, 0)) != 0) {
buffer = malloc(len);
if (buffer == NULL) return 0;
len = SSL_get_finished(conn, buffer, len);
lua_pushlstring(L, buffer, len);
free(buffer);
return 1;
} else {
return 0;
}
}
static int meth_getpeerfinished(lua_State *L)
{
p_ssl ssl = (p_ssl)luaL_checkudata(L, 1, "SSL:Connection");
SSL *conn = ssl->ssl;
char *buffer = NULL;
size_t len = 0;
if ((len = SSL_get_peer_finished(conn, NULL, 0)) != 0) {
buffer = malloc(len);
if (buffer == NULL) return 0;
len = SSL_get_peer_finished(conn, buffer, len);
lua_pushlstring(L, buffer, len);
free(buffer);
return 1;
} else {
return 0;
}
}
/*---------------------------------------------------------------------------*/
/**
* SSL metamethods
*/
static luaL_Reg methods[] = {
{"close", meth_close},
{"getfd", meth_getfd},
{"dirty", meth_dirty},
{"dohandshake", meth_handshake},
{"receive", meth_receive},
{"send", meth_send},
{"settimeout", meth_settimeout},
{"want", meth_want},
{"compression", meth_compression},
{"getpeercertificate",meth_getpeercertificate},
{"getpeerchain", meth_getpeerchain},
{"getpeerverification", meth_getpeerverification},
{"getfinished", meth_getfinished},
{"getpeerfinished", meth_getpeerfinished},
{NULL, NULL}
};
/**
* SSL functions
*/
static luaL_Reg funcs[] = {
{"create", meth_create},
{"setfd", meth_setfd},
{"rawconnection", meth_rawconn},
{NULL, NULL}
};
/**
* Context metamethods.
*/
static luaL_Reg meta[] = {
{"__gc", meth_destroy},
{"__tostring", meth_tostring},
{NULL, NULL}
};
/**
* Initialize modules
*/
LUASEC_API int luaopen_ssl_core(lua_State *L)
{
/* Initialize SSL */
if (!SSL_library_init()) {
lua_pushstring(L, "unable to initialize SSL library");
lua_error(L);
}
SSL_load_error_strings();
/* Initialize internal library */
socket_open();
/* Register the functions and tables */
luaL_newmetatable(L, "SSL:Connection");
luaL_register(L, NULL, meta);
lua_newtable(L);
luaL_register(L, NULL, methods);
lua_setfield(L, -2, "__index");
luaL_register(L, "ssl.core", funcs);
lua_pushnumber(L, SOCKET_INVALID);
lua_setfield(L, -2, "invalidfd");
return 1;
}