mirror of
https://github.com/brunoos/luasec.git
synced 2024-11-09 15:08:26 +01:00
914 lines
22 KiB
C
914 lines
22 KiB
C
/*--------------------------------------------------------------------------
|
|
* LuaSec 0.8.1
|
|
*
|
|
* Copyright (C) 2014-2019 Kim Alvefur, Paul Aurich, Tobias Markmann,
|
|
* Matthew Wild.
|
|
* Copyright (C) 2006-2019 Bruno Silvestre.
|
|
*
|
|
*--------------------------------------------------------------------------*/
|
|
|
|
#include <string.h>
|
|
|
|
#if defined(WIN32)
|
|
#include <windows.h>
|
|
#endif
|
|
|
|
#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 "options.h"
|
|
|
|
#ifndef OPENSSL_NO_EC
|
|
#include <openssl/ec.h>
|
|
#include "ec.h"
|
|
#endif
|
|
|
|
/*--------------------------- Auxiliary Functions ----------------------------*/
|
|
|
|
/**
|
|
* Return the context.
|
|
*/
|
|
static p_context checkctx(lua_State *L, int idx)
|
|
{
|
|
return (p_context)luaL_checkudata(L, idx, "SSL:Context");
|
|
}
|
|
|
|
static p_context testctx(lua_State *L, int idx)
|
|
{
|
|
return (p_context)luaL_testudata(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;
|
|
}
|
|
|
|
#if (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x2070000fL) || (OPENSSL_VERSION_NUMBER < 0x1010000fL)
|
|
/**
|
|
* Find the protocol.
|
|
*/
|
|
static const SSL_METHOD* str2method(const char *method, int *vmin, int *vmax)
|
|
{
|
|
(void)vmin;
|
|
(void)vmax;
|
|
if (!strcmp(method, "any")) return SSLv23_method();
|
|
if (!strcmp(method, "sslv23")) return SSLv23_method(); // deprecated
|
|
if (!strcmp(method, "tlsv1")) return TLSv1_method();
|
|
if (!strcmp(method, "tlsv1_1")) return TLSv1_1_method();
|
|
if (!strcmp(method, "tlsv1_2")) return TLSv1_2_method();
|
|
return NULL;
|
|
}
|
|
|
|
#else
|
|
|
|
/**
|
|
* Find the protocol.
|
|
*/
|
|
static const SSL_METHOD* str2method(const char *method, int *vmin, int *vmax)
|
|
{
|
|
if (!strcmp(method, "any") || !strcmp(method, "sslv23")) { // 'sslv23' is deprecated
|
|
*vmin = 0;
|
|
*vmax = 0;
|
|
return TLS_method();
|
|
}
|
|
else if (!strcmp(method, "tlsv1")) {
|
|
*vmin = TLS1_VERSION;
|
|
*vmax = TLS1_VERSION;
|
|
return TLS_method();
|
|
}
|
|
else if (!strcmp(method, "tlsv1_1")) {
|
|
*vmin = TLS1_1_VERSION;
|
|
*vmax = TLS1_1_VERSION;
|
|
return TLS_method();
|
|
}
|
|
else if (!strcmp(method, "tlsv1_2")) {
|
|
*vmin = TLS1_2_VERSION;
|
|
*vmax = TLS1_2_VERSION;
|
|
return TLS_method();
|
|
}
|
|
#if defined(TLS1_3_VERSION)
|
|
else if (!strcmp(method, "tlsv1_3")) {
|
|
*vmin = TLS1_3_VERSION;
|
|
*vmax = TLS1_3_VERSION;
|
|
return TLS_method();
|
|
}
|
|
#endif
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* 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) {
|
|
lua_pop(L, 1); /* Remove the result from the stack */
|
|
return 0;
|
|
}
|
|
/* fallback */
|
|
case LUA_TSTRING:
|
|
strncpy(buf, lua_tostring(L, -1), size);
|
|
lua_pop(L, 1); /* Remove the result from the stack */
|
|
buf[size-1] = '\0';
|
|
return (int)strlen(buf);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Add an error related to a depth certificate of the chain.
|
|
*/
|
|
static void add_cert_error(lua_State *L, SSL *ssl, int err, int depth)
|
|
{
|
|
luaL_getmetatable(L, "SSL:Verify:Registry");
|
|
lua_pushlightuserdata(L, (void*)ssl);
|
|
lua_gettable(L, -2);
|
|
if (lua_isnil(L, -1)) {
|
|
lua_pop(L, 1);
|
|
/* Create an error table for this connection */
|
|
lua_newtable(L);
|
|
lua_pushlightuserdata(L, (void*)ssl);
|
|
lua_pushvalue(L, -2); /* keep the table on stack */
|
|
lua_settable(L, -4);
|
|
}
|
|
lua_rawgeti(L, -1, depth+1);
|
|
/* If the table doesn't exist, create it */
|
|
if (lua_isnil(L, -1)) {
|
|
lua_pop(L, 1); /* remove 'nil' from stack */
|
|
lua_newtable(L);
|
|
lua_pushvalue(L, -1); /* keep the table on stack */
|
|
lua_rawseti(L, -3, depth+1);
|
|
}
|
|
lua_pushstring(L, X509_verify_cert_error_string(err));
|
|
lua_rawseti(L, -2, lua_rawlen(L, -2) + 1);
|
|
/* Clear the stack */
|
|
lua_pop(L, 3);
|
|
}
|
|
|
|
/**
|
|
* Call Lua user function to get the DH key.
|
|
*/
|
|
static DH *dhparam_cb(SSL *ssl, int is_export, int keylength)
|
|
{
|
|
BIO *bio;
|
|
lua_State *L;
|
|
SSL_CTX *ctx = SSL_get_SSL_CTX(ssl);
|
|
p_context pctx = (p_context)SSL_CTX_get_app_data(ctx);
|
|
|
|
L = pctx->L;
|
|
|
|
/* Get the callback */
|
|
luaL_getmetatable(L, "SSL:DH:Registry");
|
|
lua_pushlightuserdata(L, (void*)ctx);
|
|
lua_gettable(L, -2);
|
|
|
|
/* Invoke the callback */
|
|
lua_pushboolean(L, is_export);
|
|
lua_pushnumber(L, keylength);
|
|
lua_call(L, 2, 1);
|
|
|
|
/* Load parameters from returned value */
|
|
if (lua_type(L, -1) != LUA_TSTRING) {
|
|
lua_pop(L, 2); /* Remove values from stack */
|
|
return NULL;
|
|
}
|
|
|
|
bio = BIO_new_mem_buf((void*)lua_tostring(L, -1), lua_rawlen(L, -1));
|
|
if (bio) {
|
|
pctx->dh_param = PEM_read_bio_DHparams(bio, NULL, NULL, NULL);
|
|
BIO_free(bio);
|
|
}
|
|
|
|
lua_pop(L, 2); /* Remove values from stack */
|
|
return pctx->dh_param;
|
|
}
|
|
|
|
/**
|
|
* Set the "ignore purpose" before to start verifing the certificate chain.
|
|
*/
|
|
static int cert_verify_cb(X509_STORE_CTX *x509_ctx, void *ptr)
|
|
{
|
|
int verify;
|
|
lua_State *L;
|
|
SSL_CTX *ctx = (SSL_CTX*)ptr;
|
|
p_context pctx = (p_context)SSL_CTX_get_app_data(ctx);
|
|
|
|
L = pctx->L;
|
|
|
|
/* Get verify flags */
|
|
luaL_getmetatable(L, "SSL:Verify:Registry");
|
|
lua_pushlightuserdata(L, (void*)ctx);
|
|
lua_gettable(L, -2);
|
|
verify = (int)lua_tonumber(L, -1);
|
|
|
|
lua_pop(L, 2); /* Remove values from stack */
|
|
|
|
if (verify & LSEC_VERIFY_IGNORE_PURPOSE) {
|
|
/* Set parameters to ignore the server 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);
|
|
}
|
|
}
|
|
/* Call OpenSSL standard verification function */
|
|
return X509_verify_cert(x509_ctx);
|
|
}
|
|
|
|
/**
|
|
* This callback implements the "continue on error" flag and log the errors.
|
|
*/
|
|
static int verify_cb(int preverify_ok, X509_STORE_CTX *x509_ctx)
|
|
{
|
|
int err;
|
|
int verify;
|
|
SSL *ssl;
|
|
SSL_CTX *ctx;
|
|
p_context pctx;
|
|
lua_State *L;
|
|
|
|
/* 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());
|
|
ctx = SSL_get_SSL_CTX(ssl);
|
|
pctx = (p_context)SSL_CTX_get_app_data(ctx);
|
|
L = pctx->L;
|
|
|
|
/* Get verify flags */
|
|
luaL_getmetatable(L, "SSL:Verify:Registry");
|
|
lua_pushlightuserdata(L, (void*)ctx);
|
|
lua_gettable(L, -2);
|
|
verify = (int)lua_tonumber(L, -1);
|
|
|
|
lua_pop(L, 2); /* Remove values from stack */
|
|
|
|
err = X509_STORE_CTX_get_error(x509_ctx);
|
|
if (err != X509_V_OK)
|
|
add_cert_error(L, ssl, err, X509_STORE_CTX_get_error_depth(x509_ctx));
|
|
|
|
return (verify & LSEC_VERIFY_CONTINUE ? 1 : preverify_ok);
|
|
}
|
|
|
|
/*------------------------------ Lua Functions -------------------------------*/
|
|
|
|
/**
|
|
* Create a SSL context.
|
|
*/
|
|
static int create(lua_State *L)
|
|
{
|
|
p_context ctx;
|
|
const char *str_method;
|
|
const SSL_METHOD *method;
|
|
int vmin, vmax;
|
|
|
|
str_method = luaL_checkstring(L, 1);
|
|
method = str2method(str_method, &vmin, &vmax);
|
|
if (!method) {
|
|
lua_pushnil(L);
|
|
lua_pushfstring(L, "invalid protocol (%s)", str_method);
|
|
return 2;
|
|
}
|
|
ctx = (p_context) lua_newuserdata(L, sizeof(t_context));
|
|
if (!ctx) {
|
|
lua_pushnil(L);
|
|
lua_pushstring(L, "error creating context");
|
|
return 2;
|
|
}
|
|
memset(ctx, 0, sizeof(t_context));
|
|
ctx->context = SSL_CTX_new(method);
|
|
if (!ctx->context) {
|
|
lua_pushnil(L);
|
|
lua_pushfstring(L, "error creating context (%s)",
|
|
ERR_reason_error_string(ERR_get_error()));
|
|
return 2;
|
|
}
|
|
#if ! ((defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x2070000fL) || (OPENSSL_VERSION_NUMBER < 0x1010000fL))
|
|
SSL_CTX_set_min_proto_version(ctx->context, vmin);
|
|
SSL_CTX_set_max_proto_version(ctx->context, vmax);
|
|
#endif
|
|
ctx->mode = LSEC_MODE_INVALID;
|
|
ctx->L = L;
|
|
luaL_getmetatable(L, "SSL:Context");
|
|
lua_setmetatable(L, -2);
|
|
|
|
/* No session support */
|
|
SSL_CTX_set_session_cache_mode(ctx->context, SSL_SESS_CACHE_OFF);
|
|
/* Link LuaSec context with the OpenSSL context */
|
|
SSL_CTX_set_app_data(ctx->context, ctx);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Load the trusting certificates.
|
|
*/
|
|
static int load_locations(lua_State *L)
|
|
{
|
|
SSL_CTX *ctx = lsec_checkcontext(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 = lsec_checkcontext(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 = lsec_checkcontext(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;
|
|
}
|
|
|
|
/**
|
|
* Check that the certificate public key matches the private key
|
|
*/
|
|
|
|
static int check_key(lua_State *L)
|
|
{
|
|
SSL_CTX *ctx = lsec_checkcontext(L, 1);
|
|
lua_pushboolean(L, SSL_CTX_check_private_key(ctx));
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Set the cipher list.
|
|
*/
|
|
static int set_cipher(lua_State *L)
|
|
{
|
|
SSL_CTX *ctx = lsec_checkcontext(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 cipher suites.
|
|
*/
|
|
static int set_ciphersuites(lua_State *L)
|
|
{
|
|
#if defined(TLS1_3_VERSION)
|
|
SSL_CTX *ctx = lsec_checkcontext(L, 1);
|
|
const char *list = luaL_checkstring(L, 2);
|
|
if (SSL_CTX_set_ciphersuites(ctx, list) != 1) {
|
|
lua_pushboolean(L, 0);
|
|
lua_pushfstring(L, "error setting cipher list (%s)", ERR_reason_error_string(ERR_get_error()));
|
|
return 2;
|
|
}
|
|
#endif
|
|
lua_pushboolean(L, 1);
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Set the depth for certificate checking.
|
|
*/
|
|
static int set_depth(lua_State *L)
|
|
{
|
|
SSL_CTX *ctx = lsec_checkcontext(L, 1);
|
|
SSL_CTX_set_verify_depth(ctx, (int)luaL_checkinteger(L, 2));
|
|
lua_pushboolean(L, 1);
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Set the handshake verify options.
|
|
*/
|
|
static int set_verify(lua_State *L)
|
|
{
|
|
int i;
|
|
const char *str;
|
|
int flag = 0;
|
|
SSL_CTX *ctx = lsec_checkcontext(L, 1);
|
|
int max = lua_gettop(L);
|
|
for (i = 2; i <= max; i++) {
|
|
str = luaL_checkstring(L, i);
|
|
if (!set_verify_flag(str, &flag)) {
|
|
lua_pushboolean(L, 0);
|
|
lua_pushfstring(L, "invalid verify option (%s)", str);
|
|
return 2;
|
|
}
|
|
}
|
|
if (flag) SSL_CTX_set_verify(ctx, flag, NULL);
|
|
lua_pushboolean(L, 1);
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Set the protocol options.
|
|
*/
|
|
static int set_options(lua_State *L)
|
|
{
|
|
int i;
|
|
const char *str;
|
|
unsigned long flag = 0L;
|
|
SSL_CTX *ctx = lsec_checkcontext(L, 1);
|
|
int max = lua_gettop(L);
|
|
/* any option? */
|
|
if (max > 1) {
|
|
for (i = 2; i <= max; i++) {
|
|
str = luaL_checkstring(L, i);
|
|
if (!set_option_flag(str, &flag)) {
|
|
lua_pushboolean(L, 0);
|
|
lua_pushfstring(L, "invalid option (%s)", str);
|
|
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 = LSEC_MODE_SERVER;
|
|
lua_pushboolean(L, 1);
|
|
return 1;
|
|
}
|
|
if (!strcmp("client", str)) {
|
|
ctx->mode = LSEC_MODE_CLIENT;
|
|
lua_pushboolean(L, 1);
|
|
return 1;
|
|
}
|
|
lua_pushboolean(L, 0);
|
|
lua_pushfstring(L, "invalid mode (%s)", str);
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Configure DH parameters.
|
|
*/
|
|
static int set_dhparam(lua_State *L)
|
|
{
|
|
SSL_CTX *ctx = lsec_checkcontext(L, 1);
|
|
SSL_CTX_set_tmp_dh_callback(ctx, dhparam_cb);
|
|
|
|
/* Save callback */
|
|
luaL_getmetatable(L, "SSL:DH:Registry");
|
|
lua_pushlightuserdata(L, (void*)ctx);
|
|
lua_pushvalue(L, 2);
|
|
lua_settable(L, -3);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if !defined(OPENSSL_NO_EC)
|
|
/**
|
|
* Set elliptic curve.
|
|
*/
|
|
static int set_curve(lua_State *L)
|
|
{
|
|
long ret;
|
|
EC_KEY *key = NULL;
|
|
SSL_CTX *ctx = lsec_checkcontext(L, 1);
|
|
const char *str = luaL_checkstring(L, 2);
|
|
|
|
SSL_CTX_set_options(ctx, SSL_OP_SINGLE_ECDH_USE);
|
|
|
|
key = lsec_find_ec_key(L, str);
|
|
|
|
if (!key) {
|
|
lua_pushboolean(L, 0);
|
|
lua_pushfstring(L, "elliptic curve '%s' not supported", str);
|
|
return 2;
|
|
}
|
|
|
|
ret = SSL_CTX_set_tmp_ecdh(ctx, key);
|
|
/* SSL_CTX_set_tmp_ecdh takes its own reference */
|
|
EC_KEY_free(key);
|
|
|
|
if (!ret) {
|
|
lua_pushboolean(L, 0);
|
|
lua_pushfstring(L, "error setting elliptic curve (%s)",
|
|
ERR_reason_error_string(ERR_get_error()));
|
|
return 2;
|
|
}
|
|
|
|
lua_pushboolean(L, 1);
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Set elliptic curves list.
|
|
*/
|
|
static int set_curves_list(lua_State *L)
|
|
{
|
|
SSL_CTX *ctx = lsec_checkcontext(L, 1);
|
|
const char *str = luaL_checkstring(L, 2);
|
|
|
|
SSL_CTX_set_options(ctx, SSL_OP_SINGLE_ECDH_USE);
|
|
|
|
if (SSL_CTX_set1_curves_list(ctx, str) != 1) {
|
|
lua_pushboolean(L, 0);
|
|
lua_pushfstring(L, "unknown elliptic curve in \"%s\"", str);
|
|
return 2;
|
|
}
|
|
|
|
(void)SSL_CTX_set_ecdh_auto(ctx, 1);
|
|
|
|
lua_pushboolean(L, 1);
|
|
return 1;
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* Set the protocols a client should send for ALPN.
|
|
*/
|
|
static int set_alpn(lua_State *L)
|
|
{
|
|
long ret;
|
|
size_t len;
|
|
p_context ctx = checkctx(L, 1);
|
|
const char *str = luaL_checklstring(L, 2, &len);
|
|
|
|
ret = SSL_CTX_set_alpn_protos(ctx->context, (const unsigned char*)str, len);
|
|
if (ret) {
|
|
lua_pushboolean(L, 0);
|
|
lua_pushfstring(L, "error setting ALPN (%s)", ERR_reason_error_string(ERR_get_error()));
|
|
return 2;
|
|
}
|
|
lua_pushboolean(L, 1);
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* This standard callback calls the server's callback in Lua sapce.
|
|
* The server has to return a list in wire-format strings.
|
|
* This function uses a helper function to match server and client lists.
|
|
*/
|
|
static int alpn_cb(SSL *s, const unsigned char **out, unsigned char *outlen,
|
|
const unsigned char *in, unsigned int inlen, void *arg)
|
|
{
|
|
int ret;
|
|
size_t server_len;
|
|
const char *server;
|
|
p_context ctx = (p_context)arg;
|
|
lua_State *L = ctx->L;
|
|
|
|
luaL_getmetatable(L, "SSL:ALPN:Registry");
|
|
lua_pushlightuserdata(L, (void*)ctx->context);
|
|
lua_gettable(L, -2);
|
|
|
|
lua_pushlstring(L, (const char*)in, inlen);
|
|
|
|
lua_call(L, 1, 1);
|
|
|
|
if (!lua_isstring(L, -1)) {
|
|
lua_pop(L, 2);
|
|
return SSL_TLSEXT_ERR_NOACK;
|
|
}
|
|
|
|
// Protocol list from server in wire-format string
|
|
server = luaL_checklstring(L, -1, &server_len);
|
|
ret = SSL_select_next_proto((unsigned char**)out, outlen, (const unsigned char*)server,
|
|
server_len, in, inlen);
|
|
if (ret != OPENSSL_NPN_NEGOTIATED) {
|
|
lua_pop(L, 2);
|
|
return SSL_TLSEXT_ERR_NOACK;
|
|
}
|
|
|
|
// Copy the result because lua_pop() can collect the pointer
|
|
ctx->alpn = malloc(*outlen);
|
|
memcpy(ctx->alpn, (void*)*out, *outlen);
|
|
*out = (const unsigned char*)ctx->alpn;
|
|
|
|
lua_pop(L, 2);
|
|
|
|
return SSL_TLSEXT_ERR_OK;
|
|
}
|
|
|
|
/**
|
|
* Set a callback a server can use to select the next protocol with ALPN.
|
|
*/
|
|
static int set_alpn_cb(lua_State *L)
|
|
{
|
|
p_context ctx = checkctx(L, 1);
|
|
|
|
luaL_getmetatable(L, "SSL:ALPN:Registry");
|
|
lua_pushlightuserdata(L, (void*)ctx->context);
|
|
lua_pushvalue(L, 2);
|
|
lua_settable(L, -3);
|
|
|
|
SSL_CTX_set_alpn_select_cb(ctx->context, alpn_cb, ctx);
|
|
|
|
lua_pushboolean(L, 1);
|
|
return 1;
|
|
}
|
|
|
|
|
|
/**
|
|
* Package functions
|
|
*/
|
|
static luaL_Reg funcs[] = {
|
|
{"create", create},
|
|
{"locations", load_locations},
|
|
{"loadcert", load_cert},
|
|
{"loadkey", load_key},
|
|
{"checkkey", check_key},
|
|
{"setalpn", set_alpn},
|
|
{"setalpncb", set_alpn_cb},
|
|
{"setcipher", set_cipher},
|
|
{"setciphersuites", set_ciphersuites},
|
|
{"setdepth", set_depth},
|
|
{"setdhparam", set_dhparam},
|
|
{"setverify", set_verify},
|
|
{"setoptions", set_options},
|
|
{"setmode", set_mode},
|
|
|
|
#if !defined(OPENSSL_NO_EC)
|
|
{"setcurve", set_curve},
|
|
{"setcurveslist", set_curves_list},
|
|
#endif
|
|
|
|
{NULL, NULL}
|
|
};
|
|
|
|
/*-------------------------------- Metamethods -------------------------------*/
|
|
|
|
/**
|
|
* Collect SSL context -- GC metamethod.
|
|
*/
|
|
static int meth_destroy(lua_State *L)
|
|
{
|
|
p_context ctx = checkctx(L, 1);
|
|
if (ctx->context) {
|
|
/* Clear registries */
|
|
luaL_getmetatable(L, "SSL:DH:Registry");
|
|
lua_pushlightuserdata(L, (void*)ctx->context);
|
|
lua_pushnil(L);
|
|
lua_settable(L, -3);
|
|
luaL_getmetatable(L, "SSL:Verify:Registry");
|
|
lua_pushlightuserdata(L, (void*)ctx->context);
|
|
lua_pushnil(L);
|
|
lua_settable(L, -3);
|
|
luaL_getmetatable(L, "SSL:ALPN:Registry");
|
|
lua_pushlightuserdata(L, (void*)ctx->context);
|
|
lua_pushnil(L);
|
|
lua_settable(L, -3);
|
|
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* Set extra flags for handshake verification.
|
|
*/
|
|
static int meth_set_verify_ext(lua_State *L)
|
|
{
|
|
int i;
|
|
const char *str;
|
|
int crl_flag = 0;
|
|
int lsec_flag = 0;
|
|
SSL_CTX *ctx = lsec_checkcontext(L, 1);
|
|
int max = lua_gettop(L);
|
|
for (i = 2; i <= max; i++) {
|
|
str = luaL_checkstring(L, i);
|
|
if (!strcmp(str, "lsec_continue")) {
|
|
lsec_flag |= LSEC_VERIFY_CONTINUE;
|
|
} else if (!strcmp(str, "lsec_ignore_purpose")) {
|
|
lsec_flag |= LSEC_VERIFY_IGNORE_PURPOSE;
|
|
} else if (!strcmp(str, "crl_check")) {
|
|
crl_flag |= X509_V_FLAG_CRL_CHECK;
|
|
} else if (!strcmp(str, "crl_check_chain")) {
|
|
crl_flag |= X509_V_FLAG_CRL_CHECK_ALL;
|
|
} else {
|
|
lua_pushboolean(L, 0);
|
|
lua_pushfstring(L, "invalid verify option (%s)", str);
|
|
return 2;
|
|
}
|
|
}
|
|
/* Set callback? */
|
|
if (lsec_flag) {
|
|
SSL_CTX_set_verify(ctx, SSL_CTX_get_verify_mode(ctx), verify_cb);
|
|
SSL_CTX_set_cert_verify_callback(ctx, cert_verify_cb, (void*)ctx);
|
|
/* Save flag */
|
|
luaL_getmetatable(L, "SSL:Verify:Registry");
|
|
lua_pushlightuserdata(L, (void*)ctx);
|
|
lua_pushnumber(L, lsec_flag);
|
|
lua_settable(L, -3);
|
|
} else {
|
|
SSL_CTX_set_verify(ctx, SSL_CTX_get_verify_mode(ctx), NULL);
|
|
SSL_CTX_set_cert_verify_callback(ctx, NULL, NULL);
|
|
/* Remove flag */
|
|
luaL_getmetatable(L, "SSL:Verify:Registry");
|
|
lua_pushlightuserdata(L, (void*)ctx);
|
|
lua_pushnil(L);
|
|
lua_settable(L, -3);
|
|
}
|
|
|
|
/* X509 flag */
|
|
X509_STORE_set_flags(SSL_CTX_get_cert_store(ctx), crl_flag);
|
|
|
|
/* Ok */
|
|
lua_pushboolean(L, 1);
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Context metamethods.
|
|
*/
|
|
static luaL_Reg meta[] = {
|
|
{"__gc", meth_destroy},
|
|
{"__tostring", meth_tostring},
|
|
{NULL, NULL}
|
|
};
|
|
|
|
/**
|
|
* Index metamethods.
|
|
*/
|
|
static luaL_Reg meta_index[] = {
|
|
{"setverifyext", meth_set_verify_ext},
|
|
{NULL, NULL}
|
|
};
|
|
|
|
|
|
/*----------------------------- Public Functions ---------------------------*/
|
|
|
|
/**
|
|
* Retrieve the SSL context from the Lua stack.
|
|
*/
|
|
SSL_CTX* lsec_checkcontext(lua_State *L, int idx)
|
|
{
|
|
p_context ctx = checkctx(L, idx);
|
|
return ctx->context;
|
|
}
|
|
|
|
SSL_CTX* lsec_testcontext(lua_State *L, int idx)
|
|
{
|
|
p_context ctx = testctx(L, idx);
|
|
return (ctx) ? ctx->context : NULL;
|
|
}
|
|
|
|
/**
|
|
* Retrieve the mode from the context in the Lua stack.
|
|
*/
|
|
int lsec_getmode(lua_State *L, int idx)
|
|
{
|
|
p_context ctx = checkctx(L, idx);
|
|
return ctx->mode;
|
|
}
|
|
|
|
/*-- Compat - Lua 5.1 --*/
|
|
#if (LUA_VERSION_NUM == 501)
|
|
|
|
void *lsec_testudata (lua_State *L, int ud, const char *tname) {
|
|
void *p = lua_touserdata(L, ud);
|
|
if (p != NULL) { /* value is a userdata? */
|
|
if (lua_getmetatable(L, ud)) { /* does it have a metatable? */
|
|
luaL_getmetatable(L, tname); /* get correct metatable */
|
|
if (!lua_rawequal(L, -1, -2)) /* not the same? */
|
|
p = NULL; /* value is a userdata with wrong metatable */
|
|
lua_pop(L, 2); /* remove both metatables */
|
|
return p;
|
|
}
|
|
}
|
|
return NULL; /* value is not a userdata with a metatable */
|
|
}
|
|
|
|
#endif
|
|
|
|
/*------------------------------ Initialization ------------------------------*/
|
|
|
|
/**
|
|
* Registre the module.
|
|
*/
|
|
LSEC_API int luaopen_ssl_context(lua_State *L)
|
|
{
|
|
luaL_newmetatable(L, "SSL:DH:Registry"); /* Keep all DH callbacks */
|
|
luaL_newmetatable(L, "SSL:ALPN:Registry"); /* Keep all ALPN callbacks */
|
|
luaL_newmetatable(L, "SSL:Verify:Registry"); /* Keep all verify flags */
|
|
luaL_newmetatable(L, "SSL:Context");
|
|
setfuncs(L, meta);
|
|
|
|
/* Create __index metamethods for context */
|
|
luaL_newlib(L, meta_index);
|
|
lua_setfield(L, -2, "__index");
|
|
|
|
lsec_load_curves(L);
|
|
|
|
/* Return the module */
|
|
luaL_newlib(L, funcs);
|
|
|
|
return 1;
|
|
}
|