Skip to content

Commit 5f7cb92

Browse files
authored
Merge pull request #1503 from tkan145/THREESCALE-11404-crl-and-ocsp
[THREESCALE-11404] Adding support for CRL and OCSP
2 parents a1331e7 + 1952be3 commit 5f7cb92

51 files changed

Lines changed: 1708 additions & 39 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
4747
- Add `enable_extended_context` to allow JWT Claim Check access full request context [PR #1535](https://github.com/3scale/APIcast/pull/1535) [THREESCALE-9510](https://issues.redhat.com/browse/THREESCALE-9510)
4848
- JWT signature verification, support for ES256/ES512 [PR #1533](https://github.com/3scale/APIcast/pull/1533) [THREESCALE-11474](https://issues.redhat.com/browse/THREESCALE-11474)
4949
- JWT Parser policy [PR #1536](https://github.com/3scale/APIcast/pull/1536) [THREESCALE-10708](https://issues.redhat.com/browse/THREESCALE-10708)
50+
- TLS Validation Policy - add support to validate client certificate with CRL and OCSP [PR #1503](https://github.com/3scale/APIcast/pull/1503) [THREESCALE-11404](https://issues.redhat.com/browse/THREESCALE-11404)
5051

5152
## [3.15.0] 2024-04-04
5253

gateway/http.d/shdict.conf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ lua_shared_dict limiter 1m;
88
lua_shared_dict cached_auths 20m;
99
lua_shared_dict batched_reports {{env.APICAST_POLICY_BATCHER_SHARED_MEMORY_SIZE | default: "20m"}};
1010
lua_shared_dict batched_reports_locks 1m;
11+
lua_shared_dict ocsp_cache 10m;

gateway/src/apicast/policy/tls_validation/README.md

Lines changed: 69 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
# TLS Validation policy
22

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

5-
Whitelist expects PEM formatted CA or Client certificates.
6-
It is not necessary to have the full certificate chain, just partial matches are allowed.
7-
For example you can add to the whitelist just leaf client certificates without the whole bundle with a CA certificate.
5+
* Whitelist expects PEM formatted CA or Client certificates.
6+
* Revocation List expects PEM formatted certificates.
7+
8+
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. However, you can change this behaviour with `allow_partial_chain`
89

910
## Configuration
1011

@@ -18,13 +19,77 @@ NOTE: This policy is not compatible with `APICAST_PATH_ROUTING` or `APICAST_PATH
1819

1920
## Example
2021

22+
* Allow certificate verification with only an intermediate certificate.
23+
```
24+
{
25+
"name": "apicast.policy.tls_validation",
26+
"configuration": {
27+
"whitelist": [
28+
{ "pem_certificate": ""-----BEGIN CERTIFICATE----- XXXXXX -----END CERTIFICATE-----"}
29+
]
30+
}
31+
}
32+
```
33+
34+
* Use full certificate chain to verify client certificate
35+
```
36+
{
37+
"name": "apicast.policy.tls_validation",
38+
"configuration": {
39+
"whitelist": [
40+
{ "pem_certificate": ""-----BEGIN CERTIFICATE----- XXXXXX -----END CERTIFICATE-----"}
41+
],
42+
"allow_partial_chain": false
43+
}
44+
}
45+
```
46+
47+
With Certificate Revocation List (CRL)
48+
2149
```
2250
{
2351
"name": "apicast.policy.tls_validation",
2452
"configuration": {
2553
"whitelist": [
2654
{ "pem_certificate": ""-----BEGIN CERTIFICATE----- XXXXXX -----END CERTIFICATE-----"}
55+
],
56+
"revocation_check_type": "crl",
57+
"revoke_list": [
58+
{ "pem_certificate": ""-----BEGIN X509 CRL ----- XXXXXX -----END X509 CRL-----"}
2759
]
2860
}
2961
}
3062
```
63+
64+
Checking certificate status with Online Certificate Status Protocol (OCSP). The responder url is
65+
extracted from the certificate.
66+
67+
NOTE: When validating a client certificate with OCSP, APIcast requires the client to send the certificate chain
68+
(i.e. if the certificate is signed with an intermediate certificate, the client needs to send both the client certificate + the intermediate certificate)
69+
70+
```
71+
{
72+
"name": "apicast.policy.tls_validation",
73+
"configuration": {
74+
"whitelist": [
75+
{ "pem_certificate": ""-----BEGIN CERTIFICATE----- XXXXXX -----END CERTIFICATE-----"}
76+
],
77+
"revocation_check_type": "ocsp",
78+
}
79+
}
80+
```
81+
82+
Overwrite OCSP responder URL
83+
84+
```
85+
{
86+
"name": "apicast.policy.tls_validation",
87+
"configuration": {
88+
"whitelist": [
89+
{ "pem_certificate": ""-----BEGIN CERTIFICATE----- XXXXXX -----END CERTIFICATE-----"}
90+
],
91+
"revocation_check_type": "ocsp",
92+
"ocsp_responder_url": "http://<ocsp-server>:<port>"
93+
}
94+
}
95+
```

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

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,85 @@
3131
"$ref": "#/definitions/store",
3232
"title": "Certificate Whitelist",
3333
"description": "Individual certificates and CA certificates to be whitelisted."
34+
},
35+
"allow_partial_chain": {
36+
"description": "Allow certificate verification with only an intermediate certificate",
37+
"type": "boolean",
38+
"default": true
39+
},
40+
"revocation_check_type": {
41+
"title": "Certificate Revocation Check type",
42+
"type": "string",
43+
"oneOf": [
44+
{
45+
"enum": [
46+
"ocsp"
47+
],
48+
"title": "Enables OCSP validation of the client certificate."
49+
},
50+
{
51+
"enum": [
52+
"crl"
53+
],
54+
"title": "Use certificates revocation list (CRL) in the PEM format to verify client certificates."
55+
},
56+
{
57+
"enum": [
58+
"none"
59+
],
60+
"title": "Do not check for certificate recovation status"
61+
}
62+
],
63+
"default": "none"
64+
}
65+
},
66+
"dependencies": {
67+
"revocation_check_type": {
68+
"oneOf": [
69+
{
70+
"properties": {
71+
"revocation_check_type": {
72+
"enum": [
73+
"none"
74+
]
75+
}
76+
}
77+
},
78+
{
79+
"properties": {
80+
"revocation_check_type": {
81+
"enum": [
82+
"crl"
83+
]
84+
},
85+
"revoke_list": {
86+
"title": "Certificate RevokeList",
87+
"description": "Individual certificates and CA certificates to be revoked.",
88+
"$ref": "#/definitions/store"
89+
}
90+
}
91+
},
92+
{
93+
"properties": {
94+
"revocation_check_type": {
95+
"enum": [
96+
"ocsp"
97+
]
98+
},
99+
"ocsp_responder_url": {
100+
"title": "OCSP Responder URL ",
101+
"description": "Overrides the URL of the OCSP responder specified in the “Authority Information Access” certificate extension for validation of client certificates. ",
102+
"type": "string"
103+
},
104+
"cache_ttl": {
105+
"title": "Max TTL for cached OCSP response",
106+
"type": "integer",
107+
"minimum": 1,
108+
"maximum": 3600
109+
}
110+
}
111+
}
112+
]
34113
}
35114
}
36115
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
local user_agent = require "apicast.user_agent"
2+
local http_ng = require "resty.http_ng"
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+
local ocsp_shm = ngx.shared.ocsp_cache
10+
11+
local function do_ocsp_request(ocsp_url, ocsp_request)
12+
-- TODO: set default timeout
13+
local http_client = http_ng.new{
14+
options = {
15+
headers = {
16+
['User-Agent'] = user_agent()
17+
},
18+
ssl = { verify = resty_env.enabled('OPENSSL_VERIFY') }
19+
}
20+
}
21+
local res, err = http_client.post{
22+
ocsp_url,
23+
ocsp_request,
24+
headers= {
25+
["Content-Type"] = "application/ocsp-request"
26+
}}
27+
if err then
28+
return nil, err
29+
end
30+
31+
ngx.log(ngx.INFO, "fetching OCSP response from ", ocsp_url)
32+
33+
if not res then
34+
return nil, "failed to send request to OCSP responder: " .. tostring(err)
35+
end
36+
37+
if res.status ~= 200 then
38+
return nil, "unexpected OCSP responder status code: " .. res.status
39+
end
40+
41+
return res.body
42+
end
43+
44+
function _M.check_revocation_status(ocsp_responder_url, digest, ttl)
45+
-- Nginx supports leaf mode, that is only verify the client ceritificate, however
46+
-- until we have a way to detect which CA certificate is being used to verify the
47+
-- client certificate we need to get the full certificate chain here to construct
48+
-- the OCSP request.
49+
local cert_chain, err = tls.get_full_client_certificate_chain()
50+
if not cert_chain then
51+
return nil, err or "no client certificate"
52+
end
53+
54+
local der_cert
55+
der_cert, err = ngx_ssl.cert_pem_to_der(cert_chain)
56+
if not der_cert then
57+
return nil, "failed to convert certificate chain from PEM to DER " .. err
58+
end
59+
60+
local ocsp_resp
61+
ocsp_resp = ocsp_shm:get(digest)
62+
63+
if ocsp_resp == nil then
64+
ngx.log(ngx.INFO, "no ocsp resp cache found, fetch from ocsp responder")
65+
66+
67+
-- TODO: check response cache
68+
local ocsp_url
69+
if ocsp_responder_url and ocsp_responder_url ~= "" then
70+
ocsp_url = ocsp_responder_url
71+
else
72+
ocsp_url, err = ocsp.get_ocsp_responder_from_der_chain(der_cert)
73+
if not ocsp_url then
74+
return nil, err or ("could not extract OCSP responder URL, the client " ..
75+
"certificate may be missing the required extensions")
76+
end
77+
end
78+
79+
if not ocsp_url or ocsp_url == "" then
80+
return nil, " invalid OCSP responder URL"
81+
end
82+
83+
local ocsp_req
84+
ocsp_req, err = ocsp.create_ocsp_request(der_cert)
85+
if not ocsp_req then
86+
return nil, "failed to create OCSP request: " .. err
87+
end
88+
89+
ocsp_resp, err = do_ocsp_request(ocsp_url, ocsp_req)
90+
if not ocsp_resp or #ocsp_resp == 0 then
91+
return nil, "unexpected response from OCSP responder: empty body"
92+
end
93+
94+
-- Use ttl, normally this should be (nextUpdate - thisUpdate), but current version
95+
-- of openresty API does not expose those attributes. Support for this was added
96+
-- in openrest-core v0.1.31, we either need to backport or upgrade the openresty
97+
-- version.
98+
local ok
99+
ok, err = ocsp_shm:set(digest, ocsp_resp, ttl)
100+
if not ok then
101+
ngx.log(ngx.ERR, "could not save ocsp response to cache: ", err)
102+
end
103+
else
104+
ngx.log(ngx.INFO, "using ocsp from cache")
105+
end
106+
107+
local ok
108+
ok, err = ocsp.validate_ocsp_response(ocsp_resp, der_cert)
109+
if not ok then
110+
return false, "failed to validate OCSP response: " .. err
111+
end
112+
113+
return true
114+
end
115+
116+
return _M

0 commit comments

Comments
 (0)