File 0175-SSL-Make-the-handshake-fail-when-OCSP-staple-missing.patch of Package erlang
From 3b5043346f6db30bab1fa5370cdc4ee746816e60 Mon Sep 17 00:00:00 2001
From: Michal Palka <michal.palka@hiq.se>
Date: Tue, 23 Mar 2021 16:00:48 +0100
Subject: [PATCH 1/3] SSL: Make the handshake fail when OCSP staple missing
* Make the TLS handshake fail when OCSP staple is requested
but missing.
* Add test case for missing OCSP response.
* Add test case for OCSP response with certificate
status undetermined.
---
lib/ssl/src/ssl_gen_statem.erl | 3 +-
lib/ssl/src/ssl_handshake.erl | 12 ++++-
lib/ssl/test/make_certs.erl | 20 +++++++-
lib/ssl/test/openssl_ocsp_SUITE.erl | 71 ++++++++++++++++++++++++++++-
lib/ssl/test/ssl_test_lib.erl | 16 ++++++-
5 files changed, 114 insertions(+), 8 deletions(-)
diff --git a/lib/ssl/src/ssl_gen_statem.erl b/lib/ssl/src/ssl_gen_statem.erl
index 6431b5bedd..c2dd4e5c7a 100644
--- a/lib/ssl/src/ssl_gen_statem.erl
+++ b/lib/ssl/src/ssl_gen_statem.erl
@@ -504,7 +504,8 @@ initial_hello({call, From}, {start, Timeout},
negotiated_version = RequestedVersion},
session = Session,
handshake_env = HsEnv1#handshake_env{
- ocsp_stapling_state = OcspState0#{ocsp_nonce => OcspNonce}},
+ ocsp_stapling_state = OcspState0#{ocsp_nonce => OcspNonce,
+ ocsp_stapling => OcspStaplingOpt}},
start_or_recv_from = From,
key_share = KeyShare},
NextState = next_statem_state(Versions, Role),
diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl
index de5490d232..aac4c28a5f 100644
--- a/lib/ssl/src/ssl_handshake.erl
+++ b/lib/ssl/src/ssl_handshake.erl
@@ -1971,10 +1971,18 @@ cert_status_check(_, #{ocsp_state := #{ocsp_stapling := true,
valid; %% OCSP staple will now be checked by ssl_certifcate:verify_cert_extensions/2 in ssl_certifcate:validate
cert_status_check(OtpCert, #{ocsp_state := #{ocsp_stapling := false}} = SslState, VerifyResult, CertPath, LogLevel) ->
maybe_check_crl(OtpCert, SslState, VerifyResult, CertPath, LogLevel);
-cert_status_check(OtpCert, #{ocsp_state := #{ocsp_stapling := best_effort, %%TODO should we support
+cert_status_check(_OtpCert, #{ocsp_state := #{ocsp_stapling := true,
+ ocsp_expect := undetermined}},
+ _VerifyResult, _CertPath, _LogLevel) ->
+ {bad_cert, {revocation_status_undetermined, not_stapled}};
+cert_status_check(OtpCert, #{ocsp_state := #{ocsp_stapling := best_effort, %% TODO support this ?
ocsp_expect := undetermined}} = SslState,
VerifyResult, CertPath, LogLevel) ->
- maybe_check_crl(OtpCert, SslState, VerifyResult, CertPath, LogLevel).
+ maybe_check_crl(OtpCert, SslState, VerifyResult, CertPath, LogLevel);
+cert_status_check(_OtpCert, #{ocsp_state := #{ocsp_stapling := true,
+ ocsp_expect := no_staple}},
+ _VerifyResult, _CertPath, _LogLevel) ->
+ {bad_cert, {revocation_status_undetermined, not_stapled}}.
maybe_check_crl(_, #{crl_check := false}, _, _, _) ->
valid;
diff --git a/lib/ssl/test/make_certs.erl b/lib/ssl/test/make_certs.erl
index 8577397ac0..0efa776be2 100644
--- a/lib/ssl/test/make_certs.erl
+++ b/lib/ssl/test/make_certs.erl
@@ -88,7 +88,7 @@ all(DataDir, PrivDir, C = #config{}) ->
create_rnd(DataDir, PrivDir), % For all requests
rootCA(PrivDir, "erlangCA", C),
intermediateCA(PrivDir, "otpCA", "erlangCA", C),
- endusers(PrivDir, "otpCA", ["client", "server", "revoked", "a.server", "b.server"], C),
+ endusers(PrivDir, "otpCA", ["client", "server", "revoked", "undetermined", "a.server", "b.server"], C),
endusers(PrivDir, "erlangCA", ["localhost"], C),
%% Create keycert files
SDir = filename:join([PrivDir, "server"]),
@@ -107,6 +107,12 @@ all(DataDir, PrivDir, C = #config{}) ->
RKC = filename:join([RDir, "keycert.pem"]),
revoke(PrivDir, "otpCA", "revoked", C),
append_files([RK, RC], RKC),
+ MDir = filename:join([PrivDir, "undetermined"]),
+ MC = filename:join([MDir, "cert.pem"]),
+ MK = filename:join([MDir, "key.pem"]),
+ MKC = filename:join([MDir, "keycert.pem"]),
+ remove_entry(PrivDir, "otpCA", "undetermined", C),
+ append_files([MK, MC], MKC),
remove_rnd(PrivDir),
{ok, C}.
@@ -177,6 +183,18 @@ revoke(Root, CA, User, C) ->
cmd(Cmd, Env),
gencrl(Root, CA, C).
+%% Remove the certificate's entry from the database. The OCSP responder
+%% will consider the certificate unknown.
+remove_entry(Root, CA, User, C) ->
+ Db = filename:join([Root, CA, "index.txt"]),
+ Cmd = ["grep", " -v"
+ " \"/CN=", User ,"/\" ", Db,
+ " > ", Db, "~new"],
+ cmd(Cmd, []),
+ Cmd2 = ["mv ", Db, "~new ", Db],
+ cmd(Cmd2, []),
+ gencrl(Root, CA, C).
+
gencrl(Root, CA, C) ->
%% By default, the CRL is valid for a week from now.
gencrl(Root, CA, C, 24*7).
diff --git a/lib/ssl/test/openssl_ocsp_SUITE.erl b/lib/ssl/test/openssl_ocsp_SUITE.erl
index d328a3cfbd..f4a68f7543 100644
--- a/lib/ssl/test/openssl_ocsp_SUITE.erl
+++ b/lib/ssl/test/openssl_ocsp_SUITE.erl
@@ -37,7 +37,9 @@
-export([ocsp_stapling_basic/0,ocsp_stapling_basic/1,
ocsp_stapling_with_nonce/0, ocsp_stapling_with_nonce/1,
ocsp_stapling_with_responder_cert/0,ocsp_stapling_with_responder_cert/1,
- ocsp_stapling_revoked/0, ocsp_stapling_revoked/1
+ ocsp_stapling_revoked/0, ocsp_stapling_revoked/1,
+ ocsp_stapling_undetermined/0, ocsp_stapling_undetermined/1,
+ ocsp_stapling_no_staple/0, ocsp_stapling_no_staple/1
]).
%% spawn export
@@ -61,7 +63,9 @@ ocsp_tests() ->
[ocsp_stapling_basic,
ocsp_stapling_with_nonce,
ocsp_stapling_with_responder_cert,
- ocsp_stapling_revoked
+ ocsp_stapling_revoked,
+ ocsp_stapling_undetermined,
+ ocsp_stapling_no_staple
].
%%--------------------------------------------------------------------
@@ -254,6 +258,69 @@ ocsp_stapling_revoked(Config)
ssl_test_lib:check_client_alert(Client, certificate_revoked).
+%%--------------------------------------------------------------------
+ocsp_stapling_undetermined() ->
+ [{doc, "Verify OCSP stapling works with certificate with undetermined status."}].
+ocsp_stapling_undetermined(Config)
+ when is_list(Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ CACertsFile = filename:join(PrivDir, "undetermined/cacerts.pem"),
+
+ GroupName = proplists:get_value(group, Config),
+ ServerOpts = [{log_level, debug},
+ {group, GroupName}],
+ {ClientNode, _ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ Server = ssl_test_lib:start_server(openssl_ocsp_undetermined,
+ [{options, ServerOpts}], Config),
+ Port = ssl_test_lib:inet_port(Server),
+
+ ClientOpts = [{log_level, debug},
+ {verify, verify_peer},
+ {server_name_indication, disable},
+ {cacertfile, CACertsFile},
+ {ocsp_stapling, true},
+ {ocsp_nonce, true}
+ ] ++ dtls_client_opt(GroupName),
+
+ Client = ssl_test_lib:start_client_error([{node, ClientNode},{port, Port},
+ {host, Hostname}, {from, self()},
+ {options, ClientOpts}]),
+
+ ssl_test_lib:check_client_alert(Client, bad_certificate).
+
+%%--------------------------------------------------------------------
+ocsp_stapling_no_staple() ->
+ [{doc, "Verify OCSP stapling works with a missing OCSP response."}].
+ocsp_stapling_no_staple(Config)
+ when is_list(Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ CACertsFile = filename:join(PrivDir, "a.server/cacerts.pem"),
+
+ GroupName = proplists:get_value(group, Config),
+ ServerOpts = [{log_level, debug},
+ {group, GroupName}],
+ {ClientNode, _ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ %% Start a server that will not include an OCSP response.
+ Server = ssl_test_lib:start_server(openssl,
+ [{options, ServerOpts}], Config),
+ Port = ssl_test_lib:inet_port(Server),
+
+ ClientOpts = [{log_level, debug},
+ {verify, verify_peer},
+ {server_name_indication, disable},
+ {cacertfile, CACertsFile},
+ {ocsp_stapling, true},
+ {ocsp_nonce, true}
+ ] ++ dtls_client_opt(GroupName),
+
+ Client = ssl_test_lib:start_client_error([{node, ClientNode},{port, Port},
+ {host, Hostname}, {from, self()},
+ {options, ClientOpts}]),
+
+ ssl_test_lib:check_client_alert(Client, bad_certificate).
+
%%--------------------------------------------------------------------
%% Intrernal functions -----------------------------------------------
%%--------------------------------------------------------------------
diff --git a/lib/ssl/test/ssl_test_lib.erl b/lib/ssl/test/ssl_test_lib.erl
index c17883bd6f..ea2ad41c7a 100644
--- a/lib/ssl/test/ssl_test_lib.erl
+++ b/lib/ssl/test/ssl_test_lib.erl
@@ -228,6 +228,8 @@ start_server(openssl_ocsp, Options, Config) ->
start_openssl_server(openssl_ocsp, Options, Config);
start_server(openssl_ocsp_revoked, Options, Config) ->
start_openssl_server(openssl_ocsp_revoked, Options, Config);
+start_server(openssl_ocsp_undetermined, Options, Config) ->
+ start_openssl_server(openssl_ocsp_undetermined, Options, Config);
start_server(Type, _Args, _Config) ->
{error, unsupported_server_type, Type}.
@@ -266,13 +268,22 @@ get_server_opts(openssl_ocsp_revoked, Config) ->
Cert = filename:join(PrivDir, "revoked/cert.pem"),
Key = filename:join(PrivDir, "revoked/key.pem"),
CACerts = filename:join(PrivDir, "revoked/cacerts.pem"),
+ SOpts = [{reuseaddr, true},
+ {cacertfile, CACerts},
+ {certfile, Cert},
+ {keyfile, Key}],
+ ssl_options(SOpts, Config);
+get_server_opts(openssl_ocsp_undetermined, Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ Cert = filename:join(PrivDir, "undetermined/cert.pem"),
+ Key = filename:join(PrivDir, "undetermined/key.pem"),
+ CACerts = filename:join(PrivDir, "undetermined/cacerts.pem"),
SOpts = [{reuseaddr, true},
{cacertfile, CACerts},
{certfile, Cert},
{keyfile, Key}],
ssl_options(SOpts, Config).
-
get_client_opts(Config) ->
DCOpts = proplists:get_value(client_ecdsa_opts, Config),
COpts = proplists:get_value(client_opts, Config, DCOpts),
@@ -724,7 +735,8 @@ init_openssl_server(openssl, _, Options) ->
openssl_server_loop(Pid, SslPort, Args);
init_openssl_server(Mode, ResponderPort, Options) when Mode == openssl_ocsp orelse
- Mode == openssl_ocsp_revoked ->
+ Mode == openssl_ocsp_revoked orelse
+ Mode == openssl_ocsp_undetermined ->
DefaultVersions = default_tls_version(Options),
[Version | _] = proplists:get_value(versions, Options, DefaultVersions),
Port = inet_port(node()),
--
2.26.2