emqx_ssl_crl_cache.erl 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. %%
  2. %% %CopyrightBegin%
  3. %%
  4. %% Copyright Ericsson AB 2015-2022. All Rights Reserved.
  5. %%
  6. %% Licensed under the Apache License, Version 2.0 (the "License");
  7. %% you may not use this file except in compliance with the License.
  8. %% You may obtain a copy of the License at
  9. %%
  10. %% http://www.apache.org/licenses/LICENSE-2.0
  11. %%
  12. %% Unless required by applicable law or agreed to in writing, software
  13. %% distributed under the License is distributed on an "AS IS" BASIS,
  14. %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. %% See the License for the specific language governing permissions and
  16. %% limitations under the License.
  17. %%
  18. %% %CopyrightEnd%
  19. %%--------------------------------------------------------------------
  20. %% Copyright (c) 2023 EMQ Technologies Co., Ltd. All Rights Reserved.
  21. %%
  22. %% Licensed under the Apache License, Version 2.0 (the "License");
  23. %% you may not use this file except in compliance with the License.
  24. %% You may obtain a copy of the License at
  25. %%
  26. %% http://www.apache.org/licenses/LICENSE-2.0
  27. %%
  28. %% Unless required by applicable law or agreed to in writing, software
  29. %% distributed under the License is distributed on an "AS IS" BASIS,
  30. %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  31. %% See the License for the specific language governing permissions and
  32. %% limitations under the License.
  33. %%--------------------------------------------------------------------
  34. %%----------------------------------------------------------------------
  35. %% Based on `otp/lib/ssl/src/ssl_crl_cache.erl'
  36. %%----------------------------------------------------------------------
  37. %%----------------------------------------------------------------------
  38. %% Purpose: Simple default CRL cache
  39. %%----------------------------------------------------------------------
  40. -module(emqx_ssl_crl_cache).
  41. -include_lib("ssl/src/ssl_internal.hrl").
  42. -include_lib("public_key/include/public_key.hrl").
  43. -behaviour(ssl_crl_cache_api).
  44. -export_type([crl_src/0, uri/0]).
  45. -type crl_src() :: {file, file:filename()} | {der, public_key:der_encoded()}.
  46. -type uri() :: uri_string:uri_string().
  47. -export([lookup/3, select/2, fresh_crl/2]).
  48. -export([insert/1, insert/2, delete/1]).
  49. %% Allow usage of OTP certificate record fields (camelCase).
  50. -elvis([
  51. {elvis_style, atom_naming_convention, #{
  52. regex => "^([a-z][a-z0-9]*_?)([a-zA-Z0-9]*_?)*$",
  53. enclosed_atoms => ".*"
  54. }}
  55. ]).
  56. %%====================================================================
  57. %% Cache callback API
  58. %%====================================================================
  59. lookup(
  60. #'DistributionPoint'{distributionPoint = {fullName, Names}},
  61. _Issuer,
  62. CRLDbInfo
  63. ) ->
  64. get_crls(Names, CRLDbInfo);
  65. lookup(_, _, _) ->
  66. not_available.
  67. select(GenNames, CRLDbHandle) when is_list(GenNames) ->
  68. lists:flatmap(
  69. fun
  70. ({directoryName, Issuer}) ->
  71. select(Issuer, CRLDbHandle);
  72. (_) ->
  73. []
  74. end,
  75. GenNames
  76. );
  77. select(Issuer, {{_Cache, Mapping}, _}) ->
  78. case ssl_pkix_db:lookup(Issuer, Mapping) of
  79. undefined ->
  80. [];
  81. CRLs ->
  82. CRLs
  83. end.
  84. fresh_crl(#'DistributionPoint'{distributionPoint = {fullName, Names}}, CRL) ->
  85. case get_crls(Names, undefined) of
  86. not_available ->
  87. CRL;
  88. NewCRL ->
  89. NewCRL
  90. end.
  91. %%====================================================================
  92. %% API
  93. %%====================================================================
  94. insert(CRLs) ->
  95. insert(?NO_DIST_POINT, CRLs).
  96. insert(URI, {file, File}) when is_list(URI) ->
  97. case file:read_file(File) of
  98. {ok, PemBin} ->
  99. PemEntries = public_key:pem_decode(PemBin),
  100. CRLs = [
  101. CRL
  102. || {'CertificateList', CRL, not_encrypted} <-
  103. PemEntries
  104. ],
  105. do_insert(URI, CRLs);
  106. Error ->
  107. Error
  108. end;
  109. insert(URI, {der, CRLs}) ->
  110. do_insert(URI, CRLs).
  111. delete({file, File}) ->
  112. case file:read_file(File) of
  113. {ok, PemBin} ->
  114. PemEntries = public_key:pem_decode(PemBin),
  115. CRLs = [
  116. CRL
  117. || {'CertificateList', CRL, not_encrypted} <-
  118. PemEntries
  119. ],
  120. ssl_manager:delete_crls({?NO_DIST_POINT, CRLs});
  121. Error ->
  122. Error
  123. end;
  124. delete({der, CRLs}) ->
  125. ssl_manager:delete_crls({?NO_DIST_POINT, CRLs});
  126. delete(URI) ->
  127. case uri_string:normalize(URI, [return_map]) of
  128. #{scheme := "http", path := Path} ->
  129. ssl_manager:delete_crls(string:trim(Path, leading, "/"));
  130. _ ->
  131. {error, {only_http_distribution_points_supported, URI}}
  132. end.
  133. %%--------------------------------------------------------------------
  134. %%% Internal functions
  135. %%--------------------------------------------------------------------
  136. do_insert(URI, CRLs) ->
  137. case uri_string:normalize(URI, [return_map]) of
  138. #{scheme := "http", path := Path} ->
  139. ssl_manager:insert_crls(string:trim(Path, leading, "/"), CRLs);
  140. _ ->
  141. {error, {only_http_distribution_points_supported, URI}}
  142. end.
  143. get_crls([], _) ->
  144. not_available;
  145. get_crls(
  146. [{uniformResourceIdentifier, "http" ++ _ = URL} | Rest],
  147. CRLDbInfo
  148. ) ->
  149. case cache_lookup(URL, CRLDbInfo) of
  150. [] ->
  151. handle_http(URL, Rest, CRLDbInfo);
  152. CRLs ->
  153. CRLs
  154. end;
  155. get_crls([_ | Rest], CRLDbInfo) ->
  156. %% unsupported CRL location
  157. get_crls(Rest, CRLDbInfo).
  158. http_lookup(URL, Rest, CRLDbInfo, Timeout) ->
  159. case application:ensure_started(inets) of
  160. ok ->
  161. http_get(URL, Rest, CRLDbInfo, Timeout);
  162. _ ->
  163. get_crls(Rest, CRLDbInfo)
  164. end.
  165. http_get(URL, Rest, CRLDbInfo, Timeout) ->
  166. case emqx_crl_cache:http_get(URL, Timeout) of
  167. {ok, {_Status, _Headers, Body}} ->
  168. case Body of
  169. <<"-----BEGIN", _/binary>> ->
  170. Pem = public_key:pem_decode(Body),
  171. CRLs = lists:filtermap(
  172. fun
  173. ({'CertificateList', CRL, not_encrypted}) ->
  174. {true, CRL};
  175. (_) ->
  176. false
  177. end,
  178. Pem
  179. ),
  180. emqx_crl_cache:register_der_crls(URL, CRLs),
  181. CRLs;
  182. _ ->
  183. try public_key:der_decode('CertificateList', Body) of
  184. _ ->
  185. CRLs = [Body],
  186. emqx_crl_cache:register_der_crls(URL, CRLs),
  187. CRLs
  188. catch
  189. _:_ ->
  190. get_crls(Rest, CRLDbInfo)
  191. end
  192. end;
  193. {error, _Reason} ->
  194. get_crls(Rest, CRLDbInfo)
  195. end.
  196. cache_lookup(_, undefined) ->
  197. [];
  198. cache_lookup(URL, {{Cache, _}, _}) ->
  199. #{path := Path} = uri_string:normalize(URL, [return_map]),
  200. case ssl_pkix_db:lookup(string:trim(Path, leading, "/"), Cache) of
  201. undefined ->
  202. [];
  203. [CRLs] ->
  204. CRLs
  205. end.
  206. handle_http(URI, Rest, {_, [{http, Timeout}]} = CRLDbInfo) ->
  207. CRLs = http_lookup(URI, Rest, CRLDbInfo, Timeout),
  208. %% Uncomment to improve performance, but need to
  209. %% implement cache limit and or cleaning to prevent
  210. %% DoS attack possibilities
  211. %%insert(URI, {der, CRLs}),
  212. CRLs;
  213. handle_http(_, Rest, CRLDbInfo) ->
  214. get_crls(Rest, CRLDbInfo).