From fdf439bc7bc2b3e210630f10a034b2212c9db0a5 Mon Sep 17 00:00:00 2001 From: Zaiming Shi Date: Sat, 23 Oct 2021 18:13:31 +0200 Subject: [PATCH] refactor(tls): move ssl files handling to emqx_tls_lib This is an attempt ot make it more generic for other APPs to use. Aslo added test cases to cover most of the code paths. --- apps/emqx/src/emqx.erl | 4 + apps/emqx/src/emqx_authentication_config.erl | 130 +++++------------- apps/emqx/src/emqx_config.erl | 2 +- apps/emqx/src/emqx_tls_lib.erl | 133 +++++++++++++++++++ apps/emqx/test/emqx_authentication_SUITE.erl | 16 +-- apps/emqx/test/emqx_tls_lib_tests.erl | 92 ++++++++++++- 6 files changed, 261 insertions(+), 116 deletions(-) diff --git a/apps/emqx/src/emqx.erl b/apps/emqx/src/emqx.erl index 6f0329762..3a1eda6c4 100644 --- a/apps/emqx/src/emqx.erl +++ b/apps/emqx/src/emqx.erl @@ -65,6 +65,7 @@ , remove_config/1 , remove_config/2 , reset_config/2 + , data_dir/0 ]). -define(APP, ?MODULE). @@ -246,3 +247,6 @@ reset_config([RootName | _] = KeyPath, Opts) -> {error, _} = Error -> Error end. + +data_dir() -> + application:get_env(emqx, data_dir, "data"). diff --git a/apps/emqx/src/emqx_authentication_config.erl b/apps/emqx/src/emqx_authentication_config.erl index 2bea0cbe9..ab2124790 100644 --- a/apps/emqx/src/emqx_authentication_config.erl +++ b/apps/emqx/src/emqx_authentication_config.erl @@ -27,9 +27,8 @@ , authn_type/1 ]). -%% TODO: certs handling should be moved out of emqx app -ifdef(TEST). --export([convert_certs/2, convert_certs/3, diff_cert/2, clear_certs/2]). +-export([convert_certs/2, convert_certs/3, clear_certs/2]). -endif. -export_type([config/0]). @@ -64,7 +63,7 @@ pre_config_update(UpdateReq, OldConfig) -> do_pre_config_update({create_authenticator, ChainName, Config}, OldConfig) -> try - CertsDir = certs_dir([to_bin(ChainName), authenticator_id(Config)]), + CertsDir = certs_dir(ChainName, Config), NConfig = convert_certs(CertsDir, Config), {ok, OldConfig ++ [NConfig]} catch @@ -80,7 +79,7 @@ do_pre_config_update({delete_authenticator, _ChainName, AuthenticatorID}, OldCon {ok, NewConfig}; do_pre_config_update({update_authenticator, ChainName, AuthenticatorID, Config}, OldConfig) -> try - CertsDir = certs_dir([to_bin(ChainName), AuthenticatorID]), + CertsDir = certs_dir(ChainName, AuthenticatorID), NewConfig = lists:map( fun(OldConfig0) -> case AuthenticatorID =:= authenticator_id(OldConfig0) of @@ -127,9 +126,8 @@ do_post_config_update({delete_authenticator, ChainName, AuthenticatorID}, _NewCo case emqx_authentication:delete_authenticator(ChainName, AuthenticatorID) of ok -> [Config] = [Config0 || Config0 <- to_list(OldConfig), AuthenticatorID == authenticator_id(Config0)], - CertsDir = certs_dir([to_bin(ChainName), AuthenticatorID]), - clear_certs(CertsDir, Config), - ok; + CertsDir = certs_dir(ChainName, AuthenticatorID), + ok = clear_certs(CertsDir, Config); {error, Reason} -> {error, Reason} end; @@ -193,105 +191,33 @@ to_list(M) when M =:= #{} -> []; to_list(M) when is_map(M) -> [M]; to_list(L) when is_list(L) -> L. -certs_dir(Dirs) when is_list(Dirs) -> - to_bin(filename:join([emqx:get_config([node, data_dir]), "certs", "authn"] ++ Dirs)). - convert_certs(CertsDir, Config) -> - case maps:get(<<"ssl">>, Config, undefined) of - undefined -> - Config; - SSLOpts -> - NSSLOPts = lists:foldl(fun(K, Acc) -> - case maps:get(K, Acc, undefined) of - undefined -> Acc; - PemBin -> - CertFile = generate_filename(CertsDir, K), - ok = save_cert_to_file(CertFile, PemBin), - Acc#{K => CertFile} - end - end, SSLOpts, [<<"certfile">>, <<"keyfile">>, <<"cacertfile">>]), - Config#{<<"ssl">> => NSSLOPts} + case emqx_tls_lib:ensure_ssl_files(CertsDir, maps:get(<<"ssl">>, Config, undefined)) of + {ok, SSL} -> + new_ssl_config(Config, SSL); + {error, Reason} -> + ?SLOG(error, Reason#{msg => bad_ssl_config}), + throw(bad_ssl_config) end. convert_certs(CertsDir, NewConfig, OldConfig) -> - case maps:get(<<"ssl">>, NewConfig, undefined) of - undefined -> - NewConfig; - NewSSLOpts -> - OldSSLOpts = maps:get(<<"ssl">>, OldConfig, #{}), - Diff = diff_certs(NewSSLOpts, OldSSLOpts), - NSSLOpts = lists:foldl(fun({identical, K}, Acc) -> - Acc#{K => maps:get(K, OldSSLOpts)}; - ({_, K}, Acc) -> - CertFile = generate_filename(CertsDir, K), - ok = save_cert_to_file(CertFile, maps:get(K, NewSSLOpts)), - Acc#{K => CertFile} - end, NewSSLOpts, Diff), - NewConfig#{<<"ssl">> => NSSLOpts} + OldSSL = maps:get(<<"ssl">>, OldConfig, undefined), + NewSSL = maps:get(<<"ssl">>, NewConfig, undefined), + case emqx_tls_lib:ensure_ssl_files(CertsDir, NewSSL) of + {ok, NewSSL1} -> + ok = emqx_tls_lib:delete_ssl_files(CertsDir, NewSSL1, OldSSL), + new_ssl_config(NewConfig, NewSSL1); + {error, Reason} -> + ?SLOG(error, Reason#{msg => bad_ssl_config}), + throw(bad_ssl_config) end. +new_ssl_config(Config, undefined) -> Config; +new_ssl_config(Config, SSL) -> Config#{<<"ssl">> => SSL}. + clear_certs(CertsDir, Config) -> - case maps:get(<<"ssl">>, Config, undefined) of - undefined -> - ok; - SSLOpts -> - lists:foreach( - fun({_, Filename}) -> - _ = file:delete(filename:join([CertsDir, Filename])) - end, - maps:to_list(maps:with([<<"certfile">>, <<"keyfile">>, <<"cacertfile">>], SSLOpts))) - end. - -save_cert_to_file(Filename, PemBin) -> - case public_key:pem_decode(PemBin) =/= [] of - true -> - case filelib:ensure_dir(Filename) of - ok -> - case file:write_file(Filename, PemBin) of - ok -> ok; - {error, Reason} -> error({save_cert_to_file, {write_file, Reason}}) - end; - {error, Reason} -> - error({save_cert_to_file, {ensure_dir, Reason}}) - end; - false -> - error({save_cert_to_file, invalid_certificate}) - end. - -generate_filename(CertsDir, Key) -> - Prefix = case Key of - <<"keyfile">> -> "key-"; - <<"certfile">> -> "cert-"; - <<"cacertfile">> -> "cacert-" - end, - to_bin(filename:join([CertsDir, Prefix ++ emqx_misc:gen_id() ++ ".pem"])). - -diff_certs(NewSSLOpts, OldSSLOpts) -> - Keys = [<<"cacertfile">>, <<"certfile">>, <<"keyfile">>], - CertPems = maps:with(Keys, NewSSLOpts), - CertFiles = maps:with(Keys, OldSSLOpts), - Diff = lists:foldl(fun({K, CertFile}, Acc) -> - case maps:find(K, CertPems) of - error -> Acc; - {ok, PemBin1} -> - {ok, PemBin2} = file:read_file(CertFile), - case diff_cert(PemBin1, PemBin2) of - true -> - [{changed, K} | Acc]; - false -> - [{identical, K} | Acc] - end - end - end, - [], maps:to_list(CertFiles)), - Added = [{added, K} || K <- maps:keys(maps:without(maps:keys(CertFiles), CertPems))], - Diff ++ Added. - -diff_cert(Pem1, Pem2) -> - cal_md5_for_cert(Pem1) =/= cal_md5_for_cert(Pem2). - -cal_md5_for_cert(Pem) -> - crypto:hash(md5, term_to_binary(public_key:pem_decode(Pem))). + OldSSL = maps:get(<<"ssl">>, Config, undefined), + ok = emqx_tls_lib:delete_ssl_files(CertsDir, undefined, OldSSL). split_by_id(ID, AuthenticatorsConfig) -> case lists:foldl( @@ -342,3 +268,9 @@ authn_type(#{<<"mechanism">> := M}) -> atom(M). atom(Bin) -> binary_to_existing_atom(Bin, utf8). + +%% The relative dir for ssl files. +certs_dir(ChainName, ID) when is_binary(ID) -> + filename:join([to_bin(ChainName), ID]); +certs_dir(ChainName, Config) when is_map(Config) -> + certs_dir(ChainName, authenticator_id(Config)). diff --git a/apps/emqx/src/emqx_config.erl b/apps/emqx/src/emqx_config.erl index 8202d5a6a..4632c45d2 100644 --- a/apps/emqx/src/emqx_config.erl +++ b/apps/emqx/src/emqx_config.erl @@ -277,7 +277,7 @@ init_load(SchemaMod, RawConf0) when is_map(RawConf0) -> maps:with(get_root_names(), RawConf0)). include_dirs() -> - [filename:join(application:get_env(emqx, data_dir, "data/"), "configs") ++ "/"]. + [filename:join(emqx:data_dir(), "configs")]. -spec check_config(module(), raw_config()) -> {AppEnvs, CheckedConf} when AppEnvs :: app_envs(), CheckedConf :: config(). diff --git a/apps/emqx/src/emqx_tls_lib.erl b/apps/emqx/src/emqx_tls_lib.erl index 3b3953b83..8e82d3f0c 100644 --- a/apps/emqx/src/emqx_tls_lib.erl +++ b/apps/emqx/src/emqx_tls_lib.erl @@ -16,6 +16,7 @@ -module(emqx_tls_lib). +%% version & cipher suites -export([ default_versions/0 , integral_versions/1 , default_ciphers/0 @@ -25,6 +26,15 @@ , all_ciphers/0 ]). +%% files +-export([ ensure_ssl_files/2 + , delete_ssl_files/3 + ]). + +-include("logger.hrl"). + +-define(SSL_FILE_OPT_NAMES, [<<"keyfile">>, <<"certfile">>, <<"cacertfile">>]). + %% non-empty string -define(IS_STRING(L), (is_list(L) andalso L =/= [] andalso is_integer(hd(L)))). %% non-empty list of strings @@ -212,6 +222,129 @@ drop_tls13(SslOpts0) -> SslOpts1#{ciphers => Ciphers -- ?TLSV13_EXCLUSIVE_CIPHERS} end. +%% @doc The input map is a HOCON decoded result of a struct defined as +%% emqx_schema:server_ssl_opts_schema. (NOTE: before schema-checked). +%% `keyfile', `certfile' and `cacertfile' can be either pem format key or certificates, +%% or file path. +%% When PEM format key or certificate is given, it tries to to save them in the given +%% sub-dir in emqx's data_dir, and replace saved file paths for SSL options. +-spec ensure_ssl_files(file:name_all(), undefined | map()) -> + {ok, undefined | map()} | {error, map()}. +ensure_ssl_files(Dir, Opts) -> + ensure_ssl_files(Dir, Opts, _DryRun = false). + +ensure_ssl_files(_Dir, undefined, _DryRun) -> {ok, undefined}; +ensure_ssl_files(_Dir, #{<<"enable">> := false} = Opts, _DryRun) -> {ok, Opts}; +ensure_ssl_files(Dir, Opts, DryRun) -> + ensure_ssl_files(Dir, Opts, ?SSL_FILE_OPT_NAMES, DryRun). + +ensure_ssl_files(_Dir,Opts, [], _DryRun) -> {ok, Opts}; +ensure_ssl_files(Dir, Opts, [Key | Keys], DryRun) -> + case ensure_ssl_file(Dir, Key, Opts, maps:get(Key, Opts, undefined), DryRun) of + {ok, NewOpts} -> + ensure_ssl_files(Dir, NewOpts, Keys, DryRun); + {error, Reason} -> + {error, Reason#{which_option => Key}} + end. + +%% @doc Compare old and new config, delete the ones in old but not in new. +-spec delete_ssl_files(file:name_all(), undefiend | map(), undefined | map()) -> ok. +delete_ssl_files(Dir, NewOpts0, OldOpts0) -> + DryRun = true, + {ok, NewOpts} = ensure_ssl_files(Dir, NewOpts0, DryRun), + {ok, OldOpts} = ensure_ssl_files(Dir, OldOpts0, DryRun), + Get = fun(_K, undefined) -> undefined; + (K, Opts) -> maps:get(K, Opts, undefined) + end, + lists:foreach(fun(Key) -> delete_old_file(Get(Key, NewOpts), Get(Key, OldOpts)) end, + ?SSL_FILE_OPT_NAMES). + +delete_old_file(New, Old) when New =:= Old -> ok; +delete_old_file(_New, _Old = undefined) -> ok; +delete_old_file(_New, Old) -> + case filelib:is_regular(Old) andalso file:delete(Old) of + ok -> ok; + false -> ok; %% already deleted + {error, Reason} -> + ?SLOG(error, #{msg => "failed_to_delete_ssl_file", file_path => Old, reason => Reason}) + end. + +ensure_ssl_file(_Dir, _Key, Opts, undefined, _DryRun) -> + {ok, Opts}; +ensure_ssl_file(Dir, Key, Opts, MaybePem, DryRun) -> + case is_valid_string(MaybePem) of + true -> + do_ensure_ssl_file(Dir, Key, Opts, MaybePem, DryRun); + false -> + {error, #{reason => invalid_file_path_or_pem_string}} + end. + +do_ensure_ssl_file(Dir, Key, Opts, MaybePem, DryRun) -> + case is_pem(MaybePem) of + true -> + case save_pem_file(Dir, Key, MaybePem, DryRun) of + {ok, Path} -> {ok, Opts#{Key => Path}}; + {error, Reason} -> {error, Reason} + end; + false -> + case is_valid_pem_file(MaybePem) of + true -> {ok, Opts}; + {error, enoent} when DryRun -> {ok, Opts}; + {error, Reason} -> + {error, #{file_path => MaybePem, + reason => Reason + }} + end + end. + +is_valid_string(String) when is_list(String) -> + io_lib:printable_unicode_list(String); +is_valid_string(Binary) when is_binary(Binary) -> + case unicode:characters_to_list(Binary, utf8) of + String when is_list(String) -> is_valid_string(String); + _Otherwise -> false + end. + +%% Check if it is a valid PEM formated key. +is_pem(MaybePem) -> + try public_key:pem_decode(MaybePem) =/= [] + catch _ : _ -> false + end. + +%% Write the pem file to the given dir. +%% To make it simple, the file is always overwritten. +%% Also a potentially half-written PEM file (e.g. due to power outage) +%% can be corrected with an overwrite. +save_pem_file(Dir, Key, Pem, DryRun) -> + Path = pem_file_name(Dir, Key, Pem), + case filelib:ensure_dir(Path) of + ok when DryRun -> + {ok, Path}; + ok -> + case file:write_file(Path, Pem) of + ok -> {ok, Path}; + {error, Reason} -> + {error, #{failed_to_write_file => Reason, file_path => Path}} + end; + {error, Reason} -> + {error, #{failed_to_create_dir_for => Path, reason => Reason}} + end. + +%% compute the filename for a PEM format key/certificate +%% the filename is prefixed by the option name without the 'file' part +%% and suffixed with the first 8 byets of base64 encode result of the PEM content's +%% md5 checksum. e.g. key-EKjjO9um, cert-TwuCW1vh, and cacert-6ZaWqNuC +pem_file_name(Dir, Key, Pem) -> + <> = base64:encode(crypto:hash(md5, Pem)), + FileName = binary:replace(Key, <<"file">>, <<"-", CK/binary>>), + filename:join([emqx:data_dir(), Dir, FileName]). + +is_valid_pem_file(Path) -> + case file:read_file(Path) of + {ok, Pem} -> is_pem(Pem) orelse {error, not_pem}; + {error, Reason} -> {error, Reason} + end. + -if(?OTP_RELEASE > 22). -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). diff --git a/apps/emqx/test/emqx_authentication_SUITE.erl b/apps/emqx/test/emqx_authentication_SUITE.erl index cf1514918..62224a87f 100644 --- a/apps/emqx/test/emqx_authentication_SUITE.erl +++ b/apps/emqx/test/emqx_authentication_SUITE.erl @@ -97,17 +97,10 @@ end_per_suite(_) -> ok. init_per_testcase(Case, Config) -> - meck:new(emqx, [non_strict, passthrough, no_history, no_link]), - meck:expect(emqx, get_config, fun([node, data_dir]) -> - {data_dir, Data} = lists:keyfind(data_dir, 1, Config), - Data; - (C) -> meck:passthrough([C]) - end), ?MODULE:Case({'init', Config}). end_per_testcase(Case, Config) -> _ = ?MODULE:Case({'end', Config}), - meck:unload(emqx), ok. t_chain({_, Config}) -> Config; @@ -119,7 +112,7 @@ t_chain(Config) when is_list(Config) -> ?assertEqual({error, {already_exists, {chain, ChainName}}}, ?AUTHN:create_chain(ChainName)), ?assertMatch({ok, #{name := ChainName, authenticators := []}}, ?AUTHN:lookup_chain(ChainName)), ?assertMatch({ok, [#{name := ChainName}]}, ?AUTHN:list_chains()), - ?assertEqual(ok, ?AUTHN:delete_chain(ChainName)), + ?assertEqual(ok, ?AUTHN:delete_chain(ChainName)), ?assertMatch({error, {not_found, {chain, ChainName}}}, ?AUTHN:lookup_chain(ChainName)), ok. @@ -273,13 +266,11 @@ t_convert_certs(Config) when is_list(Config) -> CertsDir = certs_dir(Config, [Global, <<"password-based:built-in-database">>]), #{<<"ssl">> := NCerts} = convert_certs(CertsDir, #{<<"ssl">> => Certs}), - ?assertEqual(false, diff_cert(maps:get(<<"keyfile">>, NCerts), maps:get(<<"keyfile">>, Certs))), Certs2 = certs([ {<<"keyfile">>, "key.pem"} , {<<"certfile">>, "cert.pem"} ]), #{<<"ssl">> := NCerts2} = convert_certs(CertsDir, #{<<"ssl">> => Certs2}, #{<<"ssl">> => NCerts}), - ?assertEqual(false, diff_cert(maps:get(<<"keyfile">>, NCerts2), maps:get(<<"keyfile">>, Certs2))), ?assertEqual(maps:get(<<"keyfile">>, NCerts), maps:get(<<"keyfile">>, NCerts2)), ?assertEqual(maps:get(<<"certfile">>, NCerts), maps:get(<<"certfile">>, NCerts2)), @@ -288,7 +279,6 @@ t_convert_certs(Config) when is_list(Config) -> , {<<"cacertfile">>, "cacert.pem"} ]), #{<<"ssl">> := NCerts3} = convert_certs(CertsDir, #{<<"ssl">> => Certs3}, #{<<"ssl">> => NCerts2}), - ?assertEqual(false, diff_cert(maps:get(<<"keyfile">>, NCerts3), maps:get(<<"keyfile">>, Certs3))), ?assertNotEqual(maps:get(<<"keyfile">>, NCerts2), maps:get(<<"keyfile">>, NCerts3)), ?assertNotEqual(maps:get(<<"certfile">>, NCerts2), maps:get(<<"certfile">>, NCerts3)), @@ -306,10 +296,6 @@ certs(Certs) -> Acc#{Key => Bin} end, #{}, Certs). -diff_cert(CertFile, CertPem2) -> - {ok, CertPem1} = file:read_file(CertFile), - emqx_authentication_config:diff_cert(CertPem1, CertPem2). - register_provider(Type, Module) -> ok = ?AUTHN:register_providers([{Type, Module}]). diff --git a/apps/emqx/test/emqx_tls_lib_tests.erl b/apps/emqx/test/emqx_tls_lib_tests.erl index 4748f5854..514c365e7 100644 --- a/apps/emqx/test/emqx_tls_lib_tests.erl +++ b/apps/emqx/test/emqx_tls_lib_tests.erl @@ -38,7 +38,7 @@ use_default_ciphers_test() -> ciphers_format_test_() -> String = ?TLS_13_CIPHER ++ "," ++ ?TLS_12_CIPHER, - Binary = iolist_to_binary(String), + Binary = bin(String), List = [?TLS_13_CIPHER, ?TLS_12_CIPHER], [ {"string", fun() -> test_cipher_format(String) end} , {"binary", fun() -> test_cipher_format(Binary) end} @@ -66,3 +66,93 @@ cipher_suites_no_duplication_test() -> AllCiphers = emqx_tls_lib:default_ciphers(), ?assertEqual(length(AllCiphers), length(lists:usort(AllCiphers))). +ssl_files_failure_test_() -> + [{"undefined_is_undefined", + fun() -> + ?assertEqual({ok, undefined}, + emqx_tls_lib:ensure_ssl_files("dir", undefined)) end}, + {"no_op_if_disabled", + fun() -> + Disabled = #{<<"enable">> => false, foo => bar}, + ?assertEqual({ok, Disabled}, + emqx_tls_lib:ensure_ssl_files("dir", Disabled)) end}, + {"enoent_key_file", + fun() -> + NonExistingFile = filename:join("/tmp", integer_to_list(erlang:system_time(microsecond))), + ?assertMatch({error, #{reason := enoent}}, + emqx_tls_lib:ensure_ssl_files("/tmp", #{<<"keyfile">> => NonExistingFile})) + end}, + {"bad_pem_string", + fun() -> + %% not valid unicode + ?assertMatch({error, #{reason := invalid_file_path_or_pem_string, which_option := <<"keyfile">>}}, + emqx_tls_lib:ensure_ssl_files("/tmp", #{<<"keyfile">> => <<255, 255>>})), + %% not printable + ?assertMatch({error, #{reason := invalid_file_path_or_pem_string}}, + emqx_tls_lib:ensure_ssl_files("/tmp", #{<<"keyfile">> => <<33, 22>>})), + TmpFile = filename:join("/tmp", integer_to_list(erlang:system_time(microsecond))), + try + ok = file:write_file(TmpFile, <<"not a valid pem">>), + ?assertMatch({error, #{file_path := _, reason := not_pem}}, + emqx_tls_lib:ensure_ssl_files("/tmp", #{<<"cacertfile">> => bin(TmpFile)})) + after + file:delete(TmpFile) + end + end} + ]. + +ssl_files_save_delete_test() -> + SSL0 = #{<<"keyfile">> => bin(test_key())}, + Dir = filename:join(["/tmp", "ssl-test-dir"]), + {ok, SSL} = emqx_tls_lib:ensure_ssl_files(Dir, SSL0), + File = maps:get(<<"keyfile">>, SSL), + ?assertMatch(<<"/tmp/ssl-test-dir/key-", _:8/binary>>, File), + ?assertEqual({ok, bin(test_key())}, file:read_file(File)), + %% no old file to delete + ok = emqx_tls_lib:delete_ssl_files(Dir, SSL, undefined), + ?assertEqual({ok, bin(test_key())}, file:read_file(File)), + %% old and new identical, no delete + ok = emqx_tls_lib:delete_ssl_files(Dir, SSL, SSL), + ?assertEqual({ok, bin(test_key())}, file:read_file(File)), + %% new is gone, delete old + ok = emqx_tls_lib:delete_ssl_files(Dir, undefined, SSL), + ?assertEqual({error, enoent}, file:read_file(File)), + %% test idempotence + ok = emqx_tls_lib:delete_ssl_files(Dir, undefined, SSL), + ok. + +ssl_file_replace_test() -> + SSL0 = #{<<"keyfile">> => bin(test_key())}, + SSL1 = #{<<"keyfile">> => bin(test_key2())}, + Dir = filename:join(["/tmp", "ssl-test-dir2"]), + {ok, SSL2} = emqx_tls_lib:ensure_ssl_files(Dir, SSL0), + {ok, SSL3} = emqx_tls_lib:ensure_ssl_files(Dir, SSL1), + File1 = maps:get(<<"keyfile">>, SSL2), + File2 = maps:get(<<"keyfile">>, SSL3), + ?assert(filelib:is_regular(File1)), + ?assert(filelib:is_regular(File2)), + %% delete old file (File1, in SSL2) + ok = emqx_tls_lib:delete_ssl_files(Dir, SSL3, SSL2), + ?assertNot(filelib:is_regular(File1)), + ?assert(filelib:is_regular(File2)), + ok. + +bin(X) -> iolist_to_binary(X). + +test_key() -> +""" +-----BEGIN EC PRIVATE KEY----- +MHQCAQEEICKTbbathzvD8zvgjL7qRHhW4alS0+j0Loo7WeYX9AxaoAcGBSuBBAAK +oUQDQgAEJBdF7MIdam5T4YF3JkEyaPKdG64TVWCHwr/plC0QzNVJ67efXwxlVGTo +ju0VBj6tOX1y6C0U+85VOM0UU5xqvw== +-----END EC PRIVATE KEY----- +""". + +test_key2() -> +""" +-----BEGIN EC PRIVATE KEY----- +MHQCAQEEID9UlIyAlLFw0irkRHX29N+ZGivGtDjlVJvATY3B0TTmoAcGBSuBBAAK +oUQDQgAEUwiarudRNAT25X11js8gE9G+q0GdsT53QJQjRtBO+rTwuCW1vhLzN0Ve +AbToUD4JmV9m/XwcSVH06ZaWqNuC5w== +-----END EC PRIVATE KEY----- +""".