emqx_tls_lib_tests.erl 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. %%--------------------------------------------------------------------
  2. %% Copyright (c) 2021-2024 EMQ Technologies Co., Ltd. All Rights Reserved.
  3. %%
  4. %% Licensed under the Apache License, Version 2.0 (the "License");
  5. %% you may not use this file except in compliance with the License.
  6. %% You may obtain a copy of the License at
  7. %%
  8. %% http://www.apache.org/licenses/LICENSE-2.0
  9. %%
  10. %% Unless required by applicable law or agreed to in writing, software
  11. %% distributed under the License is distributed on an "AS IS" BASIS,
  12. %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. %% See the License for the specific language governing permissions and
  14. %% limitations under the License.
  15. %%--------------------------------------------------------------------
  16. -module(emqx_tls_lib_tests).
  17. -include_lib("eunit/include/eunit.hrl").
  18. %% one of the cipher suite from tlsv1.2 and tlsv1.3 each
  19. -define(TLS_12_CIPHER, "ECDHE-ECDSA-AES256-GCM-SHA384").
  20. -define(TLS_13_CIPHER, "TLS_AES_256_GCM_SHA384").
  21. ensure_tls13_ciphers_added_test() ->
  22. Ciphers = emqx_tls_lib:integral_ciphers(['tlsv1.3'], [?TLS_12_CIPHER]),
  23. ?assert(lists:member(?TLS_12_CIPHER, Ciphers)),
  24. ?assert(lists:member(?TLS_13_CIPHER, Ciphers)).
  25. legacy_cipher_suites_test() ->
  26. Ciphers = emqx_tls_lib:integral_ciphers(['tlsv1.2'], [?TLS_12_CIPHER]),
  27. ?assertEqual([?TLS_12_CIPHER], Ciphers).
  28. use_default_ciphers_test() ->
  29. Ciphers = emqx_tls_lib:integral_ciphers(['tlsv1.3', 'tlsv1.2'], ""),
  30. ?assert(lists:member(?TLS_12_CIPHER, Ciphers)),
  31. ?assert(lists:member(?TLS_13_CIPHER, Ciphers)).
  32. ciphers_format_test_() ->
  33. String = ?TLS_13_CIPHER ++ "," ++ ?TLS_12_CIPHER,
  34. Binary = bin(String),
  35. List = [?TLS_13_CIPHER, ?TLS_12_CIPHER],
  36. [
  37. {"string", fun() -> test_cipher_format(String) end},
  38. {"binary", fun() -> test_cipher_format(Binary) end},
  39. {"string-list", fun() -> test_cipher_format(List) end}
  40. ].
  41. test_cipher_format(Input) ->
  42. Ciphers = emqx_tls_lib:integral_ciphers(['tlsv1.3', 'tlsv1.2'], Input),
  43. ?assertEqual([?TLS_13_CIPHER, ?TLS_12_CIPHER], Ciphers).
  44. tls_versions_test() ->
  45. ?assert(lists:member('tlsv1.3', emqx_tls_lib:available_versions(tls))).
  46. tls_version_unknown_test_() ->
  47. lists:flatmap(
  48. fun(Type) ->
  49. [
  50. ?_assertEqual(
  51. emqx_tls_lib:available_versions(Type),
  52. emqx_tls_lib:integral_versions(Type, [])
  53. ),
  54. ?_assertEqual(
  55. emqx_tls_lib:available_versions(Type),
  56. emqx_tls_lib:integral_versions(Type, <<>>)
  57. ),
  58. ?_assertEqual(
  59. emqx_tls_lib:available_versions(Type),
  60. %% unknown version dropped
  61. emqx_tls_lib:integral_versions(Type, "foo")
  62. ),
  63. fun() ->
  64. ?assertError(
  65. #{reason := no_available_tls_version},
  66. emqx_tls_lib:integral_versions(Type, [foo])
  67. )
  68. end
  69. ]
  70. end,
  71. [tls, dtls]
  72. ).
  73. cipher_suites_no_duplication_test() ->
  74. AllCiphers = emqx_tls_lib:default_ciphers(),
  75. ?assertEqual(length(AllCiphers), length(lists:usort(AllCiphers))).
  76. ssl_files_failure_test_() ->
  77. [
  78. {"undefined_is_undefined", fun() ->
  79. ?assertEqual(
  80. {ok, undefined},
  81. emqx_tls_lib:ensure_ssl_files("dir", undefined)
  82. )
  83. end},
  84. {"no_op_if_disabled", fun() ->
  85. Disabled = #{<<"enable">> => false, foo => bar},
  86. ?assertEqual(
  87. {ok, Disabled},
  88. emqx_tls_lib:ensure_ssl_files("dir", Disabled)
  89. )
  90. end},
  91. {"enoent_key_file", fun() ->
  92. NonExistingFile = filename:join(
  93. "/tmp", integer_to_list(erlang:system_time(microsecond))
  94. ),
  95. ?assertMatch(
  96. {error, #{file_read := enoent, pem_check := invalid_pem}},
  97. emqx_tls_lib:ensure_ssl_files("/tmp", #{
  98. <<"keyfile">> => NonExistingFile,
  99. <<"certfile">> => test_key(),
  100. <<"cacertfile">> => test_key()
  101. })
  102. )
  103. end},
  104. {"empty_cacertfile", fun() ->
  105. ?assertMatch(
  106. {ok, _},
  107. emqx_tls_lib:ensure_ssl_files("/tmp", #{
  108. <<"keyfile">> => test_key(),
  109. <<"certfile">> => test_key(),
  110. <<"cacertfile">> => <<"">>
  111. })
  112. )
  113. end},
  114. {"bad_pem_string", fun() ->
  115. %% empty string
  116. ?assertMatch(
  117. {error, #{
  118. reason := pem_file_path_or_string_is_required,
  119. which_options := [[<<"keyfile">>]]
  120. }},
  121. emqx_tls_lib:ensure_ssl_files("/tmp", #{
  122. <<"keyfile">> => <<>>,
  123. <<"certfile">> => test_key(),
  124. <<"cacertfile">> => test_key()
  125. })
  126. ),
  127. %% not valid unicode
  128. ?assertMatch(
  129. {error, #{
  130. reason := invalid_file_path_or_pem_string, which_options := [[<<"keyfile">>]]
  131. }},
  132. emqx_tls_lib:ensure_ssl_files("/tmp", #{
  133. <<"keyfile">> => <<255, 255>>,
  134. <<"certfile">> => test_key(),
  135. <<"cacertfile">> => test_key()
  136. })
  137. ),
  138. ?assertMatch(
  139. {error, #{
  140. reason := invalid_file_path_or_pem_string,
  141. which_options := [[<<"ocsp">>, <<"issuer_pem">>]]
  142. }},
  143. emqx_tls_lib:ensure_ssl_files("/tmp", #{
  144. <<"keyfile">> => test_key(),
  145. <<"certfile">> => test_key(),
  146. <<"cacertfile">> => test_key(),
  147. <<"ocsp">> => #{<<"issuer_pem">> => <<255, 255>>}
  148. })
  149. ),
  150. %% not printable
  151. ?assertMatch(
  152. {error, #{reason := invalid_file_path_or_pem_string}},
  153. emqx_tls_lib:ensure_ssl_files("/tmp", #{
  154. <<"keyfile">> => <<33, 22>>,
  155. <<"certfile">> => test_key(),
  156. <<"cacertfile">> => test_key()
  157. })
  158. ),
  159. TmpFile = filename:join("/tmp", integer_to_list(erlang:system_time(microsecond))),
  160. try
  161. ok = file:write_file(TmpFile, <<"not a valid pem">>),
  162. ?assertMatch(
  163. {error, #{file_read := not_pem}},
  164. emqx_tls_lib:ensure_ssl_files(
  165. "/tmp",
  166. #{
  167. <<"cacertfile">> => bin(TmpFile),
  168. <<"keyfile">> => bin(TmpFile),
  169. <<"certfile">> => bin(TmpFile),
  170. <<"ocsp">> => #{<<"issuer_pem">> => bin(TmpFile)}
  171. }
  172. )
  173. )
  174. after
  175. file:delete(TmpFile)
  176. end
  177. end}
  178. ].
  179. ssl_file_replace_test() ->
  180. Key1 = test_key(),
  181. Key2 = test_key2(),
  182. SSL0 = #{
  183. <<"keyfile">> => Key1,
  184. <<"certfile">> => Key1,
  185. <<"cacertfile">> => Key1,
  186. <<"ocsp">> => #{<<"issuer_pem">> => Key1}
  187. },
  188. SSL1 = #{
  189. <<"keyfile">> => Key2,
  190. <<"certfile">> => Key2,
  191. <<"cacertfile">> => Key2,
  192. <<"ocsp">> => #{<<"issuer_pem">> => Key2}
  193. },
  194. Dir = filename:join(["/tmp", "ssl-test-dir2"]),
  195. {ok, SSL2} = emqx_tls_lib:ensure_ssl_files(Dir, SSL0),
  196. {ok, SSL3} = emqx_tls_lib:ensure_ssl_files(Dir, SSL1),
  197. File1 = maps:get(<<"keyfile">>, SSL2),
  198. File2 = maps:get(<<"keyfile">>, SSL3),
  199. IssuerPem1 = emqx_utils_maps:deep_get([<<"ocsp">>, <<"issuer_pem">>], SSL2),
  200. IssuerPem2 = emqx_utils_maps:deep_get([<<"ocsp">>, <<"issuer_pem">>], SSL3),
  201. ?assert(filelib:is_regular(File1)),
  202. ?assert(filelib:is_regular(File2)),
  203. ?assert(filelib:is_regular(IssuerPem1)),
  204. ?assert(filelib:is_regular(IssuerPem2)),
  205. ok.
  206. ssl_file_deterministic_names_test() ->
  207. SSL0 = #{
  208. <<"keyfile">> => test_key(),
  209. <<"certfile">> => test_key()
  210. },
  211. Dir0 = filename:join(["/tmp", ?FUNCTION_NAME, "ssl0"]),
  212. Dir1 = filename:join(["/tmp", ?FUNCTION_NAME, "ssl1"]),
  213. {ok, SSLFiles0} = emqx_tls_lib:ensure_ssl_files(Dir0, SSL0),
  214. ?assertEqual(
  215. {ok, SSLFiles0},
  216. emqx_tls_lib:ensure_ssl_files(Dir0, SSL0)
  217. ),
  218. ?assertNotEqual(
  219. {ok, SSLFiles0},
  220. emqx_tls_lib:ensure_ssl_files(Dir1, SSL0)
  221. ),
  222. _ = file:del_dir_r(filename:join(["/tmp", ?FUNCTION_NAME])).
  223. to_client_opts_test() ->
  224. VersionsAll = [tlsv1, 'tlsv1.1', 'tlsv1.2', 'tlsv1.3'],
  225. Versions13Only = ['tlsv1.3'],
  226. Options = #{
  227. enable => true,
  228. verify => "Verify",
  229. server_name_indication => "SNI",
  230. ciphers => "Ciphers",
  231. depth => "depth",
  232. password => "password",
  233. versions => VersionsAll,
  234. secure_renegotiate => "secure_renegotiate",
  235. reuse_sessions => "reuse_sessions"
  236. },
  237. Expected1 = lists:usort(maps:keys(Options) -- [enable]),
  238. ?assertEqual(
  239. Expected1, lists:usort(proplists:get_keys(emqx_tls_lib:to_client_opts(tls, Options)))
  240. ),
  241. Expected2 =
  242. lists:usort(
  243. maps:keys(Options) --
  244. [enable, reuse_sessions, secure_renegotiate]
  245. ),
  246. ?assertEqual(
  247. Expected2,
  248. lists:usort(
  249. proplists:get_keys(
  250. emqx_tls_lib:to_client_opts(tls, Options#{versions := Versions13Only})
  251. )
  252. )
  253. ),
  254. Expected3 = lists:usort(maps:keys(Options) -- [enable, depth, password]),
  255. ?assertEqual(
  256. Expected3,
  257. lists:usort(
  258. proplists:get_keys(
  259. emqx_tls_lib:to_client_opts(tls, Options#{depth := undefined, password := ""})
  260. )
  261. )
  262. ),
  263. ok.
  264. to_server_opts_test() ->
  265. VersionsAll = [tlsv1, 'tlsv1.1', 'tlsv1.2', 'tlsv1.3'],
  266. Versions13Only = ['tlsv1.3'],
  267. Options = #{
  268. verify => "Verify",
  269. ciphers => "Ciphers",
  270. versions => VersionsAll,
  271. user_lookup_fun => "funfunfun",
  272. client_renegotiation => "client_renegotiation"
  273. },
  274. Expected1 = lists:usort(maps:keys(Options)),
  275. ?assertEqual(
  276. Expected1, lists:usort(proplists:get_keys(emqx_tls_lib:to_server_opts(tls, Options)))
  277. ),
  278. Expected2 = lists:usort(maps:keys(Options) -- [user_lookup_fun, client_renegotiation]),
  279. ?assertEqual(
  280. Expected2,
  281. lists:usort(
  282. proplists:get_keys(
  283. emqx_tls_lib:to_server_opts(tls, Options#{versions := Versions13Only})
  284. )
  285. )
  286. ).
  287. bin(X) -> iolist_to_binary(X).
  288. test_key() ->
  289. <<
  290. "\n"
  291. "-----BEGIN EC PRIVATE KEY-----\n"
  292. "MHQCAQEEICKTbbathzvD8zvgjL7qRHhW4alS0+j0Loo7WeYX9AxaoAcGBSuBBAAK\n"
  293. "oUQDQgAEJBdF7MIdam5T4YF3JkEyaPKdG64TVWCHwr/plC0QzNVJ67efXwxlVGTo\n"
  294. "ju0VBj6tOX1y6C0U+85VOM0UU5xqvw==\n"
  295. "-----END EC PRIVATE KEY-----\n"
  296. >>.
  297. test_key2() ->
  298. <<
  299. "\n"
  300. "-----BEGIN EC PRIVATE KEY-----\n"
  301. "MHQCAQEEID9UlIyAlLFw0irkRHX29N+ZGivGtDjlVJvATY3B0TTmoAcGBSuBBAAK\n"
  302. "oUQDQgAEUwiarudRNAT25X11js8gE9G+q0GdsT53QJQjRtBO+rTwuCW1vhLzN0Ve\n"
  303. "AbToUD4JmV9m/XwcSVH06ZaWqNuC5w==\n"
  304. "-----END EC PRIVATE KEY-----\n"
  305. >>.