Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[THREESCALE-11404] Adding support for CRL and OCSP #1503

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions gateway/http.d/shdict.conf
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ lua_shared_dict limiter 1m;
lua_shared_dict cached_auths 20m;
lua_shared_dict batched_reports {{env.APICAST_POLICY_BATCHER_SHARED_MEMORY_SIZE | default: "20m"}};
lua_shared_dict batched_reports_locks 1m;
lua_shared_dict ocsp_cache 10m;
50 changes: 49 additions & 1 deletion gateway/src/apicast/policy/tls_validation/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# TLS Validation policy

This policy can validate TLS Client Certificate against a whitelist.
This policy can validate TLS Client Certificate against a whitelist and Certificate Revocation List (CRL)

Whitelist expects PEM formatted CA or Client certificates.
Revocation List expects PEM formatted certificates.
It is not necessary to have the full certificate chain, just partial matches are allowed.
For example you can add to the whitelist just leaf client certificates without the whole bundle with a CA certificate.

Expand All @@ -28,3 +29,50 @@ NOTE: This policy is not compatible with `APICAST_PATH_ROUTING` or `APICAST_PATH
}
}
```

With Certificate Revocation List (CRL)

```
{
"name": "apicast.policy.tls_validation",
"configuration": {
"whitelist": [
{ "pem_certificate": ""-----BEGIN CERTIFICATE----- XXXXXX -----END CERTIFICATE-----"}
],
"revocation_check_type": "crl",
"revoke_list": [
{ "pem_certificate": ""-----BEGIN CERTIFICATE----- XXXXXX -----END CERTIFICATE-----"}
]
}
}
```

Checking certificate status with Online Certificate Status Protocol (OCSP). The responder url is
extracted from the certificate

```
{
"name": "apicast.policy.tls_validation",
"configuration": {
"whitelist": [
{ "pem_certificate": ""-----BEGIN CERTIFICATE----- XXXXXX -----END CERTIFICATE-----"}
],
"revocation_check_type": "ocsp",
}
}
```

Overwrite OCSP responder URL

```
{
"name": "apicast.policy.tls_validation",
"configuration": {
"whitelist": [
{ "pem_certificate": ""-----BEGIN CERTIFICATE----- XXXXXX -----END CERTIFICATE-----"}
],
"revocation_check_type": "ocsp",
"ocsp_responder_url": "http://<ocsp-server>:<port>"
}
}
```
86 changes: 86 additions & 0 deletions gateway/src/apicast/policy/tls_validation/apicast-policy.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,99 @@
"items": {
"$ref": "#/definitions/certificate"
}
},
"revoke": {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this being used?

"$id": "#/definitions/revoke",
"type": "array",
"items": {
"$ref": "#/definitions/certificate"
}
}
},
"properties": {
"whitelist": {
"$ref": "#/definitions/store",
"title": "Certificate Whitelist",
"description": "Individual certificates and CA certificates to be whitelisted."
},
"allow_partial_chain": {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be documented somewhere

"description": "Allow certificate verification with only an intermediate certificate",
"type": "boolean",
"default": true
},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe revoke_list and ocsp_responder_url are missing?

"revocation_check_type": {
"title": "Certificate Revocation Check type",
"type": "string",
"oneOf": [
{
"enum": [
"ocsp"
],
"title": "Enables OCSP validation of the client certificate."
},
{
"enum": [
"crl"
],
"title": "Use certificates revocation list (CRL) in the PEM format to verify client certificates."
},
{
"enum": [
"none"
],
"title": "Do not check for certificate recovation status"
}
],
"default": "none"
}
},
"dependencies": {
"revocation_check_type": {
"oneOf": [
{
"properties": {
"revocation_check_type": {
"enum": [
"none"
]
}
}
},
{
"properties": {
"revocation_check_type": {
"enum": [
"crl"
]
},
"revoke_list": {
"title": "Certificate RevokeList",
"description": "Individual certificates and CA certificates to be revoked.",
"$ref": "#/definitions/store"
}
}
},
{
"properties": {
"revocation_check_type": {
"enum": [
"ocsp"
]
},
"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_ttl": {
"title": "Max TTL for cached OCSP response",
"type": "integer",
"minimum": 1,
"maximum": 3600
}
}
}
]
}
}
}
Expand Down
114 changes: 114 additions & 0 deletions gateway/src/apicast/policy/tls_validation/ocsp_validation.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
local user_agent = require "apicast.user_agent"
local http_ng = require "resty.http_ng"
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 ocsp_shm = ngx.shared.ocsp_cache

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()

Check warning on line 16 in gateway/src/apicast/policy/tls_validation/ocsp_validation.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/policy/tls_validation/ocsp_validation.lua#L13-L16

Added lines #L13 - L16 were not covered by tests
},
ssl = { verify = resty_env.enabled('OPENSSL_VERIFY') }

Check warning on line 18 in gateway/src/apicast/policy/tls_validation/ocsp_validation.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/policy/tls_validation/ocsp_validation.lua#L18

Added line #L18 was not covered by tests
}
}
local res, err = http_client.post{

Check warning on line 21 in gateway/src/apicast/policy/tls_validation/ocsp_validation.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/policy/tls_validation/ocsp_validation.lua#L21

Added line #L21 was not covered by tests
ocsp_url,
ocsp_request,
headers= {
["Content-Type"] = "application/ocsp-request"
}}
if err then
return nil, err

Check warning on line 28 in gateway/src/apicast/policy/tls_validation/ocsp_validation.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/policy/tls_validation/ocsp_validation.lua#L24-L28

Added lines #L24 - L28 were not covered by tests
end

ngx.log(ngx.INFO, "fetching OCSP response from ", ocsp_url)

Check warning on line 31 in gateway/src/apicast/policy/tls_validation/ocsp_validation.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/policy/tls_validation/ocsp_validation.lua#L31

Added line #L31 was not covered by tests

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

Check warning on line 34 in gateway/src/apicast/policy/tls_validation/ocsp_validation.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/policy/tls_validation/ocsp_validation.lua#L33-L34

Added lines #L33 - L34 were not covered by tests
end

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

Check warning on line 38 in gateway/src/apicast/policy/tls_validation/ocsp_validation.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/policy/tls_validation/ocsp_validation.lua#L37-L38

Added lines #L37 - L38 were not covered by tests
end

return res.body

Check warning on line 41 in gateway/src/apicast/policy/tls_validation/ocsp_validation.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/policy/tls_validation/ocsp_validation.lua#L41

Added line #L41 was not covered by tests
end

function _M.check_revocation_status(ocsp_responder_url, digest, ttl)
-- Nginx supports leaf mode, that is only verify the client ceritificate, however
-- until we have a way to detect which CA certificate is being used to verify the
-- client certificate we need to get the full certificate chain here to construct
-- the OCSP request.
local cert_chain, err = tls.get_full_client_certificate_chain()
if not cert_chain then
return nil, err or "no client certificate"

Check warning on line 51 in gateway/src/apicast/policy/tls_validation/ocsp_validation.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/policy/tls_validation/ocsp_validation.lua#L49-L51

Added lines #L49 - L51 were not covered by tests
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

Check warning on line 57 in gateway/src/apicast/policy/tls_validation/ocsp_validation.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/policy/tls_validation/ocsp_validation.lua#L55-L57

Added lines #L55 - L57 were not covered by tests
end

local ocsp_resp
ocsp_resp = ocsp_shm:get(digest)

Check warning on line 61 in gateway/src/apicast/policy/tls_validation/ocsp_validation.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/policy/tls_validation/ocsp_validation.lua#L61

Added line #L61 was not covered by tests

if ocsp_resp == nil then
ngx.log(ngx.INFO, "no ocsp resp cache found, fetch from ocsp responder")

Check warning on line 64 in gateway/src/apicast/policy/tls_validation/ocsp_validation.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/policy/tls_validation/ocsp_validation.lua#L63-L64

Added lines #L63 - L64 were not covered by tests


-- TODO: check response cache
local ocsp_url
if ocsp_responder_url and ocsp_responder_url ~= "" then
ocsp_url = ocsp_responder_url

Check warning on line 70 in gateway/src/apicast/policy/tls_validation/ocsp_validation.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/policy/tls_validation/ocsp_validation.lua#L69-L70

Added lines #L69 - L70 were not covered by tests
else
ocsp_url, err = ocsp.get_ocsp_responder_from_der_chain(der_cert)
if not ocsp_url then
return nil, err or ("could not extract OCSP responder URL, the client " ..
"certificate may be missing the required extensions")

Check warning on line 75 in gateway/src/apicast/policy/tls_validation/ocsp_validation.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/policy/tls_validation/ocsp_validation.lua#L72-L75

Added lines #L72 - L75 were not covered by tests
end
end

if not ocsp_url or ocsp_url == "" then
return nil, " invalid OCSP responder URL"

Check warning on line 80 in gateway/src/apicast/policy/tls_validation/ocsp_validation.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/policy/tls_validation/ocsp_validation.lua#L79-L80

Added lines #L79 - L80 were not covered by tests
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

Check warning on line 86 in gateway/src/apicast/policy/tls_validation/ocsp_validation.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/policy/tls_validation/ocsp_validation.lua#L84-L86

Added lines #L84 - L86 were not covered by tests
end

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

Check warning on line 91 in gateway/src/apicast/policy/tls_validation/ocsp_validation.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/policy/tls_validation/ocsp_validation.lua#L89-L91

Added lines #L89 - L91 were not covered by tests
end

-- 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.
local ok
ok, err = ocsp_shm:set(digest, ocsp_resp, ttl)
if not ok then
ngx.log(ngx.ERR, "could not save ocsp response to cache: ", err)

Check warning on line 101 in gateway/src/apicast/policy/tls_validation/ocsp_validation.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/policy/tls_validation/ocsp_validation.lua#L99-L101

Added lines #L99 - L101 were not covered by tests
end
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

Check warning on line 108 in gateway/src/apicast/policy/tls_validation/ocsp_validation.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/policy/tls_validation/ocsp_validation.lua#L106-L108

Added lines #L106 - L108 were not covered by tests
end

return true

Check warning on line 111 in gateway/src/apicast/policy/tls_validation/ocsp_validation.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/policy/tls_validation/ocsp_validation.lua#L111

Added line #L111 was not covered by tests
end

return _M
63 changes: 54 additions & 9 deletions gateway/src/apicast/policy/tls_validation/tls_validation.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
local _M = policy.new('tls_validation')
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_validation")

local ipairs = ipairs
local tostring = tostring
Expand Down Expand Up @@ -33,6 +35,29 @@
return store
end

local function init_crl_list(store, crl_certificates)
local ok, err
local crl
for _, certificate in ipairs(crl_certificates) do
-- add crl to store, but skip setting the flag
crl, err = X509_CRL.new(certificate.pem_certificate)
if crl then
ok, err = store:add(crl)

if debug then
ngx.log(ngx.DEBUG, 'adding crl certificate to the tls validation ', tostring(crl:subject_name()), ' SHA1: ', crl:hexdigest('SHA1'))

Check warning on line 48 in gateway/src/apicast/policy/tls_validation/tls_validation.lua

View check run for this annotation

Codecov / codecov/patch

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

Added line #L48 was not covered by tests
end
else
ngx.log(ngx.WARN, 'failed to add crl certificate, err: ', err)

Check warning on line 51 in gateway/src/apicast/policy/tls_validation/tls_validation.lua

View check run for this annotation

Codecov / codecov/patch

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

Added line #L51 was not covered by tests

if debug then
ngx.log(ngx.DEBUG, 'certificate: ', certificate.pem_certificate)

Check warning on line 54 in gateway/src/apicast/policy/tls_validation/tls_validation.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/policy/tls_validation/tls_validation.lua#L53-L54

Added lines #L53 - L54 were not covered by tests
end
end
end
return store
end

local new = _M.new
--- Initialize a tls_validation
-- @tparam[opt] table config Policy configuration.
Expand All @@ -42,20 +67,26 @@

self.x509_store = init_trusted_store(store, config and config.whitelist or {})
self.error_status = config and config.error_status or 400
self.allow_partial_chain = config and config.allow_partial_chain
self.revocation_type = config and config.revocation_check_type or "none"
if self.revocation_type == "crl" then
init_crl_list(store, config and config.revoke_list or {})
elseif self.revocation_type == "ocsp" then
self.ocsp_responder_url = config and config.ocsp_responder_url
self.cache_ttl = config and config.cache_ttl

Check warning on line 76 in gateway/src/apicast/policy/tls_validation/tls_validation.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/policy/tls_validation/tls_validation.lua#L75-L76

Added lines #L75 - L76 were not covered by tests
end

return self
end

function _M:ssl_certificate()
-- Request client certificate
--
-- We don't validate the certificate during the handshake, thus set `depth` to 0 (default is 1)
-- value here in order to save CPU cycles
--
-- TODO:
-- 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 All @@ -70,26 +101,40 @@
local cert, err = X509.new(client_cert)
if not cert then
ngx.status = self.error_status
ngx.log(ngx.WARN, "Invalid TLS certificate, err: ", err)
ngx.log(ngx.WARN, "Unable to load client certificate, err: ", err)

Check warning on line 104 in gateway/src/apicast/policy/tls_validation/tls_validation.lua

View check run for this annotation

Codecov / codecov/patch

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

Added line #L104 was not covered by tests
ngx.say("Invalid TLS certificate")
return ngx.exit(ngx.status)
end

local store = self.x509_store
store:set_flags(store.verify_flags.X509_V_FLAG_PARTIAL_CHAIN)

if self.allow_partial_chain then
store:set_flags(store.verify_flags.X509_V_FLAG_PARTIAL_CHAIN)
end

-- err is printed inside validate_cert method
-- so no need capture the err here
local ok, err = store:verify(cert)
local chain, ok
chain, err = store:verify(cert, nil, true)

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

return ok, nil
if self.revocation_type == "ocsp" then
ok, err = ocsp.check_revocation_status(self.ocsp_responder_url, cert.digest, self.cache_ttl)
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)

Check warning on line 133 in gateway/src/apicast/policy/tls_validation/tls_validation.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/policy/tls_validation/tls_validation.lua#L128-L133

Added lines #L128 - L133 were not covered by tests
end
end

return true, nil
end

return _M
Loading
Loading