Skip to content

Commit

Permalink
feat: add ocsp-stapling plugin (#10817)
Browse files Browse the repository at this point in the history
  • Loading branch information
yuweizzz authored Jan 24, 2024
1 parent 3a48d17 commit fa3f220
Show file tree
Hide file tree
Showing 20 changed files with 1,276 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .licenserc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -54,5 +54,7 @@ header:
- 'docs/**/*.md'
- '.ignore_words'
- '.luacheckrc'
# Exclude file contains certificate revocation information
- 't/certs/ocsp/index.txt'

comment: on-failure
4 changes: 4 additions & 0 deletions apisix/cli/ngx_tpl.lua
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,10 @@ http {
lua_shared_dict access-tokens {* http.lua_shared_dict["access-tokens"] *}; # cache for service account access tokens
{% end %}
{% if enabled_plugins["ocsp-stapling"] then %}
lua_shared_dict ocsp-stapling {* http.lua_shared_dict["ocsp-stapling"] *}; # cache for ocsp-stapling
{% end %}
{% if enabled_plugins["ext-plugin-pre-req"] or enabled_plugins["ext-plugin-post-req"] then %}
lua_shared_dict ext-plugin {* http.lua_shared_dict["ext-plugin"] *}; # cache for ext-plugin
{% end %}
Expand Down
2 changes: 2 additions & 0 deletions apisix/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ function _M.http_ssl_client_hello_phase()
core.log.error("failed to find SNI: " .. (err or advise))
ngx_exit(-1)
end
local tls_ext_status_req = apisix_ssl.get_status_request_ext()

local ngx_ctx = ngx.ctx
local api_ctx = core.tablepool.fetch("api_ctx", 0, 32)
Expand All @@ -201,6 +202,7 @@ function _M.http_ssl_client_hello_phase()
ngx_ctx.matched_ssl = api_ctx.matched_ssl
core.tablepool.release("api_ctx", api_ctx)
ngx_ctx.api_ctx = nil
ngx_ctx.tls_ext_status_req = tls_ext_status_req

if not ok then
if err then
Expand Down
220 changes: 220 additions & 0 deletions apisix/plugins/ocsp-stapling.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
--
-- Licensed to the Apache Software Foundation (ASF) under one
-- or more contributor license agreements. See the NOTICE file
-- distributed with this work for additional information
-- regarding copyright ownership. The ASF licenses this file
-- to you under the Apache License, Version 2.0 (the
-- "License"); you may not use this file except in compliance
-- with the License. You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing,
-- software distributed under the License is distributed on an
-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-- KIND, either express or implied. See the License for the
-- specific language governing permissions and limitations
-- under the License.
--

local require = require
local http = require("resty.http")
local ngx = ngx
local ngx_ocsp = require("ngx.ocsp")
local ngx_ssl = require("ngx.ssl")
local radixtree_sni = require("apisix.ssl.router.radixtree_sni")
local core = require("apisix.core")

local plugin_name = "ocsp-stapling"
local ocsp_resp_cache = ngx.shared[plugin_name]

local plugin_schema = {
type = "object",
properties = {},
}

local _M = {
name = plugin_name,
schema = plugin_schema,
version = 0.1,
priority = -44,
}


function _M.check_schema(conf)
return core.schema.check(plugin_schema, conf)
end


local function fetch_ocsp_resp(der_cert_chain)
core.log.info("fetch ocsp response from remote")
local ocsp_url, err = ngx_ocsp.get_ocsp_responder_from_der_chain(der_cert_chain)

if not ocsp_url then
-- if cert not support ocsp, the report error is nil
if not err then
err = "cert not contains authority_information_access extension"
end
return nil, "failed to get ocsp url: " .. err
end

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

local httpc = http.new()
local res, err = httpc:request_uri(ocsp_url, {
method = "POST",
headers = {
["Content-Type"] = "application/ocsp-request",
},
body = ocsp_req
})

if not res then
return nil, "ocsp responder query failed: " .. err
end

local http_status = res.status
if http_status ~= 200 then
return nil, "ocsp responder returns bad http status code: "
.. http_status
end

if res.body and #res.body > 0 then
return res.body, nil
end

return nil, "ocsp responder returns empty body"
end


local function set_ocsp_resp(full_chain_pem_cert, skip_verify, cache_ttl)
local der_cert_chain, err = ngx_ssl.cert_pem_to_der(full_chain_pem_cert)
if not der_cert_chain then
return false, "failed to convert certificate chain from PEM to DER: ", err
end

local ocsp_resp = ocsp_resp_cache:get(der_cert_chain)
if ocsp_resp == nil then
core.log.info("not ocsp resp cache found, fetch from ocsp responder")
ocsp_resp, err = fetch_ocsp_resp(der_cert_chain)
if ocsp_resp == nil then
return false, err
end
core.log.info("fetch ocsp resp ok, cache it")
ocsp_resp_cache:set(der_cert_chain, ocsp_resp, cache_ttl)
end

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

-- set the OCSP stapling
local ok, err = ngx_ocsp.set_ocsp_status_resp(ocsp_resp)
if not ok then
return false, "failed to set ocsp status response: " .. err
end

return true
end


local original_set_cert_and_key
local function set_cert_and_key(sni, value)
if value.gm then
-- should not run with gm plugin
core.log.warn("gm plugin enabled, no need to run ocsp-stapling plugin")
return original_set_cert_and_key(sni, value)
end

if not value.ocsp_stapling then
core.log.info("no 'ocsp_stapling' field found, no need to run ocsp-stapling plugin")
return original_set_cert_and_key(sni, value)
end

if not value.ocsp_stapling.enabled then
return original_set_cert_and_key(sni, value)
end

if not ngx.ctx.tls_ext_status_req then
core.log.info("no status request required, no need to send ocsp response")
return original_set_cert_and_key(sni, value)
end

local ok, err = radixtree_sni.set_pem_ssl_key(sni, value.cert, value.key)
if not ok then
return false, err
end
local fin_pem_cert = value.cert

-- multiple certificates support.
if value.certs then
for i = 1, #value.certs do
local cert = value.certs[i]
local key = value.keys[i]
ok, err = radixtree_sni.set_pem_ssl_key(sni, cert, key)
if not ok then
return false, err
end
fin_pem_cert = cert
end
end

local ok, err = set_ocsp_resp(fin_pem_cert,
value.ocsp_stapling.skip_verify,
value.ocsp_stapling.cache_ttl)
if not ok then
core.log.error("no ocsp response send: ", err)
end

return true
end


function _M.init()
if core.schema.ssl.properties.gm ~= nil then
core.log.error("ocsp-stapling plugin should not run with gm plugin")
end

original_set_cert_and_key = radixtree_sni.set_cert_and_key
radixtree_sni.set_cert_and_key = set_cert_and_key

if core.schema.ssl.properties.ocsp_stapling ~= nil then
core.log.error("Field 'ocsp_stapling' is occupied")
end

core.schema.ssl.properties.ocsp_stapling = {
type = "object",
properties = {
enabled = {
type = "boolean",
default = false,
},
skip_verify = {
type = "boolean",
default = false,
},
cache_ttl = {
type = "integer",
minimum = 60,
default = 3600,
},
}
}

end


function _M.destroy()
radixtree_sni.set_cert_and_key = original_set_cert_and_key
core.schema.ssl.properties.ocsp_stapling = nil
ocsp_resp_cache:flush_all()
end


return _M
31 changes: 31 additions & 0 deletions apisix/ssl.lua
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ local ngx_encode_base64 = ngx.encode_base64
local ngx_decode_base64 = ngx.decode_base64
local aes = require("resty.aes")
local str_lower = string.lower
local str_byte = string.byte
local str_len = string.len
local assert = assert
local type = type
local ipairs = ipairs
Expand Down Expand Up @@ -313,4 +315,33 @@ function _M.check_ssl_conf(in_dp, conf)
end


function _M.get_status_request_ext()
core.log.debug("parsing status request extension ... ")
local ext = ngx_ssl_client.get_client_hello_ext(5)
if not ext then
core.log.debug("no contains status request extension")
return false
end
local total_len = str_len(ext)
-- 1-byte for CertificateStatusType
-- 2-byte for zero-length "responder_id_list"
-- 2-byte for zero-length "request_extensions"
if total_len < 5 then
core.log.error("bad ssl client hello extension: ",
"extension data error")
return false
end

-- CertificateStatusType
local status_type = str_byte(ext, 1)
if status_type == 1 then
core.log.debug("parsing status request extension ok: ",
"status_type is ocsp(1)")
return true
end

return false
end


return _M
1 change: 1 addition & 0 deletions apisix/ssl/router/radixtree_sni.lua
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ local function set_pem_ssl_key(sni, cert, pkey)

return true
end
_M.set_pem_ssl_key = set_pem_ssl_key


-- export the set cert/key process so we can hook it in the other plugins
Expand Down
2 changes: 2 additions & 0 deletions conf/config-default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@ nginx_config: # Config for render the template to generate n
ext-plugin: 1m
tars: 1m
cas-auth: 10m
ocsp-stapling: 10m

# discovery: # Service Discovery
# dns:
Expand Down Expand Up @@ -524,6 +525,7 @@ plugins: # plugin list (sorted by priority)
# <- recommend to use priority (0, 100) for your custom plugins
- example-plugin # priority: 0
#- gm # priority: -43
#- ocsp-stapling # priority: -44
- aws-lambda # priority: -1899
- azure-functions # priority: -1900
- openwhisk # priority: -1901
Expand Down
1 change: 1 addition & 0 deletions t/APISIX.pm
Original file line number Diff line number Diff line change
Expand Up @@ -571,6 +571,7 @@ _EOC_
lua_shared_dict kubernetes-first 1m;
lua_shared_dict kubernetes-second 1m;
lua_shared_dict tars 1m;
lua_shared_dict ocsp-stapling 10m;
lua_shared_dict xds-config 1m;
lua_shared_dict xds-config-version 1m;
lua_shared_dict cas_sessions 10m;
Expand Down
45 changes: 45 additions & 0 deletions t/certs/ocsp/ecc_good.crt
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
-----BEGIN CERTIFICATE-----
MIIC+jCCAWKgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJDTjES
MBAGA1UECAwJR3VhbmdEb25nMQ8wDQYDVQQHDAZaaHVIYWkxDzANBgNVBAoMBmly
ZXN0eTERMA8GA1UEAwwIdGVzdC5jb20wHhcNMjQwMTEyMTQ1OTUwWhcNMzQwMTA5
MTQ1OTUwWjA9MQswCQYDVQQGEwJDTjEWMBQGA1UECwwNQXBhY2hlIEFQSVNJWDEW
MBQGA1UEAwwNb2NzcC50ZXN0LmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IA
BIuZCmq/vmX5LqFrpa9ot5SboEhNyS/r5UGT7akjIOAXBVwZkn1vm/EsQp9VMF8y
rWZkGKFmElo0ZAXAyhjn9D6jNzA1MDMGCCsGAQUFBwEBBCcwJTAjBggrBgEFBQcw
AYYXaHR0cDovLzEyNy4wLjAuMToxMTQ1MS8wDQYJKoZIhvcNAQELBQADggGBAIEm
LKKS+eGBazPpSRvq2agnqmjM+PHVWRB/O/+LNOO69Lji3wRtq6T2zNHPZQXw1OMA
3C9HcIwaawTyb+hm+vX8yBr5mgS1UOtmDYzbnlpERjJBjxmPXTZLDbzogHshbabp
227p/IAjWm/2F2VPXjiX+aV1pYrhCcO7zUtBEu9KaoG3Amxg8T2WVamTV+J6r0SL
fkvYItZwbawSfwQlZ+22H4Mttu/bd2USTusT4zLAflv9UFh20bA1PizvcKK1brWS
IH2rxxSLCvu2wmrGsrLVn+9yD6xNsn4m6DyCWx9S/Tas7KLub8BjnCzP8YEvrVpV
fotefEMY5h0waj9Zc32l+6gk8Ntyp2ozWi+iu4eo0Y5SUqHlPjuGUXOivp5o/6b0
gF5M9jtkXvbH2ffrOiz9YUo4fVwk6ws5OQTr9WsildEHZH4ADOW6HqPYkOnuxhdM
p6JP0LmnO/S60/k/ZH8nMTcSUfE+qcDg3LlH5ay2fv6IKz5BaVkyHPNreRi9qg==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEojCCAwqgAwIBAgIJAK253pMhgCkxMA0GCSqGSIb3DQEBCwUAMFYxCzAJBgNV
BAYTAkNOMRIwEAYDVQQIDAlHdWFuZ0RvbmcxDzANBgNVBAcMBlpodUhhaTEPMA0G
A1UECgwGaXJlc3R5MREwDwYDVQQDDAh0ZXN0LmNvbTAgFw0xOTA2MjQyMjE4MDVa
GA8yMTE5MDUzMTIyMTgwNVowVjELMAkGA1UEBhMCQ04xEjAQBgNVBAgMCUd1YW5n
RG9uZzEPMA0GA1UEBwwGWmh1SGFpMQ8wDQYDVQQKDAZpcmVzdHkxETAPBgNVBAMM
CHRlc3QuY29tMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAyCM0rqJe
cvgnCfOw4fATotPwk5Ba0gC2YvIrO+gSbQkyxXF5jhZB3W6BkWUWR4oNFLLSqcVb
VDPitz/Mt46Mo8amuS6zTbQetGnBARzPLtmVhJfoeLj0efMiOepOSZflj9Ob4yKR
2bGdEFOdHPjm+4ggXU9jMKeLqdVvxll/JiVFBW5smPtW1Oc/BV5terhscJdOgmRr
abf9xiIis9/qVYfyGn52u9452V0owUuwP7nZ01jt6iMWEGeQU6mwPENgvj1olji2
WjdG2UwpUVp3jp3l7j1ekQ6mI0F7yI+LeHzfUwiyVt1TmtMWn1ztk6FfLRqwJWR/
Evm95vnfS3Le4S2ky3XAgn2UnCMyej3wDN6qHR1onpRVeXhrBajbCRDRBMwaNw/1
/3Uvza8QKK10PzQR6OcQ0xo9psMkd9j9ts/dTuo2fzaqpIfyUbPST4GdqNG9NyIh
/B9g26/0EWcjyO7mYVkaycrtLMaXm1u9jyRmcQQI1cGrGwyXbrieNp63AgMBAAGj
cTBvMB0GA1UdDgQWBBSZtSvV8mBwl0bpkvFtgyiOUUcbszAfBgNVHSMEGDAWgBSZ
tSvV8mBwl0bpkvFtgyiOUUcbszAMBgNVHRMEBTADAQH/MB8GA1UdEQQYMBaCCHRl
c3QuY29tggoqLnRlc3QuY29tMA0GCSqGSIb3DQEBCwUAA4IBgQAHGEul/x7ViVgC
tC8CbXEslYEkj1XVr2Y4hXZXAXKd3W7V3TC8rqWWBbr6L/tsSVFt126V5WyRmOaY
1A5pju8VhnkhYxYfZALQxJN2tZPFVeME9iGJ9BE1wPtpMgITX8Rt9kbNlENfAgOl
PYzrUZN1YUQjX+X8t8/1VkSmyZysr6ngJ46/M8F16gfYXc9zFj846Z9VST0zCKob
rJs3GtHOkS9zGGldqKKCj+Awl0jvTstI4qtS1ED92tcnJh5j/SSXCAB5FgnpKZWy
hme45nBQj86rJ8FhN+/aQ9H9/2Ib6Q4wbpaIvf4lQdLUEcWAeZGW6Rk0JURwEog1
7/mMgkapDglgeFx9f/XztSTrkHTaX4Obr+nYrZ2V4KOB4llZnK5GeNjDrOOJDk2y
IJFgBOZJWyS93dQfuKEj42hA79MuX64lMSCVQSjX+ipR289GQZqFrIhiJxLyA+Ve
U/OOcSRr39Kuis/JJ+DkgHYa/PWHZhnJQBxcqXXk1bJGw9BNbhM=
-----END CERTIFICATE-----
8 changes: 8 additions & 0 deletions t/certs/ocsp/ecc_good.key
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
-----BEGIN EC PARAMETERS-----
BggqhkjOPQMBBw==
-----END EC PARAMETERS-----
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIHMwGqSAcIFnsy8Sa6NxlSmGuOXV13SbZbZVIobN+3xboAoGCCqGSM49
AwEHoUQDQgAEi5kKar++ZfkuoWulr2i3lJugSE3JL+vlQZPtqSMg4BcFXBmSfW+b
8SxCn1UwXzKtZmQYoWYSWjRkBcDKGOf0Pg==
-----END EC PRIVATE KEY-----
4 changes: 4 additions & 0 deletions t/certs/ocsp/index.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
V 340109124821Z 1 unknown /C=CN/OU=Apache APISIX/CN=ocsp.test.com1
V 340109125024Z 2 unknown /C=CN/OU=Apache APISIX/CN=ocsp.test.com2
R 340109125151Z 240109125151Z 3 unknown /C=CN/OU=Apache APISIX/CN=ocsp-revoked.test.com
V 340109125746Z 5 unknown /C=CN/OU=Apache APISIX/CN=ocsp test CA signer
Loading

0 comments on commit fa3f220

Please sign in to comment.