From 867d51385bc16fbba198d1d829a8305510ed02f4 Mon Sep 17 00:00:00 2001
From: Ao Song <andy@erlang.org>
Date: Mon, 6 Jul 2020 15:24:51 +0200
Subject: [PATCH] ssl: Prototype client OCSP support

---
 lib/ssl/src/dtls_connection.erl   |   7 +-
 lib/ssl/src/dtls_handshake.erl    |  14 +-
 lib/ssl/src/ssl.erl               |  20 +-
 lib/ssl/src/ssl_connection.erl    |  29 +++
 lib/ssl/src/ssl_connection.hrl    |   3 +-
 lib/ssl/src/ssl_handshake.erl     | 122 +++++++++-
 lib/ssl/src/ssl_handshake.hrl     |  24 +-
 lib/ssl/src/ssl_internal.hrl      |  14 +-
 lib/ssl/src/ssl_logger.erl        |   5 +
 lib/ssl/src/tls_connection.erl    | 119 +++++++---
 lib/ssl/src/tls_handshake.erl     |  31 ++-
 lib/ssl/src/tls_handshake_1_3.erl |  27 ++-
 lib/ssl/test/Makefile             |   3 +-
 lib/ssl/test/ssl_basic_SUITE.erl  |   2 +-
 lib/ssl/test/ssl_ocsp_SUITE.erl   | 367 ++++++++++++++++++++++++++++++
 15 files changed, 720 insertions(+), 67 deletions(-)
 create mode 100644 lib/ssl/test/ssl_ocsp_SUITE.erl

Index: otp-OTP-22.3/lib/ssl/src/dtls_connection.erl
===================================================================
--- otp-OTP-22.3.orig/lib/ssl/src/dtls_connection.erl
+++ otp-OTP-22.3/lib/ssl/src/dtls_connection.erl
@@ -524,13 +524,14 @@ hello(internal, #hello_verify_request{co
                                                                                         port = Port},
                                                                handshake_env = #handshake_env{renegotiation = {Renegotiation, _}} = HsEnv,
                                                                connection_env = CEnv,
-							       ssl_options = SslOpts,
+							       ssl_options = #{ocsp_stapling := OcspStaplingOpt,
+                                                   ocsp_nonce := OcspNonceOpt} = SslOpts,
 							       session = #session{own_certificate = Cert, session_id = Id},
 							       connection_states = ConnectionStates0
 							      } = State0) ->
-  
+    OcspNonce = tls_handshake:ocsp_nonce(OcspNonceOpt, OcspStaplingOpt),
     Hello = dtls_handshake:client_hello(Host, Port, Cookie, ConnectionStates0,
-					SslOpts, Id, Renegotiation, Cert),
+					SslOpts, Id, Renegotiation, Cert, OcspNonce),
     Version = Hello#client_hello.client_version,
     State1 = prepare_flight(State0#state{handshake_env =  
                                              HsEnv#handshake_env{tls_handshake_history 
Index: otp-OTP-22.3/lib/ssl/src/dtls_handshake.erl
===================================================================
--- otp-OTP-22.3.orig/lib/ssl/src/dtls_handshake.erl
+++ otp-OTP-22.3/lib/ssl/src/dtls_handshake.erl
@@ -30,7 +30,7 @@
 -include("ssl_alert.hrl").
 
 %% Handshake handling
--export([client_hello/7, client_hello/8, cookie/4, hello/4, 
+-export([client_hello/7, client_hello/9, cookie/4, hello/4, 
 	 hello_verify_request/2]).
         
 %% Handshake encoding
@@ -56,11 +56,11 @@ client_hello(Host, Port, ConnectionState
 	     Id, Renegotiation, OwnCert) ->
     %% First client hello (two sent in DTLS ) uses empty Cookie
     client_hello(Host, Port, <<>>, ConnectionStates, SslOpts,
-		 Id, Renegotiation, OwnCert).
+		 Id, Renegotiation, OwnCert, undefined).
 
 %%--------------------------------------------------------------------
 -spec client_hello(ssl:host(), inet:port_number(), term(), ssl_record:connection_states(),
-		   ssl_options(), binary(),boolean(), der_cert()) ->
+		   ssl_options(), binary(),boolean(), der_cert(), binary() | undefined) ->
 			  #client_hello{}.
 %%
 %% Description: Creates a client hello message.
@@ -69,7 +69,7 @@ client_hello(_Host, _Port, Cookie, Conne
 	     #{versions := Versions,
                ciphers := UserSuites,
                fallback := Fallback} = SslOpts,
-	     Id, Renegotiation, _OwnCert) ->
+	     Id, Renegotiation, _OwnCert, OcspNonce) ->
     Version =  dtls_record:highest_protocol_version(Versions),
     Pending = ssl_record:pending_connection_state(ConnectionStates, read),
     SecParams = maps:get(security_parameters, Pending),
@@ -79,7 +79,7 @@ client_hello(_Host, _Port, Cookie, Conne
     Extensions = ssl_handshake:client_hello_extensions(TLSVersion, CipherSuites,
                                                        SslOpts, ConnectionStates, 
                                                        Renegotiation, undefined,
-                                                       undefined),
+                                                       undefined, OcspNonce),
 
     #client_hello{session_id = Id,
 		  client_version = Version,
Index: otp-OTP-22.3/lib/ssl/src/ssl.erl
===================================================================
--- otp-OTP-22.3.orig/lib/ssl/src/ssl.erl
+++ otp-OTP-22.3/lib/ssl/src/ssl.erl
@@ -28,7 +28,6 @@
 
 -include("ssl_internal.hrl").
 -include("ssl_api.hrl").
--include("ssl_internal.hrl").
 -include("ssl_record.hrl").
 -include("ssl_cipher.hrl").
 -include("ssl_handshake.hrl").
@@ -384,7 +383,11 @@
                                 {signature_algs, client_signature_algs()} |
                                 {fallback, fallback()} |
                                 {session_tickets, client_session_tickets()} |
-                                {use_ticket, use_ticket()}.
+                                {use_ticket, use_ticket()}.%% |
+                                %% These were commented out in d542eef20e7a5380511ec27c931e22afe946e73d
+                                %% {ocsp_stapling, ocsp_stapling()} |
+                                %% {ocsp_responder_certs, ocsp_responder_certs()} |
+                                %% {ocsp_nonce, ocsp_nonce()}.
 
 -type client_verify_type()       :: verify_type().
 -type client_reuse_session()     :: session_id().
@@ -405,6 +408,10 @@
 -type client_signature_algs()    :: signature_algs().
 -type fallback()                 :: boolean().
 -type ssl_imp()                  :: new | old.
+%% These were commented out in d542eef20e7a5380511ec27c931e22afe946e73d
+%%-type ocsp_stapling()            :: boolean().
+%%-type ocsp_responder_certs()     :: [public_key:der_encoded()].
+%%-type ocsp_nonce()               :: boolean().
 
 %% -------------------------------------------------------------------------------------------------------
 
@@ -2212,6 +2219,17 @@ validate_option(anti_replay, '100k') ->
 validate_option(anti_replay, Value) when (is_tuple(Value) andalso
                                           tuple_size(Value) =:= 3) ->
     Value;
+validate_option(ocsp_stapling, Value) when Value =:= true orelse
+                                           Value =:= false ->
+    Value;
+%% The OCSP responders' certificates can be given as a suggestion and
+%% will be used to verify the OCSP response.
+validate_option(ocsp_responder_certs, Value) when is_list(Value) ->
+    [public_key:pkix_decode_cert(CertDer, plain) || CertDer <- Value,
+                                                    is_binary(CertDer)];
+validate_option(ocsp_nonce, Value) when Value =:= true orelse
+                                        Value =:= false ->
+    Value;
 validate_option(Opt, undefined = Value) ->
     AllOpts = maps:keys(?RULES),
     case lists:member(Opt, AllOpts) of
Index: otp-OTP-22.3/lib/ssl/src/ssl_connection.erl
===================================================================
--- otp-OTP-22.3.orig/lib/ssl/src/ssl_connection.erl
+++ otp-OTP-22.3/lib/ssl/src/ssl_connection.erl
@@ -1179,6 +1179,35 @@ certify(internal, #client_key_exchange{e
 	#alert{} = Alert ->
 	    handle_own_alert(Alert, Version, ?FUNCTION_NAME, State)
     end;
+%% The response will be handled only when a certificate status request has
+%% been sent by the client and confirmed by the server.
+certify(internal, #certificate_status{response = OcspRespDer},
+        #state{ssl_options = #{
+            ocsp_stapling        := true,
+            ocsp_responder_certs := ResponderCerts},
+               handshake_env = #handshake_env{
+                   ocsp_stapling_state = #{
+                       ocsp_negotiated := true,
+                       ocsp_nonce := OcspNonce} = OcspState} = HsEnv,
+               connection_env = #connection_env{
+                   negotiated_version = Version}} = State,
+        Connection) ->
+    Result = public_key:ocsp_status(
+        OcspRespDer, ResponderCerts, OcspNonce),
+    NewOcspState = OcspState#{
+        ocsp_stapling_result => Result},
+    NewState = State#state{handshake_env =
+        HsEnv#handshake_env{ocsp_stapling_state = NewOcspState}},
+    case Result of
+        {ok, [#'SingleResponse'{
+                certStatus = {revoked, _RevokedInfo}}
+             ]} ->
+            Alert = ?ALERT_REC(
+                ?FATAL, ?BAD_CERTIFICATE_STATUS_RESPONSE, revoked_certificate),
+            handle_own_alert(Alert, Version, ?FUNCTION_NAME, NewState);
+        _Other ->
+            Connection:next_event(?FUNCTION_NAME, no_record, NewState)
+    end;
 certify(Type, Msg, State, Connection) ->
     handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection).
  
Index: otp-OTP-22.3/lib/ssl/src/ssl_connection.hrl
===================================================================
--- otp-OTP-22.3.orig/lib/ssl/src/ssl_connection.hrl
+++ otp-OTP-22.3/lib/ssl/src/ssl_connection.hrl
@@ -80,7 +80,8 @@
                         public_key_info      :: ssl_handshake:public_key_info() | 'undefined',
                         premaster_secret     :: binary() | secret_printout() | 'undefined',
                         server_psk_identity         :: binary() | 'undefined',  % server psk identity hint
-                        ticket_seed
+                        ticket_seed,
+                        ocsp_stapling_state = #{}
                        }).
 
 -record(connection_env, { 
Index: otp-OTP-22.3/lib/ssl/src/ssl_handshake.erl
===================================================================
--- otp-OTP-22.3.orig/lib/ssl/src/ssl_handshake.erl
+++ otp-OTP-22.3/lib/ssl/src/ssl_handshake.erl
@@ -72,7 +72,7 @@
          premaster_secret/2, premaster_secret/3, premaster_secret/4]).
 
 %% Extensions handling
--export([client_hello_extensions/7,
+-export([client_hello_extensions/8,
 	 handle_client_hello_extensions/9, %% Returns server hello extensions
 	 handle_server_hello_extensions/9, select_curve/2, select_curve/3,
          select_hashsign/4, select_hashsign/5,
@@ -351,10 +351,10 @@ certify(#certificate{asn1_certificates =
           depth := Depth} = Opts, CRLDbHandle, Role, Host) ->
     
     ServerName = server_name(ServerNameIndication, Host, Role),
-    [PeerCert | ChainCerts ] = ASN1Certs,       
+    [PeerCert | ChainCerts ] = ASN1Certs,
     try
 	{TrustedCert, CertPath}  =
-	    ssl_certificate:trusted_cert_and_path(ASN1Certs, CertDbHandle, CertDbRef,  
+	    ssl_certificate:trusted_cert_and_path(ASN1Certs, CertDbHandle, CertDbRef,
                                                   PartialChain),
         ValidationFunAndState = validation_fun_and_state(VerifyFun, Role,
                                                          CertDbHandle, CertDbRef, ServerName,
@@ -363,11 +363,11 @@ certify(#certificate{asn1_certificates =
         Options = [{max_path_length, Depth},
                    {verify_fun, ValidationFunAndState}],
 	case public_key:pkix_path_validation(TrustedCert, CertPath, Options) of
-	    {ok, {PublicKeyInfo,_}} ->
-		{PeerCert, PublicKeyInfo};
+	    {ok, {PublicKeyInfo, _}} ->
+            {PeerCert, PublicKeyInfo};
 	    {error, Reason} ->
-		handle_path_validation_error(Reason, PeerCert, ChainCerts, Opts, Options, 
-                                             CertDbHandle, CertDbRef)
+		    handle_path_validation_error(Reason, PeerCert, ChainCerts, Opts, Options,
+                                         CertDbHandle, CertDbRef)
 	end
     catch
 	error:{_,{error, {asn1, Asn1Reason}}} ->
@@ -728,6 +728,15 @@ encode_extensions([#psk_key_exchange_mod
     ExtLen = KEModesLen + 1,
     encode_extensions(Rest, <<?UINT16(?PSK_KEY_EXCHANGE_MODES_EXT),
                               ?UINT16(ExtLen), ?BYTE(KEModesLen), KEModes/binary, Acc/binary>>);
+encode_extensions([
+    #certificate_status_request{
+        status_type = StatusRequest,
+        request = Request} | Rest], Acc) ->
+    CertStatusReq = encode_cert_status_req(StatusRequest, Request),
+    Len = byte_size(CertStatusReq),
+    encode_extensions(
+        Rest, <<?UINT16(?STATUS_REQUEST), ?UINT16(Len),
+        CertStatusReq/binary, Acc/binary>>);
 encode_extensions([#pre_shared_key_client_hello{
                       offered_psks = #offered_psks{
                                         identities = Identities0,
@@ -746,6 +755,39 @@ encode_extensions([#pre_shared_key_serve
                               ?UINT16(2), ?UINT16(Identity), Acc/binary>>).
 
 
+encode_cert_status_req(
+    StatusType,
+    #ocsp_status_request{
+        responder_id_list = ResponderIDList,
+        request_extensions = ReqExtns}) ->
+    ResponderIDListBin = encode_responderID_list(ResponderIDList),
+    ReqExtnsBin = encode_request_extensions(ReqExtns),
+    <<?BYTE(StatusType), ResponderIDListBin/binary, ReqExtnsBin/binary>>.
+
+encode_responderID_list([]) ->
+    <<?UINT16(0)>>;
+encode_responderID_list(List) ->
+    do_encode_responderID_list(List, <<>>).
+
+%% ResponderID is DER-encoded ASN.1 type defined in RFC6960.
+do_encode_responderID_list([], Acc) ->
+    Len = byte_size(Acc),
+    <<?UINT16(Len), Acc/binary>>;
+do_encode_responderID_list([Responder | Rest], Acc)
+  when is_binary(Responder) ->
+    Len = byte_size(Responder),
+    do_encode_responderID_list(
+        Rest, <<Acc/binary, ?UINT16(Len), Responder/binary>>).
+
+%% Extensions are DER-encoded ASN.1 type defined in RFC6960 following
+%% extension model employed in X.509 version 3 certificates(RFC5280).
+encode_request_extensions([]) ->
+    <<?UINT16(0)>>;
+encode_request_extensions(Extns) when is_list(Extns) ->
+    ExtnBin = public_key:der_encode('Extensions', Extns),
+    Len = byte_size(ExtnBin),
+    <<?UINT16(Len), ExtnBin/binary>>.
+
 encode_client_protocol_negotiation(undefined, _) ->
     undefined;
 encode_client_protocol_negotiation(_, false) ->
@@ -757,7 +799,8 @@ encode_protocols_advertised_on_server(un
 	undefined;
 
 encode_protocols_advertised_on_server(Protocols) ->
-	#next_protocol_negotiation{extension_data = lists:foldl(fun encode_protocol/2, <<>>, Protocols)}.
+	#next_protocol_negotiation{
+        extension_data = lists:foldl(fun encode_protocol/2, <<>>, Protocols)}.
 
 %%====================================================================
 %% Decode handshake 
@@ -796,6 +839,14 @@ decode_handshake(Version, ?SERVER_HELLO,
        extensions = HelloExtensions};
 decode_handshake(_Version, ?CERTIFICATE, <<?UINT24(ACLen), ASN1Certs:ACLen/binary>>) ->
     #certificate{asn1_certificates = certs_to_list(ASN1Certs)};
+%% RFC 6066, servers return a certificate response along with their certificate
+%% by sending a "CertificateStatus" message immediately after the "Certificate"
+%% message and before any "ServerKeyExchange" or "CertificateRequest" messages.
+decode_handshake(_Version, ?CERTIFICATE_STATUS, <<?BYTE(?CERTIFICATE_STATUS_TYPE_OCSP),
+                 ?UINT24(Len), ASN1OcspResponse:Len/binary>>) ->
+    #certificate_status{
+        status_type = ?CERTIFICATE_STATUS_TYPE_OCSP,
+        response = ASN1OcspResponse};
 decode_handshake(_Version, ?SERVER_KEY_EXCHANGE, Keys) ->
     #server_key_exchange{exchange_keys = Keys};
 decode_handshake({Major, Minor}, ?CERTIFICATE_REQUEST,
@@ -1081,10 +1132,11 @@ premaster_secret(EncSecret, #{algorithm
 %% Extensions handling
 %%====================================================================
 client_hello_extensions(Version, CipherSuites, SslOpts, ConnectionStates, Renegotiation, KeyShare,
-                        TicketData) ->
+                        TicketData, OcspNonce) ->
     HelloExtensions0 = add_tls12_extensions(Version, SslOpts, ConnectionStates, Renegotiation),
     HelloExtensions1 = add_common_extensions(Version, HelloExtensions0, CipherSuites, SslOpts),
-    maybe_add_tls13_extensions(Version, HelloExtensions1, SslOpts, KeyShare, TicketData).
+    HelloExtensions2 = maybe_add_certificate_status_request(Version, SslOpts, OcspNonce, HelloExtensions1),
+    maybe_add_tls13_extensions(Version, HelloExtensions2, SslOpts, KeyShare, TicketData).
 
 
 add_tls12_extensions(_Version,
@@ -1153,6 +1205,31 @@ maybe_add_tls13_extensions({3,4},
 maybe_add_tls13_extensions(_, HelloExtensions, _, _, _) ->
     HelloExtensions.
 
+maybe_add_certificate_status_request(
+    _Version, #{ocsp_stapling := false}, _OcspNonce, HelloExtensions) ->
+    HelloExtensions;
+maybe_add_certificate_status_request(
+    _Version, #{ocsp_stapling        := true,
+                ocsp_responder_certs := OcspResponderCerts},
+    OcspNonce, HelloExtensions) ->
+    OcspResponderList = get_ocsp_responder_list(OcspResponderCerts),
+    OcspRequestExtns = public_key:ocsp_extensions(OcspNonce),
+    Req = #ocsp_status_request{responder_id_list  = OcspResponderList,
+                               request_extensions = OcspRequestExtns},
+    CertStatusReqExtn = #certificate_status_request{
+        status_type = ?CERTIFICATE_STATUS_TYPE_OCSP,
+        request = Req
+    },
+    HelloExtensions#{status_request => CertStatusReqExtn}.
+
+get_ocsp_responder_list(ResponderCerts) ->
+    get_ocsp_responder_list(ResponderCerts, []).
+
+get_ocsp_responder_list([], Acc) ->
+    Acc;
+get_ocsp_responder_list([ResponderCert | T], Acc) ->
+    get_ocsp_responder_list(
+        T, [public_key:ocsp_responder_id(ResponderCert) | Acc]).
 
 %% TODO: Add support for PSK key establishment
 
@@ -2652,6 +2729,31 @@ decode_extensions(<<?UINT16(?PRE_SHARED_
                                #pre_shared_key_server_hello{
                                   selected_identity = Identity}});
 
+%% RFC6066, if a server returns a "CertificateStatus" message, then
+%% the server MUST have included an extension of type "status_request"
+%% with empty "extension_data" in the extended server hello.
+decode_extensions(<<?UINT16(?STATUS_REQUEST), ?UINT16(Len),
+                    _ExtensionData:Len/binary, Rest/binary>>, Version,
+                    MessageType = server_hello, Acc)
+  when Len =:= 0 ->
+    decode_extensions(Rest, Version, MessageType,
+                      Acc#{status_request => undefined});
+%% RFC8446 4.4.2.1, In TLS1.3, the body of the "status_request" extension
+%% from the server MUST be a CertificateStatus structure as defined in
+%% RFC6066.
+decode_extensions(<<?UINT16(?STATUS_REQUEST), ?UINT16(Len),
+                    CertStatus:Len/binary, Rest/binary>>, Version,
+                    MessageType, Acc) ->
+    case CertStatus of
+        <<?BYTE(?CERTIFICATE_STATUS_TYPE_OCSP),
+          ?UINT24(OCSPLen),
+          ASN1OCSPResponse:OCSPLen/binary>> ->
+            decode_extensions(Rest, Version, MessageType,
+                      Acc#{status_request => ASN1OCSPResponse});
+        _Other ->
+            decode_extensions(Rest, Version, MessageType, Acc)
+    end;
+
 %% Ignore data following the ClientHello (i.e.,
 %% extensions) if not understood.
 decode_extensions(<<?UINT16(_), ?UINT16(Len), _Unknown:Len/binary, Rest/binary>>, Version, MessageType, Acc) ->
@@ -2766,6 +2868,7 @@ certs_from_list(ACList) ->
 			CertLen = byte_size(Cert),
                         <<?UINT24(CertLen), Cert/binary>>
 		    end || Cert <- ACList]).
+
 from_3bytes(Bin3) ->
     from_3bytes(Bin3, []).
 
Index: otp-OTP-22.3/lib/ssl/src/ssl_handshake.hrl
===================================================================
--- otp-OTP-22.3.orig/lib/ssl/src/ssl_handshake.hrl
+++ otp-OTP-22.3/lib/ssl/src/ssl_handshake.hrl
@@ -376,7 +376,7 @@
 -define(NAMED_CURVE, 3).
 
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% RFC 6066 Server name indication 
+%% RFC 6066 TLS Extensions: Extension Definitions
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 
 %% section 3
@@ -389,12 +389,32 @@
           hostname = undefined
         }).
 
+%% Section 8, Certificate Status Request
+-define(STATUS_REQUEST, 5).
+-define(CERTIFICATE_STATUS_TYPE_OCSP, 1).
+-define(CERTIFICATE_STATUS, 22).
+
+%% status request record defined in RFC 6066, section 8
+-record(certificate_status_request, {
+	status_type,
+	request
+}).
+
+-record(ocsp_status_request, {
+	responder_id_list = [],
+	request_extensions = []
+}).
+
+-record(certificate_status, {
+	status_type,
+	response
+}).
+
 %% Other possible values from RFC 6066, not supported
 -define(MAX_FRAGMENT_LENGTH, 1).
 -define(CLIENT_CERTIFICATE_URL, 2).
 -define(TRUSTED_CA_KEYS, 3).
 -define(TRUNCATED_HMAC, 4).
--define(STATUS_REQUEST, 5).
 
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 %% RFC 7250 Using Raw Public Keys in Transport Layer Security (TLS)
Index: otp-OTP-22.3/lib/ssl/src/ssl_internal.hrl
===================================================================
--- otp-OTP-22.3.orig/lib/ssl/src/ssl_internal.hrl
+++ otp-OTP-22.3/lib/ssl/src/ssl_internal.hrl
@@ -155,6 +155,14 @@
           max_handshake_size         => {?DEFAULT_MAX_HANDSHAKE_SIZE, [versions]},
           next_protocol_selector     => {undefined, [versions]},
           next_protocols_advertised  => {undefined, [versions]},
+          %% If enable OCSP stapling
+          ocsp_stapling              => {false, [versions]},
+          %% Optional arg, if give suggestion of OCSP responders
+          ocsp_responder_certs       => {[], [versions,
+                                              ocsp_stapling]},
+          %% Optional arg, if add nonce extension in request
+          ocsp_nonce                 => {true, [versions,
+                                                ocsp_stapling]},
           padding_check              => {true,      [versions]},
           partial_chain              => {fun(_) -> unknown_ca end, [versions]},
           password                   => {"",        [versions]},
@@ -184,15 +192,15 @@
           verify_client_once         => {false,     [versions]},
           verify_fun                 =>
               {
-               {fun(_,{bad_cert, _}, UserState) ->
+               {fun(_, {bad_cert, _}, UserState) ->
                         {valid, UserState};
-                   (_,{extension, #'Extension'{critical = true}}, UserState) ->
+                   (_, {extension, #'Extension'{critical = true}}, UserState) ->
                         %% This extension is marked as critical, so
                         %% certificate verification should fail if we don't
                         %% understand the extension.  However, this is
                         %% `verify_none', so let's accept it anyway.
                         {valid, UserState};
-                   (_,{extension, _}, UserState) ->
+                   (_, {extension, _}, UserState) ->
                         {unknown, UserState};
                    (_, valid, UserState) ->
                         {valid, UserState};
Index: otp-OTP-22.3/lib/ssl/src/ssl_logger.erl
===================================================================
--- otp-OTP-22.3.orig/lib/ssl/src/ssl_logger.erl
+++ otp-OTP-22.3/lib/ssl/src/ssl_logger.erl
@@ -180,6 +180,11 @@ parse_handshake(Direction, #certificate{
                            [header_prefix(Direction)]),
     Message = io_lib:format("~p", [?rec_info(certificate, Certificate)]),
     {Header, Message};
+parse_handshake(Direction, #certificate_status{} = CertificateStatus) ->
+    Header = io_lib:format("~s Handshake, CertificateStatus",
+                           [header_prefix(Direction)]),
+    Message = io_lib:format("~p", [?rec_info(certificate_status, CertificateStatus)]),
+    {Header, Message};
 parse_handshake(Direction, #server_key_exchange{} = ServerKeyExchange) ->
     Header = io_lib:format("~s Handshake, ServerKeyExchange",
                            [header_prefix(Direction)]),
Index: otp-OTP-22.3/lib/ssl/src/tls_connection.erl
===================================================================
--- otp-OTP-22.3.orig/lib/ssl/src/tls_connection.erl
+++ otp-OTP-22.3/lib/ssl/src/tls_connection.erl
@@ -577,24 +577,28 @@ init({call, From}, {start, Timeout},
             handshake_env = #handshake_env{renegotiation = {Renegotiation, _}} = HsEnv,
             connection_env = CEnv,
 	    ssl_options = #{log_level := LogLevel,
-                            %% Use highest version in initial ClientHello.
-                            %% Versions is a descending list of supported versions.
-                            versions := [HelloVersion|_] = Versions,
-                            session_tickets := SessionTickets} = SslOpts,
-	    session = NewSession,
-	    connection_states = ConnectionStates0
-	   } = State0) ->
+                        %% Use highest version in initial ClientHello.
+                        %% Versions is a descending list of supported versions.
+                        versions := [HelloVersion|_] = Versions,
+                        session_tickets := SessionTickets,
+                        ocsp_stapling := OcspStaplingOpt,
+                        ocsp_nonce := OcspNonceOpt} = SslOpts,
+	                    session = NewSession,
+	                    connection_states = ConnectionStates0
+	    } = State0) ->
     KeyShare = maybe_generate_client_shares(SslOpts),
     Session = ssl_session:client_select_session({Host, Port, SslOpts}, Cache, CacheCb, NewSession),
     %% Update UseTicket in case of automatic session resumption
     {UseTicket, State1} = tls_handshake_1_3:maybe_automatic_session_resumption(State0),
     TicketData = tls_handshake_1_3:get_ticket_data(self(), SessionTickets, UseTicket),
+    OcspNonce = tls_handshake:ocsp_nonce(OcspNonceOpt, OcspStaplingOpt),
     Hello = tls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts,
                                        Session#session.session_id,
                                        Renegotiation,
                                        Session#session.own_certificate,
                                        KeyShare,
-                                       TicketData),
+                                       TicketData,
+                                       OcspNonce),
 
     Handshake0 = ssl_handshake:init_handshake_history(),
 
@@ -615,13 +619,16 @@ init({call, From}, {start, Timeout},
     %% negotiated_version is also used by the TLS 1.3 state machine and is set after
     %% ServerHello is processed.
     RequestedVersion = tls_record:hello_version(Versions),
-    State = State1#state{connection_states = ConnectionStates,
-                         connection_env = CEnv#connection_env{
-                                            negotiated_version = RequestedVersion},
-                         session = Session,
-                         handshake_env = HsEnv#handshake_env{tls_handshake_history = Handshake},
-                         start_or_recv_from = From,
-                         key_share = KeyShare},
+    State2 = State1#state{connection_states = ConnectionStates,
+                          connection_env = CEnv#connection_env{
+                                             negotiated_version = RequestedVersion},
+                          session = Session,
+                          handshake_env = HsEnv#handshake_env{
+                              tls_handshake_history = Handshake},
+                          start_or_recv_from = From,
+                          key_share = KeyShare},
+    State = tls_handshake_1_3:update_ocsp_state(
+        OcspStaplingOpt, OcspNonce, State2),
     next_event(hello, no_record, State, [{{timeout, handshake}, Timeout, close}]);
 
 init(Type, Event, State) ->
@@ -656,11 +663,23 @@ hello(internal, #client_hello{extensions
                                          handshake_env = HsEnv#handshake_env{hello = Hello}},
      [{reply, From, {ok, Extensions}}]};
 hello(internal, #server_hello{extensions = Extensions} = Hello,
-      #state{ssl_options = #{handshake := hello},
+      #state{ssl_options = #{
+                 handshake := hello,
+                 ocsp_stapling := OcspStapling},
              handshake_env = HsEnv,
              start_or_recv_from = From} = State) ->
-    {next_state, user_hello, State#state{start_or_recv_from = undefined,
-                                         handshake_env = HsEnv#handshake_env{hello = Hello}},
+    %% RFC6066.8, If a server returns a "CertificateStatus" message,
+    %% then the server MUST have included an extension of type
+    %% "status_request" with empty "extension_data" in the extended
+    %% server hello.
+    OcspState = HsEnv#handshake_env.ocsp_stapling_state,
+    OcspNegotiated = is_ocsp_stapling_negotiated(OcspStapling, Extensions, State),
+    {next_state, user_hello,
+     State#state{start_or_recv_from = undefined,
+                 handshake_env = HsEnv#handshake_env{
+                     hello = Hello,
+                     ocsp_stapling_state = OcspState#{
+                         ocsp_negotiated => OcspNegotiated}}},
      [{reply, From, {ok, Extensions}}]};     
 
 hello(internal, #client_hello{client_version = ClientVersion} = Hello,
@@ -710,12 +729,19 @@ hello(internal, #client_hello{client_ver
             end
 
     end;
-hello(internal, #server_hello{} = Hello,      
+hello(internal, #server_hello{extensions = Extensions} = Hello,
       #state{connection_states = ConnectionStates0,
              connection_env = #connection_env{negotiated_version = ReqVersion} = CEnv,
 	     static_env = #static_env{role = client},
-             handshake_env = #handshake_env{renegotiation = {Renegotiation, _}},
+             handshake_env = #handshake_env{
+                 renegotiation = {Renegotiation, _},
+                 ocsp_stapling_state = OcspState} = HsEnv,
 	     ssl_options = SslOptions} = State) ->
+    %% check if server has sent an empty certifucate status message in hello
+    #{ocsp_stapling := OcspStapling} = SslOptions,
+    OcspNegotiated = is_ocsp_stapling_negotiated(OcspStapling, Extensions, State),
+
+
     case tls_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation) of
 	#alert{} = Alert -> %%TODO
 	    ssl_connection:handle_own_alert(Alert, ReqVersion, hello,
@@ -724,14 +750,19 @@ hello(internal, #server_hello{} = Hello,
         %% Legacy TLS 1.2 and older
 	{Version, NewId, ConnectionStates, ProtoExt, Protocol} ->
 	    ssl_connection:handle_session(Hello, 
-					  Version, NewId, ConnectionStates, ProtoExt, Protocol, State);
+					  Version, NewId, ConnectionStates, ProtoExt, Protocol,
+                      State#state{handshake_env = HsEnv#handshake_env{
+                          ocsp_stapling_state = OcspState#{
+                              ocsp_negotiated => OcspNegotiated}}});
         %% TLS 1.3
         {next_state, wait_sh, SelectedVersion} ->
             %% Continue in TLS 1.3 'wait_sh' state
             {next_state, wait_sh,
              State#state{
-               connection_env = CEnv#connection_env{negotiated_version = SelectedVersion}},
-             [{next_event, internal, Hello}]}
+               connection_env = CEnv#connection_env{negotiated_version = SelectedVersion},
+               handshake_env = HsEnv#handshake_env{
+                   ocsp_stapling_state = OcspState#{
+                       ocsp_negotiated => OcspNegotiated}}},             [{next_event, internal, Hello}]}
     end;
 hello(info, Event, State) ->
     handle_info(Event, ?FUNCTION_NAME, State);
@@ -821,7 +852,9 @@ connection(internal, #hello_request{},
                                            port = Port,
                                            session_cache = Cache,
                                            session_cache_cb = CacheCb},
-                  handshake_env = #handshake_env{renegotiation = {Renegotiation, peer}},
+                  handshake_env = #handshake_env{
+                      renegotiation = {Renegotiation, peer},
+                      ocsp_stapling_state = OcspState},
 		  session = #session{own_certificate = Cert} = Session0,
 		  ssl_options = SslOpts, 
                   protocol_specific = #{sender := Pid},
@@ -832,7 +865,7 @@ connection(internal, #hello_request{},
             Hello = tls_handshake:client_hello(Host, Port, ConnectionStates, SslOpts,
                                                Session#session.session_id,
                                                Renegotiation, Cert, undefined,
-                                               undefined),
+                                               undefined, maps:get(ocsp_nonce, OcspState, undefined)),
             {State, Actions} = send_handshake(Hello, State0#state{connection_states = ConnectionStates#{current_write => Write},
                                                                   session = Session}),
             next_event(hello, no_record, State, Actions)
@@ -844,13 +877,15 @@ connection(internal, #hello_request{},
 	   #state{static_env = #static_env{role = client,
                                            host = Host,
                                            port = Port},
-                  handshake_env = #handshake_env{renegotiation = {Renegotiation, _}},
+                  handshake_env = #handshake_env{
+                      renegotiation = {Renegotiation, _},
+                      ocsp_stapling_state = OcspState},
 		  session = #session{own_certificate = Cert},
 		  ssl_options = SslOpts, 
 		  connection_states = ConnectionStates} = State0) ->
     Hello = tls_handshake:client_hello(Host, Port, ConnectionStates, SslOpts,
                                        <<>>, Renegotiation, Cert, undefined,
-                                       undefined),
+                                       undefined, maps:get(ocsp_nonce, OcspState, undefined)),
 
     {State, Actions} = send_handshake(Hello, State0),
     next_event(hello, no_record, State, Actions);
@@ -1521,3 +1556,32 @@ send_ticket_data(User, NewSessionTicket,
                    timestamp => Timestamp,
                    ticket => NewSessionTicket},
     User ! {ssl, session_ticket, {SNI, erlang:term_to_binary(TicketData)}}.
+
+is_ocsp_stapling_negotiated(true, Extensions,
+                            #state{connection_env =
+                                   #connection_env{
+                                       negotiated_version = Version}
+                            } = State) ->
+    case maps:get(status_request, Extensions, false) of
+        undefined -> %% status_request in server hello is empty
+            true;
+        false -> %% status_request is missing (not negotiated)
+            false;
+        _Else ->
+            ssl_connection:handle_own_alert(
+                ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, status_request_not_empty),
+                Version, hello, State)
+    end;
+is_ocsp_stapling_negotiated(false, Extensions,
+                            #state{connection_env =
+                                   #connection_env{
+                                       negotiated_version = Version}
+                            } = State) ->
+    case maps:get(status_request, Extensions, false) of
+        false -> %% status_request is missing (not negotiated)
+            false;
+        _Else -> %% unsolicited status_request
+            ssl_connection:handle_own_alert(
+                ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, unexpected_status_request),
+                Version, hello, State)
+    end.
Index: otp-OTP-22.3/lib/ssl/src/tls_handshake.erl
===================================================================
--- otp-OTP-22.3.orig/lib/ssl/src/tls_handshake.erl
+++ otp-OTP-22.3/lib/ssl/src/tls_handshake.erl
@@ -36,7 +36,7 @@
 -include_lib("kernel/include/logger.hrl").
 
 %% Handshake handling
--export([client_hello/9, hello/4]).
+-export([client_hello/10, hello/4]).
 
 %% Handshake encoding
 -export([encode_handshake/2]).
@@ -44,6 +44,9 @@
 %% Handshake decodeing
 -export([get_tls_handshake/4, decode_handshake/3]).
 
+%% Handshake helper
+-export([ocsp_nonce/2]).
+
 -type tls_handshake() :: #client_hello{} | ssl_handshake:ssl_handshake().
 
 %%====================================================================
@@ -52,7 +55,8 @@
 %%--------------------------------------------------------------------
 -spec client_hello(ssl:host(), inet:port_number(), ssl_record:connection_states(),
 		   ssl_options(), binary(), boolean(), der_cert(),
-                   #key_share_client_hello{} | undefined, tuple() | undefined) ->
+                   #key_share_client_hello{} | undefined, tuple() | undefined,
+                   binary() | undefined) ->
 			  #client_hello{}.
 %%
 %% Description: Creates a client hello message.
@@ -62,7 +66,7 @@ client_hello(_Host, _Port, ConnectionSta
                ciphers := UserSuites,
                fallback := Fallback
               } = SslOpts,
-	     Id, Renegotiation, _OwnCert, KeyShare, TicketData) ->
+	     Id, Renegotiation, _OwnCert, KeyShare, TicketData, OcspNonce) ->
     Version = tls_record:highest_protocol_version(Versions),
 
     %% In TLS 1.3, the client indicates its version preferences in the
@@ -82,9 +86,10 @@ client_hello(_Host, _Port, ConnectionSta
     Extensions = ssl_handshake:client_hello_extensions(Version, 
 						       AvailableCipherSuites,
 						       SslOpts, ConnectionStates, 
-                                                       Renegotiation,
-                                                       KeyShare,
-                                                       TicketData),
+                               Renegotiation,
+                               KeyShare,
+                               TicketData,
+                               OcspNonce),
     CipherSuites = ssl_handshake:cipher_suites(AvailableCipherSuites, Renegotiation, Fallback),
     #client_hello{session_id = Id,
 		  client_version = LegacyVersion,
@@ -282,6 +287,20 @@ get_tls_handshake(Version, Data, Buffer,
     get_tls_handshake_aux(Version, list_to_binary([Buffer, Data]), Options, []).
 
 %%--------------------------------------------------------------------
+%%% Handshake helper
+%%--------------------------------------------------------------------
+
+%%--------------------------------------------------------------------
+-spec ocsp_nonce(boolean(), boolean()) -> binary() | undefined.
+%%
+%% Description: Get an OCSP nonce
+%%--------------------------------------------------------------------
+ocsp_nonce(true, true) ->
+    public_key:ocsp_nonce();
+ocsp_nonce(_OcspNonceOpt, _OcspStaplingOpt) ->
+    undefined.
+
+%%--------------------------------------------------------------------
 %%% Internal functions
 %%--------------------------------------------------------------------
 handle_client_hello(Version, 
Index: otp-OTP-22.3/lib/ssl/src/tls_handshake_1_3.erl
===================================================================
--- otp-OTP-22.3.orig/lib/ssl/src/tls_handshake_1_3.erl
+++ otp-OTP-22.3/lib/ssl/src/tls_handshake_1_3.erl
@@ -55,7 +55,8 @@
          maybe_add_binders/4,
          maybe_automatic_session_resumption/1]).
 
--export([is_valid_binder/4]).
+-export([is_valid_binder/4,
+         update_ocsp_state/3]).
 
 %% crypto:hash(sha256, "HelloRetryRequest").
 -define(HELLO_RETRY_REQUEST_RANDOM, <<207,33,173,116,229,154,97,17,
@@ -650,7 +651,9 @@ do_start(#server_hello{cipher_suite = Se
                                 supported_groups := ClientGroups0,
                                 use_ticket := UseTicket,
                                 session_tickets := SessionTickets,
-                                log_level := LogLevel} = SslOpts,
+                                log_level := LogLevel,
+                                ocsp_stapling := OcspStaplingOpt,
+                                ocsp_nonce := OcspNonceOpt} = SslOpts,
                 session = #session{own_certificate = Cert} = Session0,
                 connection_states = ConnectionStates0
                } = State0) ->
@@ -717,6 +720,19 @@ do_start(#server_hello{cipher_suite = Se
     end.
 
 
+%%--------------------------------------------------------------------
+-spec update_ocsp_state(boolean(), binary() | undefined, #state{}) -> #state{}.
+%%
+%% Description: Update OCSP state in #state{}
+%%--------------------------------------------------------------------
+update_ocsp_state(true, OcspNonce, #state{handshake_env = #handshake_env{
+    ocsp_stapling_state = OcspState} = HsEnv} = State) ->
+    State#state{handshake_env = HsEnv#handshake_env{
+        ocsp_stapling_state = OcspState#{ocsp_nonce => OcspNonce}}};
+update_ocsp_state(false, _OcspNonce, State) ->
+    State.
+
+
 do_negotiated({start_handshake, PSK0},
               #state{connection_states = ConnectionStates0,
                      session = #session{session_id = SessionId,
Index: otp-OTP-22.3/lib/ssl/test/Makefile
===================================================================
--- otp-OTP-22.3.orig/lib/ssl/test/Makefile
+++ otp-OTP-22.3/lib/ssl/test/Makefile
@@ -90,7 +90,8 @@ MODULES = \
 	ssl_socket_SUITE\
 	make_certs \
         x509_test \
-	inet_crypto_dist
+	inet_crypto_dist \
+	ssl_ocsp_SUITE
 
 
 ERL_FILES = $(MODULES:%=%.erl)
Index: otp-OTP-22.3/lib/ssl/test/ssl_basic_SUITE.erl
===================================================================
--- otp-OTP-22.3.orig/lib/ssl/test/ssl_basic_SUITE.erl
+++ otp-OTP-22.3/lib/ssl/test/ssl_basic_SUITE.erl
@@ -76,7 +76,7 @@ init_per_suite(Config0) ->
     try crypto:start() of
 	ok ->
 	    ssl_test_lib:clean_start(),
-	    %% make rsa certs using oppenssl
+	    %% make rsa certs using openssl
 	    {ok, _} = make_certs:all(proplists:get_value(data_dir, Config0),
 				     proplists:get_value(priv_dir, Config0)),
 	    Config1 = ssl_test_lib:make_dsa_cert(Config0),
Index: otp-OTP-22.3/lib/ssl/test/ssl_ocsp_SUITE.erl
===================================================================
--- /dev/null
+++ otp-OTP-22.3/lib/ssl/test/ssl_ocsp_SUITE.erl
@@ -0,0 +1,367 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2011-2020. 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.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(ssl_ocsp_SUITE).
+
+-include_lib("common_test/include/ct.hrl").
+-include_lib("public_key/include/public_key.hrl").
+
+%% Note: This directive should only be used in test suites.
+-compile(export_all).
+
+%%--------------------------------------------------------------------
+%% Common Test interface functions -----------------------------------
+%%--------------------------------------------------------------------
+all() -> 
+    [{group, tls1_2},
+     {group, tls1_3},
+     {group, revoked_1_2},
+     {group, revoked_1_3}].
+
+groups() -> 
+    [{tls1_2, [], tls1_2_tests()},
+     {tls1_3, [], tls1_3_tests()},
+     {revoked_1_2, [], tls1_2_revoked_tests()},
+     {revoked_1_3, [], tls1_3_revoked_tests()}].
+
+tls1_2_tests() ->
+    [ocsp_stapling_without_nonce_and_responder_certs_tls1_2,
+     ocsp_stapling_with_nonce_tls1_2,
+     ocsp_stapling_with_responder_cert_tls1_2].
+
+tls1_2_revoked_tests() ->
+    [ocsp_stapling_revoked_tls1_2].
+
+tls1_3_tests() ->
+    [ocsp_stapling_without_nonce_and_responder_certs_tls1_3,
+     ocsp_stapling_with_nonce_and_responder_certs_tls1_3].
+
+tls1_3_revoked_tests() ->
+    [ocsp_stapling_revoked_tls1_3].
+
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+    catch crypto:stop(),
+    try crypto:start() of
+	ok ->
+        ssl_test_lib:clean_start(),
+        DataDir = proplists:get_value(data_dir, Config),
+        PrivDir = proplists:get_value(priv_dir, Config),
+
+        %% Prepare certs
+        {ok, _} = make_certs:all(DataDir, PrivDir),
+
+        ResponderPort = get_free_port(),
+        Pid =
+            start_ocsp_responder(
+                erlang:integer_to_list(ResponderPort), PrivDir),
+
+        NewConfig =
+        lists:merge(
+            [{responder_port, ResponderPort},
+             {responder_pid, Pid}
+            ], Config),
+
+	    ssl_test_lib:cert_options(NewConfig)
+    catch _:_ ->
+	    {skip, "Crypto did not start"}
+    end.
+
+
+end_per_suite(Config) ->
+    ResponderPid = proplists:get_value(responder_pid, Config),
+    stop_ocsp_responder(ResponderPid),
+    ok = ssl:stop(),
+    application:stop(crypto).
+
+%%--------------------------------------------------------------------
+init_per_group(tls1_2, Config) ->
+    setup_tls_server_for_group(tls1_2, Config);
+init_per_group(tls1_3, Config) ->
+    setup_tls_server_for_group(tls1_3, Config);
+init_per_group(revoked_1_2, Config) ->
+    setup_tls_server_for_group(revoked_1_2, Config);
+init_per_group(revoked_1_3, Config) ->
+    setup_tls_server_for_group(revoked_1_3, Config);
+init_per_group(_GroupName, Config) ->
+    Config.
+
+end_per_group(_GroupName, Config) ->
+    Pid = proplists:get_value(server_pid, Config),
+    stop_tls_server(Pid),
+    Config.
+
+%%--------------------------------------------------------------------
+init_per_testcase(_TestCase, Config) ->
+    Config.
+
+end_per_testcase(_TestCase, Config) ->
+    Config.
+
+%%--------------------------------------------------------------------
+%% Test Cases --------------------------------------------------------
+%%--------------------------------------------------------------------
+
+ocsp_stapling_without_nonce_and_responder_certs_tls1_2() ->
+    [{doc, "Verify OCSP stapling works without nonce "
+           "and responder certs for tls1.2."}].
+ocsp_stapling_without_nonce_and_responder_certs_tls1_2(Config)
+  when is_list(Config) ->
+    Port = proplists:get_value(server_port, Config),
+    {ok, Sock} =
+    ssl:connect({127,0,0,1}, Port, proplists:get_value(client_opts, Config) ++
+        [{keepalive, true},
+         {versions, ['tlsv1.2']},
+         {ocsp_stapling, true},
+         {ocsp_nonce, false},
+         {log_level, debug}], 5000),
+    ok = ssl:send(Sock, <<"ok">>),
+    ssl:close(Sock).
+
+ocsp_stapling_with_nonce_tls1_2() ->
+    [{doc, "Verify OCSP stapling works with nonce "
+           "for tls1.2."}].
+ocsp_stapling_with_nonce_tls1_2(Config)
+  when is_list(Config) ->
+    Port = proplists:get_value(server_port, Config),
+    {ok, Sock} =
+    ssl:connect({127,0,0,1}, Port, proplists:get_value(client_opts, Config) ++
+        [{keepalive, true},
+         {versions, ['tlsv1.2']},
+         {ocsp_stapling, true},
+         {log_level, debug}], 5000),
+    ok = ssl:send(Sock, <<"ok">>),
+    ssl:close(Sock).
+
+ocsp_stapling_with_responder_cert_tls1_2() ->
+    [{doc, "Verify OCSP stapling works with OCSP responder cert "
+           "for tls1.2."}].
+ocsp_stapling_with_responder_cert_tls1_2(Config)
+  when is_list(Config) ->
+    Port = proplists:get_value(server_port, Config),
+    PrivDir = proplists:get_value(priv_dir, Config),
+    {ok, ResponderCert} =
+        file:read_file(filename:join(PrivDir, "b.server/cert.pem")),
+    [{'Certificate', Der, _IsEncrypted}] =
+        public_key:pem_decode(ResponderCert),
+    {ok, Sock} =
+    ssl:connect({127,0,0,1}, Port, proplists:get_value(client_opts, Config) ++
+        [{keepalive, true},
+         {versions, ['tlsv1.2']},
+         {ocsp_stapling, true},
+         {ocsp_responder_certs, [Der]},
+         {log_level, debug}], 5000),
+    ok = ssl:send(Sock, <<"ok">>),
+    ssl:close(Sock).
+
+ocsp_stapling_revoked_tls1_2() ->
+    [{doc, "Verify OCSP stapling works for revoked cert scenario "
+           "for tls1.2."}].
+ocsp_stapling_revoked_tls1_2(Config)
+  when is_list(Config) ->
+    Port = proplists:get_value(server_port, Config),
+    PrivDir = proplists:get_value(priv_dir, Config),
+    {ok, ResponderCert} =
+        file:read_file(filename:join(PrivDir, "b.server/cert.pem")),
+    [{'Certificate', Der, _IsEncrypted}] =
+        public_key:pem_decode(ResponderCert),
+    {error, {tls_alert, {bad_certificate_status_response, _Info}}} =
+    ssl:connect({127,0,0,1}, Port, proplists:get_value(client_opts, Config) ++
+        [{keepalive, true},
+         {versions, ['tlsv1.2']},
+         {ocsp_stapling, true},
+         {ocsp_responder_certs, [Der]},
+         {log_level, debug}], 5000).
+
+
+ocsp_stapling_without_nonce_and_responder_certs_tls1_3() ->
+    [{doc, "Verify OCSP stapling works without nonce "
+           "and responder certs for tls1.3."}].
+ocsp_stapling_without_nonce_and_responder_certs_tls1_3(Config)
+  when is_list(Config) ->
+    Port = proplists:get_value(server_port, Config),
+    {ok, Sock} =
+    ssl:connect({127,0,0,1}, Port, proplists:get_value(client_opts, Config) ++
+        [{keepalive, true},
+         {versions, ['tlsv1.3']},
+         {ocsp_stapling, true},
+         {ocsp_nonce, false},
+         {log_level, debug}], 5000),
+    ok = ssl:send(Sock, <<"ok">>),
+    ssl:close(Sock).
+
+ocsp_stapling_with_nonce_and_responder_certs_tls1_3() ->
+    [{doc, "Verify OCSP stapling works with nonce "
+           "and responder certs for tls1.3."}].
+ocsp_stapling_with_nonce_and_responder_certs_tls1_3(Config)
+  when is_list(Config) ->
+    Port = proplists:get_value(server_port, Config),
+    PrivDir = proplists:get_value(priv_dir, Config),
+    {ok, ResponderCert} =
+        file:read_file(filename:join(PrivDir, "b.server/cert.pem")),
+    [{'Certificate', Der, _IsEncrypted}] =
+        public_key:pem_decode(ResponderCert),
+    {ok, Sock} =
+    ssl:connect({127,0,0,1}, Port, proplists:get_value(client_opts, Config) ++
+        [{keepalive, true},
+         {versions, ['tlsv1.3']},
+         {ocsp_stapling, true},
+         {ocsp_responder_certs, [Der]},
+         {log_level, debug}], 5000),
+    ok = ssl:send(Sock, <<"ok">>),
+    ssl:close(Sock).
+
+ocsp_stapling_revoked_tls1_3() ->
+    [{doc, "Verify OCSP stapling works for revoked cert scenario "
+           "for tls1.3."}].
+ocsp_stapling_revoked_tls1_3(Config)
+  when is_list(Config) ->
+    Port = proplists:get_value(server_port, Config),
+    {error, {tls_alert, {bad_certificate_status_response, _Info}}} =
+    ssl:connect({127,0,0,1}, Port, proplists:get_value(client_opts, Config) ++
+        [{keepalive, true},
+         {versions, ['tlsv1.3']},
+         {ocsp_stapling, true},
+         {log_level, debug}], 5000).
+
+%%--------------------------------------------------------------------
+%% Intrernal functions -----------------------------------------------
+%%--------------------------------------------------------------------
+start_ocsp_responder(ResponderPort, PrivDir) ->
+    erlang:spawn(
+        ?MODULE, do_start_ocsp_responder, [ResponderPort, PrivDir]).
+
+do_start_ocsp_responder(ResponderPort, PrivDir) ->
+    Index = filename:join(PrivDir, "otpCA/index.txt"),
+    CACerts = filename:join(PrivDir, "b.server/cacerts.pem"),
+    Cert = filename:join(PrivDir, "b.server/cert.pem"),
+    Key = filename:join(PrivDir, "b.server/key.pem"),
+
+    Args = ["ocsp", "-index", Index, "-CA", CACerts, "-rsigner", Cert,
+            "-rkey", Key, "-port", ResponderPort],
+    process_flag(trap_exit, true),
+    SSLPort = ssl_test_lib:portable_open_port("openssl", Args),
+    true = port_command(SSLPort, "Hello world"),
+    ocsp_responder_loop(SSLPort).
+
+ocsp_responder_loop(SSLPort) ->
+    receive
+	stop_ocsp_responder ->
+	    ct:log("Shut down OCSP responder!~n"),
+        ok = ssl_test_lib:close_port(SSLPort);
+	{_Port, closed} ->
+	    ct:log("Port Closed~n"),
+	    ok;
+	{'EXIT', _Port, Reason} ->
+	    ct:log("Port Closed ~p~n",[Reason]),
+	    ok;
+	Msg ->
+	    ct:log("Port Msg ~p~n",[Msg]),
+	    ocsp_responder_loop(SSLPort)
+    after 600000 ->
+        ssl_test_lib:close_port(SSLPort)
+    end.
+
+stop_ocsp_responder(Pid) ->
+    Pid ! stop_ocsp_responder.
+
+start_tls_server(Version, ResponderPort, ServerPort, PrivDir) ->
+    erlang:spawn(
+        ?MODULE, do_start_tls_server,
+        [Version, ResponderPort, ServerPort, PrivDir]).
+
+do_start_tls_server(revoked_1_2, ResponderPort, ServerPort, PrivDir) ->
+    Cert = filename:join(PrivDir, "revoked/cert.pem"),
+    Key = filename:join(PrivDir, "revoked/key.pem"),
+    CACerts = filename:join(PrivDir, "revoked/cacerts.pem"),
+
+    Args = ["s_server", "-cert", Cert, "-port", ServerPort, "-key", Key,
+            "-CAfile", CACerts, "-status_verbose", "-status_url",
+            "http://127.0.0.1:" ++ ResponderPort,
+            "-tls1_2"] ++ ["-msg", "-debug"],
+    process_flag(trap_exit, true),
+    SSLPort = ssl_test_lib:portable_open_port("openssl", Args),
+    true = port_command(SSLPort, "Hello world"),
+    tls_server_loop(SSLPort);
+do_start_tls_server(revoked_1_3, ResponderPort, ServerPort, PrivDir) ->
+    Cert = filename:join(PrivDir, "revoked/cert.pem"),
+    Key = filename:join(PrivDir, "revoked/key.pem"),
+    CACerts = filename:join(PrivDir, "revoked/cacerts.pem"),
+
+    Args = ["s_server", "-cert", Cert, "-port", ServerPort, "-key", Key,
+            "-CAfile", CACerts, "-status_verbose", "-status_url",
+            "http://127.0.0.1:" ++ ResponderPort,
+            "-tls1_3"] ++ ["-msg", "-debug"],
+    process_flag(trap_exit, true),
+    SSLPort = ssl_test_lib:portable_open_port("openssl", Args),
+    true = port_command(SSLPort, "Hello world"),
+    tls_server_loop(SSLPort);
+do_start_tls_server(Version, ResponderPort, ServerPort, PrivDir) ->
+    Cert = filename:join(PrivDir, "a.server/cert.pem"),
+    Key = filename:join(PrivDir, "a.server/key.pem"),
+    CACerts = filename:join(PrivDir, "a.server/cacerts.pem"),
+
+    Args = ["s_server", "-cert", Cert, "-port", ServerPort, "-key", Key,
+            "-CAfile", CACerts, "-status_verbose", "-status_url",
+            "http://127.0.0.1:" ++ ResponderPort,
+            "-" ++ atom_to_list(Version)] ++ ["-msg", "-debug"],
+    process_flag(trap_exit, true),
+    SSLPort = ssl_test_lib:portable_open_port("openssl", Args),
+    true = port_command(SSLPort, "Hello world"),
+    tls_server_loop(SSLPort).
+
+tls_server_loop(SSLPort) ->
+    receive
+	stop_tls_server ->
+	    ct:log("Shut down TLS responder!~n"),
+        ok = ssl_test_lib:close_port(SSLPort);
+	{_Port, closed} ->
+	    ct:log("Port Closed~n"),
+	    ok;
+	{'EXIT', _Port, Reason} ->
+	    ct:log("Port Closed ~p~n",[Reason]),
+	    ok;
+	Msg ->
+	    ct:log("Port Msg ~p~n",[Msg]),
+	    tls_server_loop(SSLPort)
+    after 600000 ->
+        ssl_test_lib:close_port(SSLPort)
+    end.
+
+stop_tls_server(Pid) ->
+    Pid ! stop_tls_server.
+
+get_free_port() ->
+    {ok, Listen} = gen_tcp:listen(0, [{reuseaddr, true}]),
+    {ok, Port} = inet:port(Listen),
+    ok = gen_tcp:close(Listen),
+    Port.
+
+setup_tls_server_for_group(Group, Config) ->
+    ResponderPort = proplists:get_value(responder_port, Config),
+    PrivDir = proplists:get_value(priv_dir, Config),
+    Port = get_free_port(),
+    Pid = start_tls_server(
+        Group, erlang:integer_to_list(ResponderPort),
+        erlang:integer_to_list(Port), PrivDir),
+    lists:merge(
+        [{server_port, Port},
+         {server_pid, Pid}
+        ], Config).
