From 72552550c5dd522ce970badcc0559015d4fd4ee5 Mon Sep 17 00:00:00 2001 From: An Tran Date: Mon, 28 Oct 2024 13:37:10 +1000 Subject: [PATCH 01/12] [tls_validation] Allow to toggle partial chain validation --- .../policy/tls_validation/apicast-policy.json | 5 +++ .../policy/tls_validation/tls_validation.lua | 6 ++- t/apicast-policy-tls_validation.t | 39 +++++++++++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/gateway/src/apicast/policy/tls_validation/apicast-policy.json b/gateway/src/apicast/policy/tls_validation/apicast-policy.json index d742a5488..a80cc9301 100644 --- a/gateway/src/apicast/policy/tls_validation/apicast-policy.json +++ b/gateway/src/apicast/policy/tls_validation/apicast-policy.json @@ -31,6 +31,11 @@ "$ref": "#/definitions/store", "title": "Certificate Whitelist", "description": "Individual certificates and CA certificates to be whitelisted." + }, + "allow_partial_chain": { + "description": "Allow certificate verification with only an intermediate certificate", + "type": "boolean", + "default": true } } } diff --git a/gateway/src/apicast/policy/tls_validation/tls_validation.lua b/gateway/src/apicast/policy/tls_validation/tls_validation.lua index 151bd97c3..b58dd2dfd 100644 --- a/gateway/src/apicast/policy/tls_validation/tls_validation.lua +++ b/gateway/src/apicast/policy/tls_validation/tls_validation.lua @@ -42,6 +42,7 @@ function _M.new(config) 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.allow_partial_chain return self end @@ -76,7 +77,10 @@ function _M:access() 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 diff --git a/t/apicast-policy-tls_validation.t b/t/apicast-policy-tls_validation.t index 15b2c66ab..84bc8765e 100644 --- a/t/apicast-policy-tls_validation.t +++ b/t/apicast-policy-tls_validation.t @@ -353,3 +353,42 @@ No required TLS certificate was sent --- no_error_log [error] --- user_files fixture=CA/files.pl eval + + + +=== TEST 9: TLS Client Certificate with intermediate certificate fails when +allow_partial_chain set to false +--- configuration eval +use JSON qw(to_json); +use File::Slurp qw(read_file); + +to_json({ + services => [{ + proxy => { + hosts => ['test'], + policy_chain => [ + { name => 'apicast.policy.tls_validation', + configuration => { + whitelist => [ + { pem_certificate => CORE::join('', read_file('t/fixtures/CA/client.crt')) } + ], + allow_partial_chain => JSON::false + } + }, + { name => 'apicast.policy.echo' }, + ] + } + }] +}); +--- test env +proxy_ssl_verify on; +proxy_ssl_trusted_certificate $TEST_NGINX_SERVER_ROOT/html/ca.crt; +proxy_ssl_certificate $TEST_NGINX_SERVER_ROOT/html/client.crt; +proxy_ssl_certificate_key $TEST_NGINX_SERVER_ROOT/html/client.key; +proxy_pass https://$server_addr:$apicast_port/t; +proxy_set_header Host test; +log_by_lua_block { collectgarbage() } +--- error_code: 400 +--- error_log +unable to get local issuer certificat +--- user_files fixture=CA/files.pl eval From 11ad5f8cbb321f8c7a39e7be3d3664aa88dfcd52 Mon Sep 17 00:00:00 2001 From: An Tran Date: Tue, 29 Oct 2024 13:38:33 +1000 Subject: [PATCH 02/12] [tls_validation] Adding support for Certificate Revocation List (CRL) --- .../apicast/policy/tls_validation/README.md | 20 ++++- .../policy/tls_validation/apicast-policy.json | 55 ++++++++++++ .../policy/tls_validation/tls_validation.lua | 39 +++++++-- .../tls_validation/tls_validation_spec.lua | 26 ++++++ t/apicast-policy-tls_validation.t | 85 ++++++++++++++++++- t/fixtures/CA/Makefile | 11 ++- t/fixtures/CA/crl.pem | 9 ++ t/fixtures/CA/crl/crlnumber | 1 + t/fixtures/CA/crl/index.txt | 2 + t/fixtures/CA/crl/index.txt.attr | 1 + t/fixtures/CA/crl/index.txt.old | 1 + t/fixtures/CA/crl/serial | 1 + t/fixtures/CA/crl_openssl.conf | 35 ++++++++ t/fixtures/CA/files.pl | 2 + t/fixtures/CA/revoked_client.crt | 12 +++ t/fixtures/CA/revoked_client.key | 5 ++ 16 files changed, 297 insertions(+), 8 deletions(-) create mode 100644 t/fixtures/CA/crl.pem create mode 100644 t/fixtures/CA/crl/crlnumber create mode 100644 t/fixtures/CA/crl/index.txt create mode 100644 t/fixtures/CA/crl/index.txt.attr create mode 100644 t/fixtures/CA/crl/index.txt.old create mode 100644 t/fixtures/CA/crl/serial create mode 100644 t/fixtures/CA/crl_openssl.conf create mode 100644 t/fixtures/CA/revoked_client.crt create mode 100644 t/fixtures/CA/revoked_client.key diff --git a/gateway/src/apicast/policy/tls_validation/README.md b/gateway/src/apicast/policy/tls_validation/README.md index 97adb15d2..026ccf122 100644 --- a/gateway/src/apicast/policy/tls_validation/README.md +++ b/gateway/src/apicast/policy/tls_validation/README.md @@ -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. @@ -28,3 +29,20 @@ 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-----"} + ] + } +} +``` diff --git a/gateway/src/apicast/policy/tls_validation/apicast-policy.json b/gateway/src/apicast/policy/tls_validation/apicast-policy.json index a80cc9301..348f53e9d 100644 --- a/gateway/src/apicast/policy/tls_validation/apicast-policy.json +++ b/gateway/src/apicast/policy/tls_validation/apicast-policy.json @@ -24,6 +24,13 @@ "items": { "$ref": "#/definitions/certificate" } + }, + "revoke": { + "$id": "#/definitions/revoke", + "type": "array", + "items": { + "$ref": "#/definitions/certificate" + } } }, "properties": { @@ -36,6 +43,54 @@ "description": "Allow certificate verification with only an intermediate certificate", "type": "boolean", "default": true + }, + "revocation_check_type": { + "title": "Certificate Revocation Check type", + "type": "string", + "oneOf": [ + { + "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" + } + } + } + ] } } } diff --git a/gateway/src/apicast/policy/tls_validation/tls_validation.lua b/gateway/src/apicast/policy/tls_validation/tls_validation.lua index b58dd2dfd..fb6b63408 100644 --- a/gateway/src/apicast/policy/tls_validation/tls_validation.lua +++ b/gateway/src/apicast/policy/tls_validation/tls_validation.lua @@ -4,6 +4,7 @@ local policy = require('apicast.policy') 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 ipairs = ipairs @@ -33,6 +34,29 @@ local function init_trusted_store(store, certificates) 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')) + end + else + ngx.log(ngx.WARN, 'failed to add crl certificate, err: ', err) + + if debug then + ngx.log(ngx.DEBUG, 'certificate: ', certificate.pem_certificate) + end + end + end + return store +end + local new = _M.new --- Initialize a tls_validation -- @tparam[opt] table config Policy configuration. @@ -42,7 +66,11 @@ function _M.new(config) 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.allow_partial_chain + 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 {}) + end return self end @@ -84,16 +112,17 @@ function _M:access() -- 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 + return true, nil end return _M diff --git a/spec/policy/tls_validation/tls_validation_spec.lua b/spec/policy/tls_validation/tls_validation_spec.lua index bf06d401f..c059ab2d0 100644 --- a/spec/policy/tls_validation/tls_validation_spec.lua +++ b/spec/policy/tls_validation/tls_validation_spec.lua @@ -3,7 +3,9 @@ local _M = require('apicast.policy.tls_validation') local server = assert(fixture('CA', 'server.crt')) local CA = assert(fixture('CA', 'intermediate-ca.crt')) local client = assert(fixture('CA', 'client.crt')) +local revoked_client = assert(fixture('CA', 'revoked_client.crt')) local ssl_helper = require 'ssl_helper' +local crl = assert(fixture('CA', 'crl.pem')) describe('tls_validation policy', function() describe('.new', function() @@ -74,5 +76,29 @@ describe('tls_validation policy', function() assert.is_true(policy:access()) end) + + it('accepts CRL', function() + ngx.var = { ssl_client_raw_cert = client } + + local policy = _M.new({ + whitelist = { { pem_certificate = CA }}, + revocation_check_type = "crl", + revoke_list = { { pem_certificate = crl }}}) + + assert.is_true(policy:access()) + end) + + it('reject revoked certificate', function() + ngx.var = { ssl_client_raw_cert = revoked_client } + + local policy = _M.new({ + whitelist = { { pem_certificate = CA }}, + revocation_check_type = "crl", + revoke_list = { { pem_certificate = crl }}}) + + policy:access() + assert.stub(ngx.exit).was_called_with(400) + assert.stub(ngx.say).was_called_with([[TLS certificate validation failed]]) + end) end) end) diff --git a/t/apicast-policy-tls_validation.t b/t/apicast-policy-tls_validation.t index 84bc8765e..37d80984c 100644 --- a/t/apicast-policy-tls_validation.t +++ b/t/apicast-policy-tls_validation.t @@ -390,5 +390,88 @@ proxy_set_header Host test; log_by_lua_block { collectgarbage() } --- error_code: 400 --- error_log -unable to get local issuer certificat +unable to get local issuer certificate +--- user_files fixture=CA/files.pl eval + + + +=== TEST 10: TLS Client Certificate with Certificate Revoke List (CRL) +--- configuration eval +use JSON qw(to_json); +use File::Slurp qw(read_file); + +to_json({ + services => [{ + proxy => { + hosts => ['test'], + policy_chain => [ + { name => 'apicast.policy.tls_validation', + configuration => { + whitelist => [ + { pem_certificate => CORE::join('', read_file('t/fixtures/CA/intermediate-ca.crt')) } + ], + revoke_list => [ + { pem_certificate => CORE::join('', read_file('t/fixtures/CA/crl.pem')) } + ], + revocation_check_type => 'crl' + } + }, + { name => 'apicast.policy.echo' }, + ] + } + }] +}); +--- test env +proxy_ssl_verify on; +proxy_ssl_trusted_certificate $TEST_NGINX_SERVER_ROOT/html/ca.crt; +proxy_ssl_certificate $TEST_NGINX_SERVER_ROOT/html/client.crt; +proxy_ssl_certificate_key $TEST_NGINX_SERVER_ROOT/html/client.key; +proxy_pass https://$server_addr:$apicast_port/t; +proxy_set_header Host test; +log_by_lua_block { collectgarbage() } +--- error_code: 200 +--- no_error_log +[error] +--- user_files fixture=CA/files.pl eval + + + +=== TEST 11: TLS Client Certificate with Certificate Revoke List (CRL) and +revoked certificate +--- configuration eval +use JSON qw(to_json); +use File::Slurp qw(read_file); + +to_json({ + services => [{ + proxy => { + hosts => ['test'], + policy_chain => [ + { name => 'apicast.policy.tls_validation', + configuration => { + whitelist => [ + { pem_certificate => CORE::join('', read_file('t/fixtures/CA/intermediate-ca.crt')) } + ], + revoke_list => [ + { pem_certificate => CORE::join('', read_file('t/fixtures/CA/crl.pem')) } + ], + revocation_check_type => 'crl' + } + }, + { name => 'apicast.policy.echo' }, + ] + } + }] +}); +--- test env +proxy_ssl_verify on; +proxy_ssl_trusted_certificate $TEST_NGINX_SERVER_ROOT/html/ca.crt; +proxy_ssl_certificate $TEST_NGINX_SERVER_ROOT/html/revoked_client.crt; +proxy_ssl_certificate_key $TEST_NGINX_SERVER_ROOT/html/revoked_client.key; +proxy_pass https://$server_addr:$apicast_port/t; +proxy_set_header Host test; +log_by_lua_block { collectgarbage() } +--- error_code: 400 +--- error_log +TLS certificate validation failed, err: certificate revoked --- user_files fixture=CA/files.pl eval diff --git a/t/fixtures/CA/Makefile b/t/fixtures/CA/Makefile index e4046bda0..c0ae7f937 100644 --- a/t/fixtures/CA/Makefile +++ b/t/fixtures/CA/Makefile @@ -1,4 +1,4 @@ -all: client.key client.crt server.key server.crt ca-bundle.crt: +all: client.key client.crt server.key server.crt ca-bundle.crt crl.pem EXPIRATION := 87600h # 10 years @@ -27,3 +27,12 @@ client.crt client.key: intermediate-ca.crt intermediate-ca.key step certificate create client client.crt client.key --profile leaf \ --ca ./intermediate-ca.crt --ca-key ./intermediate-ca.key \ --no-password --insecure --not-after=${EXPIRATION} + +revoke_client.crt revoke_client.key: + step certificate create client revoked_client.crt revoked_client.key --profile leaf \ + --ca ./intermediate-ca.crt --ca-key ./intermediate-ca.key \ + --no-password --insecure --not-after=${EXPIRATION} + +crl.pem: revoke_client.crt revoke_client.key + openssl ca --revoke revoked_client.crt -config ./crl_openssl.conf + openssl ca -config crl_openssl.conf -gencrl -out crl.pem -crldays 3650 diff --git a/t/fixtures/CA/crl.pem b/t/fixtures/CA/crl.pem new file mode 100644 index 000000000..f477d2880 --- /dev/null +++ b/t/fixtures/CA/crl.pem @@ -0,0 +1,9 @@ +-----BEGIN X509 CRL----- +MIIBRzCB7wIBATAKBggqhkjOPQQDAjAaMRgwFgYDVQQDEw9pbnRlcm1lZGlhdGUt +Y2EXDTI0MTAzMDA1NDQyNVoXDTM0MTAyODA1NDQyNVowRzAhAhAMx1ybLzQvkRKJ +0xUQrJteFw0yNDEwMzAwNTQ0MjVaMCICEQDD68D5lvxp5CXYFTVYWT0FFw0yNDEw +MzAwNTI0MjJaoFswWTBKBgNVHSMEQzBBgBTaEjeKcCSJEuUcnwUlddtXhKhMcaEW +pBQwEjEQMA4GA1UEAxMHcm9vdC1jYYIRAPvf2lJeYv9BaUhvVQzCD8wwCwYDVR0U +BAQCAhACMAoGCCqGSM49BAMCA0cAMEQCIHmFVLW2NnerBataPZSmGfyfQaXsjT1W +a4j7Hp+5IA2zAiBOXNqOIqElfShj6UVVc6j2geuUtkD6NLjOPRRqoEUw4A== +-----END X509 CRL----- diff --git a/t/fixtures/CA/crl/crlnumber b/t/fixtures/CA/crl/crlnumber new file mode 100644 index 000000000..baccd0398 --- /dev/null +++ b/t/fixtures/CA/crl/crlnumber @@ -0,0 +1 @@ +1003 diff --git a/t/fixtures/CA/crl/index.txt b/t/fixtures/CA/crl/index.txt new file mode 100644 index 000000000..3c1672366 --- /dev/null +++ b/t/fixtures/CA/crl/index.txt @@ -0,0 +1,2 @@ +R 341028052243Z 241030052422Z C3EBC0F996FC69E425D8153558593D05 unknown /CN=client +R 341028054420Z 241030054425Z 0CC75C9B2F342F911289D31510AC9B5E unknown /CN=client diff --git a/t/fixtures/CA/crl/index.txt.attr b/t/fixtures/CA/crl/index.txt.attr new file mode 100644 index 000000000..8f7e63a34 --- /dev/null +++ b/t/fixtures/CA/crl/index.txt.attr @@ -0,0 +1 @@ +unique_subject = yes diff --git a/t/fixtures/CA/crl/index.txt.old b/t/fixtures/CA/crl/index.txt.old new file mode 100644 index 000000000..b9d17d432 --- /dev/null +++ b/t/fixtures/CA/crl/index.txt.old @@ -0,0 +1 @@ +R 341028052243Z 241030052422Z C3EBC0F996FC69E425D8153558593D05 unknown /CN=client diff --git a/t/fixtures/CA/crl/serial b/t/fixtures/CA/crl/serial new file mode 100644 index 000000000..83b33d238 --- /dev/null +++ b/t/fixtures/CA/crl/serial @@ -0,0 +1 @@ +1000 diff --git a/t/fixtures/CA/crl_openssl.conf b/t/fixtures/CA/crl_openssl.conf new file mode 100644 index 000000000..6871ddadb --- /dev/null +++ b/t/fixtures/CA/crl_openssl.conf @@ -0,0 +1,35 @@ +# OpenSSL configuration for CRL generation +# +[ ca ] +default_ca = CA_default # The default ca section + +[ CA_default ] +dir = ./ +certs = $dir/certs # Where the issued certs are kept +crl_dir = $dir/crl # Where the issued crl are kept +database = $dir/crl/index.txt # database index file. +new_certs_dir = $dir/newcerts # default place for new certs. +serial = $dir/crl/serial # The current serial number + +private_key = intermediate-ca.key +certificate = intermediate-ca.crt + +# Comment out the following two lines for the "traditional" +# (and highly broken) format. +name_opt = ca_default # Subject Name options +cert_opt = ca_default # Certificate field options + +# For certificate revocation lists. +crlnumber = $dir/crl/crlnumber +crl = $dir/crl/intermediate.crl.pem +crl_extensions = crl_ext +default_days = 365 # how long to certify for +default_crl_days = 30 # How long before the next CRL +default_md = sha256 # use public key default MD +preserve = no # keep passed DN ordering + +[ crl_ext ] +# CRL extensions. +# Only issuerAltName and authorityKeyIdentifier make any sense in a CRL. +# issuerAltName=issuer:copy +authorityKeyIdentifier=keyid:always,issuer:always diff --git a/t/fixtures/CA/files.pl b/t/fixtures/CA/files.pl index 64fa1b284..fd6f351d4 100644 --- a/t/fixtures/CA/files.pl +++ b/t/fixtures/CA/files.pl @@ -21,4 +21,6 @@ read_file('t/fixtures/CA/root-ca.crt'), ) ], [ "client.key" => CORE::join('', read_file('t/fixtures/CA/client.key')) ], + [ "revoked_client.crt" => CORE::join('',read_file('t/fixtures/CA/revoked_client.crt')) ], + [ "revoked_client.key" => CORE::join('', read_file('t/fixtures/CA/revoked_client.key')) ], ] diff --git a/t/fixtures/CA/revoked_client.crt b/t/fixtures/CA/revoked_client.crt new file mode 100644 index 000000000..f58f87b8c --- /dev/null +++ b/t/fixtures/CA/revoked_client.crt @@ -0,0 +1,12 @@ +-----BEGIN CERTIFICATE----- +MIIBrzCCAVWgAwIBAgIQDMdcmy80L5ESidMVEKybXjAKBggqhkjOPQQDAjAaMRgw +FgYDVQQDEw9pbnRlcm1lZGlhdGUtY2EwHhcNMjQxMDMwMDU0NDIwWhcNMzQxMDI4 +MDU0NDIwWjARMQ8wDQYDVQQDEwZjbGllbnQwWTATBgcqhkjOPQIBBggqhkjOPQMB +BwNCAAQcDPD//JMA0j46aYVdsTh5Eb5KIfNhoJcHkhVRjMbhhsZGcMD+pysHvUlQ +7lwprlYOMSXgnPJUM7IUPq9eCMvso4GFMIGCMA4GA1UdDwEB/wQEAwIHgDAdBgNV +HSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwHQYDVR0OBBYEFNPTMaJOoyQXFMEz +6Cf7WfWFLlXxMB8GA1UdIwQYMBaAFNoSN4pwJIkS5RyfBSV121eEqExxMBEGA1Ud +EQQKMAiCBmNsaWVudDAKBggqhkjOPQQDAgNIADBFAiAmcpvUCOettopMTnaCoIaX +k3/Xw2GpM5OB90YWvbs69wIhAJhhK6+73p3yMu6MvTn4bSLL8eLAWrDcAuSX17vM +OAOb +-----END CERTIFICATE----- diff --git a/t/fixtures/CA/revoked_client.key b/t/fixtures/CA/revoked_client.key new file mode 100644 index 000000000..86eb16452 --- /dev/null +++ b/t/fixtures/CA/revoked_client.key @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEINyI8SLVOG+ufV0+tAzwhRT9/Xe19z//O0xci/SqZJCAoAoGCCqGSM49 +AwEHoUQDQgAEHAzw//yTANI+OmmFXbE4eRG+SiHzYaCXB5IVUYzG4YbGRnDA/qcr +B71JUO5cKa5WDjEl4JzyVDOyFD6vXgjL7A== +-----END EC PRIVATE KEY----- From d9fb83bd255b7f9ea44cdfa44e0b59d3690b6d8d Mon Sep 17 00:00:00 2001 From: An Tran Date: Mon, 18 Nov 2024 18:10:34 +1000 Subject: [PATCH 03/12] [tls_validation] Validate client certificate with Online Certificate Status Protocol (OCSP) --- .../apicast/policy/tls_validation/README.md | 30 ++ .../policy/tls_validation/apicast-policy.json | 45 ++ .../policy/tls_validation/ocsp_validation.lua | 97 +++++ .../policy/tls_validation/tls_validation.lua | 15 + gateway/src/resty/http_ng/backend/resty.lua | 4 + gateway/src/resty/tls.lua | 36 ++ spec/resty/http_ng/backend/resty_spec.lua | 6 +- spec/resty/http_ng_spec.lua | 8 +- t/apicast-policy-tls_validation.t | 391 ++++++++++++++++++ t/fixtures/ocsp/README.md | 58 +++ t/fixtures/ocsp/ca-key.pem | 27 ++ t/fixtures/ocsp/ca.csr | 17 + t/fixtures/ocsp/ca.pem | 23 ++ t/fixtures/ocsp/cfssl/ca_csr.json | 16 + t/fixtures/ocsp/cfssl/cfssl_config.json | 32 ++ .../ocsp/cfssl/intermediate_ca_csr.json | 16 + t/fixtures/ocsp/cfssl/leaf_csr.json | 20 + t/fixtures/ocsp/cfssl/ocsp_csr.json | 16 + t/fixtures/ocsp/chain.pem | 74 ++++ t/fixtures/ocsp/client-key.pem | 27 ++ t/fixtures/ocsp/client.csr | 18 + t/fixtures/ocsp/client.pem | 26 ++ t/fixtures/ocsp/files.pl | 15 + t/fixtures/ocsp/intermediate_ca-key.pem | 27 ++ t/fixtures/ocsp/intermediate_ca.csr | 18 + t/fixtures/ocsp/intermediate_ca.pem | 25 ++ t/fixtures/ocsp/ocsp-key.pem | 27 ++ .../ocsp/ocsp-response-good-response.der | Bin 0 -> 1708 bytes .../ocsp/ocsp-response-revoked-response.der | Bin 0 -> 1732 bytes t/fixtures/ocsp/ocsp.csr | 17 + t/fixtures/ocsp/ocsp.pem | 26 ++ t/fixtures/ocsp/server-key.pem | 27 ++ t/fixtures/ocsp/server.csr | 18 + t/fixtures/ocsp/server.pem | 26 ++ t/fixtures/ocsp/wrong-issuer-order-chain.pem | 74 ++++ 35 files changed, 1294 insertions(+), 8 deletions(-) create mode 100644 gateway/src/apicast/policy/tls_validation/ocsp_validation.lua create mode 100644 t/fixtures/ocsp/README.md create mode 100644 t/fixtures/ocsp/ca-key.pem create mode 100644 t/fixtures/ocsp/ca.csr create mode 100644 t/fixtures/ocsp/ca.pem create mode 100644 t/fixtures/ocsp/cfssl/ca_csr.json create mode 100644 t/fixtures/ocsp/cfssl/cfssl_config.json create mode 100644 t/fixtures/ocsp/cfssl/intermediate_ca_csr.json create mode 100644 t/fixtures/ocsp/cfssl/leaf_csr.json create mode 100644 t/fixtures/ocsp/cfssl/ocsp_csr.json create mode 100644 t/fixtures/ocsp/chain.pem create mode 100644 t/fixtures/ocsp/client-key.pem create mode 100644 t/fixtures/ocsp/client.csr create mode 100644 t/fixtures/ocsp/client.pem create mode 100644 t/fixtures/ocsp/files.pl create mode 100644 t/fixtures/ocsp/intermediate_ca-key.pem create mode 100644 t/fixtures/ocsp/intermediate_ca.csr create mode 100644 t/fixtures/ocsp/intermediate_ca.pem create mode 100644 t/fixtures/ocsp/ocsp-key.pem create mode 100644 t/fixtures/ocsp/ocsp-response-good-response.der create mode 100644 t/fixtures/ocsp/ocsp-response-revoked-response.der create mode 100644 t/fixtures/ocsp/ocsp.csr create mode 100644 t/fixtures/ocsp/ocsp.pem create mode 100644 t/fixtures/ocsp/server-key.pem create mode 100644 t/fixtures/ocsp/server.csr create mode 100644 t/fixtures/ocsp/server.pem create mode 100644 t/fixtures/ocsp/wrong-issuer-order-chain.pem diff --git a/gateway/src/apicast/policy/tls_validation/README.md b/gateway/src/apicast/policy/tls_validation/README.md index 026ccf122..df9316ced 100644 --- a/gateway/src/apicast/policy/tls_validation/README.md +++ b/gateway/src/apicast/policy/tls_validation/README.md @@ -46,3 +46,33 @@ With Certificate Revocation List (CRL) } } ``` + +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://:" + } +} +``` diff --git a/gateway/src/apicast/policy/tls_validation/apicast-policy.json b/gateway/src/apicast/policy/tls_validation/apicast-policy.json index 348f53e9d..117e87bbc 100644 --- a/gateway/src/apicast/policy/tls_validation/apicast-policy.json +++ b/gateway/src/apicast/policy/tls_validation/apicast-policy.json @@ -48,6 +48,12 @@ "title": "Certificate Revocation Check type", "type": "string", "oneOf": [ + { + "enum": [ + "ocsp" + ], + "title": "Enables OCSP validation of the client certificate." + }, { "enum": [ "crl" @@ -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" + } + } } ] } diff --git a/gateway/src/apicast/policy/tls_validation/ocsp_validation.lua b/gateway/src/apicast/policy/tls_validation/ocsp_validation.lua new file mode 100644 index 000000000..f78bfabb1 --- /dev/null +++ b/gateway/src/apicast/policy/tls_validation/ocsp_validation.lua @@ -0,0 +1,97 @@ +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 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(ocsp_responder_url) + 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 + if ocsp_responder_url and ocsp_responder_url ~= "" then + ocsp_url = ocsp_responder_url + 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") + end + end + + if not ocsp_url or ocsp_url == "" then + return nil, " invalid OCSP responder URL" + 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_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 diff --git a/gateway/src/apicast/policy/tls_validation/tls_validation.lua b/gateway/src/apicast/policy/tls_validation/tls_validation.lua index fb6b63408..97582ad1c 100644 --- a/gateway/src/apicast/policy/tls_validation/tls_validation.lua +++ b/gateway/src/apicast/policy/tls_validation/tls_validation.lua @@ -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_validation") local ipairs = ipairs local tostring = tostring @@ -70,6 +71,9 @@ function _M.new(config) 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 + -- TODO: should we set empty string as default value? + self.ocsp_responder_url = config and config.ocsp_responder_url end return self @@ -85,6 +89,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 @@ -122,6 +127,16 @@ function _M:access() return ngx.exit(ngx.status) end + if self.revocation_type == "ocsp" then + ok, err = ocsp.check_revocation_status(self.ocsp_responder_url) + 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 diff --git a/gateway/src/resty/http_ng/backend/resty.lua b/gateway/src/resty/http_ng/backend/resty.lua index 595286b9f..0dfb748ef 100644 --- a/gateway/src/resty/http_ng/backend/resty.lua +++ b/gateway/src/resty/http_ng/backend/resty.lua @@ -36,6 +36,10 @@ backend.send = function(_, request) local res local httpc, err = http_proxy.new(request) + if err then + return nil, err + end + if httpc then res, err = send(httpc, request) end diff --git a/gateway/src/resty/tls.lua b/gateway/src/resty/tls.lua index 0b5a65e7f..bb2a3db6f 100644 --- a/gateway/src/resty/tls.lua +++ b/gateway/src/resty/tls.lua @@ -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( @@ -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 @@ -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 diff --git a/spec/resty/http_ng/backend/resty_spec.lua b/spec/resty/http_ng/backend/resty_spec.lua index 5cc2fd50f..9f4e5ff7c 100755 --- a/spec/resty/http_ng/backend/resty_spec.lua +++ b/spec/resty/http_ng/backend/resty_spec.lua @@ -37,10 +37,8 @@ describe('resty backend', function() local req = { method = method, url = 'http://0.0.0.0:0/' } local response, err = backend:send(req) - assert.falsy(err) - assert.truthy(response.error) - assert.falsy(response.ok) - assert.same(req, response.request) + assert.falsy(response) + assert.equal("connection refused", err) end) context('http proxy is set', function() diff --git a/spec/resty/http_ng_spec.lua b/spec/resty/http_ng_spec.lua index 280b0f48b..1fdcb8b85 100755 --- a/spec/resty/http_ng_spec.lua +++ b/spec/resty/http_ng_spec.lua @@ -224,18 +224,18 @@ describe('http_ng', function() end) describe('when there is error #network', function() - local response + local response, err before_each(function() http = http_ng.new{ backend = resty_backend } - response = http.get('http://127.0.0.1:1') + response, err = http.get('http://127.0.0.1:1') end) it('is not ok', function() - assert.equal(false, response.ok) + assert.falsy(response) end) it('has error', function() - assert.equal('string', type(response.error)) -- depending on the openresty version it can be "timeout" or "connection refused" + assert.equal("connection refused", err) end) end) diff --git a/t/apicast-policy-tls_validation.t b/t/apicast-policy-tls_validation.t index 37d80984c..120d5d0d8 100644 --- a/t/apicast-policy-tls_validation.t +++ b/t/apicast-policy-tls_validation.t @@ -8,6 +8,24 @@ env_to_apicast( 'APICAST_HTTPS_SESSION_REUSE' => 'on', ); +add_block_preprocessor(sub { + my $block = shift; + my $custom_config = $block->custom_config; + my $sites_d = $block->sites_d || ''; + + + if (defined $custom_config) { + + $sites_d .= <<_EOC_; + $custom_config +_EOC_ + + $block->set_value('sites_d', $sites_d) + } +}); + +$ENV{TEST_NGINX_HTML_DIR} ||= "$Test::Nginx::Util::ServRoot/html"; + run_tests(); __DATA__ @@ -475,3 +493,376 @@ log_by_lua_block { collectgarbage() } --- error_log TLS certificate validation failed, err: certificate revoked --- user_files fixture=CA/files.pl eval + + + +=== TEST 12: TLS Client Certificate with OCSP and cert without no responder URL +--- configuration eval +use JSON qw(to_json); +use File::Slurp qw(read_file); + +to_json({ + services => [{ + proxy => { + hosts => ['test'], + policy_chain => [ + { name => 'apicast.policy.tls_validation', + configuration => { + whitelist => [ + { pem_certificate => CORE::join('', read_file('t/fixtures/CA/intermediate-ca.crt')) } + ], + revocation_check_type => 'ocsp' + } + }, + { name => 'apicast.policy.echo' }, + ] + } + }] +}); +--- test env +proxy_ssl_verify on; +proxy_ssl_trusted_certificate $TEST_NGINX_SERVER_ROOT/html/ca.crt; +proxy_ssl_certificate $TEST_NGINX_SERVER_ROOT/html/client.crt; +proxy_ssl_certificate_key $TEST_NGINX_SERVER_ROOT/html/client.key; +proxy_pass https://$server_addr:$apicast_port/t; +proxy_set_header Host test; +log_by_lua_block { collectgarbage() } +--- error_code: 400 +--- error_log +TLS certificate validation failed, err: could not extract OCSP responder URL, the client certificate may be missing the required extensions +--- user_files fixture=CA/files.pl eval + + + +=== TEST 13: TLS Client Certificate with OCSP and cert with ocsp supported (no issuer) +--- env eval +( + 'APICAST_HTTPS_CERTIFICATE' => "$Test::Nginx::Util::ServRoot/html/server.pem", + 'APICAST_HTTPS_CERTIFICATE_KEY' => "$Test::Nginx::Util::ServRoot/html/server-key.pem", + 'APICAST_HTTPS_SESSION_REUSE' => 'on', +) +--- configuration eval +use JSON qw(to_json); +use File::Slurp qw(read_file); +to_json({ + services => [{ + proxy => { + hosts => ['test.com'], + policy_chain => [ + { name => 'apicast.policy.tls_validation', + configuration => { + whitelist => [ + { pem_certificate => CORE::join('', read_file('t/fixtures/ocsp/intermediate_ca.pem')) } + ], + revocation_check_type => 'ocsp' + } + }, + { name => 'apicast.policy.echo' }, + ] + } + }] +}); +--- test env +proxy_ssl_verify on; +proxy_ssl_trusted_certificate $TEST_NGINX_SERVER_ROOT/html/ca.pem; +proxy_ssl_certificate $TEST_NGINX_SERVER_ROOT/html/client.pem; +proxy_ssl_certificate_key $TEST_NGINX_SERVER_ROOT/html/client-key.pem; +proxy_pass https://$server_addr:$apicast_port/t; +proxy_set_header Host test.com; +log_by_lua_block { collectgarbage() } +--- error_code: 400 +--- error_log +no issuer certificate in chain +--- user_files fixture=ocsp/files.pl eval + + + +=== TEST 14: TLS Client Certificate with OCSP and cert with ocsp supported (issuer +cert not next to the leaf cert) +--- env eval +( + 'APICAST_HTTPS_CERTIFICATE' => "$Test::Nginx::Util::ServRoot/html/server.pem", + 'APICAST_HTTPS_CERTIFICATE_KEY' => "$Test::Nginx::Util::ServRoot/html/server-key.pem", + 'APICAST_HTTPS_SESSION_REUSE' => 'on', + 'APICAST_HTTPS_VERIFY_DEPTH' => 2 +) +--- configuration eval +use JSON qw(to_json); +use File::Slurp qw(read_file); +to_json({ + services => [{ + proxy => { + hosts => ['test'], + policy_chain => [ + { name => 'apicast.policy.tls_validation', + configuration => { + whitelist => [ + { pem_certificate => CORE::join('', read_file('t/fixtures/ocsp/intermediate_ca.pem')) } + ], + revocation_check_type => 'ocsp' + } + }, + { name => 'apicast.policy.echo' }, + ] + } + }] +}); +--- test env +proxy_ssl_verify on; +proxy_ssl_trusted_certificate $TEST_NGINX_SERVER_ROOT/html/ca.pem; +proxy_ssl_certificate $TEST_NGINX_SERVER_ROOT/html/wrong-issuer-order-chain.pem; +proxy_ssl_certificate_key $TEST_NGINX_SERVER_ROOT/html/client-key.pem; +proxy_pass https://$server_addr:$apicast_port/t; +proxy_set_header Host test; +log_by_lua_block { collectgarbage() } +--- error_code: 400 +--- error_log +issuer certificate not next to leaf +--- user_files fixture=ocsp/files.pl eval + + + +=== TEST 15: TLS Client Certificate with OCSP and unreachable OCSP responder URL +--- env eval +( + 'APICAST_HTTPS_CERTIFICATE' => "$Test::Nginx::Util::ServRoot/html/server.pem", + 'APICAST_HTTPS_CERTIFICATE_KEY' => "$Test::Nginx::Util::ServRoot/html/server-key.pem", + 'APICAST_HTTPS_SESSION_REUSE' => 'on', + 'APICAST_HTTPS_VERIFY_DEPTH' => 2 +) +--- configuration eval +use JSON qw(to_json); +use File::Slurp qw(read_file); +to_json({ + services => [{ + proxy => { + hosts => ['test'], + policy_chain => [ + { name => 'apicast.policy.tls_validation', + configuration => { + whitelist => [ + { pem_certificate => CORE::join('', read_file('t/fixtures/ocsp/intermediate_ca.pem')) } + ], + revocation_check_type => 'ocsp' + } + }, + { name => 'apicast.policy.echo' }, + ] + } + }] +}); +--- test env +proxy_ssl_verify on; +proxy_ssl_trusted_certificate $TEST_NGINX_SERVER_ROOT/html/ca.pem; +proxy_ssl_certificate $TEST_NGINX_SERVER_ROOT/html/chain.pem; +proxy_ssl_certificate_key $TEST_NGINX_SERVER_ROOT/html/client-key.pem; +proxy_pass https://$server_addr:$apicast_port/t; +proxy_set_header Host test; +log_by_lua_block { collectgarbage() } +--- error_code: 400 +--- error_log +ocsp-responder.test could not be resolved (3: Host not found) +--- user_files fixture=ocsp/files.pl eval + + + +=== TEST 15: TLS Client Certificate with OCSP and invalid OCSP respond +--- init eval +$Test::Nginx::Util::ENDPOINT_SSL_PORT = Test::APIcast::get_random_port(); +--- env eval +( + 'APICAST_HTTPS_CERTIFICATE' => "$Test::Nginx::Util::ServRoot/html/server.pem", + 'APICAST_HTTPS_CERTIFICATE_KEY' => "$Test::Nginx::Util::ServRoot/html/server-key.pem", + 'APICAST_HTTPS_SESSION_REUSE' => 'on', + 'APICAST_HTTPS_VERIFY_DEPTH' => 2 +) +--- configuration env eval +use JSON qw(to_json); +use File::Slurp qw(read_file); +to_json({ + services => [{ + proxy => { + hosts => ['test'], + policy_chain => [ + { name => 'apicast.policy.tls_validation', + configuration => { + whitelist => [ + { pem_certificate => CORE::join('', read_file('t/fixtures/ocsp/intermediate_ca.pem')) } + ], + revocation_check_type => 'ocsp', + ocsp_responder_url => "https://127.0.0.1:$Test::Nginx::Util::ENDPOINT_SSL_PORT/ocsp", + } + }, + { name => 'apicast.policy.echo' }, + ] + } + }] +}); +--- custom_config eval +< "$Test::Nginx::Util::ServRoot/html/server.pem", + 'APICAST_HTTPS_CERTIFICATE_KEY' => "$Test::Nginx::Util::ServRoot/html/server-key.pem", + 'APICAST_HTTPS_SESSION_REUSE' => 'on', + 'APICAST_HTTPS_VERIFY_DEPTH' => 2 +) +--- backend + location /ocsp { + content_by_lua_block { + } + } +--- configuration env eval +use JSON qw(to_json); +use File::Slurp qw(read_file); +to_json({ + services => [{ + proxy => { + hosts => ['test'], + policy_chain => [ + { name => 'apicast.policy.tls_validation', + configuration => { + whitelist => [ + { pem_certificate => CORE::join('', read_file('t/fixtures/ocsp/intermediate_ca.pem')) } + ], + revocation_check_type => 'ocsp', + ocsp_responder_url => "https://127.0.0.1:$Test::Nginx::Util::ENDPOINT_SSL_PORT/ocsp", + } + }, + { name => 'apicast.policy.echo' }, + ] + } + }] +}); +--- custom_config eval +< "$Test::Nginx::Util::ServRoot/html/server.pem", + 'APICAST_HTTPS_CERTIFICATE_KEY' => "$Test::Nginx::Util::ServRoot/html/server-key.pem", + 'APICAST_HTTPS_SESSION_REUSE' => 'on', + 'APICAST_HTTPS_VERIFY_DEPTH' => 2 +) +--- backend + location /ocsp { + content_by_lua_block { + } + } +--- configuration env eval +use JSON qw(to_json); +use File::Slurp qw(read_file); +to_json({ + services => [{ + proxy => { + hosts => ['test'], + policy_chain => [ + { name => 'apicast.policy.tls_validation', + configuration => { + whitelist => [ + { pem_certificate => CORE::join('', read_file('t/fixtures/ocsp/intermediate_ca.pem')) } + ], + revocation_check_type => 'ocsp', + ocsp_responder_url => "https://127.0.0.1:$Test::Nginx::Util::ENDPOINT_SSL_PORT/ocsp", + } + }, + { name => 'apicast.policy.echo' }, + ] + } + }] +}); +--- custom_config eval +< leaf-bundle.pem +``` + +Inspect OCSP response to see what is the Next Update: + +``` +openssl ocsp -text -no_cert_verify -respin t/cert/ocsp/cfssl/ocsp-response-good-response.der | grep "Next Update" +``` + +Create an OCSP response with revoked status for the certificate: + +``` +cfssl ocspsign -ca intermediate_ca.pem -responder ocsp.pem -responder-key ocsp-key.pem -cert client.pem -status revoked -reason 1 | cfssljson -bare ocsp-response-good +``` diff --git a/t/fixtures/ocsp/ca-key.pem b/t/fixtures/ocsp/ca-key.pem new file mode 100644 index 000000000..cdf00d3c2 --- /dev/null +++ b/t/fixtures/ocsp/ca-key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAt2e0klGfWpwv2iCIlJU3Gq6T2e/d4KBph6k8PX8wyIlUWL6C +4xknOMAyUlOjUoqPdXBV9iepQioRFBonFAYtoy+WKN+V1HOkmVPSABG4FHoKKCwM +izTTC8teOJwa+oIHATcJbPIR005hFg32DwdpCqxIxVSRtP93rSXoqLM4AQQ+WFdW +hG6KfzjTMZ9D0CLzshUKRRKyQ7+gAf6ZrvirExZ7UT4rHyPUKnh1CGxrnUTdoXhU +UaBKod1MkiRlCBahT6MZhnl3gnK+VVEL0F2k56UOEbhsXnpPxVozwSTFjlcQveF8 +P4WGPI/9Q1f+pgnHBcq16OicoOVGKB4VkhO8KQIDAQABAoIBAH1/pCsfWSiaiY/8 +BRmPBVbnsNpHbY8glTW0UzlNiGcguavEKTIW6yTFN2noO3DmyYW16cx3lTVmLauQ +vb7Q/3eTv2+WlyL6Qt58Yc2UUl8Ip1yXz7mN2wBOF9oqz7ctNHfbXkLDXfk2v+5k +fUQJcRJRk7YbyVyOFylPMGYHxMHEKc4wyeF3krD+JvQIwRPf+p7kp0jWTe9//DOc +I5HilERUcdJoOeDqQLo/11ySJ5CxMBVPQbLJh6Fa5tiAzcVUWdaquPjZcH4HpON1 +wo0xDaNwURcM5RereKKsKQ2dD2ZRDMn9iX6G/pqMveRmThcQF86NUKyFm6v6s41W +DMdiAbUCgYEA4bzhZ/kaetmXck3gKp1HQA7s1w2E5K6sVA8H2OnsFo4Yog7FqP4H +y8jbIjVx7D1t/LFyhZgWLYTc3KV8QaNAv4X8XZ/S1J3t3k1/WLlzZHl2h5UAi3FI +Dh9O5N6Bxq8sBgMhI7YgQwp+NG8fI1UiKZhydcwiH1Od48CGMOpHc+sCgYEAz/4B +BqZmxGNPL+Los6PTDOiMcmngjMBdIIg6qTF5/UiKKIYTq7uCCQGiJ7pZT2OgJAEo +ZrtZCma8bKPEK+BD3KrCVj7ba9wKg0WNrSiaNopWKZnq6BN95Jwcyg9c0IiFa0oX +WlK3XLQ49DxrTlu+homxVMLbvE88GHYp8KtjzzsCgYBA7zhX1ExNsnHiK4ykNYHd +Z22xnu3val+f/oLfXsrO4ikr1Nv+9LQBZtHCUWVn83PEELdEvq8CgQmXXvMTXoat +kTk7JVJcXuGh5OERAKe060uxTKdPdVKo3VCwfWEPaixbvmK2BxnYrxgGtUBbxY2C +MZwbTpEyObddYHA5eer76QKBgDXMxgcqU7rI1VrVnsuYvKbCIFIYAPVnbK9VjZbm +LV/Ou63vwHbTcbdSrNcJ5MAcTPgDNKgeHdIK8QJI+h0/TU5u/QXCDI/BwxZhj1wS +/JDPB6qLHbAaGp5fozLA9okaRyiRaaj7bGKWW4URe9Aw+v9h7nS2UpBLDohlSEPw +kUixAoGAAiLrCo9BZeCTTVZcDLrIh7H+ptUTI4LttzaPEI57LbkPLH1w5PN4ZE2R +CeMU5pgMMTFlmBttbhx5EvT5SFGva9i43JwfkTfjclq9OKr2xpuWO5Jl7aFHUyCs +R0E2U2ZbznMO/fnH+JdifchH/Ge/O5gyDe1DxWzNaAbQ9Ci+zq0= +-----END RSA PRIVATE KEY----- diff --git a/t/fixtures/ocsp/ca.csr b/t/fixtures/ocsp/ca.csr new file mode 100644 index 000000000..a0f11263e --- /dev/null +++ b/t/fixtures/ocsp/ca.csr @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICvTCCAaUCAQAweDELMAkGA1UEBhMCQ0ExEDAOBgNVBAgTB09udGFyaW8xDzAN +BgNVBAcTBk90dGF3YTESMBAGA1UEChMJTHVhIE5naW54MRgwFgYDVQQLEw9BUElj +YXN0IFJvb3QgQ0ExGDAWBgNVBAMTD0FQSWNhc3QgUm9vdCBDQTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBALdntJJRn1qcL9ogiJSVNxquk9nv3eCgaYep +PD1/MMiJVFi+guMZJzjAMlJTo1KKj3VwVfYnqUIqERQaJxQGLaMvlijfldRzpJlT +0gARuBR6CigsDIs00wvLXjicGvqCBwE3CWzyEdNOYRYN9g8HaQqsSMVUkbT/d60l +6KizOAEEPlhXVoRuin840zGfQ9Ai87IVCkUSskO/oAH+ma74qxMWe1E+Kx8j1Cp4 +dQhsa51E3aF4VFGgSqHdTJIkZQgWoU+jGYZ5d4JyvlVRC9BdpOelDhG4bF56T8Va +M8EkxY5XEL3hfD+FhjyP/UNX/qYJxwXKtejonKDlRigeFZITvCkCAwEAAaAAMA0G +CSqGSIb3DQEBCwUAA4IBAQAXv+NudAib4JeSu5d8bM0p84HC4iarRTv1UYMULBE9 +eQRt04khs4wjtHoki0EkyE9M027W8x7otTdLsNSCh7QP4UxiZH3z/jF/Pa6WdKZy +qDtLVJ/i1fpMba0Jct8HwJo1uO0boexakR7OkgxfAn4RKZ/IR9PzsoQ10WMHbXGz +5PYWAZEGUSt4zkhqaSwYaA6oZ6HvJzo+QtkyIDnlfV6FX9v7jgGP/KadBkYbtVuj +xBgE88dNUVsBsxFxDms03SiSqtPc7YSEZO3koKC4ziZ8QUKMHRlnsvILf1nSYr6+ +osOP1tycj9Pd1WyOkhORssBZOGBMrTeCdaCHJ19Rkw5v +-----END CERTIFICATE REQUEST----- diff --git a/t/fixtures/ocsp/ca.pem b/t/fixtures/ocsp/ca.pem new file mode 100644 index 000000000..c980d667c --- /dev/null +++ b/t/fixtures/ocsp/ca.pem @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIIDwDCCAqigAwIBAgIUcSXJIwYu13xp3Iewm41m+UYc7zIwDQYJKoZIhvcNAQEL +BQAweDELMAkGA1UEBhMCQ0ExEDAOBgNVBAgTB09udGFyaW8xDzANBgNVBAcTBk90 +dGF3YTESMBAGA1UEChMJTHVhIE5naW54MRgwFgYDVQQLEw9BUEljYXN0IFJvb3Qg +Q0ExGDAWBgNVBAMTD0FQSWNhc3QgUm9vdCBDQTAeFw0yNTAyMDQwNTI1MDBaFw0z +MDAyMDMwNTI1MDBaMHgxCzAJBgNVBAYTAkNBMRAwDgYDVQQIEwdPbnRhcmlvMQ8w +DQYDVQQHEwZPdHRhd2ExEjAQBgNVBAoTCUx1YSBOZ2lueDEYMBYGA1UECxMPQVBJ +Y2FzdCBSb290IENBMRgwFgYDVQQDEw9BUEljYXN0IFJvb3QgQ0EwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3Z7SSUZ9anC/aIIiUlTcarpPZ793goGmH +qTw9fzDIiVRYvoLjGSc4wDJSU6NSio91cFX2J6lCKhEUGicUBi2jL5Yo35XUc6SZ +U9IAEbgUegooLAyLNNMLy144nBr6ggcBNwls8hHTTmEWDfYPB2kKrEjFVJG0/3et +JeioszgBBD5YV1aEbop/ONMxn0PQIvOyFQpFErJDv6AB/pmu+KsTFntRPisfI9Qq +eHUIbGudRN2heFRRoEqh3UySJGUIFqFPoxmGeXeCcr5VUQvQXaTnpQ4RuGxeek/F +WjPBJMWOVxC94Xw/hYY8j/1DV/6mCccFyrXo6Jyg5UYoHhWSE7wpAgMBAAGjQjBA +MA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQaaSka +5maUzsFM/Vnn+cUUp54uSTANBgkqhkiG9w0BAQsFAAOCAQEAIhIQpkC8EVxA6Lhm +uDutU9LGKQjp0PrzJLWCCcDCnxQ8cbaH2CUr2mw0et/cBM8R5TR35JMZe6MOGSbt +lFfWKfLE+QzNPaLhQkOI21YZMqY1kcGNmsc2UyHxhPo6PZ+J1IF/9N/P7+BjDQeN +yCd+dwXpUZlRZRyEVVXNl2yKUKLynViiNpa9rRb/mT5pqD/b523/Icva0mi6xyJZ +UbaYQBE4IozQ2YEChP3EpfUjJepZCeHWbCEzKgC61H21uSK62K33pPPN3+zKKJrk +eu2xwIugYpLwf3dvFGdW47vfUvxZdYltRkrAASDOPTpaU5dHwfR8MsoGy2h2A2jz +qOUrUg== +-----END CERTIFICATE----- diff --git a/t/fixtures/ocsp/cfssl/ca_csr.json b/t/fixtures/ocsp/cfssl/ca_csr.json new file mode 100644 index 000000000..eaefaafcc --- /dev/null +++ b/t/fixtures/ocsp/cfssl/ca_csr.json @@ -0,0 +1,16 @@ +{ + "CN": "APIcast Root CA", + "key": { + "algo": "rsa", + "size": 2048 + }, + "names": [ + { + "C": "CA", + "L": "Ottawa", + "O": "Lua Nginx", + "OU": "APIcast Root CA", + "ST": "Ontario" + } + ] +} diff --git a/t/fixtures/ocsp/cfssl/cfssl_config.json b/t/fixtures/ocsp/cfssl/cfssl_config.json new file mode 100644 index 000000000..cbdfe1c15 --- /dev/null +++ b/t/fixtures/ocsp/cfssl/cfssl_config.json @@ -0,0 +1,32 @@ +{ + "signing": { + "default": { + "ocsp_url": "https://ocsp-responder.test", + "expiry": "2190000h", + "usages": [ + "signing", + "key encipherment", + "client auth" + ] + }, + "profiles": { + "ocsp": { + "usages": ["digital signature", "ocsp signing"], + "expiry": "876000h" + }, + "intermediate": { + "usages": ["cert sign", "crl sign"], + "expiry": "2190000h", + "ca_constraint": {"is_ca": true} + }, + "server": { + "usages": ["signing", "key encipherment", "server auth"], + "expiry": "876000h" + }, + "client": { + "usages": ["signing", "key encipherment", "client auth"], + "expiry": "876000h" + } + } + } +} diff --git a/t/fixtures/ocsp/cfssl/intermediate_ca_csr.json b/t/fixtures/ocsp/cfssl/intermediate_ca_csr.json new file mode 100644 index 000000000..a6288d58c --- /dev/null +++ b/t/fixtures/ocsp/cfssl/intermediate_ca_csr.json @@ -0,0 +1,16 @@ +{ + "CN": "APIcast Intermediate CA", + "key": { + "algo": "rsa", + "size": 2048 + }, + "names": [ + { + "C": "CA", + "L": "Ottawa", + "O": "Lua Nginx", + "OU": "APIcast Intermediate CA", + "ST": "Ontario" + } + ] +} diff --git a/t/fixtures/ocsp/cfssl/leaf_csr.json b/t/fixtures/ocsp/cfssl/leaf_csr.json new file mode 100644 index 000000000..77e6b396c --- /dev/null +++ b/t/fixtures/ocsp/cfssl/leaf_csr.json @@ -0,0 +1,20 @@ +{ + "CN": "test.com", + "hosts": [ + "localhost", + "test" + ], + "key": { + "algo": "rsa", + "size": 2048 + }, + "names": [ + { + "C": "US", + "L": "San Francisco", + "O": "Customer", + "OU": "Website", + "ST": "California" + } + ] +} diff --git a/t/fixtures/ocsp/cfssl/ocsp_csr.json b/t/fixtures/ocsp/cfssl/ocsp_csr.json new file mode 100644 index 000000000..572acef4d --- /dev/null +++ b/t/fixtures/ocsp/cfssl/ocsp_csr.json @@ -0,0 +1,16 @@ +{ + "CN": "APIcast OCSP Responder", + "key": { + "algo": "rsa", + "size": 2048 + }, + "names": [ + { + "C": "CA", + "L": "Ottawa", + "O": "Lua Nginx", + "OU": "APIcast OCSP Responder", + "ST": "Ontario" + } + ] +} diff --git a/t/fixtures/ocsp/chain.pem b/t/fixtures/ocsp/chain.pem new file mode 100644 index 000000000..34d78d166 --- /dev/null +++ b/t/fixtures/ocsp/chain.pem @@ -0,0 +1,74 @@ +-----BEGIN CERTIFICATE----- +MIIEVzCCAz+gAwIBAgIUTKcAbc6mgr6a9FcHhywQ6lrsoBkwDQYJKoZIhvcNAQEL +BQAwgYgxCzAJBgNVBAYTAkNBMRAwDgYDVQQIEwdPbnRhcmlvMQ8wDQYDVQQHEwZP +dHRhd2ExEjAQBgNVBAoTCUx1YSBOZ2lueDEgMB4GA1UECxMXQVBJY2FzdCBJbnRl +cm1lZGlhdGUgQ0ExIDAeBgNVBAMTF0FQSWNhc3QgSW50ZXJtZWRpYXRlIENBMCAX +DTI1MDIwNDA4MDAwMFoYDzIxMjUwMTExMDgwMDAwWjByMQswCQYDVQQGEwJVUzET +MBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzERMA8G +A1UEChMIQ3VzdG9tZXIxEDAOBgNVBAsTB1dlYnNpdGUxETAPBgNVBAMTCHRlc3Qu +Y29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv+EK5duP98xw5Fmj +WwnC5xOdz6AnqwaxkWcfBOtCX7W2tuN7gOF3L7NXtJ5Tnyr9uCYhhL4zqRF6j6rp ++yP/jIR4ZN1UHmq+ScHG7//8NCFCu6R9h3YkkDRh5OLSdksGzIIeBRkqiyUOiJaH +vciXFOX/pcjDSQCVqeTFTzOaOmHNT4A9dC4zCNjR1Zn02LqK0IxylFp6MzXCt+lW +cGJvLniSo1KUOW6JPj1TVrgUQygns0ijb1nok5jbsCSHQW9lRK16JrCqVangABvg +V2WAoieB1RPLkCIj1ZJneHSZKsBN9HcF3x0AMoOOTHkpmpv1eqbkVXLbKWU6i305 +4Jn8lwIDAQABo4HLMIHIMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEF +BQcDAjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBR1FdZ2snwxIUD+KrVkBO9pZ9Ib +iTAfBgNVHSMEGDAWgBTYX0fMoRXzpdBMwUvIwgfO2UunvTA3BggrBgEFBQcBAQQr +MCkwJwYIKwYBBQUHMAGGG2h0dHBzOi8vb2NzcC1yZXNwb25kZXIudGVzdDAaBgNV +HREEEzARgglsb2NhbGhvc3SCBHRlc3QwDQYJKoZIhvcNAQELBQADggEBAB5talFl +dl0faUMOCAq7np0BHYCXm/txxqfIuIKetkFNortrpMYxsktkaAcFmEQV5+O6kVyC +Frxui/ogZt1d2uG/jCy8eAhKkdO2/Y3siYenT8wAS/Z3iFiBRb5KfIPcTIZp3CFj +f3y9ND6HWtMSXqNaPRBqX3hWrWIkEIer7YeSjBeceaJhrddfMyW1Wo2cOPHvz6eA +53Swz2yjYkaaU2UMBIb3xxGl4q4a8HqNyDqeI7pzobBxmhroAFS3dD0t8JpycmzM +lDvTDoUu/vtp194CcxJnGJ0kAWNemfAEriIqh6koBeXslUqFbxhXJhwYwyheArp/ +yk9pbkJR+3rrWrk= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIELzCCAxegAwIBAgIUfUh/NyK/i7Zpk5xEAE/Hycg8oGMwDQYJKoZIhvcNAQEL +BQAweDELMAkGA1UEBhMCQ0ExEDAOBgNVBAgTB09udGFyaW8xDzANBgNVBAcTBk90 +dGF3YTESMBAGA1UEChMJTHVhIE5naW54MRgwFgYDVQQLEw9BUEljYXN0IFJvb3Qg +Q0ExGDAWBgNVBAMTD0FQSWNhc3QgUm9vdCBDQTAgFw0yNTAyMDQwNTI2MDBaGA8y +Mjc0MTIwNjA1MjYwMFowgYgxCzAJBgNVBAYTAkNBMRAwDgYDVQQIEwdPbnRhcmlv +MQ8wDQYDVQQHEwZPdHRhd2ExEjAQBgNVBAoTCUx1YSBOZ2lueDEgMB4GA1UECxMX +QVBJY2FzdCBJbnRlcm1lZGlhdGUgQ0ExIDAeBgNVBAMTF0FQSWNhc3QgSW50ZXJt +ZWRpYXRlIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzdmEqwQL +fSnEfXUmMO9TMsY2y7pPr++n4vt03wuo0ZsJLWW7LZ7irtWOHwxnWeu0tfc5UmoU +bjwfxltxn+lw1I+Z+Q5pg/uSe8IwyoA051TCu7MqljPNt93pX97m6wLJnOlGbaIR +S6r8uBuYIm9//f+jZBI+7gL3CRpkRHgRn6QsYqAOyn1s8DYT6O3cGJ+ldVb75KdD +A11leT4Pxdap+iGY7rKu5F+K60QhUgStwiV8jK0f9dpx/cm1eutzC2c5WlqmQ15f +Qfq3rQOGoiVLt1USqzvELdaWff2RkULDeS63Vn4JMaY4dhRkyMNSwmx7ZixDpsx2 +sV3pqHbdAkP2QwIDAQABo4GdMIGaMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBTYX0fMoRXzpdBMwUvIwgfO2UunvTAfBgNVHSMEGDAW +gBQaaSka5maUzsFM/Vnn+cUUp54uSTA3BggrBgEFBQcBAQQrMCkwJwYIKwYBBQUH +MAGGG2h0dHBzOi8vb2NzcC1yZXNwb25kZXIudGVzdDANBgkqhkiG9w0BAQsFAAOC +AQEAs/V67Aah67xA0dRXMChK+IE6y3bTjDDo3MndJAnImCVjtbyJJyX4udBfDOKj +sCslF/bFWmLgb1w5ek0kwEOAPc5lDteQ/lPRIW7o9EnIyZico1qnR/bXG5XzQ/UZ +G2UYvHuwPqY2NEsbbPP2yxqaoIL1vhNwm3/Ga5KriiP0Zd3KgIvpfbZ4OcyJBWtm +9P1qsBDpNNkBiNm7i/D9vPDC4skvHsmm2lOBF3CNQ1+WcZRAf5oL9IfptOru79+N +qfOjWbnIMugxez+7TiFGlE81RyKocyflFMwIcGeeQD0KFD9XXnaVPQEovFIspcto +SXPTXb7wJL19OSGBOMBEJ6oS5Q== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDwDCCAqigAwIBAgIUcSXJIwYu13xp3Iewm41m+UYc7zIwDQYJKoZIhvcNAQEL +BQAweDELMAkGA1UEBhMCQ0ExEDAOBgNVBAgTB09udGFyaW8xDzANBgNVBAcTBk90 +dGF3YTESMBAGA1UEChMJTHVhIE5naW54MRgwFgYDVQQLEw9BUEljYXN0IFJvb3Qg +Q0ExGDAWBgNVBAMTD0FQSWNhc3QgUm9vdCBDQTAeFw0yNTAyMDQwNTI1MDBaFw0z +MDAyMDMwNTI1MDBaMHgxCzAJBgNVBAYTAkNBMRAwDgYDVQQIEwdPbnRhcmlvMQ8w +DQYDVQQHEwZPdHRhd2ExEjAQBgNVBAoTCUx1YSBOZ2lueDEYMBYGA1UECxMPQVBJ +Y2FzdCBSb290IENBMRgwFgYDVQQDEw9BUEljYXN0IFJvb3QgQ0EwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3Z7SSUZ9anC/aIIiUlTcarpPZ793goGmH +qTw9fzDIiVRYvoLjGSc4wDJSU6NSio91cFX2J6lCKhEUGicUBi2jL5Yo35XUc6SZ +U9IAEbgUegooLAyLNNMLy144nBr6ggcBNwls8hHTTmEWDfYPB2kKrEjFVJG0/3et +JeioszgBBD5YV1aEbop/ONMxn0PQIvOyFQpFErJDv6AB/pmu+KsTFntRPisfI9Qq +eHUIbGudRN2heFRRoEqh3UySJGUIFqFPoxmGeXeCcr5VUQvQXaTnpQ4RuGxeek/F +WjPBJMWOVxC94Xw/hYY8j/1DV/6mCccFyrXo6Jyg5UYoHhWSE7wpAgMBAAGjQjBA +MA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQaaSka +5maUzsFM/Vnn+cUUp54uSTANBgkqhkiG9w0BAQsFAAOCAQEAIhIQpkC8EVxA6Lhm +uDutU9LGKQjp0PrzJLWCCcDCnxQ8cbaH2CUr2mw0et/cBM8R5TR35JMZe6MOGSbt +lFfWKfLE+QzNPaLhQkOI21YZMqY1kcGNmsc2UyHxhPo6PZ+J1IF/9N/P7+BjDQeN +yCd+dwXpUZlRZRyEVVXNl2yKUKLynViiNpa9rRb/mT5pqD/b523/Icva0mi6xyJZ +UbaYQBE4IozQ2YEChP3EpfUjJepZCeHWbCEzKgC61H21uSK62K33pPPN3+zKKJrk +eu2xwIugYpLwf3dvFGdW47vfUvxZdYltRkrAASDOPTpaU5dHwfR8MsoGy2h2A2jz +qOUrUg== +-----END CERTIFICATE----- diff --git a/t/fixtures/ocsp/client-key.pem b/t/fixtures/ocsp/client-key.pem new file mode 100644 index 000000000..6ea4407e1 --- /dev/null +++ b/t/fixtures/ocsp/client-key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAv+EK5duP98xw5FmjWwnC5xOdz6AnqwaxkWcfBOtCX7W2tuN7 +gOF3L7NXtJ5Tnyr9uCYhhL4zqRF6j6rp+yP/jIR4ZN1UHmq+ScHG7//8NCFCu6R9 +h3YkkDRh5OLSdksGzIIeBRkqiyUOiJaHvciXFOX/pcjDSQCVqeTFTzOaOmHNT4A9 +dC4zCNjR1Zn02LqK0IxylFp6MzXCt+lWcGJvLniSo1KUOW6JPj1TVrgUQygns0ij +b1nok5jbsCSHQW9lRK16JrCqVangABvgV2WAoieB1RPLkCIj1ZJneHSZKsBN9HcF +3x0AMoOOTHkpmpv1eqbkVXLbKWU6i3054Jn8lwIDAQABAoIBADWzoD8wshxiRX5D +cCq8kqXloZdIdZGyL+RH/jB8P3lV/Cs3g4tQjvD0uqk8Xp7/gz8UvPDP4tmAoNKw +vQeizZTgRmnEYBSbP2KZLzpgZI+ad3jY0qEk+Zq6bmOG7+xXlzGJgSlgH74NwwGL +T7ZX+6DHtkMJ6031Me34rxg5kwKffKPymjAG440UVQiOXeIOAp8WlZVjFfwZIpnr +2QLosS2FdEilNYMcY4U++MrqxzkKjoiQ0b09fPlzWNo7StIGchlfdYM9mZaIc//1 +2dAr9XyYWGHlr20AawSiKkzQtvRpYN91kE/gt494k+6CV6Ck7w2WEhHzzb53yW2i +jzwvuYECgYEA4xz/mUf+sTYWq1u/o1O4kREK5H15tdbhxmnyiuKESnzp3Ylr5+g0 +zLKdIrytKKf02cbcJzguS9wC2lteqC1wvk11OcJPXxN9Gvik4guRWg0ecDu9U8xh +yclxkdYoYN+J2YjuUTOo0YUwkLu0vTaFX0bImgOfqf+dxsCGMjFlCfcCgYEA2EjJ +26gxWveicDZrt3vad7ykWsF6v/YswauU4y/UUP2AuaZYB6zz29eZ2lIkWR2eOKMo +icDFBmMAdxtEli0SYr8I7MBz/B7HfWx86g7lPSgsQTeNxmq8Tc5r8DHKMwe9X5zE +sj+/OSRnWSLr6392z48MRQQAlaZsCQBrq7ma+mECgYAXRviXgD6FcTK/m2a32bPY +AnQoWQfpcD2xtVgrje3bKJRbpWMO4MjQlhnu3oGs4hIBZBVaW7K7CbCtKqI4AHhG +uO+j3Nt/tiKjagqUNHVQUnnlLOpJtW/sW54tQVLgIukhB5zNa8sItmr02pg6sGTZ +EAstZeOua8mC/maCrHdbnwKBgGWc3OlL/dQlH7Xke3r6v0sd34GcANWLDL/4oryR +zgg28XFHS3hfszoH8wp1KQqNNbaaQQNnA6nafW936iM8DbVC1yiTymoa89JjU9SL +JvwykYgaV4tuvgU4A9IXMk4DRt9Swb6U4HwyNDPWikxb2C/vnu4V0Uh9pagdAHjv +fHZBAoGBAJkJ2LTZiSb6Li5SvznVTSkVZbT9xClXy3B6tHq/0TUfl5yzwTNfgTUZ +hn8Bs7smgLbU/VQZpPcpk7c8kzgcpimGk+q2lpWdvsBtdaat708zzFmfjFj13BKA +d/qyWACDg4yEOYEFc9Fy7agJIfz6aB430EBVlHuB4C32IjCnoQrK +-----END RSA PRIVATE KEY----- diff --git a/t/fixtures/ocsp/client.csr b/t/fixtures/ocsp/client.csr new file mode 100644 index 000000000..fe557063f --- /dev/null +++ b/t/fixtures/ocsp/client.csr @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIC5DCCAcwCAQAwcjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWEx +FjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xETAPBgNVBAoTCEN1c3RvbWVyMRAwDgYD +VQQLEwdXZWJzaXRlMREwDwYDVQQDEwh0ZXN0LmNvbTCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBAL/hCuXbj/fMcORZo1sJwucTnc+gJ6sGsZFnHwTrQl+1 +trbje4Dhdy+zV7SeU58q/bgmIYS+M6kReo+q6fsj/4yEeGTdVB5qvknBxu///DQh +QrukfYd2JJA0YeTi0nZLBsyCHgUZKoslDoiWh73IlxTl/6XIw0kAlankxU8zmjph +zU+APXQuMwjY0dWZ9Ni6itCMcpRaejM1wrfpVnBiby54kqNSlDluiT49U1a4FEMo +J7NIo29Z6JOY27Akh0FvZUSteiawqlWp4AAb4FdlgKIngdUTy5AiI9WSZ3h0mSrA +TfR3Bd8dADKDjkx5KZqb9Xqm5FVy2yllOot9OeCZ/JcCAwEAAaAtMCsGCSqGSIb3 +DQEJDjEeMBwwGgYDVR0RBBMwEYIJbG9jYWxob3N0ggR0ZXN0MA0GCSqGSIb3DQEB +CwUAA4IBAQADIG7CyC6epszpaxeCaqS4s+hF15/1WCekSek43gyoXQ8/8isoflK5 +IX3aVMK9fDwlamCPkOYprcQaPTfcTWdjmxOlCK4gIzV35JzcnhdV9jfGOsSQ3Ced +dXeGdazFI0kCJ15bo8vs6X+U4nAWvUJVq/HGqc2PG4HWOSCUXqKsksnbrXYtOZdC +pkagIiLKOoUplRz1PNyrbe5T0BYxYcRjhPoI4zbC3Xg3Woun6eWMcokFMXD2PnFT +iLqkRI21gJvdrn3VWXG6Brm2GoWUCx8KrCU+JPdVMBetnei78bfTHa/emtRNEFZB +PtEw25RkjzwLPc4AJAagH9l4jzYJZRK9 +-----END CERTIFICATE REQUEST----- diff --git a/t/fixtures/ocsp/client.pem b/t/fixtures/ocsp/client.pem new file mode 100644 index 000000000..f2f116007 --- /dev/null +++ b/t/fixtures/ocsp/client.pem @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE----- +MIIEVzCCAz+gAwIBAgIUTKcAbc6mgr6a9FcHhywQ6lrsoBkwDQYJKoZIhvcNAQEL +BQAwgYgxCzAJBgNVBAYTAkNBMRAwDgYDVQQIEwdPbnRhcmlvMQ8wDQYDVQQHEwZP +dHRhd2ExEjAQBgNVBAoTCUx1YSBOZ2lueDEgMB4GA1UECxMXQVBJY2FzdCBJbnRl +cm1lZGlhdGUgQ0ExIDAeBgNVBAMTF0FQSWNhc3QgSW50ZXJtZWRpYXRlIENBMCAX +DTI1MDIwNDA4MDAwMFoYDzIxMjUwMTExMDgwMDAwWjByMQswCQYDVQQGEwJVUzET +MBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzERMA8G +A1UEChMIQ3VzdG9tZXIxEDAOBgNVBAsTB1dlYnNpdGUxETAPBgNVBAMTCHRlc3Qu +Y29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv+EK5duP98xw5Fmj +WwnC5xOdz6AnqwaxkWcfBOtCX7W2tuN7gOF3L7NXtJ5Tnyr9uCYhhL4zqRF6j6rp ++yP/jIR4ZN1UHmq+ScHG7//8NCFCu6R9h3YkkDRh5OLSdksGzIIeBRkqiyUOiJaH +vciXFOX/pcjDSQCVqeTFTzOaOmHNT4A9dC4zCNjR1Zn02LqK0IxylFp6MzXCt+lW +cGJvLniSo1KUOW6JPj1TVrgUQygns0ijb1nok5jbsCSHQW9lRK16JrCqVangABvg +V2WAoieB1RPLkCIj1ZJneHSZKsBN9HcF3x0AMoOOTHkpmpv1eqbkVXLbKWU6i305 +4Jn8lwIDAQABo4HLMIHIMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEF +BQcDAjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBR1FdZ2snwxIUD+KrVkBO9pZ9Ib +iTAfBgNVHSMEGDAWgBTYX0fMoRXzpdBMwUvIwgfO2UunvTA3BggrBgEFBQcBAQQr +MCkwJwYIKwYBBQUHMAGGG2h0dHBzOi8vb2NzcC1yZXNwb25kZXIudGVzdDAaBgNV +HREEEzARgglsb2NhbGhvc3SCBHRlc3QwDQYJKoZIhvcNAQELBQADggEBAB5talFl +dl0faUMOCAq7np0BHYCXm/txxqfIuIKetkFNortrpMYxsktkaAcFmEQV5+O6kVyC +Frxui/ogZt1d2uG/jCy8eAhKkdO2/Y3siYenT8wAS/Z3iFiBRb5KfIPcTIZp3CFj +f3y9ND6HWtMSXqNaPRBqX3hWrWIkEIer7YeSjBeceaJhrddfMyW1Wo2cOPHvz6eA +53Swz2yjYkaaU2UMBIb3xxGl4q4a8HqNyDqeI7pzobBxmhroAFS3dD0t8JpycmzM +lDvTDoUu/vtp194CcxJnGJ0kAWNemfAEriIqh6koBeXslUqFbxhXJhwYwyheArp/ +yk9pbkJR+3rrWrk= +-----END CERTIFICATE----- diff --git a/t/fixtures/ocsp/files.pl b/t/fixtures/ocsp/files.pl new file mode 100644 index 000000000..39892b791 --- /dev/null +++ b/t/fixtures/ocsp/files.pl @@ -0,0 +1,15 @@ +use File::Slurp qw(read_file); + +[ + [ "ca.pem" => CORE::join('', + read_file('t/fixtures/ocsp/intermediate_ca.pem'), + read_file('t/fixtures/ocsp/ca.pem'), + ) ], + [ "intermediate_ca.pem" => CORE::join('', read_file('t/fixtures/ocsp/intermediate_ca.pem')) ], + [ "server.pem" => CORE::join('', read_file('t/fixtures/ocsp/server.pem')) ], + [ "server-key.pem" => CORE::join('', read_file('t/fixtures/ocsp/server-key.pem')) ], + [ "client.pem" => CORE::join('', read_file('t/fixtures/ocsp/client.pem')) ], + [ "client-key.pem" => CORE::join('', read_file('t/fixtures/ocsp/client-key.pem')) ], + [ "chain.pem" => CORE::join('', read_file('t/fixtures/ocsp/chain.pem')) ], + [ "wrong-issuer-order-chain.pem" => CORE::join('', read_file('t/fixtures/ocsp/wrong-issuer-order-chain.pem')) ], +] diff --git a/t/fixtures/ocsp/intermediate_ca-key.pem b/t/fixtures/ocsp/intermediate_ca-key.pem new file mode 100644 index 000000000..c2311d5b3 --- /dev/null +++ b/t/fixtures/ocsp/intermediate_ca-key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAzdmEqwQLfSnEfXUmMO9TMsY2y7pPr++n4vt03wuo0ZsJLWW7 +LZ7irtWOHwxnWeu0tfc5UmoUbjwfxltxn+lw1I+Z+Q5pg/uSe8IwyoA051TCu7Mq +ljPNt93pX97m6wLJnOlGbaIRS6r8uBuYIm9//f+jZBI+7gL3CRpkRHgRn6QsYqAO +yn1s8DYT6O3cGJ+ldVb75KdDA11leT4Pxdap+iGY7rKu5F+K60QhUgStwiV8jK0f +9dpx/cm1eutzC2c5WlqmQ15fQfq3rQOGoiVLt1USqzvELdaWff2RkULDeS63Vn4J +MaY4dhRkyMNSwmx7ZixDpsx2sV3pqHbdAkP2QwIDAQABAoIBAQDBBZ9mcROI2Pp7 +nbcjGbcNHFpTbK++TN5DdADiCV/StCrF2+fJXnw27t8mdA4RfnjkTB7/DaN8vnoZ +GLQR++amGsWJiZ8deG+iBrSTneZ9qLg1I0rir07Og5KLKEulwOZmcUNuGBNZvcrd +pFpip+T/uo9ujcFek38rlQjCP0hfRPcmtDmG8OjhaLYuDM4KZhqMSgRzm0uc5oUT +i4WH2MdPUwy4+1VnCykw6JdoMtSfXWG7EF9FU0GqABb7LKSJY11rGyMMxiHhxz9W +J77wikJ3UVFYVzLvlt0Zf3Y+JXZhoRm8YF42/JIUVZ+wj3YKytRKVC1F9Yr9N3Zj +WaLcjWp5AoGBAOPPxTrhC5nxAGogoak3eyh5Qxz9ovf8R1vqs7cQyLC0Kg7kNI5j +8YeihZ9Frrar+pmXek/IC6JEwLhbC+LR+KH3sQ0wpp6R6ZL/8wDBKHJ6vHDVx+Qm +x5WwEQIgZxj2xHA7tFfKG8zST/R7qN7vyi6T6vWDpibQ20Z88uGUEDdlAoGBAOdS +E0Tf8vB1zpuJUtN9jw/NTlWcuJZcZYqbv2Svvv+Ec8VlHFvJnbbZnDn37kigecpQ +uXqpKbNYKjtX9GS80j7+aitGoHrZR05rcMopyLFjeOPptFTGS0IjmXQ4FXSu1Tl0 +nlD9c5iJ+EkzSp+BEHSiEDee5BJ0GAc7oPM8T8CHAoGAHvNzkHRS5y9fJwwX6q2n +BfCztZSgN6EG8O4KTKzzF07jB/LpUQ8zGYJzNRh6jEcBXSy/aWd8bSd6J5H3I3Hz +nc3ROEGL4a9hVJ+BnHCh1ivmvk3NQqoiI6wwI2HToAim17jE38iNzl4kjsrePqvF +hF6EZablRBDMOd9wAEXljiECgYEAmrwlVZo0yTMUzKg0dnJ0A08DinM2aqZN6kU6 +5yBEtGmmg7GaIbAhRtIUFhbKF1z5eyM6n1mPIn/kaCPywHwqa6cF4ALzkJnNgCCQ +pzsCG979W//V+CtY5lon6T0m5RGaYj+nM2gHLk+EfXrI/R95oXtTO9GWoN11wUja +5jo+4vsCgYEAuXpNId5WvzaU2kB7kEL1UWPhIuMkz5Ez1ol6cpL/KKoE/AVVLhSn +HsTzuhmf8sFwZp4oxhLAKrrG8IaeZ4y5Cd0RaAApjd20n5fO0p++2eDvc7gyA6JK +t6UQhpqjrA0WUYMyGg3ktoOTvHKDjv53BcvOyldQcA1D/9sY3iO3y4c= +-----END RSA PRIVATE KEY----- diff --git a/t/fixtures/ocsp/intermediate_ca.csr b/t/fixtures/ocsp/intermediate_ca.csr new file mode 100644 index 000000000..58580694e --- /dev/null +++ b/t/fixtures/ocsp/intermediate_ca.csr @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICzjCCAbYCAQAwgYgxCzAJBgNVBAYTAkNBMRAwDgYDVQQIEwdPbnRhcmlvMQ8w +DQYDVQQHEwZPdHRhd2ExEjAQBgNVBAoTCUx1YSBOZ2lueDEgMB4GA1UECxMXQVBJ +Y2FzdCBJbnRlcm1lZGlhdGUgQ0ExIDAeBgNVBAMTF0FQSWNhc3QgSW50ZXJtZWRp +YXRlIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzdmEqwQLfSnE +fXUmMO9TMsY2y7pPr++n4vt03wuo0ZsJLWW7LZ7irtWOHwxnWeu0tfc5UmoUbjwf +xltxn+lw1I+Z+Q5pg/uSe8IwyoA051TCu7MqljPNt93pX97m6wLJnOlGbaIRS6r8 +uBuYIm9//f+jZBI+7gL3CRpkRHgRn6QsYqAOyn1s8DYT6O3cGJ+ldVb75KdDA11l +eT4Pxdap+iGY7rKu5F+K60QhUgStwiV8jK0f9dpx/cm1eutzC2c5WlqmQ15fQfq3 +rQOGoiVLt1USqzvELdaWff2RkULDeS63Vn4JMaY4dhRkyMNSwmx7ZixDpsx2sV3p +qHbdAkP2QwIDAQABoAAwDQYJKoZIhvcNAQELBQADggEBAFwpVkYRmQz+JUfWmvwz +ZU9r3XYNHs7FH4E635TkN54PMJtAFBcOGLQOJxI13a4hHYPvDjrVN3C1CeB5LkQn +8Lq/x/dSIZOpIggOaWQJi73PZW9BO/pOIynAkOxBm0Giqw0pzCsGyRUdkoOgwHXw +dIgzzptUtH2cabitafMG3Cu76z0jRtb5K57b79BlmFnCNRxITSewtRefMQKrp+ha +LzH8nb7WYv0e4ISlW/A9tyg6JW/dWPSywWA0aaRFPptt1VgF/1hoM5O4kmckmSmf +vXIC5QTO0eUI/pBqMTLt+xg4EMnH5wpTzt0aVz+HD0gvgBzezOUv1Ov0j01bYYxd +iYY= +-----END CERTIFICATE REQUEST----- diff --git a/t/fixtures/ocsp/intermediate_ca.pem b/t/fixtures/ocsp/intermediate_ca.pem new file mode 100644 index 000000000..e251d7b08 --- /dev/null +++ b/t/fixtures/ocsp/intermediate_ca.pem @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIELzCCAxegAwIBAgIUfUh/NyK/i7Zpk5xEAE/Hycg8oGMwDQYJKoZIhvcNAQEL +BQAweDELMAkGA1UEBhMCQ0ExEDAOBgNVBAgTB09udGFyaW8xDzANBgNVBAcTBk90 +dGF3YTESMBAGA1UEChMJTHVhIE5naW54MRgwFgYDVQQLEw9BUEljYXN0IFJvb3Qg +Q0ExGDAWBgNVBAMTD0FQSWNhc3QgUm9vdCBDQTAgFw0yNTAyMDQwNTI2MDBaGA8y +Mjc0MTIwNjA1MjYwMFowgYgxCzAJBgNVBAYTAkNBMRAwDgYDVQQIEwdPbnRhcmlv +MQ8wDQYDVQQHEwZPdHRhd2ExEjAQBgNVBAoTCUx1YSBOZ2lueDEgMB4GA1UECxMX +QVBJY2FzdCBJbnRlcm1lZGlhdGUgQ0ExIDAeBgNVBAMTF0FQSWNhc3QgSW50ZXJt +ZWRpYXRlIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzdmEqwQL +fSnEfXUmMO9TMsY2y7pPr++n4vt03wuo0ZsJLWW7LZ7irtWOHwxnWeu0tfc5UmoU +bjwfxltxn+lw1I+Z+Q5pg/uSe8IwyoA051TCu7MqljPNt93pX97m6wLJnOlGbaIR +S6r8uBuYIm9//f+jZBI+7gL3CRpkRHgRn6QsYqAOyn1s8DYT6O3cGJ+ldVb75KdD +A11leT4Pxdap+iGY7rKu5F+K60QhUgStwiV8jK0f9dpx/cm1eutzC2c5WlqmQ15f +Qfq3rQOGoiVLt1USqzvELdaWff2RkULDeS63Vn4JMaY4dhRkyMNSwmx7ZixDpsx2 +sV3pqHbdAkP2QwIDAQABo4GdMIGaMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBTYX0fMoRXzpdBMwUvIwgfO2UunvTAfBgNVHSMEGDAW +gBQaaSka5maUzsFM/Vnn+cUUp54uSTA3BggrBgEFBQcBAQQrMCkwJwYIKwYBBQUH +MAGGG2h0dHBzOi8vb2NzcC1yZXNwb25kZXIudGVzdDANBgkqhkiG9w0BAQsFAAOC +AQEAs/V67Aah67xA0dRXMChK+IE6y3bTjDDo3MndJAnImCVjtbyJJyX4udBfDOKj +sCslF/bFWmLgb1w5ek0kwEOAPc5lDteQ/lPRIW7o9EnIyZico1qnR/bXG5XzQ/UZ +G2UYvHuwPqY2NEsbbPP2yxqaoIL1vhNwm3/Ga5KriiP0Zd3KgIvpfbZ4OcyJBWtm +9P1qsBDpNNkBiNm7i/D9vPDC4skvHsmm2lOBF3CNQ1+WcZRAf5oL9IfptOru79+N +qfOjWbnIMugxez+7TiFGlE81RyKocyflFMwIcGeeQD0KFD9XXnaVPQEovFIspcto +SXPTXb7wJL19OSGBOMBEJ6oS5Q== +-----END CERTIFICATE----- diff --git a/t/fixtures/ocsp/ocsp-key.pem b/t/fixtures/ocsp/ocsp-key.pem new file mode 100644 index 000000000..c71a0bddd --- /dev/null +++ b/t/fixtures/ocsp/ocsp-key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAuEYwCfh0srnGHBPV3fSl72SmPhGMl3TBkbpyGYeNfLigiFYX +rZCptZqtHDRcXiWMpHohZ4W5ym+jPVJIdIkAPRcRkV7O+Bwd8v8rg5rRTtP44xs5 +g7oHHkzF6KugRIh3arzAsUsyzh9oNXb2N9a3sKyjHv2sNJEuWy+LjLn4hDTLCqx4 +z6Zjyk6maKnqF3ohZKM6HTOgz24ZfpyffWjEmflQx8eupqEN8esoPlT5Z8e0Lvt+ +LFRKIizk6Lt/duhHGDWbQp3ceHXq7ipIlv3MmmUewSO8Ote1mBCgh9KJI6imgr/A +Ao/MxeizPVblg4mRvcFb4KuEXsTzhvSW7wGbwwIDAQABAoIBABbbVeAWulrk4ibf +avqx+tcb0+cFBDxRFiV+S+LQxoL+36KpkxxLQlGtUuWZtQ9UdwJ7x6Lwml/6wvO7 +PhN+XtI1B0/Rw7hQvIJElmAnlujBMDDzUon9TB69BRtcFnAJwU4zBW+4VR/uE132 +WIoKjxRSBZW1f2Ox0l8rIZMeCi5TrCscHRtWdBtrQ9jlxb6wSnQmuAwTUqn8QZC1 +ZhM5f+mSOzKiZ7h9owbeDkbPcEzSM77o5ObUn4XG+1k29yfXotI00xdvqJrHRR72 +7QqxfruQszsmWFi8q+M/hdv3qdftyNSoEXTtCCuCTsDfoMo76e9lH4bNwqLZh73e +Z/0hmnkCgYEA3bf+z8ApcShs+fmT3Ll8Sw+oBoRYQAi8gYOkiyjGADgOvzYJHZVj +SXMz0cwijkEwX66n8Ql6vvNL2MmE9hgTfrMrebJiuaYy/LJYDcEwKTjBw1fJEk4q +0Utya3065/zgp89IQcwT1KxNQGCwccTVl0RNDtOYh0oHVkywzwzOjDUCgYEA1MQS +piYlEhDJCc2bLlMwSII652yJKP5n6oXBmtBp2z5baZQWWw+/+LFyjeBlpyKbeB2L +9bLo9rdhwIDEFRNp+M2a1A2L2WY3N/sigNKjHD8b6mQLmk1C5qHau1cSA5vhgtwg +Wb68Tf5iB7hDxCYZ6c4qnJpGr1bOWXGflcM5VxcCgYBUI0y5Pez8GiEww3tPGbyH +J4vV3Uc4FZijdXdtr61jy+Icwb630Ub6SpNxnAvI/AgFZ1wn5jcEW4N+4wpn8T8E +p61p4z3/wYg7yNoNbjB+lCFDpf6RycQJ5J2pGL7P5nDCfbCn92/XFwPB2G9c3RtI +6gxsAfQx8u/R2gyGpg+DuQKBgB/iRh0p6DI927KhYhjeFIU5JtSZWjCPd0kYxDuL +8eEOBirbK0J4KL9+7/i84N7b+8pTOy8VoX2fW4G6iifSYfYL8aDHauaoP6we8Tu2 +1QSdnHRSi76Sx2zCYoHieWubB39Rt3NbduKFNXGMtnMFdqqn35DaOa965a9XrJ+H +wub/AoGBALOHGYpo4M1i8UHe5YecxSiYfSydK4lvlX2zauJaS5OvahWvSCXUXoIf +Xr90+I+VoQtMTZw6wOLZuUV4Ds81KI4MapFexzjP73+X6rN67GFNnq60nqpqMQgp +SBrVQqzL42Xf/Qv28Nj2zgmC0jofm0tBnRYDRYBLHkA3LF6u9VDY +-----END RSA PRIVATE KEY----- diff --git a/t/fixtures/ocsp/ocsp-response-good-response.der b/t/fixtures/ocsp/ocsp-response-good-response.der new file mode 100644 index 0000000000000000000000000000000000000000..3cf85c55bab204fd59b474edfd3940f9c53a7db8 GIT binary patch literal 1708 zcmXqLVq3w*$grS^ZJ|LE+gvtIZ8k<$R(1nMMwTYFKA=#SK@+3c!p2U6#x_H415P&P zP!={}CTB-O0Ruh|heMd%Kd&UQC^O%X-+&h+!Y<6_Us94-o@gj!AOI5J66W+NO;qqp z&&;bZlsAwCiE#^yIRWNKgxgyyCe z1_n_Efg;?Cm-NucF>8kZfQ;w#5Jlq812(gp5HJ z8yc8{g@E4W)N1o+`_9YA$j!>Y+{DPp(3p5m+a&%Sal+?^RH_g zv$K9y)2E!`J16X)EqLC_^rF$=@=?Q9=Sx$IuIZFn?`viK{9;3=Qj6r*ey4d|j1fs$!|s+m7DUVEi3dMHx;P(|c^*`e9tG{60LK?Dx@W=cMkc zHA&@ngiSR*1YWM0_xF-Sn_pw+g^2sE?^}9iZWgz8_#JvNJMd8zA6MC&{%afzJN6rU z*5+^KTcuw=ea2Pkvo|#UIV|k057s=F|M>jw_L5d{_m>NrSRxFXSVDj}z@UlQVgWM~ zBNLMdqqc2SbnXG(Ad?+s?)oRo9JEg^K?^>E#ty0^Fa-lSNCFdwB{0vtlGLKy)RfG` zl2ip?%7v<67RIH*KtY@rn8b_>Obkqo%z%*(jCw<$fT5uwRAA8L24L<~LJmkqa605? z0E%-lffD2nHv`TeC7X60lM%jp_si1vDa-5xd#0BhoVcq-o-BGbM(dP z1uh-sS$hs_^fo#tpJ7_|&HUQ-4Qm$5{as@+Q7>A*yJzQ*7L(IlYbwq!OFredEMw&> z@hZiX#a6P$3(n_B*3Fq;n{j03&w%5{*DYJf`|-7gUC7V$<6HE8*Xe|KDd{|UvAe$P zg}a35Y^S+*DoS6y)AE@1_spzRxr54ktgdgJA+VtRQm68YWlj4JF!i4~`eL(f*wg0D ziF*%5KUm!ocjR;1muc@AXCG!_W@KPo+_>JLaSgb@lI3S){LjL|%*5VcAPh_>vZ^dR z23%|$kaCZi(|`vgC=61>WWWF}tYrCE#8^a@sb`#@`-dqrY0a#Uky=d~r?xWRFpvi+ zR%Vef5Np6%ycn3nOa>OH+6I~i>M*_mW1DnFNl8JmmA-y{a&duf5v&B%D@iRbL5@>c z*>Yyit8BIo)g06HWxhr`9UQCXUO1syI%i%)CbO2kSkX@JGkUB3cHVxY#q+H!gG0;a z^RIHSB7On>E2U`>2^P6$r~Omx|@3Ei;Td$IzzV!zjFK5>xe6E3)OLWUUi6F z(Rh)Kslb9mZ#O7e2c&UI^-bS)eR)sLsc3&i_A~z3PH#&y5A$A2k9o6b63_Xf{c4>l z)*%a?{!-tbDU!eAncf=XFQR5Dzm*k#vF{K|-MqBkw(wr~q+5rpzrT<_5^&abraotE Q)Pl3cCrVz-?^a0!01T~kQ~&?~ literal 0 HcmV?d00001 diff --git a/t/fixtures/ocsp/ocsp-response-revoked-response.der b/t/fixtures/ocsp/ocsp-response-revoked-response.der new file mode 100644 index 0000000000000000000000000000000000000000..454cf9508d05c9b76034d9b235e094d7f6b39cf8 GIT binary patch literal 1732 zcmXqLVmrXa$grS^ZKpvK+g3JCZ8k<$R(1nMMwTYFWk8`t22G553mZEP8ruxH4LI4D zLs{5_nVcOB1q}E=91dZ2|GbjKqRf0legj^R2)i(we@RJVd7`0^fdEK=OPJHAG*Q7X zJu|PuP~JcmB*rZ)<{03aoLF3<;O`t9pb(T=T#%oal3D~)z>G_Q1iz7ik*R@^k-32x zkT!@iXzVj+>^ATPI*wJFkC{n|m4QWM`ZuFlo8FXU+wYyRs7r-w2#8!^Uw8f6C?`cT(q2*V9i=A;$vJ$D5t&LLL zTl3ktEW#+F_S*u+pRbBz|403PlKbL^@ZaBT_2;{vh$`f7RFawfYiDf6y)|m_tv#}j zuL-HH;di{v=z4Lf-brQ0`5sxv%{J&RG(45^eYVVWi5=?uFE?CmTCO^+NJsWo%mMM$ z2anA=k{|MC?WX*>UslM8zf%h}c(UujtDH0rp-<}`nQ||Q6)fGNGI3_sk$~%N2TGY^ z4R(KDzWGnC`6F(Fr)kos!;|h!XuUlrCSmK2MKfYnOI-sOa1Syg?>A%G~u&mN{sjT!I#S28|t5Nq7nda*%{4 z4oi5Rc_pbuxv43ci6yBDz!VKt!7PkRg@J-NFDU65m>8HE0pmIf81;rg0YgJWsKB7f zCBR&(gdC8J;B?5(02Jq90wu^DZU&q`N;d60CL?_H?w6(SQcpGT4d>PK-PELq_(~f9>X37yT~( zcr0z%yo+7V=je;o3tT$Nv-TX==xuaPKEt%^oB6fv8`dnA`@6NEdmVC-@S;op&;#G<%i>+jh7o5+NteZ2xHsi?5p8>~@uUoc|_v32~yO5vh$G7PH zuG0zeQqp5Cd<#r_@9M^ znTfr@Kp2=%WK~&s47k`hAmt%5rvVR0P#C0$$$$Y|h{^J?h_Q$)Q_na*_YYHM(wbQx zBej|~PHknrVIU7utjr=|Al87j&@nKFnG7swwGA{4)M0!B#y07Ul9GaAD}DX^f*hx?vgOR2SJ`YGsyU|X%Y2P?IyhF%y>LRabk4kpOlB>6v7(*cXY^M6 z?Y#X)i|1Qe28Wi-=U)rE9^Yb=n5VFUySDz@!*BOudHl^kEI*jN>TM;@lkm&&4}QN} zakSrdR!Q&a-5(yQ>fVxzRNiuXuMEqE(!Hk=)9rrDk~chBbT{?R7a4(hb%t&ee&zP9 z*AZ9T7OLa$yy_6UqVXadQ-KAC-fmE`4oKsa>YKjp`tqKfQ_=p4>}UM5o!*va9_GE4 z9`k0=B%bp{`_(#AtV0$&{iVJ=QzU=KGrcv&UqsDRek&{fV&5T_x_N26ZQ;G}Nw*GH ce}5r=B;c&=OnuJSs0C+>Pn5iv->s4e0D3EU!T Date: Mon, 3 Mar 2025 10:47:18 +1000 Subject: [PATCH 04/12] [tls_validation] Cache OCSP response --- gateway/http.d/shdict.conf | 1 + .../policy/tls_validation/apicast-policy.json | 29 ++----- .../policy/tls_validation/ocsp_validation.lua | 77 +++++++++++-------- .../policy/tls_validation/tls_validation.lua | 9 +-- t/apicast-policy-tls_validation.t | 2 +- 5 files changed, 57 insertions(+), 61 deletions(-) diff --git a/gateway/http.d/shdict.conf b/gateway/http.d/shdict.conf index ba0901d57..c5a28e07e 100644 --- a/gateway/http.d/shdict.conf +++ b/gateway/http.d/shdict.conf @@ -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; diff --git a/gateway/src/apicast/policy/tls_validation/apicast-policy.json b/gateway/src/apicast/policy/tls_validation/apicast-policy.json index 117e87bbc..ebdd5ecf2 100644 --- a/gateway/src/apicast/policy/tls_validation/apicast-policy.json +++ b/gateway/src/apicast/policy/tls_validation/apicast-policy.json @@ -103,35 +103,16 @@ "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" + "cache_ttl": { + "title": "Max TTL for cached OCSP response", + "type": "integer", + "minimum": 1, + "maximum": 3600 } } } diff --git a/gateway/src/apicast/policy/tls_validation/ocsp_validation.lua b/gateway/src/apicast/policy/tls_validation/ocsp_validation.lua index f78bfabb1..06dc44e71 100644 --- a/gateway/src/apicast/policy/tls_validation/ocsp_validation.lua +++ b/gateway/src/apicast/policy/tls_validation/ocsp_validation.lua @@ -6,6 +6,7 @@ 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 @@ -27,6 +28,8 @@ local function do_ocsp_request(ocsp_url, ocsp_request) return nil, err end + ngx.log(ngx.INFO, "fetching OCSP response from ", ocsp_url) + if not res then return nil, "failed to send request to OCSP responder: " .. tostring(err) end @@ -38,7 +41,11 @@ local function do_ocsp_request(ocsp_url, ocsp_request) return res.body end -function _M.check_revocation_status(ocsp_responder_url) +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" @@ -50,32 +57,49 @@ function _M.check_revocation_status(ocsp_responder_url) return nil, "failed to convert certificate chain from PEM to DER " .. err end - -- TODO: check response cache - local ocsp_url - if ocsp_responder_url and ocsp_responder_url ~= "" then - ocsp_url = ocsp_responder_url - 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") + local ocsp_resp + ocsp_resp = ocsp_shm:get(digest) + + if ocsp_resp == nil then + ngx.log(ngx.INFO, "no ocsp resp cache found, fetch from ocsp responder") + + + -- TODO: check response cache + local ocsp_url + if ocsp_responder_url and ocsp_responder_url ~= "" then + ocsp_url = ocsp_responder_url + 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") + end end - end - if not ocsp_url or ocsp_url == "" then - return nil, " invalid OCSP responder URL" - end + if not ocsp_url or ocsp_url == "" then + return nil, " invalid OCSP responder URL" + 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_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_resp or #ocsp_resp == 0 then - return nil, "unexpected response from OCSP responder: empty body" + 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" + 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) + end end local ok @@ -84,13 +108,6 @@ function _M.check_revocation_status(ocsp_responder_url) 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 diff --git a/gateway/src/apicast/policy/tls_validation/tls_validation.lua b/gateway/src/apicast/policy/tls_validation/tls_validation.lua index 97582ad1c..2d47a9506 100644 --- a/gateway/src/apicast/policy/tls_validation/tls_validation.lua +++ b/gateway/src/apicast/policy/tls_validation/tls_validation.lua @@ -72,8 +72,8 @@ function _M.new(config) if self.revocation_type == "crl" then init_crl_list(store, config and config.revoke_list or {}) elseif self.revocation_type == "ocsp" then - -- TODO: should we set empty string as default value? self.ocsp_responder_url = config and config.ocsp_responder_url + self.cache_ttl = config and config.cache_ttl end return self @@ -82,9 +82,6 @@ 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 @@ -104,7 +101,7 @@ function _M:access() 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) ngx.say("Invalid TLS certificate") return ngx.exit(ngx.status) end @@ -128,7 +125,7 @@ function _M:access() end if self.revocation_type == "ocsp" then - ok, err = ocsp.check_revocation_status(self.ocsp_responder_url) + 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) diff --git a/t/apicast-policy-tls_validation.t b/t/apicast-policy-tls_validation.t index 120d5d0d8..51d1f1796 100644 --- a/t/apicast-policy-tls_validation.t +++ b/t/apicast-policy-tls_validation.t @@ -799,7 +799,7 @@ log_by_lua_block { collectgarbage() } -=== TEST 17: TLS Client Certificate with OCSP and revoked OCSP respond +=== TEST 17: TLS Client Certificate with OCSP and revoked OCSP response --- init eval $Test::Nginx::Util::ENDPOINT_SSL_PORT = Test::APIcast::get_random_port(); --- env eval From b03fc49a584350599823869c54dc94dea75412ef Mon Sep 17 00:00:00 2001 From: An Tran Date: Mon, 3 Mar 2025 19:04:30 +1000 Subject: [PATCH 05/12] [t] fix failing test --- t/prometheus-metrics.t | 2 ++ 1 file changed, 2 insertions(+) diff --git a/t/prometheus-metrics.t b/t/prometheus-metrics.t index aca2dc2bb..9d6bbb8d5 100644 --- a/t/prometheus-metrics.t +++ b/t/prometheus-metrics.t @@ -37,6 +37,7 @@ openresty_shdict_capacity{dict="batched_reports"} 20971520 openresty_shdict_capacity{dict="batched_reports_locks"} 1048576 openresty_shdict_capacity{dict="cached_auths"} 20971520 openresty_shdict_capacity{dict="limiter"} 1048576 +openresty_shdict_capacity{dict="ocsp_cache"} 10485760 openresty_shdict_capacity{dict="prometheus_metrics"} 16777216 openresty_shdict_capacity{dict="rate_limit_headers"} 20971520 # HELP openresty_shdict_free_space OpenResty shared dictionary free space @@ -46,6 +47,7 @@ openresty_shdict_free_space{dict="batched_reports"} 20840448 openresty_shdict_free_space{dict="batched_reports_locks"} 1032192 openresty_shdict_free_space{dict="cached_auths"} 20840448 openresty_shdict_free_space{dict="limiter"} 1032192 +openresty_shdict_free_space{dict="ocsp_cache"} 10412032 openresty_shdict_free_space{dict="prometheus_metrics"} 16662528 openresty_shdict_free_space{dict="rate_limit_headers"} 20840448 # HELP worker_process Number of times that a nginx worker has been started From 99240c27734a39178872628b647f969c7c1ac271 Mon Sep 17 00:00:00 2001 From: An Tran Date: Wed, 5 Mar 2025 17:24:02 +1000 Subject: [PATCH 06/12] [t] small adjustment to test description --- t/apicast-policy-tls_validation.t | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/t/apicast-policy-tls_validation.t b/t/apicast-policy-tls_validation.t index 51d1f1796..c1f015536 100644 --- a/t/apicast-policy-tls_validation.t +++ b/t/apicast-policy-tls_validation.t @@ -374,8 +374,7 @@ No required TLS certificate was sent -=== TEST 9: TLS Client Certificate with intermediate certificate fails when -allow_partial_chain set to false +=== TEST 9: TLS Client Certificate is whitelisted but allow_partial_chain set to false --- configuration eval use JSON qw(to_json); use File::Slurp qw(read_file); From e767e2b311d98a47a756e77e37ec6d2099df3a92 Mon Sep 17 00:00:00 2001 From: An Tran Date: Wed, 5 Mar 2025 19:35:17 +1000 Subject: [PATCH 07/12] [tls] Normalize certificate string Previously, when the certificate was provided as a single line string, the openssl library would normalize the string before converting it to a format valid for X509. However, this was ignored when we migrated to lua-resty-openssl. --- .../policy/tls_validation/tls_validation.lua | 60 +++++++++++-------- .../policy/upstream_mtls/upstream_mtls.lua | 17 ++++-- gateway/src/resty/tls.lua | 10 ++++ 3 files changed, 57 insertions(+), 30 deletions(-) diff --git a/gateway/src/apicast/policy/tls_validation/tls_validation.lua b/gateway/src/apicast/policy/tls_validation/tls_validation.lua index 2d47a9506..9d3b691ef 100644 --- a/gateway/src/apicast/policy/tls_validation/tls_validation.lua +++ b/gateway/src/apicast/policy/tls_validation/tls_validation.lua @@ -2,9 +2,10 @@ local policy = require('apicast.policy') local _M = policy.new('tls_validation') -local X509_STORE = require('resty.openssl.x509.store') local X509 = require('resty.openssl.x509') +local X509_STORE = require('resty.openssl.x509.store') local X509_CRL = require('resty.openssl.x509.crl') +local tls = require('resty.tls') local ngx_ssl = require "ngx.ssl" local ocsp = require ("ocsp_validation") @@ -15,20 +16,25 @@ local debug = ngx.config.debug local function init_trusted_store(store, certificates) for _,certificate in ipairs(certificates) do - local cert, err = X509.new(certificate.pem_certificate) -- TODO: handle errors - - if cert then - store:add(cert) - - if debug then - ngx.log(ngx.DEBUG, 'adding certificate to the tls validation ', tostring(cert:subject_name()), ' SHA1: ', cert:hexdigest('SHA1')) + local normalized_cert = tls.normalize_pem_cert(certificate.pem_certificate) + if normalized_cert then + local cert, err = X509.new(normalized_cert) -- TODO: handle errors + + if cert then + store:add(cert) + + if debug then + ngx.log(ngx.DEBUG, 'adding certificate to the tls validation ', tostring(cert:subject_name()), ' SHA1: ', cert:hexdigest('SHA1')) + end + else + ngx.log(ngx.WARN, 'error whitelisting certificate, err: ', err) + + if debug then + ngx.log(ngx.DEBUG, 'certificate: ', certificate.pem_certificate) + end end else - ngx.log(ngx.WARN, 'error whitelisting certificate, err: ', err) - - if debug then - ngx.log(ngx.DEBUG, 'certificate: ', certificate.pem_certificate) - end + ngx.log(ngx.WARN, "invalid cert") end end @@ -39,20 +45,24 @@ 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')) + local normalized_cert = tls.normalize_pem_cert(certificate.pem_certificate) + if normalized_cert then + crl, err = X509_CRL.new(normalized_cert) + 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')) + end + else + ngx.log(ngx.WARN, 'failed to add crl certificate, err: ', err) + + if debug then + ngx.log(ngx.DEBUG, 'certificate: ', certificate.pem_certificate) + end end else - ngx.log(ngx.WARN, 'failed to add crl certificate, err: ', err) - - if debug then - ngx.log(ngx.DEBUG, 'certificate: ', certificate.pem_certificate) - end + ngx.log(ngx.WARN, "invalid CRL cert") end end return store diff --git a/gateway/src/apicast/policy/upstream_mtls/upstream_mtls.lua b/gateway/src/apicast/policy/upstream_mtls/upstream_mtls.lua index da9bbd37c..d2d88ae7c 100644 --- a/gateway/src/apicast/policy/upstream_mtls/upstream_mtls.lua +++ b/gateway/src/apicast/policy/upstream_mtls/upstream_mtls.lua @@ -2,6 +2,7 @@ local ssl = require('ngx.ssl') local data_url = require('resty.data_url') +local tls = require 'resty.tls' local util = require 'apicast.util' local pairs = pairs @@ -58,12 +59,18 @@ local function read_ca_certificates(ca_certificates) local valid = false local store = X509_STORE.new() for _,certificate in pairs(ca_certificates) do - local cert, err = X509.new(certificate) - if cert then - valid = true - store:add(cert) + local normalized_cert = tls.normalize_pem_cert(certificate) + + if normalized_cert then + local cert, err = X509.new(normalized_cert) + if cert then + valid = true + store:add(cert) + else + ngx.log(ngx.INFO, "cannot load certificate, err: ", err) + end else - ngx.log(ngx.INFO, "cannot load certificate, err: ", err) + ngx.log(ngx.WARN, "invalid cert") end end diff --git a/gateway/src/resty/tls.lua b/gateway/src/resty/tls.lua index bb2a3db6f..201698b89 100644 --- a/gateway/src/resty/tls.lua +++ b/gateway/src/resty/tls.lua @@ -2,6 +2,7 @@ local base = require "resty.core.base" local type = type local tostring = tostring +local re_gsub = ngx.re.gsub local get_request = base.get_request local get_size_ptr = base.get_size_ptr @@ -10,6 +11,7 @@ local ffi_new = ffi.new local ffi_str = ffi.string local C = ffi.C + local _M = {} local NGX_OK = ngx.OK @@ -124,4 +126,12 @@ function _M.get_full_client_certificate_chain() end end +function _M.normalize_pem_cert(str) + if not str then return end + if #(str) == 0 then return end + + -- using also jit compiler (j) will result in a segfault with some certificates + return re_gsub(str, [[\s(?!(CERTIFICATE|X509|CRL))]], '\n', 'o') +end + return _M From 09a52ce2a34e80711c8877c043dd7e1de6a10d1d Mon Sep 17 00:00:00 2001 From: An Tran Date: Thu, 6 Mar 2025 12:16:38 +1000 Subject: [PATCH 08/12] [tls_validation] Delay CRL status check --- .../policy/tls_validation/tls_validation.lua | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/gateway/src/apicast/policy/tls_validation/tls_validation.lua b/gateway/src/apicast/policy/tls_validation/tls_validation.lua index 9d3b691ef..ad7bc298f 100644 --- a/gateway/src/apicast/policy/tls_validation/tls_validation.lua +++ b/gateway/src/apicast/policy/tls_validation/tls_validation.lua @@ -49,7 +49,8 @@ local function init_crl_list(store, crl_certificates) if normalized_cert then crl, err = X509_CRL.new(normalized_cert) if crl then - ok, err = store:add(crl) + -- add crl to store, but skip setting the flag + ok, err = store:add(crl, true) if debug then ngx.log(ngx.DEBUG, 'adding crl certificate to the tls validation ', tostring(crl:subject_name()), ' SHA1: ', crl:hexdigest('SHA1')) @@ -134,7 +135,15 @@ function _M:access() return ngx.exit(ngx.status) end - if self.revocation_type == "ocsp" then + if self.revocation_type == "crl" then + ok, err = store:check_revocation(chain) + 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 + elseif 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 From 15c900841844dc955ac083f14692a8947b043632 Mon Sep 17 00:00:00 2001 From: An Tran Date: Thu, 6 Mar 2025 16:41:41 +1000 Subject: [PATCH 09/12] [tls_validation] Update README --- .../apicast/policy/tls_validation/README.md | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/gateway/src/apicast/policy/tls_validation/README.md b/gateway/src/apicast/policy/tls_validation/README.md index df9316ced..867f73b66 100644 --- a/gateway/src/apicast/policy/tls_validation/README.md +++ b/gateway/src/apicast/policy/tls_validation/README.md @@ -2,10 +2,10 @@ 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. +* 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. However, you can change this behaviour with `allow_partial_chain` ## Configuration @@ -19,6 +19,7 @@ NOTE: This policy is not compatible with `APICAST_PATH_ROUTING` or `APICAST_PATH ## Example +* Allow certificate verification with only an intermediate certificate. ``` { "name": "apicast.policy.tls_validation", @@ -30,6 +31,19 @@ NOTE: This policy is not compatible with `APICAST_PATH_ROUTING` or `APICAST_PATH } ``` +* Use full certificate chain to verify client certificate +``` +{ + "name": "apicast.policy.tls_validation", + "configuration": { + "whitelist": [ + { "pem_certificate": ""-----BEGIN CERTIFICATE----- XXXXXX -----END CERTIFICATE-----"} + ], + "allow_partial_chain": false + } +} +``` + With Certificate Revocation List (CRL) ``` @@ -41,14 +55,17 @@ With Certificate Revocation List (CRL) ], "revocation_check_type": "crl", "revoke_list": [ - { "pem_certificate": ""-----BEGIN CERTIFICATE----- XXXXXX -----END CERTIFICATE-----"} + { "pem_certificate": ""-----BEGIN X509 CRL ----- XXXXXX -----END X509 CRL-----"} ] } } ``` Checking certificate status with Online Certificate Status Protocol (OCSP). The responder url is -extracted from the certificate +extracted from the certificate. + +NOTE: When validating a client certificate with OCSP, APIcast requires the client to send the certificate chain +(i.e. if the certificate is signed with an intermediate certificate, the client needs to send both the client certificate + the intermediate certificate) ``` { From 17b9e5b69bf1807d8010f13a047a5d3078143708 Mon Sep 17 00:00:00 2001 From: An Tran Date: Thu, 6 Mar 2025 16:47:42 +1000 Subject: [PATCH 10/12] [tls_validation] Remove unused field from the schema --- CHANGELOG.md | 1 + .../src/apicast/policy/tls_validation/apicast-policy.json | 7 ------- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b024e6087..cb1358a6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - 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) - 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) - JWT Parser policy [PR #1536](https://github.com/3scale/APIcast/pull/1536) [THREESCALE-10708](https://issues.redhat.com/browse/THREESCALE-10708) +- 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) ## [3.15.0] 2024-04-04 diff --git a/gateway/src/apicast/policy/tls_validation/apicast-policy.json b/gateway/src/apicast/policy/tls_validation/apicast-policy.json index ebdd5ecf2..5c499c38c 100644 --- a/gateway/src/apicast/policy/tls_validation/apicast-policy.json +++ b/gateway/src/apicast/policy/tls_validation/apicast-policy.json @@ -24,13 +24,6 @@ "items": { "$ref": "#/definitions/certificate" } - }, - "revoke": { - "$id": "#/definitions/revoke", - "type": "array", - "items": { - "$ref": "#/definitions/certificate" - } } }, "properties": { From 88892c8dd0f86a1de0d47dd8987e18bc45e2b699 Mon Sep 17 00:00:00 2001 From: An Tran Date: Fri, 7 Mar 2025 11:48:18 +1000 Subject: [PATCH 11/12] [tls_validation] ensure allow_partial_chain is default to true --- gateway/src/apicast/policy/tls_validation/tls_validation.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gateway/src/apicast/policy/tls_validation/tls_validation.lua b/gateway/src/apicast/policy/tls_validation/tls_validation.lua index ad7bc298f..60c2cfc19 100644 --- a/gateway/src/apicast/policy/tls_validation/tls_validation.lua +++ b/gateway/src/apicast/policy/tls_validation/tls_validation.lua @@ -78,7 +78,7 @@ function _M.new(config) 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.allow_partial_chain = config and config.allow_partial_chain ~= false and true or false 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 {}) From 1952be3d1cf604efe3c59ac84406a26eb3cd1f4a Mon Sep 17 00:00:00 2001 From: An Tran Date: Fri, 7 Mar 2025 15:16:34 +1000 Subject: [PATCH 12/12] [tls_validation] Fix wrong certificate digest --- gateway/src/apicast/policy/tls_validation/ocsp_validation.lua | 2 ++ gateway/src/apicast/policy/tls_validation/tls_validation.lua | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/gateway/src/apicast/policy/tls_validation/ocsp_validation.lua b/gateway/src/apicast/policy/tls_validation/ocsp_validation.lua index 06dc44e71..fd7dbf8ae 100644 --- a/gateway/src/apicast/policy/tls_validation/ocsp_validation.lua +++ b/gateway/src/apicast/policy/tls_validation/ocsp_validation.lua @@ -100,6 +100,8 @@ function _M.check_revocation_status(ocsp_responder_url, digest, ttl) if not ok then ngx.log(ngx.ERR, "could not save ocsp response to cache: ", err) end + else + ngx.log(ngx.INFO, "using ocsp from cache") end local ok diff --git a/gateway/src/apicast/policy/tls_validation/tls_validation.lua b/gateway/src/apicast/policy/tls_validation/tls_validation.lua index 60c2cfc19..44c4ede18 100644 --- a/gateway/src/apicast/policy/tls_validation/tls_validation.lua +++ b/gateway/src/apicast/policy/tls_validation/tls_validation.lua @@ -144,7 +144,7 @@ function _M:access() return ngx.exit(ngx.status) end elseif self.revocation_type == "ocsp" then - ok, err = ocsp.check_revocation_status(self.ocsp_responder_url, cert.digest, self.cache_ttl) + ok, err = ocsp.check_revocation_status(self.ocsp_responder_url, cert:digest("SHA256"), self.cache_ttl) if not ok then ngx.status = self.error_status ngx.log(ngx.WARN, "TLS certificate validation failed, err: ", err)