Skip to content

Commit

Permalink
[tls_validation] Add support for OCSP
Browse files Browse the repository at this point in the history
  • Loading branch information
tkan145 committed Jan 31, 2025
1 parent 12c8361 commit 594e7d8
Show file tree
Hide file tree
Showing 4 changed files with 185 additions and 0 deletions.
45 changes: 45 additions & 0 deletions gateway/src/apicast/policy/tls_validation/apicast-policy.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@
"title": "Certificate Revocation Check type",
"type": "string",
"oneOf": [
{
"enum": [
"ocsp"
],
"title": "Enables OCSP validation of the client certificate."
},
{
"enum": [
"crl"
Expand Down Expand Up @@ -89,6 +95,45 @@
"$ref": "#/definitions/store"
}
}
},
{
"properties": {
"revocation_check_type": {
"enum": [
"ocsp"
]
},
"revocation_check_mode": {
"title": "Certificate Mode",
"description": "Certificate revocation check mode",
"type": "string",
"oneOf": [
{
"enum": [
"ignore_error"
],
"title": "Ignore Network Error: respects the revocation status when either OCSP or CRL URL is set, and doesn’t fail on network issues"
},
{
"enum": [
"strict"
],
"title": "Strict: The certificate is valid only when it’s able to verify the revocation status."
}
],
"default": "strict"
},
"ocsp_responder_url": {
"title": "OCSP Responder URL ",
"description": "Overrides the URL of the OCSP responder specified in the “Authority Information Access” certificate extension for validation of client certificates. ",
"type": "string"
},
"cache_timeout": {
"title": " Cache timeout",
"description": "The length of time in milliseconds between refreshes of the revocation check status cache.",
"type": "integer"
}
}
}
]
}
Expand Down
92 changes: 92 additions & 0 deletions gateway/src/apicast/policy/tls_validation/ocsp.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
local http_ng = require 'resty.http_ng'
local user_agent = require 'apicast.user_agent'
local resty_env = require('resty.env')
local tls = require "resty.tls"
local ngx_ssl = require "ngx.ssl"
local ocsp = require("ngx.ocsp")

local _M = {}

local function do_ocsp_request(ocsp_url, ocsp_request)
-- TODO: set default timeout
local http_client = http_ng.new{
options = {
headers = {
['User-Agent'] = user_agent()
},
ssl = { verify = resty_env.enabled('OPENSSL_VERIFY') }
}
}
local res, err = http_client.post{
ocsp_url,
ocsp_request,
headers= {
["Content-Type"] = "application/ocsp-request"
}}
if err then
return nil, err
end

if not res then
return nil, "failed to send request to OCSP responder: " .. tostring(err)
end

if res.status ~= 200 then
return nil, "unexpected OCSP responder status code: " .. res.status
end

return res.body
end

function _M:check_revocation_status()
local cert_chain, err = tls.get_full_client_certificate_chain()
if not cert_chain then
return nil, err or "no client certificate"
end

local der_cert
der_cert, err = ngx_ssl.cert_pem_to_der(cert_chain)
if not der_cert then
return nil, "failed to convert certificate chain from PEM to DER " .. err
end

-- TODO: check response cache
local ocsp_url
ocsp_url, err = ocsp.get_ocsp_responder_from_der_chain(der_cert)
if not ocsp_url then
return nil, error or ("could not extract OCSP responder URL, the client " ..
"certificate may be missing the required extensions")
end

local ocsp_req
ocsp_req, err = ocsp.create_ocsp_request(der_cert)
if not ocsp_req then
return nil, "failed to create OCSP request: " .. err
end

local ocsp_resp
ocsp_resp, err = do_ocsp_request(ocsp_url, ocsp_req)
if not ocsp_req then
return nil, "failed to get OCSP response: " .. err
end
if not ocsp_resp or #ocsp_resp == 0 then
return nil, "unexpected response from OCSP responder: empty body"
end

local ok
ok, err = ocsp.validate_ocsp_response(ocsp_resp, der_cert)
if not ok then
return false, "failed to validate OCSP response: " .. err
end

-- TODO: cache the response
-- Use ttl, normally this should be (nextUpdate - thisUpdate), but current version
-- of openresty API does not expose those attributes. Support for this was added
-- in openrest-core v0.1.31, we either need to backport or upgrade the openresty
-- version
--
-- TODO: use cert digest or uid instead
return true
end

return _M
12 changes: 12 additions & 0 deletions gateway/src/apicast/policy/tls_validation/tls_validation.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ local X509_STORE = require('resty.openssl.x509.store')
local X509 = require('resty.openssl.x509')
local X509_CRL = require('resty.openssl.x509.crl')
local ngx_ssl = require "ngx.ssl"
local ocsp = require ("ocsp")

local ipairs = ipairs
local tostring = tostring
Expand Down Expand Up @@ -85,6 +86,7 @@ function _M:ssl_certificate()
-- provide ca_certs: See https://github.com/openresty/lua-resty-core/blob/master/lib/ngx/ssl.md#verify_client
-- handle verify_depth
--
-- TODO: OCSP stapling
return ngx_ssl.verify_client()
end

Expand Down Expand Up @@ -122,6 +124,16 @@ function _M:access()
return ngx.exit(ngx.status)
end

if self.revocation_type == "ocsp" then
ok, err = ocsp:check_for_revocation_status()
if not ok then
ngx.status = self.error_status
ngx.log(ngx.WARN, "TLS certificate validation failed, err: ", err)
ngx.say("TLS certificate validation failed")
return ngx.exit(ngx.status)
end
end

return true, nil
end

Expand Down
36 changes: 36 additions & 0 deletions gateway/src/resty/tls.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,28 @@ local type = type
local tostring = tostring

local get_request = base.get_request
local get_size_ptr = base.get_size_ptr
local ffi = require "ffi"
local ffi_new = ffi.new
local ffi_str = ffi.string
local C = ffi.C

local _M = {}

local NGX_OK = ngx.OK
local NGX_ERROR = ngx.ERROR
local NGX_DECLINED = ngx.DECLINED

local ngx_http_apicast_ffi_get_full_client_certificate_chain;
local ngx_http_apicast_ffi_set_proxy_cert_key;
local ngx_http_apicast_ffi_set_proxy_ca_cert;
local ngx_http_apicast_ffi_set_ssl_verify

local value_ptr = ffi_new("unsigned char *[1]")

ffi.cdef([[
int ngx_http_apicast_ffi_get_full_client_certificate_chain(
ngx_http_request_t *r, char **value, size_t *value_len);
int ngx_http_apicast_ffi_set_proxy_cert_key(
ngx_http_request_t *r, void *cdata_chain, void *cdata_key);
int ngx_http_apicast_ffi_set_proxy_ca_cert(
Expand All @@ -24,6 +34,7 @@ ffi.cdef([[
ngx_http_request_t *r, int verify, int verify_deph);
]])

ngx_http_apicast_ffi_get_full_client_certificate_chain = C.ngx_http_apicast_ffi_get_full_client_certificate_chain
ngx_http_apicast_ffi_set_proxy_cert_key = C.ngx_http_apicast_ffi_set_proxy_cert_key
ngx_http_apicast_ffi_set_proxy_ca_cert = C.ngx_http_apicast_ffi_set_proxy_ca_cert
ngx_http_apicast_ffi_set_ssl_verify = C.ngx_http_apicast_ffi_set_ssl_verify
Expand Down Expand Up @@ -88,4 +99,29 @@ function _M.set_upstream_ssl_verify(verify, verify_deph)
end
end

-- Retrieve the full client certificate chain
function _M.get_full_client_certificate_chain()
local r = get_request()
if not r then
error("no request found")
end

local size_ptr = get_size_ptr()

local rc = ngx_http_apicast_ffi_get_full_client_certificate_chain(r, value_ptr, size_ptr)

if rc == NGX_OK then
return ffi_str(value_ptr[0], size_ptr[0])
end

if rc == NGX_ERROR then
return nil, "error while obtaining client certificate chain"
end


if rc == NGX_DECLINED then
return nil
end
end

return _M

0 comments on commit 594e7d8

Please sign in to comment.