diff --git a/src/ngx_stream_lua_ssl_certby.c b/src/ngx_stream_lua_ssl_certby.c index 13fb3663..7b374ce2 100644 --- a/src/ngx_stream_lua_ssl_certby.c +++ b/src/ngx_stream_lua_ssl_certby.c @@ -1317,4 +1317,142 @@ ngx_stream_lua_ffi_set_priv_key(ngx_stream_lua_request_t *r, } +static int +ngx_stream_lua_ssl_verify_callback(int ok, X509_STORE_CTX *x509_store) +{ + /* + * we never terminate handshake here and user can later use + * $ssl_client_verify to check verification result. + * + * this is consistent with Nginx behavior. + */ + return 1; +} + + +int +ngx_stream_lua_ffi_ssl_verify_client(ngx_stream_lua_request_t *r, + void *ca_certs, int depth, char **err) +{ + ngx_stream_lua_ctx_t *ctx; + ngx_ssl_conn_t *ssl_conn; + ngx_stream_ssl_conf_t *sscf; + STACK_OF(X509) *chain = ca_certs; + STACK_OF(X509_NAME) *name_chain = NULL; + X509 *x509 = NULL; + X509_NAME *subject = NULL; + X509_STORE *ca_store = NULL; +#ifdef OPENSSL_IS_BORINGSSL + size_t i; +#else + int i; +#endif + + ctx = ngx_stream_get_module_ctx(r->session, ngx_stream_lua_module); + if (ctx == NULL) { + *err = "no request ctx found"; + return NGX_ERROR; + } + + if (!(ctx->context & NGX_STREAM_LUA_CONTEXT_SSL_CERT)) { + *err = "API disabled in the current context"; + return NGX_ERROR; + } + + if (r->connection == NULL || r->connection->ssl == NULL) { + *err = "bad request"; + return NGX_ERROR; + } + + ssl_conn = r->connection->ssl->connection; + if (ssl_conn == NULL) { + *err = "bad ssl conn"; + return NGX_ERROR; + } + + /* enable verify */ + + SSL_set_verify(ssl_conn, SSL_VERIFY_PEER, + ngx_stream_lua_ssl_verify_callback); + + /* set depth */ + + if (depth < 0) { + sscf = ngx_stream_get_module_srv_conf(r->session, + ngx_stream_ssl_module); + if (sscf != NULL) { + depth = sscf->verify_depth; + + } else { + /* same as the default value of ssl_verify_depth */ + depth = 1; + } + } + + SSL_set_verify_depth(ssl_conn, depth); + + /* set CA chain */ + + if (chain != NULL) { + ca_store = X509_STORE_new(); + if (ca_store == NULL) { + *err = "X509_STORE_new() failed"; + return NGX_ERROR; + } + + /* construct name chain */ + + name_chain = sk_X509_NAME_new_null(); + if (name_chain == NULL) { + *err = "sk_X509_NAME_new_null() failed"; + goto failed; + } + + for (i = 0; i < sk_X509_num(chain); i++) { + x509 = sk_X509_value(chain, i); + if (x509 == NULL) { + *err = "sk_X509_value() failed"; + goto failed; + } + + /* add subject to name chain, which will be sent to client */ + subject = X509_NAME_dup(X509_get_subject_name(x509)); + if (subject == NULL) { + *err = "X509_get_subject_name() failed"; + goto failed; + } + + if (!sk_X509_NAME_push(name_chain, subject)) { + *err = "sk_X509_NAME_push() failed"; + X509_NAME_free(subject); + goto failed; + } + + /* add to trusted CA store */ + if (X509_STORE_add_cert(ca_store, x509) == 0) { + *err = "X509_STORE_add_cert() failed"; + goto failed; + } + } + + if (SSL_set0_verify_cert_store(ssl_conn, ca_store) == 0) { + *err = "SSL_set0_verify_cert_store() failed"; + goto failed; + } + + SSL_set_client_CA_list(ssl_conn, name_chain); + } + + return NGX_OK; + +failed: + + sk_X509_NAME_free(name_chain); + + X509_STORE_free(ca_store); + + return NGX_ERROR; +} + + #endif /* NGX_STREAM_SSL */ diff --git a/t/140-ssl-c-api.t b/t/140-ssl-c-api.t index 88ab42df..5ab0dccd 100644 --- a/t/140-ssl-c-api.t +++ b/t/140-ssl-c-api.t @@ -61,6 +61,9 @@ ffi.cdef[[ void ngx_stream_lua_ffi_free_priv_key(void *cdata); int ngx_stream_lua_ffi_ssl_clear_certs(void *r, char **err); + + int ngx_stream_lua_ffi_ssl_verify_client(void *r, void *cdata, int depth, char **err); + ]] _EOC_ } @@ -675,3 +678,189 @@ lua ssl server name: "test.com" --- no_error_log [error] [alert] + + + +=== TEST 6: verify client with CA certificates +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/test2.crt; + ssl_certificate_key ../../cert/test2.key; + + ssl_certificate_by_lua_block { + collectgarbage() + + local ffi = require "ffi" + require "defines" + + local errmsg = ffi.new("char *[1]") + + local r = require "resty.core.base" .get_request() + if not r then + ngx.log(ngx.ERR, "no request found") + return + end + + local f = assert(io.open("t/cert/test.crt", "rb")) + local cert_data = f:read("*all") + f:close() + + local cert = ffi.C.ngx_stream_lua_ffi_parse_pem_cert(cert_data, #cert_data, errmsg) + if not cert then + ngx.log(ngx.ERR, "failed to parse PEM cert: ", + ffi.string(errmsg[0])) + return + end + + local rc = ffi.C.ngx_stream_lua_ffi_ssl_verify_client(r, cert, -1, errmsg) + if rc ~= 0 then + ngx.log(ngx.ERR, "failed to set cdata cert: ", + ffi.string(errmsg[0])) + return + end + + ffi.C.ngx_stream_lua_ffi_free_cert(cert) + } + + content_by_lua_block { + print('client certificate subject: ', ngx.var.ssl_client_s_dn) + ngx.say(ngx.var.ssl_client_verify) + } + } +--- stream_server_config + proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl on; + proxy_ssl_certificate ../../cert/test.crt; + proxy_ssl_certificate_key ../../cert/test.key; + proxy_ssl_session_reuse off; + +--- stream_response +SUCCESS + +--- error_log +client certificate subject: emailAddress=agentzh@gmail.com,CN=test.com + +--- no_error_log +[error] +[alert] + + + +=== TEST 7: verify client without CA certificates +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/test2.crt; + ssl_certificate_key ../../cert/test2.key; + + ssl_certificate_by_lua_block { + collectgarbage() + + local ffi = require "ffi" + require "defines" + + local errmsg = ffi.new("char *[1]") + + local r = require "resty.core.base" .get_request() + if not r then + ngx.log(ngx.ERR, "no request found") + return + end + + local rc = ffi.C.ngx_stream_lua_ffi_ssl_verify_client(r, nil, -1, errmsg) + if rc ~= 0 then + ngx.log(ngx.ERR, "failed to set cdata cert: ", + ffi.string(errmsg[0])) + return + end + } + + content_by_lua_block { + print('client certificate subject: ', ngx.var.ssl_client_s_dn) + ngx.say(ngx.var.ssl_client_verify) + } + } +--- stream_server_config + proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl on; + proxy_ssl_certificate ../../cert/test.crt; + proxy_ssl_certificate_key ../../cert/test.key; + proxy_ssl_session_reuse off; + +--- stream_response +FAILED:self signed certificate + +--- error_log +client certificate subject: emailAddress=agentzh@gmail.com,CN=test.com + +--- no_error_log +[error] +[alert] + + + +=== TEST 8: verify client but client provides no certificate +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/test2.crt; + ssl_certificate_key ../../cert/test2.key; + + ssl_certificate_by_lua_block { + collectgarbage() + + local ffi = require "ffi" + require "defines" + + local errmsg = ffi.new("char *[1]") + + local r = require "resty.core.base" .get_request() + if not r then + ngx.log(ngx.ERR, "no request found") + return + end + + local f = assert(io.open("t/cert/test.crt", "rb")) + local cert_data = f:read("*all") + f:close() + + local cert = ffi.C.ngx_stream_lua_ffi_parse_pem_cert(cert_data, #cert_data, errmsg) + if not cert then + ngx.log(ngx.ERR, "failed to parse PEM cert: ", + ffi.string(errmsg[0])) + return + end + + local rc = ffi.C.ngx_stream_lua_ffi_ssl_verify_client(r, cert, 1, errmsg) + if rc ~= 0 then + ngx.log(ngx.ERR, "failed to set cdata cert: ", + ffi.string(errmsg[0])) + return + end + + ffi.C.ngx_stream_lua_ffi_free_cert(cert) + } + + content_by_lua_block { + print('client certificate subject: ', ngx.var.ssl_client_s_dn) + ngx.say(ngx.var.ssl_client_verify) + } + } +--- stream_server_config + proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl on; + proxy_ssl_session_reuse off; + +--- stream_response +NONE + +--- error_log +client certificate subject: nil + +--- no_error_log +[error] +[alert]