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 {
|
||||
desc {
|
||||
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_stats, 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(
|
||||
case Type of
|
||||
tcp -> Opts3#{tcp_options => tcp_opts(Opts0)};
|
||||
ssl -> Opts3#{ssl_options => ssl_opts(Opts0), tcp_options => tcp_opts(Opts0)}
|
||||
tcp ->
|
||||
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
|
||||
).
|
||||
|
||||
|
@ -785,3 +789,8 @@ quic_listener_optional_settings() ->
|
|||
max_binding_stateless_operations,
|
||||
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",
|
||||
sc(
|
||||
ref("listener_ssl_opts"),
|
||||
#{}
|
||||
#{validator => fun mqtt_ssl_listener_ssl_options_validator/1}
|
||||
)}
|
||||
];
|
||||
fields("mqtt_ws_listener") ->
|
||||
|
@ -1294,6 +1294,56 @@ fields("listener_quic_ssl_opts") ->
|
|||
);
|
||||
fields("ssl_client_opts") ->
|
||||
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") ->
|
||||
[
|
||||
{"level",
|
||||
|
@ -2017,6 +2067,8 @@ desc("trace") ->
|
|||
"Real-time filtering logs for the ClientID or Topic or IP for debugging.";
|
||||
desc("shared_subscription_group") ->
|
||||
"Per group dispatch strategy for shared subscription";
|
||||
desc("ocsp") ->
|
||||
"Per listener OCSP Stapling configuration.";
|
||||
desc(_) ->
|
||||
undefined.
|
||||
|
||||
|
@ -2199,14 +2251,62 @@ server_ssl_opts_schema(Defaults, IsRanchListener) ->
|
|||
)}
|
||||
] ++
|
||||
[
|
||||
{"gc_after_handshake",
|
||||
sc(boolean(), #{
|
||||
default => false,
|
||||
desc => ?DESC(server_ssl_opts_schema_gc_after_handshake)
|
||||
})}
|
||||
|| not IsRanchListener
|
||||
Field
|
||||
|| not IsRanchListener,
|
||||
Field <- [
|
||||
{"gc_after_handshake",
|
||||
sc(boolean(), #{
|
||||
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.
|
||||
-spec client_ssl_opts_schema(map()) -> hocon_schema:field_schema().
|
||||
client_ssl_opts_schema(Defaults) ->
|
||||
|
@ -2865,3 +2965,11 @@ is_quic_ssl_opts(Name) ->
|
|||
%% , "handshake_timeout"
|
||||
%% , "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,
|
||||
start_apps/1,
|
||||
start_apps/2,
|
||||
start_apps/3,
|
||||
stop_apps/1,
|
||||
reload/2,
|
||||
app_path/2,
|
||||
|
@ -36,7 +37,8 @@
|
|||
deps_path/2,
|
||||
flush/0,
|
||||
flush/1,
|
||||
render_and_load_app_config/1
|
||||
render_and_load_app_config/1,
|
||||
render_and_load_app_config/2
|
||||
]).
|
||||
|
||||
-export([
|
||||
|
@ -185,17 +187,21 @@ start_apps(Apps) ->
|
|||
application:set_env(system_monitor, db_hostname, ""),
|
||||
ok
|
||||
end,
|
||||
start_apps(Apps, DefaultHandler).
|
||||
start_apps(Apps, DefaultHandler, #{}).
|
||||
|
||||
-spec start_apps(Apps :: apps(), Handler :: special_config_handler()) -> ok.
|
||||
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
|
||||
%% Because, minirest, ekka etc.. application will scan these modules
|
||||
lists:foreach(fun load/1, [emqx | Apps]),
|
||||
ok = start_ekka(),
|
||||
mnesia:clear_table(emqx_admin),
|
||||
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) ->
|
||||
case application:load(App) of
|
||||
|
@ -205,27 +211,31 @@ load(App) ->
|
|||
end.
|
||||
|
||||
render_and_load_app_config(App) ->
|
||||
render_and_load_app_config(App, #{}).
|
||||
|
||||
render_and_load_app_config(App, Opts) ->
|
||||
load(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
|
||||
do_render_app_config(App, Schema, Conf)
|
||||
do_render_app_config(App, Schema, Conf, Opts)
|
||||
catch
|
||||
throw:E:St ->
|
||||
%% turn throw into error
|
||||
error({Conf, E, St})
|
||||
end.
|
||||
|
||||
do_render_app_config(App, Schema, ConfigFile) ->
|
||||
Vars = mustache_vars(App),
|
||||
do_render_app_config(App, Schema, ConfigFile, Opts) ->
|
||||
Vars = mustache_vars(App, Opts),
|
||||
RenderedConfigFile = render_config_file(ConfigFile, Vars),
|
||||
read_schema_configs(Schema, RenderedConfigFile),
|
||||
force_set_config_file_paths(App, [RenderedConfigFile]),
|
||||
copy_certs(App, RenderedConfigFile),
|
||||
ok.
|
||||
|
||||
start_app(App, SpecAppConfig) ->
|
||||
render_and_load_app_config(App),
|
||||
start_app(App, SpecAppConfig, Opts) ->
|
||||
render_and_load_app_config(App, Opts),
|
||||
SpecAppConfig(App),
|
||||
case application:ensure_all_started(App) of
|
||||
{ok, _} ->
|
||||
|
@ -248,12 +258,13 @@ app_schema(App) ->
|
|||
no_schema
|
||||
end.
|
||||
|
||||
mustache_vars(App) ->
|
||||
mustache_vars(App, Opts) ->
|
||||
ExtraMustacheVars = maps:get(extra_mustache_vars, Opts, []),
|
||||
[
|
||||
{platform_data_dir, app_path(App, "data")},
|
||||
{platform_etc_dir, app_path(App, "etc")},
|
||||
{platform_log_dir, app_path(App, "log")}
|
||||
].
|
||||
] ++ ExtraMustacheVars.
|
||||
|
||||
render_config_file(ConfigFile, Vars0) ->
|
||||
Temp =
|
||||
|
@ -337,7 +348,7 @@ safe_relative_path_2(Path) ->
|
|||
-spec reload(App :: atom(), SpecAppConfig :: special_config_handler()) -> ok.
|
||||
reload(App, SpecAppConfigHandler) ->
|
||||
application:stop(App),
|
||||
start_app(App, SpecAppConfigHandler),
|
||||
start_app(App, SpecAppConfigHandler, #{}),
|
||||
application:start(App).
|
||||
|
||||
ensure_mnesia_stopped() ->
|
||||
|
@ -479,7 +490,7 @@ is_all_tcp_servers_available(Servers) ->
|
|||
{_, []} ->
|
||||
true;
|
||||
{_, Unavail} ->
|
||||
ct:print("Unavailable servers: ~p", [Unavail]),
|
||||
ct:pal("Unavailable servers: ~p", [Unavail]),
|
||||
false
|
||||
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-----
|
||||
MIIJQQIBADANBgkqhkiG9w0BAQEFAASCCSswggknAgEAAoICAQC0Bu3THlP8vRwz
|
||||
R84cnUdG5sDfVBqZaiTiT8KiUWmWXK5/bpoCfUlNopdnHhFcTA8MwVgkSiTufx/Q
|
||||
PZNd0qrFzNnNnPcV3yBdZG/NtJQptVKMOYWf2rg48ZYjZPZDSQ4oAkYt0KCLP3VX
|
||||
lsxbIBAtpYMUamrjdnjRW+v8l2O/PGACxwbAkVdjRThV8bT7Ly16Q/UP/BKmLEjL
|
||||
iwEUXR1TVK8mhBeZYUoaXLs9E5SLk8tk4rlyVLSfqe2uGe364meibK5kFrFUAY+O
|
||||
BfEyUR1s1gu75vXPncxF/Xqm6+Yjy/i+HLTFvS8+nj3GHyF53ughJbrEVH/BvsTN
|
||||
bsKLxfW6VfvSKmO7QLvRtYmPYjbSYDTQyjgnDhinGQsegoNV2DhCkXjZ1DGs72Hq
|
||||
ka641eVkNLznA+c3AUTU1wnXQi2u1NILlz/ZkK37pODUx9Qbe+yYnkyfHHw1x8LW
|
||||
in+mhm5mT8JdV4vxrlzVD/8XqIO/ESshcAZo7diSsL1szKX5h+JhJBvZqIdmpb2v
|
||||
mhM+iyCbj9eoOrNEOnO/d/zr1LaTXHsB8OFY59/Sw/aOlS6fOeBuJ/HVaVFkm1kv
|
||||
rFw9WROvR4P7DDTMqvtPbn5G3sGPMaApv60Naag2QFTFrLLSRGmLu2/3m+0fkRjo
|
||||
9WBAglf2KRGT3Q6LW0jUZCItnf2tTQIDAQABAoICAAVlH8Nv6TxtvmabBEY/QF+T
|
||||
krwenR1z3N8bXM3Yer2S0XfoLJ1ee8/jy32/nO2TKfBL6wRLZIfxL1biQYRSR+Pd
|
||||
m7lZtt3k7edelysm+jm1wV+KacK8n0C1nLY61FZ33gC88LV2xxjlMfMKBd3FPDbh
|
||||
+ueluMZQSpablprfPpIAkTAEHuOud1v2OxX4RGAyrb44QyPTfguU0CmpZMLjd3mD
|
||||
1CvnUX27OKlJliLib1UvfKztTnlqqG8QfJr3E/asykZH04IUXAQUd+TdsLi9TZBx
|
||||
abCb30n1hKWkTwSplSAFgNLRsWkrnjrWKyvAyxQH5hT4OHyhu6JmwScW5qWhrRd3
|
||||
ld+pMaKQlOmtrTiRzSeFD2pOHFHvZ3N/1BhH5TGfnTIXKuEja3xdOArCHTBkh/9S
|
||||
kEZegVIAjoFW+t3gfbz12JzNmDUUX+sWfadBBiwYepTUr2aZQehZM8+dzdSwQeh4
|
||||
XcAUC55YgaC2oFCfcc8rD5o+57nlR+7xAjZ/Z61SuUJHrKSRzB6w2PARiEIuYotK
|
||||
E/CsQfL9tgjoc0aN0uVl8SH+GvKvRWM6LV711ep8w2XoPIAxId3ne/Ktw+wKCrqC
|
||||
CJsHXIGOi8n0YZLZ6vz/6WrjmY1GdJc1aywQvr5eDFP5g0j3e+WzGBxoCKX8Gah5
|
||||
KpA4fcN44s2umsu7WcoBAoIBAQDZyGhtu9rbm3JMJrA9Eyq97niv6axwbhocE/bU
|
||||
tPwdeWbPdprkK4aQ9UqJwHmVHkAUrGFRsY2iPJFLvdRwvixFYVAf/WLlAepd+HFz
|
||||
Xit1oX5ouzbcjq2+13zUQpfjXFqfLqVYcu/sW7UFaD3yJEstkhI+ZM6Ci+kLWXN5
|
||||
+KOXASGzO8p7WBHFABRMH0bUjRnZy8xX3wdOhAKRFaCalxABodH9wz/cMunzrmEa
|
||||
uHRsNWIIdWIVle4ZX4QTcsDgJSf5LeDaLtrpMu2AnFafQ2VCAb/jdKdighBsZG3H
|
||||
Pu6e1fJzSKZEUtWSLMzBoB6R/oNDW9cPhcXWXlNc8QsZ7DAtAoIBAQDTnmUqf8Lo
|
||||
lWPEQCrfkgQm2Gom/75uj5TnHsQYf2xk3vZNF5UwErD3Ixzh4F1E5ewA1Xvy5t3J
|
||||
VCOLypiKDlfcZnsMPncdubGMrT575mkpZgsvR/w8u8pd4mFSdyCc/y5TeyfcNFQe
|
||||
0Ho1NXMH6czutQs3oX+yfaTUr6Oa3brG1SAJQpG53nQI74pMWKHcivI/ytlA26Ki
|
||||
zxIVzeAzJ/ToVc6MzbObkXjFxrnVlvjsLyGMJEfW2lmny4Gpx1xpc2j3YW8vehfx
|
||||
DalWOJai1mtAo8ieo7CVw+kV2CqL7gJOJ2iNmCKT+IFk4LRtfJxd4wUJz6A/+vWp
|
||||
o0LMvApAnIWhAoIBAER1S+Zaq9Rmi8pGSxYXxVLI+KULhkodQhXbbLa2YZ3+QIQs
|
||||
m0noKLe+c3zTxSRLywb0nO7qKkR6V44AkRwTm6T/jwlPRFwKexqo8zi5vF2Qs0TG
|
||||
vNsd+p3H7RRoDojIyi/JoO4pyyN4PHIDr51DLWKYzSVR2NyOkGYh6zvHHd1k3KwT
|
||||
unWFXKiZesfm+QPtite8yXJByHE06/2hV8fgfoaU0Ia9boCQfJw+D4Yvv2EYcsWH
|
||||
6JoydBMDxGe8pcaPx337nvfWzLeLa78G5e/QZq8WD7S3Qbqkefcopp2AOdAyHrGA
|
||||
f8twYnQ9ouumopVv9OEiqHrXqTXWlsvbdYrjhM0CggEABOEHBhbSAJjJJxIvqt3r
|
||||
+JVOxT1qP5RR445DCSmO7zhwx1A+4U/dAqWtmcuZeuguK8rAQ9Zs0KJ++08dezlf
|
||||
bzZxqdOa3XWVkV/BLAwg6pJuuZVYTHIr9UQt6D/U4anEgKo7Pgl60wcNekKUN199
|
||||
mRdVfd/cWNoqvbia9gwcrU7moTAGuhlV5YrYTnBQswwFD9F2dtdZhZVunlAT1joa
|
||||
nGy2CWsItBKDjVPKnxEPBisEA/4mJd786DB5+dcd21SM2/9EF/0hpi4hdFpzpqd4
|
||||
65GbI4U0og9VRWqpeHZxWSnxcCpMycqV+SRxJIEV/dgpGpPN5wu7NEEOXjgLqHez
|
||||
YQKCAQBjwMVQUgn2KZK6Q9Lwe09ZpWTxGMh9mevU3eMA/6awajkE4UVgV8hSVvcG
|
||||
i3Otn9UMnMhYu+HuU9O9W4zzncH0nRoiwjQr3X0MTT3Lc0rSJNPb/a6pcvysBuvB
|
||||
wvhQ/dRXbCtmK9VE9ctPa9EO9f9SQRZF2NQsTOkyILdsgISm4zXSBhyT8KkQbiTe
|
||||
0ToI7qMM73HqLHKOkjA+8jYkE5MTVQaaRXx2JlCeHEsIpH/2Nj1OsmUfn3paL6ZN
|
||||
3loKhFfGy4onSOJOxoYaI3r6aykTFm7Qyg1xrG+8uFhK/qTOCB22I63LmSLZ1wlY
|
||||
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(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(),
|
||||
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().
|
||||
|
||||
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
|
||||
CONNACK
|
||||
CoAP
|
||||
CRLs
|
||||
Cygwin
|
||||
DES
|
||||
DN
|
||||
|
@ -41,6 +42,7 @@ Makefile
|
|||
MitM
|
||||
Multicast
|
||||
NIF
|
||||
OCSP
|
||||
OTP
|
||||
PEM
|
||||
PINGREQ
|
||||
|
|
Loading…
Reference in New Issue