From cb16d84c66b6040ca668b2e23ad4e740a3f3d0af Mon Sep 17 00:00:00 2001
From: Andrew Thompson <andrew@hijacked.us>
Date: Mon, 21 Oct 2013 23:19:34 -0400
Subject: Implement 'honor_cipher_order' SSL server-side option

HonorCipherOrder as implemented in Apache, nginx, lighttpd, etc. This
instructs the server to prefer its own cipher ordering rather than the
client's and can help protect against things like BEAST while
maintaining compatability with clients which only support older ciphers.

This code is mostly written by Andrew Thompson, only the test case was
added by Andreas Schultz.
---
 lib/ssl/src/ssl.erl           |  8 ++++++--
 lib/ssl/src/ssl_handshake.erl | 10 ++++++++--
 lib/ssl/src/ssl_internal.hrl  |  5 ++++-
 3 files changed, 18 insertions(+), 5 deletions(-)

(limited to 'lib/ssl/src')

diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl
index a7fd9f5f81..4646468cb6 100644
--- a/lib/ssl/src/ssl.erl
+++ b/lib/ssl/src/ssl.erl
@@ -640,7 +640,8 @@ handle_options(Opts0, _Role) ->
 			make_next_protocol_selector(
 			  handle_option(client_preferred_next_protocols, Opts, undefined)),
 		    log_alert = handle_option(log_alert, Opts, true),
-		    server_name_indication = handle_option(server_name_indication, Opts, undefined)
+		    server_name_indication = handle_option(server_name_indication, Opts, undefined),
+		    honor_cipher_order = handle_option(honor_cipher_order, Opts, false)
 		   },
 
     CbInfo  = proplists:get_value(cb_info, Opts, {gen_tcp, tcp, tcp_closed, tcp_error}),
@@ -652,7 +653,8 @@ handle_options(Opts0, _Role) ->
 		  reuse_session, reuse_sessions, ssl_imp,
 		  cb_info, renegotiate_at, secure_renegotiate, hibernate_after,
 		  erl_dist, next_protocols_advertised,
-		  client_preferred_next_protocols, log_alert, server_name_indication],
+		  client_preferred_next_protocols, log_alert,
+		  server_name_indication, honor_cipher_order],
 
     SockOpts = lists:foldl(fun(Key, PropList) ->
 				   proplists:delete(Key, PropList)
@@ -840,6 +842,8 @@ validate_option(server_name_indication, disable) ->
     disable;
 validate_option(server_name_indication, undefined) ->
     undefined;
+validate_option(honor_cipher_order, Value) when is_boolean(Value) ->
+    Value;
 validate_option(Opt, Value) ->
     throw({error, {options, {Opt, Value}}}).
 
diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl
index f5c0034f1b..62de49a349 100644
--- a/lib/ssl/src/ssl_handshake.erl
+++ b/lib/ssl/src/ssl_handshake.erl
@@ -1029,14 +1029,15 @@ cipher_suites(Suites, true) ->
 
 select_session(SuggestedSessionId, CipherSuites, Compressions, Port, #session{ecc = ECCCurve} = 
 		   Session, Version,
-	       #ssl_options{ciphers = UserSuites} = SslOpts, Cache, CacheCb, Cert) ->
+	       #ssl_options{ciphers = UserSuites, honor_cipher_order = HCO} = SslOpts,
+	       Cache, CacheCb, Cert) ->
     {SessionId, Resumed} = ssl_session:server_id(Port, SuggestedSessionId,
 						 SslOpts, Cert,
 						 Cache, CacheCb),
     case Resumed of
         undefined ->
 	    Suites = available_suites(Cert, UserSuites, Version, ECCCurve),
-	    CipherSuite = select_cipher_suite(CipherSuites, Suites),
+	    CipherSuite = select_cipher_suite(CipherSuites, Suites, HCO),
 	    Compression = select_compression(Compressions),
 	    {new, Session#session{session_id = SessionId,
 				  cipher_suite = CipherSuite,
@@ -1792,6 +1793,11 @@ handle_srp_extension(#srp{username = Username}, Session) ->
 
 %%-------------Misc --------------------------------
 
+select_cipher_suite(CipherSuites, Suites, false) ->
+    select_cipher_suite(CipherSuites, Suites);
+select_cipher_suite(CipherSuites, Suites, true) ->
+    select_cipher_suite(Suites, CipherSuites).
+
 select_cipher_suite([], _) ->
    no_suite;
 select_cipher_suite([Suite | ClientSuites], SupportedSuites) ->
diff --git a/lib/ssl/src/ssl_internal.hrl b/lib/ssl/src/ssl_internal.hrl
index 0186f9fca2..5a823ec8a4 100644
--- a/lib/ssl/src/ssl_internal.hrl
+++ b/lib/ssl/src/ssl_internal.hrl
@@ -114,7 +114,10 @@
 	  next_protocols_advertised = undefined, %% [binary()],
 	  next_protocol_selector = undefined,  %% fun([binary()]) -> binary())
 	  log_alert             :: boolean(),
-	  server_name_indication = undefined
+	  server_name_indication = undefined,
+	  %% Should the server prefer its own cipher order over the one provided by
+	  %% the client?
+	  honor_cipher_order = false
 	  }).
 
 -record(config, {ssl,               %% SSL parameters
-- 
cgit v1.2.3