diff --git a/src/certifi.erl b/src/certifi.erl index 7be01a3..81f242d 100644 --- a/src/certifi.erl +++ b/src/certifi.erl @@ -6,6 +6,10 @@ -export([cacertfile/0, cacerts/0]). +-ifdef(TEST). +-export([cacerts_test_data/0]). +-endif. + %% @doc CACertFile gives the path to the file with an X.509 certificate list %% containing the Mozilla CA Certificate that can then be used via the %% cacertfile setting in ssl options passed to the connect function. @@ -29,4 +33,11 @@ cacertfile() -> %% passed to the connect function. -spec cacerts() -> [binary(),...]. cacerts() -> - ok. \ No newline at end of file + []. + +-ifdef(TEST). +%% @doc CACertsTestData provides fixed certificates for testing. +-spec cacerts_test_data() -> [binary(),...]. +cacerts_test_data() -> + []. +-endif. diff --git a/src/certifi_pt.erl b/src/certifi_pt.erl index 3566ecf..f140023 100644 --- a/src/certifi_pt.erl +++ b/src/certifi_pt.erl @@ -1,27 +1,40 @@ -module(certifi_pt). -export([parse_transform/2]). +%% The function replace_cacerts/1 calls public_key:pem_decode/1. +%% Dialyzer warns that public_key:pem_decode/1 is undefined because +%% the public_key application is not listed in the certifi.app.src file. +%% Since public_key is not required at runtime, we suppress this warning +%% instead of adding public_key as a dependency. +-dialyzer([{no_unknown, [replace_cacerts/1]}]). + parse_transform(Forms, _Opts) -> [replace_cacerts(Form) || Form <- Forms]. -replace_cacerts({function, Ann, cacerts, 0, [_]}) -> - {ok, Binary} = file:read_file(cert_file() ), +replace_cacerts({function, Ann, Function, 0, [_]}) when + Function =:= cacerts orelse Function =:= cacerts_test_data-> + {ok, Binary} = file:read_file(cert_file(Function)), Pems = public_key:pem_decode(Binary), Cacerts = [Der || {'Certificate', Der, _} <- Pems], Body = lists:foldl(fun(Cert, Acc) -> {cons, 0, cert_to_bin_ast(Cert), Acc} end, {nil, 0}, Cacerts), - {function, Ann, cacerts, 0, [{clause, Ann, [], [], [Body]}]}; + {function, Ann, Function, 0, [{clause, Ann, [], [], [Body]}]}; replace_cacerts(Other) -> Other. --spec cert_file() -> Result when +-spec cert_file(Function) -> Result when + Function :: cacerts | cacerts_test_data, Result :: file:filename_all(). -cert_file() -> +cert_file(Function) -> AppDir = filename:dirname( filename:dirname(code:which(?MODULE)) ), - filename:join([AppDir, "priv", "cacerts.pem"]). + Dir = case Function of + cacerts -> "priv"; + cacerts_test_data -> "test/data" + end, + filename:join([AppDir, Dir, "cacerts.pem"]). -spec cert_to_bin_ast(Cert) -> Result when Cert :: binary(), diff --git a/test/certifi_tests.erl b/test/certifi_tests.erl index 4ba2439..7218f5c 100644 --- a/test/certifi_tests.erl +++ b/test/certifi_tests.erl @@ -2,6 +2,9 @@ -include_lib("eunit/include/eunit.hrl"). +%% Update this when updating priv/cacerts.pem. +-define(NUM_OF_CERTS, 147). + -ifdef('OTP_20_AND_ABOVE'). reproducible_module_test() -> %% When compiled with +deterministic, only version is left out. @@ -9,10 +12,17 @@ reproducible_module_test() -> -endif. cacerts_test_() -> - Certs = [Cert1, Cert2, Cert3 | _] = certifi:cacerts(), - [?_assertEqual(128, length(Certs)) - ,?_assertMatch(<<48,130,5,192,48,130,3,168,160,3,2,1,2,2,16,30,191,89,80,184,_/binary>>, Cert1) - ,?_assertMatch(<<48,130,2,101,48,130,1,235,160,3,2,1,2,2,16,120,143,39,92,_/binary>>, Cert2) - ,?_assertMatch(<<48,130,5,239,48,130,3,215,160,3,2,1,2,2,8,13,211,227,188,_/binary>>, Cert3) - ,?_assertMatch(<<48,130,3,117,48,130,2,93,160,3,2,1,2,2,11,4,0,0,0,0,1,21,75,90,195,148,48,13,6,_/binary>>, lists:last(Certs)) + %% Checking the contents is difficult because they change frequently. + %% Therefore, this test only checks the number and type of certificates. + Certs = certifi:cacerts(), + [?_assertEqual(?NUM_OF_CERTS, length(Certs)) + ,?_assert(lists:all(fun is_binary/1, Certs)) + ]. + +cacerts_test_data_test_() -> + Certs = [Cert1, Cert2, Cert3] = certifi:cacerts_test_data(), + [?_assertEqual(3, length(Certs)) + ,?_assertMatch(<<48,130,3,199,48,130,2,175,160,3,2,1,2,2,20,46,59,44,50,129,_/binary>>, Cert1) + ,?_assertMatch(<<48,130,3,199,48,130,2,175,160,3,2,1,2,2,20,22,71,8,124,36,_/binary>>, Cert2) + ,?_assertMatch(<<48,130,3,199,48,130,2,175,160,3,2,1,2,2,20,39,199,152,45,116,_/binary>>, Cert3) ]. diff --git a/test/data/cacerts.pem b/test/data/cacerts.pem new file mode 100644 index 0000000..68c8784 --- /dev/null +++ b/test/data/cacerts.pem @@ -0,0 +1,71 @@ +-----BEGIN CERTIFICATE----- +MIIDxzCCAq+gAwIBAgIUJ8eYLXRZClJruoSCfyQr/0phTOMwDQYJKoZIhvcNAQEL +BQAwcjELMAkGA1UEBhMCWFgxDTALBgNVBAgMBEJFQU0xDzANBgNVBAcMBkVybGFu +ZzEWMBQGA1UECgwNRXJsYW5nQ2VydGlmaTERMA8GA1UECwwIVW5pdFRlc3QxGDAW +BgNVBAMMD0NBQ2VydFRlc3REYXRhMTAgFw0yNDA1MjYwMjU3MjVaGA8yMTI0MDUw +MjAyNTcyNVowcjELMAkGA1UEBhMCWFgxDTALBgNVBAgMBEJFQU0xDzANBgNVBAcM +BkVybGFuZzEWMBQGA1UECgwNRXJsYW5nQ2VydGlmaTERMA8GA1UECwwIVW5pdFRl +c3QxGDAWBgNVBAMMD0NBQ2VydFRlc3REYXRhMTCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAJlSHRxjQvZrnO4UnbgPaiXHDPkxossCBWpz9/U3ZEFnvT3j +j0vMAdEmWtH+2Q9J/2OtEzWwpkcpr7IrhNdIiLLK9WJA2RzZSiVj3Cczf7tLcAxL +Jd+x9cyRi/olKXONOU0in27BxK5krM+kawBB7OPA9iCZ2OowRwh7h/WhbuSjDvfs +jBvOYDl5S1cRWnHvY0IccDjqd+HpwonOM8dK+RxFz1J2NbFJUhJRJ65GAntSAYXM +FF9ad4t7oeFYdCWmdqeFTYyhB5GEDD+57XNUlK1XH64ekBHoD2Zathndl0Xo817w +0V5nTZuqZZVjHzYPAB7Sefl6UkKzIQpw3PVotj8CAwEAAaNTMFEwHQYDVR0OBBYE +FPpgdokCaVn6K8eW/V/V57c9LuI+MB8GA1UdIwQYMBaAFPpgdokCaVn6K8eW/V/V +57c9LuI+MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAAQZKsgt +hT/dHomlNGEc/Z92O8pSKnqFNrrdToAlsXRwDc4LAOeOJOu2WKvKWc8za3jYk+S3 +giiLRghnjbztX/WaZofjGQOhlSQeTUVXBSd9hGZv6MDC0Ug/oJYZ9jvmVxjJpiac +qOf461R3PVayo8hVjZe6z0G4Z0Rfq/HnZ3pZU9HV5+wMJ7/wQBq7E0o0kW4tlX9G +U2Wn4gZbu9FimVoS1kfpYYtBSRCpvVlj0lxjDQLXtAq4F0nokR/MZb55Wkm1YA+i +6v0Ec+UQ+cYTEEProIM9IjiOCZxLJ3DdAFa95fTbsWqyPp2h+ZrmujqNlZ8K8d8h +eKGL33vz11++ozQ= +-----END CERTIFICATE----- + +-----BEGIN CERTIFICATE----- +MIIDxzCCAq+gAwIBAgIUFkcIfCRoKId9e4a2eU/p1h8YLxgwDQYJKoZIhvcNAQEL +BQAwcjELMAkGA1UEBhMCWFgxDTALBgNVBAgMBEJFQU0xDzANBgNVBAcMBkVybGFu +ZzEWMBQGA1UECgwNRXJsYW5nQ2VydGlmaTERMA8GA1UECwwIVW5pdFRlc3QxGDAW +BgNVBAMMD0NBQ2VydFRlc3REYXRhMjAgFw0yNDA1MjYwMjU3NDVaGA8yMTI0MDUw +MjAyNTc0NVowcjELMAkGA1UEBhMCWFgxDTALBgNVBAgMBEJFQU0xDzANBgNVBAcM +BkVybGFuZzEWMBQGA1UECgwNRXJsYW5nQ2VydGlmaTERMA8GA1UECwwIVW5pdFRl +c3QxGDAWBgNVBAMMD0NBQ2VydFRlc3REYXRhMjCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAK/6T2WlAH86DQ/J1rLuRXt+1He9ye9nTa23u8jTcBP2ilIj +HWCqNkhaG+kYW5uiTvz+TCBCM+uOJGQ17dzQfwiky4PP0qbm02WTxgy51nMPYfyc +rJ4l6M/XWkrRz7gtr+iaefdKcT8+T1Oejo2Sk+JcVoAfsFAzgoiv8D0/ilI3BMRz +ia9dCLVQH/dBgXkHuogaeQfnACIf2MBf9eBYJk9+TDgOgRQbp4kpovst6wCopazS +2P2+4zyKWn6Lqqllc3hovB3DxerIDuFOXxsF7ZgXMyBrw4lWlXnfZ1nWT6KQ+Su1 +UaEgrCrT/zEa4vv73456K4POTB7hPnoKIbjY8zcCAwEAAaNTMFEwHQYDVR0OBBYE +FNib8+GDQ6H/++tkS52rVHlZlmY9MB8GA1UdIwQYMBaAFNib8+GDQ6H/++tkS52r +VHlZlmY9MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAKEJ8D10 +rXBJchkwHtj9FdDukLZthnp+NUniYkQTvFm3a7Xw6z6kAdlwLZ5rgp6qRQc1Fumr +MA9YMmp0Y9VyTdyHTrykTWgdEKRd1hn/v1Rsoh96g6zOphE8BSBl/2Z9k3AOMl3p +qWwrAzgOEWxdYrkUpicTGrbocKiVm5YBYryMeB1r+UDQ8uJDOlhYjUg9Zmm0yTyf +b4N9U5qxZvTpxv/msxD8woxhiO6AATg5hNX2Ed2wPUECbUMre7hL4QKrT6J435Qm +XOHz5C72QNy3Hz0gUFJdRhR8IwW8gi8pXQooqu5AmzuqllVmNF29CriSBplhjoND +8SKQlVZBs51f8E0= +-----END CERTIFICATE----- + +-----BEGIN CERTIFICATE----- +MIIDxzCCAq+gAwIBAgIULjssMoENyVlErmdM909t9a8TPEkwDQYJKoZIhvcNAQEL +BQAwcjELMAkGA1UEBhMCWFgxDTALBgNVBAgMBEJFQU0xDzANBgNVBAcMBkVybGFu +ZzEWMBQGA1UECgwNRXJsYW5nQ2VydGlmaTERMA8GA1UECwwIVW5pdFRlc3QxGDAW +BgNVBAMMD0NBQ2VydFRlc3REYXRhMzAgFw0yNDA1MjYwMjU3NTRaGA8yMTI0MDUw +MjAyNTc1NFowcjELMAkGA1UEBhMCWFgxDTALBgNVBAgMBEJFQU0xDzANBgNVBAcM +BkVybGFuZzEWMBQGA1UECgwNRXJsYW5nQ2VydGlmaTERMA8GA1UECwwIVW5pdFRl +c3QxGDAWBgNVBAMMD0NBQ2VydFRlc3REYXRhMzCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAJZ+C5Z1n6dHs2dCmV7jm/Yi+48BFU4Esky5fX+wcwJ/oWZn +BPCOTJpBJ03+23Smsexyxr5Ybf7YMBOMrP1+A1l7S3XTFWViofds+dQTT8KOaZBU +KsVdZfFyjHsojS7RZ44iMziO6iUUwaFYG3DDAwU3N8DOYqJhEm4I5C+vkc65sJSh +b0IqdQ4QX17c5zKY+jUpeDxraCIwpIRaNHuIS6CfG1n1NUv9PqHPigRlTlQYHCdj +vmOhl2VnIHqnWpBt6VQWXibbUF/D48MBcYf9oZH7xBEtUwPalTDK/v19RmWxnCMh +Rck1YbCqCunxAR/YhqikfBbbO6YTJqVHkmhc388CAwEAAaNTMFEwHQYDVR0OBBYE +FMcwbk5r9achU5j8ij2ESXZJqMyMMB8GA1UdIwQYMBaAFMcwbk5r9achU5j8ij2E +SXZJqMyMMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBABCQECeb +vLkQlLcKaNu57ccaiwyKOKl9PRZIjCy43lhVVe6ida837XUPOANR7bH54oPCq9Yp +ElV0IwE3SBoxBElhFqB0iL78wFw4OMQMmQOe1R8tAmGI0GZRs5P+wM+7PsXELy8f +bvfHyLK1JDwFMeZ63NIVlwkJfGkQOQljGhTPDvyEszGNc2nEEZo4dLaCBjeuLtHo +gG+e6lI3jxZuphBXW6AUlOmJQWhk9pRlE4Cj5lU+R8Cb24QxRWneXdn3vpX974zc +rrvErTtrQjFNdvkAKINZzJgukXzYaRKQ8N2ULqD2xtJ6YkOgBQEHqBIowxSdKvHD +lrO3tTA5Ejr4Udg= +-----END CERTIFICATE-----