Merge pull request #231 from noahshaw11/upgrade-crypto-functions-to-support-OTP-23

Upgrade crypto functions to support OTP 23
diff --git a/.travis.yml b/.travis.yml
index 271323e..b1cfa14 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,6 +3,7 @@
 notifications:
   email: false
 otp_release:
+  - 23.0
   - 22.0
   - 21.3
   - 21.2.3
diff --git a/rebar b/rebar
index 0d1980b..c57f7ad 100755
--- a/rebar
+++ b/rebar
Binary files differ
diff --git a/rebar.config b/rebar.config
index 2fc417e..3176eda 100644
--- a/rebar.config
+++ b/rebar.config
@@ -2,6 +2,7 @@
 {erl_opts, [debug_info,
             {platform_define, "^R15", 'gen_tcp_r15b_workaround'},
             {platform_define, "^(R14|R15|R16B-)", 'crypto_compatibility'},
+            {platform_define, "^(R14|R15|R16|17|18|19|20|21|22)", new_crypto_unavailable},
             {platform_define, "^(R14|R15|R16B|17)", 'rand_mod_unavailable'},
             {platform_define, "^(R14|R15|R16B|17)", 'sni_unavailable'},
             {platform_define, "^(R14|R15|R16)", 'map_unavailable'},
diff --git a/src/mochiweb_session.erl b/src/mochiweb_session.erl
index f89f19b..f288560 100644
--- a/src/mochiweb_session.erl
+++ b/src/mochiweb_session.erl
@@ -131,7 +131,7 @@
     crypto:aes_cfb_128_decrypt(Key, IV, Crypt).
 
 -spec gen_key(iolist(), iolist()) -> binary().
-gen_key(ExpirationTime, ServerKey)->
+gen_key(ExpirationTime, ServerKey) ->
     crypto:md5_mac(ServerKey, [ExpirationTime]).
 
 -spec gen_hmac(iolist(), binary(), iolist(), binary()) -> binary().
@@ -139,6 +139,7 @@
     crypto:sha_mac(Key, [ExpirationTime, Data, SessionKey]).
 
 -else.
+-ifdef(new_crypto_unavailable).
 -spec encrypt_data(binary(), binary()) -> binary().
 encrypt_data(Data, Key) ->
     IV = crypto:strong_rand_bytes(16),
@@ -150,13 +151,33 @@
     crypto:block_decrypt(aes_cfb128, Key, IV, Crypt).
 
 -spec gen_key(iolist(), iolist()) -> binary().
-gen_key(ExpirationTime, ServerKey)->
+gen_key(ExpirationTime, ServerKey) ->
     crypto:hmac(md5, ServerKey, [ExpirationTime]).
 
 -spec gen_hmac(iolist(), binary(), iolist(), binary()) -> binary().
 gen_hmac(ExpirationTime, Data, SessionKey, Key) ->
     crypto:hmac(sha, Key, [ExpirationTime, Data, SessionKey]).
 
+-else. % new crypto available (OTP 23+)
+-spec encrypt_data(binary(), binary()) -> binary().
+encrypt_data(Data, Key) ->
+    IV = crypto:strong_rand_bytes(16),
+    Crypt = crypto:crypto_one_time(aes_128_cfb128, Key, IV, Data, true),
+    <<IV/binary, Crypt/binary>>.
+
+-spec decrypt_data(binary(), binary()) -> binary().
+decrypt_data(<<IV:16/binary, Crypt/binary>>, Key) ->
+    crypto:crypto_one_time(aes_128_cfb128, Key, IV, Crypt, false).
+
+-spec gen_key(iolist(), iolist()) -> binary().
+gen_key(ExpirationTime, ServerKey) ->
+    crypto:mac(hmac, md5, ServerKey, [ExpirationTime]).
+
+-spec gen_hmac(iolist(), binary(), iolist(), binary()) -> binary().
+gen_hmac(ExpirationTime, Data, SessionKey, Key) ->
+    crypto:mac(hmac, sha, Key, [ExpirationTime, Data, SessionKey]).
+
+-endif.
 -endif.
 
 -ifdef(TEST).
diff --git a/src/mochiweb_socket.erl b/src/mochiweb_socket.erl
index 9aeca2a..7408469 100644
--- a/src/mochiweb_socket.erl
+++ b/src/mochiweb_socket.erl
@@ -17,8 +17,8 @@
 listen(Ssl, Port, Opts, SslOpts) ->
     case Ssl of
         true ->
-            Opts1 = add_unbroken_ciphers_default(Opts ++ SslOpts),
-            Opts2 = add_safe_protocol_versions(Opts1),
+            Opts1 = add_safe_protocol_versions(Opts),
+            Opts2 = add_unbroken_ciphers_default(Opts1 ++ SslOpts),
             case ssl:listen(Port, Opts2) of
                 {ok, ListenSocket} ->
                     {ok, {ssl, ListenSocket}};
@@ -29,11 +29,39 @@
             gen_tcp:listen(Port, Opts)
     end.
 
+-ifdef(new_crypto_unavailable).
 add_unbroken_ciphers_default(Opts) ->
     Default = filter_unsecure_cipher_suites(ssl:cipher_suites()),
     Ciphers = filter_broken_cipher_suites(proplists:get_value(ciphers, Opts, Default)),
     [{ciphers, Ciphers} | proplists:delete(ciphers, Opts)].
 
+%% Filter old map style cipher suites
+filter_unsecure_cipher_suites(Ciphers) ->
+    lists:filter(fun
+                    ({_,des_cbc,_}) -> false;
+                    ({_,_,md5}) -> false;
+                    (_) -> true
+                 end,
+                 Ciphers).
+
+-else.
+add_unbroken_ciphers_default(Opts) ->
+    %% add_safe_protocol_versions/1 must have been called to ensure a {versions, _} tuple is present
+    Versions = proplists:get_value(versions, Opts),
+    CipherSuites = lists:append([ssl:cipher_suites(all, Version) || Version <- Versions]),
+    Default = filter_unsecure_cipher_suites(CipherSuites),
+    Ciphers = filter_broken_cipher_suites(proplists:get_value(ciphers, Opts, Default)),
+    [{ciphers, Ciphers} | proplists:delete(ciphers, Opts)].
+
+%% Filter new map style cipher suites
+filter_unsecure_cipher_suites(Ciphers) ->
+    ssl:filter_cipher_suites(Ciphers, [
+        {key_exchange, fun(des_cbc) -> false; (_) -> true end},
+        {mac, fun(md5) -> false; (_) -> true end}
+    ]).
+
+-endif.
+
 filter_broken_cipher_suites(Ciphers) ->
 	case proplists:get_value(ssl_app, ssl:versions()) of
 		"5.3" ++ _ ->
@@ -44,14 +72,6 @@
             Ciphers
     end.
 
-filter_unsecure_cipher_suites(Ciphers) ->
-    lists:filter(fun
-                    ({_,des_cbc,_}) -> false;
-                    ({_,_,md5}) -> false;
-                    (_) -> true
-                 end,
-                 Ciphers).
-
 add_safe_protocol_versions(Opts) ->
     case proplists:is_defined(versions, Opts) of
         true ->