summaryrefslogtreecommitdiff
path: root/lib/public_key/src/pubkey_os_cacerts.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/public_key/src/pubkey_os_cacerts.erl')
-rw-r--r--lib/public_key/src/pubkey_os_cacerts.erl215
1 files changed, 215 insertions, 0 deletions
diff --git a/lib/public_key/src/pubkey_os_cacerts.erl b/lib/public_key/src/pubkey_os_cacerts.erl
new file mode 100644
index 0000000000..1970c8b3d3
--- /dev/null
+++ b/lib/public_key/src/pubkey_os_cacerts.erl
@@ -0,0 +1,215 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2023. 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(pubkey_os_cacerts).
+
+-include("public_key.hrl").
+-export([load/0, load/1, get/0, clear/0]).
+
+-on_load(on_load/0).
+-nifs([os_cacerts/0]).
+
+%% API
+
+%% Return cacerts
+-spec get() -> [public_key:combined_cert()].
+get() ->
+ case persistent_term:get(?MODULE, not_loaded) of
+ not_loaded ->
+ ok = load(),
+ persistent_term:get(?MODULE);
+ CaCerts ->
+ CaCerts
+ end.
+
+%% (Re)Load default os cacerts and cache result.
+-spec load() -> ok | {error, Reason::term()}.
+load() ->
+ case os:type() of
+ {unix, linux} ->
+ load_from_file(linux_paths());
+ {unix, openbsd} ->
+ load_from_file(bsd_paths());
+ {unix, freebsd} ->
+ load_from_file(bsd_paths());
+ {unix, netbsd} ->
+ load_from_file(bsd_paths());
+ {unix, sunos} ->
+ load_from_files(sunos_path());
+ {win32, _} ->
+ load_win32();
+ {unix, darwin} ->
+ load_darwin();
+ Os ->
+ {error, {enotsup, Os}}
+ end.
+
+%% (Re)Load cacerts from file and cache result.
+%% The file-paths will be tried in order.
+%% Can be used when load/0 doesn't work for an unsupported os type.
+-spec load([file:filename_all()]) -> ok | {error, Reason::term()}.
+load(Paths) ->
+ load_from_file(Paths).
+
+
+%% cleanup persistent_key
+-spec clear() -> boolean().
+clear() ->
+ persistent_term:erase(?MODULE).
+
+%% Implementation
+load_from_file([Path|Paths]) when is_list(Path); is_binary(Path) ->
+ try
+ {ok, Binary} = file:read_file(Path),
+ ok = decode_result(Binary)
+ catch _:_Reason ->
+ load_from_file(Paths)
+ end;
+load_from_file([]) ->
+ {error, enoent}.
+
+decode_result(Binary) ->
+ try
+ MakeCert = fun({'Certificate', Der, not_encrypted}, Acc) ->
+ try
+ Decoded = public_key:pkix_decode_cert(Der, otp),
+ [#cert{der=Der, otp=Decoded}|Acc]
+ catch _:_ ->
+ Acc
+ end
+ end,
+ Certs = lists:foldl(MakeCert, [], pubkey_pem:decode(Binary)),
+ store(Certs)
+ catch _:Reason ->
+ {error, Reason}
+ end.
+
+
+load_from_files(Path) ->
+ MakeCert = fun(FileName, Acc) ->
+ try
+ {ok, Bin} = file:read_file(FileName),
+ [#cert{der=Der, otp=public_key:pkix_decode_cert(Der, otp)}
+ || {'Certificate', Der, not_encrypted} <- pubkey_pem:decode(Bin)]
+ ++ Acc
+ catch _:_ ->
+ Acc
+ end
+ end,
+ Certs = filelib:fold_files(Path, ".*\.pem", false, MakeCert, []),
+ store(Certs).
+
+
+load_win32() ->
+ Dec = fun({_Enc, Der}, Acc) ->
+ try
+ Decoded = public_key:pkix_decode_cert(Der, otp),
+ [#cert{der=Der, otp=Decoded}|Acc]
+ catch _:_ ->
+ Acc
+ end
+ end,
+ store(lists:foldl(Dec, [], os_cacerts())).
+
+load_darwin() ->
+ %% Could/should probably be re-written to use Keychain Access API
+ KeyChainFile = "/System/Library/Keychains/SystemRootCertificates.keychain",
+ Args = ["export", "-t", "certs", "-f", "pemseq", "-k", KeyChainFile],
+ try run_cmd("/usr/bin/security", Args) of
+ {ok, Bin} -> decode_result(Bin);
+ Err -> Err
+ catch error:Reason ->
+ {error, {eopnotsupp, Reason}}
+ end.
+
+store([]) ->
+ {error, no_cacerts_found};
+store(CaCerts) ->
+ persistent_term:put(?MODULE, CaCerts).
+
+linux_paths() ->
+ ["/etc/ssl/certs/ca-certificates.crt", %% Debian, Ubuntu, Gentoo
+ "/etc/pki/tls/certs/ca-bundle.crt", %% Fedora, RHEL 6, Amazon Linux
+ "/etc/ssl/ca-bundle.pem", %% OpenSUSE
+ "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem", %% CentOS, RHEL 7
+ "/etc/ssl/cert.pem" %% Alpine Linux
+ ].
+
+bsd_paths() ->
+ ["/usr/local/share/certs/ca-root-nss.crt",
+ "/etc/ssl/cert.pem",
+ "/etc/openssl/certs/cacert.pem", %% netbsd (if installed)
+ "/etc/openssl/certs/ca-certificates.crt"
+ ].
+
+sunos_path() ->
+ "/etc/certs/CA/".
+
+run_cmd(Cmd, Args) ->
+ Opts = [binary, exit_status, stderr_to_stdout],
+ Port = open_port({spawn_executable, Cmd}, [{args, Args}|Opts]),
+ unlink(Port),
+ cmd_data(Port, <<>>).
+
+cmd_data(Port, Acc) ->
+ receive
+ {Port, {data, Bin}} ->
+ cmd_data(Port, <<Acc/binary, Bin/binary>>);
+ {Port, {exit_status, 0}} ->
+ {ok, Acc};
+ {Port, {exit_status, Status}} ->
+ {error, {eopnotsupp, Status, Acc}}
+ end.
+
+%%%
+%%% NIF placeholders
+%%%
+
+-spec os_cacerts() -> [{Encoding::atom(), Cert::binary()}].
+
+os_cacerts() ->
+ erlang:nif_error(nif_not_loaded).
+
+on_load() ->
+ case os:type() of
+ {win32, _} -> load_nif();
+ _ -> ok
+ end.
+
+load_nif() ->
+ PrivDir = code:priv_dir(public_key),
+ LibName = "public_key",
+ Lib = filename:join([PrivDir, "lib", LibName]),
+ case erlang:load_nif(Lib, 0) of
+ ok -> ok;
+ {error, {load_failed, _}}=Error1 ->
+ Arch = erlang:system_info(system_architecture),
+ ArchLibDir = filename:join([PrivDir, "lib", Arch]),
+ Candidate = filelib:wildcard(filename:join([ArchLibDir,LibName ++ "*" ])),
+ case Candidate of
+ [] -> Error1;
+ _ ->
+ ArchLib = filename:join([ArchLibDir, LibName]),
+ erlang:load_nif(ArchLib, 0)
+ end;
+ Error1 -> Error1
+ end.