diff --git a/.ci/docker-compose-file/docker-compose.yaml b/.ci/docker-compose-file/docker-compose.yaml index ec505ddf4..e52702633 100644 --- a/.ci/docker-compose-file/docker-compose.yaml +++ b/.ci/docker-compose-file/docker-compose.yaml @@ -3,7 +3,7 @@ version: '3.9' services: erlang: container_name: erlang - image: ghcr.io/emqx/emqx-builder/4.4-19:24.1.5-3-ubuntu20.04 + image: ghcr.io/emqx/emqx-builder/4.4-20:24.3.4.2-1-ubuntu20.04 env_file: - conf.env environment: diff --git a/.ci/fvt_tests/local_relup_test_run.sh b/.ci/fvt_tests/local_relup_test_run.sh index 649af9587..0344823c9 100755 --- a/.ci/fvt_tests/local_relup_test_run.sh +++ b/.ci/fvt_tests/local_relup_test_run.sh @@ -15,8 +15,8 @@ PROFILE="$1" VSN="$2" OLD_VSN="$3" PACKAGE_PATH="$4" -FROM_OTP_VSN="${5:-24.1.5-3}" -TO_OTP_VSN="${6:-24.1.5-3}" +FROM_OTP_VSN="${5:-24.3.4.2-1}" +TO_OTP_VSN="${6:-24.3.4.2-1}" TEMPDIR=$(mktemp -d) trap '{ rm -rf -- "$TEMPDIR"; }' EXIT diff --git a/.github/workflows/run_automate_tests.yaml b/.github/workflows/run_automate_tests.yaml index a4473f62b..7bae9ff60 100644 --- a/.github/workflows/run_automate_tests.yaml +++ b/.github/workflows/run_automate_tests.yaml @@ -29,7 +29,7 @@ jobs: - uses: actions/checkout@v2 - uses: erlef/setup-beam@v1 with: - otp-version: "24.1.5" + otp-version: "24.3.4.2" - name: prepare id: prepare run: | diff --git a/.tool-versions b/.tool-versions index a6568713b..9ff4a119c 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -erlang 24.1.5-3 +erlang 24.3.4.2-1 diff --git a/Makefile b/Makefile index 0d02f1b51..84cfcda5e 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ REBAR = $(CURDIR)/rebar3 BUILD = $(CURDIR)/build SCRIPTS = $(CURDIR)/scripts export EMQX_RELUP ?= true -export EMQX_DEFAULT_BUILDER = ghcr.io/emqx/emqx-builder/4.4-19:24.1.5-3-alpine3.15.1 +export EMQX_DEFAULT_BUILDER = ghcr.io/emqx/emqx-builder/4.4-20:24.3.4.2-1-alpine3.15.1 export EMQX_DEFAULT_RUNNER = alpine:3.15.1 export OTP_VSN ?= $(shell $(CURDIR)/scripts/get-otp-vsn.sh) export PKG_VSN ?= $(shell $(CURDIR)/pkg-vsn.sh) diff --git a/apps/emqx_auth_ldap/src/emqx_acl_ldap.erl b/apps/emqx_auth_ldap/src/emqx_acl_ldap.erl index 5a249c860..a8f08f2b4 100644 --- a/apps/emqx_auth_ldap/src/emqx_acl_ldap.erl +++ b/apps/emqx_auth_ldap/src/emqx_acl_ldap.erl @@ -19,7 +19,7 @@ -include("emqx_auth_ldap.hrl"). -include_lib("emqx/include/emqx.hrl"). --include_lib("eldap/include/eldap.hrl"). +-include_lib("eldap2/include/eldap.hrl"). -include_lib("emqx/include/logger.hrl"). -export([ check_acl/5 diff --git a/apps/emqx_auth_ldap/src/emqx_auth_ldap.erl b/apps/emqx_auth_ldap/src/emqx_auth_ldap.erl index 0b26f6125..26c590913 100644 --- a/apps/emqx_auth_ldap/src/emqx_auth_ldap.erl +++ b/apps/emqx_auth_ldap/src/emqx_auth_ldap.erl @@ -19,7 +19,7 @@ -include("emqx_auth_ldap.hrl"). -include_lib("emqx/include/emqx.hrl"). --include_lib("eldap/include/eldap.hrl"). +-include_lib("eldap2/include/eldap.hrl"). -include_lib("emqx/include/logger.hrl"). -import(proplists, [get_value/2]). diff --git a/build b/build index 7577d2219..eaae30b83 100755 --- a/build +++ b/build @@ -268,7 +268,7 @@ make_docker() { ## Name Default Example ## --------------------------------------------------------------------- ## EMQX_BASE_IMAGE current os centos:7 -## EMQX_ZIP_PACKAGE _packages/ /tmp/emqx-4.4.0-otp24.1.5-3-el7-amd64.zip +## EMQX_ZIP_PACKAGE _packages/ /tmp/emqx-4.4.0-otp24.3.4.2-1-el7-amd64.zip ## EMQX_IMAGE_TAG emqx/emqx: emqx/emqx:testing-tag ## make_docker_testing() { diff --git a/deploy/docker/Dockerfile b/deploy/docker/Dockerfile index f9b414260..f1d54693a 100644 --- a/deploy/docker/Dockerfile +++ b/deploy/docker/Dockerfile @@ -1,4 +1,4 @@ -ARG BUILD_FROM=ghcr.io/emqx/emqx-builder/4.4-19:24.1.5-3-alpine3.15.1 +ARG BUILD_FROM=ghcr.io/emqx/emqx-builder/4.4-20:24.3.4.2-1-alpine3.15.1 ARG RUN_FROM=alpine:3.15.1 FROM ${BUILD_FROM} AS builder diff --git a/etc/emqx.conf b/etc/emqx.conf index c16a11c31..0ec15be31 100644 --- a/etc/emqx.conf +++ b/etc/emqx.conf @@ -1521,6 +1521,27 @@ listener.ssl.external.certfile = {{ platform_etc_dir }}/certs/cert.pem ## Value: File listener.ssl.external.cacertfile = {{ platform_etc_dir }}/certs/cacert.pem +## URL for the OCSP responder to check the server certificate against. +## +## Value: String +## listener.ssl.external.ocsp_responder_url = http://my.ocsp.responder.com + +## Path to the file containing PEM-encoded certificate of the OCSP +## issuer for the server certificate. +## +## Value: File +## listener.ssl.external.ocsp_issuer_pem = {{ platform_etc_dir }}/certs/ocsp-issuer.pem + +## The period to refresh the OCSP response for the server. +## +## Value: Duration +## listener.ssl.external.ocsp_refresh_interval = 5m + +## The timeout for the HTTP request when checking OCSP responses. +## +## Value: Duration +## listener.ssl.external.ocsp_refresh_http_timeout = 15s + ## The Ephemeral Diffie-Helman key exchange is a very effective way of ## ensuring Forward Secrecy by exchanging a set of keys that never hit ## the wire. Since the DH key is effectively signed by the private key, diff --git a/priv/emqx.schema b/priv/emqx.schema index dc567bdf4..6ee8fb71c 100644 --- a/priv/emqx.schema +++ b/priv/emqx.schema @@ -852,6 +852,14 @@ end}. {datatype, string} ]}. +{mapping, "clientid_enrichment_module", "emqx.clientid_enrichment_module", [ + {datatype, atom} +]}. + +{mapping, "special_auth_module", "emqx.special_auth_module", [ + {datatype, atom} +]}. + %% @doc Allow anonymous authentication. {mapping, "allow_anonymous", "emqx.allow_anonymous", [ {default, false}, @@ -1664,6 +1672,49 @@ end}. {datatype, {duration, ms}} ]}. +{mapping, "listener.ssl.$name.ocsp_responder_url", "emqx.listeners", [ + {datatype, string} +]}. + +{mapping, "listener.ssl.$name.ocsp_issuer_pem", "emqx.listeners", [ + {datatype, file} +]}. + +{mapping, "listener.ssl.$name.ocsp_refresh_interval", "emqx.listeners", [ + {default, "5m"}, + {datatype, {duration, ms}} +]}. + +{mapping, "listener.ssl.$name.ocsp_refresh_http_timeout", "emqx.listeners", [ + {default, "15000ms"}, + {datatype, {duration, ms}} +]}. + +{mapping, "listener.ssl.$name.enable_crl_cache", "emqx.listeners", [ + {default, false}, + {datatype, {enum, [true, false]}} +]}. + +{mapping, "listener.ssl.$name.crl_cache_http_timeout", "emqx.listeners", [ + {default, "15s"}, + {datatype, {duration, ms}} +]}. + +{mapping, "crl_cache.refresh_interval", "emqx.crl_cache_refresh_interval", [ + {default, "15m"}, + {datatype, {duration, ms}} +]}. + +{mapping, "crl_cache.urls", "emqx.crl_cache_urls", [ + {default, ""}, + {datatype, string} +]}. + +{translation, "emqx.crl_cache_urls", fun(Conf) -> + Val = cuttlefish:conf_get("crl_cache.urls", Conf), + string:tokens(Val, ", ") +end}. + %%-------------------------------------------------------------------- %% MQTT/WebSocket Listeners @@ -2184,6 +2235,10 @@ end}. {supported_subprotocols, string:tokens(cuttlefish:conf_get(Prefix ++ ".supported_subprotocols", Conf, ""), ", ")}, {peer_cert_as_username, cuttlefish:conf_get(Prefix ++ ".peer_cert_as_username", Conf, undefined)}, {peer_cert_as_clientid, cuttlefish:conf_get(Prefix ++ ".peer_cert_as_clientid", Conf, undefined)}, + {ocsp_responder_url, cuttlefish:conf_get(Prefix ++ ".ocsp_responder_url", Conf, undefined)}, + {ocsp_issuer_pem, cuttlefish:conf_get(Prefix ++ ".ocsp_issuer_pem", Conf, undefined)}, + {ocsp_refresh_interval, cuttlefish:conf_get(Prefix ++ ".ocsp_refresh_interval", Conf, undefined)}, + {ocsp_refresh_http_timeout, cuttlefish:conf_get(Prefix ++ ".ocsp_refresh_http_timeout", Conf, undefined)}, {compress, cuttlefish:conf_get(Prefix ++ ".compress", Conf, undefined)}, {idle_timeout, cuttlefish:conf_get(Prefix ++ ".idle_timeout", Conf, undefined)}, {max_frame_size, cuttlefish:conf_get(Prefix ++ ".max_frame_size", Conf, undefined)}, @@ -2271,7 +2326,18 @@ end}. undefined -> undefined; _ -> {fun emqx_psk:lookup/3, <<>>} end, - Filter([{versions, Versions}, + CRLCheck = case cuttlefish:conf_get(Prefix ++ ".enable_crl_cache", Conf, false) of + true -> + HTTPTimeout = cuttlefish:conf_get(Prefix ++ ".crl_cache_http_timeout", Conf, timer:seconds(15)), + %% {crl_check, true} doesn't work + [ {crl_check, peer} + , {crl_cache, {ssl_crl_cache, {internal, [{http, HTTPTimeout}]}}} + ]; + false -> + [] + end, + Filter(CRLCheck ++ + [{versions, Versions}, {ciphers, Ciphers}, {user_lookup_fun, UserLookupFun}, {handshake_timeout, cuttlefish:conf_get(Prefix ++ ".handshake_timeout", Conf, undefined)}, diff --git a/scripts/buildx.sh b/scripts/buildx.sh index 87e548e7e..5df4cc3fe 100755 --- a/scripts/buildx.sh +++ b/scripts/buildx.sh @@ -8,7 +8,7 @@ ## i.e. will not work if docker command has to be executed with sudo ## example: -## ./scripts/buildx.sh --profile emqx --pkgtype zip --builder ghcr.io/emqx/emqx-builder/4.4-19:24.1.5-3-debian10 --arch arm64 +## ./scripts/buildx.sh --profile emqx --pkgtype zip --builder ghcr.io/emqx/emqx-builder/4.4-20:24.3.4.2-1-debian10 --arch arm64 set -euo pipefail @@ -20,7 +20,7 @@ help() { echo "--arch amd64|arm64: Target arch to build the EMQ X package for" echo "--src_dir : EMQ X source ode in this dir, default to PWD" echo "--builder : Builder image to pull" - echo " E.g. ghcr.io/emqx/emqx-builder/4.4-19:24.1.5-3-debian11" + echo " E.g. ghcr.io/emqx/emqx-builder/4.4-20:24.3.4.2-1-debian11" echo "--ssh: Pass ssh agent to the builder." echo " Also configures git in container to use ssh instead of https to clone deps" } diff --git a/scripts/check-apps-vsn.sh b/scripts/check-apps-vsn.sh index 3de9e7fe7..fe40048ad 100755 --- a/scripts/check-apps-vsn.sh +++ b/scripts/check-apps-vsn.sh @@ -9,7 +9,7 @@ set -euo pipefail ## i.e. if we are to release a new enterprise without cutting a new opensource release ## we should tag rel-e4.4.X in the opensource repo, and merge this tag to enterprise ## then cut a release from the enterprise repo. -latest_release="$(git describe --abbrev=0 --tags --match 'rel-e4.4.*' --match '[v|e]4.4*' --exclude '*beta*' --exclude '*alpha*' --exclude '*rc*')" +latest_release="$(git describe --abbrev=0 --tags --match 'rel-e4.4.*' --match '[v|e]4.4*' --exclude '*beta*' --exclude '*alpha*' --exclude '*rc*' --exclude '*gocsp*')" echo "Compare base: $latest_release" bad_app_count=0 diff --git a/scripts/pkg-full-vsn.sh b/scripts/pkg-full-vsn.sh index e118643c9..4ac62b527 100755 --- a/scripts/pkg-full-vsn.sh +++ b/scripts/pkg-full-vsn.sh @@ -18,7 +18,7 @@ case "${VSN_MATCH}" in PKG_VSN='*' ;; *) - echo "$0 ERROR: second arg must " + echo "$0 ERROR: second arg must be either 'vsn_exact' or 'vsn_matcher'" exit 1 ;; esac diff --git a/scripts/update-appup.sh b/scripts/update-appup.sh index f0055f6e7..a464d90a8 100755 --- a/scripts/update-appup.sh +++ b/scripts/update-appup.sh @@ -48,7 +48,7 @@ esac ## e4.3.11 ## rel-v4.4.3 ## rel-e4.4.3 -PREV_TAG="${PREV_TAG:-$(git describe --tag --abbrev=0 --match "[${TAG_PREFIX}|rel-]*" --exclude '*rc*' --exclude '*alpha*' --exclude '*beta*')}" +PREV_TAG="${PREV_TAG:-$(git describe --tag --abbrev=0 --match "[${TAG_PREFIX}|rel-]*" --exclude '*rc*' --exclude '*alpha*' --exclude '*beta*' --exclude '*gocsp*')}" shift 1 # bash 3.2 treat empty array as unbound, so we can't use 'ESCRIPT_ARGS=()' here, diff --git a/src/emqx_access_rule.erl b/src/emqx_access_rule.erl index decebb6b0..57c65f595 100644 --- a/src/emqx_access_rule.erl +++ b/src/emqx_access_rule.erl @@ -79,7 +79,10 @@ compile(topic, Topic) -> end. pattern(Words) -> - lists:member(<<"%u">>, Words) orelse lists:member(<<"%c">>, Words). + lists:member(<<"%u">>, Words) + orelse lists:member(<<"%c">>, Words) + orelse lists:member(<<"%cida">>, Words) + orelse lists:member(<<"%cna">>, Words). bin(L) when is_list(L) -> list_to_binary(L); @@ -165,5 +168,13 @@ feed_var(#{username := undefined}, [<<"%u">>|_Words], _Acc) -> nomatch; feed_var(ClientInfo = #{username := Username}, [<<"%u">>|Words], Acc) -> feed_var(ClientInfo, Words, [Username|Acc]); +feed_var(#{clientid_alias := undefined}, [<<"%cida">>|_Words], _Acc) -> + nomatch; +feed_var(ClientInfo = #{clientid_alias := Alias}, [<<"%cida">>|Words], Acc) -> + feed_var(ClientInfo, Words, [Alias|Acc]); +feed_var(#{common_name_alias := undefined}, [<<"%cna">>|_Words], _Acc) -> + nomatch; +feed_var(ClientInfo = #{common_name_alias := Alias}, [<<"%cna">>|Words], Acc) -> + feed_var(ClientInfo, Words, [Alias|Acc]); feed_var(ClientInfo, [W|Words], Acc) -> feed_var(ClientInfo, Words, [W|Acc]). diff --git a/src/emqx_app.erl b/src/emqx_app.erl index a2ff06cdf..7571e7c04 100644 --- a/src/emqx_app.erl +++ b/src/emqx_app.erl @@ -49,6 +49,8 @@ start(_Type, _Args) -> ok = emqx_plugins:init(), _ = emqx_plugins:load(), _ = start_ce_modules(), + set_clientid_enrichment_module(), + _ = set_special_auth_module(), register(emqx, self()), print_vsn(), {ok, Sup}. @@ -78,6 +80,33 @@ start_ce_modules() -> ok. -endif. +set_clientid_enrichment_module() -> + case emqx:get_env(clientid_enrichment_module) of + undefined -> + ok; + Mod -> + case erlang:function_exported(Mod, enrich_clientid_alias, 2) of + true -> + persistent_term:put(clientid_enrichment_module, Mod); + false -> + ok + end + end. + +set_special_auth_module() -> + case emqx:get_env(special_auth_module) of + undefined -> + ok; + Mod -> + case erlang:function_exported(Mod, check_authn, 2) of + true -> + persistent_term:put(special_auth_module, Mod), + emqx:hook('client.authenticate', fun Mod:check_authn/2, []); + false -> + ok + end + end. + %%-------------------------------------------------------------------- %% Print Banner %%-------------------------------------------------------------------- diff --git a/src/emqx_channel.erl b/src/emqx_channel.erl index 18f2eefe9..60301abd5 100644 --- a/src/emqx_channel.erl +++ b/src/emqx_channel.erl @@ -313,6 +313,7 @@ handle_in(?CONNECT_PACKET(ConnPkt) = Packet, Channel) -> fun set_log_meta/2, fun check_banned/2, fun count_flapping_event/2, + fun enrich_clientid_alias/2, fun auth_connect/2 ], ConnPkt, Channel#channel{conn_state = connecting}) of {ok, NConnPkt, NChannel = #channel{clientinfo = ClientInfo}} -> @@ -1353,6 +1354,17 @@ check_banned(_ConnPkt, #channel{clientinfo = ClientInfo = #{zone := Zone}}) -> false -> ok end. +%%-------------------------------------------------------------------- +%% Enrich ClientID Alias + +enrich_clientid_alias(Packet, Channel) -> + case persistent_term:get(clientid_enrichment_module, undefined) of + undefined -> + {ok, Channel}; + Mod -> + Mod:enrich_clientid_alias(Packet, Channel) + end. + %%-------------------------------------------------------------------- %% Auth Connect diff --git a/src/emqx_crl_cache.erl b/src/emqx_crl_cache.erl new file mode 100644 index 000000000..e6132bfef --- /dev/null +++ b/src/emqx_crl_cache.erl @@ -0,0 +1,165 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2022 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% @doc EMQX CRL cache. +%%-------------------------------------------------------------------- + +-module(emqx_crl_cache). + +%% API +-export([ start_link/0 + , start_link/1 + , refresh/1 + , evict/1 + ]). + +%% gen_server callbacks +-export([ init/1 + , handle_call/3 + , handle_cast/2 + , handle_info/2 + ]). + +%% internal exports +-export([http_get/2]). + +-behaviour(gen_server). + +-include_lib("snabbkaffe/include/snabbkaffe.hrl"). + +-define(LOG(Level, Format, Args), + logger:log(Level, "[~p] " ++ Format, [?MODULE | Args])). +-define(HTTP_TIMEOUT, timer:seconds(10)). +-define(RETRY_TIMEOUT, 5_000). + +-record(state, + { refresh_timers = #{} :: #{binary() => timer:tref()} + , refresh_interval = timer:minutes(15) :: timer:time() + }). + +%%-------------------------------------------------------------------- +%% API +%%-------------------------------------------------------------------- + +start_link() -> + URLs = emqx:get_env(crl_cache_urls, []), + RefreshIntervalMS = emqx:get_env(crl_cache_refresh_interval, + timer:minutes(15)), + start_link(#{urls => URLs, refresh_interval => RefreshIntervalMS}). + +start_link(Opts = #{urls := _, refresh_interval := _}) -> + gen_server:start_link({local, ?MODULE}, ?MODULE, Opts, []). + +refresh(URL) -> + gen_server:call(?MODULE, {refresh, URL}, ?HTTP_TIMEOUT + 2_000). + +evict(URL) -> + gen_server:cast(?MODULE, {evict, URL}). + +%%-------------------------------------------------------------------- +%% gen_server behaviour +%%-------------------------------------------------------------------- + +init(#{urls := URLs, refresh_interval := RefreshIntervalMS}) -> + State = lists:foldl(fun(URL, Acc) -> ensure_timer(URL, Acc, 0) end, + #state{refresh_interval = RefreshIntervalMS}, + URLs), + {ok, State}. + +handle_call({refresh, URL}, _From, State0) -> + case do_http_fetch_and_cache(URL) of + {error, Error} -> + ?LOG(error, "failed to fetch crl response for ~p; error: ~p", + [URL, Error]), + {reply, error, ensure_timer(URL, State0, ?RETRY_TIMEOUT)}; + {ok, CRLs} -> + ?LOG(debug, "fetched crl response for ~p", [URL]), + {reply, {ok, CRLs}, ensure_timer(URL, State0)} + end; +handle_call(Call, _From, State) -> + {reply, {error, {bad_call, Call}}, State}. + +handle_cast({evict, URL}, State0 = #state{refresh_timers = RefreshTimers0}) -> + ssl_crl_cache:delete(URL), + MTimer = maps:get(URL, RefreshTimers0, undefined), + emqx_misc:cancel_timer(MTimer), + RefreshTimers = maps:without([URL], RefreshTimers0), + State = State0#state{refresh_timers = RefreshTimers}, + ?tp(crl_cache_evict, + #{ url => URL + }), + {noreply, State}; +handle_cast(_Cast, State) -> + {noreply, State}. + +handle_info({timeout, TRef, {refresh, URL}}, + State = #state{refresh_timers = RefreshTimers}) -> + case maps:get(URL, RefreshTimers, undefined) of + TRef -> + ?tp(crl_refresh_timer, #{url => URL}), + ?LOG(debug, "refreshing crl response for ~p", [URL]), + case do_http_fetch_and_cache(URL) of + {error, Error} -> + ?LOG(error, "failed to fetch crl response for ~p; error: ~p", + [URL, Error]), + {noreply, ensure_timer(URL, State, ?RETRY_TIMEOUT)}; + {ok, _CRLs} -> + ?LOG(debug, "fetched crl response for ~p", [URL]), + {noreply, ensure_timer(URL, State)} + end; + _ -> + {noreply, State} + end; +handle_info(_Info, State) -> + {noreply, State}. + +%%-------------------------------------------------------------------- +%% internal functions +%%-------------------------------------------------------------------- + +http_get(URL, HTTPTimeout) -> + httpc:request( + get, + {URL, + [{"connection", "close"}]}, + [{timeout, HTTPTimeout}], + [{body_format, binary}] + ). + +do_http_fetch_and_cache(URL) -> + %% FIXME + Resp = ?MODULE:http_get(URL, ?HTTP_TIMEOUT), + case Resp of + {ok, {{_, 200, _}, _, Body}} -> + case parse_crls(Body) of + error -> + {error, invalid_crl}; + CRLs -> + ssl_crl_cache:insert(URL, {der, CRLs}), + ?tp(crl_cache_insert, #{url => URL}), + {ok, CRLs} + end; + {ok, {{_, Code, _}, _, Body}} -> + {error, {bad_response, #{code => Code, body => Body}}}; + {error, Error} -> + {error, {http_error, Error}} + end. + +parse_crls(Bin) -> + try + [CRL || {'CertificateList', CRL, not_encrypted} <- public_key:pem_decode(Bin)] + catch + _:_ -> + error + end. + +ensure_timer(URL, State = #state{refresh_interval = Timeout}) -> + ensure_timer(URL, State, Timeout). + +ensure_timer(URL, State = #state{refresh_timers = RefreshTimers0}, Timeout) -> + MTimer = maps:get(URL, RefreshTimers0, undefined), + emqx_misc:cancel_timer(MTimer), + RefreshTimers = RefreshTimers0#{URL => emqx_misc:start_timer( + Timeout, + {refresh, URL})}, + State#state{refresh_timers = RefreshTimers}. diff --git a/src/emqx_kernel_sup.erl b/src/emqx_kernel_sup.erl index 6a80e539b..7adbd1842 100644 --- a/src/emqx_kernel_sup.erl +++ b/src/emqx_kernel_sup.erl @@ -33,7 +33,9 @@ init([]) -> child_spec(emqx_stats, worker), child_spec(emqx_metrics, worker), child_spec(emqx_ctl, worker), - child_spec(emqx_zone, worker)]}}. + child_spec(emqx_zone, worker), + child_spec(emqx_ocsp_cache, worker), + child_spec(emqx_crl_cache, worker)]}}. child_spec(M, Type) -> child_spec(M, Type, []). diff --git a/src/emqx_listeners.erl b/src/emqx_listeners.erl index ad7d34630..1fb4fbd68 100644 --- a/src/emqx_listeners.erl +++ b/src/emqx_listeners.erl @@ -45,6 +45,7 @@ , proto := esockd:proto() , listen_on := esockd:listen_on() , opts := [esockd:option()] + , any() => term() }). %% @doc Find listener identifier by listen-on. @@ -104,8 +105,9 @@ ensure_all_started([L | Rest], Results) -> format_listen_on(ListenOn) -> format(ListenOn). -spec(start_listener(listener()) -> ok). -start_listener(#{proto := Proto, name := Name, listen_on := ListenOn, opts := Options}) -> +start_listener(Listener = #{proto := Proto, name := Name, listen_on := ListenOn}) -> ID = identifier(Proto, Name), + Options = emqx_ocsp_cache:inject_sni_fun(Listener), case start_listener(Proto, ListenOn, Options) of {ok, _} -> console_print("Start ~s listener on ~s successfully.~n", [ID, format(ListenOn)]); diff --git a/src/emqx_ocsp_cache.erl b/src/emqx_ocsp_cache.erl new file mode 100644 index 000000000..0c03987f3 --- /dev/null +++ b/src/emqx_ocsp_cache.erl @@ -0,0 +1,313 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2022 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% @doc EMQX OCSP cache. +%%-------------------------------------------------------------------- + +-module(emqx_ocsp_cache). + +-include_lib("public_key/include/public_key.hrl"). +-include_lib("ssl/src/ssl_handshake.hrl"). +-include_lib("snabbkaffe/include/snabbkaffe.hrl"). + +-behaviour(gen_server). + +-export([ start_link/0 + , sni_fun/2 + , fetch_response/1 + , register_listener/1 + , inject_sni_fun/1 + ]). + +%% gen_server API +-export([ init/1 + , handle_call/3 + , handle_cast/2 + , handle_info/2 + , code_change/3 + ]). + +%% internal export; only for mocking in tests +-export([http_get/2]). + +-define(LOG(Level, Format, Args), + logger:log(Level, "[~p] " ++ Format, [?MODULE | Args])). +-define(CACHE_TAB, ?MODULE). +-define(CALL_TIMEOUT, 20_000). +-define(RETRY_TIMEOUT, 5_000). +-define(REFRESH_TIMER(LID), {refresh_timer, LID}). + +%%-------------------------------------------------------------------- +%% API +%%-------------------------------------------------------------------- + +start_link() -> + gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). + +sni_fun(_ServerName, ListenerID) -> + Res = try + fetch_response(ListenerID) + catch + _:_ -> error + end, + case Res of + {ok, Response} -> + [{certificate_status, #certificate_status{ + status_type = ?CERTIFICATE_STATUS_TYPE_OCSP, + response = Response + }}]; + error -> + [] + end. + +fetch_response(ListenerID) -> + case ets:lookup(?CACHE_TAB, cache_key(ListenerID)) of + [{_, DERResponse}] -> + ?tp(ocsp_cache_hit, #{listener_id => ListenerID}), + ?LOG(debug, "using cached ocsp response for ~p", [ListenerID]), + {ok, DERResponse}; + [] -> + ?tp(ocsp_cache_miss, #{listener_id => ListenerID}), + ?LOG(debug, "fetching new ocsp response for ~p", [ListenerID]), + http_fetch(ListenerID) + end. + +register_listener(ListenerID) -> + gen_server:call(?MODULE, {register_listener, ListenerID}, ?CALL_TIMEOUT). + +-spec inject_sni_fun(emqx_listeners:listener()) -> [esockd:option()]. +inject_sni_fun(Listener = #{proto := Proto, name := Name, opts := Options0}) -> + %% We need to patch `sni_fun' here and not in `emqx.schema' + %% because otherwise an anonymous function will end up in + %% `app.*.config'... + ListenerID = emqx_listeners:identifier(Listener), + case proplists:get_value(ocsp_responder_url, Options0, undefined) of + undefined -> + Options0; + _URL -> + SSLOpts0 = proplists:get_value(ssl_options, Options0, []), + SNIFun = fun(SN) -> emqx_ocsp_cache:sni_fun(SN, ListenerID) end, + Options1 = proplists:delete(ssl_options, Options0), + Options = [{ssl_options, [{sni_fun, SNIFun} | SSLOpts0]} | Options1], + %% save to env + {[ThisListener0], Listeners} = + lists:partition( + fun(#{name := N, proto := P}) -> + N =:= Name andalso P =:= Proto + end, + emqx:get_env(listeners)), + ThisListener = ThisListener0#{opts => Options}, + application:set_env(emqx, listeners, [ThisListener | Listeners]), + ok = emqx_ocsp_cache:register_listener(ListenerID), + Options + end. + +%%-------------------------------------------------------------------- +%% gen_server behaviour +%%-------------------------------------------------------------------- + +init(_Args) -> + logger:set_process_metadata(#{domain => [emqx, ocsp, cache]}), + _ = ets:new(?CACHE_TAB, [ named_table + , protected + , {read_concurrency, true} + ]), + ?tp(ocsp_cache_init, #{}), + {ok, #{}}. + +handle_call({http_fetch, ListenerID}, _From, State) -> + %% Respond immediately if a concurrent call already fetched it. + case ets:lookup(?CACHE_TAB, cache_key(ListenerID)) of + [{_, Response}] -> + {reply, {ok, Response}, State}; + [] -> + case do_http_fetch_and_cache(ListenerID) of + error -> {reply, error, ensure_timer(ListenerID, State, ?RETRY_TIMEOUT)}; + {ok, Response} -> {reply, {ok, Response}, ensure_timer(ListenerID, State)} + end + end; +handle_call({register_listener, ListenerID}, _From, State0) -> + ?LOG(debug, "registering ocsp cache for ~p", [ListenerID]), + #{opts := Opts} = emqx_listeners:find_by_id(ListenerID), + RefreshInterval = proplists:get_value(ocsp_refresh_interval, Opts), + State = State0#{{refresh_interval, ListenerID} => RefreshInterval}, + {reply, ok, ensure_timer(ListenerID, State, 0)}; +handle_call(Call, _From, State) -> + {reply, {error, {unknown_call, Call}}, State}. + +handle_cast(_Cast, State) -> + {noreply, State}. + +handle_info({timeout, TRef, {refresh, ListenerID}}, State0) -> + case maps:get(?REFRESH_TIMER(ListenerID), State0, undefined) of + TRef -> + ?tp(ocsp_refresh_timer, #{listener_id => ListenerID}), + ?LOG(debug, "refreshing ocsp response for ~p", [ListenerID]), + case do_http_fetch_and_cache(ListenerID) of + error -> + ?LOG(debug, "failed to fetch ocsp response for ~p", [ListenerID]), + {noreply, ensure_timer(ListenerID, State0, ?RETRY_TIMEOUT)}; + {ok, _Response} -> + ?LOG(debug, "fetched ocsp response for ~p", [ListenerID]), + {noreply, ensure_timer(ListenerID, State0)} + end; + _ -> + {noreply, State0} + end; +handle_info(_Info, State) -> + {noreply, State}. + +code_change(_Vsn, State, _Extra) -> + %% we need to re-create the `sni_fun' lambda that the SSL + %% listeners are holding onto to avoid them becoming `badfun''s. + ListenersToPatch = + lists:filter( + fun(#{opts := Opts}) -> + undefined =/= proplists:get_value(ocsp_responder_url, Opts) + end, + emqx:get_env(listeners, [])), + PatchedListeners = [L#{opts => ?MODULE:inject_sni_fun(L)} || L <- ListenersToPatch], + lists:foreach( + fun(L) -> + emqx_listeners:update_listeners_env(update, L) + end, + PatchedListeners), + {ok, State}. + +%%-------------------------------------------------------------------- +%% internal functions +%%-------------------------------------------------------------------- + +http_fetch(ListenerID) -> + %% TODO: configurable call timeout? + gen_server:call(?MODULE, {http_fetch, ListenerID}, ?CALL_TIMEOUT). + +cache_key(ListenerID) -> + #{opts := Options} = emqx_listeners:find_by_id(ListenerID), + SSLOpts = proplists:get_value(ssl_options, Options, undefined), + ServerCertPemPath = proplists:get_value(certfile, SSLOpts, undefined), + #'Certificate'{ + tbsCertificate = + #'TBSCertificate'{ + signature = Signature + }} = read_server_cert(ServerCertPemPath), + {ocsp_response, Signature}. + +read_server_cert(ServerCertPemPath0) -> + ServerCertPemPath = to_bin(ServerCertPemPath0), + case ets:lookup(ssl_pem_cache, ServerCertPemPath) of + [{_, [{'Certificate', ServerCertDer, _} | _]}] -> + public_key:der_decode('Certificate', ServerCertDer); + [] -> + case file:read_file(ServerCertPemPath) of + {ok, ServerCertPem} -> + [{'Certificate', ServerCertDer, _} | _] = + public_key:pem_decode(ServerCertPem), + public_key:der_decode('Certificate', ServerCertDer); + {error, Error1} -> error({bad_server_cert_file, Error1}) + end + end. + +do_http_fetch_and_cache(ListenerID) -> + #{opts := Options} = emqx_listeners:find_by_id(ListenerID), + ResponderURL0 = proplists:get_value(ocsp_responder_url, Options, undefined), + ResponderURL = uri_string:normalize(ResponderURL0), + IssuerPemPath = proplists:get_value(ocsp_issuer_pem, Options, undefined), + SSLOpts = proplists:get_value(ssl_options, Options, undefined), + ServerCertPemPath = proplists:get_value(certfile, SSLOpts, undefined), + IssuerPem = case file:read_file(IssuerPemPath) of + {ok, IssuerPem0} -> IssuerPem0; + {error, Error0} -> error({bad_issuer_pem_file, Error0}) + end, + ServerCert = read_server_cert(ServerCertPemPath), + Request = build_ocsp_request(IssuerPem, ServerCert), + HTTPTimeout = proplists:get_value(ocsp_refresh_http_timeout, Options), + ?tp(ocsp_http_fetch, #{ listener_id => ListenerID + , responder_url => ResponderURL + , timeout => HTTPTimeout + }), + Resp = ?MODULE:http_get(ResponderURL ++ Request, HTTPTimeout), + case Resp of + {ok, {{_, 200, _}, _, Body}} -> + ?LOG(debug, "caching ocsp response for ~p", [ListenerID]), + true = ets:insert(?CACHE_TAB, {cache_key(ListenerID), Body}), + ?tp(ocsp_http_fetch_and_cache, #{listener_id => ListenerID}), + {ok, Body}; + {ok, {{_, Code, _}, _, Body}} -> + ?tp(error, ocsp_http_fetch_bad_code, + #{ listener_id => ListenerID + , body => Body + , code => Code + }), + ?LOG(error, "error fetching ocsp response for ~p: code ~b, body: ~p", [ListenerID, Code, Body]), + error; + {error, Error} -> + ?tp(error, ocsp_http_fetch_error, + #{ listener_id => ListenerID + , error => Error + }), + ?LOG(error, "error fetching ocsp response for ~p: ~p", [ListenerID, Error]), + error + end. + +http_get(URL, HTTPTimeout) -> + httpc:request( + get, + {URL, + [{"connection", "close"}]}, + [{timeout, HTTPTimeout}], + [{body_format, binary}] + ). + +ensure_timer(ListenerID, State) -> + Timeout = maps:get({refresh_interval, ListenerID}, State, timer:minutes(5)), + ensure_timer(ListenerID, State, Timeout). + +ensure_timer(ListenerID, State, Timeout) -> + emqx_misc:cancel_timer(maps:get(?REFRESH_TIMER(ListenerID), State, undefined)), + State#{?REFRESH_TIMER(ListenerID) => emqx_misc:start_timer( + Timeout, + {refresh, ListenerID})}. + +build_ocsp_request(IssuerPem, ServerCert) -> + [{'Certificate', IssuerDer, _} | _] = public_key:pem_decode(IssuerPem), + #'Certificate'{ + tbsCertificate = + #'TBSCertificate'{ + serialNumber = SerialNumber, + issuer = Issuer + }} = ServerCert, + #'Certificate'{ + tbsCertificate = + #'TBSCertificate'{ + subjectPublicKeyInfo = + #'SubjectPublicKeyInfo'{subjectPublicKey = IssuerPublicKeyDer} + }} = public_key:der_decode('Certificate', IssuerDer), + IssuerDNHash = crypto:hash(sha, public_key:der_encode('Name', Issuer)), + IssuerPKHash = crypto:hash(sha, IssuerPublicKeyDer), + Req = #'OCSPRequest'{ + tbsRequest = + #'TBSRequest'{ + version = 0, + requestList = + [#'Request'{ + reqCert = + #'CertID'{ + hashAlgorithm = + #'AlgorithmIdentifier'{ + algorithm = ?'id-sha1', + %% ??? + parameters = <<5, 0>> + }, + issuerNameHash = IssuerDNHash, + issuerKeyHash = IssuerPKHash, + serialNumber = SerialNumber + } + }] + } + }, + ReqDer = public_key:der_encode('OCSPRequest', Req), + base64:encode_to_string(ReqDer). + +to_bin(Str) when is_list(Str) -> list_to_binary(Str); +to_bin(Bin) when is_binary(Bin) -> Bin. diff --git a/test/emqx_access_rule_SUITE.erl b/test/emqx_access_rule_SUITE.erl index 449427590..c6d569007 100644 --- a/test/emqx_access_rule_SUITE.erl +++ b/test/emqx_access_rule_SUITE.erl @@ -31,6 +31,84 @@ init_per_suite(Config) -> end_per_suite(_Config) -> emqx_ct_helpers:stop_apps([]). +t_compile_clientid_common_name_alias_placeholders(_Config) -> + Rule1 = {allow, all, pubsub, <<"%cida">>}, + ?assertEqual( + {allow, all, pubsub, [{pattern,[<<"%cida">>]}]}, + emqx_access_rule:compile(Rule1)), + + Rule2 = {allow, all, pubsub, <<"%cna">>}, + ?assertEqual( + {allow, all, pubsub, [{pattern,[<<"%cna">>]}]}, + emqx_access_rule:compile(Rule2)), + + ok. + +t_match_clientid_common_name_alias_placeholders(_Config) -> + ClientInfo1 = #{clientid => <<"something-123456789">>, + cn => <<"another-987654321">>, + clientid_alias => <<"123456789">>, + common_name_alias => <<"987654321">> + }, + + Rule1 = {allow, all, pubsub, <<"t/%cida">>}, + Compiled1 = emqx_access_rule:compile(Rule1), + ?assertEqual({matched, allow}, + emqx_access_rule:match( + ClientInfo1, + <<"t/123456789">>, + Compiled1)), + ?assertEqual(nomatch, + emqx_access_rule:match( + ClientInfo1, + <<"t/987654321">>, + Compiled1)), + ?assertEqual(nomatch, + emqx_access_rule:match( + ClientInfo1, + <<"123456789">>, + Compiled1)), + ?assertEqual(nomatch, + emqx_access_rule:match( + ClientInfo1, + <<"t/something-123456789">>, + Compiled1)), + ?assertEqual(nomatch, + emqx_access_rule:match( + ClientInfo1, + <<"t/%cida">>, + Compiled1)), + + Rule2 = {allow, all, pubsub, <<"t/%cna">>}, + Compiled2 = emqx_access_rule:compile(Rule2), + ?assertEqual(nomatch, + emqx_access_rule:match( + ClientInfo1, + <<"t/123456789">>, + Compiled2)), + ?assertEqual({matched, allow}, + emqx_access_rule:match( + ClientInfo1, + <<"t/987654321">>, + Compiled2)), + ?assertEqual(nomatch, + emqx_access_rule:match( + ClientInfo1, + <<"987654321">>, + Compiled2)), + ?assertEqual(nomatch, + emqx_access_rule:match( + ClientInfo1, + <<"t/another-987654321">>, + Compiled2)), + ?assertEqual(nomatch, + emqx_access_rule:match( + ClientInfo1, + <<"t/%cida">>, + Compiled2)), + + ok. + t_compile(_) -> Rule1 = {allow, all, pubsub, <<"%u">>}, Compile1 = {allow, all, pubsub, [{pattern,[<<"%u">>]}]}, diff --git a/test/emqx_crl_cache_SUITE.erl b/test/emqx_crl_cache_SUITE.erl new file mode 100644 index 000000000..9fb9a03ea --- /dev/null +++ b/test/emqx_crl_cache_SUITE.erl @@ -0,0 +1,400 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2022 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- + +-module(emqx_crl_cache_SUITE). + +-compile(export_all). +-compile(nowarn_export_all). + +-include_lib("eunit/include/eunit.hrl"). +-include_lib("common_test/include/ct.hrl"). +-include_lib("snabbkaffe/include/snabbkaffe.hrl"). + +%% from ssl_manager.erl +-record(state, { + session_cache_client, + session_cache_client_cb, + session_lifetime, + certificate_db, + session_validation_timer, + session_cache_client_max, + session_client_invalidator, + options, + client_session_order + }). + +%%-------------------------------------------------------------------- +%% CT boilerplate +%%-------------------------------------------------------------------- + +all() -> + emqx_ct:all(?MODULE). + +init_per_testcase(TestCase, Config) + when TestCase =:= t_empty_cache; + TestCase =:= t_filled_cache; + TestCase =:= t_revoked -> + DataDir = ?config(data_dir, Config), + CRLFile = filename:join([DataDir, "crl.pem"]), + {ok, CRLPem} = file:read_file(CRLFile), + [{'CertificateList', CRLDer, not_encrypted}] = public_key:pem_decode(CRLPem), + ServerPid = start_crl_server(CRLPem), + IsCached = lists:member(TestCase, [t_filled_cache, t_revoked]), + ok = setup_crl_options(Config, #{is_cached => IsCached}), + [ {crl_pem, CRLPem} + , {crl_der, CRLDer} + , {http_server, ServerPid} + | Config]; +init_per_testcase(t_not_cached_and_unreachable, Config) -> + DataDir = ?config(data_dir, Config), + CRLFile = filename:join([DataDir, "crl.pem"]), + {ok, CRLPem} = file:read_file(CRLFile), + [{'CertificateList', CRLDer, not_encrypted}] = public_key:pem_decode(CRLPem), + application:stop(cowboy), + ok = setup_crl_options(Config, #{is_cached => false}), + [ {crl_pem, CRLPem} + , {crl_der, CRLDer} + | Config]; +init_per_testcase(_TestCase, Config) -> + DataDir = ?config(data_dir, Config), + CRLFile = filename:join([DataDir, "crl.pem"]), + {ok, CRLPem} = file:read_file(CRLFile), + [{'CertificateList', CRLDer, not_encrypted}] = public_key:pem_decode(CRLPem), + TestPid = self(), + ok = meck:new(emqx_crl_cache, [non_strict, passthrough, no_history, no_link]), + meck:expect(emqx_crl_cache, http_get, + fun(URL, _HTTPTimeout) -> + TestPid ! {http_get, URL}, + {ok, {{"HTTP/1.0", 200, 'OK'}, [], CRLPem}} + end), + [ {crl_pem, CRLPem} + , {crl_der, CRLDer} + | Config]. + +end_per_testcase(TestCase, Config) + when TestCase =:= t_empty_cache; + TestCase =:= t_filled_cache; + TestCase =:= t_revoked -> + ServerPid = ?config(http_server, Config), + emqx_crl_cache_http_server:stop(ServerPid), + emqx_ct_helpers:stop_apps([]), + application:set_env(emqx, crl_cache_urls, []), + application:stop(cowboy), + clear_crl_cache(), + ok; +end_per_testcase(t_not_cached_and_unreachable, _Config) -> + emqx_ct_helpers:stop_apps([]), + application:set_env(emqx, crl_cache_urls, []), + clear_crl_cache(), + ok; +end_per_testcase(_TestCase, _Config) -> + meck:unload([emqx_crl_cache]), + clear_crl_cache(), + ok. + +%%-------------------------------------------------------------------- +%% Helper functions +%%-------------------------------------------------------------------- + +assert_http_get(URL) -> + receive + {http_get, URL} -> + ok + after + 1000 -> + error({should_have_requested, URL}) + end. + +get_crl_cache_table() -> + #state{certificate_db = [_, _, _, {Ref, _}]} = sys:get_state(ssl_manager), + Ref. + +start_crl_server(Port, CRLPem) -> + {ok, LSock} = gen_tcp:listen(Port, [binary, {active, true}, reusedaddr]), + spawn_link(fun() -> accept_loop(LSock, CRLPem) end), + ok. + +accept_loop(LSock, CRLPem) -> + case gen_tcp:accept(LSock) of + {ok, Sock} -> + Worker = spawn_link(fun() -> crl_loop(Sock, CRLPem) end), + gen_tcp:controlling_process(Sock, Worker), + accept_loop(LSock, CRLPem); + {error, Reason} -> + error({accept_error, Reason}) + end. + +crl_loop(Sock, CRLPem) -> + receive + {tcp, Sock, _Data} -> + gen_tcp:send(Sock, CRLPem), + crl_loop(Sock, CRLPem); + _Msg -> + ok + end. + +drain_msgs() -> + receive + _Msg -> + drain_msgs() + after + 0 -> + ok + end. + +clear_crl_cache() -> + %% reset the CRL cache + exit(whereis(ssl_manager), kill), + ok. + +force_cacertfile(Cacertfile) -> + {SSLListeners0, OtherListeners} = lists:partition( + fun(#{proto := Proto}) -> Proto =:= ssl end, + emqx:get_env(listeners)), + SSLListeners = + lists:map( + fun(Listener = #{opts := Opts0}) -> + SSLOpts0 = proplists:get_value(ssl_options, Opts0), + %% it injects some garbage... + SSLOpts1 = lists:keydelete(cacertfile, 1, lists:keydelete(cacertfile, 1, SSLOpts0)), + SSLOpts2 = [{cacertfile, Cacertfile} | SSLOpts1], + Opts1 = lists:keyreplace(ssl_options, 1, Opts0, {ssl_options, SSLOpts2}), + Listener#{opts => Opts1} + end, + SSLListeners0), + application:set_env(emqx, listeners, SSLListeners ++ OtherListeners), + ok. + +setup_crl_options(Config, #{is_cached := IsCached}) -> + DataDir = ?config(data_dir, Config), + Cacertfile = filename:join(DataDir, "ca-chain.cert.pem"), + Certfile = filename:join(DataDir, "server.cert.pem"), + Keyfile = filename:join(DataDir, "server.key.pem"), + URLs = case IsCached of + false -> []; + true -> ["http://localhost:9878/intermediate.crl.pem"] + end, + Handler = + fun(emqx) -> + application:set_env(emqx, crl_cache_urls, URLs), + emqx_ct_helpers:change_emqx_opts( + ssl_twoway, [{ssl_options, [ {certfile, Certfile} + , {keyfile, Keyfile} + , {verify, verify_peer} + %% {crl_check, true} does not work; probably bug in OTP + , {crl_check, peer} + , {crl_cache, + {ssl_crl_cache, {internal, [{http, timer:seconds(15)}]}}} + ]}]), + %% emqx_ct_helpers:change_emqx_opts has cacertfile hardcoded.... + ok = force_cacertfile(Cacertfile), + ok; + (_) -> + ok + end, + emqx_ct_helpers:start_apps([], Handler), + case IsCached of + true -> + %% wait the cache to be filled + receive + http_get -> ok + after + 1_000 -> error(crl_cache_not_filled) + end; + false -> + %% ensure cache is empty + clear_crl_cache(), + ct:sleep(200), + ok + end, + drain_msgs(), + ok. + +start_crl_server(CRLPem) -> + application:ensure_all_started(cowboy), + {ok, ServerPid} = emqx_crl_cache_http_server:start_link(self(), 9878, CRLPem, []), + receive + {ServerPid, ready} -> ok + after + 1000 -> error(timeout_starting_http_server) + end, + ServerPid. + +%%-------------------------------------------------------------------- +%% Test cases +%%-------------------------------------------------------------------- + +t_init_empty_urls(_Config) -> + Ref = get_crl_cache_table(), + ?assertEqual([], ets:tab2list(Ref)), + ?assertMatch({ok, _}, emqx_crl_cache:start_link()), + receive + {http_get, _} -> + error(should_not_make_http_request) + after + 1000 -> ok + end, + ?assertEqual([], ets:tab2list(Ref)), + ok. + +t_init_refresh(Config) -> + CRLDer = ?config(crl_der, Config), + Ref = get_crl_cache_table(), + ?assertEqual([], ets:tab2list(Ref)), + URL1 = "http://localhost/crl1.pem", + URL2 = "http://localhost/crl2.pem", + Opts = #{ urls => [URL1, URL2] + , refresh_interval => timer:minutes(15) + }, + ok = snabbkaffe:start_trace(), + {ok, SubRef} = snabbkaffe:subscribe( + fun(#{?snk_kind := Kind}) -> + Kind =:= crl_cache_insert + end, + _NEvents = 2, + _Timeout = 2_000), + ?assertMatch({ok, _}, emqx_crl_cache:start_link(Opts)), + lists:foreach(fun assert_http_get/1, [URL1, URL2]), + {ok, _} = snabbkaffe:receive_events(SubRef), + snabbkaffe:stop(), + ?assertEqual( + [{"crl1.pem", [CRLDer]}, {"crl2.pem", [CRLDer]}], + lists:sort(ets:tab2list(Ref))), + ok. + +t_manual_refresh(Config) -> + CRLDer = ?config(crl_der, Config), + Ref = get_crl_cache_table(), + ?assertEqual([], ets:tab2list(Ref)), + {ok, _} = emqx_crl_cache:start_link(), + URL = "http://localhost/crl.pem", + ?assertEqual({ok, [CRLDer]}, emqx_crl_cache:refresh(URL)), + ?assertEqual( + [{"crl.pem", [CRLDer]}], + ets:tab2list(Ref)), + ok. + +t_refresh_request_error(_Config) -> + meck:expect(emqx_crl_cache, http_get, + fun(_URL, _HTTPTimeout) -> + {ok, {{"HTTP/1.0", 404, 'Not Found'}, [], <<"not found">>}} + end), + {ok, _} = emqx_crl_cache:start_link(), + URL = "http://localhost/crl.pem", + ?assertEqual(error, emqx_crl_cache:refresh(URL)), + ok. + +t_refresh_invalid_response(_Config) -> + meck:expect(emqx_crl_cache, http_get, + fun(_URL, _HTTPTimeout) -> + {ok, {{"HTTP/1.0", 200, 'OK'}, [], <<"not a crl">>}} + end), + {ok, _} = emqx_crl_cache:start_link(), + URL = "http://localhost/crl.pem", + ?assertEqual({ok, []}, emqx_crl_cache:refresh(URL)), + ok. + +t_refresh_http_error(_Config) -> + meck:expect(emqx_crl_cache, http_get, + fun(_URL, _HTTPTimeout) -> + {error, timeout} + end), + {ok, _} = emqx_crl_cache:start_link(), + URL = "http://localhost/crl.pem", + ?assertEqual(error, emqx_crl_cache:refresh(URL)), + ok. + +t_unknown_messages(_Config) -> + {ok, Server} = emqx_crl_cache:start_link(), + gen_server:call(Server, foo), + gen_server:cast(Server, foo), + Server ! foo, + ok. + +t_evict(_Config) -> + {ok, _} = emqx_crl_cache:start_link(), + URL = "http://localhost/crl.pem", + {ok, [_]} = emqx_crl_cache:refresh(URL), + Ref = get_crl_cache_table(), + ?assertMatch([{"crl.pem", _}], ets:tab2list(Ref)), + snabbkaffe:start_trace(), + {ok, {ok, _}} = ?wait_async_action(emqx_crl_cache:evict(URL), + #{?snk_kind := crl_cache_evict}), + snabbkaffe:stop(), + ?assertEqual([], ets:tab2list(Ref)), + ok. + +%% check that the URL in the certificate is checked on the fly if the +%% cache is empty. +t_empty_cache(Config) -> + DataDir = ?config(data_dir, Config), + ClientCert = filename:join(DataDir, "client.cert.pem"), + ClientKey = filename:join(DataDir, "client.key.pem"), + {ok, C} = emqtt:start_link([ {ssl, true} + , {ssl_opts, [ {certfile, ClientCert} + , {keyfile, ClientKey} + ]} + , {port, 8883} + ]), + {ok, _} = emqtt:connect(C), + receive + http_get -> ok + after + 2_000 -> error(should_have_checked_server) + end, + emqtt:disconnect(C), + ok. + +%% check that the URL in the certificate is *not* checked if the cache +%% contains that URL. +t_filled_cache(Config) -> + DataDir = ?config(data_dir, Config), + ClientCert = filename:join(DataDir, "client.cert.pem"), + ClientKey = filename:join(DataDir, "client.key.pem"), + {ok, C} = emqtt:start_link([ {ssl, true} + , {ssl_opts, [ {certfile, ClientCert} + , {keyfile, ClientKey} + ]} + , {port, 8883} + ]), + {ok, _} = emqtt:connect(C), + receive + http_get -> error(should_have_used_cache) + after + 2_000 -> ok + end, + emqtt:disconnect(C), + ok. + +%% If the CRL is not cached when the client tries to connect and the +%% CRL server is unreachable, the client will be denied connection. +t_not_cached_and_unreachable(Config) -> + DataDir = ?config(data_dir, Config), + ClientCert = filename:join(DataDir, "client.cert.pem"), + ClientKey = filename:join(DataDir, "client.key.pem"), + {ok, C} = emqtt:start_link([ {ssl, true} + , {ssl_opts, [ {certfile, ClientCert} + , {keyfile, ClientKey} + ]} + , {port, 8883} + ]), + Ref = get_crl_cache_table(), + ?assertEqual([], ets:tab2list(Ref)), + process_flag(trap_exit, true), + ?assertMatch({error, {{shutdown, {tls_alert, {bad_certificate, _}}}, _}}, emqtt:connect(C)), + ok. + +t_revoked(Config) -> + DataDir = ?config(data_dir, Config), + ClientCert = filename:join(DataDir, "client-revoked.cert.pem"), + ClientKey = filename:join(DataDir, "client-revoked.key.pem"), + {ok, C} = emqtt:start_link([ {ssl, true} + , {ssl_opts, [ {certfile, ClientCert} + , {keyfile, ClientKey} + ]} + , {port, 8883} + ]), + process_flag(trap_exit, true), + ?assertMatch({error, {{shutdown, {tls_alert, {certificate_revoked, _}}}, _}}, emqtt:connect(C)), + ok. diff --git a/test/emqx_crl_cache_SUITE_data/ca-chain.cert.pem b/test/emqx_crl_cache_SUITE_data/ca-chain.cert.pem new file mode 100644 index 000000000..7fed6be59 --- /dev/null +++ b/test/emqx_crl_cache_SUITE_data/ca-chain.cert.pem @@ -0,0 +1,68 @@ +-----BEGIN CERTIFICATE----- +MIIF+zCCA+OgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwbzELMAkGA1UEBhMCU0Ux +EjAQBgNVBAgMCVN0b2NraG9sbTESMBAGA1UEBwwJU3RvY2tob2xtMRIwEAYDVQQK +DAlNeU9yZ05hbWUxETAPBgNVBAsMCE15Um9vdENBMREwDwYDVQQDDAhNeVJvb3RD +QTAeFw0yMjA2MTMxMjQyMDVaFw0zMjA2MTAxMjQyMDVaMGsxCzAJBgNVBAYTAlNF +MRIwEAYDVQQIDAlTdG9ja2hvbG0xEjAQBgNVBAoMCU15T3JnTmFtZTEZMBcGA1UE +CwwQTXlJbnRlcm1lZGlhdGVDQTEZMBcGA1UEAwwQTXlJbnRlcm1lZGlhdGVDQTCC +AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAN32wUPOGrLjVHY37ICI4sWH +2GBEYgGtoKyZTfLKqK8W965uxHxMKKeKC7Ro93ScT4giR2GdsCvMyLP4Dlv7FxL7 +rWEHc7vbP22uT4NQQ0hgfl4ch8rTiSl/5FfynOuvMHnKh58z+lGyQ/uqKqAzc6OD +6FypzSQO2R6JpA+kxKlbTYmOcsLiLFKjCjxyA1ibeozUaRHjPRM7VLHVxYfmpGm/ +86NJxtHrw4hwoKIM9bfURboxfn9R1YM14mZYA6Uw2pScS1+j79tNy74NQNYu1t9E +cyPQk+AJdp2BsbR5KPYr1SMVLHlTwnzjk1IVW4wUUdX2h56ygmRNo9ui74ODfyud +4cclg45SeRdOT0w5e3g20ZvfLZpIkhXk19EIIU/YbG6GpL8gLvBHkz6vvidE7L/2 +h2//alJBeWCvyOloIWYNYnwnHGeXR2c5pzNxHipkBSuMeBaLJOO7X9oqKVanu+xq +nVagFhEYnd+T0PsPa5IVA73KiWMWWeFgJI0pRUydyp0/FhXEhkMWZNJHiscbxcdn +hTdNCAbMfV/4fMar+d/QKY/GMWUVQ4OlXUoo3WjjRi4T8NJEjZGfLdKw5x81WM4A +yqDV3OVVCBf1XrHH4IbvbUDgeG1OEGSV9pdvKX4Sm7226vdOc3HPfRnVyf/N1Pv7 +lzPbUlCheKbTW6Oeq97VAgMBAAGjgaQwgaEwHQYDVR0OBBYEFCuv1TkzC1fSgTfz +E1m1u5pRCJsVMB8GA1UdIwQYMBaAFErA8sZMX6obhoo3XvUpaTy6Z4uhMBIGA1Ud +EwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgGGMDsGA1UdHwQ0MDIwMKAuoCyG +Kmh0dHA6Ly9sb2NhbGhvc3Q6OTg3OC9pbnRlcm1lZGlhdGUuY3JsLnBlbTANBgkq +hkiG9w0BAQsFAAOCAgEAtn/u/ZfmioZyh5DNETRNAftXWvxOyi2MK8soNEsaSbmq +2ajkQwJ1MZ0+C5HuzsoEEoqStYtD3JG34ydPzQbMPwkTDg48+guu8ji30jYXGIfI +RQQfseEj1hN8wTLEDAJGl17kJA+js6dcHkJp93qocOCoOwa5MAYB8ZZq/uRJlzVt +ol6dvhBvhoxkKvJfrhg5dNISVBgIXrs5YOMyXqh6W3YMmepNjs05e5bcLYADyHCd +f4TK9pgyys4OIVHiRZo0+hlaChKo4vDK7acgZOds7qxS/sxrwKe49FTIrAWWP6fG +Ij2RHF91fLhi+10oVVSWtCyWRJOaSM4cenbLN36OUg1JswacsqojTCUylMAa8sAB +RggZ+tt8LlARj3/pdz6IWrccabC+AGZQa1kOKl97hjsE0qy9V5WOiueJ+78u+BY5 +NXIoIyuPG0WCItb76jyn7UDjiCsJt7rfJ5t5rRVLpm8YRG43KuMWjXih/bT07YdE +tA3X5Bk/XLQBqQbRKpR5+CKFhvXbNmKnuAdFbiG8UTFQdo/HGyBR7zzkF4vTqu8s +2pMl4xFAMNnGsWQYce6YioQfYL3apgx9PIqgHr+IzXae6/NIpoIs9ShymEZP1jT5 +9DqKzmUQz3czc7RNXLUZbWxoWRtivrDJGPgbSHyMqVu4h2yLINftHSGegC+ZEgc= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFzzCCA7egAwIBAgIUVYtdlrTMH1BoMy9JpiiL/FXv5NMwDQYJKoZIhvcNAQEL +BQAwbzELMAkGA1UEBhMCU0UxEjAQBgNVBAgMCVN0b2NraG9sbTESMBAGA1UEBwwJ +U3RvY2tob2xtMRIwEAYDVQQKDAlNeU9yZ05hbWUxETAPBgNVBAsMCE15Um9vdENB +MREwDwYDVQQDDAhNeVJvb3RDQTAeFw0yMjA2MTMxMjQyMDVaFw00MjA2MDgxMjQy +MDVaMG8xCzAJBgNVBAYTAlNFMRIwEAYDVQQIDAlTdG9ja2hvbG0xEjAQBgNVBAcM +CVN0b2NraG9sbTESMBAGA1UECgwJTXlPcmdOYW1lMREwDwYDVQQLDAhNeVJvb3RD +QTERMA8GA1UEAwwITXlSb290Q0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQDRKeWdrXluKLuVxTXxpWbCzBm/6x+kg8EjhIKUm9Mq0t+cOLC1sn99s4E0 +/NBA7jc3O+hQ9S2fZuMp9OUXuf8HjjtlKQY2M9T9kCE9Yxc+ygpcPEvDYl0ke/1r +m6eUOGS2LkIDHebuYbY+KGXjpaF15w49q199wQAk0sbdcCiv+OymBUxi3lrHepo3 +EBIjHtTwNtehgeiJfxCe8TLlLpbPdCsMNwlPvNa0E+1Ol1P9DfHPfK1Dt0ua8X5q +ZBhnW1cawrwzrnth2ZtrSw7RaWhtNWFt/SdedGPgVUMIgFY2I7f2wT2hoe8R/D+e +XLDzVMAW+yrlu/LlBaHE2Ffkk2La8mSHwJOvCZ/Q0V1mJnj6F4pfNemY4MnFX6Jm +7u2TSWooowMppdNPCCRlGk+XS8lfmb9N/0RsOMSz5+1u8wOfFI6Z1GaH6K2Ne2xf +VIBjhGRAiTCHDIFNYBCeAxR64zYeGFKv8L19oV6cbA6St7GMikfk5U/fyG9SUK88 +Fvjm+MrNDu/EEyjkKhK6I9ao3Pb4LcCofTcu44RBYT0HhkjqGWQgapm0i80NcRlL +yK7wm5C8Tntmbehg1KE8iqSzAcBZu6L+j4qQ90T+AIHNTmRUa9dH2uhEej4hIhjd +yJSJcDm4/CYEuK6WgZ6EuJadRy4QgPiH7zWCFXp/OGLvFmUEWQIDAQABo2MwYTAd +BgNVHQ4EFgQUSsDyxkxfqhuGijde9SlpPLpni6EwHwYDVR0jBBgwFoAUSsDyxkxf +qhuGijde9SlpPLpni6EwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYw +DQYJKoZIhvcNAQELBQADggIBAJQSy3WpS/j8emep2gJXB7jYxolhhN78DiJaqUZh +0YXa19YaghAsk5YVqbe2XLAGyFqAmmGQ6ymCbULqkdSNUTLTvwHYGFYH7NizUaCS +r9XaB9lizr7dwpd/8HwpqMq7rxNXLMNfl3iVywiKkah6r3goNwz9xgclOj1Q2Uly +s5S3BaNnf8Q9ypygbUMOalYN6KzHloj52inX3fpGfOov0O/1+bkp/3SvATU0aRpB +4bKLt99b69x1HSIkCNxeJQz2klgEMNQYGsnXOYCvnI0cOpGGDxQuIYiZbYbTJb7X +FI7FfV0ryn55mMLgZboGezMhWGYeHpWfa7H0La1ZjgedBsu3HH5VaPuHOngFNa+X +78vbWFcD09biZJwatwZlRjFGItaPSyhqSWJx1IqBZ98ZG9ziOVQ+kq1+uRecI/3S +jNw8LFDnOH20UxJnhRddA7f7cFWkk3WRIIec+wvE7uOibHDwFmqCo6JeOhkZDF5f +R1dfN4CZznMzbUb5iqUb4JwGdViijCaDHTEe3C3nKS1mPxt2kn3H1C37y71Fh5yu +mStVxjZZTdbaP8WPEAEDhK+7eB6pAKa+ERXOlWY1L5nNkh4ahIw5837yIY60oyBh +goiqX/Vy0wEJMa7HgXT3cDnW4NiXQA8nVKsRUiZco136YAATS89iLk3ibYJxzGP+ +G2LZ +-----END CERTIFICATE----- diff --git a/test/emqx_crl_cache_SUITE_data/client-revoked.cert.pem b/test/emqx_crl_cache_SUITE_data/client-revoked.cert.pem new file mode 100644 index 000000000..53bd2436c --- /dev/null +++ b/test/emqx_crl_cache_SUITE_data/client-revoked.cert.pem @@ -0,0 +1,32 @@ +-----BEGIN CERTIFICATE----- +MIIFnDCCA4SgAwIBAgICEAIwDQYJKoZIhvcNAQELBQAwazELMAkGA1UEBhMCU0Ux +EjAQBgNVBAgMCVN0b2NraG9sbTESMBAGA1UECgwJTXlPcmdOYW1lMRkwFwYDVQQL +DBBNeUludGVybWVkaWF0ZUNBMRkwFwYDVQQDDBBNeUludGVybWVkaWF0ZUNBMB4X +DTIyMDYxMzEyNDIwNVoXDTIzMDYyMzEyNDIwNVowfTELMAkGA1UEBhMCU0UxEjAQ +BgNVBAgMCVN0b2NraG9sbTESMBAGA1UEBwwJU3RvY2tob2xtMRIwEAYDVQQKDAlN +eU9yZ05hbWUxGTAXBgNVBAsMEE15SW50ZXJtZWRpYXRlQ0ExFzAVBgNVBAMMDmNs +aWVudC1yZXZva2VkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA584w +VAczDWrlIJXm6+0oYepacPPrVEMC9WwsQFO5GbBDdxrBvxgQ5u8/DDNEtYk0sJMt +zgSLxsYK5duhrwVyICXpKgxMI2fKdKP0zxB/eB4V0vrc6FqR4L8D8XoNPKSaTnjv +NNh3wjLNvmBRfHRSUCe4zEvPZzMLuBIHXRR20prtA90FFV8fliNvMCBIbFkqthjf +fQ/tSXXxNQNjacuHVfY+LVN3xu9Jjll4AaCKKz19rDexq9HTLLZ8y4jBD1eRobp+ +spKKu4HNpon+YMp3vJuNmxsTU+xBkbESWGJTFot7lZL1PVBvxdgbSd3OrPKI+QbK +06FN3iBrcW3Yjp04LQIDAQABo4IBNjCCATIwCQYDVR0TBAIwADARBglghkgBhvhC +AQEEBAMCBaAwMwYJYIZIAYb4QgENBCYWJE9wZW5TU0wgR2VuZXJhdGVkIENsaWVu +dCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUSq+9djRFuryFk0Mdqlsy0chljWswHwYD +VR0jBBgwFoAUK6/VOTMLV9KBN/MTWbW7mlEImxUwDgYDVR0PAQH/BAQDAgXgMB0G +A1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDBDA7BgNVHR8ENDAyMDCgLqAshipo +dHRwOi8vbG9jYWxob3N0Ojk4NzgvaW50ZXJtZWRpYXRlLmNybC5wZW0wMQYIKwYB +BQUHAQEEJTAjMCEGCCsGAQUFBzABhhVodHRwOi8vbG9jYWxob3N0Ojk4NzcwDQYJ +KoZIhvcNAQELBQADggIBAD4XyEjAfU0VE+YRQXUxlqpeJdMij0Io9ZCf1j6JMNFl +/p8vbOm6orK55bYWXCzRaIEkgOGlpQLDIXpViAjmBbXisA97hS1v6rW10W6LyNkN +i7LC6kTgBi4yIV3FiQk5CIE3Tj22+0GjaepHc2bPGaLSRBaoe8uBvDlDqRjxW64H +KYOIyux3WqauEziNEVklXG3VNX+6WfUw0jP9p4cglLaL43htwdavA1g7RP0wE1/6 +hvZ242jK4bvCGn/p7IDa8YtgXTufjQf6hJB9kRAJRqrSJQnihb6P9UZr2saaR7mp +28w7a6L1RLSkCSpcwRoTHgTMkFwCD/h7NxZmpoOCBk4vrHY7SXG05ptb6o7x8TD7 +lRV/+ay8EIPWCKGpTrGWHGzjxiuXw4TqnfETlvz0rFUG4TapXwmWnaSwTUk5d10v +olBQJ22CIidkKvoW/6bmD6mtyRX/F3KDTN7vHSsjvaty+TzqjWLLd2rjWpZi5xcc +h/lOyWHXbFkakRT879USUvqU/Y6+2CpbZ2ssks4+bnD8Dsdq1fFLTXtLhBBFcz11 +amuwx923tJTY9f6e7y3X/TveCKcibo+aluA4ACkYix8mR/oFxmsulW6MTVcqZZ+i +9+oo9hyOPQsJhWYISAtLBuDCqz9fKM4llmnuQZA55FuZBkSmHBRAXw5xwA+gbZcs +-----END CERTIFICATE----- diff --git a/test/emqx_crl_cache_SUITE_data/client-revoked.key.pem b/test/emqx_crl_cache_SUITE_data/client-revoked.key.pem new file mode 100644 index 000000000..b9865d4d6 --- /dev/null +++ b/test/emqx_crl_cache_SUITE_data/client-revoked.key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEA584wVAczDWrlIJXm6+0oYepacPPrVEMC9WwsQFO5GbBDdxrB +vxgQ5u8/DDNEtYk0sJMtzgSLxsYK5duhrwVyICXpKgxMI2fKdKP0zxB/eB4V0vrc +6FqR4L8D8XoNPKSaTnjvNNh3wjLNvmBRfHRSUCe4zEvPZzMLuBIHXRR20prtA90F +FV8fliNvMCBIbFkqthjffQ/tSXXxNQNjacuHVfY+LVN3xu9Jjll4AaCKKz19rDex +q9HTLLZ8y4jBD1eRobp+spKKu4HNpon+YMp3vJuNmxsTU+xBkbESWGJTFot7lZL1 +PVBvxdgbSd3OrPKI+QbK06FN3iBrcW3Yjp04LQIDAQABAoIBAQDN52MaYMLCel9I +0J6slp62SxtHFgPFdzjbk9jC0xuqa92hoIzVF6V73KxeQ/QWZOf+qN2ZEISwbh4k +CzHVa7ryP3qbtQy0rm8xqKm+fGMd6WttWxR6+Gh4AHSaPNYhNf0zE0033ciTIdmL +77ayHAk51e7a2cRDYR5ZxPnxfkoFy5M2y9U7daAZsEjIyoxjGmkO92YU4byPfUxA +vxC+rcSCUHA1OSoKONuGmwAYrLyHHoIgmVSXe6qSjgSWBPFVLp6OKTiBc7klRnlw +EAcoop7NSit+eMCqQM85Tp8wMmKd+9jfoek9yc5ahWgLDMsd5Dlc/+CXoChmQ5dO +w0h7vqbpAoGBAPk5HEuZ752VJQOtcSfJacOsYlAc8tlOBl33negGIplAKYeWKib7 +xtCzdU29oW4tPIJO6v+e7y+GcV7n2+6DpoXTUr6f4fG3vPQ3ZKPctsAHyFpKvzR3 +137YllShpgQKIcnwC7y/KG+wwgiVjZ+cDWCUuGA9/3m249DdO2b8F+jXAoGBAO4b +1KC32rjzOk3QOZWIvWADbC8MxY0jeM+phtycoeyCreSOKq6dLpX4JD5NZ1XP4uol +wr0Ta0D8FN7T/JRfT8FnoFXTJxwKTi6oGwXQ3Am/bXsz2A2H8vsXL8tgutYfX4Xi +YNf57zNiCTHLC4K9aiS8iJ2UtJIwGi558ONVOPKbAoGBAJcjS0WN1QJ7sDbKuBSo +0LsZj4WGCMA/0RyrTden4NOPVaAkMOvzRF7MdhbvKTbnuApOUbUzbVok7hvgAEBl +FleSEFwKGbu88Zoo/Z9h+nH6RkZ7jfkDtGv4bTJl1YgdnOAZ8wRD6QHS79jE2V4y +BOrNRgMXlhb6Eq5Xe+64cseBAoGBANuLB6tEukQr6AdVRbMNyGbd4QMkyIXRPhRj +IDkLpvVWrJV/S/WCcjDPAkP7xJrHulbgiEUjwZHCnE+0sD/x/ay7KofX0Ei3a8zz +LS9Ym3nValHdxIj9X9mKUIQ6ZSsG9GGTEG4zQg1jiEzEBZH/qf3DZEe/lBryhUFz +J9vEeWSfAoGAPV6RXEP4uJstCBUsWnWXGNtQ8TwS/NsH5FyPuWEXWxJwrUHEDyiA +FhRW2tGH/k5cCZw7lvzmZui+iWBv90l1/N3J2+SKZMVZNGsXCYx9szSd5uAer/Zs +emH0oOBC6NXXVDTIp+vDzy/lx6Xxcp4n6iiI3I+uCOeI5qRhM0GHcdU= +-----END RSA PRIVATE KEY----- diff --git a/test/emqx_crl_cache_SUITE_data/client.cert.pem b/test/emqx_crl_cache_SUITE_data/client.cert.pem new file mode 100644 index 000000000..e0405e673 --- /dev/null +++ b/test/emqx_crl_cache_SUITE_data/client.cert.pem @@ -0,0 +1,32 @@ +-----BEGIN CERTIFICATE----- +MIIFljCCA36gAwIBAgICEAEwDQYJKoZIhvcNAQELBQAwazELMAkGA1UEBhMCU0Ux +EjAQBgNVBAgMCVN0b2NraG9sbTESMBAGA1UECgwJTXlPcmdOYW1lMRkwFwYDVQQL +DBBNeUludGVybWVkaWF0ZUNBMRkwFwYDVQQDDBBNeUludGVybWVkaWF0ZUNBMB4X +DTIyMDYxMzEyNDIwNVoXDTIzMDYyMzEyNDIwNVowdzELMAkGA1UEBhMCU0UxEjAQ +BgNVBAgMCVN0b2NraG9sbTESMBAGA1UEBwwJU3RvY2tob2xtMRIwEAYDVQQKDAlN +eU9yZ05hbWUxGTAXBgNVBAsMEE15SW50ZXJtZWRpYXRlQ0ExETAPBgNVBAMMCE15 +Q2xpZW50MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtoHIpsxDnO4y +TLKghUGkAIg5102fcVj2sQ4kJhybnN7tfbQ0gZo0GE8A7SQAYM/lO0VcDknp/nkz +A8Lsc5Lc1SJ7z1Iyd+/ZUg3GJE/ijPoPdMlFU4/HndmoMdLhcflCRZiirnLGwdvT +bBrlGD0alK/kOqbSc9LT3oFVXWo2XvAyNtBxU7dHznxT5JuFnPyCZvoY+FvSpCbP +5rV0NLzdCpk+6C4KTs3YAaTh6kVvSCch55E7hE0I2KKLjz1Ge563vPEWOnB40p6w +ZhzcwFIbiXmq8TpA342HInES1tFRwv7ZGSs+B8t/q9nGWTDf1as4+wBrQ/i35Xav +sMuNp+O+mQIDAQABo4IBNjCCATIwCQYDVR0TBAIwADARBglghkgBhvhCAQEEBAMC +BaAwMwYJYIZIAYb4QgENBCYWJE9wZW5TU0wgR2VuZXJhdGVkIENsaWVudCBDZXJ0 +aWZpY2F0ZTAdBgNVHQ4EFgQUwRox6T4QUt1kUQDCuZ6NJNKGHIgwHwYDVR0jBBgw +FoAUK6/VOTMLV9KBN/MTWbW7mlEImxUwDgYDVR0PAQH/BAQDAgXgMB0GA1UdJQQW +MBQGCCsGAQUFBwMCBggrBgEFBQcDBDA7BgNVHR8ENDAyMDCgLqAshipodHRwOi8v +bG9jYWxob3N0Ojk4NzgvaW50ZXJtZWRpYXRlLmNybC5wZW0wMQYIKwYBBQUHAQEE +JTAjMCEGCCsGAQUFBzABhhVodHRwOi8vbG9jYWxob3N0Ojk4NzcwDQYJKoZIhvcN +AQELBQADggIBAEFjWfL32GKfOExZ+4FPUj80V282pJ4iEuRSmbti/EVZse0GsKvZ +xF1swDdoTrcdNTruHxIKfojwKz28XP6JoM6MUruxQglwdwZGJGlC0KpanR7dvQHa +XT/ushugHptobRh3+f47Gbyd0A0MKfGFLGLC1XZokdrvLPoKHCJWq/DRUgHMnn53 +FrM7a+JXZUpkSU+uqCPTMpkmuZc0E+SPVtLGiH71q1kbMzJKIKSuzkRCG91PW8fT +B2PUmy4W6YV6MUSi+jHx5JDLvjwycTsNFBiK0zZtcKsaz9QWi/qnrjkCWR0LXaRZ +W99ER7hEMe8J5CYt3lrmtaVL8lZ4TnoQE/Rsr1GBr7+WA9WnB/ijSG/3Q02hyqcK +EaFE+aqOvY03UlIyk/m+PFoRwge9vERf2dGgFN4osbczDMwXc67jx1MKyFQLQvg4 +TcN7fXCbqQeWDH0RCAF4goFGqjq/KZQYHN+BOJ1cubUxbY4+9zuQ7pZ1s1tonchA +TQFYbZNXKFUYTrgSSZRoJGGkSQkUKvARsraIdFvOt6qzLq4k86efQAgPDwFd1Kni ++iML7b3fIzHAl00WYH/hvjyjiDbh9YJpGorflgzjyt/dDNcYk5mcMNJJyrY/kPUG +3/eiMMIfW6UFSJq3ePK0NBFCzmdGWGUb+ZQS+JYP51eN2zsbZRm/Pfel +-----END CERTIFICATE----- diff --git a/test/emqx_crl_cache_SUITE_data/client.key.pem b/test/emqx_crl_cache_SUITE_data/client.key.pem new file mode 100644 index 000000000..6df146424 --- /dev/null +++ b/test/emqx_crl_cache_SUITE_data/client.key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAtoHIpsxDnO4yTLKghUGkAIg5102fcVj2sQ4kJhybnN7tfbQ0 +gZo0GE8A7SQAYM/lO0VcDknp/nkzA8Lsc5Lc1SJ7z1Iyd+/ZUg3GJE/ijPoPdMlF +U4/HndmoMdLhcflCRZiirnLGwdvTbBrlGD0alK/kOqbSc9LT3oFVXWo2XvAyNtBx +U7dHznxT5JuFnPyCZvoY+FvSpCbP5rV0NLzdCpk+6C4KTs3YAaTh6kVvSCch55E7 +hE0I2KKLjz1Ge563vPEWOnB40p6wZhzcwFIbiXmq8TpA342HInES1tFRwv7ZGSs+ +B8t/q9nGWTDf1as4+wBrQ/i35XavsMuNp+O+mQIDAQABAoIBACZaJ5xFmH/F3nQX +pXvbS2eBOQZxnWvoUg7q9dW8dUcF4cpksBP8H65sC7nJsvqlNXq7HJk0FyQOvBWy +RJYU6qsvT+1FTK2/jV+c3WKMFwOhGNZl5VemA0C8mIe/1PhqdO7DIIygOfxLAaba +EAKD9K4COGfK3rbQOw2rCBFVXI+ed0L2q+IzpyZdvtUFVdDKF9d5i3m2Qr6Ayeuq +dpf2ig69nC+4I505govODX2O9+q7x+zkddooxL72OXdEQFHn8p1K69Zk9iLz2jPX +HPVyKXGYzCOyfZZk/jJjBCv+8bkFQg/Ncm/KSBeBjp2Gxo6EjfERUhkShkxtOB5P +pAETPXECgYEA4SP/hV/MbmXnouKvjoWCZ0dYH92lNVOMMr2Ij/nOpvxTAyc1FXg+ +m5NrvMjRnGu32QfLcJmaXnK9YjqAZD9koQ3DisS0FbwTa7V8NSR5ImJfPWpa2VMP +SiyWQCh6Iba2852hl8Ryph7wbbSXAcocthx6dIuP/QsX2Q95jrh+g0cCgYEAz4XO +MSjwsrT/9i3voVKD+7eWBYkFpOJ+piYxrrmIrkMSWrwPIE9KBFfn7TlPvBPCb+W2 +uN/eLtO8HDePKfJQdZxphvlmy8eeIfjzBhod2HELJy3ShHS57uaD7iLLhPXrV9Gm +As99lS+kKGeH30j9Si53oQU0eIyH8iZX+beR3x8CgYB/DGhqZHghqIIByjhVjgPb +skgJm3NaV25bR9ejn829L9DMi7iKCBQUiSmYHB8lTSgvYhWs0hFp0QgMQYUojRmF +RRYe3gfd6AdxlbWk65MsEyU5rCXeU9/h9K1JQU5CbjBp439H/MTR982nquw4R0zS +e9mioQs9OaBYjkIDhxtliwKBgCRtaHRYq2ezPfsItTesNF7LKxptov/+ghzIN5Bk +IQn13BLxT/Zr9KIujBeoJ8br8QWTXS+2nFm78RlC526Finoaqqt2vASpVajA+mfn +zbVgooSOFpYJp1m4PRBgKzl7sYQI2QtFQNYfNsGg6sjXFx8eaQFq2HsQsAxhjq/W ++VQhAoGBALWrW12fQ0dJffRIXs/PCfwPy24071Q41YEadiZbV2/ZYlpDG6MQIENe +KaDMpflkuHhR6RQZmfJoImLvpKH0iOoxnTzPJhDQHKvwiqei3v7SuN2vh7VWGCGH +ikLROTT/oVrq4Z5XrZuAR1wtqRgi/KwvztG+QTzxLhygOmmkbSdt +-----END RSA PRIVATE KEY----- diff --git a/test/emqx_crl_cache_SUITE_data/crl.pem b/test/emqx_crl_cache_SUITE_data/crl.pem new file mode 100644 index 000000000..a119cede2 --- /dev/null +++ b/test/emqx_crl_cache_SUITE_data/crl.pem @@ -0,0 +1,20 @@ +-----BEGIN X509 CRL----- +MIIDPDCCASQCAQEwDQYJKoZIhvcNAQELBQAwazELMAkGA1UEBhMCU0UxEjAQBgNV +BAgMCVN0b2NraG9sbTESMBAGA1UECgwJTXlPcmdOYW1lMRkwFwYDVQQLDBBNeUlu +dGVybWVkaWF0ZUNBMRkwFwYDVQQDDBBNeUludGVybWVkaWF0ZUNBFw0yMjA3MjAy +MDIzNTNaFw0zMjEwMjUyMDIzNTNaMBUwEwICEAIXDTIyMDYxMzEyNDIwNVqgbjBs +MB8GA1UdIwQYMBaAFCuv1TkzC1fSgTfzE1m1u5pRCJsVMDwGA1UdHAQ1MDOgLqAs +hipodHRwOi8vbG9jYWxob3N0Ojk4NzgvaW50ZXJtZWRpYXRlLmNybC5wZW2EAf8w +CwYDVR0UBAQCAhADMA0GCSqGSIb3DQEBCwUAA4ICAQBbWdqRFsIrG6coL6ln1RL+ +uhgW9l3XMmjNlyiYHHNzOgnlBok9xu9UdaVCOKC6GEthWSzSlBY1AZugje57DQQd +RkIJol9am94lKMTjF/qhzFLiSjho8fwZGDGyES5YeZXkLqNMOf6m/drKaI3iofWf +l63qU9jY8dnSrVDkwgCguUL2FTx60v5H9NPxSctQ3VDxDvDj0sTAcHFknQcZbfvY +ZWpOYNS0FAJlQPVK9wUoDxI0LhrWDq5h/T1jcGO34fPT8RUA5HRtFVUevqSuOLWx +WTfTx5oDeMZPJTvHWUcg4yMElHty4tEvtkFxLSYbZqj7qTU+mi/LAN3UKBH/gBEN +y2OsJvFhVRgHf+zPYegf3WzBSoeaXNAJZ4UnRo34P9AL3Mrh+4OOUP++oYRKjWno +pYtAmTrIwEYoLyisEhhZ6aD92f/Op3dIYsxwhHt0n0lKrbTmUfiJUAe7kUZ4PMn4 +Gg/OHlbEDaDxW1dCymjyRGl+3/8kjy7bkYUXCf7w6JBeL2Hw2dFp1Gh13NRjre93 +PYlSOvI6QNisYGscfuYPwefXogVrNjf/ttCksMa51tUk+ylw7ZMZqQjcPPSzmwKc +5CqpnQHfolvRuN0xIVZiAn5V6/MdHm7ocrXxOkzWQyaoNODTq4js8h8eYXgAkt1w +p1PTEFBucGud7uBDE6Ub6A== +-----END X509 CRL----- diff --git a/test/emqx_crl_cache_SUITE_data/emqx_crl_cache_http_server.erl b/test/emqx_crl_cache_SUITE_data/emqx_crl_cache_http_server.erl new file mode 100644 index 000000000..cceee7333 --- /dev/null +++ b/test/emqx_crl_cache_SUITE_data/emqx_crl_cache_http_server.erl @@ -0,0 +1,67 @@ +-module(emqx_crl_cache_http_server). + +-behaviour(gen_server). +-compile([nowarn_export_all, export_all]). + +%%-------------------------------------------------------------------- +%% APIs +%%-------------------------------------------------------------------- + +start_link(Parent, BasePort, CRLPem, Opts) -> + process_flag(trap_exit, true), + stop_http(), + timer:sleep(100), + gen_server:start_link(?MODULE, {Parent, BasePort, CRLPem, Opts}, []). + +init({Parent, BasePort, CRLPem, Opts}) -> + ok = start_http(Parent, CRLPem, [{port, BasePort} | Opts]), + Parent ! {self(), ready}, + {ok, #{parent => Parent, crl_pem => CRLPem}}. + +handle_call(_Request, _From, State) -> + {reply, ignored, State}. + +handle_cast(_Msg, State) -> + {noreply, State}. + +handle_info(_Info, State) -> + {noreply, State}. + +terminate(_Reason, _State) -> + stop_http(). + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +stop(Pid) -> + ok = gen_server:stop(Pid). + +%%-------------------------------------------------------------------- +%% Callbacks +%%-------------------------------------------------------------------- + +start_http(Parent, CRLPem, Opts) -> + {ok, _Pid1} = cowboy:start_clear(http, Opts, #{ + env => #{dispatch => compile_router(Parent, CRLPem)} + }), + ok. + +stop_http() -> + cowboy:stop_listener(http), + ok. + +compile_router(Parent, CRLPem) -> + {ok, _} = application:ensure_all_started(cowboy), + cowboy_router:compile([ + {'_', [{'_', ?MODULE, #{parent => Parent, crl_pem => CRLPem}}]} + ]). + +init(Req, #{parent := Parent, crl_pem := CRLPem} = State) -> + %% assert + <<"GET">> = cowboy_req:method(Req), + Parent ! http_get, + Reply = reply(Req, CRLPem), + {ok, Reply, State}. + +reply(Req, CRLPem) -> + cowboy_req:reply(200, #{<<"content-type">> => <<"text/plain">>}, CRLPem, Req). diff --git a/test/emqx_crl_cache_SUITE_data/intermediate.crl.pem b/test/emqx_crl_cache_SUITE_data/intermediate.crl.pem new file mode 100644 index 000000000..a119cede2 --- /dev/null +++ b/test/emqx_crl_cache_SUITE_data/intermediate.crl.pem @@ -0,0 +1,20 @@ +-----BEGIN X509 CRL----- +MIIDPDCCASQCAQEwDQYJKoZIhvcNAQELBQAwazELMAkGA1UEBhMCU0UxEjAQBgNV +BAgMCVN0b2NraG9sbTESMBAGA1UECgwJTXlPcmdOYW1lMRkwFwYDVQQLDBBNeUlu +dGVybWVkaWF0ZUNBMRkwFwYDVQQDDBBNeUludGVybWVkaWF0ZUNBFw0yMjA3MjAy +MDIzNTNaFw0zMjEwMjUyMDIzNTNaMBUwEwICEAIXDTIyMDYxMzEyNDIwNVqgbjBs +MB8GA1UdIwQYMBaAFCuv1TkzC1fSgTfzE1m1u5pRCJsVMDwGA1UdHAQ1MDOgLqAs +hipodHRwOi8vbG9jYWxob3N0Ojk4NzgvaW50ZXJtZWRpYXRlLmNybC5wZW2EAf8w +CwYDVR0UBAQCAhADMA0GCSqGSIb3DQEBCwUAA4ICAQBbWdqRFsIrG6coL6ln1RL+ +uhgW9l3XMmjNlyiYHHNzOgnlBok9xu9UdaVCOKC6GEthWSzSlBY1AZugje57DQQd +RkIJol9am94lKMTjF/qhzFLiSjho8fwZGDGyES5YeZXkLqNMOf6m/drKaI3iofWf +l63qU9jY8dnSrVDkwgCguUL2FTx60v5H9NPxSctQ3VDxDvDj0sTAcHFknQcZbfvY +ZWpOYNS0FAJlQPVK9wUoDxI0LhrWDq5h/T1jcGO34fPT8RUA5HRtFVUevqSuOLWx +WTfTx5oDeMZPJTvHWUcg4yMElHty4tEvtkFxLSYbZqj7qTU+mi/LAN3UKBH/gBEN +y2OsJvFhVRgHf+zPYegf3WzBSoeaXNAJZ4UnRo34P9AL3Mrh+4OOUP++oYRKjWno +pYtAmTrIwEYoLyisEhhZ6aD92f/Op3dIYsxwhHt0n0lKrbTmUfiJUAe7kUZ4PMn4 +Gg/OHlbEDaDxW1dCymjyRGl+3/8kjy7bkYUXCf7w6JBeL2Hw2dFp1Gh13NRjre93 +PYlSOvI6QNisYGscfuYPwefXogVrNjf/ttCksMa51tUk+ylw7ZMZqQjcPPSzmwKc +5CqpnQHfolvRuN0xIVZiAn5V6/MdHm7ocrXxOkzWQyaoNODTq4js8h8eYXgAkt1w +p1PTEFBucGud7uBDE6Ub6A== +-----END X509 CRL----- diff --git a/test/emqx_crl_cache_SUITE_data/server.cert.pem b/test/emqx_crl_cache_SUITE_data/server.cert.pem new file mode 100644 index 000000000..41f04044a --- /dev/null +++ b/test/emqx_crl_cache_SUITE_data/server.cert.pem @@ -0,0 +1,35 @@ +-----BEGIN CERTIFICATE----- +MIIGCTCCA/GgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwazELMAkGA1UEBhMCU0Ux +EjAQBgNVBAgMCVN0b2NraG9sbTESMBAGA1UECgwJTXlPcmdOYW1lMRkwFwYDVQQL +DBBNeUludGVybWVkaWF0ZUNBMRkwFwYDVQQDDBBNeUludGVybWVkaWF0ZUNBMB4X +DTIyMDYxMzEyNDIwNVoXDTIzMDYyMzEyNDIwNVoweDELMAkGA1UEBhMCU0UxEjAQ +BgNVBAgMCVN0b2NraG9sbTESMBAGA1UEBwwJU3RvY2tob2xtMRIwEAYDVQQKDAlN +eU9yZ05hbWUxGTAXBgNVBAsMEE15SW50ZXJtZWRpYXRlQ0ExEjAQBgNVBAMMCWxv +Y2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOpBaUId5ga7 +NzIXvSvnzqLAsyejjEZYcSVwWQinAH3il/Y/NbUOakPbDCQbb3U7p3oD6H0R0pVx +x0nQyhh2XlSNRc8ORAcrZlfS6/Su5o07htSKL7FZ0ySYbia2EQ6ZRFpke/+UPwr3 +eXSQUhdOX+iA0Uf5gN3fUtgGorCuT2POGemGjBvYiPoA3aD1MxHyV7PnI7CoTw8Y +cvFiXIW0Jqp69ZgSuzrmfIiMyum94pfkB1ljxao9TBSFqJTn2A3ysNZAXpGPyfP+ +1zPznxiMSvAFLihUVjKwE+glSNKvdhUgl3yAxaFI9+IeoAytPNmQU+E+o4YCuRHT +TCM1Tch95/ECAwEAAaOCAagwggGkMAkGA1UdEwQCMAAwEQYJYIZIAYb4QgEBBAQD +AgZAMDMGCWCGSAGG+EIBDQQmFiRPcGVuU1NMIEdlbmVyYXRlZCBTZXJ2ZXIgQ2Vy +dGlmaWNhdGUwHQYDVR0OBBYEFAPF2lui5EhHG8lTbpglZ3BZYj2kMIGaBgNVHSME +gZIwgY+AFCuv1TkzC1fSgTfzE1m1u5pRCJsVoXOkcTBvMQswCQYDVQQGEwJTRTES +MBAGA1UECAwJU3RvY2tob2xtMRIwEAYDVQQHDAlTdG9ja2hvbG0xEjAQBgNVBAoM +CU15T3JnTmFtZTERMA8GA1UECwwITXlSb290Q0ExETAPBgNVBAMMCE15Um9vdENB +ggIQADAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwOwYDVR0f +BDQwMjAwoC6gLIYqaHR0cDovL2xvY2FsaG9zdDo5ODc4L2ludGVybWVkaWF0ZS5j +cmwucGVtMDEGCCsGAQUFBwEBBCUwIzAhBggrBgEFBQcwAYYVaHR0cDovL2xvY2Fs +aG9zdDo5ODc3MA0GCSqGSIb3DQEBCwUAA4ICAQCCTuM+KXLH2EuUn+TB78LgYrzl +/dlPj5UWPTDgiF+93fQSNvkg2uWVKPkckKNKttg+VyoRnRX87vI/bZp+dzvHfp5v +hXZuv2TkvA8ZMnJ6QUbAmiQlNTqNh0esftfXbmZAo+VvtwC/NZfevH08SO1+3R4S +PMC/Bnqmv/G3mR2qWqLu4X1ikpkgNGj4DTEkkqZFerlRyaeZHZLSVRNt6x3u4lXa +KQKbTPYP+yYpJfi0eBFy6t0hdN63BZDao3z9fulpxfjWfL32gt+TQLMf/6aJugs+ ++2BS0LAWXr9mP89Ljzo4V0G6pMBQ84/oK+mYtkFCNjRaAT+b4xGE1Ttp9H40xRZY +hm2xzVH05PgXw+IJdfnvH+245vtPUTtrnysy9FEfF7px3tAAZCiT4ogrgr52+VK6 +vGliEIxZq2ICWCK1Wy0i9zZFk3lQCFF3bWjKQCapCAFPE0naZZv7g10HkXl8+5Gc +1aSyP2LcroUyDfYx8VwKw1sLi1KtO71hs/TOTeoAPzFBmZA8h325mU5krpQtuNPg +TRxAPLztWltHR3T+WKOUMq+YZx0IiUvS6VZaCeoQeXTSZZTX5j6BXz8ffmyAcXMZ +tNATxIgDyGUfw0V6fiKQrnZCqP+fF/tdks5LKeU6tYE4JKWDarIzNNnPmhPQnJOI +YeW1Ts3aExER9EorUw== +-----END CERTIFICATE----- diff --git a/test/emqx_crl_cache_SUITE_data/server.key.pem b/test/emqx_crl_cache_SUITE_data/server.key.pem new file mode 100644 index 000000000..ab62ccffa --- /dev/null +++ b/test/emqx_crl_cache_SUITE_data/server.key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEA6kFpQh3mBrs3Mhe9K+fOosCzJ6OMRlhxJXBZCKcAfeKX9j81 +tQ5qQ9sMJBtvdTunegPofRHSlXHHSdDKGHZeVI1Fzw5EBytmV9Lr9K7mjTuG1Iov +sVnTJJhuJrYRDplEWmR7/5Q/Cvd5dJBSF05f6IDRR/mA3d9S2AaisK5PY84Z6YaM +G9iI+gDdoPUzEfJXs+cjsKhPDxhy8WJchbQmqnr1mBK7OuZ8iIzK6b3il+QHWWPF +qj1MFIWolOfYDfKw1kBekY/J8/7XM/OfGIxK8AUuKFRWMrAT6CVI0q92FSCXfIDF +oUj34h6gDK082ZBT4T6jhgK5EdNMIzVNyH3n8QIDAQABAoIBABG/NN86bqPR6SOV +YtKBtEjmOmxlWoo1xxSkB0q9hC8FTKfuL/5wgiJz5N6TaYVDKLP4udNH12FVBlkU +RUtHJGxZa5F9LjAw3IcIxrF50qOef994PJa+DF34YlfycSZe/Cuw8yfwrjoBd6Ua +De4QFPoDUFeYkme8tIUDM64Y9pDD9llfS/ZOQiCAgn+8mk99uCnJVIFo19Umof5N +009Cn6PElDKOw40Pz8s4ZJ9bpXH/YPKLeNRn0wlgFzYGhJ1z74YBmXF3CUHU6WEy +pVbJkPDAwnFScuAjpwJmzO4TDBfTBd5L/CJy4nsLpY06UOm1PHuUpAhPAgiwuADR +TM3100ECgYEA/F5Glpb0ROR38UTgCSOT8xvxyzNV84YMaQrWzGMUxqJpWWyVaKEy +k/jzUxUKbkUrJDz/Y+bcGElVCI+g8x0yekpcLn1mtB8XwltpmCSBhNBTMgUHnI9v +M+PadQhyZiJFrE/eoXo0V/K30Xv8jokWhWGTIkCdhiVSks/0eWSzPzkCgYEA7aBo +d/0e0R2Y2wXDsfGqxcKFs4IMHnzNl0isycakLOoZwinKPvb1MSg1zxW0mU+I1c/n +18rftcS8/siai26iXErDeZnvtlj+OKzQboRQf5JvDHp+rXBjGpZSmI2Nb+kAQ71S +JeIkglfBHGCKAMJQjE3N3U9YMBqCB1xdJ/TnNnkCgYA4PJnmPMU6BN9leD+kSbVS +W0vKSCpDFf/1+GBdM0cR7GclcjjpE+K9bqBqRyoH4In4jU8r5+nrz4uPWNI42qzA +64kXIwKb6MHWoaAqMxhZjEK9xrknfh79pSytH7C+aay09SdbPGwlnQSxPbvN12aZ +WmD7JQL1PaPk60pDMtluoQKBgDYJLxhyB+r3twW/VtQFJ5dW975tSUI5kSrgzOIJ +eNX52iesByCwWet2wF26Ctp+Gpi8cXVB3gNgnLW3emVQoD0qhy8E0Vz++bh7m941 +2nRYIUaOKHZaQz8NhfTI46vaKUQ+LgsNVM4LFI/WaCtqBJUTMEguPdiafo0b9Ncc +OuPJAoGBAPSYfuXinT9IAVwzIjc1TkfdN3G39ckBFQtNSvIoynBro+dlh3us+QJi +i24MtpUiBbCKmFZrrk5WrddxNLbE/JSa40+O+s1RaP3SfNuDGTWMe3HGMh+PVH8D +yte7L8pWouXSQPLVXUb3SmPboxBvXwPZMB54gkbKcSB2Gg7CjfCS +-----END RSA PRIVATE KEY----- diff --git a/test/emqx_ocsp_cache_SUITE.erl b/test/emqx_ocsp_cache_SUITE.erl new file mode 100644 index 000000000..d5d52718d --- /dev/null +++ b/test/emqx_ocsp_cache_SUITE.erl @@ -0,0 +1,467 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2022 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- + +-module(emqx_ocsp_cache_SUITE). + +-compile(export_all). +-compile(nowarn_export_all). + +-include_lib("eunit/include/eunit.hrl"). +-include_lib("common_test/include/ct.hrl"). +-include_lib("snabbkaffe/include/snabbkaffe.hrl"). + +-include_lib("ssl/src/ssl_handshake.hrl"). + +-define(CACHE_TAB, emqx_ocsp_cache). + +all() -> + [{group, openssl}] ++ tests(). + +tests() -> + emqx_ct:all(?MODULE) -- openssl_tests(). + +openssl_tests() -> + [t_openssl_client]. + +groups() -> + OpensslTests = openssl_tests(), + [ {openssl, [ {group, tls12} + , {group, tls13} + ]} + , {tls12, [ {group, with_status_request} + , {group, without_status_request} + ]} + , {tls13, [ {group, with_status_request} + , {group, without_status_request} + ]} + , {with_status_request, [], OpensslTests} + , {without_status_request, [], OpensslTests} + ]. + +init_per_suite(Config) -> + application:load(emqx), + OriginalListeners = application:get_env(emqx, listeners, []), + [ {original_listeners, OriginalListeners} + | Config]. + +end_per_suite(Config) -> + OriginalListeners = ?config(original_listeners, Config), + application:set_env(emqx, listeners, OriginalListeners), + ok. + +init_per_group(tls12, Config) -> + [{tls_vsn, "-tls1_2"} | Config]; +init_per_group(tls13, Config) -> + [{tls_vsn, "-tls1_3"} | Config]; +init_per_group(with_status_request, Config) -> + [{status_request, true} | Config]; +init_per_group(without_status_request, Config) -> + [{status_request, false} | Config]; +init_per_group(_Group, Config) -> + Config. + +end_per_group(_Group, _Config) -> + ok. + +init_per_testcase(t_openssl_client, Config) -> + ct:timetrap(10_000), + OriginalListeners = application:get_env(emqx, listeners), + DataDir = ?config(data_dir, Config), + IssuerPem = filename:join([DataDir, "ocsp-issuer.pem"]), + ServerCert = filename:join([DataDir, "server.pem"]), + ServerKey = filename:join([DataDir, "server.key"]), + CACert = filename:join([DataDir, "ca.pem"]), + Handler = + fun(emqx) -> + Listeners0 = emqx:get_env(listeners, []), + {[SSLListener0 = #{opts := Opts0}], Listeners1} = + lists:partition( + fun(#{proto := P, name := N}) -> + N =:= "external" andalso P =:= ssl + end, + Listeners0), + SSLOpts0 = proplists:get_value(ssl_options, Opts0), + SSLOpts1 = lists:foldl( + fun proplists:delete/2, + SSLOpts0, + [certfile, keyfile]), + SSLOpts2 = lists:foldl( + fun({K, V}, Acc) -> + [{K, V} | Acc] + end, + SSLOpts1, + [ {certfile, ServerCert} + , {keyfile, ServerKey} + , {cacertfile, CACert} + ]), + Opts1 = proplists:delete(ssl_options, Opts0), + Opts2 = [ {ocsp_responder_url, "http://127.0.0.1:9877"} + , {ocsp_issuer_pem, IssuerPem} + , {ssl_options, SSLOpts2} + | Opts1], + Listeners = [ SSLListener0#{opts => Opts2} + | Listeners1], + application:set_env(emqx, listeners, Listeners), + ok; + (_) -> + ok + end, + OCSPResponderPort = spawn_openssl_ocsp_responder(Config), + {os_pid, OCSPOSPid} = erlang:port_info(OCSPResponderPort, os_pid), + ensure_port_open(9877), + ct:sleep(1_000), + emqx_ct_helpers:start_apps([], Handler), + ct:sleep(1_000), + [ {original_listeners, OriginalListeners} + , {ocsp_responder_port, OCSPResponderPort} + , {ocsp_responder_os_pid, OCSPOSPid} + | Config]; +init_per_testcase(_TestCase, Config) -> + ct:timetrap(10_000), + TestPid = self(), + ok = meck:new(emqx_ocsp_cache, [non_strict, passthrough, no_history, no_link]), + meck:expect(emqx_ocsp_cache, http_get, + fun(URL, _HTTPTimeout) -> + TestPid ! {http_get, URL}, + {ok, {{"HTTP/1.0", 200, 'OK'}, [], <<"ocsp response">>}} + end), + {ok, CachePid} = emqx_ocsp_cache:start_link(), + DataDir = ?config(data_dir, Config), + application:set_env( + emqx, listeners, + [#{ proto => ssl + , name => "test_ocsp" + , opts => [ {ssl_options, [{certfile, + filename:join(DataDir, "server.pem")}]} + , {ocsp_responder_url, "http://localhost:9877"} + , {ocsp_issuer_pem, + filename:join(DataDir, "ocsp-issuer.pem")} + , {ocsp_refresh_http_timeout, 15_000} + , {ocsp_refresh_interval, 1_000} + ] + }]), + snabbkaffe:start_trace(), + [ {cache_pid, CachePid} + | Config]. + +end_per_testcase(t_openssl_client, Config) -> + OriginalListeners = ?config(original_listeners, Config), + OCSPResponderOSPid = ?config(ocsp_responder_os_pid, Config), + case OriginalListeners of + {ok, Listeners} -> application:set_env(emqx, listeners, Listeners); + _ -> ok + end, + catch kill_pid(OCSPResponderOSPid), + emqx_ct_helpers:stop_apps([]), + ok; +end_per_testcase(_TestCase, Config) -> + CachePid = ?config(cache_pid, Config), + catch gen_server:stop(CachePid), + meck:unload([emqx_ocsp_cache]), + ok. + +%%-------------------------------------------------------------------- +%% Helper functions +%%-------------------------------------------------------------------- + +assert_no_http_get() -> + receive + {http_get, _URL} -> + error(should_be_cached) + after + 0 -> + ok + end. + +assert_http_get(0) -> ok; +assert_http_get(N) when N > 0 -> + receive + {http_get, URL} -> + ?assertMatch("http://localhost:9877/" ++ _Request64, URL), + ok + after + 0 -> + error(no_http_get) + end, + assert_http_get(N - 1). + +spawn_openssl_client(TLSVsn, RequestStatus, Config) -> + DataDir = ?config(data_dir, Config), + ClientCert = filename:join([DataDir, "client.pem"]), + ClientKey = filename:join([DataDir, "client.key"]), + Cacert = filename:join([DataDir, "ca.pem"]), + Openssl = os:find_executable("openssl"), + StatusOpt = case RequestStatus of + true -> ["-status"]; + false -> [] + end, + open_port( {spawn_executable, Openssl} + , [ {args, [ "s_client" + , "-connect", "localhost:8883" + %% needed to trigger `sni_fun' + , "-servername", "localhost" + , TLSVsn + , "-CAfile", Cacert + , "-cert", ClientCert + , "-key", ClientKey + ] ++ StatusOpt} + , binary + , stderr_to_stdout + ] + ). + +spawn_openssl_ocsp_responder(Config) -> + DataDir = ?config(data_dir, Config), + IssuerCert = filename:join([DataDir, "ocsp-issuer.pem"]), + IssuerKey = filename:join([DataDir, "ocsp-issuer.key"]), + Cacert = filename:join([DataDir, "ca.pem"]), + Index = filename:join([DataDir, "index.txt"]), + Openssl = os:find_executable("openssl"), + open_port( {spawn_executable, Openssl} + , [ {args, [ "ocsp" + , "-ignore_err" + , "-port", "9877" + , "-CA", Cacert + , "-rkey", IssuerKey + , "-rsigner", IssuerCert + , "-index", Index + ]} + , binary + , stderr_to_stdout + ] + ). + +kill_pid(OSPid) -> + os:cmd("kill -9 " ++ integer_to_list(OSPid)). + +test_ocsp_connection(TLSVsn, WithRequestStatus = true, Config) -> + ClientPort = spawn_openssl_client(TLSVsn, WithRequestStatus, Config), + {os_pid, ClientOSPid} = erlang:port_info(ClientPort, os_pid), + try + timer:sleep(timer:seconds(1)), + {messages, Messages} = process_info(self(), messages), + OCSPOutput0 = [Output || {_Port, {data, Output}} <- Messages, + re:run(Output, "OCSP response:") =/= nomatch], + ?assertMatch([_], OCSPOutput0, + #{ all_messages => Messages + }), + [OCSPOutput] = OCSPOutput0, + ?assertMatch({match, _}, re:run(OCSPOutput, "OCSP Response Status: successful"), + #{all_messages => Messages}), + ?assertMatch({match, _}, re:run(OCSPOutput, "Cert Status: good"), + #{all_messages => Messages}), + ok + after + catch kill_pid(ClientOSPid) + end; +test_ocsp_connection(TLSVsn, WithRequestStatus = false, Config) -> + ClientPort = spawn_openssl_client(TLSVsn, WithRequestStatus, Config), + {os_pid, ClientOSPid} = erlang:port_info(ClientPort, os_pid), + try + timer:sleep(timer:seconds(1)), + {messages, Messages} = process_info(self(), messages), + OCSPOutput = [Output || {_Port, {data, Output}} <- Messages, + re:run(Output, "OCSP response:") =/= nomatch], + ?assertEqual([], OCSPOutput, + #{all_messages => Messages}), + ok + after + catch kill_pid(ClientOSPid) + end. + +ensure_port_open(Port) -> + do_ensure_port_open(Port, 10). + +do_ensure_port_open(Port, 0) -> + error({port_not_open, Port}); +do_ensure_port_open(Port, N) when N > 0 -> + Timeout = 1_000, + case gen_tcp:connect("localhost", Port, [], Timeout) of + {ok, Sock} -> + gen_tcp:close(Sock), + ok; + {error, _} -> + ct:sleep(500), + do_ensure_port_open(Port, N - 1) + end. + +get_sni_fun(ListenerID) -> + #{opts := Opts} = emqx_listeners:find_by_id(ListenerID), + SSLOpts = proplists:get_value(ssl_options, Opts), + proplists:get_value(sni_fun, SSLOpts). + +%%-------------------------------------------------------------------- +%% Test cases +%%-------------------------------------------------------------------- + +t_request_ocsp_response(_Config) -> + ?check_trace( + begin + ListenerID = <<"mqtt:ssl:test_ocsp">>, + %% not yet cached. + ?assertEqual([], ets:tab2list(?CACHE_TAB)), + ?assertEqual({ok, <<"ocsp response">>}, + emqx_ocsp_cache:fetch_response(ListenerID)), + assert_http_get(1), + ?assertMatch([{_, <<"ocsp response">>}], ets:tab2list(?CACHE_TAB)), + %% already cached; should not perform request again. + ?assertEqual({ok, <<"ocsp response">>}, + emqx_ocsp_cache:fetch_response(ListenerID)), + assert_no_http_get(), + ok + end, + fun(Trace) -> + ?assert( + ?strict_causality( + #{?snk_kind := ocsp_cache_miss, listener_id := _ListenerID}, + #{?snk_kind := ocsp_http_fetch_and_cache, listener_id := _ListenerID}, + Trace)), + ?assertMatch( + [_], + ?of_kind(ocsp_cache_miss, Trace)), + ?assertMatch( + [_], + ?of_kind(ocsp_http_fetch_and_cache, Trace)), + ?assertMatch( + [_], + ?of_kind(ocsp_cache_hit, Trace)), + ok + end). + +t_request_ocsp_response_restart_cache(Config) -> + process_flag(trap_exit, true), + CachePid = ?config(cache_pid, Config), + ListenerID = <<"mqtt:ssl:test_ocsp">>, + ?check_trace( + begin + [] = ets:tab2list(?CACHE_TAB), + {ok, _} = emqx_ocsp_cache:fetch_response(ListenerID), + ?wait_async_action( + begin + Ref = monitor(process, CachePid), + exit(CachePid, kill), + receive + {'DOWN', Ref, process, CachePid, killed} -> + ok + after + 1_000 -> + error(cache_not_killed) + end, + {ok, _} = emqx_ocsp_cache:start_link(), + ok + end, + #{?snk_kind := ocsp_cache_init}), + {ok, _} = emqx_ocsp_cache:fetch_response(ListenerID), + ok + end, + fun(Trace) -> + ?assertMatch( + [_, _], + ?of_kind(ocsp_http_fetch_and_cache, Trace)), + assert_http_get(2), + ok + end). + +t_request_ocsp_response_bad_http_status(_Config) -> + TestPid = self(), + meck:expect(emqx_ocsp_cache, http_get, + fun(URL, _HTTPTimeout) -> + TestPid ! {http_get, URL}, + {ok, {{"HTTP/1.0", 404, 'Not Found'}, [], <<"not found">>}} + end), + ListenerID = <<"mqtt:ssl:test_ocsp">>, + %% not yet cached. + ?assertEqual([], ets:tab2list(?CACHE_TAB)), + ?assertEqual(error, + emqx_ocsp_cache:fetch_response(ListenerID)), + assert_http_get(1), + ?assertEqual([], ets:tab2list(?CACHE_TAB)), + ok. + +t_request_ocsp_response_timeout(_Config) -> + TestPid = self(), + meck:expect(emqx_ocsp_cache, http_get, + fun(URL, _HTTPTimeout) -> + TestPid ! {http_get, URL}, + {error, timeout} + end), + ListenerID = <<"mqtt:ssl:test_ocsp">>, + %% not yet cached. + ?assertEqual([], ets:tab2list(?CACHE_TAB)), + ?assertEqual(error, + emqx_ocsp_cache:fetch_response(ListenerID)), + assert_http_get(1), + ?assertEqual([], ets:tab2list(?CACHE_TAB)), + ok. + +t_register_listener(_Config) -> + ListenerID = <<"mqtt:ssl:test_ocsp">>, + %% should fetch and cache immediately + {ok, {ok, _}} = + ?wait_async_action( + emqx_ocsp_cache:register_listener(ListenerID), + #{?snk_kind := ocsp_http_fetch_and_cache, listener_id := ListenerID}), + assert_http_get(1), + ?assertMatch([{_, <<"ocsp response">>}], ets:tab2list(?CACHE_TAB)), + ok. + +t_refresh_periodically(_Config) -> + ListenerID = <<"mqtt:ssl:test_ocsp">>, + %% should refresh periodically + {ok, SubRef} = + snabbkaffe:subscribe( + fun(#{?snk_kind := ocsp_http_fetch_and_cache, listener_id := ListenerID0}) -> + ListenerID0 =:= ListenerID; + (_) -> + false + end, + _NEvents = 2, + _Timeout = 5_000), + ok = emqx_ocsp_cache:register_listener(ListenerID), + ?assertMatch({ok, [_, _]}, snabbkaffe:receive_events(SubRef)), + assert_http_get(2), + ok. + +t_sni_fun_success(_Config) -> + ListenerID = <<"mqtt:ssl:test_ocsp">>, + ServerName = "localhost", + ?assertEqual( + [{certificate_status, + #certificate_status{ + status_type = ?CERTIFICATE_STATUS_TYPE_OCSP, + response = <<"ocsp response">> + }}], + emqx_ocsp_cache:sni_fun(ServerName, ListenerID)), + ok. + +t_sni_fun_http_error(_Config) -> + meck:expect(emqx_ocsp_cache, http_get, + fun(_URL, _HTTPTimeout) -> + {error, timeout} + end), + ListenerID = <<"mqtt:ssl:test_ocsp">>, + ServerName = "localhost", + ?assertEqual( + [], + emqx_ocsp_cache:sni_fun(ServerName, ListenerID)), + ok. + +t_code_change(_Config) -> + ListenerID = <<"mqtt:ssl:test_ocsp">>, + SNIFun0 = get_sni_fun(ListenerID), + ?assertMatch({ok, _}, emqx_ocsp_cache:code_change(vsn, state, extra)), + SNIFun1 = get_sni_fun(ListenerID), + ?assertNotEqual(SNIFun0, SNIFun1). + +t_openssl_client(Config) -> + TLSVsn = ?config(tls_vsn, Config), + WithStatusRequest = ?config(status_request, Config), + %% ensure ocsp response is already cached. + ListenerID = <<"mqtt:ssl:external">>, + ?assertMatch( + {ok, _}, + emqx_ocsp_cache:fetch_response(ListenerID), + #{msgs => process_info(self(), messages)}), + timer:sleep(500), + test_ocsp_connection(TLSVsn, WithStatusRequest, Config). diff --git a/test/emqx_ocsp_cache_SUITE_data/ca.pem b/test/emqx_ocsp_cache_SUITE_data/ca.pem new file mode 100644 index 000000000..ff61162e8 --- /dev/null +++ b/test/emqx_ocsp_cache_SUITE_data/ca.pem @@ -0,0 +1,68 @@ +-----BEGIN CERTIFICATE----- +MIIF+zCCA+OgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwbzELMAkGA1UEBhMCU0Ux +EjAQBgNVBAgMCVN0b2NraG9sbTESMBAGA1UEBwwJU3RvY2tob2xtMRIwEAYDVQQK +DAlNeU9yZ05hbWUxETAPBgNVBAsMCE15Um9vdENBMREwDwYDVQQDDAhNeVJvb3RD +QTAeFw0yMjA2MDYxNjU1NTlaFw0zMjA2MDMxNjU1NTlaMGsxCzAJBgNVBAYTAlNF +MRIwEAYDVQQIDAlTdG9ja2hvbG0xEjAQBgNVBAoMCU15T3JnTmFtZTEZMBcGA1UE +CwwQTXlJbnRlcm1lZGlhdGVDQTEZMBcGA1UEAwwQTXlJbnRlcm1lZGlhdGVDQTCC +AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANbHOqvXq/7kNmXX6DW4O6EW +/2PBdj/1oPwdIOYo6NfmieCa0BoQ/+W1k+Cj6OR75m0c6JmzPLfUckMvduuW/tJ9 +aDz/wFFbSYv/bA9f99mNXI0cebAZvYOI+cRBF4nyag3miRbMcCiOPMMDq936F3hd +Mi6CL+pIFHif74Q66Ncx10M/nL+E9sEhVyTLrAF0FsMmjkSPdzChb+HByKWRD4f2 +OYiJxhvJNjLf/+qtn7+A5cwqqbW8zYi6jJPWun24zd2HWHLfehVW8CYzGxBJR7kw +KRnPXEuXhnkgEkgfqj2122Jyd35UC/VUpJW1uJJgFNa2w7+bDj7PS6i4kdqjcEHF +GqUELN8BUIwyicvJ25KnGt0J5JnHy0rDfJeVv9/6EB1Ekm/tKwtJsKLqLThH5Kql +nFM0PUx6OV3nYzXvmmU44SvfIDPy3rhhpVCyaBBzdPyt0ugDkKuwwWcuo6KWSxR1 +lGTs2YjIYPwnRm8OqvynqCsn0vWUFOluR7suKlUmJFdPpWw04x+v1GZ3YRAig/GL +6qtByXDgZ27F4QwNPR5nGMmiu7M2QOE/nJS65A3mjrKeYSD+reQ/WbIWtLuHIhn0 +XDfHIsuzG6yK9Up1Ske+S1Uos8hTGzvF5NhJlqexd5VJ+VKYLtLkEa/tOj4p/Z5S +3nH+tEqg15nE54ovtDMdAgMBAAGjgaQwgaEwHQYDVR0OBBYEFNdekbfOfTuU9Z2G +fCx0uoDMaaT5MB8GA1UdIwQYMBaAFJf5t2YMW5pYapGnvfGXaTDV4tQKMBIGA1Ud +EwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgGGMDsGA1UdHwQ0MDIwMKAuoCyG +Kmh0dHA6Ly9sb2NhbGhvc3Q6OTg3OC9pbnRlcm1lZGlhdGUuY3JsLnBlbTANBgkq +hkiG9w0BAQsFAAOCAgEAn6Mw7+BBqzsEEtSDrd97v8niqJdVXPgxyIeHJvdZm1iF +3eRmxAUXy1DvD8tCqI8vJoz6p/4ELjedxc/Pcum0OravAElO6+jBOplwZCyhmpl8 +D4wgh/tbKnOWL20sX6sQESq/dORPf/oPDm9CBu55LaYnNTQHCNtYBZ7UrBhlsdYA +CDtsthnYcC4bYTjmm5fYlg/r3LeVN5HINYFUkh3ffKxIpqWdYOhQShOHv2k5hrzM +Lg8qu098q3kqJ+2thauXuEpD2HME0HelXaqWokwv9ETtJpuA6xQshJrT+2DjF3CY +UY3qdwjWwLr9xjf9DyHmC4hdsaO2ZAqKfMoVi6pcJ4F/20vsTM8063SFxKCbb9aw +hpzLWHB2iiKdRs/bczrW7f6XUbCFF2Et6Wl271Q7cyf8GzIdepvGsRGlcl6UaZ40 +wGfpQTgrCZ/kWHNaii4BAXGc9oj8f4NeKRVybV+cCwboed0cvNqnnKBPuz6iQz9c +JzVc7SSgOoAPJ5oh8QYi0vhfVYo34C+ZC+NderWGtMrcQrUzYH1vAv1MQ7Sfxnso +VWO7tCIjeBHcZZ+F7t2d4dJEaGYO9eYjIKSq2brFEQ+4Ix124ZWSbluCzQIXuaR3 +49qRH9Ont5UW4mGBAAgSAbyQxPkOZ3IH9vIFa61jyUE4RKGxkOAwHgajKPpmG9w= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +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 +8d9i +-----END CERTIFICATE----- diff --git a/test/emqx_ocsp_cache_SUITE_data/client.key b/test/emqx_ocsp_cache_SUITE_data/client.key new file mode 100644 index 000000000..601ac3f44 --- /dev/null +++ b/test/emqx_ocsp_cache_SUITE_data/client.key @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY----- diff --git a/test/emqx_ocsp_cache_SUITE_data/client.pem b/test/emqx_ocsp_cache_SUITE_data/client.pem new file mode 100644 index 000000000..866fbef42 --- /dev/null +++ b/test/emqx_ocsp_cache_SUITE_data/client.pem @@ -0,0 +1,34 @@ +-----BEGIN CERTIFICATE----- +MIIF1jCCA76gAwIBAgICEAMwDQYJKoZIhvcNAQELBQAwazELMAkGA1UEBhMCU0Ux +EjAQBgNVBAgMCVN0b2NraG9sbTESMBAGA1UECgwJTXlPcmdOYW1lMRkwFwYDVQQL +DBBNeUludGVybWVkaWF0ZUNBMRkwFwYDVQQDDBBNeUludGVybWVkaWF0ZUNBMB4X +DTIyMDYwNjE2NTYwMVoXDTIzMDYxNjE2NTYwMVowejELMAkGA1UEBhMCU0UxEjAQ +BgNVBAgMCVN0b2NraG9sbTESMBAGA1UEBwwJU3RvY2tob2xtMRIwEAYDVQQKDAlN +eU9yZ05hbWUxGTAXBgNVBAsMEE15SW50ZXJtZWRpYXRlQ0ExFDASBgNVBAMMC29j +c3AuY2xpZW50MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvte8rjsO +QmvDS/5Nu+HVwiphq1TmmMnkEXA2J8xar3WUbyuehW4JaWg1c2FBTPG9egGmrZ3Y +7x62ZfBCUap2IgyVZjC9F74EMOSTU9Xd2QSsU9QlzAwq6RvHfUQj2mUgEibyT4Mt +aqwiIXC7zALXNPr2JYD/08/5ORysiUUt5Yi+vrgfMYntCJ84ZGkoaP4Y/TUf4uuV +KJlrZCcQ48g8h1z9mOlxznhm26esn/S0xyuThFEUI2xWySannxSCTqVt/+IDEeVT +Iw4ZVLCTnGsoV1weKRKfrI32krnMYrSxZPoAp9ISSGio8eWmtn29O8Z/N87DEFKd +WVZCTdq1wb0RlfP6SMAYltFw01hj5UKgj9IqzrndiK7s9svs4HmyVS3TbfwJOLy8 +GTZOsNxFLbO//yVJbrXHh6YblKHa5YN4/DTiN9yigllAk/Sh3drrnBr7wn7zRD92 +0GUZPd/MI/e6My4CMcvVBAFFaTH4iu/hpYLMSH4MO7eGNIAvpLuVMNgrDTZ+JbUn +XaVbIU8CtrT+MvBFrj1eexGNWtoOA22NxBvXVrJtDV5kBO9mwhCKMnth1wxinCrO +XE8wS1YslykIWTEiKZFPSeOhivIojZNSKOrMMpKnh8nglHiuBN2RBottFzuMLAze +LIOmkhCKBVbq4wNKZxzgdfQsk9h8FSZXgOkCAwEAAaN1MHMwCQYDVR0TBAIwADAd +BgNVHQ4EFgQUMKfAZbTdlLNdtBwAz5GR633YxkwwHwYDVR0jBBgwFoAU116Rt859 +O5T1nYZ8LHS6gMxppPkwDgYDVR0PAQH/BAQDAgeAMBYGA1UdJQEB/wQMMAoGCCsG +AQUFBwMJMA0GCSqGSIb3DQEBCwUAA4ICAQA/w+fCPxmZTIH6DUmFJeNR1sFGE0yi +oaSjOxHsKhOdOnDj9UQekXEYX9qDbooWgElyMS9V65FKg2CkM3c/DD4vqK5yBiy/ +8iK21XKBV2ZeOQJdfFsZlNOgugc9FNsQhYZJfSsEdZhHyhANLj7hEeNyc1CqosFm +inmTPCwyGNYYN3DsGcSb8D4pM3CmMPxnJnZ1EI5jY11Qe4rURSax8KO3cyz5fSTq +LCNqpby1PAK5q5GRKGPN3mqKkPym0BXX7ASB8eNCYrkgqGxkHc1riE1rJ9G9xAau +6kFgS7lOuEsb12aZ95NT4tdPEXU5+3ute8Zq0PoLn2ejOCPiE0whqzF53QcdRKdO +rZ7pPzFpF6Djo+8Q8Q2kYr16KEJ2VQYLKAl5zWW5J8qBUJdG53svp/gt8SqLcvJB +HVNBIcVc/PkRF1yUgJSukVxA30bxv3AqSlATUu0f2cQAQaOftn7dL0g+20pnbJ3f +i5D9xyNozSuuCIBeutaG2vTh8LXJbauH4xC5eF606oNITuKjituHnUP2h32Q3lwN +LiOozmVfHmCz4IdvcW9sbsJu/anwMrnl4JYkBgMfTxAO9juNJOR93SrR4CUoDMaU +Xgv43+dH5Xjap/Rw14t9WfHNVI5yCwAc9f95bo98XKng0W71sKLLg2ZTPgNv38p3 +mGlYiXqsw2ImTw== +-----END CERTIFICATE----- diff --git a/test/emqx_ocsp_cache_SUITE_data/index.txt b/test/emqx_ocsp_cache_SUITE_data/index.txt new file mode 100644 index 000000000..d5c54d3f8 --- /dev/null +++ b/test/emqx_ocsp_cache_SUITE_data/index.txt @@ -0,0 +1,4 @@ +V 230616165600Z 1000 unknown /C=SE/ST=Stockholm/L=Stockholm/O=MyOrgName/OU=MyIntermediateCA/CN=localhost +V 230616165600Z 1001 unknown /C=SE/ST=Stockholm/L=Stockholm/O=MyOrgName/OU=MyIntermediateCA/CN=MyClient +V 230616165601Z 1002 unknown /C=SE/ST=Stockholm/L=Stockholm/O=MyOrgName/OU=MyIntermediateCA/CN=ocsp.server +V 230616165601Z 1003 unknown /C=SE/ST=Stockholm/L=Stockholm/O=MyOrgName/OU=MyIntermediateCA/CN=ocsp.client diff --git a/test/emqx_ocsp_cache_SUITE_data/ocsp-issuer.key b/test/emqx_ocsp_cache_SUITE_data/ocsp-issuer.key new file mode 100644 index 000000000..2adb03dad --- /dev/null +++ b/test/emqx_ocsp_cache_SUITE_data/ocsp-issuer.key @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY----- diff --git a/test/emqx_ocsp_cache_SUITE_data/ocsp-issuer.pem b/test/emqx_ocsp_cache_SUITE_data/ocsp-issuer.pem new file mode 100644 index 000000000..f30b683c7 --- /dev/null +++ b/test/emqx_ocsp_cache_SUITE_data/ocsp-issuer.pem @@ -0,0 +1,34 @@ +-----BEGIN CERTIFICATE----- +MIIF+zCCA+OgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwbzELMAkGA1UEBhMCU0Ux +EjAQBgNVBAgMCVN0b2NraG9sbTESMBAGA1UEBwwJU3RvY2tob2xtMRIwEAYDVQQK +DAlNeU9yZ05hbWUxETAPBgNVBAsMCE15Um9vdENBMREwDwYDVQQDDAhNeVJvb3RD +QTAeFw0yMjA2MDYxNjU1NTlaFw0zMjA2MDMxNjU1NTlaMGsxCzAJBgNVBAYTAlNF +MRIwEAYDVQQIDAlTdG9ja2hvbG0xEjAQBgNVBAoMCU15T3JnTmFtZTEZMBcGA1UE +CwwQTXlJbnRlcm1lZGlhdGVDQTEZMBcGA1UEAwwQTXlJbnRlcm1lZGlhdGVDQTCC +AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANbHOqvXq/7kNmXX6DW4O6EW +/2PBdj/1oPwdIOYo6NfmieCa0BoQ/+W1k+Cj6OR75m0c6JmzPLfUckMvduuW/tJ9 +aDz/wFFbSYv/bA9f99mNXI0cebAZvYOI+cRBF4nyag3miRbMcCiOPMMDq936F3hd +Mi6CL+pIFHif74Q66Ncx10M/nL+E9sEhVyTLrAF0FsMmjkSPdzChb+HByKWRD4f2 +OYiJxhvJNjLf/+qtn7+A5cwqqbW8zYi6jJPWun24zd2HWHLfehVW8CYzGxBJR7kw +KRnPXEuXhnkgEkgfqj2122Jyd35UC/VUpJW1uJJgFNa2w7+bDj7PS6i4kdqjcEHF +GqUELN8BUIwyicvJ25KnGt0J5JnHy0rDfJeVv9/6EB1Ekm/tKwtJsKLqLThH5Kql +nFM0PUx6OV3nYzXvmmU44SvfIDPy3rhhpVCyaBBzdPyt0ugDkKuwwWcuo6KWSxR1 +lGTs2YjIYPwnRm8OqvynqCsn0vWUFOluR7suKlUmJFdPpWw04x+v1GZ3YRAig/GL +6qtByXDgZ27F4QwNPR5nGMmiu7M2QOE/nJS65A3mjrKeYSD+reQ/WbIWtLuHIhn0 +XDfHIsuzG6yK9Up1Ske+S1Uos8hTGzvF5NhJlqexd5VJ+VKYLtLkEa/tOj4p/Z5S +3nH+tEqg15nE54ovtDMdAgMBAAGjgaQwgaEwHQYDVR0OBBYEFNdekbfOfTuU9Z2G +fCx0uoDMaaT5MB8GA1UdIwQYMBaAFJf5t2YMW5pYapGnvfGXaTDV4tQKMBIGA1Ud +EwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgGGMDsGA1UdHwQ0MDIwMKAuoCyG +Kmh0dHA6Ly9sb2NhbGhvc3Q6OTg3OC9pbnRlcm1lZGlhdGUuY3JsLnBlbTANBgkq +hkiG9w0BAQsFAAOCAgEAn6Mw7+BBqzsEEtSDrd97v8niqJdVXPgxyIeHJvdZm1iF +3eRmxAUXy1DvD8tCqI8vJoz6p/4ELjedxc/Pcum0OravAElO6+jBOplwZCyhmpl8 +D4wgh/tbKnOWL20sX6sQESq/dORPf/oPDm9CBu55LaYnNTQHCNtYBZ7UrBhlsdYA +CDtsthnYcC4bYTjmm5fYlg/r3LeVN5HINYFUkh3ffKxIpqWdYOhQShOHv2k5hrzM +Lg8qu098q3kqJ+2thauXuEpD2HME0HelXaqWokwv9ETtJpuA6xQshJrT+2DjF3CY +UY3qdwjWwLr9xjf9DyHmC4hdsaO2ZAqKfMoVi6pcJ4F/20vsTM8063SFxKCbb9aw +hpzLWHB2iiKdRs/bczrW7f6XUbCFF2Et6Wl271Q7cyf8GzIdepvGsRGlcl6UaZ40 +wGfpQTgrCZ/kWHNaii4BAXGc9oj8f4NeKRVybV+cCwboed0cvNqnnKBPuz6iQz9c +JzVc7SSgOoAPJ5oh8QYi0vhfVYo34C+ZC+NderWGtMrcQrUzYH1vAv1MQ7Sfxnso +VWO7tCIjeBHcZZ+F7t2d4dJEaGYO9eYjIKSq2brFEQ+4Ix124ZWSbluCzQIXuaR3 +49qRH9Ont5UW4mGBAAgSAbyQxPkOZ3IH9vIFa61jyUE4RKGxkOAwHgajKPpmG9w= +-----END CERTIFICATE----- diff --git a/test/emqx_ocsp_cache_SUITE_data/server.key b/test/emqx_ocsp_cache_SUITE_data/server.key new file mode 100644 index 000000000..601ac3f44 --- /dev/null +++ b/test/emqx_ocsp_cache_SUITE_data/server.key @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY----- diff --git a/test/emqx_ocsp_cache_SUITE_data/server.pem b/test/emqx_ocsp_cache_SUITE_data/server.pem new file mode 100644 index 000000000..10ad2fcf1 --- /dev/null +++ b/test/emqx_ocsp_cache_SUITE_data/server.pem @@ -0,0 +1,34 @@ +-----BEGIN CERTIFICATE----- +MIIF1jCCA76gAwIBAgICEAIwDQYJKoZIhvcNAQELBQAwazELMAkGA1UEBhMCU0Ux +EjAQBgNVBAgMCVN0b2NraG9sbTESMBAGA1UECgwJTXlPcmdOYW1lMRkwFwYDVQQL +DBBNeUludGVybWVkaWF0ZUNBMRkwFwYDVQQDDBBNeUludGVybWVkaWF0ZUNBMB4X +DTIyMDYwNjE2NTYwMVoXDTIzMDYxNjE2NTYwMVowejELMAkGA1UEBhMCU0UxEjAQ +BgNVBAgMCVN0b2NraG9sbTESMBAGA1UEBwwJU3RvY2tob2xtMRIwEAYDVQQKDAlN +eU9yZ05hbWUxGTAXBgNVBAsMEE15SW50ZXJtZWRpYXRlQ0ExFDASBgNVBAMMC29j +c3Auc2VydmVyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvte8rjsO +QmvDS/5Nu+HVwiphq1TmmMnkEXA2J8xar3WUbyuehW4JaWg1c2FBTPG9egGmrZ3Y +7x62ZfBCUap2IgyVZjC9F74EMOSTU9Xd2QSsU9QlzAwq6RvHfUQj2mUgEibyT4Mt +aqwiIXC7zALXNPr2JYD/08/5ORysiUUt5Yi+vrgfMYntCJ84ZGkoaP4Y/TUf4uuV +KJlrZCcQ48g8h1z9mOlxznhm26esn/S0xyuThFEUI2xWySannxSCTqVt/+IDEeVT +Iw4ZVLCTnGsoV1weKRKfrI32krnMYrSxZPoAp9ISSGio8eWmtn29O8Z/N87DEFKd +WVZCTdq1wb0RlfP6SMAYltFw01hj5UKgj9IqzrndiK7s9svs4HmyVS3TbfwJOLy8 +GTZOsNxFLbO//yVJbrXHh6YblKHa5YN4/DTiN9yigllAk/Sh3drrnBr7wn7zRD92 +0GUZPd/MI/e6My4CMcvVBAFFaTH4iu/hpYLMSH4MO7eGNIAvpLuVMNgrDTZ+JbUn +XaVbIU8CtrT+MvBFrj1eexGNWtoOA22NxBvXVrJtDV5kBO9mwhCKMnth1wxinCrO +XE8wS1YslykIWTEiKZFPSeOhivIojZNSKOrMMpKnh8nglHiuBN2RBottFzuMLAze +LIOmkhCKBVbq4wNKZxzgdfQsk9h8FSZXgOkCAwEAAaN1MHMwCQYDVR0TBAIwADAd +BgNVHQ4EFgQUMKfAZbTdlLNdtBwAz5GR633YxkwwHwYDVR0jBBgwFoAU116Rt859 +O5T1nYZ8LHS6gMxppPkwDgYDVR0PAQH/BAQDAgeAMBYGA1UdJQEB/wQMMAoGCCsG +AQUFBwMJMA0GCSqGSIb3DQEBCwUAA4ICAQCT7/RmzBl1TAQIvSFlnmqVd9Ygh1MD +kKcLA9akepntTvZ4nlmHZergx8D2CDovT3wKonvmp8pBt83sXDBjZxejET7TXX8j +NlvlwZk99/ikWBI0ZA8udl+V15HOVQKF571eeSgRgrsy19YiqLwoK5hZdiYpBLl7 +zy9EO9X+zggzerAGBDuf87BxAbYChSjVC4PBgsWfGgD0SMehGvVDq90u5FYr8Fd7 +karVIQYZM3+z/BPqjyX9Ac6bfNFHrxj3cXifu4cLS4Ly0wWNIuNo9t733wfcP6ff +PYaibEWsQ7iLH8E5Q/cJ4Pwrwqpxh7PuJlHWe8ObmR2I5YiHIi6dWHwKxveAw3ks +Ep56kdEk69jBgT51VzvLhIunSMpf8glPWIL1GGyRyFvDGWeHFPq2zLuEivKlYQpY +Nm3xn6yTiO2d6qTgDMTB+nO0mM2dpk8yFepsC4Jsgw6iHh5ofuyXS2GleucxPiii +nPvNKFOFPXYXqqScwisnQ2GJjzmLap3SWMRxHt72IyecAQ+e6Bb5WuGM2DOu16in +Ds2oXiE3zOPCOyKwA/IOawNBPQvqmNPdDBYVEdYcQelw9KfiRtyyRppl+EIYM4ay +ekIKHwN3efb45+TnVAY2ea936vfjyb5C/frpnP8voa6aEkgzOSIxAuLMWQg20Y94 +jl8CEkTUF4vfqg== +-----END CERTIFICATE-----