feat: add ocsp stapling and crl support to mqtt ssl listener
This commit is contained in:
parent
422597a441
commit
52263a0448
|
@ -1753,6 +1753,63 @@ server_ssl_opts_schema_gc_after_handshake {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
server_ssl_opts_schema_enable_ocsp_stapling {
|
||||||
|
desc {
|
||||||
|
en: "Whether to enable OCSP stapling for the listener. If set to true,"
|
||||||
|
" requires defining the OCSP responder URL and issuer PEM path."
|
||||||
|
zh: "是否为监听器启用OCSP装订功能。 如果设置为 true,"
|
||||||
|
"需要定义OCSP响应者URL和发行者PEM路径。"
|
||||||
|
}
|
||||||
|
label: {
|
||||||
|
en: "Enable OCSP Stapling"
|
||||||
|
zh: "启用OCSP订书机"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
server_ssl_opts_schema_ocsp_responder_url {
|
||||||
|
desc {
|
||||||
|
en: "URL for the OCSP responder to check the server certificate against."
|
||||||
|
zh: "用于检查服务器证书的OCSP响应器的URL。"
|
||||||
|
}
|
||||||
|
label: {
|
||||||
|
en: "OCSP Responder URL"
|
||||||
|
zh: "OCSP响应者URL"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
server_ssl_opts_schema_ocsp_issuer_pem {
|
||||||
|
desc {
|
||||||
|
en: "PEM-encoded certificate of the OCSP issuer for the server certificate."
|
||||||
|
zh: "服务器证书的OCSP签发者的PEM编码证书。"
|
||||||
|
}
|
||||||
|
label: {
|
||||||
|
en: "OCSP Issuer Certificate"
|
||||||
|
zh: "OCSP发行人证书"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
server_ssl_opts_schema_ocsp_refresh_interval {
|
||||||
|
desc {
|
||||||
|
en: "The period to refresh the OCSP response for the server."
|
||||||
|
zh: "为服务器刷新OCSP响应的周期。"
|
||||||
|
}
|
||||||
|
label: {
|
||||||
|
en: "OCSP Refresh Interval"
|
||||||
|
zh: "OCSP刷新间隔"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
server_ssl_opts_schema_ocsp_refresh_http_timeout {
|
||||||
|
desc {
|
||||||
|
en: "The timeout for the HTTP request when checking OCSP responses."
|
||||||
|
zh: "检查OCSP响应时,HTTP请求的超时。"
|
||||||
|
}
|
||||||
|
label: {
|
||||||
|
en: "OCSP Refresh HTTP Timeout"
|
||||||
|
zh: "OCSP刷新HTTP超时"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fields_listeners_tcp {
|
fields_listeners_tcp {
|
||||||
desc {
|
desc {
|
||||||
en: """TCP listeners."""
|
en: """TCP listeners."""
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Copyright (c) 2022-2023 EMQ Technologies Co., Ltd. All Rights Reserved.
|
||||||
|
%%
|
||||||
|
%% Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
%% you may not use this file except in compliance with the License.
|
||||||
|
%% You may obtain a copy of the License at
|
||||||
|
%%
|
||||||
|
%% http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
%%
|
||||||
|
%% Unless required by applicable law or agreed to in writing, software
|
||||||
|
%% distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
%% See the License for the specific language governing permissions and
|
||||||
|
%% limitations under the License.
|
||||||
|
%%
|
||||||
|
%% @doc Never update this module, create a v2 instead.
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
-module(emqx_const_v1).
|
||||||
|
|
||||||
|
-export([make_sni_fun/1]).
|
||||||
|
|
||||||
|
make_sni_fun(ListenerID) ->
|
||||||
|
fun(SN) -> emqx_ocsp_cache:sni_fun(SN, ListenerID) end.
|
|
@ -35,7 +35,8 @@ init([]) ->
|
||||||
child_spec(emqx_hooks, worker),
|
child_spec(emqx_hooks, worker),
|
||||||
child_spec(emqx_stats, worker),
|
child_spec(emqx_stats, worker),
|
||||||
child_spec(emqx_metrics, worker),
|
child_spec(emqx_metrics, worker),
|
||||||
child_spec(emqx_authn_authz_metrics_sup, supervisor)
|
child_spec(emqx_authn_authz_metrics_sup, supervisor),
|
||||||
|
child_spec(emqx_ocsp_cache, worker)
|
||||||
]
|
]
|
||||||
}}.
|
}}.
|
||||||
|
|
||||||
|
|
|
@ -484,8 +484,12 @@ esockd_opts(ListenerId, Type, Opts0) ->
|
||||||
},
|
},
|
||||||
maps:to_list(
|
maps:to_list(
|
||||||
case Type of
|
case Type of
|
||||||
tcp -> Opts3#{tcp_options => tcp_opts(Opts0)};
|
tcp ->
|
||||||
ssl -> Opts3#{ssl_options => ssl_opts(Opts0), tcp_options => tcp_opts(Opts0)}
|
Opts3#{tcp_options => tcp_opts(Opts0)};
|
||||||
|
ssl ->
|
||||||
|
OptsWithSNI = inject_sni_fun(ListenerId, Opts0),
|
||||||
|
SSLOpts = ssl_opts(OptsWithSNI),
|
||||||
|
Opts3#{ssl_options => SSLOpts, tcp_options => tcp_opts(Opts0)}
|
||||||
end
|
end
|
||||||
).
|
).
|
||||||
|
|
||||||
|
@ -785,3 +789,8 @@ quic_listener_optional_settings() ->
|
||||||
max_binding_stateless_operations,
|
max_binding_stateless_operations,
|
||||||
stateless_operation_expiration_ms
|
stateless_operation_expiration_ms
|
||||||
].
|
].
|
||||||
|
|
||||||
|
inject_sni_fun(ListenerId, Conf = #{ssl_options := #{ocsp := #{enable_ocsp_stapling := true}}}) ->
|
||||||
|
emqx_ocsp_cache:inject_sni_fun(ListenerId, Conf);
|
||||||
|
inject_sni_fun(_ListenerId, Conf) ->
|
||||||
|
Conf.
|
||||||
|
|
|
@ -0,0 +1,521 @@
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Copyright (c) 2022-2023 EMQ Technologies Co., Ltd. All Rights Reserved.
|
||||||
|
%%
|
||||||
|
%% Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
%% you may not use this file except in compliance with the License.
|
||||||
|
%% You may obtain a copy of the License at
|
||||||
|
%%
|
||||||
|
%% http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
%%
|
||||||
|
%% Unless required by applicable law or agreed to in writing, software
|
||||||
|
%% distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
%% See the License for the specific language governing permissions and
|
||||||
|
%% limitations under the License.
|
||||||
|
%%
|
||||||
|
%% @doc EMQX OCSP cache.
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
-module(emqx_ocsp_cache).
|
||||||
|
|
||||||
|
-include("logger.hrl").
|
||||||
|
-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/2,
|
||||||
|
inject_sni_fun/2
|
||||||
|
]).
|
||||||
|
|
||||||
|
%% 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(CACHE_TAB, ?MODULE).
|
||||||
|
-define(CALL_TIMEOUT, 20_000).
|
||||||
|
-define(RETRY_TIMEOUT, 5_000).
|
||||||
|
-define(REFRESH_TIMER(LID), {refresh_timer, LID}).
|
||||||
|
-ifdef(TEST).
|
||||||
|
-define(MIN_REFRESH_INTERVAL, timer:seconds(5)).
|
||||||
|
-else.
|
||||||
|
-define(MIN_REFRESH_INTERVAL, timer:minutes(1)).
|
||||||
|
-endif.
|
||||||
|
|
||||||
|
-define(WITH_LISTENER_CONFIG(ListenerID, ConfPath, Pattern, ErrorResp, Action),
|
||||||
|
case emqx_listeners:parse_listener_id(ListenerID) of
|
||||||
|
{ok, #{type := Type, name := Name}} ->
|
||||||
|
case emqx_config:get_listener_conf(Type, Name, ConfPath, not_found) of
|
||||||
|
not_found ->
|
||||||
|
?SLOG(error, #{
|
||||||
|
msg => "listener_config_missing",
|
||||||
|
listener_id => ListenerID
|
||||||
|
}),
|
||||||
|
(ErrorResp);
|
||||||
|
Pattern ->
|
||||||
|
Action;
|
||||||
|
OtherConfig ->
|
||||||
|
?SLOG(error, #{
|
||||||
|
msg => "listener_config_inconsistent",
|
||||||
|
listener_id => ListenerID,
|
||||||
|
config => OtherConfig
|
||||||
|
}),
|
||||||
|
(ErrorResp)
|
||||||
|
end;
|
||||||
|
_Err ->
|
||||||
|
?SLOG(error, #{
|
||||||
|
msg => "listener_id_not_found",
|
||||||
|
listener_id => ListenerID
|
||||||
|
}),
|
||||||
|
(ErrorResp)
|
||||||
|
end
|
||||||
|
).
|
||||||
|
|
||||||
|
%% Allow usage of OTP certificate record fields (camelCase).
|
||||||
|
-elvis([
|
||||||
|
{elvis_style, atom_naming_convention, #{
|
||||||
|
regex => "^([a-z][a-z0-9]*_?)([a-zA-Z0-9]*_?)*$",
|
||||||
|
enclosed_atoms => ".*"
|
||||||
|
}}
|
||||||
|
]).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% 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 do_lookup(ListenerID) of
|
||||||
|
{ok, DERResponse} ->
|
||||||
|
{ok, DERResponse};
|
||||||
|
{error, invalid_listener_id} ->
|
||||||
|
error;
|
||||||
|
{error, not_cached} ->
|
||||||
|
?tp(ocsp_cache_miss, #{listener_id => ListenerID}),
|
||||||
|
?SLOG(debug, #{
|
||||||
|
msg => "fetching_new_ocsp_response",
|
||||||
|
listener_id => ListenerID
|
||||||
|
}),
|
||||||
|
http_fetch(ListenerID)
|
||||||
|
end.
|
||||||
|
|
||||||
|
register_listener(ListenerID, Opts) ->
|
||||||
|
gen_server:call(?MODULE, {register_listener, ListenerID, Opts}, ?CALL_TIMEOUT).
|
||||||
|
|
||||||
|
-spec inject_sni_fun(emqx_listeners:listener_id(), map()) -> map().
|
||||||
|
inject_sni_fun(ListenerID, Conf0) ->
|
||||||
|
SNIFun = emqx_const_v1:make_sni_fun(ListenerID),
|
||||||
|
Conf = emqx_map_lib:deep_merge(Conf0, #{ssl_options => #{sni_fun => SNIFun}}),
|
||||||
|
ok = ?MODULE:register_listener(ListenerID, Conf),
|
||||||
|
Conf.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% 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) ->
|
||||||
|
case do_lookup(ListenerID) of
|
||||||
|
{ok, DERResponse} ->
|
||||||
|
{reply, {ok, DERResponse}, State};
|
||||||
|
{error, invalid_listener_id} ->
|
||||||
|
{reply, error, State};
|
||||||
|
{error, not_cached} ->
|
||||||
|
Conf = undefined,
|
||||||
|
with_refresh_params(ListenerID, Conf, {reply, error, State}, fun(Params) ->
|
||||||
|
case do_http_fetch_and_cache(ListenerID, Params) of
|
||||||
|
error -> {reply, error, ensure_timer(ListenerID, State, ?RETRY_TIMEOUT)};
|
||||||
|
{ok, Response} -> {reply, {ok, Response}, ensure_timer(ListenerID, State)}
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end;
|
||||||
|
handle_call({register_listener, ListenerID, Conf}, _From, State0) ->
|
||||||
|
?SLOG(debug, #{
|
||||||
|
msg => "registering_ocsp_cache",
|
||||||
|
listener_id => ListenerID
|
||||||
|
}),
|
||||||
|
RefreshInterval0 = emqx_map_lib:deep_get([ssl_options, ocsp, refresh_interval], Conf),
|
||||||
|
RefreshInterval = max(RefreshInterval0, ?MIN_REFRESH_INTERVAL),
|
||||||
|
State = State0#{{refresh_interval, ListenerID} => RefreshInterval},
|
||||||
|
%% we need to pass the config along because this might be called
|
||||||
|
%% during the listener's `post_config_update', hence the config is
|
||||||
|
%% not yet "commited" and accessible when we need it.
|
||||||
|
Message = {refresh, ListenerID, Conf},
|
||||||
|
{reply, ok, ensure_timer(ListenerID, Message, 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}),
|
||||||
|
?SLOG(debug, #{
|
||||||
|
msg => "refreshing_ocsp_response",
|
||||||
|
listener_id => ListenerID
|
||||||
|
}),
|
||||||
|
Conf = undefined,
|
||||||
|
handle_refresh(ListenerID, Conf, State0);
|
||||||
|
_ ->
|
||||||
|
{noreply, State0}
|
||||||
|
end;
|
||||||
|
handle_info({timeout, TRef, {refresh, ListenerID, Conf}}, State0) ->
|
||||||
|
case maps:get(?REFRESH_TIMER(ListenerID), State0, undefined) of
|
||||||
|
TRef ->
|
||||||
|
?tp(ocsp_refresh_timer, #{listener_id => ListenerID}),
|
||||||
|
?SLOG(debug, #{
|
||||||
|
msg => "refreshing_ocsp_response",
|
||||||
|
listener_id => ListenerID
|
||||||
|
}),
|
||||||
|
handle_refresh(ListenerID, Conf, State0);
|
||||||
|
_ ->
|
||||||
|
{noreply, State0}
|
||||||
|
end;
|
||||||
|
handle_info(_Info, State) ->
|
||||||
|
{noreply, State}.
|
||||||
|
|
||||||
|
code_change(_Vsn, State, _Extra) ->
|
||||||
|
{ok, State}.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% internal functions
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
http_fetch(ListenerID) ->
|
||||||
|
%% TODO: configurable call timeout?
|
||||||
|
gen_server:call(?MODULE, {http_fetch, ListenerID}, ?CALL_TIMEOUT).
|
||||||
|
|
||||||
|
cache_key(ListenerID) ->
|
||||||
|
?WITH_LISTENER_CONFIG(
|
||||||
|
ListenerID,
|
||||||
|
[ssl_options],
|
||||||
|
#{certfile := ServerCertPemPath},
|
||||||
|
error,
|
||||||
|
begin
|
||||||
|
#'Certificate'{
|
||||||
|
tbsCertificate =
|
||||||
|
#'TBSCertificate'{
|
||||||
|
signature = Signature
|
||||||
|
}
|
||||||
|
} = read_server_cert(ServerCertPemPath),
|
||||||
|
{ok, {ocsp_response, Signature}}
|
||||||
|
end
|
||||||
|
).
|
||||||
|
|
||||||
|
do_lookup(ListenerID) ->
|
||||||
|
CacheKey = cache_key(ListenerID),
|
||||||
|
case CacheKey of
|
||||||
|
error ->
|
||||||
|
{error, invalid_listener_id};
|
||||||
|
{ok, Key} ->
|
||||||
|
%% Respond immediately if a concurrent call already fetched it.
|
||||||
|
case ets:lookup(?CACHE_TAB, Key) of
|
||||||
|
[{_, DERResponse}] ->
|
||||||
|
?tp(ocsp_cache_hit, #{listener_id => ListenerID}),
|
||||||
|
{ok, DERResponse};
|
||||||
|
[] ->
|
||||||
|
{error, not_cached}
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
handle_refresh(ListenerID, Conf, State0) ->
|
||||||
|
%% no point in retrying if the config is inconsistent or non
|
||||||
|
%% existent.
|
||||||
|
State1 = maps:without([{refresh_interval, ListenerID}, ?REFRESH_TIMER(ListenerID)], State0),
|
||||||
|
with_refresh_params(ListenerID, Conf, {noreply, State1}, fun(Params) ->
|
||||||
|
case do_http_fetch_and_cache(ListenerID, Params) of
|
||||||
|
error ->
|
||||||
|
?SLOG(debug, #{
|
||||||
|
msg => "failed_to_fetch_ocsp_response",
|
||||||
|
listener_id => ListenerID
|
||||||
|
}),
|
||||||
|
{noreply, ensure_timer(ListenerID, State0, ?RETRY_TIMEOUT)};
|
||||||
|
{ok, _Response} ->
|
||||||
|
?SLOG(debug, #{
|
||||||
|
msg => "fetched_ocsp_response",
|
||||||
|
listener_id => ListenerID
|
||||||
|
}),
|
||||||
|
{noreply, ensure_timer(ListenerID, State0)}
|
||||||
|
end
|
||||||
|
end).
|
||||||
|
|
||||||
|
with_refresh_params(ListenerID, Conf, ErrorRet, Fn) ->
|
||||||
|
case get_refresh_params(ListenerID, Conf) of
|
||||||
|
error ->
|
||||||
|
ErrorRet;
|
||||||
|
{ok, Params} ->
|
||||||
|
Fn(Params)
|
||||||
|
end.
|
||||||
|
|
||||||
|
get_refresh_params(ListenerID, undefined = _Conf) ->
|
||||||
|
%% during normal periodic refreshes, we read from the emqx config.
|
||||||
|
?WITH_LISTENER_CONFIG(
|
||||||
|
ListenerID,
|
||||||
|
[ssl_options],
|
||||||
|
#{
|
||||||
|
ocsp := #{
|
||||||
|
issuer_pem := IssuerPemPath,
|
||||||
|
responder_url := ResponderURL,
|
||||||
|
refresh_http_timeout := HTTPTimeout
|
||||||
|
},
|
||||||
|
certfile := ServerCertPemPath
|
||||||
|
},
|
||||||
|
error,
|
||||||
|
{ok, #{
|
||||||
|
issuer_pem => IssuerPemPath,
|
||||||
|
responder_url => ResponderURL,
|
||||||
|
refresh_http_timeout => HTTPTimeout,
|
||||||
|
server_certfile => ServerCertPemPath
|
||||||
|
}}
|
||||||
|
);
|
||||||
|
get_refresh_params(_ListenerID, #{
|
||||||
|
ssl_options := #{
|
||||||
|
ocsp := #{
|
||||||
|
issuer_pem := IssuerPemPath,
|
||||||
|
responder_url := ResponderURL,
|
||||||
|
refresh_http_timeout := HTTPTimeout
|
||||||
|
},
|
||||||
|
certfile := ServerCertPemPath
|
||||||
|
}
|
||||||
|
}) ->
|
||||||
|
{ok, #{
|
||||||
|
issuer_pem => IssuerPemPath,
|
||||||
|
responder_url => ResponderURL,
|
||||||
|
refresh_http_timeout => HTTPTimeout,
|
||||||
|
server_certfile => ServerCertPemPath
|
||||||
|
}};
|
||||||
|
get_refresh_params(_ListenerID, _Conf) ->
|
||||||
|
error.
|
||||||
|
|
||||||
|
do_http_fetch_and_cache(ListenerID, Params) ->
|
||||||
|
#{
|
||||||
|
issuer_pem := IssuerPemPath,
|
||||||
|
responder_url := ResponderURL,
|
||||||
|
refresh_http_timeout := HTTPTimeout,
|
||||||
|
server_certfile := ServerCertPemPath
|
||||||
|
} = Params,
|
||||||
|
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),
|
||||||
|
?tp(ocsp_http_fetch, #{
|
||||||
|
listener_id => ListenerID,
|
||||||
|
responder_url => ResponderURL,
|
||||||
|
timeout => HTTPTimeout
|
||||||
|
}),
|
||||||
|
RequestURI = iolist_to_binary([ResponderURL, Request]),
|
||||||
|
Resp = ?MODULE:http_get(RequestURI, HTTPTimeout),
|
||||||
|
case Resp of
|
||||||
|
{ok, {{_, 200, _}, _, Body}} ->
|
||||||
|
?SLOG(debug, #{
|
||||||
|
msg => "caching_ocsp_response",
|
||||||
|
listener_id => ListenerID
|
||||||
|
}),
|
||||||
|
%% if we got this far, the certfile is correct.
|
||||||
|
{ok, CacheKey} = cache_key(ListenerID),
|
||||||
|
true = ets:insert(?CACHE_TAB, {CacheKey, Body}),
|
||||||
|
?tp(ocsp_http_fetch_and_cache, #{
|
||||||
|
listener_id => ListenerID,
|
||||||
|
headers => true
|
||||||
|
}),
|
||||||
|
{ok, Body};
|
||||||
|
{ok, {200, Body}} ->
|
||||||
|
?SLOG(debug, #{
|
||||||
|
msg => "caching_ocsp_response",
|
||||||
|
listener_id => ListenerID
|
||||||
|
}),
|
||||||
|
%% if we got this far, the certfile is correct.
|
||||||
|
{ok, CacheKey} = cache_key(ListenerID),
|
||||||
|
true = ets:insert(?CACHE_TAB, {CacheKey, Body}),
|
||||||
|
?tp(ocsp_http_fetch_and_cache, #{
|
||||||
|
listener_id => ListenerID,
|
||||||
|
headers => false
|
||||||
|
}),
|
||||||
|
{ok, Body};
|
||||||
|
{ok, {{_, Code, _}, _, Body}} ->
|
||||||
|
?tp(
|
||||||
|
error,
|
||||||
|
ocsp_http_fetch_bad_code,
|
||||||
|
#{
|
||||||
|
listener_id => ListenerID,
|
||||||
|
body => Body,
|
||||||
|
code => Code,
|
||||||
|
headers => true
|
||||||
|
}
|
||||||
|
),
|
||||||
|
?SLOG(error, #{
|
||||||
|
msg => "error_fetching_ocsp_response",
|
||||||
|
listener_id => ListenerID,
|
||||||
|
code => Code,
|
||||||
|
body => Body
|
||||||
|
}),
|
||||||
|
error;
|
||||||
|
{ok, {Code, Body}} ->
|
||||||
|
?tp(
|
||||||
|
error,
|
||||||
|
ocsp_http_fetch_bad_code,
|
||||||
|
#{
|
||||||
|
listener_id => ListenerID,
|
||||||
|
body => Body,
|
||||||
|
code => Code,
|
||||||
|
headers => false
|
||||||
|
}
|
||||||
|
),
|
||||||
|
?SLOG(error, #{
|
||||||
|
msg => "error_fetching_ocsp_response",
|
||||||
|
listener_id => ListenerID,
|
||||||
|
code => Code,
|
||||||
|
body => Body
|
||||||
|
}),
|
||||||
|
error;
|
||||||
|
{error, Error} ->
|
||||||
|
?tp(
|
||||||
|
error,
|
||||||
|
ocsp_http_fetch_error,
|
||||||
|
#{
|
||||||
|
listener_id => ListenerID,
|
||||||
|
error => Error
|
||||||
|
}
|
||||||
|
),
|
||||||
|
?SLOG(error, #{
|
||||||
|
msg => "error_fetching_ocsp_response",
|
||||||
|
listener_id => ListenerID,
|
||||||
|
error => 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) ->
|
||||||
|
ensure_timer(ListenerID, {refresh, ListenerID}, State, Timeout).
|
||||||
|
|
||||||
|
ensure_timer(ListenerID, Message, State, Timeout) ->
|
||||||
|
emqx_misc:cancel_timer(maps:get(?REFRESH_TIMER(ListenerID), State, undefined)),
|
||||||
|
State#{
|
||||||
|
?REFRESH_TIMER(ListenerID) => emqx_misc:start_timer(
|
||||||
|
Timeout,
|
||||||
|
Message
|
||||||
|
)
|
||||||
|
}.
|
||||||
|
|
||||||
|
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.
|
|
@ -810,7 +810,7 @@ fields("mqtt_ssl_listener") ->
|
||||||
{"ssl_options",
|
{"ssl_options",
|
||||||
sc(
|
sc(
|
||||||
ref("listener_ssl_opts"),
|
ref("listener_ssl_opts"),
|
||||||
#{}
|
#{validator => fun mqtt_ssl_listener_ssl_options_validator/1}
|
||||||
)}
|
)}
|
||||||
];
|
];
|
||||||
fields("mqtt_ws_listener") ->
|
fields("mqtt_ws_listener") ->
|
||||||
|
@ -1294,6 +1294,56 @@ fields("listener_quic_ssl_opts") ->
|
||||||
);
|
);
|
||||||
fields("ssl_client_opts") ->
|
fields("ssl_client_opts") ->
|
||||||
client_ssl_opts_schema(#{});
|
client_ssl_opts_schema(#{});
|
||||||
|
fields("ocsp") ->
|
||||||
|
[
|
||||||
|
{"enable_ocsp_stapling",
|
||||||
|
sc(
|
||||||
|
boolean(),
|
||||||
|
#{
|
||||||
|
default => false,
|
||||||
|
desc => ?DESC("server_ssl_opts_schema_enable_ocsp_stapling")
|
||||||
|
}
|
||||||
|
)},
|
||||||
|
{"responder_url",
|
||||||
|
sc(
|
||||||
|
binary(),
|
||||||
|
#{
|
||||||
|
required => false,
|
||||||
|
validator => fun ocsp_responder_url_validator/1,
|
||||||
|
converter => fun
|
||||||
|
(undefined, _Opts) ->
|
||||||
|
undefined;
|
||||||
|
(URL, _Opts) ->
|
||||||
|
uri_string:normalize(URL)
|
||||||
|
end,
|
||||||
|
desc => ?DESC("server_ssl_opts_schema_ocsp_responder_url")
|
||||||
|
}
|
||||||
|
)},
|
||||||
|
{"issuer_pem",
|
||||||
|
sc(
|
||||||
|
binary(),
|
||||||
|
#{
|
||||||
|
required => false,
|
||||||
|
desc => ?DESC("server_ssl_opts_schema_ocsp_issuer_pem")
|
||||||
|
}
|
||||||
|
)},
|
||||||
|
{"refresh_interval",
|
||||||
|
sc(
|
||||||
|
duration(),
|
||||||
|
#{
|
||||||
|
default => <<"5m">>,
|
||||||
|
desc => ?DESC("server_ssl_opts_schema_ocsp_refresh_interval")
|
||||||
|
}
|
||||||
|
)},
|
||||||
|
{"refresh_http_timeout",
|
||||||
|
sc(
|
||||||
|
duration(),
|
||||||
|
#{
|
||||||
|
default => <<"15s">>,
|
||||||
|
desc => ?DESC("server_ssl_opts_schema_ocsp_refresh_http_timeout")
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
];
|
||||||
fields("deflate_opts") ->
|
fields("deflate_opts") ->
|
||||||
[
|
[
|
||||||
{"level",
|
{"level",
|
||||||
|
@ -2017,6 +2067,8 @@ desc("trace") ->
|
||||||
"Real-time filtering logs for the ClientID or Topic or IP for debugging.";
|
"Real-time filtering logs for the ClientID or Topic or IP for debugging.";
|
||||||
desc("shared_subscription_group") ->
|
desc("shared_subscription_group") ->
|
||||||
"Per group dispatch strategy for shared subscription";
|
"Per group dispatch strategy for shared subscription";
|
||||||
|
desc("ocsp") ->
|
||||||
|
"Per listener OCSP Stapling configuration.";
|
||||||
desc(_) ->
|
desc(_) ->
|
||||||
undefined.
|
undefined.
|
||||||
|
|
||||||
|
@ -2199,14 +2251,62 @@ server_ssl_opts_schema(Defaults, IsRanchListener) ->
|
||||||
)}
|
)}
|
||||||
] ++
|
] ++
|
||||||
[
|
[
|
||||||
{"gc_after_handshake",
|
Field
|
||||||
sc(boolean(), #{
|
|| not IsRanchListener,
|
||||||
default => false,
|
Field <- [
|
||||||
desc => ?DESC(server_ssl_opts_schema_gc_after_handshake)
|
{"gc_after_handshake",
|
||||||
})}
|
sc(boolean(), #{
|
||||||
|| not IsRanchListener
|
default => false,
|
||||||
|
desc => ?DESC(server_ssl_opts_schema_gc_after_handshake)
|
||||||
|
})},
|
||||||
|
{"ocsp",
|
||||||
|
sc(
|
||||||
|
ref("ocsp"),
|
||||||
|
#{
|
||||||
|
required => false,
|
||||||
|
validator => fun ocsp_inner_validator/1
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
]
|
||||||
].
|
].
|
||||||
|
|
||||||
|
mqtt_ssl_listener_ssl_options_validator(Conf) ->
|
||||||
|
Checks = [
|
||||||
|
fun ocsp_outer_validator/1
|
||||||
|
],
|
||||||
|
case emqx_misc:pipeline(Checks, Conf, not_used) of
|
||||||
|
{ok, _, _} ->
|
||||||
|
ok;
|
||||||
|
{error, Reason, _NotUsed} ->
|
||||||
|
{error, Reason}
|
||||||
|
end.
|
||||||
|
|
||||||
|
ocsp_outer_validator(#{<<"ocsp">> := #{<<"enable_ocsp_stapling">> := true}} = Conf) ->
|
||||||
|
%% outer mqtt listener ssl server config
|
||||||
|
ServerCertPemPath = maps:get(<<"certfile">>, Conf, undefined),
|
||||||
|
case ServerCertPemPath of
|
||||||
|
undefined ->
|
||||||
|
{error, "Server certificate must be defined when using OCSP stapling"};
|
||||||
|
_ ->
|
||||||
|
%% check if issuer pem is readable and/or valid?
|
||||||
|
ok
|
||||||
|
end;
|
||||||
|
ocsp_outer_validator(_Conf) ->
|
||||||
|
ok.
|
||||||
|
|
||||||
|
ocsp_inner_validator(#{enable_ocsp_stapling := _} = Conf) ->
|
||||||
|
ocsp_inner_validator(emqx_map_lib:binary_key_map(Conf));
|
||||||
|
ocsp_inner_validator(#{<<"enable_ocsp_stapling">> := false} = _Conf) ->
|
||||||
|
ok;
|
||||||
|
ocsp_inner_validator(#{<<"enable_ocsp_stapling">> := true} = Conf) ->
|
||||||
|
assert_required_field(
|
||||||
|
Conf, <<"responder_url">>, "The responder URL is required for OCSP stapling"
|
||||||
|
),
|
||||||
|
assert_required_field(
|
||||||
|
Conf, <<"issuer_pem">>, "The issuer PEM path is required for OCSP stapling"
|
||||||
|
),
|
||||||
|
ok.
|
||||||
|
|
||||||
%% @doc Make schema for SSL client.
|
%% @doc Make schema for SSL client.
|
||||||
-spec client_ssl_opts_schema(map()) -> hocon_schema:field_schema().
|
-spec client_ssl_opts_schema(map()) -> hocon_schema:field_schema().
|
||||||
client_ssl_opts_schema(Defaults) ->
|
client_ssl_opts_schema(Defaults) ->
|
||||||
|
@ -2865,3 +2965,11 @@ is_quic_ssl_opts(Name) ->
|
||||||
%% , "handshake_timeout"
|
%% , "handshake_timeout"
|
||||||
%% , "gc_after_handshake"
|
%% , "gc_after_handshake"
|
||||||
]).
|
]).
|
||||||
|
|
||||||
|
assert_required_field(Conf, Key, ErrorMessage) ->
|
||||||
|
case maps:get(Key, Conf, undefined) of
|
||||||
|
undefined ->
|
||||||
|
throw(ErrorMessage);
|
||||||
|
_ ->
|
||||||
|
ok
|
||||||
|
end.
|
||||||
|
|
|
@ -29,6 +29,7 @@
|
||||||
boot_modules/1,
|
boot_modules/1,
|
||||||
start_apps/1,
|
start_apps/1,
|
||||||
start_apps/2,
|
start_apps/2,
|
||||||
|
start_apps/3,
|
||||||
stop_apps/1,
|
stop_apps/1,
|
||||||
reload/2,
|
reload/2,
|
||||||
app_path/2,
|
app_path/2,
|
||||||
|
@ -36,7 +37,8 @@
|
||||||
deps_path/2,
|
deps_path/2,
|
||||||
flush/0,
|
flush/0,
|
||||||
flush/1,
|
flush/1,
|
||||||
render_and_load_app_config/1
|
render_and_load_app_config/1,
|
||||||
|
render_and_load_app_config/2
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-export([
|
-export([
|
||||||
|
@ -185,17 +187,21 @@ start_apps(Apps) ->
|
||||||
application:set_env(system_monitor, db_hostname, ""),
|
application:set_env(system_monitor, db_hostname, ""),
|
||||||
ok
|
ok
|
||||||
end,
|
end,
|
||||||
start_apps(Apps, DefaultHandler).
|
start_apps(Apps, DefaultHandler, #{}).
|
||||||
|
|
||||||
-spec start_apps(Apps :: apps(), Handler :: special_config_handler()) -> ok.
|
-spec start_apps(Apps :: apps(), Handler :: special_config_handler()) -> ok.
|
||||||
start_apps(Apps, SpecAppConfig) when is_function(SpecAppConfig) ->
|
start_apps(Apps, SpecAppConfig) when is_function(SpecAppConfig) ->
|
||||||
|
start_apps(Apps, SpecAppConfig, #{}).
|
||||||
|
|
||||||
|
-spec start_apps(Apps :: apps(), Handler :: special_config_handler(), map()) -> ok.
|
||||||
|
start_apps(Apps, SpecAppConfig, Opts) when is_function(SpecAppConfig) ->
|
||||||
%% Load all application code to beam vm first
|
%% Load all application code to beam vm first
|
||||||
%% Because, minirest, ekka etc.. application will scan these modules
|
%% Because, minirest, ekka etc.. application will scan these modules
|
||||||
lists:foreach(fun load/1, [emqx | Apps]),
|
lists:foreach(fun load/1, [emqx | Apps]),
|
||||||
ok = start_ekka(),
|
ok = start_ekka(),
|
||||||
mnesia:clear_table(emqx_admin),
|
mnesia:clear_table(emqx_admin),
|
||||||
ok = emqx_ratelimiter_SUITE:load_conf(),
|
ok = emqx_ratelimiter_SUITE:load_conf(),
|
||||||
lists:foreach(fun(App) -> start_app(App, SpecAppConfig) end, [emqx | Apps]).
|
lists:foreach(fun(App) -> start_app(App, SpecAppConfig, Opts) end, [emqx | Apps]).
|
||||||
|
|
||||||
load(App) ->
|
load(App) ->
|
||||||
case application:load(App) of
|
case application:load(App) of
|
||||||
|
@ -205,27 +211,31 @@ load(App) ->
|
||||||
end.
|
end.
|
||||||
|
|
||||||
render_and_load_app_config(App) ->
|
render_and_load_app_config(App) ->
|
||||||
|
render_and_load_app_config(App, #{}).
|
||||||
|
|
||||||
|
render_and_load_app_config(App, Opts) ->
|
||||||
load(App),
|
load(App),
|
||||||
Schema = app_schema(App),
|
Schema = app_schema(App),
|
||||||
Conf = app_path(App, filename:join(["etc", app_conf_file(App)])),
|
ConfFilePath = maps:get(conf_file_path, Opts, filename:join(["etc", app_conf_file(App)])),
|
||||||
|
Conf = app_path(App, ConfFilePath),
|
||||||
try
|
try
|
||||||
do_render_app_config(App, Schema, Conf)
|
do_render_app_config(App, Schema, Conf, Opts)
|
||||||
catch
|
catch
|
||||||
throw:E:St ->
|
throw:E:St ->
|
||||||
%% turn throw into error
|
%% turn throw into error
|
||||||
error({Conf, E, St})
|
error({Conf, E, St})
|
||||||
end.
|
end.
|
||||||
|
|
||||||
do_render_app_config(App, Schema, ConfigFile) ->
|
do_render_app_config(App, Schema, ConfigFile, Opts) ->
|
||||||
Vars = mustache_vars(App),
|
Vars = mustache_vars(App, Opts),
|
||||||
RenderedConfigFile = render_config_file(ConfigFile, Vars),
|
RenderedConfigFile = render_config_file(ConfigFile, Vars),
|
||||||
read_schema_configs(Schema, RenderedConfigFile),
|
read_schema_configs(Schema, RenderedConfigFile),
|
||||||
force_set_config_file_paths(App, [RenderedConfigFile]),
|
force_set_config_file_paths(App, [RenderedConfigFile]),
|
||||||
copy_certs(App, RenderedConfigFile),
|
copy_certs(App, RenderedConfigFile),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
start_app(App, SpecAppConfig) ->
|
start_app(App, SpecAppConfig, Opts) ->
|
||||||
render_and_load_app_config(App),
|
render_and_load_app_config(App, Opts),
|
||||||
SpecAppConfig(App),
|
SpecAppConfig(App),
|
||||||
case application:ensure_all_started(App) of
|
case application:ensure_all_started(App) of
|
||||||
{ok, _} ->
|
{ok, _} ->
|
||||||
|
@ -248,12 +258,13 @@ app_schema(App) ->
|
||||||
no_schema
|
no_schema
|
||||||
end.
|
end.
|
||||||
|
|
||||||
mustache_vars(App) ->
|
mustache_vars(App, Opts) ->
|
||||||
|
ExtraMustacheVars = maps:get(extra_mustache_vars, Opts, []),
|
||||||
[
|
[
|
||||||
{platform_data_dir, app_path(App, "data")},
|
{platform_data_dir, app_path(App, "data")},
|
||||||
{platform_etc_dir, app_path(App, "etc")},
|
{platform_etc_dir, app_path(App, "etc")},
|
||||||
{platform_log_dir, app_path(App, "log")}
|
{platform_log_dir, app_path(App, "log")}
|
||||||
].
|
] ++ ExtraMustacheVars.
|
||||||
|
|
||||||
render_config_file(ConfigFile, Vars0) ->
|
render_config_file(ConfigFile, Vars0) ->
|
||||||
Temp =
|
Temp =
|
||||||
|
@ -337,7 +348,7 @@ safe_relative_path_2(Path) ->
|
||||||
-spec reload(App :: atom(), SpecAppConfig :: special_config_handler()) -> ok.
|
-spec reload(App :: atom(), SpecAppConfig :: special_config_handler()) -> ok.
|
||||||
reload(App, SpecAppConfigHandler) ->
|
reload(App, SpecAppConfigHandler) ->
|
||||||
application:stop(App),
|
application:stop(App),
|
||||||
start_app(App, SpecAppConfigHandler),
|
start_app(App, SpecAppConfigHandler, #{}),
|
||||||
application:start(App).
|
application:start(App).
|
||||||
|
|
||||||
ensure_mnesia_stopped() ->
|
ensure_mnesia_stopped() ->
|
||||||
|
@ -479,7 +490,7 @@ is_all_tcp_servers_available(Servers) ->
|
||||||
{_, []} ->
|
{_, []} ->
|
||||||
true;
|
true;
|
||||||
{_, Unavail} ->
|
{_, Unavail} ->
|
||||||
ct:print("Unavailable servers: ~p", [Unavail]),
|
ct:pal("Unavailable servers: ~p", [Unavail]),
|
||||||
false
|
false
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,908 @@
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Copyright (c) 2022-2023 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_common_test_helpers: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),
|
||||||
|
emqx_config:save_schema_mod_and_names(emqx_schema),
|
||||||
|
emqx_common_test_helpers:boot_modules(all),
|
||||||
|
Config.
|
||||||
|
|
||||||
|
end_per_suite(_Config) ->
|
||||||
|
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({seconds, 30}),
|
||||||
|
DataDir = ?config(data_dir, Config),
|
||||||
|
Handler = fun(_) -> ok end,
|
||||||
|
{OCSPResponderPort, OCSPOSPid} = setup_openssl_ocsp(Config),
|
||||||
|
ConfFilePath = filename:join([DataDir, "openssl_listeners.conf"]),
|
||||||
|
emqx_common_test_helpers:start_apps(
|
||||||
|
[],
|
||||||
|
Handler,
|
||||||
|
#{
|
||||||
|
extra_mustache_vars => [{test_data_dir, DataDir}],
|
||||||
|
conf_file_path => ConfFilePath
|
||||||
|
}
|
||||||
|
),
|
||||||
|
ct:sleep(1_000),
|
||||||
|
[
|
||||||
|
{ocsp_responder_port, OCSPResponderPort},
|
||||||
|
{ocsp_responder_os_pid, OCSPOSPid}
|
||||||
|
| Config
|
||||||
|
];
|
||||||
|
init_per_testcase(TestCase, Config) when
|
||||||
|
TestCase =:= t_update_listener;
|
||||||
|
TestCase =:= t_validations
|
||||||
|
->
|
||||||
|
%% when running emqx standalone tests, we can't use those
|
||||||
|
%% features.
|
||||||
|
case does_module_exist(emqx_mgmt_api_test_util) of
|
||||||
|
true ->
|
||||||
|
ct:timetrap({seconds, 30}),
|
||||||
|
%% start the listener with the default (non-ocsp) config
|
||||||
|
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) ->
|
||||||
|
ct:pal("ocsp http request ~p", [URL]),
|
||||||
|
TestPid ! {http_get, URL},
|
||||||
|
{ok, {{"HTTP/1.0", 200, 'OK'}, [], <<"ocsp response">>}}
|
||||||
|
end
|
||||||
|
),
|
||||||
|
emqx_mgmt_api_test_util:init_suite([emqx_conf]),
|
||||||
|
snabbkaffe:start_trace(),
|
||||||
|
Config;
|
||||||
|
false ->
|
||||||
|
[{skip_does_not_apply, true} | Config]
|
||||||
|
end;
|
||||||
|
init_per_testcase(t_ocsp_responder_error_responses, Config) ->
|
||||||
|
ct:timetrap({seconds, 30}),
|
||||||
|
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) ->
|
||||||
|
ct:pal("ocsp http request ~p", [URL]),
|
||||||
|
TestPid ! {http_get, URL},
|
||||||
|
persistent_term:get({?MODULE, http_response})
|
||||||
|
end
|
||||||
|
),
|
||||||
|
DataDir = ?config(data_dir, Config),
|
||||||
|
Type = ssl,
|
||||||
|
Name = test_ocsp,
|
||||||
|
ListenerOpts = #{
|
||||||
|
ssl_options =>
|
||||||
|
#{
|
||||||
|
certfile => filename:join(DataDir, "server.pem"),
|
||||||
|
ocsp => #{
|
||||||
|
enable_ocsp_stapling => true,
|
||||||
|
responder_url => <<"http://localhost:9877/">>,
|
||||||
|
issuer_pem => filename:join(DataDir, "ocsp-issuer.pem"),
|
||||||
|
refresh_http_timeout => 15_000,
|
||||||
|
refresh_interval => 1_000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Conf = #{listeners => #{Type => #{Name => ListenerOpts}}},
|
||||||
|
ConfBin = emqx_map_lib:binary_key_map(Conf),
|
||||||
|
hocon_tconf:check_plain(emqx_schema, ConfBin, #{required => false, atom_keys => false}),
|
||||||
|
emqx_config:put_listener_conf(Type, Name, [], ListenerOpts),
|
||||||
|
snabbkaffe:start_trace(),
|
||||||
|
{ok, CachePid} = emqx_ocsp_cache:start_link(),
|
||||||
|
[
|
||||||
|
{cache_pid, CachePid}
|
||||||
|
| Config
|
||||||
|
];
|
||||||
|
init_per_testcase(_TestCase, Config) ->
|
||||||
|
ct:timetrap({seconds, 10}),
|
||||||
|
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),
|
||||||
|
Type = ssl,
|
||||||
|
Name = test_ocsp,
|
||||||
|
ListenerOpts = #{
|
||||||
|
ssl_options =>
|
||||||
|
#{
|
||||||
|
certfile => filename:join(DataDir, "server.pem"),
|
||||||
|
ocsp => #{
|
||||||
|
enable_ocsp_stapling => true,
|
||||||
|
responder_url => <<"http://localhost:9877/">>,
|
||||||
|
issuer_pem => filename:join(DataDir, "ocsp-issuer.pem"),
|
||||||
|
refresh_http_timeout => 15_000,
|
||||||
|
refresh_interval => 1_000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Conf = #{listeners => #{Type => #{Name => ListenerOpts}}},
|
||||||
|
ConfBin = emqx_map_lib:binary_key_map(Conf),
|
||||||
|
hocon_tconf:check_plain(emqx_schema, ConfBin, #{required => false, atom_keys => false}),
|
||||||
|
emqx_config:put_listener_conf(Type, Name, [], ListenerOpts),
|
||||||
|
snabbkaffe:start_trace(),
|
||||||
|
[
|
||||||
|
{cache_pid, CachePid}
|
||||||
|
| Config
|
||||||
|
].
|
||||||
|
|
||||||
|
end_per_testcase(t_openssl_client, Config) ->
|
||||||
|
OCSPResponderOSPid = ?config(ocsp_responder_os_pid, Config),
|
||||||
|
catch kill_pid(OCSPResponderOSPid),
|
||||||
|
emqx_common_test_helpers:stop_apps([]),
|
||||||
|
ok;
|
||||||
|
end_per_testcase(TestCase, Config) when
|
||||||
|
TestCase =:= t_update_listener;
|
||||||
|
TestCase =:= t_validations
|
||||||
|
->
|
||||||
|
Skip = proplists:get_bool(skip_does_not_apply, Config),
|
||||||
|
case Skip of
|
||||||
|
true ->
|
||||||
|
ok;
|
||||||
|
false ->
|
||||||
|
emqx_mgmt_api_test_util:end_suite([emqx_conf]),
|
||||||
|
meck:unload([emqx_ocsp_cache]),
|
||||||
|
ok
|
||||||
|
end;
|
||||||
|
end_per_testcase(t_ocsp_responder_error_responses, Config) ->
|
||||||
|
CachePid = ?config(cache_pid, Config),
|
||||||
|
catch gen_server:stop(CachePid),
|
||||||
|
meck:unload([emqx_ocsp_cache]),
|
||||||
|
persistent_term:erase({?MODULE, http_response}),
|
||||||
|
ok;
|
||||||
|
end_per_testcase(_TestCase, Config) ->
|
||||||
|
CachePid = ?config(cache_pid, Config),
|
||||||
|
catch gen_server:stop(CachePid),
|
||||||
|
meck:unload([emqx_ocsp_cache]),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Helper functions
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
does_module_exist(Mod) ->
|
||||||
|
case erlang:module_loaded(Mod) of
|
||||||
|
true ->
|
||||||
|
true;
|
||||||
|
false ->
|
||||||
|
case code:ensure_loaded(Mod) of
|
||||||
|
ok ->
|
||||||
|
true;
|
||||||
|
{module, Mod} ->
|
||||||
|
true;
|
||||||
|
_ ->
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
|
assert_no_http_get() ->
|
||||||
|
receive
|
||||||
|
{http_get, _URL} ->
|
||||||
|
error(should_be_cached)
|
||||||
|
after 0 ->
|
||||||
|
ok
|
||||||
|
end.
|
||||||
|
|
||||||
|
assert_http_get(N) ->
|
||||||
|
assert_http_get(N, 0).
|
||||||
|
|
||||||
|
assert_http_get(0, _Timeout) ->
|
||||||
|
ok;
|
||||||
|
assert_http_get(N, Timeout) when N > 0 ->
|
||||||
|
receive
|
||||||
|
{http_get, URL} ->
|
||||||
|
?assertMatch(<<"http://localhost:9877/", _Request64/binary>>, URL),
|
||||||
|
ok
|
||||||
|
after Timeout ->
|
||||||
|
error({no_http_get, #{mailbox => process_info(self(), messages)}})
|
||||||
|
end,
|
||||||
|
assert_http_get(N - 1, Timeout).
|
||||||
|
|
||||||
|
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).
|
||||||
|
|
||||||
|
openssl_version() ->
|
||||||
|
Res0 = string:trim(os:cmd("openssl version"), trailing),
|
||||||
|
[_, Res] = string:split(Res0, " "),
|
||||||
|
{match, [Version]} = re:run(Res, "^([^ ]+)", [{capture, first, list}]),
|
||||||
|
Version.
|
||||||
|
|
||||||
|
setup_openssl_ocsp(Config) ->
|
||||||
|
OCSPResponderPort = spawn_openssl_ocsp_responder(Config),
|
||||||
|
{os_pid, OCSPOSPid} = erlang:port_info(OCSPResponderPort, os_pid),
|
||||||
|
%%%%%%%% Warning!!!
|
||||||
|
%% Apparently, openssl 3.0.7 introduced a bug in the responder
|
||||||
|
%% that makes it hang forever if one probes the port with
|
||||||
|
%% `gen_tcp:open' / `gen_tcp:close'... Comment this out if
|
||||||
|
%% openssl gets updated in CI or in your local machine.
|
||||||
|
OpenSSLVersion = openssl_version(),
|
||||||
|
ct:pal("openssl version: ~p", [OpenSSLVersion]),
|
||||||
|
case OpenSSLVersion of
|
||||||
|
"3." ++ _ ->
|
||||||
|
%% hope that the responder has started...
|
||||||
|
ok;
|
||||||
|
_ ->
|
||||||
|
ensure_port_open(9877)
|
||||||
|
end,
|
||||||
|
ct:sleep(1_000),
|
||||||
|
{OCSPResponderPort, OCSPOSPid}.
|
||||||
|
|
||||||
|
request(Method, Url, QueryParams, Body) ->
|
||||||
|
AuthHeader = emqx_mgmt_api_test_util:auth_header_(),
|
||||||
|
Opts = #{return_all => true},
|
||||||
|
case emqx_mgmt_api_test_util:request_api(Method, Url, QueryParams, AuthHeader, Body, Opts) of
|
||||||
|
{ok, {Reason, Headers, BodyR}} ->
|
||||||
|
{ok, {Reason, Headers, emqx_json:decode(BodyR, [return_maps])}};
|
||||||
|
Error ->
|
||||||
|
Error
|
||||||
|
end.
|
||||||
|
|
||||||
|
get_listener_via_api(ListenerId) ->
|
||||||
|
Path = emqx_mgmt_api_test_util:api_path(["listeners", ListenerId]),
|
||||||
|
request(get, Path, [], []).
|
||||||
|
|
||||||
|
update_listener_via_api(ListenerId, NewConfig) ->
|
||||||
|
Path = emqx_mgmt_api_test_util:api_path(["listeners", ListenerId]),
|
||||||
|
request(put, Path, [], NewConfig).
|
||||||
|
|
||||||
|
put_http_response(Response) ->
|
||||||
|
persistent_term:put({?MODULE, http_response}, Response).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Test cases
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
t_request_ocsp_response(_Config) ->
|
||||||
|
?check_trace(
|
||||||
|
begin
|
||||||
|
ListenerID = <<"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 = <<"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 = <<"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 = <<"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 = <<"ssl:test_ocsp">>,
|
||||||
|
Conf = emqx_config:get_listener_conf(ssl, test_ocsp, []),
|
||||||
|
%% should fetch and cache immediately
|
||||||
|
{ok, {ok, _}} =
|
||||||
|
?wait_async_action(
|
||||||
|
emqx_ocsp_cache:register_listener(ListenerID, Conf),
|
||||||
|
#{?snk_kind := ocsp_http_fetch_and_cache, listener_id := ListenerID}
|
||||||
|
),
|
||||||
|
assert_http_get(1),
|
||||||
|
?assertMatch([{_, <<"ocsp response">>}], ets:tab2list(?CACHE_TAB)),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
t_register_twice(_Config) ->
|
||||||
|
ListenerID = <<"ssl:test_ocsp">>,
|
||||||
|
Conf = emqx_config:get_listener_conf(ssl, test_ocsp, []),
|
||||||
|
{ok, {ok, _}} =
|
||||||
|
?wait_async_action(
|
||||||
|
emqx_ocsp_cache:register_listener(ListenerID, Conf),
|
||||||
|
#{?snk_kind := ocsp_http_fetch_and_cache, listener_id := ListenerID}
|
||||||
|
),
|
||||||
|
assert_http_get(1),
|
||||||
|
?assertMatch([{_, <<"ocsp response">>}], ets:tab2list(?CACHE_TAB)),
|
||||||
|
%% should have no problem in registering the same listener again.
|
||||||
|
%% this prompts an immediate refresh.
|
||||||
|
{ok, {ok, _}} =
|
||||||
|
?wait_async_action(
|
||||||
|
emqx_ocsp_cache:register_listener(ListenerID, Conf),
|
||||||
|
#{?snk_kind := ocsp_http_fetch_and_cache, listener_id := ListenerID}
|
||||||
|
),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
t_refresh_periodically(_Config) ->
|
||||||
|
ListenerID = <<"ssl:test_ocsp">>,
|
||||||
|
Conf = emqx_config:get_listener_conf(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 = 10_000
|
||||||
|
),
|
||||||
|
ok = emqx_ocsp_cache:register_listener(ListenerID, Conf),
|
||||||
|
?assertMatch({ok, [_, _]}, snabbkaffe:receive_events(SubRef)),
|
||||||
|
assert_http_get(2),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
t_sni_fun_success(_Config) ->
|
||||||
|
ListenerID = <<"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 = <<"ssl:test_ocsp">>,
|
||||||
|
ServerName = "localhost",
|
||||||
|
?assertEqual(
|
||||||
|
[],
|
||||||
|
emqx_ocsp_cache:sni_fun(ServerName, ListenerID)
|
||||||
|
),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
%% check that we can start with a non-ocsp stapling listener and
|
||||||
|
%% restart it with the new ocsp config.
|
||||||
|
t_update_listener(Config) ->
|
||||||
|
case proplists:get_bool(skip_does_not_apply, Config) of
|
||||||
|
true ->
|
||||||
|
ok;
|
||||||
|
false ->
|
||||||
|
do_t_update_listener(Config)
|
||||||
|
end.
|
||||||
|
|
||||||
|
do_t_update_listener(Config) ->
|
||||||
|
DataDir = ?config(data_dir, Config),
|
||||||
|
Keyfile = filename:join([DataDir, "server.key"]),
|
||||||
|
Certfile = filename:join([DataDir, "server.pem"]),
|
||||||
|
Cacertfile = filename:join([DataDir, "ca.pem"]),
|
||||||
|
IssuerPem = filename:join([DataDir, "ocsp-issuer.pem"]),
|
||||||
|
|
||||||
|
%% no ocsp at first
|
||||||
|
ListenerId = "ssl:default",
|
||||||
|
{ok, {{_, 200, _}, _, ListenerData0}} = get_listener_via_api(ListenerId),
|
||||||
|
?assertMatch(
|
||||||
|
#{
|
||||||
|
<<"ssl_options">> :=
|
||||||
|
#{
|
||||||
|
<<"ocsp">> :=
|
||||||
|
#{<<"enable_ocsp_stapling">> := false}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ListenerData0
|
||||||
|
),
|
||||||
|
assert_no_http_get(),
|
||||||
|
|
||||||
|
%% configure ocsp
|
||||||
|
OCSPConfig =
|
||||||
|
#{
|
||||||
|
<<"ssl_options">> =>
|
||||||
|
#{
|
||||||
|
<<"keyfile">> => Keyfile,
|
||||||
|
<<"certfile">> => Certfile,
|
||||||
|
<<"cacertfile">> => Cacertfile,
|
||||||
|
<<"ocsp">> =>
|
||||||
|
#{
|
||||||
|
<<"enable_ocsp_stapling">> => true,
|
||||||
|
<<"issuer_pem">> => IssuerPem,
|
||||||
|
<<"responder_url">> => <<"http://localhost:9877">>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ListenerData1 = emqx_map_lib:deep_merge(ListenerData0, OCSPConfig),
|
||||||
|
{ok, {_, _, ListenerData2}} = update_listener_via_api(ListenerId, ListenerData1),
|
||||||
|
?assertMatch(
|
||||||
|
#{
|
||||||
|
<<"ssl_options">> :=
|
||||||
|
#{
|
||||||
|
<<"ocsp">> :=
|
||||||
|
#{
|
||||||
|
<<"enable_ocsp_stapling">> := true,
|
||||||
|
<<"issuer_pem">> := _,
|
||||||
|
<<"responder_url">> := _
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ListenerData2
|
||||||
|
),
|
||||||
|
assert_http_get(1, 5_000),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
t_ocsp_responder_error_responses(_Config) ->
|
||||||
|
ListenerId = <<"ssl:test_ocsp">>,
|
||||||
|
Conf = emqx_config:get_listener_conf(ssl, test_ocsp, []),
|
||||||
|
?check_trace(
|
||||||
|
begin
|
||||||
|
%% successful response without headers
|
||||||
|
put_http_response({ok, {200, <<"ocsp_response">>}}),
|
||||||
|
{ok, {ok, _}} =
|
||||||
|
?wait_async_action(
|
||||||
|
emqx_ocsp_cache:register_listener(ListenerId, Conf),
|
||||||
|
#{?snk_kind := ocsp_http_fetch_and_cache, headers := false},
|
||||||
|
1_000
|
||||||
|
),
|
||||||
|
|
||||||
|
%% error response with headers
|
||||||
|
put_http_response({ok, {{"HTTP/1.0", 500, "Internal Server Error"}, [], <<"error">>}}),
|
||||||
|
{ok, {ok, _}} =
|
||||||
|
?wait_async_action(
|
||||||
|
emqx_ocsp_cache:register_listener(ListenerId, Conf),
|
||||||
|
#{?snk_kind := ocsp_http_fetch_bad_code, code := 500, headers := true},
|
||||||
|
1_000
|
||||||
|
),
|
||||||
|
|
||||||
|
%% error response without headers
|
||||||
|
put_http_response({ok, {500, <<"error">>}}),
|
||||||
|
{ok, {ok, _}} =
|
||||||
|
?wait_async_action(
|
||||||
|
emqx_ocsp_cache:register_listener(ListenerId, Conf),
|
||||||
|
#{?snk_kind := ocsp_http_fetch_bad_code, code := 500, headers := false},
|
||||||
|
1_000
|
||||||
|
),
|
||||||
|
|
||||||
|
%% econnrefused
|
||||||
|
put_http_response(
|
||||||
|
{error,
|
||||||
|
{failed_connect, [
|
||||||
|
{to_address, {"localhost", 9877}},
|
||||||
|
{inet, [inet], econnrefused}
|
||||||
|
]}}
|
||||||
|
),
|
||||||
|
{ok, {ok, _}} =
|
||||||
|
?wait_async_action(
|
||||||
|
emqx_ocsp_cache:register_listener(ListenerId, Conf),
|
||||||
|
#{?snk_kind := ocsp_http_fetch_error, error := {failed_connect, _}},
|
||||||
|
1_000
|
||||||
|
),
|
||||||
|
|
||||||
|
%% timeout
|
||||||
|
put_http_response({error, timeout}),
|
||||||
|
{ok, {ok, _}} =
|
||||||
|
?wait_async_action(
|
||||||
|
emqx_ocsp_cache:register_listener(ListenerId, Conf),
|
||||||
|
#{?snk_kind := ocsp_http_fetch_error, error := timeout},
|
||||||
|
1_000
|
||||||
|
),
|
||||||
|
|
||||||
|
ok
|
||||||
|
end,
|
||||||
|
[]
|
||||||
|
),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
t_unknown_requests(_Config) ->
|
||||||
|
emqx_ocsp_cache ! unknown,
|
||||||
|
?assertEqual(ok, gen_server:cast(emqx_ocsp_cache, unknown)),
|
||||||
|
?assertEqual({error, {unknown_call, unknown}}, gen_server:call(emqx_ocsp_cache, unknown)),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
t_validations(Config) ->
|
||||||
|
case proplists:get_bool(skip_does_not_apply, Config) of
|
||||||
|
true ->
|
||||||
|
ok;
|
||||||
|
false ->
|
||||||
|
do_t_validations(Config)
|
||||||
|
end.
|
||||||
|
|
||||||
|
do_t_validations(_Config) ->
|
||||||
|
ListenerId = <<"ssl:default">>,
|
||||||
|
{ok, {{_, 200, _}, _, ListenerData0}} = get_listener_via_api(ListenerId),
|
||||||
|
|
||||||
|
ListenerData1 =
|
||||||
|
emqx_map_lib:deep_merge(
|
||||||
|
ListenerData0,
|
||||||
|
#{
|
||||||
|
<<"ssl_options">> =>
|
||||||
|
#{<<"ocsp">> => #{<<"enable_ocsp_stapling">> => true}}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
{error, {_, _, ResRaw1}} = update_listener_via_api(ListenerId, ListenerData1),
|
||||||
|
#{<<"code">> := <<"BAD_REQUEST">>, <<"message">> := MsgRaw1} =
|
||||||
|
emqx_json:decode(ResRaw1, [return_maps]),
|
||||||
|
?assertMatch(
|
||||||
|
#{
|
||||||
|
<<"mismatches">> :=
|
||||||
|
#{
|
||||||
|
<<"listeners:ssl_not_required_bind">> :=
|
||||||
|
#{
|
||||||
|
<<"reason">> :=
|
||||||
|
<<"The responder URL is required for OCSP stapling">>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emqx_json:decode(MsgRaw1, [return_maps])
|
||||||
|
),
|
||||||
|
|
||||||
|
ListenerData2 =
|
||||||
|
emqx_map_lib:deep_merge(
|
||||||
|
ListenerData0,
|
||||||
|
#{
|
||||||
|
<<"ssl_options">> =>
|
||||||
|
#{
|
||||||
|
<<"ocsp">> => #{
|
||||||
|
<<"enable_ocsp_stapling">> => true,
|
||||||
|
<<"responder_url">> => <<"http://localhost:9877">>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
{error, {_, _, ResRaw2}} = update_listener_via_api(ListenerId, ListenerData2),
|
||||||
|
#{<<"code">> := <<"BAD_REQUEST">>, <<"message">> := MsgRaw2} =
|
||||||
|
emqx_json:decode(ResRaw2, [return_maps]),
|
||||||
|
?assertMatch(
|
||||||
|
#{
|
||||||
|
<<"mismatches">> :=
|
||||||
|
#{
|
||||||
|
<<"listeners:ssl_not_required_bind">> :=
|
||||||
|
#{
|
||||||
|
<<"reason">> :=
|
||||||
|
<<"The issuer PEM path is required for OCSP stapling">>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emqx_json:decode(MsgRaw2, [return_maps])
|
||||||
|
),
|
||||||
|
|
||||||
|
ListenerData3a =
|
||||||
|
emqx_map_lib:deep_merge(
|
||||||
|
ListenerData0,
|
||||||
|
#{
|
||||||
|
<<"ssl_options">> =>
|
||||||
|
#{
|
||||||
|
<<"ocsp">> => #{
|
||||||
|
<<"enable_ocsp_stapling">> => true,
|
||||||
|
<<"responder_url">> => <<"http://localhost:9877">>,
|
||||||
|
<<"issuer_pem">> => <<"some_file">>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
ListenerData3 = emqx_map_lib:deep_remove([<<"ssl_options">>, <<"certfile">>], ListenerData3a),
|
||||||
|
{error, {_, _, ResRaw3}} = update_listener_via_api(ListenerId, ListenerData3),
|
||||||
|
#{<<"code">> := <<"BAD_REQUEST">>, <<"message">> := MsgRaw3} =
|
||||||
|
emqx_json:decode(ResRaw3, [return_maps]),
|
||||||
|
?assertMatch(
|
||||||
|
#{
|
||||||
|
<<"mismatches">> :=
|
||||||
|
#{
|
||||||
|
<<"listeners:ssl_not_required_bind">> :=
|
||||||
|
#{
|
||||||
|
<<"reason">> :=
|
||||||
|
<<"Server certificate must be defined when using OCSP stapling">>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emqx_json:decode(MsgRaw3, [return_maps])
|
||||||
|
),
|
||||||
|
|
||||||
|
ok.
|
||||||
|
|
||||||
|
t_openssl_client(Config) ->
|
||||||
|
TLSVsn = ?config(tls_vsn, Config),
|
||||||
|
WithStatusRequest = ?config(status_request, Config),
|
||||||
|
%% ensure ocsp response is already cached.
|
||||||
|
ListenerID = <<"ssl:default">>,
|
||||||
|
?assertMatch(
|
||||||
|
{ok, _},
|
||||||
|
emqx_ocsp_cache:fetch_response(ListenerID),
|
||||||
|
#{msgs => process_info(self(), messages)}
|
||||||
|
),
|
||||||
|
timer:sleep(500),
|
||||||
|
test_ocsp_connection(TLSVsn, WithStatusRequest, Config).
|
|
@ -0,0 +1,68 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIF+zCCA+OgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwbzELMAkGA1UEBhMCU0Ux
|
||||||
|
EjAQBgNVBAgMCVN0b2NraG9sbTESMBAGA1UEBwwJU3RvY2tob2xtMRIwEAYDVQQK
|
||||||
|
DAlNeU9yZ05hbWUxETAPBgNVBAsMCE15Um9vdENBMREwDwYDVQQDDAhNeVJvb3RD
|
||||||
|
QTAeFw0yMzAxMTIxMzA4MTZaFw0zMzAxMDkxMzA4MTZaMGsxCzAJBgNVBAYTAlNF
|
||||||
|
MRIwEAYDVQQIDAlTdG9ja2hvbG0xEjAQBgNVBAoMCU15T3JnTmFtZTEZMBcGA1UE
|
||||||
|
CwwQTXlJbnRlcm1lZGlhdGVDQTEZMBcGA1UEAwwQTXlJbnRlcm1lZGlhdGVDQTCC
|
||||||
|
AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALQG7dMeU/y9HDNHzhydR0bm
|
||||||
|
wN9UGplqJOJPwqJRaZZcrn9umgJ9SU2il2ceEVxMDwzBWCRKJO5/H9A9k13SqsXM
|
||||||
|
2c2c9xXfIF1kb820lCm1Uow5hZ/auDjxliNk9kNJDigCRi3QoIs/dVeWzFsgEC2l
|
||||||
|
gxRqauN2eNFb6/yXY788YALHBsCRV2NFOFXxtPsvLXpD9Q/8EqYsSMuLARRdHVNU
|
||||||
|
ryaEF5lhShpcuz0TlIuTy2TiuXJUtJ+p7a4Z7friZ6JsrmQWsVQBj44F8TJRHWzW
|
||||||
|
C7vm9c+dzEX9eqbr5iPL+L4ctMW9Lz6ePcYfIXne6CElusRUf8G+xM1uwovF9bpV
|
||||||
|
+9IqY7tAu9G1iY9iNtJgNNDKOCcOGKcZCx6Cg1XYOEKReNnUMazvYeqRrrjV5WQ0
|
||||||
|
vOcD5zcBRNTXCddCLa7U0guXP9mQrfuk4NTH1Bt77JieTJ8cfDXHwtaKf6aGbmZP
|
||||||
|
wl1Xi/GuXNUP/xeog78RKyFwBmjt2JKwvWzMpfmH4mEkG9moh2alva+aEz6LIJuP
|
||||||
|
16g6s0Q6c793/OvUtpNcewHw4Vjn39LD9o6VLp854G4n8dVpUWSbWS+sXD1ZE69H
|
||||||
|
g/sMNMyq+09ufkbewY8xoCm/rQ1pqDZAVMWsstJEaYu7b/eb7R+RGOj1YECCV/Yp
|
||||||
|
EZPdDotbSNRkIi2d/a1NAgMBAAGjgaQwgaEwHQYDVR0OBBYEFExwhjsVUom6tQ+S
|
||||||
|
qq6xMUETvnPzMB8GA1UdIwQYMBaAFD90kfU5pc5l48THu0Ayj9SNpHuhMBIGA1Ud
|
||||||
|
EwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgGGMDsGA1UdHwQ0MDIwMKAuoCyG
|
||||||
|
Kmh0dHA6Ly9sb2NhbGhvc3Q6OTg3OC9pbnRlcm1lZGlhdGUuY3JsLnBlbTANBgkq
|
||||||
|
hkiG9w0BAQsFAAOCAgEAK6NgdWQYtPNKQNBGjsgtgqTRh+k30iqSO6Y3yE1KGABO
|
||||||
|
EuQdVqkC2qUIbCB0M0qoV0ab50KNLfU6cbshggW4LDpcMpoQpI05fukNh1jm3ZuZ
|
||||||
|
0xsB7vlmlsv00tpqmfIl/zykPDynHKOmFh/hJP/KetMy4+wDv4/+xP31UdEj5XvG
|
||||||
|
HvMtuqOS23A+H6WPU7ol7KzKBnU2zz/xekvPbUD3JqV+ynP5bgbIZHAndd0o9T8e
|
||||||
|
NFX23Us4cTenU2/ZlOq694bRzGaK+n3Ksz995Nbtzv5fbUgqmf7Mcq4iHGRVtV11
|
||||||
|
MRyBrsXZp2vbF63c4hrf2Zd6SWRoaDKRhP2DMhajpH9zZASSTlfejg/ZRO2s+Clh
|
||||||
|
YrSTkeMAdnRt6i/q4QRcOTCfsX75RFM5v67njvTXsSaSTnAwaPi78tRtf+WSh0EP
|
||||||
|
VVPzy++BszBVlJ1VAf7soWZHCjZxZ8ZPqVTy5okoHwWQ09WmYe8GfulDh1oj0wbK
|
||||||
|
3FjN7bODWHJN+bFf5aQfK+tumYKoPG8RXL6QxpEzjFWjxhIMJHHMKfDWnAV1o1+7
|
||||||
|
/1/aDzq7MzEYBbrgQR7oE5ZHtyqhCf9LUgw0Kr7/8QWuNAdeDCJzjXRROU0hJczp
|
||||||
|
dOyfRlLbHmLLmGOnROlx6LsGNQ17zuz6SPi7ei8/ylhykawDOAGkM1+xFakmQhM=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIFzzCCA7egAwIBAgIUYjc7hD7/UJ0/VPADfNfp/WpOwRowDQYJKoZIhvcNAQEL
|
||||||
|
BQAwbzELMAkGA1UEBhMCU0UxEjAQBgNVBAgMCVN0b2NraG9sbTESMBAGA1UEBwwJ
|
||||||
|
U3RvY2tob2xtMRIwEAYDVQQKDAlNeU9yZ05hbWUxETAPBgNVBAsMCE15Um9vdENB
|
||||||
|
MREwDwYDVQQDDAhNeVJvb3RDQTAeFw0yMzAxMTIxMzA4MTRaFw00MzAxMDcxMzA4
|
||||||
|
MTRaMG8xCzAJBgNVBAYTAlNFMRIwEAYDVQQIDAlTdG9ja2hvbG0xEjAQBgNVBAcM
|
||||||
|
CVN0b2NraG9sbTESMBAGA1UECgwJTXlPcmdOYW1lMREwDwYDVQQLDAhNeVJvb3RD
|
||||||
|
QTERMA8GA1UEAwwITXlSb290Q0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK
|
||||||
|
AoICAQCnBwSOYVJw47IoMHMXTVDtOYvUt3rqsurEhFcB4O8xmf2mmwr6m7s8A5Ft
|
||||||
|
AvAehg1GvnXT3t/KiyU7BK+acTwcErGyZwS2wvdB0lpHWSpOn/u5y+4ZETvQefcj
|
||||||
|
ZTdDOM9VN5nutpitgNb+1yL8sqSexfVbY7DnYYvFjOVBYoP/SGvM9jVjCad+0WL3
|
||||||
|
FhuD+L8QAxzCieX3n9UMymlFwINQuEc+TDjuNcEqt+0J5EgS1fwzxb2RCVL0TNv4
|
||||||
|
9a71hFGCNRj20AeZm99hbdufm7+0AFO7ocV5q43rLrWFUoBzqKPYIjga/cv/UdWZ
|
||||||
|
c5RLRXw3JDSrCqkf/mOlaEhNPlmWRF9MSus5Da3wuwgGCaVzmrf30rWR5aHHcscG
|
||||||
|
e+AOgJ4HayvBUQeb6ZlRXc0YlACiLToMKxuyxDyUcDfVEXpUIsDILF8dkiVQxEU3
|
||||||
|
j9g6qjXiqPVdNiwpqXfBKObj8vNCzORnoHYs8cCgib3RgDVWeqkDmlSwlZE7CvQh
|
||||||
|
U4Loj4l7813xxzYEKkVaT1JdXPWu42CG/b4Y/+f4V+3rkJkYzUwndX6kZNksIBai
|
||||||
|
phmtvKt+CTdP1eAbT+C9AWWF3PT31+BIhuT0u9tR8BVSkXdQB8dG4M/AAJcTo640
|
||||||
|
0mdYYOXT153gEKHJuUBm750ZTy+r6NjNvpw8VrMAakJwHqnIdQIDAQABo2MwYTAd
|
||||||
|
BgNVHQ4EFgQUP3SR9TmlzmXjxMe7QDKP1I2ke6EwHwYDVR0jBBgwFoAUP3SR9Tml
|
||||||
|
zmXjxMe7QDKP1I2ke6EwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYw
|
||||||
|
DQYJKoZIhvcNAQELBQADggIBAFMFv4C+I0+xOAb9v6G/IOpfPBZ1ez31EXKJJBra
|
||||||
|
lulP4nRHQMeb310JS8BIeQ3dl+7+PkSxPABZSwc3jkxdSMvhc+Z4MQtTgos+Qsjs
|
||||||
|
gH7sTqwWeeQ0lHYxWmkXijrh5OPRZwTKzYQlkcn85BCUXl2KDuNEdiqPbDTao+lc
|
||||||
|
lA0/UAvC6NCyFKq/jqf4CmW5Kx6yG1v1LaE+IXn7cbIXj+DaehocVXi0wsXqj03Q
|
||||||
|
DDUHuLHZP+LBsg4e91/0Jy2ekNRTYJifSqr+9ufHl0ZX1pFDZyf396IgZ5CQZ0PJ
|
||||||
|
nRxZHlCfsxWxmxxdy3FQSE6YwXhdTjjoAa1ApZcKkkt1beJa6/oRLze/ux5x+5q+
|
||||||
|
4QczufHd6rjoKBi6BM3FgFQ8As5iNohHXlMHd/xITo1Go3CWw2j9TGH5vzksOElK
|
||||||
|
B0mcwwt2zwNEjvfytc+tI5jcfGN3tiT5fVHS8hw9dWKevypLL+55Ua9G8ZgDHasT
|
||||||
|
XFRJHgmnbyFcaAe26D2dSKmhC9u2mHBH+MaI8dj3e7wNBfpxNgp41aFIk+QTmiFW
|
||||||
|
VXFED6DHQ/Mxq93ACalHdYg18PlIYClbT6Pf2xXBnn33YPhn5xzoTZ+cDH/RpaQp
|
||||||
|
s0UUTSJT1UTXgtXPnZWQfvKlMjJEIiVFiLEC0sgZRlWuZDRAY0CdZJJxvQp59lqu
|
||||||
|
cbTm
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -0,0 +1,52 @@
|
||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
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
|
||||||
|
rkE8Bz5a/iwO+QwdO5Q2cp5+0nm41A==
|
||||||
|
-----END PRIVATE KEY-----
|
|
@ -0,0 +1,38 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIGmjCCBIKgAwIBAgICEAYwDQYJKoZIhvcNAQELBQAwazELMAkGA1UEBhMCU0Ux
|
||||||
|
EjAQBgNVBAgMCVN0b2NraG9sbTESMBAGA1UECgwJTXlPcmdOYW1lMRkwFwYDVQQL
|
||||||
|
DBBNeUludGVybWVkaWF0ZUNBMRkwFwYDVQQDDBBNeUludGVybWVkaWF0ZUNBMB4X
|
||||||
|
DTIzMDMwNjE5NTA0N1oXDTMzMDYxMTE5NTA0N1owezELMAkGA1UEBhMCU0UxEjAQ
|
||||||
|
BgNVBAgMCVN0b2NraG9sbTESMBAGA1UEBwwJU3RvY2tob2xtMRIwEAYDVQQKDAlN
|
||||||
|
eU9yZ05hbWUxGTAXBgNVBAsMEE15SW50ZXJtZWRpYXRlQ0ExFTATBgNVBAMMDG9j
|
||||||
|
c3AuY2xpZW50MjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKZ9mYEA
|
||||||
|
5knzGM9iSnJDFMZnjS8jA67KXBKjDXaJ1Y3qNxb2DloiKn4Hagqt/NHs9izFsOh1
|
||||||
|
yP8o6lAbAIUC6NJiBi+KI6yYBNDWZ42hyedaMCzJvD51Jcr9mN+tWAhokXtEdouI
|
||||||
|
O2rlMjhvgqNQXKHk0gshz7N9mqg1TqSFQXRtw4EAbYEVoHNF/5r+GBlcQ/ISeioU
|
||||||
|
rknmT4MNKUpcNs5MQeFcbhAVPWFlq1rGm0PWdMQVFi7N+aH+DhEO9a1rCb1hTeP9
|
||||||
|
XRSfu39NqPR8Bc2LfRPMo5VpVwOuRW0MUd7aFfeU4l3ncjk2lLtqxqr5pM3K3P4j
|
||||||
|
cF/9ri+SESCsqmMFJdeb/5XDk3Oxc3Ap11JQbLCYhtXZgFicr7QPCE5cuxM+Hem/
|
||||||
|
r3fU1XGmJ9bSczhSvpcIMc49Vf+MhlnzX1vMCVlncGAxPPumpP33TqWY1FAGBKCv
|
||||||
|
5lrD2CNWEuDVUN/AXzl5qeFvOTkhOd99H6zZyidmvaD4sQOyrZ0m/+wRti4cE0GQ
|
||||||
|
O2Gfjf0yA2rWevc9XSGfyk1yBLlkVTD583zjTdFj1NguywH8UVhEQMJrHSPvmovc
|
||||||
|
Tsttkrwl+w/y6HV7c91HrhOC2hihMq7C0ip6WaH6vIBYWNpcs+g/8CpiTVVxEJ68
|
||||||
|
5dQrDouKyR26SmWT1zLZw8tyb4Q/7YmzcHbxAgMBAAGjggE2MIIBMjAJBgNVHRME
|
||||||
|
AjAAMBEGCWCGSAGG+EIBAQQEAwIFoDAzBglghkgBhvhCAQ0EJhYkT3BlblNTTCBH
|
||||||
|
ZW5lcmF0ZWQgQ2xpZW50IENlcnRpZmljYXRlMB0GA1UdDgQWBBSJ/yia067wCafe
|
||||||
|
kDCgk+e8PJTCUDAfBgNVHSMEGDAWgBRMcIY7FVKJurUPkqqusTFBE75z8zAOBgNV
|
||||||
|
HQ8BAf8EBAMCBeAwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMEMDsGA1Ud
|
||||||
|
HwQ0MDIwMKAuoCyGKmh0dHA6Ly9sb2NhbGhvc3Q6OTg3OC9pbnRlcm1lZGlhdGUu
|
||||||
|
Y3JsLnBlbTAxBggrBgEFBQcBAQQlMCMwIQYIKwYBBQUHMAGGFWh0dHA6Ly9sb2Nh
|
||||||
|
bGhvc3Q6OTg3NzANBgkqhkiG9w0BAQsFAAOCAgEAN2XfYgbrjxC6OWh9UoMLQaDD
|
||||||
|
59JPxAUBxlRtWzTWqxY2jfT+OwJfDP4e+ef2G1YEG+qyt57ddlm/EwX9IvAvG0D4
|
||||||
|
wd4tfItG88IJWKDM3wpT5KYrUsu+PlQTFmGmaWlORK/mRKlmfjbP5CIAcUedvCS9
|
||||||
|
j9PkCrbbkklAmp0ULLSLUkYajmfFOkQ+VdGhQ6nAamTeyh2Z2S4dVjsKc8yBViMo
|
||||||
|
/V6HP56rOvUqiVTcvhZtH7QDptMSTzuJ+AsmreYjwIiTGzYS/i8QVAFuPfXJKEOB
|
||||||
|
jD5WhUaP/8Snbuft4MxssPAph8okcmxLfb55nw+soNc2oS1wWwKMe7igRelq8vtg
|
||||||
|
bu00QSEGiY1eq/vFgZh0+Wohy/YeYzhO4Jq40FFpKiVbkLzexpNH/Afj2QrHuZ7y
|
||||||
|
259uGGfv5tGA+TW6PsckCQknEb5V4V35ZZlbWVRKpuADeNPoDuoYPtc5eOomIkmw
|
||||||
|
rFz/gPZWSA+4pYEgXgqcaM8+KP0i53eTbWqwy5DVgXiuaTYWU4m1FTsIZ+/nGIqW
|
||||||
|
Dsgqd/D6jivf9Yvm+VFYTZsxIfq5sMdjxSuMBo0nZrzFDpqc6m6fVVoHv5R9Yliw
|
||||||
|
MbxgmFQ84CKLy7iNKGSGVN2SIr1obMQ0e/t3NiCHib3WKzmZFoNoFCtVzAgsxGmF
|
||||||
|
Q6rY83JdIPPW4LqZNcE=
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -0,0 +1,6 @@
|
||||||
|
V 330419130816Z 1000 unknown /C=SE/ST=Stockholm/L=Stockholm/O=MyOrgName/OU=MyIntermediateCA/CN=localhost
|
||||||
|
V 330419130816Z 1001 unknown /C=SE/ST=Stockholm/L=Stockholm/O=MyOrgName/OU=MyIntermediateCA/CN=MyClient
|
||||||
|
R 330419130816Z 230112130816Z 1002 unknown /C=SE/ST=Stockholm/L=Stockholm/O=MyOrgName/OU=MyIntermediateCA/CN=client-revoked
|
||||||
|
V 330419130816Z 1003 unknown /C=SE/ST=Stockholm/L=Stockholm/O=MyOrgName/OU=MyIntermediateCA/CN=ocsp.server
|
||||||
|
V 330419130816Z 1004 unknown /C=SE/ST=Stockholm/L=Stockholm/O=MyOrgName/OU=MyIntermediateCA/CN=ocsp.client
|
||||||
|
V 330425123656Z 1005 unknown /C=SE/ST=Stockholm/L=Stockholm/O=MyOrgName/OU=MyIntermediateCA/CN=client-no-dist-points
|
|
@ -0,0 +1,52 @@
|
||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
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
|
||||||
|
xBO4CmF79pAcAXvDoRB619Flx5/G
|
||||||
|
-----END PRIVATE KEY-----
|
|
@ -0,0 +1,34 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIF+zCCA+OgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwbzELMAkGA1UEBhMCU0Ux
|
||||||
|
EjAQBgNVBAgMCVN0b2NraG9sbTESMBAGA1UEBwwJU3RvY2tob2xtMRIwEAYDVQQK
|
||||||
|
DAlNeU9yZ05hbWUxETAPBgNVBAsMCE15Um9vdENBMREwDwYDVQQDDAhNeVJvb3RD
|
||||||
|
QTAeFw0yMzAxMTIxMzA4MTZaFw0zMzAxMDkxMzA4MTZaMGsxCzAJBgNVBAYTAlNF
|
||||||
|
MRIwEAYDVQQIDAlTdG9ja2hvbG0xEjAQBgNVBAoMCU15T3JnTmFtZTEZMBcGA1UE
|
||||||
|
CwwQTXlJbnRlcm1lZGlhdGVDQTEZMBcGA1UEAwwQTXlJbnRlcm1lZGlhdGVDQTCC
|
||||||
|
AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALQG7dMeU/y9HDNHzhydR0bm
|
||||||
|
wN9UGplqJOJPwqJRaZZcrn9umgJ9SU2il2ceEVxMDwzBWCRKJO5/H9A9k13SqsXM
|
||||||
|
2c2c9xXfIF1kb820lCm1Uow5hZ/auDjxliNk9kNJDigCRi3QoIs/dVeWzFsgEC2l
|
||||||
|
gxRqauN2eNFb6/yXY788YALHBsCRV2NFOFXxtPsvLXpD9Q/8EqYsSMuLARRdHVNU
|
||||||
|
ryaEF5lhShpcuz0TlIuTy2TiuXJUtJ+p7a4Z7friZ6JsrmQWsVQBj44F8TJRHWzW
|
||||||
|
C7vm9c+dzEX9eqbr5iPL+L4ctMW9Lz6ePcYfIXne6CElusRUf8G+xM1uwovF9bpV
|
||||||
|
+9IqY7tAu9G1iY9iNtJgNNDKOCcOGKcZCx6Cg1XYOEKReNnUMazvYeqRrrjV5WQ0
|
||||||
|
vOcD5zcBRNTXCddCLa7U0guXP9mQrfuk4NTH1Bt77JieTJ8cfDXHwtaKf6aGbmZP
|
||||||
|
wl1Xi/GuXNUP/xeog78RKyFwBmjt2JKwvWzMpfmH4mEkG9moh2alva+aEz6LIJuP
|
||||||
|
16g6s0Q6c793/OvUtpNcewHw4Vjn39LD9o6VLp854G4n8dVpUWSbWS+sXD1ZE69H
|
||||||
|
g/sMNMyq+09ufkbewY8xoCm/rQ1pqDZAVMWsstJEaYu7b/eb7R+RGOj1YECCV/Yp
|
||||||
|
EZPdDotbSNRkIi2d/a1NAgMBAAGjgaQwgaEwHQYDVR0OBBYEFExwhjsVUom6tQ+S
|
||||||
|
qq6xMUETvnPzMB8GA1UdIwQYMBaAFD90kfU5pc5l48THu0Ayj9SNpHuhMBIGA1Ud
|
||||||
|
EwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgGGMDsGA1UdHwQ0MDIwMKAuoCyG
|
||||||
|
Kmh0dHA6Ly9sb2NhbGhvc3Q6OTg3OC9pbnRlcm1lZGlhdGUuY3JsLnBlbTANBgkq
|
||||||
|
hkiG9w0BAQsFAAOCAgEAK6NgdWQYtPNKQNBGjsgtgqTRh+k30iqSO6Y3yE1KGABO
|
||||||
|
EuQdVqkC2qUIbCB0M0qoV0ab50KNLfU6cbshggW4LDpcMpoQpI05fukNh1jm3ZuZ
|
||||||
|
0xsB7vlmlsv00tpqmfIl/zykPDynHKOmFh/hJP/KetMy4+wDv4/+xP31UdEj5XvG
|
||||||
|
HvMtuqOS23A+H6WPU7ol7KzKBnU2zz/xekvPbUD3JqV+ynP5bgbIZHAndd0o9T8e
|
||||||
|
NFX23Us4cTenU2/ZlOq694bRzGaK+n3Ksz995Nbtzv5fbUgqmf7Mcq4iHGRVtV11
|
||||||
|
MRyBrsXZp2vbF63c4hrf2Zd6SWRoaDKRhP2DMhajpH9zZASSTlfejg/ZRO2s+Clh
|
||||||
|
YrSTkeMAdnRt6i/q4QRcOTCfsX75RFM5v67njvTXsSaSTnAwaPi78tRtf+WSh0EP
|
||||||
|
VVPzy++BszBVlJ1VAf7soWZHCjZxZ8ZPqVTy5okoHwWQ09WmYe8GfulDh1oj0wbK
|
||||||
|
3FjN7bODWHJN+bFf5aQfK+tumYKoPG8RXL6QxpEzjFWjxhIMJHHMKfDWnAV1o1+7
|
||||||
|
/1/aDzq7MzEYBbrgQR7oE5ZHtyqhCf9LUgw0Kr7/8QWuNAdeDCJzjXRROU0hJczp
|
||||||
|
dOyfRlLbHmLLmGOnROlx6LsGNQ17zuz6SPi7ei8/ylhykawDOAGkM1+xFakmQhM=
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -0,0 +1,14 @@
|
||||||
|
listeners.ssl.default {
|
||||||
|
bind = "0.0.0.0:8883"
|
||||||
|
max_connections = 512000
|
||||||
|
ssl_options {
|
||||||
|
keyfile = "{{ test_data_dir }}/server.key"
|
||||||
|
certfile = "{{ test_data_dir }}/server.pem"
|
||||||
|
cacertfile = "{{ test_data_dir }}/ca.pem"
|
||||||
|
ocsp {
|
||||||
|
enable_ocsp_stapling = true
|
||||||
|
issuer_pem = "{{ test_data_dir }}/ocsp-issuer.pem"
|
||||||
|
responder_url = "http://127.0.0.1:9877"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCnVPRWgP59GU15
|
||||||
|
HddFwPZflFfcSkeuWU8tgKQhZcNoBli4lIfemuoV/hkGRVFexAiAw3/u5wvOaMaN
|
||||||
|
V8n9KxxgAUNLh5YaknpnNdhfQDyM0S5UJIbVeLzAQWxkBXpI3uBfW4WPSULRnVyR
|
||||||
|
psLEfl1qOklGOyuZfRbkkkkVwtJEmGEH0kz0fy6xenn3R3/mTeIbj+5TNqiBXWn1
|
||||||
|
/qgTiNf2Ni7SE6Nk2lP4V8iofcBIrsp6KtEWdipGEJZeXCg/X0g/qVt15tF1l00M
|
||||||
|
uEWRHt1qGBELJJTcNzQvdqHAPz0AfQRjTtXyocw5+pFth8Q8a7gyjrjv5nhnpAKQ
|
||||||
|
msrt3vyNAgMBAAECggEABnWvIQ/Fw0qQxRYz00uJt1LguW5cqgxklBsdOvTUwFVO
|
||||||
|
Y4HIZP2R/9tZV/ahF4l10pK5g52DxSoiUB6Ne6qIY+RolqfbUZdKBmX7vmGadM02
|
||||||
|
fqUSV3dbwghEiO/1Mo74FnZQB6IKZFEw26aWakN+k7VAUufB3SEJGzXSgHaO63ru
|
||||||
|
dFGSiYI8U+q+YnhUJjCnmI12fycNfy451TdUQtGZb6pNmm5HRUF6hpAV8Le9LojP
|
||||||
|
Ql9eacPpsrzU15X5ElCQZ/f9iNh1bplcISuhrULgKUKOvAVrBlEK67uRVy6g98xA
|
||||||
|
c/rgNLkbL/jZEsAc3/vHAyFgd3lABfwpBGLHej3QgQKBgQDFNYmfBNQr89HC5Zc+
|
||||||
|
M6jXcAT/R+0GNczBTfC4iyNemwqsumSSRelNZ748UefKuS3F6Mvb2CBqE2LbB61G
|
||||||
|
hrnCffG2pARjZ491SefRwghhWWVGLP1p8KliLgOGBehA1REgJb+XULncjuHZuh4O
|
||||||
|
LVn3HVnWGxeBGg+yKa6Z4YQi3QKBgQDZN0O8ZcZY74lRJ0UjscD9mJ1yHlsssZag
|
||||||
|
njkX/f0GR/iVpfaIxQNC3gvWUy2LsU0He9sidcB0cfej0j/qZObQyFsCB0+utOgy
|
||||||
|
+hX7gokV2pes27WICbNWE2lJL4QZRJgvf82OaEy57kfDrm+eK1XaSZTZ10P82C9u
|
||||||
|
gAmMnontcQKBgGu29lhY9tqa7jOZ26Yp6Uri8JfO3XPK5u+edqEVvlfqL0Zw+IW8
|
||||||
|
kdWpmIqx4f0kcA/tO4v03J+TvycLZmVjKQtGZ0PvCkaRRhY2K9yyMomZnmtaH4BB
|
||||||
|
5wKtR1do2pauyg/ZDnDDswD5OfsGYWw08TK8YVlEqu3lIjWZ9rguKVIxAoGAZYUk
|
||||||
|
zVqr10ks3pcCA2rCjkPT4lA5wKvHgI4ylPoKVfMxRY/pp4acvZXV5ne9o7pcDBFh
|
||||||
|
G7v5FPNnEFPlt4EtN4tMragJH9hBZgHoYEJkG6islweg0lHmVWaBIMlqbfzXO+v5
|
||||||
|
gINSyNuLAvP2CvCqEXmubhnkFrpbgMOqsuQuBqECgYB3ss2PDhBF+5qoWgqymFof
|
||||||
|
1ovRPuQ9sPjWBn5IrCdoYITDnbBzBZERx7GLs6A/PUlWgST7jkb1PY/TxYSUfXzJ
|
||||||
|
SNd47q0mCQ+IUdqUbHgpK9b1ncwLMsnexpYZdHJWRLgnUhOx7OMjJc/4iLCAFCoN
|
||||||
|
3KJ7/V1keo7GBHOwnsFcCA==
|
||||||
|
-----END PRIVATE KEY-----
|
|
@ -0,0 +1,35 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIGCTCCA/GgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwazELMAkGA1UEBhMCU0Ux
|
||||||
|
EjAQBgNVBAgMCVN0b2NraG9sbTESMBAGA1UECgwJTXlPcmdOYW1lMRkwFwYDVQQL
|
||||||
|
DBBNeUludGVybWVkaWF0ZUNBMRkwFwYDVQQDDBBNeUludGVybWVkaWF0ZUNBMB4X
|
||||||
|
DTIzMDExMjEzMDgxNloXDTMzMDQxOTEzMDgxNloweDELMAkGA1UEBhMCU0UxEjAQ
|
||||||
|
BgNVBAgMCVN0b2NraG9sbTESMBAGA1UEBwwJU3RvY2tob2xtMRIwEAYDVQQKDAlN
|
||||||
|
eU9yZ05hbWUxGTAXBgNVBAsMEE15SW50ZXJtZWRpYXRlQ0ExEjAQBgNVBAMMCWxv
|
||||||
|
Y2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKdU9FaA/n0Z
|
||||||
|
TXkd10XA9l+UV9xKR65ZTy2ApCFlw2gGWLiUh96a6hX+GQZFUV7ECIDDf+7nC85o
|
||||||
|
xo1Xyf0rHGABQ0uHlhqSemc12F9APIzRLlQkhtV4vMBBbGQFekje4F9bhY9JQtGd
|
||||||
|
XJGmwsR+XWo6SUY7K5l9FuSSSRXC0kSYYQfSTPR/LrF6efdHf+ZN4huP7lM2qIFd
|
||||||
|
afX+qBOI1/Y2LtITo2TaU/hXyKh9wEiuynoq0RZ2KkYQll5cKD9fSD+pW3Xm0XWX
|
||||||
|
TQy4RZEe3WoYEQsklNw3NC92ocA/PQB9BGNO1fKhzDn6kW2HxDxruDKOuO/meGek
|
||||||
|
ApCayu3e/I0CAwEAAaOCAagwggGkMAkGA1UdEwQCMAAwEQYJYIZIAYb4QgEBBAQD
|
||||||
|
AgZAMDMGCWCGSAGG+EIBDQQmFiRPcGVuU1NMIEdlbmVyYXRlZCBTZXJ2ZXIgQ2Vy
|
||||||
|
dGlmaWNhdGUwHQYDVR0OBBYEFGy5LQPzIelruJl7mL0mtUXM57XhMIGaBgNVHSME
|
||||||
|
gZIwgY+AFExwhjsVUom6tQ+Sqq6xMUETvnPzoXOkcTBvMQswCQYDVQQGEwJTRTES
|
||||||
|
MBAGA1UECAwJU3RvY2tob2xtMRIwEAYDVQQHDAlTdG9ja2hvbG0xEjAQBgNVBAoM
|
||||||
|
CU15T3JnTmFtZTERMA8GA1UECwwITXlSb290Q0ExETAPBgNVBAMMCE15Um9vdENB
|
||||||
|
ggIQADAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwOwYDVR0f
|
||||||
|
BDQwMjAwoC6gLIYqaHR0cDovL2xvY2FsaG9zdDo5ODc4L2ludGVybWVkaWF0ZS5j
|
||||||
|
cmwucGVtMDEGCCsGAQUFBwEBBCUwIzAhBggrBgEFBQcwAYYVaHR0cDovL2xvY2Fs
|
||||||
|
aG9zdDo5ODc3MA0GCSqGSIb3DQEBCwUAA4ICAQCX3EQgiCVqLhnCNd0pmptxXPxo
|
||||||
|
l1KyZkpdrFa/NgSqRhkuZSAkszwBDDS/gzkHFKEUhmqs6/UZwN4+Rr3LzrHonBiN
|
||||||
|
aQ6GeNNXZ/3xAQfUCwjjGmz9Sgw6kaX19Gnk2CjI6xP7T+O5UmsMI9hHUepC9nWa
|
||||||
|
XX2a0hsO/KOVu5ZZckI16Ek/jxs2/HEN0epYdvjKFAaVmzZZ5PATNjrPQXvPmq2r
|
||||||
|
x++La+3bXZsrH8P2FhPpM5t/IxKKW/Tlpgz92c2jVSIHF5khSA/MFDC+dk80OFmm
|
||||||
|
v4ZTPIMuZ//Q+wo0f9P48rsL9D27qS7CA+8pn9wu+cfnBDSt7JD5Yipa1gHz71fy
|
||||||
|
YTa9qRxIAPpzW2v7TFZE8eSKFUY9ipCeM2BbdmCQGmq4+v36b5TZoyjH4k0UVWGo
|
||||||
|
Gclos2cic5Vxi8E6hb7b7yZpjEfn/5lbCiGMfAnI6aoOyrWg6keaRA33kaLUEZiK
|
||||||
|
OgFNbPkjiTV0ZQyLXf7uK9YFhpVzJ0dv0CFNse8rZb7A7PLn8VrV/ZFnJ9rPoawn
|
||||||
|
t7ZGxC0d5BRSEyEeEgsQdxuY4m8OkE18zwhCkt2Qs3uosOWlIrYmqSEa0i/sPSQP
|
||||||
|
jiwB4nEdBrf8ZygzuYjT5T9YRSwhVox4spS/Av8Ells5JnkuKAhCVv9gHxYwbj0c
|
||||||
|
CzyLJgE1z9Tq63m+gQ==
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -24,12 +24,15 @@ init_suite() ->
|
||||||
init_suite([]).
|
init_suite([]).
|
||||||
|
|
||||||
init_suite(Apps) ->
|
init_suite(Apps) ->
|
||||||
init_suite(Apps, fun set_special_configs/1).
|
init_suite(Apps, fun set_special_configs/1, #{}).
|
||||||
|
|
||||||
init_suite(Apps, SetConfigs) ->
|
init_suite(Apps, SetConfigs) when is_function(SetConfigs) ->
|
||||||
|
init_suite(Apps, SetConfigs, #{}).
|
||||||
|
|
||||||
|
init_suite(Apps, SetConfigs, Opts) ->
|
||||||
mria:start(),
|
mria:start(),
|
||||||
application:load(emqx_management),
|
application:load(emqx_management),
|
||||||
emqx_common_test_helpers:start_apps(Apps ++ [emqx_dashboard], SetConfigs),
|
emqx_common_test_helpers:start_apps(Apps ++ [emqx_dashboard], SetConfigs, Opts),
|
||||||
emqx_common_test_http:create_default_app().
|
emqx_common_test_http:create_default_app().
|
||||||
|
|
||||||
end_suite() ->
|
end_suite() ->
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Add support for OCSP stapling and CRL check for SSL MQTT listeners.
|
|
@ -0,0 +1 @@
|
||||||
|
为SSL MQTT监听器增加对OCSP订书和CRL检查的支持。
|
|
@ -12,6 +12,7 @@ CMD
|
||||||
CN
|
CN
|
||||||
CONNACK
|
CONNACK
|
||||||
CoAP
|
CoAP
|
||||||
|
CRLs
|
||||||
Cygwin
|
Cygwin
|
||||||
DES
|
DES
|
||||||
DN
|
DN
|
||||||
|
@ -41,6 +42,7 @@ Makefile
|
||||||
MitM
|
MitM
|
||||||
Multicast
|
Multicast
|
||||||
NIF
|
NIF
|
||||||
|
OCSP
|
||||||
OTP
|
OTP
|
||||||
PEM
|
PEM
|
||||||
PINGREQ
|
PINGREQ
|
||||||
|
|
Loading…
Reference in New Issue