From f9d841d9c954510f010d92ec95da5852fd763514 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonatan=20M=C3=A4nnchen?= Date: Tue, 21 Jul 2026 12:55:48 +0200 Subject: [PATCH] public_key: Fix crash when no OS CA certificates are found When no OS CA certificate bundle can be found, for example on a minimal system where none of the default paths exist, or when the configured path is an empty directory, pubkey_os_cacerts:get/0 is supposed to raise a descriptive {failed_load_cacerts, Reason} error. Instead it crashed with a function_clause error in the internal error conversion helper, masking the real problem: ** exception error: no function clause matching pubkey_os_cacerts:conv_error_reason(no_cacerts_found) This can also be triggered indirectly through httpc's default verify_peer setup (httpc:ssl_verify_host_options/1). format_error/2 had the same gap, so error formatting would have crashed in the same way instead of producing a message. Add the missing no_cacerts_found clauses so that the intended {failed_load_cacerts, no_cacerts_found} error is raised and formatted properly, and extend the cacerts_load test case to cover this. --- lib/public_key/src/pubkey_os_cacerts.erl | 3 +++ lib/public_key/test/public_key_SUITE.erl | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/lib/public_key/src/pubkey_os_cacerts.erl b/lib/public_key/src/pubkey_os_cacerts.erl index e0b15810d9a5..b4412111342b 100644 --- a/lib/public_key/src/pubkey_os_cacerts.erl +++ b/lib/public_key/src/pubkey_os_cacerts.erl @@ -299,6 +299,7 @@ load_nif() -> %%% conv_error_reason(enoent) -> enoent; +conv_error_reason(no_cacerts_found) -> no_cacerts_found; conv_error_reason({enotsup, _OS}) -> enotsup; conv_error_reason({eopnotsupp, _Reason}) -> eopnotsupp; conv_error_reason({eopnotsupp, _Status, _Acc}) -> eopnotsupp. @@ -316,6 +317,8 @@ format_error(Reason, [{_M, _F, _As, Info} | _]) -> Message = case Cause of enoent -> "operating system CA bundle could not be located"; + no_cacerts_found -> + "no CA certificates were found on the system"; {enotsup, OS} -> io_lib:format("operating system ~p is not supported", [OS]); {eopnotsupp, SubReason} -> diff --git a/lib/public_key/test/public_key_SUITE.erl b/lib/public_key/test/public_key_SUITE.erl index 4c2a37a6f65d..05e2e76ed5c8 100644 --- a/lib/public_key/test/public_key_SUITE.erl +++ b/lib/public_key/test/public_key_SUITE.erl @@ -2250,6 +2250,25 @@ cacerts_load(Config) -> Datadir = proplists:get_value(data_dir, Config), {error, enoent} = public_key:cacerts_load("/dummy.file"), + %% When no CA certificates can be found (missing bundle or one + %% without any CA certificates) this should surface as a + %% {failed_load_cacerts, no_cacerts_found} error through cacerts_get() + %% and be formattable, not crash the error conversion + _ = public_key:cacerts_clear(), + EmptyDir = filename:join(proplists:get_value(priv_dir, Config), + "empty_cacerts_dir"), + ok = filelib:ensure_path(EmptyDir), + {error, no_cacerts_found} = pubkey_os_cacerts:load([EmptyDir]), + application:set_env(public_key, cacerts_path, EmptyDir), + try public_key:cacerts_get() of + _ -> ct:fail(no_cacerts_found_not_raised) + catch + error:{failed_load_cacerts, no_cacerts_found} = Error:ST -> + #{general := _, reason := _} = + pubkey_os_cacerts:format_error(Error, ST) + end, + application:unset_env(public_key, cacerts_path), + %% White box testing of paths loading %% TestDirs ok = pubkey_os_cacerts:load([filename:join(Datadir, "non_existing_dir"),