The Safari browser on OSX versions 10.8 to 10.8.3 advertises support for
several ECDHE-ECDSA ciphers but fails to negotiate them.
When a Safari client connects to an OpenSSL-based server that has the
attached patch (against the "master" branch) applied, the server will
prefer other mutually supported ciphers above ECDHE-ECDSA ciphers.
This patch enables a webserver to have an ECC certificate together with
an RSA and/or DSA certificate, and to offer ECDHE-ECDSA ciphers without
fear of breaking compatibility with Safari clients.
The ssl_check_for_safari() function, which fingerprints Safari clients
based on the TLS Extensions used, was written by Adam Langley.
A representative from Apple has told me that the Safari bug will be
fixed in OSX 10.8.4. However, since OSX users won't be forced to
upgrade, I believe that a significant number of users will still be
using affected Safari versions a few years from now.
--
Rob Stradling
Senior Research & Development Scientist
COMODO - Creating Trust Online
diff --git a/ssl/s3_lib.c b/ssl/s3_lib.c
index 7ad8a54..fff73eb 100644
--- a/ssl/s3_lib.c
+++ b/ssl/s3_lib.c
@@ -3076,7 +3076,10 @@ void ssl3_clear(SSL *s)
OPENSSL_free(s->s3->tlsext_authz_client_types);
s->s3->tlsext_authz_client_types = NULL;
}
-#endif
+#ifndef OPENSSL_NO_EC
+ s->s3->is_probably_safari = 0;
+#endif /* OPENSSL_NO_EC */
+#endif /* OPENSSL_NO_TLSEXT */
rp = s->s3->rbuf.buf;
wp = s->s3->wbuf.buf;
@@ -4145,8 +4148,15 @@ SSL_CIPHER *ssl3_choose_cipher(SSL *s, STACK_OF(SSL_CIPHER) *clnt,
ii=sk_SSL_CIPHER_find(allow,c);
if (ii >= 0)
{
- ret=sk_SSL_CIPHER_value(allow,ii);
- break;
+ if ((alg_k & SSL_kEECDH) && (alg_a & SSL_aECDSA) && s->s3->is_probably_safari)
+ {
+ if (!ret) ret=sk_SSL_CIPHER_value(allow,ii);
+ }
+ else
+ {
+ ret=sk_SSL_CIPHER_value(allow,ii);
+ break;
+ }
}
}
return(ret);
diff --git a/ssl/ssl.h b/ssl/ssl.h
index e14a8d4..dd62578 100644
--- a/ssl/ssl.h
+++ b/ssl/ssl.h
@@ -568,6 +568,7 @@ struct ssl_session_st
#define SSL_OP_SSLEAY_080_CLIENT_DH_BUG 0x00000080L
#define SSL_OP_TLS_D5_BUG 0x00000100L
#define SSL_OP_TLS_BLOCK_PADDING_BUG 0x00000200L
+#define SSL_OP_SAFARI_ECDHE_ECDSA_BUG 0x00000400L
/* Disable SSL 3.0/TLS 1.0 CBC vulnerability workaround that was added
* in OpenSSL 0.9.6d. Usually (depending on the application protocol)
@@ -578,7 +579,7 @@ struct ssl_session_st
/* SSL_OP_ALL: various bug workarounds that should be rather harmless.
* This used to be 0x000FFFFFL before 0.9.7. */
-#define SSL_OP_ALL 0x80000BFFL
+#define SSL_OP_ALL 0x80000FFFL
/* DTLS options */
#define SSL_OP_NO_QUERY_MTU 0x00001000L
diff --git a/ssl/ssl3.h b/ssl/ssl3.h
index d8ed725..0b900b5 100644
--- a/ssl/ssl3.h
+++ b/ssl/ssl3.h
@@ -577,7 +577,14 @@ typedef struct ssl3_state_st
* server echoed our server_authz extension and therefore must send us
* a supplemental data handshake message. */
char tlsext_authz_server_promised;
-#endif
+
+#ifndef OPENSSL_NO_EC
+ /* This is set to true if we believe that this is a version of Safari
+ * running on OS X 10.6 .. 10.8. We wish to know this because Safari
+ * on 10.8 has broken ECDHE-ECDSA support. */
+ char is_probably_safari;
+#endif /* OPENSSL_NO_EC */
+#endif /* OPENSSL_NO_TLSEXT */
} SSL3_STATE;
#endif
diff --git a/ssl/t1_lib.c b/ssl/t1_lib.c
index 31daa50..c5e2b85 100644
--- a/ssl/t1_lib.c
+++ b/ssl/t1_lib.c
@@ -1716,6 +1716,89 @@ unsigned char *ssl_add_serverhello_tlsext(SSL *s, unsigned char *p, unsigned cha
return ret;
}
+#ifndef OPENSSL_NO_EC
+/* ssl_check_for_safari attempts to fingerprint Safari using OS X
+ * SecureTransport using the TLS extension block in |d|, of length |n|.
+ * Safari, since 10.6, sends exactly these extensions, in this order:
+ * SNI,
+ * elliptic_curves
+ * ec_point_formats
+ *
+ * We wish to fingerprint Safari because they broke ECDHE-ECDSA support in 10.8,
+ * but they advertise support. So enabling ECDHE-ECDSA ciphers breaks them.
+ * Sadly we cannot differentiate 10.6 and 10.7 (which work), from 10.8 (which
+ * doesn't).
+ */
+static void ssl_check_for_safari(SSL *s, const unsigned char *data, const unsigned char *d, int n) {
+ unsigned short type, size;
+ static const unsigned char kSafariExtensionsBlock[] = {
+ 0x00, 0x0a, /* elliptic_curves extension */
+ 0x00, 0x08, /* 8 bytes */
+ 0x00, 0x06, /* 6 bytes of curve ids */
+ 0x00, 0x17, /* P-256 */
+ 0x00, 0x18, /* P-384 */
+ 0x00, 0x19, /* P-521 */
+
+ 0x00, 0x0b, /* ec_point_formats */
+ 0x00, 0x02, /* 2 bytes */
+ 0x01, /* 1 point format */
+ 0x00, /* uncompressed */
+ };
+
+ /* The following is only present in TLS 1.2 */
+ static const unsigned char kSafariTLS12ExtensionsBlock[] = {
+ 0x00, 0x0d, /* signature_algorithms */
+ 0x00, 0x0c, /* 12 bytes */
+ 0x00, 0x0a, /* 10 bytes */
+ 0x05, 0x01, /* SHA-384/RSA */
+ 0x04, 0x01, /* SHA-256/RSA */
+ 0x02, 0x01, /* SHA-1/RSA */
+ 0x04, 0x03, /* SHA-256/ECDSA */
+ 0x02, 0x03, /* SHA-1/ECDSA */
+ };
+
+ if (data >= (d+n-2))
+ return;
+ data += 2;
+
+ if (data > (d+n-4))
+ return;
+ n2s(data,type);
+ n2s(data,size);
+
+ if (type != TLSEXT_TYPE_server_name)
+ return;
+
+ if (data+size > d+n)
+ return;
+ data += size;
+
+ if (TLS1_get_version(s) >= TLS1_2_VERSION)
+ {
+ const size_t len1 = sizeof(kSafariExtensionsBlock);
+ const size_t len2 = sizeof(kSafariTLS12ExtensionsBlock);
+
+ if (data + len1 + len2 != d+n)
+ return;
+ if (memcmp(data, kSafariExtensionsBlock, len1) != 0)
+ return;
+ if (memcmp(data + len1, kSafariTLS12ExtensionsBlock, len2) != 0)
+ return;
+ }
+ else
+ {
+ const size_t len = sizeof(kSafariExtensionsBlock);
+
+ if (data + len != d+n)
+ return;
+ if (memcmp(data, kSafariExtensionsBlock, len) != 0)
+ return;
+ }
+
+ s->s3->is_probably_safari = 1;
+}
+#endif /* OPENSSL_NO_EC */
+
static int ssl_scan_clienthello_tlsext(SSL *s, unsigned char **p, unsigned char *d, int n, int *al)
{
unsigned short type;
@@ -1735,6 +1818,12 @@ static int ssl_scan_clienthello_tlsext(SSL *s, unsigned char **p, unsigned char
s->tlsext_heartbeat &= ~(SSL_TLSEXT_HB_ENABLED |
SSL_TLSEXT_HB_DONT_SEND_REQUESTS);
#endif
+
+#ifndef OPENSSL_NO_EC
+ if (s->options & SSL_OP_SAFARI_ECDHE_ECDSA_BUG)
+ ssl_check_for_safari(s, data, d, n);
+#endif /* OPENSSL_NO_EC */
+
/* Clear any signature algorithms extension received */
if (s->cert->peer_sigalgs)
{