File feature-upstream-ocsp.patch of Package erlang.30223
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).