Skip to content

Commit 5d8f675

Browse files
authored
feature: add FFI interface to verify SSL client certificate (#190)
1 parent 5a8bc29 commit 5d8f675

File tree

2 files changed

+327
-0
lines changed

2 files changed

+327
-0
lines changed

src/ngx_stream_lua_ssl_certby.c

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1317,4 +1317,142 @@ ngx_stream_lua_ffi_set_priv_key(ngx_stream_lua_request_t *r,
13171317
}
13181318

13191319

1320+
static int
1321+
ngx_stream_lua_ssl_verify_callback(int ok, X509_STORE_CTX *x509_store)
1322+
{
1323+
/*
1324+
* we never terminate handshake here and user can later use
1325+
* $ssl_client_verify to check verification result.
1326+
*
1327+
* this is consistent with Nginx behavior.
1328+
*/
1329+
return 1;
1330+
}
1331+
1332+
1333+
int
1334+
ngx_stream_lua_ffi_ssl_verify_client(ngx_stream_lua_request_t *r,
1335+
void *ca_certs, int depth, char **err)
1336+
{
1337+
ngx_stream_lua_ctx_t *ctx;
1338+
ngx_ssl_conn_t *ssl_conn;
1339+
ngx_stream_ssl_conf_t *sscf;
1340+
STACK_OF(X509) *chain = ca_certs;
1341+
STACK_OF(X509_NAME) *name_chain = NULL;
1342+
X509 *x509 = NULL;
1343+
X509_NAME *subject = NULL;
1344+
X509_STORE *ca_store = NULL;
1345+
#ifdef OPENSSL_IS_BORINGSSL
1346+
size_t i;
1347+
#else
1348+
int i;
1349+
#endif
1350+
1351+
ctx = ngx_stream_get_module_ctx(r->session, ngx_stream_lua_module);
1352+
if (ctx == NULL) {
1353+
*err = "no request ctx found";
1354+
return NGX_ERROR;
1355+
}
1356+
1357+
if (!(ctx->context & NGX_STREAM_LUA_CONTEXT_SSL_CERT)) {
1358+
*err = "API disabled in the current context";
1359+
return NGX_ERROR;
1360+
}
1361+
1362+
if (r->connection == NULL || r->connection->ssl == NULL) {
1363+
*err = "bad request";
1364+
return NGX_ERROR;
1365+
}
1366+
1367+
ssl_conn = r->connection->ssl->connection;
1368+
if (ssl_conn == NULL) {
1369+
*err = "bad ssl conn";
1370+
return NGX_ERROR;
1371+
}
1372+
1373+
/* enable verify */
1374+
1375+
SSL_set_verify(ssl_conn, SSL_VERIFY_PEER,
1376+
ngx_stream_lua_ssl_verify_callback);
1377+
1378+
/* set depth */
1379+
1380+
if (depth < 0) {
1381+
sscf = ngx_stream_get_module_srv_conf(r->session,
1382+
ngx_stream_ssl_module);
1383+
if (sscf != NULL) {
1384+
depth = sscf->verify_depth;
1385+
1386+
} else {
1387+
/* same as the default value of ssl_verify_depth */
1388+
depth = 1;
1389+
}
1390+
}
1391+
1392+
SSL_set_verify_depth(ssl_conn, depth);
1393+
1394+
/* set CA chain */
1395+
1396+
if (chain != NULL) {
1397+
ca_store = X509_STORE_new();
1398+
if (ca_store == NULL) {
1399+
*err = "X509_STORE_new() failed";
1400+
return NGX_ERROR;
1401+
}
1402+
1403+
/* construct name chain */
1404+
1405+
name_chain = sk_X509_NAME_new_null();
1406+
if (name_chain == NULL) {
1407+
*err = "sk_X509_NAME_new_null() failed";
1408+
goto failed;
1409+
}
1410+
1411+
for (i = 0; i < sk_X509_num(chain); i++) {
1412+
x509 = sk_X509_value(chain, i);
1413+
if (x509 == NULL) {
1414+
*err = "sk_X509_value() failed";
1415+
goto failed;
1416+
}
1417+
1418+
/* add subject to name chain, which will be sent to client */
1419+
subject = X509_NAME_dup(X509_get_subject_name(x509));
1420+
if (subject == NULL) {
1421+
*err = "X509_get_subject_name() failed";
1422+
goto failed;
1423+
}
1424+
1425+
if (!sk_X509_NAME_push(name_chain, subject)) {
1426+
*err = "sk_X509_NAME_push() failed";
1427+
X509_NAME_free(subject);
1428+
goto failed;
1429+
}
1430+
1431+
/* add to trusted CA store */
1432+
if (X509_STORE_add_cert(ca_store, x509) == 0) {
1433+
*err = "X509_STORE_add_cert() failed";
1434+
goto failed;
1435+
}
1436+
}
1437+
1438+
if (SSL_set0_verify_cert_store(ssl_conn, ca_store) == 0) {
1439+
*err = "SSL_set0_verify_cert_store() failed";
1440+
goto failed;
1441+
}
1442+
1443+
SSL_set_client_CA_list(ssl_conn, name_chain);
1444+
}
1445+
1446+
return NGX_OK;
1447+
1448+
failed:
1449+
1450+
sk_X509_NAME_free(name_chain);
1451+
1452+
X509_STORE_free(ca_store);
1453+
1454+
return NGX_ERROR;
1455+
}
1456+
1457+
13201458
#endif /* NGX_STREAM_SSL */

t/140-ssl-c-api.t

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ ffi.cdef[[
6161
void ngx_stream_lua_ffi_free_priv_key(void *cdata);
6262
6363
int ngx_stream_lua_ffi_ssl_clear_certs(void *r, char **err);
64+
65+
int ngx_stream_lua_ffi_ssl_verify_client(void *r, void *cdata, int depth, char **err);
66+
6467
]]
6568
_EOC_
6669
}
@@ -675,3 +678,189 @@ lua ssl server name: "test.com"
675678
--- no_error_log
676679
[error]
677680
[alert]
681+
682+
683+
684+
=== TEST 6: verify client with CA certificates
685+
--- stream_config
686+
server {
687+
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl;
688+
689+
ssl_certificate ../../cert/test2.crt;
690+
ssl_certificate_key ../../cert/test2.key;
691+
692+
ssl_certificate_by_lua_block {
693+
collectgarbage()
694+
695+
local ffi = require "ffi"
696+
require "defines"
697+
698+
local errmsg = ffi.new("char *[1]")
699+
700+
local r = require "resty.core.base" .get_request()
701+
if not r then
702+
ngx.log(ngx.ERR, "no request found")
703+
return
704+
end
705+
706+
local f = assert(io.open("t/cert/test.crt", "rb"))
707+
local cert_data = f:read("*all")
708+
f:close()
709+
710+
local cert = ffi.C.ngx_stream_lua_ffi_parse_pem_cert(cert_data, #cert_data, errmsg)
711+
if not cert then
712+
ngx.log(ngx.ERR, "failed to parse PEM cert: ",
713+
ffi.string(errmsg[0]))
714+
return
715+
end
716+
717+
local rc = ffi.C.ngx_stream_lua_ffi_ssl_verify_client(r, cert, -1, errmsg)
718+
if rc ~= 0 then
719+
ngx.log(ngx.ERR, "failed to set cdata cert: ",
720+
ffi.string(errmsg[0]))
721+
return
722+
end
723+
724+
ffi.C.ngx_stream_lua_ffi_free_cert(cert)
725+
}
726+
727+
content_by_lua_block {
728+
print('client certificate subject: ', ngx.var.ssl_client_s_dn)
729+
ngx.say(ngx.var.ssl_client_verify)
730+
}
731+
}
732+
--- stream_server_config
733+
proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock;
734+
proxy_ssl on;
735+
proxy_ssl_certificate ../../cert/test.crt;
736+
proxy_ssl_certificate_key ../../cert/test.key;
737+
proxy_ssl_session_reuse off;
738+
739+
--- stream_response
740+
SUCCESS
741+
742+
--- error_log
743+
client certificate subject: [email protected],CN=test.com
744+
745+
--- no_error_log
746+
[error]
747+
[alert]
748+
749+
750+
751+
=== TEST 7: verify client without CA certificates
752+
--- stream_config
753+
server {
754+
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl;
755+
756+
ssl_certificate ../../cert/test2.crt;
757+
ssl_certificate_key ../../cert/test2.key;
758+
759+
ssl_certificate_by_lua_block {
760+
collectgarbage()
761+
762+
local ffi = require "ffi"
763+
require "defines"
764+
765+
local errmsg = ffi.new("char *[1]")
766+
767+
local r = require "resty.core.base" .get_request()
768+
if not r then
769+
ngx.log(ngx.ERR, "no request found")
770+
return
771+
end
772+
773+
local rc = ffi.C.ngx_stream_lua_ffi_ssl_verify_client(r, nil, -1, errmsg)
774+
if rc ~= 0 then
775+
ngx.log(ngx.ERR, "failed to set cdata cert: ",
776+
ffi.string(errmsg[0]))
777+
return
778+
end
779+
}
780+
781+
content_by_lua_block {
782+
print('client certificate subject: ', ngx.var.ssl_client_s_dn)
783+
ngx.say(ngx.var.ssl_client_verify)
784+
}
785+
}
786+
--- stream_server_config
787+
proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock;
788+
proxy_ssl on;
789+
proxy_ssl_certificate ../../cert/test.crt;
790+
proxy_ssl_certificate_key ../../cert/test.key;
791+
proxy_ssl_session_reuse off;
792+
793+
--- stream_response
794+
FAILED:self signed certificate
795+
796+
--- error_log
797+
client certificate subject: [email protected],CN=test.com
798+
799+
--- no_error_log
800+
[error]
801+
[alert]
802+
803+
804+
805+
=== TEST 8: verify client but client provides no certificate
806+
--- stream_config
807+
server {
808+
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl;
809+
810+
ssl_certificate ../../cert/test2.crt;
811+
ssl_certificate_key ../../cert/test2.key;
812+
813+
ssl_certificate_by_lua_block {
814+
collectgarbage()
815+
816+
local ffi = require "ffi"
817+
require "defines"
818+
819+
local errmsg = ffi.new("char *[1]")
820+
821+
local r = require "resty.core.base" .get_request()
822+
if not r then
823+
ngx.log(ngx.ERR, "no request found")
824+
return
825+
end
826+
827+
local f = assert(io.open("t/cert/test.crt", "rb"))
828+
local cert_data = f:read("*all")
829+
f:close()
830+
831+
local cert = ffi.C.ngx_stream_lua_ffi_parse_pem_cert(cert_data, #cert_data, errmsg)
832+
if not cert then
833+
ngx.log(ngx.ERR, "failed to parse PEM cert: ",
834+
ffi.string(errmsg[0]))
835+
return
836+
end
837+
838+
local rc = ffi.C.ngx_stream_lua_ffi_ssl_verify_client(r, cert, 1, errmsg)
839+
if rc ~= 0 then
840+
ngx.log(ngx.ERR, "failed to set cdata cert: ",
841+
ffi.string(errmsg[0]))
842+
return
843+
end
844+
845+
ffi.C.ngx_stream_lua_ffi_free_cert(cert)
846+
}
847+
848+
content_by_lua_block {
849+
print('client certificate subject: ', ngx.var.ssl_client_s_dn)
850+
ngx.say(ngx.var.ssl_client_verify)
851+
}
852+
}
853+
--- stream_server_config
854+
proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock;
855+
proxy_ssl on;
856+
proxy_ssl_session_reuse off;
857+
858+
--- stream_response
859+
NONE
860+
861+
--- error_log
862+
client certificate subject: nil
863+
864+
--- no_error_log
865+
[error]
866+
[alert]

0 commit comments

Comments
 (0)