diff --git a/.travis.yml b/.travis.yml index 14ee63603c..ff3675d8c6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -82,7 +82,7 @@ install: - git clone https://github.com/openresty/rds-json-nginx-module.git ../rds-json-nginx-module - git clone https://github.com/openresty/srcache-nginx-module.git ../srcache-nginx-module - git clone https://github.com/openresty/redis2-nginx-module.git ../redis2-nginx-module - - git clone https://github.com/openresty/lua-resty-core.git ../lua-resty-core + - git clone -b lua-ffi-api-sslctx https://github.com/detailyang/lua-resty-core.git ../lua-resty-core - git clone -b v2.1-agentzh https://github.com/openresty/luajit2.git before_script: diff --git a/src/ngx_http_lua_socket_tcp.c b/src/ngx_http_lua_socket_tcp.c index 49811168d1..3b1b44aa93 100644 --- a/src/ngx_http_lua_socket_tcp.c +++ b/src/ngx_http_lua_socket_tcp.c @@ -183,6 +183,12 @@ enum { } +#if (NGX_HTTP_SSL) + +#define ngx_http_lua_ngx_socket_tcp_mt_key "__ngx_socket_tcp_mt" + +#endif + static char ngx_http_lua_req_socket_metatable_key; static char ngx_http_lua_raw_req_socket_metatable_key; static char ngx_http_lua_tcp_socket_metatable_key; @@ -316,6 +322,20 @@ ngx_http_lua_inject_socket_tcp_api(ngx_log_t *log, lua_State *L) lua_pushvalue(L, -1); lua_setfield(L, -2, "__index"); + +#if (NGX_HTTP_SSL) + +#ifndef NGX_LUA_NO_FFI_API + + /* expose tcp object metatable to REGISTRY for FFI */ + lua_pushliteral(L, ngx_http_lua_ngx_socket_tcp_mt_key); + lua_pushvalue(L, -2); + lua_rawset(L, LUA_REGISTRYINDEX); + +#endif /* NGX_LUA_NO_FFI_API */ + +#endif /* NGX_HTTP_SSL */ + lua_rawset(L, LUA_REGISTRYINDEX); /* }}} */ @@ -587,6 +607,12 @@ ngx_http_lua_socket_tcp_connect(lua_State *L) u->conf = llcf; +#if (NGX_HTTP_SSL) + + u->ssl = llcf->ssl; + +#endif + pc = &u->peer; pc->log = r->connection->log; @@ -1200,6 +1226,35 @@ ngx_http_lua_socket_conn_error_retval_handler(ngx_http_request_t *r, #if (NGX_HTTP_SSL) + +#ifndef NGX_LUA_NO_FFI_API + +int +ngx_http_lua_ffi_socket_tcp_setsslctx(ngx_http_request_t *r, + ngx_http_lua_socket_tcp_upstream_t *u, void *cdata_ctx, char **err) +{ + SSL_CTX *ssl_ctx = cdata_ctx; + + ngx_ssl_t *ssl; + + ssl = ngx_pcalloc(r->pool, sizeof(ngx_ssl_t)); + if (ssl == NULL) { + *err = "no memory"; + return NGX_ERROR; + } + + ssl->ctx = ssl_ctx; + ssl->log = u->ssl->log; + ssl->buffer_size = u->ssl->buffer_size; + + u->ssl = ssl; + + return NGX_OK; +} + +#endif /* NGX_LUA_NO_FFI_API */ + + static int ngx_http_lua_socket_tcp_sslhandshake(lua_State *L) { @@ -1286,7 +1341,7 @@ ngx_http_lua_socket_tcp_sslhandshake(lua_State *L) return 1; } - if (ngx_ssl_create_connection(u->conf->ssl, c, + if (ngx_ssl_create_connection(u->ssl, c, NGX_SSL_BUFFER|NGX_SSL_CLIENT) != NGX_OK) { diff --git a/src/ngx_http_lua_socket_tcp.h b/src/ngx_http_lua_socket_tcp.h index dbdee41c6e..1443ab22ec 100644 --- a/src/ngx_http_lua_socket_tcp.h +++ b/src/ngx_http_lua_socket_tcp.h @@ -92,6 +92,7 @@ struct ngx_http_lua_socket_tcp_upstream_s { #if (NGX_HTTP_SSL) ngx_str_t ssl_name; + ngx_ssl_t *ssl; #endif unsigned ft_type:16; diff --git a/src/ngx_http_lua_ssl.c b/src/ngx_http_lua_ssl.c index 8ed7b95417..1a06b2ba5c 100644 --- a/src/ngx_http_lua_ssl.c +++ b/src/ngx_http_lua_ssl.c @@ -13,6 +13,10 @@ #if (NGX_HTTP_SSL) +static size_t ngx_http_lua_ssl_get_error(u_long e, u_char *ssl_err, + size_t ssl_err_len, const char *default_err, size_t default_err_len); + + int ngx_http_lua_ssl_ctx_index = -1; @@ -34,4 +38,504 @@ ngx_http_lua_ssl_init(ngx_log_t *log) } +static size_t +ngx_http_lua_ssl_get_error(u_long e, u_char *ssl_err, + size_t ssl_err_len, const char *default_err, size_t default_err_len) +{ + size_t len; + + if (e == 0) { + len = ngx_min(ssl_err_len, default_err_len); + ngx_memcpy(ssl_err, default_err, len); + + return len; + } + + ERR_error_string_n(e, (char *) ssl_err, ssl_err_len); + + return ngx_strlen(ssl_err); +} + + +#ifndef NGX_LUA_NO_FFI_API + + +void * +ngx_http_lua_ffi_ssl_ctx_init(ngx_uint_t protocols, char **err) +{ + ngx_ssl_t ssl; + + ssl.log = ngx_cycle->log; + if (ngx_ssl_create(&ssl, protocols, NULL) != NGX_OK) { + *err = "failed to create ssl ctx"; + return NULL; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ssl.log, 0, "lua ssl ctx init: %p:%d", + ssl.ctx, ssl.ctx->references); + + return ssl.ctx; +} + + +int +ngx_http_lua_ffi_ssl_ctx_set_cert_store(void *cdata_ctx, void *cdata_store, + int up_ref, unsigned char *err, size_t *err_len) +{ + SSL_CTX *ssl_ctx = cdata_ctx; + X509_STORE *x509_store = cdata_store; + + size_t n; + u_long e; + const char *default_err; + + /* + * Note: If another X509_STORE object is currently set in ctx, + * it will be X509_STORE_free()ed + */ + + SSL_CTX_set_cert_store(ssl_ctx, x509_store); + + if (up_ref == 0) { + return NGX_OK; + } + + /* + * X509_STORE_up_ref() require OpenSSL at least 1.1.0, so we use CRYPTO_add + * to implement X509_STORE_up_ref + */ + + if (CRYPTO_add(&x509_store->references, 1, CRYPTO_LOCK_X509_STORE) < 2) { + default_err = "X509_STORE_up_ref() failed"; + goto failed; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, + "lua ssl x509 store up reference: %p:%d", x509_store, + x509_store->references); + + return NGX_OK; + +failed: + + e = ERR_get_error(); + n = ngx_strlen(default_err); + *err_len = ngx_http_lua_ssl_get_error(e, err, *err_len, default_err, n); + + return NGX_ERROR; +} + + +int +ngx_http_lua_ffi_ssl_ctx_add_ca_cert(void *cdata_ctx, const u_char *cert, + size_t size, unsigned char *err, size_t *err_len) +{ + BIO *bio = NULL; + SSL_CTX *ssl_ctx = cdata_ctx; + + X509 *x509; + size_t n; + u_long e; + const char *default_err; + X509_STORE *store; + + bio = BIO_new_mem_buf(cert, size); + if (bio == NULL) { + default_err = "BIO_new_mem_buf() failed"; + goto failed; + } + + store = SSL_CTX_get_cert_store(ssl_ctx); + if (store == NULL) { + + store = X509_STORE_new(); + if (store == NULL) { + default_err = "X509_STORE_new() failed"; + goto failed; + } + + SSL_CTX_set_cert_store(ssl_ctx, store); + } + + for (;;) { + x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL); + if (x509 == NULL) { + n = ERR_peek_last_error(); + + if (ERR_GET_LIB(n) == ERR_LIB_PEM + && ERR_GET_REASON(n) == PEM_R_NO_START_LINE) + { + /* end of file */ + ERR_clear_error(); + break; + } + + default_err = "PEM_read_bio_X509() failed"; + goto failed; + } + + if (!X509_STORE_add_cert(store, x509)) { + X509_free(x509); + default_err = "X509_STORE_add_cert() failed"; + goto failed; + } + + X509_free(x509); + } + + BIO_free_all(bio); + + return NGX_OK; + +failed: + + if(bio != NULL) { + BIO_free_all(bio); + } + + e = ERR_get_error(); + n = ngx_strlen(default_err); + *err_len = ngx_http_lua_ssl_get_error(e, err, *err_len, default_err, n); + + return NGX_ERROR; +} + + +int +ngx_http_lua_ffi_ssl_ctx_set_cert(void *cdata_ctx, void *cdata_cert, + u_char *err, size_t *err_len) +{ + const char *default_err; + +#ifdef LIBRESSL_VERSION_NUMBER + + default_err = "LibreSSL not supported"; + goto failed; + +#else + +# if OPENSSL_VERSION_NUMBER < 0x1000205fL + + default_err = "at least OpenSSL 1.0.2e required but found " + OPENSSL_VERSION_TEXT; + goto failed; + +# else + + X509 *x509 = NULL; + SSL_CTX *ssl_ctx = cdata_ctx; + STACK_OF(X509) *cert = cdata_cert; + +# ifdef OPENSSL_IS_BORINGSSL + size_t i; +# else + int i; +# endif + int num; + size_t n; + u_long e; + + num = sk_X509_num(cert); + if (num < 1) { + default_err = "sk_X509_num() failed"; + goto failed; + } + + x509 = sk_X509_value(cert, 0); + if (x509 == NULL) { + default_err = "sk_X509_value() failed"; + goto failed; + } + + if (SSL_CTX_use_certificate(ssl_ctx, x509) == 0) { + default_err = "SSL_CTX_use_certificate() failed"; + goto failed; + } + + /* read rest of the chain */ + + for (i = 1; i < num; i++) { + + x509 = sk_X509_value(cert, i); + if (x509 == NULL) { + default_err = "sk_X509_value() failed"; + goto failed; + } + + if (SSL_CTX_add1_chain_cert(ssl_ctx, x509) == 0) { + default_err = "SSL_add1_chain_cert() failed"; + goto failed; + } + } + + return NGX_OK; + +# endif /* OPENSSL_VERSION_NUMBER < 0x1000205fL */ +#endif + +failed: + + e = ERR_get_error(); + n = ngx_strlen(default_err); + *err_len = ngx_http_lua_ssl_get_error(e, err, *err_len, default_err, n); + + return NGX_ERROR; +} + + +int +ngx_http_lua_ffi_ssl_ctx_set_priv_key(void *cdata_ctx, void *cdata_key, + u_char *err, size_t *err_len) +{ + SSL_CTX *ssl_ctx = cdata_ctx; + EVP_PKEY *key = cdata_key; + + size_t n; + u_long e; + const char *default_err; + + if (SSL_CTX_use_PrivateKey(ssl_ctx, key) == 0) { + default_err = "SSL_CTX_use_PrivateKey() failed"; + goto failed; + } + + return NGX_OK; + +failed: + + e = ERR_get_error(); + n = ngx_strlen(default_err); + *err_len = ngx_http_lua_ssl_get_error(e, err, *err_len, default_err, n); + + return NGX_ERROR; +} + + +int +ngx_http_lua_ffi_ssl_ctx_set_ciphers(void *cdata_ctx, const char *cipher, + unsigned char *err, size_t *err_len) +{ + SSL_CTX *ssl_ctx = cdata_ctx; + + size_t n; + u_long e; + const char *default_err; + + if (!SSL_CTX_set_cipher_list(ssl_ctx, cipher)) { + default_err = "SSL_CTX_set_cipher_list() failed"; + goto failed; + } + + return NGX_OK; + +failed: + + e = ERR_get_error(); + n = ngx_strlen(default_err); + *err_len = ngx_http_lua_ssl_get_error(e, err, *err_len, default_err, n); + + return NGX_ERROR; +} + + +int +ngx_http_lua_ffi_ssl_ctx_set_crl(void *cdata_ctx, const u_char *crl, + size_t size, unsigned char *err, size_t *err_len) +{ + BIO *bio = NULL; + SSL_CTX *ssl_ctx = cdata_ctx; + + size_t n; + u_long e; + X509_CRL *x509_crl; + X509_STORE *x509_store; + const char *default_err; + + x509_store = SSL_CTX_get_cert_store(ssl_ctx); + if (x509_store == NULL) { + default_err = "ca cert store is empty"; + goto failed; + } + + bio = BIO_new_mem_buf(crl, size); + if (bio == NULL) { + default_err = "BIO_new_mem_buf() failed"; + goto failed; + } + + for (;;) { + x509_crl = PEM_read_bio_X509_CRL(bio, NULL, NULL, NULL); + if (x509_crl == NULL) { + n = ERR_peek_last_error(); + + if (ERR_GET_LIB(n) == ERR_LIB_PEM + && ERR_GET_REASON(n) == PEM_R_NO_START_LINE) + { + ERR_clear_error(); + break; + } + + default_err = "PEM_read_bio_X509_CRL() failed"; + goto failed; + } + + if (!X509_STORE_add_crl(x509_store, x509_crl)) { + X509_CRL_free(x509_crl); + default_err = "X509_STORE_add_crl() failed"; + goto failed; + } + + X509_CRL_free(x509_crl); + } + + X509_STORE_set_flags(x509_store, + X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL); + + BIO_free_all(bio); + + return NGX_OK; + +failed: + + if (bio != NULL) { + BIO_free_all(bio); + } + + e = ERR_get_error(); + n = ngx_strlen(default_err); + *err_len = ngx_http_lua_ssl_get_error(e, err, *err_len, default_err, n); + + return NGX_ERROR; +} + + +void +ngx_http_lua_ffi_ssl_ctx_free(void *cdata) +{ + SSL_CTX *ssl_ctx = cdata; + + X509_STORE *x509_store; + + x509_store = SSL_CTX_get_cert_store(ssl_ctx); + if (x509_store != NULL) { + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, + "lua ssl ctx x509 store reference: %p:%d", x509_store, + x509_store->references); + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, + 0, "lua ssl ctx free: %p:%d", ssl_ctx, ssl_ctx->references); + + SSL_CTX_free(ssl_ctx); +} + + +X509_STORE * +ngx_http_lua_ffi_ssl_x509_store_init(unsigned char *err, size_t *err_len) +{ + size_t n; + u_long e; + X509_STORE *x509_store; + const char *default_err; + + x509_store = X509_STORE_new(); + if (x509_store == NULL) { + default_err = "X509_STORE_new() failed"; + goto failed; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, + "lua ssl x509 store init: %p:%d", x509_store, + x509_store->references); + + return x509_store; + +failed: + + e = ERR_get_error(); + n = ngx_strlen(default_err); + *err_len = ngx_http_lua_ssl_get_error(e, err, *err_len, default_err, n); + + return NULL; +} + + +int +ngx_http_lua_ffi_ssl_x509_store_add_cert(void *cdata_store, const u_char *cert, + size_t size, unsigned char *err, size_t *err_len) +{ + BIO *bio = NULL; + X509_STORE *x509_store = cdata_store; + + X509 *x509; + size_t n; + u_long e; + const char *default_err; + + bio = BIO_new_mem_buf(cert, size); + if (bio == NULL) { + default_err = "BIO_new_mem_buf() failed"; + goto failed; + } + + for (;;) { + x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL); + if (x509 == NULL) { + n = ERR_peek_last_error(); + + if (ERR_GET_LIB(n) == ERR_LIB_PEM + && ERR_GET_REASON(n) == PEM_R_NO_START_LINE) + { + /* end of file */ + ERR_clear_error(); + break; + } + + default_err = "PEM_read_bio_X509() failed"; + goto failed; + } + + if (!X509_STORE_add_cert(x509_store, x509)) { + X509_free(x509); + default_err = "X509_STORE_add_cert() failed"; + goto failed; + } + + X509_free(x509); + } + + BIO_free_all(bio); + + return NGX_OK; + +failed: + + if(bio != NULL) { + BIO_free_all(bio); + } + + e = ERR_get_error(); + n = ngx_strlen(default_err); + *err_len = ngx_http_lua_ssl_get_error(e, err, *err_len, default_err, n); + + return NGX_ERROR; +} + + +void +ngx_http_lua_ffi_ssl_x509_store_free(void *cdata_store) +{ + X509_STORE *x509_store = cdata_store; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, + "lua ssl x509 store free: %p:%d", x509_store, + x509_store->references); + + X509_STORE_free(x509_store); +} + + +#endif /* NGX_LUA_NO_FFI_API */ + + #endif /* NGX_HTTP_SSL */ diff --git a/t/151-ssl-ctx.t b/t/151-ssl-ctx.t new file mode 100644 index 0000000000..2f98de75fd --- /dev/null +++ b/t/151-ssl-ctx.t @@ -0,0 +1,39 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: + +use Test::Nginx::Socket::Lua; + +repeat_each(3); + +plan tests => repeat_each() * (blocks()); + +$ENV{TEST_NGINX_HTML_DIR} ||= html_dir(); + +log_level 'debug'; + +no_long_string(); + +run_tests(); + +__DATA__ + +=== TEST 1: ssl ctx init and free +--- log_level: debug +--- http_config + lua_package_path "../lua-resty-core/lib/?.lua;;"; +--- config + location /t { + content_by_lua_block { + local ssl = require "ngx.ssl" + local ssl_ctx, err = ssl.create_ctx({}) + ssl_ctx = nil + collectgarbage("collect") + } + } +--- request +GET /t +--- ignore_response +--- grep_error_log eval: qr/lua ssl ctx (?:init|free): [0-9A-F]+:\d+/ +--- grep_error_log_out eval +qr/^lua ssl ctx init: ([0-9A-F]+):1 +lua ssl ctx free: ([0-9A-F]+):1 +$/