CDNはopenrestyライブラリを通じてocsp staplingを実現し、クライアントのソースバック効率を効果的に向上させる
6970 ワード
背景
最近CDNオンラインアクセラレーション機能を開発しており、各CDNメーカーはoscp stapling機能をサポートしているので、私たちの製品もそれを実現しなければなりません.これを実現するメリットは、ブラウザとCA機関のサーバが証明書を検証する時間を省くことで、ブラウザの応答速度を向上させることです.
一、ocsp staplingとは
信頼できるCA機関が発行した有効な証明書は、証明書が期限切れになる前にCAが取り消されていない限り、この証明書は有効で信頼できるものです.場合によっては、秘密鍵の漏洩、証明書情報の誤り、CAの脆弱性がハッカーに利用され、他のドメイン名の証明書が発行されたなど、特定の理由で、証明書を取り消す必要があります.では、ブラウザやクライアントは、現在使用されている証明書が取り消されていることをどのように知っていますか.通常、CRL(Certificate Revocation List、証明書取り消しリスト)とOCSP(Online Certificate Status Protocol、オンライン証明書ステータスプロトコル)の2つの方法があります.
CRLの方式:CAサーバーはすでに取り消した証明書のリストを維持することができて、ブラウザはSSLを創立する前に先にCAサーバーに行って自分の訪問したドメイン名の証明書がすでに取り消しられたかどうかを調べて、この方式の欠点も明らかで、例えば:このリストはますます大きくなって、問い合わせの時間も長くなります.ここではこの方法について詳しく議論しません.
OCSPの方式:この方式はOCSPプロトコルを通じてCAサーバーに行って証明書が取り消されたかどうかを照会することであり、OCSPにプライバシーと性能の問題があることは明らかである.例えば、ブラウザはサードパーティCA(Certificate Authority、デジタル証明書認証機関)に直接要求し、ウェブサイトの訪問者(Let’s EncryptがFundebugにアクセスしているユーザーを知っている)を暴露します.また、ブラウザがOCSPクエリーを行うとHTTPSのパフォーマンスが低下します(Fundebugへのアクセスが遅くなります).
OCSPに存在する2つの問題を解決するために,OCSP staplingがある.WebサーバからOCSPクエリーを行い、クエリー結果をキャッシュし、ブラウザとTLS接続したときにブラウザに戻ると、ブラウザはクエリーを必要としません.これにより、プライバシーとパフォーマンスの問題が解決されます.
二、openrestyによるocsp stapling
1、原理紹介
私たちのCDNのエッジノード(ユーザーに最も近い層)は、CAサイトに定期的にOCSPクエリーを行い、結果をローカルにキャッシュし、期限が切れるまで更新します.このように,ユーザが訪問して尋ねると,TLS握手段階で我々CDNはserver helloでOCSPの結果をユーザブラウザに直接送信し,ユーザブラウザは外部のCAサーバにアクセスする必要がなくなった.
2、献上コード
三、梱包検証結果
OCSP staplingの実装と実装されていない結果を比較します.
1、OCSP staplingが実現されたスクリーンショットは以下の通りである.スクリーンショットから分かるように、server Helloのとき、サーバはcertifcat Statusをユーザーに送信しました.
2、OCSP staplingが実現されていないスクリーンショットは以下の通りである.
最近CDNオンラインアクセラレーション機能を開発しており、各CDNメーカーはoscp stapling機能をサポートしているので、私たちの製品もそれを実現しなければなりません.これを実現するメリットは、ブラウザとCA機関のサーバが証明書を検証する時間を省くことで、ブラウザの応答速度を向上させることです.
一、ocsp staplingとは
信頼できるCA機関が発行した有効な証明書は、証明書が期限切れになる前にCAが取り消されていない限り、この証明書は有効で信頼できるものです.場合によっては、秘密鍵の漏洩、証明書情報の誤り、CAの脆弱性がハッカーに利用され、他のドメイン名の証明書が発行されたなど、特定の理由で、証明書を取り消す必要があります.では、ブラウザやクライアントは、現在使用されている証明書が取り消されていることをどのように知っていますか.通常、CRL(Certificate Revocation List、証明書取り消しリスト)とOCSP(Online Certificate Status Protocol、オンライン証明書ステータスプロトコル)の2つの方法があります.
CRLの方式:CAサーバーはすでに取り消した証明書のリストを維持することができて、ブラウザはSSLを創立する前に先にCAサーバーに行って自分の訪問したドメイン名の証明書がすでに取り消しられたかどうかを調べて、この方式の欠点も明らかで、例えば:このリストはますます大きくなって、問い合わせの時間も長くなります.ここではこの方法について詳しく議論しません.
OCSPの方式:この方式はOCSPプロトコルを通じてCAサーバーに行って証明書が取り消されたかどうかを照会することであり、OCSPにプライバシーと性能の問題があることは明らかである.例えば、ブラウザはサードパーティCA(Certificate Authority、デジタル証明書認証機関)に直接要求し、ウェブサイトの訪問者(Let’s EncryptがFundebugにアクセスしているユーザーを知っている)を暴露します.また、ブラウザがOCSPクエリーを行うとHTTPSのパフォーマンスが低下します(Fundebugへのアクセスが遅くなります).
OCSPに存在する2つの問題を解決するために,OCSP staplingがある.WebサーバからOCSPクエリーを行い、クエリー結果をキャッシュし、ブラウザとTLS接続したときにブラウザに戻ると、ブラウザはクエリーを必要としません.これにより、プライバシーとパフォーマンスの問題が解決されます.
二、openrestyによるocsp stapling
1、原理紹介
私たちのCDNのエッジノード(ユーザーに最も近い層)は、CAサイトに定期的にOCSPクエリーを行い、結果をローカルにキャッシュし、期限が切れるまで更新します.このように,ユーザが訪問して尋ねると,TLS握手段階で我々CDNはserver helloでOCSPの結果をユーザブラウザに直接送信し,ユーザブラウザは外部のCAサーバにアクセスする必要がなくなった.
2、献上コード
-- Description: ssl
-- Copyright (C) by CRGT, shaoshuli, 2020.05.05
local ssl = require "ngx.ssl"
local ocsp = require "ngx.ocsp"
local http = require "resty.http"
local redis = require "redis_utils"
local cjson = require "cjson"
--
local ok, err = ssl.clear_certs()
if not ok then
ngx.log(ngx.ERR, "Failed to clear existing (fallback) certificates.")
return ngx.exit(ngx.ERROR)
end
--[[ SNI name, nil, SNI,
raw_server_addr method() serv IP, dns server name.
。]]
local req_host, err = ssl.server_name()
ngx.log(ngx.INFO, "req_host:", req_host)
if not req_host then
ngx.log(ngx.ERR, "Failed to get server name, exit.")
--return ngx.exit(ngx.ERROR)
end
-- share dict
local shared_data = ngx.shared.shared_data
-- redis ssl
local function get_my_pem_cert_data()
local domain_conf_json = nil
--shared_data:delete(req_host)
local domain_conf_str, flags = shared_data:get(req_host)
--KEY , nil
if domain_conf_str == nil then
--ngx.log(ngx.INFO, "Not found key in share dict, start to set key: ", req_host)
local redis_host = "127.0.0.1"
local port = 6379
local db_num = 1
local red_handle = redis.connect_redis(redis_host, port, db_num, nil)
domain_conf_json = redis.get_meta_from_redis(red_handle, req_host)
--ngx.log(ngx.INFO, "domain_conf_json: ", domain_conf_json["domain_name"])
--
local domain_conf_str = cjson.encode(domain_conf_json)
local ok,err = shared_data:safe_set(req_host, domain_conf_str, 120)
else
-- key ,
domain_conf_json = cjson.decode(domain_conf_str)
--ngx.log(ngx.INFO, "Found key in share dict:", req_host)
end
-- ssl json
local ssl_https_data = domain_conf_json["ssl_https_data"]
return ssl_https_data
end
-- , redis
local ssl_https_data = get_my_pem_cert_data()
if not ssl_https_data then
ngx.log(ngx.ERR, "Failed to get PEM ssl_https: ", err)
return
end
local der_cert_chain = nil
local der_priv_key = nil
-- pem der , pem , der, oscp stapling。
if ssl_https_data['type'] == 'pem' then
der_cert_chain, err = ssl.cert_pem_to_der(ssl_https_data['cert_data'])
if not der_cert_chain then
ngx.log(ngx.ERR, "pem cert transe to der cert chain faild.")
return
end
der_priv_key, err = ssl.priv_key_pem_to_der(ssl_https_data['pkey_data'])
if not der_priv_key then
ngx.log(ngx.ERR, "Pem key transe to der private key faild.")
return
end
else
der_cert_chain = ssl_https_data['cert_data']
der_priv_key = ssl_https_data['pkey_data']
end
local ok, err = ssl.set_der_cert(der_cert_chain)
if not ok then
ngx.log(ngx.ERR, "Set der cert faild.")
return
end
local ok, err = ssl.set_der_priv_key(der_priv_key)
if not ok then
ngx.log(ngx.ERR, "set der private key faild.")
return
end
---OSCP 。 , oscp response, update share dict
local req_host_oscp_res = nil
--oscp key
local req_host_oscp = req_host..'_oscp'
req_host_oscp_res = shared_data:get(req_host_oscp)
if req_host_oscp_res == nil then
local ocsp_url, err = ocsp.get_ocsp_responder_from_der_chain(der_cert_chain)
if not ocsp_url then
ngx.log(ngx.ERR, "failed to get OCSP responder: ", err)
return ngx.exit(ngx.ERROR)
end
ngx.log(ngx.INFO, "oscp url:", ocsp_url)
local ocsp_req, err = ocsp.create_ocsp_request(der_cert_chain)
if not ocsp_req then
ngx.log(ngx.ERR, "failed to create OCSP request: ", err)
return ngx.exit(ngx.ERROR)
end
local httpc = http.new()
httpc:set_timeout(10000)
local res, req_err = httpc:request_uri(ocsp_url, {
method = "POST",
body = ocsp_req,
headers = {
["Content-Type"] = "application/ocsp-request",
}
})
-- CA
if not res then
ngx.log(ngx.ERR, "OCSP responder query failed: ", err)
return ngx.exit(ngx.ERROR)
end
local http_status = res.status
if http_status ~= 200 then
ngx.log(ngx.ERR, "OCSP responder returns bad HTTP status code ",
http_status)
return ngx.exit(ngx.ERROR)
end
req_host_oscp_res = res.body
--ngx.log(ngx.INFO, 'req_host_oscp_res:', req_host_oscp_res)
-- ocsp share dict
local ok,err = shared_data:safe_set(req_host_oscp, req_host_oscp_res, 600)
end
--if req_host_oscp_res and #req_host_oscp_res > 0 then
if req_host_oscp_res then
local ok, err = ocsp.validate_ocsp_response(req_host_oscp_res, der_cert_chain)
if not ok then
ngx.log(ngx.ERR, "failed to validate OCSP response: ", err)
return ngx.exit(ngx.ERROR)
end
-- SSL OCSP stapling
ok, err = ocsp.set_ocsp_status_resp(req_host_oscp_res)
if not ok then
ngx.log(ngx.ERR, "failed to set ocsp status resp: ", err)
return ngx.exit(ngx.ERROR)
end
end
三、梱包検証結果
OCSP staplingの実装と実装されていない結果を比較します.
1、OCSP staplingが実現されたスクリーンショットは以下の通りである.スクリーンショットから分かるように、server Helloのとき、サーバはcertifcat Statusをユーザーに送信しました.
2、OCSP staplingが実現されていないスクリーンショットは以下の通りである.