Skip to content

Commit 594e7d8

Browse files
committed
[tls_validation] Add support for OCSP
1 parent 12c8361 commit 594e7d8

File tree

4 files changed

+185
-0
lines changed

4 files changed

+185
-0
lines changed

gateway/src/apicast/policy/tls_validation/apicast-policy.json

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,12 @@
4848
"title": "Certificate Revocation Check type",
4949
"type": "string",
5050
"oneOf": [
51+
{
52+
"enum": [
53+
"ocsp"
54+
],
55+
"title": "Enables OCSP validation of the client certificate."
56+
},
5157
{
5258
"enum": [
5359
"crl"
@@ -89,6 +95,45 @@
8995
"$ref": "#/definitions/store"
9096
}
9197
}
98+
},
99+
{
100+
"properties": {
101+
"revocation_check_type": {
102+
"enum": [
103+
"ocsp"
104+
]
105+
},
106+
"revocation_check_mode": {
107+
"title": "Certificate Mode",
108+
"description": "Certificate revocation check mode",
109+
"type": "string",
110+
"oneOf": [
111+
{
112+
"enum": [
113+
"ignore_error"
114+
],
115+
"title": "Ignore Network Error: respects the revocation status when either OCSP or CRL URL is set, and doesn’t fail on network issues"
116+
},
117+
{
118+
"enum": [
119+
"strict"
120+
],
121+
"title": "Strict: The certificate is valid only when it’s able to verify the revocation status."
122+
}
123+
],
124+
"default": "strict"
125+
},
126+
"ocsp_responder_url": {
127+
"title": "OCSP Responder URL ",
128+
"description": "Overrides the URL of the OCSP responder specified in the “Authority Information Access” certificate extension for validation of client certificates. ",
129+
"type": "string"
130+
},
131+
"cache_timeout": {
132+
"title": " Cache timeout",
133+
"description": "The length of time in milliseconds between refreshes of the revocation check status cache.",
134+
"type": "integer"
135+
}
136+
}
92137
}
93138
]
94139
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
local http_ng = require 'resty.http_ng'
2+
local user_agent = require 'apicast.user_agent'
3+
local resty_env = require('resty.env')
4+
local tls = require "resty.tls"
5+
local ngx_ssl = require "ngx.ssl"
6+
local ocsp = require("ngx.ocsp")
7+
8+
local _M = {}
9+
10+
local function do_ocsp_request(ocsp_url, ocsp_request)
11+
-- TODO: set default timeout
12+
local http_client = http_ng.new{
13+
options = {
14+
headers = {
15+
['User-Agent'] = user_agent()
16+
},
17+
ssl = { verify = resty_env.enabled('OPENSSL_VERIFY') }
18+
}
19+
}
20+
local res, err = http_client.post{
21+
ocsp_url,
22+
ocsp_request,
23+
headers= {
24+
["Content-Type"] = "application/ocsp-request"
25+
}}
26+
if err then
27+
return nil, err
28+
end
29+
30+
if not res then
31+
return nil, "failed to send request to OCSP responder: " .. tostring(err)
32+
end
33+
34+
if res.status ~= 200 then
35+
return nil, "unexpected OCSP responder status code: " .. res.status
36+
end
37+
38+
return res.body
39+
end
40+
41+
function _M:check_revocation_status()
42+
local cert_chain, err = tls.get_full_client_certificate_chain()
43+
if not cert_chain then
44+
return nil, err or "no client certificate"
45+
end
46+
47+
local der_cert
48+
der_cert, err = ngx_ssl.cert_pem_to_der(cert_chain)
49+
if not der_cert then
50+
return nil, "failed to convert certificate chain from PEM to DER " .. err
51+
end
52+
53+
-- TODO: check response cache
54+
local ocsp_url
55+
ocsp_url, err = ocsp.get_ocsp_responder_from_der_chain(der_cert)
56+
if not ocsp_url then
57+
return nil, error or ("could not extract OCSP responder URL, the client " ..
58+
"certificate may be missing the required extensions")
59+
end
60+
61+
local ocsp_req
62+
ocsp_req, err = ocsp.create_ocsp_request(der_cert)
63+
if not ocsp_req then
64+
return nil, "failed to create OCSP request: " .. err
65+
end
66+
67+
local ocsp_resp
68+
ocsp_resp, err = do_ocsp_request(ocsp_url, ocsp_req)
69+
if not ocsp_req then
70+
return nil, "failed to get OCSP response: " .. err
71+
end
72+
if not ocsp_resp or #ocsp_resp == 0 then
73+
return nil, "unexpected response from OCSP responder: empty body"
74+
end
75+
76+
local ok
77+
ok, err = ocsp.validate_ocsp_response(ocsp_resp, der_cert)
78+
if not ok then
79+
return false, "failed to validate OCSP response: " .. err
80+
end
81+
82+
-- TODO: cache the response
83+
-- Use ttl, normally this should be (nextUpdate - thisUpdate), but current version
84+
-- of openresty API does not expose those attributes. Support for this was added
85+
-- in openrest-core v0.1.31, we either need to backport or upgrade the openresty
86+
-- version
87+
--
88+
-- TODO: use cert digest or uid instead
89+
return true
90+
end
91+
92+
return _M

gateway/src/apicast/policy/tls_validation/tls_validation.lua

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ local X509_STORE = require('resty.openssl.x509.store')
66
local X509 = require('resty.openssl.x509')
77
local X509_CRL = require('resty.openssl.x509.crl')
88
local ngx_ssl = require "ngx.ssl"
9+
local ocsp = require ("ocsp")
910

1011
local ipairs = ipairs
1112
local tostring = tostring
@@ -85,6 +86,7 @@ function _M:ssl_certificate()
8586
-- provide ca_certs: See https://github.com/openresty/lua-resty-core/blob/master/lib/ngx/ssl.md#verify_client
8687
-- handle verify_depth
8788
--
89+
-- TODO: OCSP stapling
8890
return ngx_ssl.verify_client()
8991
end
9092

@@ -122,6 +124,16 @@ function _M:access()
122124
return ngx.exit(ngx.status)
123125
end
124126

127+
if self.revocation_type == "ocsp" then
128+
ok, err = ocsp:check_for_revocation_status()
129+
if not ok then
130+
ngx.status = self.error_status
131+
ngx.log(ngx.WARN, "TLS certificate validation failed, err: ", err)
132+
ngx.say("TLS certificate validation failed")
133+
return ngx.exit(ngx.status)
134+
end
135+
end
136+
125137
return true, nil
126138
end
127139

gateway/src/resty/tls.lua

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,28 @@ local type = type
44
local tostring = tostring
55

66
local get_request = base.get_request
7+
local get_size_ptr = base.get_size_ptr
78
local ffi = require "ffi"
9+
local ffi_new = ffi.new
10+
local ffi_str = ffi.string
811
local C = ffi.C
912

1013
local _M = {}
1114

1215
local NGX_OK = ngx.OK
16+
local NGX_ERROR = ngx.ERROR
17+
local NGX_DECLINED = ngx.DECLINED
1318

19+
local ngx_http_apicast_ffi_get_full_client_certificate_chain;
1420
local ngx_http_apicast_ffi_set_proxy_cert_key;
1521
local ngx_http_apicast_ffi_set_proxy_ca_cert;
1622
local ngx_http_apicast_ffi_set_ssl_verify
1723

24+
local value_ptr = ffi_new("unsigned char *[1]")
25+
1826
ffi.cdef([[
27+
int ngx_http_apicast_ffi_get_full_client_certificate_chain(
28+
ngx_http_request_t *r, char **value, size_t *value_len);
1929
int ngx_http_apicast_ffi_set_proxy_cert_key(
2030
ngx_http_request_t *r, void *cdata_chain, void *cdata_key);
2131
int ngx_http_apicast_ffi_set_proxy_ca_cert(
@@ -24,6 +34,7 @@ ffi.cdef([[
2434
ngx_http_request_t *r, int verify, int verify_deph);
2535
]])
2636

37+
ngx_http_apicast_ffi_get_full_client_certificate_chain = C.ngx_http_apicast_ffi_get_full_client_certificate_chain
2738
ngx_http_apicast_ffi_set_proxy_cert_key = C.ngx_http_apicast_ffi_set_proxy_cert_key
2839
ngx_http_apicast_ffi_set_proxy_ca_cert = C.ngx_http_apicast_ffi_set_proxy_ca_cert
2940
ngx_http_apicast_ffi_set_ssl_verify = C.ngx_http_apicast_ffi_set_ssl_verify
@@ -88,4 +99,29 @@ function _M.set_upstream_ssl_verify(verify, verify_deph)
8899
end
89100
end
90101

102+
-- Retrieve the full client certificate chain
103+
function _M.get_full_client_certificate_chain()
104+
local r = get_request()
105+
if not r then
106+
error("no request found")
107+
end
108+
109+
local size_ptr = get_size_ptr()
110+
111+
local rc = ngx_http_apicast_ffi_get_full_client_certificate_chain(r, value_ptr, size_ptr)
112+
113+
if rc == NGX_OK then
114+
return ffi_str(value_ptr[0], size_ptr[0])
115+
end
116+
117+
if rc == NGX_ERROR then
118+
return nil, "error while obtaining client certificate chain"
119+
end
120+
121+
122+
if rc == NGX_DECLINED then
123+
return nil
124+
end
125+
end
126+
91127
return _M

0 commit comments

Comments
 (0)