Merge smarter certificate selection for pre-TLS-1.2 clients
diff --git a/ChangeLog b/ChangeLog
index 0ea522e..32e20bd 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -29,6 +29,8 @@
      length of an X.509 verification chain.
    * Support for renegotiation can now be disabled at compile-time
    * Support for 1/n-1 record splitting, a countermeasure against BEAST.
+   * Certificate selection based on signature hash, prefering SHA-1 over SHA-2
+     for pre-1.2 clients when multiple certificates are available.
 
 Bugfix
    * Stack buffer overflow if ctr_drbg_update() is called with too large
@@ -51,6 +53,9 @@
    * debug_print_buf() now prints a text view in addition to hexadecimal.
    * Skip writing and parsing signature_algorithm extension if none of the
      key exchanges enabled needs certificates.
+   * A specific error is now returned when there are ciphersuites in common
+     but none of them is usable due to external factors such as no certificate
+     with a suitable (extended)KeyUsage or curve or no PSK set.
 
 = PolarSSL 1.3.9 released 2014-10-20
 Security
diff --git a/include/polarssl/error.h b/include/polarssl/error.h
index 7ce2828..6a1143f 100644
--- a/include/polarssl/error.h
+++ b/include/polarssl/error.h
@@ -91,7 +91,7 @@
  * ECP       4   8 (Started from top)
  * MD        5   4
  * CIPHER    6   6
- * SSL       6   10 (Started from top)
+ * SSL       6   11 (Started from top)
  * SSL       7   31
  *
  * Module dependent error code (5 bits 0x.00.-0x.F8.)
diff --git a/include/polarssl/ssl.h b/include/polarssl/ssl.h
index 5624ebe..0c5d8b2 100644
--- a/include/polarssl/ssl.h
+++ b/include/polarssl/ssl.h
@@ -152,6 +152,7 @@
 #define POLARSSL_ERR_SSL_INTERNAL_ERROR                    -0x6C00  /**< Internal error (eg, unexpected failure in lower-level module) */
 #define POLARSSL_ERR_SSL_COUNTER_WRAPPING                  -0x6B80  /**< A counter would wrap (eg, too many messages exchanged). */
 #define POLARSSL_ERR_SSL_WAITING_SERVER_HELLO_RENEGO       -0x6B00  /**< Unexpected message at ServerHello in renegotiation. */
+#define POLARSSL_ERR_SSL_NO_USABLE_CIPHERSUITE             -0x6A80  /**< None of the common ciphersuites is usable (eg, no suitable certificate) */
 
 /*
  * Various constants
diff --git a/library/error.c b/library/error.c
index 73504d4..0095636 100644
--- a/library/error.c
+++ b/library/error.c
@@ -452,6 +452,8 @@
             snprintf( buf, buflen, "SSL - A counter would wrap (eg, too many messages exchanged)" );
         if( use_ret == -(POLARSSL_ERR_SSL_WAITING_SERVER_HELLO_RENEGO) )
             snprintf( buf, buflen, "SSL - Unexpected message at ServerHello in renegotiation" );
+        if( use_ret == -(POLARSSL_ERR_SSL_NO_USABLE_CIPHERSUITE) )
+            snprintf( buf, buflen, "SSL - None of the common ciphersuites is usable (eg, no suitable certificate)" );
 #endif /* POLARSSL_SSL_TLS_C */
 
 #if defined(POLARSSL_X509_USE_C) || defined(POLARSSL_X509_CREATE_C)
diff --git a/library/ssl_srv.c b/library/ssl_srv.c
index 4fac0e4..4420e22 100644
--- a/library/ssl_srv.c
+++ b/library/ssl_srv.c
@@ -801,11 +801,11 @@
 
 #if defined(POLARSSL_X509_CRT_PARSE_C)
 /*
- * Return 1 if the given EC key uses the given curve, 0 otherwise
+ * Return 0 if the given key uses one of the acceptable curves, -1 otherwise
  */
 #if defined(POLARSSL_ECDSA_C)
-static int ssl_key_matches_curves( pk_context *pk,
-                                   const ecp_curve_info **curves )
+static int ssl_check_key_curve( pk_context *pk,
+                                const ecp_curve_info **curves )
 {
     const ecp_curve_info **crv = curves;
     ecp_group_id grp_id = pk_ec( *pk )->grp.id;
@@ -813,11 +813,11 @@
     while( *crv != NULL )
     {
         if( (*crv)->grp_id == grp_id )
-            return( 1 );
+            return( 0 );
         crv++;
     }
 
-    return( 0 );
+    return( -1 );
 }
 #endif /* POLARSSL_ECDSA_C */
 
@@ -828,7 +828,7 @@
 static int ssl_pick_cert( ssl_context *ssl,
                           const ssl_ciphersuite_t * ciphersuite_info )
 {
-    ssl_key_cert *cur, *list;
+    ssl_key_cert *cur, *list, *fallback = NULL;
     pk_type_t pk_alg = ssl_get_ciphersuite_sig_pk_alg( ciphersuite_info );
 
 #if defined(POLARSSL_SSL_SERVER_NAME_INDICATION)
@@ -861,21 +861,41 @@
         }
 
 #if defined(POLARSSL_ECDSA_C)
-        if( pk_alg == POLARSSL_PK_ECDSA )
-        {
-            if( ssl_key_matches_curves( cur->key, ssl->handshake->curves ) )
-                break;
-        }
-        else
+        if( pk_alg == POLARSSL_PK_ECDSA &&
+            ssl_check_key_curve( cur->key, ssl->handshake->curves ) != 0 )
+            continue;
 #endif
-            break;
+
+        /*
+         * Try to select a SHA-1 certificate for pre-1.2 clients, but still
+         * present them a SHA-higher cert rather than failing if it's the only
+         * one we got that satisfies the other conditions.
+         */
+        if( ssl->minor_ver < SSL_MINOR_VERSION_3 &&
+            cur->cert->sig_md != POLARSSL_MD_SHA1 )
+        {
+            if( fallback == NULL )
+                fallback = cur;
+            continue;
+        }
+
+        /* If we get there, we got a winner */
+        break;
     }
 
-    if( cur == NULL )
-        return( -1 );
+    if( cur != NULL )
+    {
+        ssl->handshake->key_cert = cur;
+        return( 0 );
+    }
 
-    ssl->handshake->key_cert = cur;
-    return( 0 );
+    if( fallback != NULL )
+    {
+        ssl->handshake->key_cert = fallback;
+        return( 0 );
+    }
+
+    return( -1 );
 }
 #endif /* POLARSSL_X509_CRT_PARSE_C */
 
@@ -891,8 +911,8 @@
     suite_info = ssl_ciphersuite_from_id( suite_id );
     if( suite_info == NULL )
     {
-        SSL_DEBUG_MSG( 1, ( "ciphersuite info for %04x not found", suite_id ) );
-        return( POLARSSL_ERR_SSL_BAD_INPUT_DATA );
+        SSL_DEBUG_MSG( 1, ( "should never happen" ) );
+        return( POLARSSL_ERR_SSL_INTERNAL_ERROR );
     }
 
     if( suite_info->min_minor_ver > ssl->minor_ver ||
@@ -935,7 +955,7 @@
 #if defined(POLARSSL_SSL_SRV_SUPPORT_SSLV2_CLIENT_HELLO)
 static int ssl_parse_client_hello_v2( ssl_context *ssl )
 {
-    int ret;
+    int ret, got_common_suite;
     unsigned int i, j;
     size_t n;
     unsigned int ciph_len, sess_len, chal_len;
@@ -1133,6 +1153,7 @@
     }
 #endif /* POLARSSL_SSL_FALLBACK_SCSV */
 
+    got_common_suite = 0;
     ciphersuites = ssl->ciphersuite_list[ssl->minor_ver];
     ciphersuite_info = NULL;
 #if defined(POLARSSL_SSL_SRV_RESPECT_CLIENT_PREFERENCE)
@@ -1150,6 +1171,8 @@
                 p[2] != ( ( ciphersuites[i]      ) & 0xFF ) )
                 continue;
 
+            got_common_suite = 1;
+
             if( ( ret = ssl_ciphersuite_match( ssl, ciphersuites[i],
                                                &ciphersuite_info ) ) != 0 )
                 return( ret );
@@ -1159,9 +1182,17 @@
         }
     }
 
-    SSL_DEBUG_MSG( 1, ( "got no ciphersuites in common" ) );
-
-    return( POLARSSL_ERR_SSL_NO_CIPHER_CHOSEN );
+    if( got_common_suite )
+    {
+        SSL_DEBUG_MSG( 1, ( "got ciphersuites in common, "
+                            "but none of them usable" ) );
+        return( POLARSSL_ERR_SSL_NO_USABLE_CIPHERSUITE );
+    }
+    else
+    {
+        SSL_DEBUG_MSG( 1, ( "got no ciphersuites in common" ) );
+        return( POLARSSL_ERR_SSL_NO_CIPHER_CHOSEN );
+    }
 
 have_ciphersuite_v2:
     ssl->session_negotiate->ciphersuite = ciphersuites[i];
@@ -1193,7 +1224,7 @@
 
 static int ssl_parse_client_hello( ssl_context *ssl )
 {
-    int ret;
+    int ret, got_common_suite;
     unsigned int i, j;
     size_t n;
     unsigned int ciph_len, sess_len;
@@ -1675,6 +1706,7 @@
      * (At the end because we need information from the EC-based extensions
      * and certificate from the SNI callback triggered by the SNI extension.)
      */
+    got_common_suite = 0;
     ciphersuites = ssl->ciphersuite_list[ssl->minor_ver];
     ciphersuite_info = NULL;
 #if defined(POLARSSL_SSL_SRV_RESPECT_CLIENT_PREFERENCE)
@@ -1691,6 +1723,8 @@
                 p[1] != ( ( ciphersuites[i]      ) & 0xFF ) )
                 continue;
 
+            got_common_suite = 1;
+
             if( ( ret = ssl_ciphersuite_match( ssl, ciphersuites[i],
                                                &ciphersuite_info ) ) != 0 )
                 return( ret );
@@ -1700,12 +1734,19 @@
         }
     }
 
-    SSL_DEBUG_MSG( 1, ( "got no ciphersuites in common" ) );
-
-    if( ( ret = ssl_send_fatal_handshake_failure( ssl ) ) != 0 )
-        return( ret );
-
-    return( POLARSSL_ERR_SSL_NO_CIPHER_CHOSEN );
+    if( got_common_suite )
+    {
+        SSL_DEBUG_MSG( 1, ( "got ciphersuites in common, "
+                            "but none of them usable" ) );
+        ssl_send_fatal_handshake_failure( ssl );
+        return( POLARSSL_ERR_SSL_NO_USABLE_CIPHERSUITE );
+    }
+    else
+    {
+        SSL_DEBUG_MSG( 1, ( "got no ciphersuites in common" ) );
+        ssl_send_fatal_handshake_failure( ssl );
+        return( POLARSSL_ERR_SSL_NO_CIPHER_CHOSEN );
+    }
 
 have_ciphersuite:
     ssl->session_negotiate->ciphersuite = ciphersuites[i];
diff --git a/scripts/generate_errors.pl b/scripts/generate_errors.pl
index 0ee992d..b9a8e9c 100755
--- a/scripts/generate_errors.pl
+++ b/scripts/generate_errors.pl
@@ -46,7 +46,7 @@
 
 $/ = $line_separator;
 
-open(GREP, "/bin/grep \"define POLARSSL_ERR_\" $include_dir/* |") || die("Failure when calling grep: $!");
+open(GREP, "grep \"define POLARSSL_ERR_\" $include_dir/* |") || die("Failure when calling grep: $!");
 
 my $ll_old_define = "";
 my $hl_old_define = "";
diff --git a/tests/ssl-opt.sh b/tests/ssl-opt.sh
index 8bf7d97..42e4eee 100755
--- a/tests/ssl-opt.sh
+++ b/tests/ssl-opt.sh
@@ -1404,6 +1404,60 @@
             -C "! ssl_handshake returned" \
             -S "X509 - Certificate verification failed"
 
+# Tests for certificate selection based on SHA verson
+
+run_test    "Certificate hash: client TLS 1.2 -> SHA-2" \
+            "$P_SRV crt_file=data_files/server5.crt \
+                    key_file=data_files/server5.key \
+                    crt_file2=data_files/server5-sha1.crt \
+                    key_file2=data_files/server5.key" \
+            "$P_CLI force_version=tls1_2" \
+            0 \
+            -c "signed using.*ECDSA with SHA256" \
+            -C "signed using.*ECDSA with SHA1"
+
+run_test    "Certificate hash: client TLS 1.1 -> SHA-1" \
+            "$P_SRV crt_file=data_files/server5.crt \
+                    key_file=data_files/server5.key \
+                    crt_file2=data_files/server5-sha1.crt \
+                    key_file2=data_files/server5.key" \
+            "$P_CLI force_version=tls1_1" \
+            0 \
+            -C "signed using.*ECDSA with SHA256" \
+            -c "signed using.*ECDSA with SHA1"
+
+run_test    "Certificate hash: client TLS 1.0 -> SHA-1" \
+            "$P_SRV crt_file=data_files/server5.crt \
+                    key_file=data_files/server5.key \
+                    crt_file2=data_files/server5-sha1.crt \
+                    key_file2=data_files/server5.key" \
+            "$P_CLI force_version=tls1" \
+            0 \
+            -C "signed using.*ECDSA with SHA256" \
+            -c "signed using.*ECDSA with SHA1"
+
+run_test    "Certificate hash: client TLS 1.1, no SHA-1 -> SHA-2 (order 1)" \
+            "$P_SRV crt_file=data_files/server5.crt \
+                    key_file=data_files/server5.key \
+                    crt_file2=data_files/server6.crt \
+                    key_file2=data_files/server6.key" \
+            "$P_CLI force_version=tls1_1" \
+            0 \
+            -c "serial number.*09" \
+            -c "signed using.*ECDSA with SHA256" \
+            -C "signed using.*ECDSA with SHA1"
+
+run_test    "Certificate hash: client TLS 1.1, no SHA-1 -> SHA-2 (order 2)" \
+            "$P_SRV crt_file=data_files/server6.crt \
+                    key_file=data_files/server6.key \
+                    crt_file2=data_files/server5.crt \
+                    key_file2=data_files/server5.key" \
+            "$P_CLI force_version=tls1_1" \
+            0 \
+            -c "serial number.*0A" \
+            -c "signed using.*ECDSA with SHA256" \
+            -C "signed using.*ECDSA with SHA1"
+
 # tests for SNI
 
 run_test    "SNI: no SNI callback" \
@@ -1956,7 +2010,7 @@
             "$P_CLI force_ciphersuite=TLS-PSK-WITH-AES-128-CBC-SHA \
             psk_identity=foo psk=abc123" \
             0 \
-            -S "SSL - The server has no ciphersuites in common" \
+            -S "SSL - None of the common ciphersuites is usable" \
             -S "SSL - Unknown identity received" \
             -S "SSL - Verification of the message MAC failed"
 
@@ -1965,7 +2019,7 @@
             "$P_CLI force_ciphersuite=TLS-PSK-WITH-AES-128-CBC-SHA \
             psk_identity=foo psk=abc123" \
             1 \
-            -s "SSL - The server has no ciphersuites in common" \
+            -s "SSL - None of the common ciphersuites is usable" \
             -S "SSL - Unknown identity received" \
             -S "SSL - Verification of the message MAC failed"
 
@@ -1974,7 +2028,7 @@
             "$P_CLI force_ciphersuite=TLS-PSK-WITH-AES-128-CBC-SHA \
             psk_identity=foo psk=abc123" \
             1 \
-            -S "SSL - The server has no ciphersuites in common" \
+            -S "SSL - None of the common ciphersuites is usable" \
             -s "SSL - Unknown identity received" \
             -S "SSL - Verification of the message MAC failed"
 
@@ -1983,7 +2037,7 @@
             "$P_CLI force_ciphersuite=TLS-PSK-WITH-AES-128-CBC-SHA \
             psk_identity=abc psk=dead" \
             0 \
-            -S "SSL - The server has no ciphersuites in common" \
+            -S "SSL - None of the common ciphersuites is usable" \
             -S "SSL - Unknown identity received" \
             -S "SSL - Verification of the message MAC failed"
 
@@ -1992,7 +2046,7 @@
             "$P_CLI force_ciphersuite=TLS-PSK-WITH-AES-128-CBC-SHA \
             psk_identity=def psk=beef" \
             0 \
-            -S "SSL - The server has no ciphersuites in common" \
+            -S "SSL - None of the common ciphersuites is usable" \
             -S "SSL - Unknown identity received" \
             -S "SSL - Verification of the message MAC failed"
 
@@ -2001,7 +2055,7 @@
             "$P_CLI force_ciphersuite=TLS-PSK-WITH-AES-128-CBC-SHA \
             psk_identity=ghi psk=beef" \
             1 \
-            -S "SSL - The server has no ciphersuites in common" \
+            -S "SSL - None of the common ciphersuites is usable" \
             -s "SSL - Unknown identity received" \
             -S "SSL - Verification of the message MAC failed"
 
@@ -2010,7 +2064,7 @@
             "$P_CLI force_ciphersuite=TLS-PSK-WITH-AES-128-CBC-SHA \
             psk_identity=abc psk=beef" \
             1 \
-            -S "SSL - The server has no ciphersuites in common" \
+            -S "SSL - None of the common ciphersuites is usable" \
             -S "SSL - Unknown identity received" \
             -s "SSL - Verification of the message MAC failed"