Skip to content

Commit

Permalink
ssl: stapling and OCSP implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
u3s committed Jan 29, 2024
1 parent 2b1a742 commit 8e20f46
Showing 27 changed files with 538 additions and 414 deletions.
35 changes: 13 additions & 22 deletions lib/ssl/doc/src/ssl.xml
Original file line number Diff line number Diff line change
@@ -1272,28 +1272,19 @@ fun(srp, Username :: binary(), UserState :: term()) ->
</desc>
</datatype>

<!-- <datatype> -->
<!-- <name name="ocsp_stapling"/> -->
<!-- <desc><p>If true, OCSP stapling will be enabled, an extension of type "status_request" will be -->
<!-- included in the client hello to indicate the desire to receive certificate status information. -->
<!-- If false (the default), OCSP stapling will be disabled.</p> -->
<!-- </desc> -->
<!-- </datatype> -->

<!-- <datatype> -->
<!-- <name name="ocsp_responder_certs"/> -->
<!-- <desc><p>This option is a list of DER encoded certificates of OCSP responders which the client -->
<!-- trusts. This option is an empty list by default which means that the responders are implicitly -->
<!-- known to the server.</p> -->
<!-- </desc> -->
<!-- </datatype> -->

<!-- <datatype> -->
<!-- <name name="ocsp_nonce"/> -->
<!-- <desc><p>If true (the default), the nonce will be included as one of the request extensions in the -->
<!-- request. If false, nonce will not present in the request extensions.</p> -->
<!-- </desc> -->
<!-- </datatype> -->
<datatype>
<name name="stapling"/>
<desc><p>If <c>staple</c> or a map, OCSP stapling will be enabled, an
extension of type "status_request" will be included in the
client hello to indicate the desire to receive certificate
status information. If <c>no_staple</c> (the default), OCSP stapling will
be disabled.</p>

<p>When map is used, boolean ocsp_nonce key may indicate if
OCSP nonce should be requested by the client (default is
<c>true</c>).</p>
</desc>
</datatype>

</datatypes>
<datatypes>
12 changes: 6 additions & 6 deletions lib/ssl/doc/src/standards_compliance.xml
Original file line number Diff line number Diff line change
@@ -309,8 +309,8 @@
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle">status_request (RFC6066)</cell>
<cell align="left" valign="middle"><em>NC</em></cell>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle"><em>C</em></cell>
<cell align="left" valign="middle"><em>27.0</em></cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
@@ -1473,8 +1473,8 @@
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle">status_request (RFC6066)</cell>
<cell align="left" valign="middle"><em>NC</em></cell>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle"><em>C</em></cell>
<cell align="left" valign="middle">27.0</cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
@@ -1509,8 +1509,8 @@
</url>
</cell>
<cell align="left" valign="middle"><em>Client</em></cell>
<cell align="left" valign="middle"><em>NC</em></cell>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle"><em>PC</em></cell>
<cell align="left" valign="middle"><em>27.0</em></cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
Original file line number Diff line number Diff line change
@@ -1,5 +1,53 @@
# Notes on the PEM and cert caches
## Data relations
# ssl dev notes
## client-side OCSP stapling
1. stapling - is ssl option holding configuration provided by user
- ocsp_nonce :: true|false
2. stapling_state - holds handshake process data
- status :: not_negotiated | negotiated | not_received | received_staple
- ocsp_nonce :: binary()
3. stapling_info - holds date required for verifying the certificate chain

```mermaid
classDiagram
stapling .. stapling_state
stapling_state ..* stapling_info
stapling: ocsp_nonce
note for stapling "- stapling option is a boolean or a map\n- map is interpreted as stapling enabled\n- ocsp_nonce is boolean"
stapling_state: configured
stapling_state: ocsp_nonce
note for stapling_state "ocsp_nonce is random binary"
stapling_state: status
stapling_state: response
stapling_info: cert_ext #{SubjectId => Status}
```
## ssl test certificates
- test certificates are generated by `ssl/test/make_certs.erl/`

```mermaid
---
title: Test certs
---
flowchart RL
localhost["`2:localhost
3:localhost`"] --> erlangCA[["BIG_RAND_SERIAL:erlangCA"]]
otpCA[[1:otpCA]] --> erlangCA
client["`1:client
2:client`"] --> otpCA
server["`3:server
4:server`"] --> otpCA
aserver["`9:a.server
10:a.server`"] --> otpCA
bserver["`11:b.server
12:b.server`"] --> otpCA
revoked["`5:revoked
6:revoked`"] --> otpCA
undetermined["`7:undetermined
8:undetermined`"] --> otpCA
```

## Notes on the PEM and cert caches
### Data relations

|---------------| |------------------------|
| PemCache | | CertDb |
@@ -17,21 +65,21 @@
| Ref (FK) | | Counter |
|-----------------| |------------|

### PemCache
#### PemCache
1. stores a copy of file content in memory
2. includes files from cacertfile, certfile, keyfile options
3. content is added unless FileMapDb table contains entry with specified path

### FileMapDb
#### FileMapDb
1. holds relation between specific path (PEM file with CA certificates) and a ref
2. ref is generated when file from path is added for 1st time
3. ref is used as path identifier in CertDb and RefDb tables

### RefDb
#### RefDb
1. holds an active connections counter for a specific ref
2. when counter reaches zero - related data in CertDb, FileMapDb, RefDb is deleted

### CertDb
#### CertDb
1. holds decoded CA ceritificates (only those taken from cacertfile option)
2. used for building certificate chains
3. it is an ETS set table - when iterating in search of Issuer certificate,
24 changes: 12 additions & 12 deletions lib/ssl/src/dtls_connection.erl
Original file line number Diff line number Diff line change
@@ -88,7 +88,7 @@
%% | Abbrev Flight 1 to Abbrev Flight 2 part 1
%% |
%% New session | Resumed session
%% WAIT_OCSP_STAPLING CERTIFY <----------------------------------> ABBREVIATED
%% WAIT_STAPLING CERTIFY <----------------------------------> ABBREVIATED
%%
%% <- Possibly Receive -- | |
%% OCSP Stapel ------> | Send/ Recv Flight 5 |
@@ -142,7 +142,7 @@
downgrade/3,
hello/3,
user_hello/3,
wait_ocsp_stapling/3,
wait_stapling/3,
certify/3,
wait_cert_verify/3,
cipher/3,
@@ -304,7 +304,7 @@ hello(internal, #hello_verify_request{cookie = Cookie},
host = Host,
port = Port},
handshake_env = #handshake_env{renegotiation = {Renegotiation, _},
ocsp_stapling_state = OcspState0} = HsEnv,
stapling_state = StaplingState0} = HsEnv,
connection_env = CEnv,
ssl_options = SslOpts,
session = #session{session_id = Id},
@@ -320,7 +320,7 @@ hello(internal, #hello_verify_request{cookie = Cookie},
State0#state{handshake_env =
HsEnv#handshake_env{
tls_handshake_history = ssl_handshake:init_handshake_history(),
ocsp_stapling_state = OcspState0#{ocsp_nonce => OcspNonce}}}),
stapling_state = StaplingState0#{ocsp_nonce => OcspNonce}}}),
{State2, Actions} = dtls_gen_connection:send_handshake(Hello, State1),
State = State2#state{connection_env =
CEnv#connection_env{negotiated_version = Version}, % RequestedVersion
@@ -365,18 +365,18 @@ hello(internal, #server_hello{} = Hello,
static_env = #static_env{role = client},
handshake_env = #handshake_env{
renegotiation = {Renegotiation, _},
ocsp_stapling_state = OcspState0} = HsEnv,
stapling_state = StaplingState0} = HsEnv,
connection_states = ConnectionStates0,
session = #session{session_id = OldId},
ssl_options = SslOptions} = State) ->
try
{Version, NewId, ConnectionStates, ProtoExt, Protocol, OcspState} =
{Version, NewId, ConnectionStates, ProtoExt, Protocol, StaplingState} =
dtls_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation, OldId),
tls_dtls_connection:handle_session(
Hello, Version, NewId, ConnectionStates, ProtoExt, Protocol,
State#state{handshake_env =
HsEnv#handshake_env{
ocsp_stapling_state = maps:merge(OcspState0,OcspState)}})
stapling_state = maps:merge(StaplingState0,StaplingState)}})
catch throw:#alert{} = Alert ->
ssl_gen_statem:handle_own_alert(Alert, ?FUNCTION_NAME, State)
end;
@@ -430,17 +430,17 @@ abbreviated(Type, Event, State) ->
gen_handshake(?FUNCTION_NAME, Type, Event, State).

%%--------------------------------------------------------------------
-spec wait_ocsp_stapling(gen_statem:event_type(), term(), #state{}) ->
-spec wait_stapling(gen_statem:event_type(), term(), #state{}) ->
gen_statem:state_function_result().
%%--------------------------------------------------------------------
wait_ocsp_stapling(enter, _Event, State0) ->
wait_stapling(enter, _Event, State0) ->
{State, Actions} = handle_flight_timer(State0),
{keep_state, State, Actions};
wait_ocsp_stapling(info, Event, State) ->
wait_stapling(info, Event, State) ->
gen_info(Event, ?FUNCTION_NAME, State);
wait_ocsp_stapling(state_timeout, Event, State) ->
wait_stapling(state_timeout, Event, State) ->
handle_state_timeout(Event, ?FUNCTION_NAME, State);
wait_ocsp_stapling(Type, Event, State) ->
wait_stapling(Type, Event, State) ->
gen_handshake(?FUNCTION_NAME, Type, Event, State).

%%--------------------------------------------------------------------
4 changes: 2 additions & 2 deletions lib/ssl/src/dtls_handshake.erl
Original file line number Diff line number Diff line change
@@ -226,12 +226,12 @@ handle_client_hello_extensions(Version, Type, Random, CipherSuites,
handle_server_hello_extensions(Version, SessionId, Random, CipherSuite,
HelloExt, SslOpt, ConnectionStates0,
Renegotiation, IsNew) ->
{ConnectionStates, ProtoExt, Protocol, OcspState} =
{ConnectionStates, ProtoExt, Protocol, StaplingState} =
ssl_handshake:handle_server_hello_extensions(
dtls_record, Random, CipherSuite, HelloExt,
dtls_v1:corresponding_tls_version(Version), SslOpt, ConnectionStates0,
Renegotiation, IsNew),
{Version, SessionId, ConnectionStates, ProtoExt, Protocol, OcspState}.
{Version, SessionId, ConnectionStates, ProtoExt, Protocol, StaplingState}.

%%--------------------------------------------------------------------

2 changes: 1 addition & 1 deletion lib/ssl/src/ssl.app.src
Original file line number Diff line number Diff line change
@@ -88,6 +88,6 @@
{applications, [crypto, public_key, kernel, stdlib]},
{env, []},
{mod, {ssl_app, []}},
{runtime_dependencies, ["stdlib-4.1","public_key-@OTP-18876@","kernel-9.0",
{runtime_dependencies, ["stdlib-4.1","public_key-@OTP-18876:OTP-18606@","kernel-9.0",
"erts-@OTP-18941@","crypto-5.0", "inets-5.10.7",
"runtime_tools-1.15.1"]}]}.
69 changes: 28 additions & 41 deletions lib/ssl/src/ssl.erl
Original file line number Diff line number Diff line change
@@ -434,10 +434,8 @@
{session_tickets, client_session_tickets()} |
{use_ticket, use_ticket()} |
{early_data, client_early_data()} |
{use_srtp, use_srtp()}.
%% {ocsp_stapling, ocsp_stapling()} |
%% {ocsp_responder_certs, ocsp_responder_certs()} |
%% {ocsp_nonce, ocsp_nonce()}.
{use_srtp, use_srtp()} |
{stapling, stapling()}.

-type client_verify_type() :: verify_type().
-type client_reuse_session() :: session_id() | {session_id(), SessionData::binary()}.
@@ -459,9 +457,7 @@
-type max_fragment_length() :: undefined | 512 | 1024 | 2048 | 4096.
-type fallback() :: boolean().
-type ssl_imp() :: new | old.
%% -type ocsp_stapling() :: boolean().
%% -type ocsp_responder_certs() :: [public_key:der_encoded()].
%% -type ocsp_nonce() :: boolean().
-type stapling() :: staple | no_staple | map().

%% -------------------------------------------------------------------------------------------------------

@@ -1645,7 +1641,7 @@ ssl_options() ->
middlebox_comp_mode,
max_fragment_length,
next_protocol_selector, next_protocols_advertised,
ocsp_stapling, ocsp_responder_certs, ocsp_nonce,
stapling,
padding_check,
partial_chain,
password,
@@ -1687,7 +1683,7 @@ process_options(UserSslOpts, SslOpts0, Env) ->
SslOpts2 = opt_verification(UserSslOptsMap, SslOpts1, Env),
SslOpts3 = opt_certs(UserSslOptsMap, SslOpts2, Env),
SslOpts4 = opt_tickets(UserSslOptsMap, SslOpts3, Env),
SslOpts5 = opt_ocsp(UserSslOptsMap, SslOpts4, Env),
SslOpts5 = opt_stapling(UserSslOptsMap, SslOpts4, Env),
SslOpts6 = opt_sni(UserSslOptsMap, SslOpts5, Env),
SslOpts7 = opt_signature_algs(UserSslOptsMap, SslOpts6, Env),
SslOpts8 = opt_alpn(UserSslOptsMap, SslOpts7, Env),
@@ -2073,33 +2069,30 @@ opt_tickets(UserOpts, #{versions := Versions} = Opts, #{role := server}) ->
Opts#{session_tickets => SessionTickets, early_data => EarlyData,
anti_replay => AntiReplay, stateless_tickets_seed => STS}.

opt_ocsp(UserOpts, #{versions := _Versions} = Opts, #{role := Role}) ->
{Stapling, SMap} =
case get_opt(ocsp_stapling, ?DEFAULT_OCSP_STAPLING, UserOpts, Opts) of
{old, Map} when is_map(Map) -> {true, Map};
{_, Bool} when is_boolean(Bool) -> {Bool, #{}};
{_, Value} -> option_error(ocsp_stapling, Value)
opt_stapling(UserOpts, #{versions := _Versions} = Opts, #{role := client}) ->
{Stapling, Nonce} =
case get_opt(stapling, ?DEFAULT_STAPLING_OPT, UserOpts, Opts) of
{old, StaplingMap} when is_map(StaplingMap) ->
{true, maps:get(ocsp_nonce, StaplingMap, ?DEFAULT_OCSP_NONCE_OPT)};
{_, staple} ->
{true, ?DEFAULT_OCSP_NONCE_OPT};
{_, no_staple} ->
{false, ignore};
{_, Map} when is_map(Map) ->
{true, maps:get(ocsp_nonce, Map, ?DEFAULT_OCSP_NONCE_OPT)};
{_, Value} ->
option_error(stapling, Value)
end,
assert_client_only(Role, Stapling, ocsp_stapling),
{_, Nonce} = get_opt_bool(ocsp_nonce, ?DEFAULT_OCSP_NONCE, UserOpts, SMap),
option_incompatible(Stapling =:= false andalso Nonce =:= false,
[{ocsp_nonce, false}, {ocsp_stapling, false}]),
{_, ORC} = get_opt_list(ocsp_responder_certs, ?DEFAULT_OCSP_RESPONDER_CERTS,
UserOpts, SMap),
CheckBinary = fun(Cert) when is_binary(Cert) -> ok;
(_Cert) -> option_error(ocsp_responder_certs, ORC)
end,
[CheckBinary(C) || C <- ORC],
option_incompatible(Stapling =:= false andalso ORC =/= [],
[ocsp_responder_certs, {ocsp_stapling, false}]),
case Stapling of
true ->
Opts#{ocsp_stapling =>
#{ocsp_nonce => Nonce,
ocsp_responder_certs => ORC}};
Opts#{stapling =>
#{ocsp_nonce => Nonce}};
false ->
Opts
end.
end;
opt_stapling(UserOpts, Opts, #{role := server}) ->
assert_client_only(stapling, UserOpts),
Opts.

opt_sni(UserOpts, #{versions := _Versions} = Opts, #{role := server}) ->
{_, SniHosts} = get_opt_list(sni_hosts, [], UserOpts, Opts),
@@ -2609,10 +2602,6 @@ assert_server_only(client, Bool, Option) ->
role_error(Bool, server_only, Option);
assert_server_only(_, _, _) ->
ok.
assert_client_only(server, Bool, Option) ->
role_error(Bool, client_only, Option);
assert_client_only(_, _, _) ->
ok.

role_error(false, _ErrorDesc, _Option) ->
ok;
@@ -3054,9 +3043,9 @@ unambiguous_path(Value) ->
%%%#
%%%# Tracing
%%%#
handle_trace(csp, {call, {?MODULE, opt_ocsp, [UserOpts | _]}}, Stack) ->
handle_trace(csp, {call, {?MODULE, opt_stapling, [UserOpts | _]}}, Stack) ->
{format_ocsp_params(UserOpts), Stack};
handle_trace(csp, {return_from, {?MODULE, opt_ocsp, 3}, Return}, Stack) ->
handle_trace(csp, {return_from, {?MODULE, opt_stapling, 3}, Return}, Stack) ->
{format_ocsp_params(Return), Stack};
handle_trace(rle, {call, {?MODULE, listen, Args}}, Stack0) ->
Role = server,
@@ -3066,8 +3055,6 @@ handle_trace(rle, {call, {?MODULE, connect, Args}}, Stack0) ->
{io_lib:format("(*~w) Args = ~W", [Role, Args, 10]), [{role, Role} | Stack0]}.

format_ocsp_params(Map) ->
Stapling = maps:get(ocsp_stapling, Map, '?'),
Stapling = maps:get(stapling, Map, '?'),
Nonce = maps:get(ocsp_nonce, Map, '?'),
Certs = maps:get(ocsp_responder_certs, Map, '?'),
io_lib:format("Stapling = ~W Nonce = ~W Certs = ~W",
[Stapling, 5, Nonce, 5, Certs, 5]).
io_lib:format("Stapling = ~W Nonce = ~W", [Stapling, 5, Nonce, 5]).
Loading

0 comments on commit 8e20f46

Please sign in to comment.