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

openSUSE Build Service is sponsored by