Skip to content

Commit c0e460a

Browse files
authored
feature: add FFI interface to verify SSL client certificate (#1666)
1 parent fd25474 commit c0e460a

File tree

2 files changed

+350
-0
lines changed

2 files changed

+350
-0
lines changed

src/ngx_http_lua_ssl_certby.c

+136
Original file line numberDiff line numberDiff line change
@@ -1294,4 +1294,140 @@ ngx_http_lua_ffi_set_priv_key(ngx_http_request_t *r,
12941294
}
12951295

12961296

1297+
static int
1298+
ngx_http_lua_ssl_verify_callback(int ok, X509_STORE_CTX *x509_store)
1299+
{
1300+
/*
1301+
* we never terminate handshake here and user can later use
1302+
* $ssl_client_verify to check verification result.
1303+
*
1304+
* this is consistent with Nginx behavior.
1305+
*/
1306+
return 1;
1307+
}
1308+
1309+
1310+
int
1311+
ngx_http_lua_ffi_ssl_verify_client(ngx_http_request_t *r, void *ca_certs,
1312+
int depth, char **err)
1313+
{
1314+
ngx_http_lua_ctx_t *ctx;
1315+
ngx_ssl_conn_t *ssl_conn;
1316+
ngx_http_ssl_srv_conf_t *sscf;
1317+
STACK_OF(X509) *chain = ca_certs;
1318+
STACK_OF(X509_NAME) *name_chain = NULL;
1319+
X509 *x509 = NULL;
1320+
X509_NAME *subject = NULL;
1321+
X509_STORE *ca_store = NULL;
1322+
#ifdef OPENSSL_IS_BORINGSSL
1323+
size_t i;
1324+
#else
1325+
int i;
1326+
#endif
1327+
1328+
ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module);
1329+
if (ctx == NULL) {
1330+
*err = "no request ctx found";
1331+
return NGX_ERROR;
1332+
}
1333+
1334+
if (!(ctx->context & NGX_HTTP_LUA_CONTEXT_SSL_CERT)) {
1335+
*err = "API disabled in the current context";
1336+
return NGX_ERROR;
1337+
}
1338+
1339+
if (r->connection == NULL || r->connection->ssl == NULL) {
1340+
*err = "bad request";
1341+
return NGX_ERROR;
1342+
}
1343+
1344+
ssl_conn = r->connection->ssl->connection;
1345+
if (ssl_conn == NULL) {
1346+
*err = "bad ssl conn";
1347+
return NGX_ERROR;
1348+
}
1349+
1350+
/* enable verify */
1351+
1352+
SSL_set_verify(ssl_conn, SSL_VERIFY_PEER, ngx_http_lua_ssl_verify_callback);
1353+
1354+
/* set depth */
1355+
1356+
if (depth < 0) {
1357+
sscf = ngx_http_get_module_srv_conf(r, ngx_http_ssl_module);
1358+
if (sscf != NULL) {
1359+
depth = sscf->verify_depth;
1360+
1361+
} else {
1362+
/* same as the default value of ssl_verify_depth */
1363+
depth = 1;
1364+
}
1365+
}
1366+
1367+
SSL_set_verify_depth(ssl_conn, depth);
1368+
1369+
/* set CA chain */
1370+
1371+
if (chain != NULL) {
1372+
ca_store = X509_STORE_new();
1373+
if (ca_store == NULL) {
1374+
*err = "X509_STORE_new() failed";
1375+
return NGX_ERROR;
1376+
}
1377+
1378+
/* construct name chain */
1379+
1380+
name_chain = sk_X509_NAME_new_null();
1381+
if (name_chain == NULL) {
1382+
*err = "sk_X509_NAME_new_null() failed";
1383+
goto failed;
1384+
}
1385+
1386+
for (i = 0; i < sk_X509_num(chain); i++) {
1387+
x509 = sk_X509_value(chain, i);
1388+
if (x509 == NULL) {
1389+
*err = "sk_X509_value() failed";
1390+
goto failed;
1391+
}
1392+
1393+
/* add subject to name chain, which will be sent to client */
1394+
subject = X509_NAME_dup(X509_get_subject_name(x509));
1395+
if (subject == NULL) {
1396+
*err = "X509_get_subject_name() failed";
1397+
goto failed;
1398+
}
1399+
1400+
if (!sk_X509_NAME_push(name_chain, subject)) {
1401+
*err = "sk_X509_NAME_push() failed";
1402+
X509_NAME_free(subject);
1403+
goto failed;
1404+
}
1405+
1406+
/* add to trusted CA store */
1407+
if (X509_STORE_add_cert(ca_store, x509) == 0) {
1408+
*err = "X509_STORE_add_cert() failed";
1409+
goto failed;
1410+
}
1411+
}
1412+
1413+
if (SSL_set0_verify_cert_store(ssl_conn, ca_store) == 0) {
1414+
*err = "SSL_set0_verify_cert_store() failed";
1415+
goto failed;
1416+
}
1417+
1418+
SSL_set_client_CA_list(ssl_conn, name_chain);
1419+
}
1420+
1421+
return NGX_OK;
1422+
1423+
failed:
1424+
1425+
sk_X509_NAME_free(name_chain);
1426+
1427+
X509_STORE_free(ca_store);
1428+
1429+
return NGX_ERROR;
1430+
}
1431+
1432+
12971433
#endif /* NGX_HTTP_SSL */

t/140-ssl-c-api.t

+214
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ ffi.cdef[[
6363
void ngx_http_lua_ffi_free_priv_key(void *cdata);
6464
6565
int ngx_http_lua_ffi_ssl_clear_certs(void *r, char **err);
66+
67+
int ngx_http_lua_ffi_ssl_verify_client(void *r, void *cdata,
68+
int depth, char **err);
69+
6670
]]
6771
_EOC_
6872
}
@@ -812,3 +816,213 @@ lua ssl server name: "test.com"
812816
--- no_error_log
813817
[error]
814818
[alert]
819+
820+
821+
822+
=== TEST 6: verify client with CA certificates
823+
--- http_config
824+
server {
825+
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl;
826+
server_name test.com;
827+
828+
ssl_certificate_by_lua_block {
829+
collectgarbage()
830+
831+
require "defines"
832+
local ffi = require "ffi"
833+
834+
local errmsg = ffi.new("char *[1]")
835+
836+
local r = require "resty.core.base" .get_request()
837+
if r == nil then
838+
ngx.log(ngx.ERR, "no request found")
839+
return
840+
end
841+
842+
local f = assert(io.open("t/cert/test.crt", "rb"))
843+
local cert_data = f:read("*all")
844+
f:close()
845+
846+
local cert = ffi.C.ngx_http_lua_ffi_parse_pem_cert(cert_data, #cert_data, errmsg)
847+
if not cert then
848+
ngx.log(ngx.ERR, "failed to parse PEM cert: ",
849+
ffi.string(errmsg[0]))
850+
return
851+
end
852+
853+
local rc = ffi.C.ngx_http_lua_ffi_ssl_verify_client(r, cert, 1, errmsg)
854+
if rc ~= 0 then
855+
ngx.log(ngx.ERR, "failed to verify client: ",
856+
ffi.string(errmsg[0]))
857+
return
858+
end
859+
860+
ffi.C.ngx_http_lua_ffi_free_cert(cert)
861+
}
862+
863+
ssl_certificate ../../cert/test2.crt;
864+
ssl_certificate_key ../../cert/test2.key;
865+
866+
location / {
867+
default_type 'text/plain';
868+
content_by_lua_block {
869+
print('client certificate subject: ', ngx.var.ssl_client_s_dn)
870+
ngx.say(ngx.var.ssl_client_verify)
871+
}
872+
more_clear_headers Date;
873+
}
874+
}
875+
--- config
876+
location /t {
877+
proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock;
878+
proxy_ssl_certificate ../../cert/test.crt;
879+
proxy_ssl_certificate_key ../../cert/test.key;
880+
proxy_ssl_session_reuse off;
881+
}
882+
883+
--- request
884+
GET /t
885+
--- response_body
886+
SUCCESS
887+
888+
--- error_log
889+
client certificate subject: [email protected],CN=test.com
890+
891+
--- no_error_log
892+
[error]
893+
[alert]
894+
895+
896+
897+
=== TEST 7: verify client without CA certificates
898+
--- http_config
899+
server {
900+
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl;
901+
server_name test.com;
902+
903+
ssl_certificate_by_lua_block {
904+
collectgarbage()
905+
906+
require "defines"
907+
local ffi = require "ffi"
908+
909+
local errmsg = ffi.new("char *[1]")
910+
911+
local r = require "resty.core.base" .get_request()
912+
if r == nil then
913+
ngx.log(ngx.ERR, "no request found")
914+
return
915+
end
916+
917+
local rc = ffi.C.ngx_http_lua_ffi_ssl_verify_client(r, nil, -1, errmsg)
918+
if rc ~= 0 then
919+
ngx.log(ngx.ERR, "failed to verify client: ",
920+
ffi.string(errmsg[0]))
921+
return
922+
end
923+
}
924+
925+
ssl_certificate ../../cert/test2.crt;
926+
ssl_certificate_key ../../cert/test2.key;
927+
928+
location / {
929+
default_type 'text/plain';
930+
content_by_lua_block {
931+
print('client certificate subject: ', ngx.var.ssl_client_s_dn)
932+
ngx.say(ngx.var.ssl_client_verify)
933+
}
934+
more_clear_headers Date;
935+
}
936+
}
937+
--- config
938+
location /t {
939+
proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock;
940+
proxy_ssl_certificate ../../cert/test.crt;
941+
proxy_ssl_certificate_key ../../cert/test.key;
942+
proxy_ssl_session_reuse off;
943+
}
944+
945+
--- request
946+
GET /t
947+
--- response_body
948+
FAILED:self signed certificate
949+
950+
--- error_log
951+
client certificate subject: [email protected],CN=test.com
952+
953+
--- no_error_log
954+
[error]
955+
[alert]
956+
957+
958+
959+
=== TEST 8: verify client but client provides no certificate
960+
--- http_config
961+
server {
962+
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl;
963+
server_name test.com;
964+
965+
ssl_certificate_by_lua_block {
966+
collectgarbage()
967+
968+
require "defines"
969+
local ffi = require "ffi"
970+
971+
local errmsg = ffi.new("char *[1]")
972+
973+
local r = require "resty.core.base" .get_request()
974+
if r == nil then
975+
ngx.log(ngx.ERR, "no request found")
976+
return
977+
end
978+
979+
local f = assert(io.open("t/cert/test.crt", "rb"))
980+
local cert_data = f:read("*all")
981+
f:close()
982+
983+
local cert = ffi.C.ngx_http_lua_ffi_parse_pem_cert(cert_data, #cert_data, errmsg)
984+
if not cert then
985+
ngx.log(ngx.ERR, "failed to parse PEM cert: ",
986+
ffi.string(errmsg[0]))
987+
return
988+
end
989+
990+
local rc = ffi.C.ngx_http_lua_ffi_ssl_verify_client(r, cert, 1, errmsg)
991+
if rc ~= 0 then
992+
ngx.log(ngx.ERR, "failed to verify client: ",
993+
ffi.string(errmsg[0]))
994+
return
995+
end
996+
997+
ffi.C.ngx_http_lua_ffi_free_cert(cert)
998+
}
999+
1000+
ssl_certificate ../../cert/test2.crt;
1001+
ssl_certificate_key ../../cert/test2.key;
1002+
1003+
location / {
1004+
default_type 'text/plain';
1005+
content_by_lua_block {
1006+
print('client certificate subject: ', ngx.var.ssl_client_s_dn)
1007+
ngx.say(ngx.var.ssl_client_verify)
1008+
}
1009+
more_clear_headers Date;
1010+
}
1011+
}
1012+
--- config
1013+
location /t {
1014+
proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock;
1015+
proxy_ssl_session_reuse off;
1016+
}
1017+
1018+
--- request
1019+
GET /t
1020+
--- response_body
1021+
NONE
1022+
1023+
--- error_log
1024+
client certificate subject: nil
1025+
1026+
--- no_error_log
1027+
[error]
1028+
[alert]

0 commit comments

Comments
 (0)