From 5e5bbe7a4960f11559a8a8eb00a5a3d4ab83e82d Mon Sep 17 00:00:00 2001 From: Ingela Anderton Andin Date: Tue, 15 Jul 2025 17:43:36 +0200 Subject: [PATCH 1/3] ssl: Add max value for CRL cache --- lib/ssl/doc/ssl_app.md | 92 ++++++++++++++++++----------------- lib/ssl/src/ssl_config.erl | 14 ++++++ lib/ssl/src/ssl_crl_cache.erl | 10 ++-- lib/ssl/src/ssl_manager.erl | 15 +++--- lib/ssl/src/ssl_pkix_db.erl | 32 ++++++++++-- 5 files changed, 104 insertions(+), 59 deletions(-) diff --git a/lib/ssl/doc/ssl_app.md b/lib/ssl/doc/ssl_app.md index 0badf972a12a..e70a41a3f581 100644 --- a/lib/ssl/doc/ssl_app.md +++ b/lib/ssl/doc/ssl_app.md @@ -65,6 +65,53 @@ The environment parameters can be set on the command line, for example: might be configurable, by the SSL application. This option can be overridden by the version option to `ssl:connect/2,3` and `ssl:listen/2`. + - **`max_crl_cache = pos_integer() `** - Sets the + maximum number of entries allowed in the default crl store. Defaults to 150. Size limit + is enforced by emptying cache when limit is reached. + +- **`ssl_pem_cache_clean = pos_integer() `** - Number of milliseconds + between PEM cache validations. Defaults to 2 minutes. + + Note: The cache can be reloaded by calling `ssl:clear_pem_cache/0`. + +- **`bypass_pem_cache = boolean() `** - Introduced in ssl-8.0.2. + Disables the PEM-cache. Can be used as a workaround for the PEM-cache + bottleneck before ssl-8.1.1. Defaults to false. + +- **`internal_active_n = integer() `** - For TLS connections this + value is used to handle the internal socket. As the implementation was changed + from an active once to an active N behavior (N = 100), for performance + reasons, this option exist for possible tweaking or restoring of the old + behavior (internal_active_n = 1) in unforeseen scenarios. The option will not + affect erlang distribution over TLS that will always run in active N mode. + Added in ssl-9.1 (OTP-21.2). + +- **`server_session_tickets_amount = integer() `** - Number of session + tickets sent by the server. It must be greater than 0. Defaults to 3. + +- **`server_session_ticket_lifetime = integer() `** - Lifetime of + session tickets sent by the server. Servers must not use any value greater + than 604800 seconds (7 days). Expired tickets are automatically removed. + Defaults to 7200 seconds (2 hours). + +- **`server_session_ticket_store_size = integer() `** - Sets the + maximum size of the server session ticket store (stateful tickets). Defaults + to 1000. Size limit is enforced by dropping old tickets. + +- **`server_session_ticket_max_early_data = integer() `** - Sets the + maximum size of the early data that the server accepts and also configures its + NewSessionTicket messages to include this same size limit in their + early_data_indication extension. Defaults to 16384. Size limit is enforced by + both client and server. + +- **`client_session_ticket_lifetime = integer() `** - Lifetime of + session tickets in the client ticket store. Expired tickets are automatically + removed. Defaults to 7200 seconds (2 hours). + +- **`client_session_ticket_store_size = integer() `** - Sets the + maximum size of the client session ticket store. Defaults to 1000. Size limit + is enforced by dropping old tickets. + - **`session_lifetime = integer() `** - Maximum lifetime of the session data in seconds. Defaults to 24 hours which is the maximum recommended lifetime by [RFC 5246](http://www.ietf.org/rfc/5246rfc.txt). However sessions @@ -108,54 +155,11 @@ The environment parameters can be set on the command line, for example: entries will be invalidated regardless of their remaining lifetime. Defaults to 1000. Recommended ssl-8.2.1 or later for this option to work as intended. -- **`ssl_pem_cache_clean = integer() `** - Number of milliseconds - between PEM cache validations. Defaults to 2 minutes. - - Note: The cache can be reloaded by calling `ssl:clear_pem_cache/0`. - -- **`bypass_pem_cache = boolean() `** - Introduced in ssl-8.0.2. - Disables the PEM-cache. Can be used as a workaround for the PEM-cache - bottleneck before ssl-8.1.1. Defaults to false. - -- **`alert_timeout = integer() `** - Number of milliseconds between +- **`alert_timeout = pos_integer() `** - Number of milliseconds between sending of a fatal alert and closing the connection. Waiting a little while improves the peers chances to properly receiving the alert so it may shutdown gracefully. Defaults to 5000 milliseconds. -- **`internal_active_n = integer() `** - For TLS connections this - value is used to handle the internal socket. As the implementation was changed - from an active once to an active N behavior (N = 100), for performance - reasons, this option exist for possible tweaking or restoring of the old - behavior (internal_active_n = 1) in unforeseen scenarios. The option will not - affect erlang distribution over TLS that will always run in active N mode. - Added in ssl-9.1 (OTP-21.2). - -- **`server_session_tickets_amount = integer() `** - Number of session - tickets sent by the server. It must be greater than 0. Defaults to 3. - -- **`server_session_ticket_lifetime = integer() `** - Lifetime of - session tickets sent by the server. Servers must not use any value greater - than 604800 seconds (7 days). Expired tickets are automatically removed. - Defaults to 7200 seconds (2 hours). - -- **`server_session_ticket_store_size = integer() `** - Sets the - maximum size of the server session ticket store (stateful tickets). Defaults - to 1000. Size limit is enforced by dropping old tickets. - -- **`server_session_ticket_max_early_data = integer() `** - Sets the - maximum size of the early data that the server accepts and also configures its - NewSessionTicket messages to include this same size limit in their - early_data_indication extension. Defaults to 16384. Size limit is enforced by - both client and server. - -- **`client_session_ticket_lifetime = integer() `** - Lifetime of - session tickets in the client ticket store. Expired tickets are automatically - removed. Defaults to 7200 seconds (2 hours). - -- **`client_session_ticket_store_size = integer() `** - Sets the - maximum size of the client session ticket store. Defaults to 1000. Size limit - is enforced by dropping old tickets. - ## Error Logger and Event Handlers The SSL application uses [OTP logger](`m:logger`). TLS/DTLS alerts are logged on diff --git a/lib/ssl/src/ssl_config.erl b/lib/ssl/src/ssl_config.erl index 2e52b2f34590..f0ee500d8389 100644 --- a/lib/ssl/src/ssl_config.erl +++ b/lib/ssl/src/ssl_config.erl @@ -32,6 +32,8 @@ -define(DEFAULT_MAX_SESSION_CACHE, 1000). -define(TWO_HOURS, 7200). -define(SEVEN_DAYS, 604800). +-define(DEFAULT_MAX_CRL_CACHE, 150). +-define(INTERNAL_ACTIVE_N, 100). %% Connection parameter configuration -export([init/2, @@ -44,6 +46,7 @@ %% Application configuration -export([pre_1_3_session_opts/1, get_max_early_data_size/0, + get_max_crl_cache/0, get_ticket_lifetime/0, get_ticket_store_size/0, get_internal_active_n/0, @@ -143,6 +146,14 @@ get_internal_active_n(true) -> get_internal_active_n(false) -> application_int(internal_active_n, ?INTERNAL_ACTIVE_N). +get_max_crl_cache() -> + case application:get_env(ssl, max_crl_cache) of + {ok, Size} when is_integer(Size) -> + Size; + _ -> + ?DEFAULT_MAX_CRL_CACHE + end. + %%==================================================================== %% Certificate and Key configuration %%==================================================================== @@ -362,6 +373,9 @@ mldsa_priv_key_dec(Type, DERKey, PrivKey) -> expandedkey = ExpandedKey} end. +%%==================================================================== +%% Internal functions +%%==================================================================== init_manager_name(false) -> put(ssl_manager, ssl_manager:name(normal)), put(ssl_pem_cache, ssl_pem_cache:name(normal)); diff --git a/lib/ssl/src/ssl_crl_cache.erl b/lib/ssl/src/ssl_crl_cache.erl index 1fd86db21c09..db0b727a2485 100644 --- a/lib/ssl/src/ssl_crl_cache.erl +++ b/lib/ssl/src/ssl_crl_cache.erl @@ -234,10 +234,12 @@ cache_lookup(URL, {{Cache, _}, _}) -> handle_http(URI, Rest, {_, [{http, Timeout}]} = CRLDbInfo) -> CRLs = http_lookup(URI, Rest, CRLDbInfo, Timeout), - %% Uncomment to improve performance, but need to - %% implement cache limit and or cleaning to prevent - %% DoS attack possibilities - %%insert(URI, {der, CRLs}), + case CRLs of + not_available -> + ok; + _ -> + insert(URI, {der, CRLs}) + end, CRLs; handle_http(_, Rest, CRLDbInfo) -> get_crls(Rest, CRLDbInfo). diff --git a/lib/ssl/src/ssl_manager.erl b/lib/ssl/src/ssl_manager.erl index 97565305ad62..8cfc1812fb82 100644 --- a/lib/ssl/src/ssl_manager.erl +++ b/lib/ssl/src/ssl_manager.erl @@ -68,7 +68,8 @@ session_cache_client_max :: integer(), session_client_invalidator :: undefined | pid(), options :: list(), - client_session_order :: gb_trees:tree() + client_session_order :: gb_trees:tree(), + max_crl_db_entries :: pos_integer() }). -define(GEN_UNIQUE_ID_MAX_TRIES, 10). @@ -268,7 +269,8 @@ init([ManagerName, PemCacheName, Opts]) -> session_cache_client_max = ClientSessMax, session_client_invalidator = undefined, options = Opts, - client_session_order = gb_trees:empty() + client_session_order = gb_trees:empty(), + max_crl_db_entries = ssl_config:get_max_crl_cache() }}. %%-------------------------------------------------------------------- @@ -307,8 +309,9 @@ handle_call({{connection_init, Trustedcerts, Role, {CRLCb, UserCRLDb}}, Pid}, _F end; handle_call({{insert_crls, Path, CRLs}, _}, _From, - #state{certificate_db = Db} = State) -> - ssl_pkix_db:add_crls(Db, Path, CRLs), + #state{certificate_db = Db, + max_crl_db_entries = Max} = State) -> + ssl_pkix_db:add_crls(Db, Path, CRLs, Max), {reply, ok, State}; handle_call({{delete_crls, CRLsOrPath}, _}, _From, @@ -345,8 +348,8 @@ handle_cast({invalidate_session, Host, Port, session_cache_client_cb = CacheCb} = State) -> invalidate_session(Cache, CacheCb, {{Host, Port}, ID}, Session, State); handle_cast({insert_crls, Path, CRLs}, - #state{certificate_db = Db} = State) -> - ssl_pkix_db:add_crls(Db, Path, CRLs), + #state{certificate_db = Db, max_crl_db_entries = Max} = State) -> + ssl_pkix_db:add_crls(Db, Path, CRLs, Max), {noreply, State}; handle_cast({delete_crls, CRLsOrPath}, diff --git a/lib/ssl/src/ssl_pkix_db.erl b/lib/ssl/src/ssl_pkix_db.erl index d0a6022a2858..6c5e798b446b 100644 --- a/lib/ssl/src/ssl_pkix_db.erl +++ b/lib/ssl/src/ssl_pkix_db.erl @@ -32,14 +32,27 @@ -include_lib("kernel/include/file.hrl"). -include_lib("kernel/include/logger.hrl"). --export([create/1, create_pem_cache/1, - add_crls/3, remove_crls/2, remove/1, add_trusted_certs/3, +-export([create/1, + create_pem_cache/1, + add_crls/4, + remove_crls/2, + remove/1, + add_trusted_certs/3, refresh_trusted_certs/2, refresh_trusted_certs/3, extract_trusted_certs/1, - remove_trusted_certs/2, insert/3, remove/2, clear/1, db_size/1, - ref_count/3, lookup_trusted_cert/4, foldl/3, select_certentries_by_ref/2, - select_certs_by_ref/2, decode_pem_file/1, lookup/2]). + remove_trusted_certs/2, + insert/3, + remove/2, + clear/1, + db_size/1, + ref_count/3, + lookup_trusted_cert/4, + foldl/3, + select_certentries_by_ref/2, + select_certs_by_ref/2, + decode_pem_file/1, + lookup/2]). %%==================================================================== %% Internal application API @@ -365,6 +378,15 @@ new_trusted_cert_entry(File, [CertsDb, RefsDb, _ | _]) -> Error end. +add_crls([_,_,_, {Cache, _} | _]= DB, Path, CRLs, MaxSize) -> + case ets:info(Cache, size) + length(CRLs) > MaxSize of + true -> + ets:delete_all_objects(Cache), + add_crls(DB, Path, CRLs); + false -> + add_crls(DB, Path, CRLs) + end. + add_crls([_,_,_, {_, Mapping} | _], ?NO_DIST_POINT, CRLs) -> [add_crls(CRL, Mapping) || CRL <- CRLs]; add_crls([_,_,_, {Cache, Mapping} | _], Path, CRLs) -> From b81a7a5c2778b815b756d21f9652b3b75b86a2dd Mon Sep 17 00:00:00 2001 From: Ingela Anderton Andin Date: Wed, 16 Jul 2025 09:25:56 +0200 Subject: [PATCH 2/3] ssl: Remove code duplication --- lib/ssl/src/dtls_packet_demux.erl | 10 +--------- lib/ssl/src/dtls_server_session_cache_sup.erl | 1 - lib/ssl/src/ssl_internal.hrl | 1 - lib/ssl/src/ssl_manager.erl | 3 --- lib/ssl/src/tls_gen_connection_1_3.erl | 19 +++---------------- 5 files changed, 4 insertions(+), 30 deletions(-) diff --git a/lib/ssl/src/dtls_packet_demux.erl b/lib/ssl/src/dtls_packet_demux.erl index dc635b11513d..a7b07228bd9c 100644 --- a/lib/ssl/src/dtls_packet_demux.erl +++ b/lib/ssl/src/dtls_packet_demux.erl @@ -115,7 +115,7 @@ getstat(PacketSocket, Opts) -> %%%=================================================================== init([Owner, Port0, TransportInfo, EmOpts, DTLSOptions, Socket]) -> - InternalActiveN = get_internal_active_n(), + InternalActiveN = ssl_config:get_internal_active_n(), erlang:monitor(process, Owner), {ok, SessionIdHandle} = session_id_tracker(Socket, DTLSOptions), proc_lib:set_label({dtls_server_packet_demultiplexer, Port0}), @@ -431,11 +431,3 @@ emulated_opts_list(Opts, [active | Rest], Acc) -> session_id_tracker(Listener,_) -> dtls_server_session_cache_sup:start_child(Listener). -get_internal_active_n() -> - case application:get_env(ssl, internal_active_n) of - {ok, N} when is_integer(N) -> - N; - _ -> - ?INTERNAL_ACTIVE_N - end. - diff --git a/lib/ssl/src/dtls_server_session_cache_sup.erl b/lib/ssl/src/dtls_server_session_cache_sup.erl index 043a8db4c3b6..eef36fb40dc3 100644 --- a/lib/ssl/src/dtls_server_session_cache_sup.erl +++ b/lib/ssl/src/dtls_server_session_cache_sup.erl @@ -38,7 +38,6 @@ %% Supervisor callback -export([init/1]). --define(DEFAULT_MAX_SESSION_CACHE, 1000). %%%========================================================================= %%% API %%%========================================================================= diff --git a/lib/ssl/src/ssl_internal.hrl b/lib/ssl/src/ssl_internal.hrl index a0640319f4ff..38d4f443a214 100644 --- a/lib/ssl/src/ssl_internal.hrl +++ b/lib/ssl/src/ssl_internal.hrl @@ -52,7 +52,6 @@ -define(CDR_MAGIC, "GIOP"). -define(CDR_HDR_SIZE, 12). --define(INTERNAL_ACTIVE_N, 100). -define(DEPTH, 20). -define(DEFAULT_TIMEOUT, 5000). diff --git a/lib/ssl/src/ssl_manager.erl b/lib/ssl/src/ssl_manager.erl index 8cfc1812fb82..639d639f59fc 100644 --- a/lib/ssl/src/ssl_manager.erl +++ b/lib/ssl/src/ssl_manager.erl @@ -72,11 +72,8 @@ max_crl_db_entries :: pos_integer() }). --define(GEN_UNIQUE_ID_MAX_TRIES, 10). -define(SESSION_VALIDATION_INTERVAL, 60000). --define(CLEAN_SESSION_DB, 60000). -define(CLEAN_CERT_DB, 500). --define(DEFAULT_MAX_SESSION_CACHE, 1000). -define(LOAD_MITIGATION, 10). %%==================================================================== diff --git a/lib/ssl/src/tls_gen_connection_1_3.erl b/lib/ssl/src/tls_gen_connection_1_3.erl index 006d49938bd8..f2056323f5ac 100644 --- a/lib/ssl/src/tls_gen_connection_1_3.erl +++ b/lib/ssl/src/tls_gen_connection_1_3.erl @@ -434,22 +434,9 @@ init_max_early_data_size(server) -> internal_active_n(#{ktls := true}, Socket) -> inet:setopts(Socket, [{packet, ssl_tls}]), 1; -internal_active_n(#{erl_dist := true}, _) -> - %% Start with a random number between 1 and ?INTERNAL_ACTIVE_N - %% In most cases distribution connections are established all at - %% the same time, and flow control engages with ?INTERNAL_ACTIVE_N for - %% all connections. Which creates a wave of "passive" messages, leading - %% to significant bump of memory & scheduler utilisation. Starting with - %% a random number between 1 and ?INTERNAL_ACTIVE_N helps to spread the - %% spike. - erlang:system_time() rem ?INTERNAL_ACTIVE_N + 1; -internal_active_n(_,_) -> - case application:get_env(ssl, internal_active_n) of - {ok, N} when is_integer(N) -> - N; - _ -> - ?INTERNAL_ACTIVE_N - end. +internal_active_n(Options, _) -> + Boolean = maps:get(erl_dist, Options, false), + ssl_config:get_internal_active_n(Boolean). exporter_secrets(ExporterMasterSecret, Labels, PRFAlgorithm) -> DeriveSecret = From c9244d18e6e5afdcf1afa64026a0d781df86ac53 Mon Sep 17 00:00:00 2001 From: Ingela Anderton Andin Date: Thu, 9 Apr 2026 15:07:19 +0200 Subject: [PATCH 3/3] ssl: Fix white space errors --- lib/ssl/src/ssl_pkix_db.erl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/ssl/src/ssl_pkix_db.erl b/lib/ssl/src/ssl_pkix_db.erl index 6c5e798b446b..39073adde542 100644 --- a/lib/ssl/src/ssl_pkix_db.erl +++ b/lib/ssl/src/ssl_pkix_db.erl @@ -34,23 +34,23 @@ -export([create/1, create_pem_cache/1, - add_crls/4, + add_crls/4, remove_crls/2, remove/1, add_trusted_certs/3, refresh_trusted_certs/2, refresh_trusted_certs/3, - extract_trusted_certs/1, - remove_trusted_certs/2, + extract_trusted_certs/1, + remove_trusted_certs/2, insert/3, remove/2, clear/1, db_size/1, - ref_count/3, + ref_count/3, lookup_trusted_cert/4, foldl/3, select_certentries_by_ref/2, - select_certs_by_ref/2, + select_certs_by_ref/2, decode_pem_file/1, lookup/2]).