/*--------------------------------------------------------------------------
* LuaSec 0.4
* Copyright (C) 2006-2009 Bruno Silvestre
*
*--------------------------------------------------------------------------*/
#include <string.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <lua.h>
#include <lauxlib.h>
#include "context.h"
#include "ssl.h"
struct ssl_option_s {
const char *name;
unsigned long code;
};
typedef struct ssl_option_s ssl_option_t;
/* index into the SSL storage where the context is.
* see SSL_CTX_get_ex_data().
*/
static int luasec_sslctx_idx = -1;
/* The export DH key */
static DH *dh_512 = NULL;
/* The larger key (builtin is 2048, caller may specify larger) */
static DH *dh_larger = NULL;
/* Generated via "openssl dhparam -2 -noout -C 512 2>/dev/null" */
static unsigned char dh512_p[] = {
0xE4,0x3F,0x75,0x82,0xAD,0x0B,0x28,0xC7,0xEF,0xCE,0xBC,0x3B,
0x14,0xBB,0xA6,0xF4,0xA2,0xE9,0xA6,0x59,0xCF,0x97,0x1C,0x86,
0x43,0x3B,0x92,0x4A,0x6B,0x15,0x4B,0x0C,0xAC,0x8F,0xFA,0x43,
0xE2,0xA8,0xC3,0x3B,0x7B,0x51,0x1B,0x46,0x21,0xBF,0x8C,0x06,
0x6C,0xB1,0x49,0x75,0xC7,0xAC,0x47,0x1D,0x9D,0x64,0xD5,0x99,
0x33,0x86,0xAD,0xEB,
};
/* Generated via "openssl dhparam -2 -noout -C 2048 2>/dev/null" */
static unsigned char dh2048_p[] = {
0x9B,0xF4,0xC5,0x57,0x81,0x8F,0xCF,0x31,0x78,0x95,0x04,0xCD,
0xEA,0xCC,0x30,0xEA,0xF7,0xCA,0x76,0xC8,0x8F,0x91,0xEA,0x0E,
0x44,0x8D,0xE2,0x63,0x19,0x3B,0x4D,0x04,0xC8,0x7D,0x0D,0xFF,
0x3D,0x52,0x76,0x02,0xF3,0xCA,0x1C,0x44,0xAF,0x0E,0xA9,0x59,
0x02,0x40,0x75,0xD6,0xED,0x35,0x4D,0x11,0x5B,0x2B,0x73,0x23,
0xE5,0x53,0x0B,0x1F,0xB0,0x47,0xC4,0x7F,0x95,0x5D,0xB0,0xD5,
0xF3,0xD3,0xAB,0x5F,0x28,0x2B,0xEC,0x2C,0x15,0x0B,0x1B,0x0C,
0xD4,0xBE,0x24,0x2F,0xC5,0x07,0x3C,0xE4,0xC5,0xE6,0x16,0x42,
0x4C,0x31,0x04,0xBB,0x80,0x96,0xFF,0x64,0x50,0xA4,0xA5,0xB5,
0xF5,0x3A,0xBA,0x57,0xE4,0xE6,0xC2,0x23,0x0A,0xB6,0x27,0xC4,
0x06,0x01,0x1E,0x98,0x20,0x09,0xC8,0xB7,0x90,0x09,0x86,0x06,
0xAA,0x85,0xE7,0x02,0xC8,0xC6,0xD9,0x1D,0xAB,0x17,0xEE,0x78,
0x73,0x78,0x88,0x7F,0xA7,0xF2,0x34,0xA7,0xDD,0x02,0x16,0x36,
0x0D,0x77,0x16,0x3E,0x95,0xAE,0x02,0xEE,0x36,0x37,0xD5,0x61,
0x5D,0xFE,0xC6,0x0B,0xDF,0xCE,0xB9,0x26,0x31,0x6F,0x34,0x92,
0xBB,0xBB,0x91,0x29,0x77,0x62,0x1D,0x75,0xA0,0x51,0x8D,0x31,
0x4C,0x64,0x4E,0xBF,0xDC,0xE8,0x67,0x17,0x90,0x6A,0x80,0xE9,
0xD7,0xD8,0x56,0x4E,0x85,0x21,0x9C,0xFB,0xE6,0x1B,0xD8,0x05,
0xFD,0x13,0x77,0x00,0x96,0x2D,0x0C,0x2A,0x95,0x1A,0x08,0x82,
0x2E,0xB3,0xE2,0xFC,0xE8,0xA6,0xF1,0x16,0x37,0x57,0x82,0xD6,
0xF5,0xAB,0xA9,0x43,0x8F,0x33,0xB0,0x57,0x38,0x6E,0x61,0xD4,
0xDD,0xE0,0x1C,0xCB,
};
static ssl_option_t ssl_options[] = {
/* OpenSSL 0.9.7 and 0.9.8 */
{"all", SSL_OP_ALL},
{"cipher_server_preference", SSL_OP_CIPHER_SERVER_PREFERENCE},
{"dont_insert_empty_fragments", SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS},
{"ephemeral_rsa", SSL_OP_EPHEMERAL_RSA},
{"netscape_ca_dn_bug", SSL_OP_NETSCAPE_CA_DN_BUG},
{"netscape_challenge_bug", SSL_OP_NETSCAPE_CHALLENGE_BUG},
{"microsoft_big_sslv3_buffer", SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER},
{"microsoft_sess_id_bug", SSL_OP_MICROSOFT_SESS_ID_BUG},
{"msie_sslv2_rsa_padding", SSL_OP_MSIE_SSLV2_RSA_PADDING},
{"netscape_demo_cipher_change_bug", SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG},
{"netscape_reuse_cipher_change_bug", SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG},
{"no_session_resumption_on_renegotiation",
SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION},
{"no_sslv2", SSL_OP_NO_SSLv2},
{"no_sslv3", SSL_OP_NO_SSLv3},
{"no_tlsv1", SSL_OP_NO_TLSv1},
{"pkcs1_check_1", SSL_OP_PKCS1_CHECK_1},
{"pkcs1_check_2", SSL_OP_PKCS1_CHECK_2},
{"single_dh_use", SSL_OP_SINGLE_DH_USE},
{"ssleay_080_client_dh_bug", SSL_OP_SSLEAY_080_CLIENT_DH_BUG},
{"sslref2_reuse_cert_type_bug", SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG},
{"tls_block_padding_bug", SSL_OP_TLS_BLOCK_PADDING_BUG},
{"tls_d5_bug", SSL_OP_TLS_D5_BUG},
{"tls_rollback_bug", SSL_OP_TLS_ROLLBACK_BUG},
/* OpenSSL 0.9.8 only */
#if OPENSSL_VERSION_NUMBER > 0x00908000L
{"cookie_exchange", SSL_OP_COOKIE_EXCHANGE},
{"no_query_mtu", SSL_OP_NO_QUERY_MTU},
{"single_ecdh_use", SSL_OP_SINGLE_ECDH_USE},
#endif
/* OpenSSL 0.9.8f and above */
#if defined(SSL_OP_NO_TICKET)
{"no_ticket", SSL_OP_NO_TICKET},
#endif
#if defined(SSL_OP_NO_COMPRESSION)
{"no_compression", SSL_OP_NO_COMPRESSION},
#endif
{NULL, 0L}
};
/*--------------------------- Auxiliary Functions ----------------------------*/
/**
* Return the context.
*/
p_context checkctx(lua_State *L, int idx)
{
return (p_context)luaL_checkudata(L, idx, "SSL:Context");
}
/**
* Prepare the SSL options flag.
*/
static int set_option_flag(const char *opt, unsigned long *flag)
{
ssl_option_t *p;
for (p = ssl_options; p->name; p++) {
if (!strcmp(opt, p->name)) {
*flag |= p->code;
return 1;
}
}
return 0;
}
/**
* Find the protocol.
*/
static const SSL_METHOD* str2method(const char *method)
{
if (!strcmp(method, "sslv3")) return SSLv3_method();
if (!strcmp(method, "tlsv1")) return TLSv1_method();
if (!strcmp(method, "sslv23")) return SSLv23_method();
return NULL;
}
/**
* Prepare the SSL handshake verify flag.
*/
static int set_verify_flag(const char *str, int *flag)
{
if (!strcmp(str, "none")) {
*flag |= SSL_VERIFY_NONE;
return 1;
}
if (!strcmp(str, "peer")) {
*flag |= SSL_VERIFY_PEER;
return 1;
}
if (!strcmp(str, "client_once")) {
*flag |= SSL_VERIFY_CLIENT_ONCE;
return 1;
}
if (!strcmp(str, "fail_if_no_peer_cert")) {
*flag |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
return 1;
}
return 0;
}
/**
* Password callback for reading the private key.
*/
static int passwd_cb(char *buf, int size, int flag, void *udata)
{
lua_State *L = (lua_State*)udata;
switch (lua_type(L, 3)) {
case LUA_TFUNCTION:
lua_pushvalue(L, 3);
lua_call(L, 0, 1);
if (lua_type(L, -1) != LUA_TSTRING)
return 0;
/* fallback */
case LUA_TSTRING:
strncpy(buf, lua_tostring(L, -1), size);
buf[size-1] = '\0';
return (int)strlen(buf);
}
return 0;
}
static DH *get_dh(const unsigned char *p, int len)
{
DH *dh = NULL;
static unsigned char g[] = { 0x02 };
if ((dh = DH_new()) == NULL) return NULL;
dh->p = BN_bin2bn(p, len, NULL);
dh->g = BN_bin2bn(g, sizeof(g), NULL);
if (dh->p == NULL || dh->g == NULL) {
DH_free(dh);
return NULL;
}
return dh;
}
/**
* DH parameter callback
*/
static DH *dh_param_cb(SSL *ssl, int is_export, int keylength)
{
/* Logic in postfix and dovecot, but we're using a 2048-bit group... */
if (is_export && keylength == 512) {
if (dh_512 == NULL) { dh_512 = get_dh(dh512_p, sizeof(dh512_p)); }
return dh_512;
} else {
if (dh_larger == NULL) { dh_larger = get_dh(dh2048_p, sizeof(dh2048_p)); }
return dh_larger;
}
}
/*------------------------------ Lua Functions -------------------------------*/
/**
* Create a SSL context.
*/
static int create(lua_State *L)
{
p_context ctx;
const SSL_METHOD *method;
if (luasec_sslctx_idx == -1) {
luasec_sslctx_idx = SSL_CTX_get_ex_new_index(0, "luasec sslctx context", NULL, NULL, NULL);
if (luasec_sslctx_idx == -1) {
lua_pushnil(L);
lua_pushstring(L, "error creating luasec SSL index");
return 2;
}
}
method = str2method(luaL_checkstring(L, 1));
if (!method) {
lua_pushnil(L);
lua_pushstring(L, "invalid protocol");
return 2;
}
ctx = (p_context) lua_newuserdata(L, sizeof(t_context));
if (!ctx) {
lua_pushnil(L);
lua_pushstring(L, "error creating context");
return 2;
}
ctx->L = L;
ctx->context = SSL_CTX_new(method);
if (!ctx->context) {
lua_pushnil(L);
lua_pushstring(L, "error creating context");
return 2;
}
ctx->verify_flags = LUASEC_VERIFY_FLAGS_NONE;
ctx->mode = MD_CTX_INVALID;
/* No session support */
SSL_CTX_set_session_cache_mode(ctx->context, SSL_SESS_CACHE_OFF);
/*
* Support ephemeral diffie-hellman key exchange. This is only needed
* for server mode, but clearer to put it here rather than set_mode.
*/
SSL_CTX_set_tmp_dh_callback(ctx->context, dh_param_cb);
#if defined(SSL_CTX_set_tmp_ecdh)
/*
* Support ECDH parameters. This uses the 384 bit prime field from
* NIST.
*/
SSL_CTX_set_tmp_ecdh(ctx->context, EC_KEY_new_by_curve_name(NID_secp384r1));
#endif
SSL_CTX_set_ex_data(ctx->context, luasec_sslctx_idx, ctx);
luaL_getmetatable(L, "SSL:Context");
lua_setmetatable(L, -2);
return 1;
}
/**
* Load the trusting certificates.
*/
static int load_locations(lua_State *L)
{
SSL_CTX *ctx = ctx_getcontext(L, 1);
const char *cafile = luaL_optstring(L, 2, NULL);
const char *capath = luaL_optstring(L, 3, NULL);
if (SSL_CTX_load_verify_locations(ctx, cafile, capath) != 1) {
lua_pushboolean(L, 0);
lua_pushfstring(L, "error loading CA locations (%s)",
ERR_reason_error_string(ERR_get_error()));
return 2;
}
lua_pushboolean(L, 1);
return 1;
}
/**
* Load the certificate file.
*/
static int load_cert(lua_State *L)
{
SSL_CTX *ctx = ctx_getcontext(L, 1);
const char *filename = luaL_checkstring(L, 2);
if (SSL_CTX_use_certificate_chain_file(ctx, filename) != 1) {
lua_pushboolean(L, 0);
lua_pushfstring(L, "error loading certificate (%s)",
ERR_reason_error_string(ERR_get_error()));
return 2;
}
lua_pushboolean(L, 1);
return 1;
}
/**
* Load the key file -- only in PEM format.
*/
static int load_key(lua_State *L)
{
int ret = 1;
SSL_CTX *ctx = ctx_getcontext(L, 1);
const char *filename = luaL_checkstring(L, 2);
switch (lua_type(L, 3)) {
case LUA_TSTRING:
case LUA_TFUNCTION:
SSL_CTX_set_default_passwd_cb(ctx, passwd_cb);
SSL_CTX_set_default_passwd_cb_userdata(ctx, L);
/* fallback */
case LUA_TNIL:
if (SSL_CTX_use_PrivateKey_file(ctx, filename, SSL_FILETYPE_PEM) == 1)
lua_pushboolean(L, 1);
else {
ret = 2;
lua_pushboolean(L, 0);
lua_pushfstring(L, "error loading private key (%s)",
ERR_reason_error_string(ERR_get_error()));
}
SSL_CTX_set_default_passwd_cb(ctx, NULL);
SSL_CTX_set_default_passwd_cb_userdata(ctx, NULL);
break;
default:
lua_pushstring(L, "invalid callback value");
lua_error(L);
}
return ret;
}
/**
* Load a DH params files. This is a global LuaSec thing.
*/
static int load_dhparams(lua_State *L)
{
const char *filename = luaL_checkstring(L, 1);
FILE *paramfile;
DH *dh;
paramfile = fopen(filename, "r");
if (!paramfile) {
lua_pushboolean(L, 0);
lua_pushfstring(L, "error reading dh param file %s: %s", filename,
strerror(errno));
return 2;
}
dh = PEM_read_DHparams(paramfile, NULL, NULL, NULL);
fclose(paramfile);
if (!dh) {
lua_pushboolean(L, 0);
lua_pushfstring(L, "error loading dh param file %s: %s", filename,
ERR_reason_error_string(ERR_get_error()));
return 2;
}
if (dh_larger)
DH_free(dh_larger);
dh_larger = dh;
lua_pushboolean(L, 1);
return 1;
}
/**
* Set the cipher list.
*/
static int set_cipher(lua_State *L)
{
SSL_CTX *ctx = ctx_getcontext(L, 1);
const char *list = luaL_checkstring(L, 2);
if (SSL_CTX_set_cipher_list(ctx, list) != 1) {
lua_pushboolean(L, 0);
lua_pushfstring(L, "error setting cipher list (%s)",
ERR_reason_error_string(ERR_get_error()));
return 2;
}
lua_pushboolean(L, 1);
return 1;
}
/**
* Set the depth for certificate checking.
*/
static int set_depth(lua_State *L)
{
SSL_CTX *ctx = ctx_getcontext(L, 1);
SSL_CTX_set_verify_depth(ctx, luaL_checkint(L, 2));
lua_pushboolean(L, 1);
return 1;
}
static void
luasec_push_cert_error(lua_State *L, int ref, int depth, int err)
{
int created = 0;
lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
lua_rawgeti(L, -1, depth + 1);
if (!lua_istable(L, -1)) {
/* If the table doesn't exist, create it */
created = 1;
lua_pop(L, 1);
lua_newtable(L);
}
lua_pushstring(L, X509_verify_cert_error_string(err));
lua_rawseti(L, -2, lua_objlen(L, -2)+1);
if (created) {
lua_rawseti(L, -2, depth + 1);
lua_pop(L, 1);
} else
lua_pop(L, 2);
}
int verify_cb(int preverify_ok, X509_STORE_CTX *x509_ctx)
{
SSL_CTX *context;
SSL *ssl;
p_context l_ctx;
p_ssl l_ssl;
int err, depth;
/* Short-circuit optimization */
if (preverify_ok)
return 1;
ssl = X509_STORE_CTX_get_ex_data(x509_ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
context = ssl->ctx;
l_ssl = SSL_get_ex_data(ssl, luasec_ssl_idx);
l_ctx = SSL_CTX_get_ex_data(context, luasec_sslctx_idx);
err = X509_STORE_CTX_get_error(x509_ctx);
depth = X509_STORE_CTX_get_error_depth(x509_ctx);
if (err != X509_V_OK) {
if (l_ssl->t_cert_errors == LUA_NOREF) {
lua_newtable(l_ctx->L);
l_ssl->t_cert_errors = luaL_ref(l_ctx->L, LUA_REGISTRYINDEX);
}
luasec_push_cert_error(l_ctx->L, l_ssl->t_cert_errors, depth, err);
}
return (l_ctx->verify_flags & LUASEC_VERIFY_FLAGS_ALWAYS_CONTINUE ? 1 : preverify_ok);
}
static int luasec_verify(X509_STORE_CTX *x509_ctx, void *ptr)
{
p_context ctx = ptr;
if (ctx->verify_flags & LUASEC_VERIFY_FLAGS_IGNORE_PURPOSE) {
X509_VERIFY_PARAM *param = X509_STORE_CTX_get0_param(x509_ctx);
if (param) {
X509_VERIFY_PARAM_set_purpose(param, X509_PURPOSE_SSL_SERVER);
X509_VERIFY_PARAM_set_trust(param, X509_TRUST_SSL_SERVER);
}
}
return X509_verify_cert(x509_ctx);
}
/**
* Set the handshake verify options.
*/
static int set_verify(lua_State *L)
{
int i;
int flag = 0, vflag = 0;
p_context ctx = checkctx(L, 1);
int max = lua_gettop(L);
/* any flag? */
if (max > 1) {
ctx->verify_flags = LUASEC_VERIFY_FLAGS_NONE;
for (i = 2; i <= max; i++) {
const char *s = luaL_checkstring(L, i);
if (!strcmp(s, "continue")) {
ctx->verify_flags |= LUASEC_VERIFY_FLAGS_ALWAYS_CONTINUE;
} else if (!strcmp(s, "ignore_purpose")) {
ctx->verify_flags |= LUASEC_VERIFY_FLAGS_IGNORE_PURPOSE;
} else if (!strcmp(s, "crl_check")) {
vflag |= X509_V_FLAG_CRL_CHECK;
} else if (!strcmp(s, "crl_check_chain")) {
vflag |= X509_V_FLAG_CRL_CHECK_ALL;
} else if (!set_verify_flag(s, &flag)) {
lua_pushboolean(L, 0);
lua_pushstring(L, "invalid verify option");
return 2;
}
}
SSL_CTX_set_verify(ctx->context, flag, ctx->verify_flags ? verify_cb : NULL);
SSL_CTX_set_cert_verify_callback(ctx->context, luasec_verify, ctx);
if(vflag)
{
X509_STORE *store = SSL_CTX_get_cert_store(ctx->context);
X509_STORE_set_flags(store, vflag);
}
}
lua_pushboolean(L, 1);
return 1;
}
/**
* Set the protocol options.
*/
static int set_options(lua_State *L)
{
int i;
unsigned long flag = 0L;
SSL_CTX *ctx = ctx_getcontext(L, 1);
int max = lua_gettop(L);
/* any option? */
if (max > 1) {
for (i = 2; i <= max; i++) {
#if !defined(SSL_OP_NO_COMPRESSION)
/* Manually disable compression if built against an older
* OpenSSL which doesn't have SSL_OP_NO_COMPRESSION
*/
if (!strcmp(luaL_checkstring(L, i), "no_compression")) {
ctx->comp_methods = NULL;
} else
#endif
if (!set_option_flag(luaL_checkstring(L, i), &flag)) {
lua_pushboolean(L, 0);
lua_pushstring(L, "invalid option");
return 2;
}
}
SSL_CTX_set_options(ctx, flag);
}
lua_pushboolean(L, 1);
return 1;
}
/**
* Set the context mode.
*/
static int set_mode(lua_State *L)
{
p_context ctx = checkctx(L, 1);
const char *str = luaL_checkstring(L, 2);
if (!strcmp("server", str)) {
ctx->mode = MD_CTX_SERVER;
lua_pushboolean(L, 1);
return 1;
}
if(!strcmp("client", str)) {
ctx->mode = MD_CTX_CLIENT;
lua_pushboolean(L, 1);
return 1;
}
lua_pushboolean(L, 0);
lua_pushstring(L, "invalid mode");
return 1;
}
/**
* Return a pointer to SSL_CTX structure.
*/
static int raw_ctx(lua_State *L)
{
p_context ctx = checkctx(L, 1);
lua_pushlightuserdata(L, (void*)ctx->context);
return 1;
}
/**
* Package functions
*/
static luaL_Reg funcs[] = {
{"create", create},
{"locations", load_locations},
{"loadcert", load_cert},
{"loadkey", load_key},
{"setcipher", set_cipher},
{"setdepth", set_depth},
{"setverify", set_verify},
{"setoptions", set_options},
{"setmode", set_mode},
{"rawcontext", raw_ctx},
{"loaddhparams", load_dhparams},
{NULL, NULL}
};
/*-------------------------------- Metamethods -------------------------------*/
/**
* Collect SSL context -- GC metamethod.
*/
static int meth_destroy(lua_State *L)
{
p_context ctx = checkctx(L, 1);
if (ctx->context) {
SSL_CTX_free(ctx->context);
ctx->context = NULL;
}
return 0;
}
/**
* Object information -- tostring metamethod.
*/
static int meth_tostring(lua_State *L)
{
p_context ctx = checkctx(L, 1);
lua_pushfstring(L, "SSL context: %p", ctx);
return 1;
}
/**
* Context metamethods.
*/
static luaL_Reg meta[] = {
{"__gc", meth_destroy},
{"__tostring", meth_tostring},
{NULL, NULL}
};
/*----------------------------- Public Functions ---------------------------*/
/**
* Retrieve the SSL context from the Lua stack.
*/
SSL_CTX* ctx_getcontext(lua_State *L, int idx)
{
p_context ctx = checkctx(L, idx);
return ctx->context;
}
/**
* Retrieve the mode from the context in the Lua stack.
*/
char ctx_getmode(lua_State *L, int idx)
{
p_context ctx = checkctx(L, idx);
return ctx->mode;
}
/*------------------------------ Initialization ------------------------------*/
/**
* Registre the module.
*/
int luaopen_ssl_context(lua_State *L)
{
luaL_newmetatable(L, "SSL:Context");
luaL_register(L, NULL, meta);
luaL_register(L, "ssl.context", funcs);
return 1;
}