summaryrefslogtreecommitdiffstats
path: root/ssl/statem
diff options
context:
space:
mode:
authorViktor Dukhovni <openssl-users@dukhovni.org>2015-12-29 03:24:17 -0500
committerViktor Dukhovni <openssl-users@dukhovni.org>2016-01-02 10:49:06 -0500
commit4fa52141b08fca89250805afcf2f112a2e0d3500 (patch)
treeab8988a8267c6032f6a8b48846d12fb907930b3b /ssl/statem
parent57ce7b617c602ae8513c22daa2bda31f179edb0f (diff)
Protocol version selection and negotiation rewrite
The protocol selection code is now consolidated in a few consecutive short functions in a single file and is table driven. Protocol-specific constraints that influence negotiation are moved into the flags field of the method structure. The same protocol version constraints are now applied in all code paths. It is now much easier to add new protocol versions without reworking the protocol selection logic. In the presence of "holes" in the list of enabled client protocols we no longer select client protocols below the hole based on a subset of the constraints and then fail shortly after when it is found that these don't meet the remaining constraints (suiteb, FIPS, security level, ...). Ideally, with the new min/max controls users will be less likely to create "holes" in the first place. Reviewed-by: Kurt Roeckx <kurt@openssl.org>
Diffstat (limited to 'ssl/statem')
-rw-r--r--ssl/statem/statem.c15
-rw-r--r--ssl/statem/statem_clnt.c246
-rw-r--r--ssl/statem/statem_lib.c385
-rw-r--r--ssl/statem/statem_srvr.c112
4 files changed, 422 insertions, 336 deletions
diff --git a/ssl/statem/statem.c b/ssl/statem/statem.c
index bc3fc54fab..a03fe32083 100644
--- a/ssl/statem/statem.c
+++ b/ssl/statem/statem.c
@@ -261,7 +261,8 @@ static void (*get_callback(SSL *s))(const SSL *, int, int)
* 1: Success
* <=0: NBIO or error
*/
-static int state_machine(SSL *s, int server) {
+static int state_machine(SSL *s, int server)
+{
BUF_MEM *buf = NULL;
unsigned long Time = (unsigned long)time(NULL);
void (*cb) (const SSL *ssl, int type, int val) = NULL;
@@ -336,19 +337,15 @@ static int state_machine(SSL *s, int server) {
goto end;
}
} else {
- if ((s->version >> 8) != SSL3_VERSION_MAJOR
- && s->version != TLS_ANY_VERSION) {
+ if ((s->version >> 8) != SSL3_VERSION_MAJOR) {
SSLerr(SSL_F_STATE_MACHINE, ERR_R_INTERNAL_ERROR);
goto end;
}
}
- if (!SSL_IS_DTLS(s)) {
- if (s->version != TLS_ANY_VERSION &&
- !ssl_security(s, SSL_SECOP_VERSION, 0, s->version, NULL)) {
- SSLerr(SSL_F_STATE_MACHINE, SSL_R_VERSION_TOO_LOW);
- goto end;
- }
+ if (!ssl_security(s, SSL_SECOP_VERSION, 0, s->version, NULL)) {
+ SSLerr(SSL_F_STATE_MACHINE, SSL_R_VERSION_TOO_LOW);
+ goto end;
}
if (s->init_buf == NULL) {
diff --git a/ssl/statem/statem_clnt.c b/ssl/statem/statem_clnt.c
index 26acdc5488..cfbfa5f8c3 100644
--- a/ssl/statem/statem_clnt.c
+++ b/ssl/statem/statem_clnt.c
@@ -166,7 +166,6 @@
static ossl_inline int cert_req_allowed(SSL *s);
static int key_exchange_expected(SSL *s);
-static int ssl_set_version(SSL *s);
static int ca_dn_cmp(const X509_NAME *const *a, const X509_NAME *const *b);
static int ssl_cipher_list_to_bytes(SSL *s, STACK_OF(SSL_CIPHER) *sk,
unsigned char *p);
@@ -800,128 +799,12 @@ WORK_STATE ossl_statem_client_post_process_message(SSL *s, WORK_STATE wst)
return WORK_ERROR;
}
-/*
- * Work out what version we should be using for the initial ClientHello if
- * the version is currently set to (D)TLS_ANY_VERSION.
- * Returns 1 on success
- * Returns 0 on error
- */
-static int ssl_set_version(SSL *s)
-{
- unsigned long mask, options = s->options;
-
- if (s->method->version == TLS_ANY_VERSION) {
- /*
- * SSL_OP_NO_X disables all protocols above X *if* there are
- * some protocols below X enabled. This is required in order
- * to maintain "version capability" vector contiguous. So
- * that if application wants to disable TLS1.0 in favour of
- * TLS1>=1, it would be insufficient to pass SSL_NO_TLSv1, the
- * answer is SSL_OP_NO_TLSv1|SSL_OP_NO_SSLv3.
- */
- mask = SSL_OP_NO_TLSv1_1 | SSL_OP_NO_TLSv1
-#if !defined(OPENSSL_NO_SSL3)
- | SSL_OP_NO_SSLv3
-#endif
- ;
-#if !defined(OPENSSL_NO_TLS1_2_CLIENT)
- if (options & SSL_OP_NO_TLSv1_2) {
- if ((options & mask) != mask) {
- s->version = TLS1_1_VERSION;
- } else {
- SSLerr(SSL_F_SSL_SET_VERSION, SSL_R_NO_PROTOCOLS_AVAILABLE);
- return 0;
- }
- } else {
- s->version = TLS1_2_VERSION;
- }
-#else
- if ((options & mask) == mask) {
- SSLerr(SSL_F_SSL_SET_VERSION, SSL_R_NO_PROTOCOLS_AVAILABLE);
- return 0;
- }
- s->version = TLS1_1_VERSION;
-#endif
-
- mask &= ~SSL_OP_NO_TLSv1_1;
- if ((options & SSL_OP_NO_TLSv1_1) && (options & mask) != mask)
- s->version = TLS1_VERSION;
- mask &= ~SSL_OP_NO_TLSv1;
-#if !defined(OPENSSL_NO_SSL3)
- if ((options & SSL_OP_NO_TLSv1) && (options & mask) != mask)
- s->version = SSL3_VERSION;
-#endif
-
- if (s->max_proto_version != 0 && (s->version > s->max_proto_version))
- s->version = s->max_proto_version;
- if (s->version < s->min_proto_version)
- {
- SSLerr(SSL_F_SSL_SET_VERSION, SSL_R_NO_PROTOCOLS_AVAILABLE);
- return 0;
- }
-
- if (s->version != TLS1_2_VERSION && tls1_suiteb(s)) {
- SSLerr(SSL_F_SSL_SET_VERSION,
- SSL_R_ONLY_TLS_1_2_ALLOWED_IN_SUITEB_MODE);
- return 0;
- }
-
- if (s->version == SSL3_VERSION && FIPS_mode()) {
- SSLerr(SSL_F_SSL_SET_VERSION, SSL_R_ONLY_TLS_ALLOWED_IN_FIPS_MODE);
- return 0;
- }
-
- } else if (s->method->version == DTLS_ANY_VERSION) {
- int max_version = DTLS_MAX_VERSION;
- int min_version = DTLS_MIN_VERSION;
-
- if (s->max_proto_version != 0)
- max_version = s->max_proto_version;
- if (s->min_proto_version != 0)
- min_version = s->min_proto_version;
-
- /* If DTLS 1.2 disabled correct the version number */
- if (options & SSL_OP_NO_DTLSv1_2
- || DTLS_VERSION_GT(DTLS1_2_VERSION, max_version)) {
- if (tls1_suiteb(s)) {
- SSLerr(SSL_F_SSL_SET_VERSION,
- SSL_R_ONLY_DTLS_1_2_ALLOWED_IN_SUITEB_MODE);
- return 0;
- }
- /*
- * Disabling all versions is silly: return an error.
- */
- if (options & SSL_OP_NO_DTLSv1
- || DTLS_VERSION_GT(min_version, DTLS1_VERSION)) {
- SSLerr(SSL_F_SSL_SET_VERSION, SSL_R_WRONG_SSL_VERSION);
- return 0;
- }
- /*
- * Update method so we don't use any DTLS 1.2 features.
- */
- s->method = DTLSv1_client_method();
- s->version = DTLS1_VERSION;
- } else {
- /*
- * We only support one version: update method
- */
- if (options & SSL_OP_NO_DTLSv1
- || DTLS_VERSION_GE(min_version, DTLS1_2_VERSION))
- s->method = DTLSv1_2_client_method();
- s->version = DTLS1_2_VERSION;
- }
- }
-
- s->client_version = s->version;
-
- return 1;
-}
-
int tls_construct_client_hello(SSL *s)
{
unsigned char *buf;
unsigned char *p, *d;
int i;
+ int protverr;
unsigned long l;
int al = 0;
#ifndef OPENSSL_NO_COMP
@@ -933,8 +816,11 @@ int tls_construct_client_hello(SSL *s)
buf = (unsigned char *)s->init_buf->data;
/* Work out what SSL/TLS/DTLS version to use */
- if (ssl_set_version(s) == 0)
+ protverr = ssl_set_client_hello_version(s);
+ if (protverr != 0) {
+ SSLerr(SSL_F_TLS_CONSTRUCT_CLIENT_HELLO, protverr);
goto err;
+ }
if ((sess == NULL) || (sess->ssl_version != s->version) ||
/*
@@ -1141,121 +1027,23 @@ MSG_PROCESS_RETURN tls_process_server_hello(SSL *s, PACKET *pkt)
unsigned char *cipherchars;
int i, al = SSL_AD_INTERNAL_ERROR;
unsigned int compression;
+ unsigned int sversion;
+ int protverr;
#ifndef OPENSSL_NO_COMP
SSL_COMP *comp;
#endif
- if (s->method->version == TLS_ANY_VERSION) {
- unsigned int sversion;
- int max_version = TLS_MAX_VERSION;
-
- if (s->max_proto_version != 0)
- max_version = s->max_proto_version;
-
- if (!PACKET_get_net_2(pkt, &sversion)) {
- al = SSL_AD_DECODE_ERROR;
- SSLerr(SSL_F_TLS_PROCESS_SERVER_HELLO, SSL_R_LENGTH_MISMATCH);
- goto f_err;
- }
-
-#if TLS_MAX_VERSION != TLS1_2_VERSION
-#error Code needs updating for new TLS version
-#endif
-#ifndef OPENSSL_NO_SSL3
- if ((sversion == SSL3_VERSION) && !(s->options & SSL_OP_NO_SSLv3) &&
- (s->min_proto_version <= SSL3_VERSION) &&
- (max_version >= SSL3_VERSION)) {
- if (FIPS_mode()) {
- SSLerr(SSL_F_TLS_PROCESS_SERVER_HELLO,
- SSL_R_ONLY_TLS_ALLOWED_IN_FIPS_MODE);
- al = SSL_AD_PROTOCOL_VERSION;
- goto f_err;
- }
- s->method = SSLv3_client_method();
- } else
-#endif
- if ((sversion == TLS1_VERSION) && !(s->options & SSL_OP_NO_TLSv1) &&
- (s->min_proto_version <= TLS1_VERSION) &&
- (max_version >= TLS1_VERSION)) {
- s->method = TLSv1_client_method();
- } else if ((sversion == TLS1_1_VERSION) &&
- !(s->options & SSL_OP_NO_TLSv1_1) &&
- (s->min_proto_version <= TLS1_1_VERSION) &&
- (max_version >= TLS1_1_VERSION)) {
- s->method = TLSv1_1_client_method();
- } else if ((sversion == TLS1_2_VERSION) &&
- !(s->options & SSL_OP_NO_TLSv1_2) &&
- (s->min_proto_version <= TLS1_2_VERSION) &&
- (max_version >= TLS1_2_VERSION)) {
- s->method = TLSv1_2_client_method();
- } else {
- SSLerr(SSL_F_TLS_PROCESS_SERVER_HELLO, SSL_R_UNSUPPORTED_PROTOCOL);
- al = SSL_AD_PROTOCOL_VERSION;
- goto f_err;
- }
- s->session->ssl_version = s->version = s->method->version;
-
- if ((s->version < s->min_proto_version)
- || !ssl_security(s, SSL_SECOP_VERSION, 0, s->version, NULL)) {
- SSLerr(SSL_F_TLS_PROCESS_SERVER_HELLO, SSL_R_VERSION_TOO_LOW);
- al = SSL_AD_PROTOCOL_VERSION;
- goto f_err;
- }
- } else if (s->method->version == DTLS_ANY_VERSION) {
- /* Work out correct protocol version to use */
- unsigned int hversion;
- int options;
- int max_version = DTLS_MAX_VERSION;
- int min_version = DTLS_MIN_VERSION;
-
- if (s->max_proto_version != 0)
- max_version = s->max_proto_version;
- if (s->min_proto_version != 0)
- min_version = s->min_proto_version;
-
- if (!PACKET_get_net_2(pkt, &hversion)) {
- al = SSL_AD_DECODE_ERROR;
- SSLerr(SSL_F_TLS_PROCESS_SERVER_HELLO, SSL_R_LENGTH_MISMATCH);
- goto f_err;
- }
-
- options = s->options;
- if (hversion == DTLS1_2_VERSION && !(options & SSL_OP_NO_DTLSv1_2) &&
- DTLS_VERSION_LE(min_version, DTLS1_2_VERSION) &&
- DTLS_VERSION_GE(max_version, DTLS1_2_VERSION))
- s->method = DTLSv1_2_client_method();
- else if (tls1_suiteb(s)) {
- SSLerr(SSL_F_TLS_PROCESS_SERVER_HELLO,
- SSL_R_ONLY_DTLS_1_2_ALLOWED_IN_SUITEB_MODE);
- s->version = hversion;
- al = SSL_AD_PROTOCOL_VERSION;
- goto f_err;
- } else if (hversion == DTLS1_VERSION && !(options & SSL_OP_NO_DTLSv1) &&
- DTLS_VERSION_LE(min_version, DTLS1_VERSION) &&
- DTLS_VERSION_GE(max_version, DTLS1_VERSION))
- s->method = DTLSv1_client_method();
- else {
- SSLerr(SSL_F_TLS_PROCESS_SERVER_HELLO, SSL_R_WRONG_SSL_VERSION);
- s->version = hversion;
- al = SSL_AD_PROTOCOL_VERSION;
- goto f_err;
- }
- s->session->ssl_version = s->version = s->method->version;
- } else {
- unsigned char *vers;
+ if (!PACKET_get_net_2(pkt, &sversion)) {
+ al = SSL_AD_DECODE_ERROR;
+ SSLerr(SSL_F_TLS_PROCESS_SERVER_HELLO, SSL_R_LENGTH_MISMATCH);
+ goto f_err;
+ }
- if (!PACKET_get_bytes(pkt, &vers, 2)) {
- al = SSL_AD_DECODE_ERROR;
- SSLerr(SSL_F_TLS_PROCESS_SERVER_HELLO, SSL_R_LENGTH_MISMATCH);
- goto f_err;
- }
- if ((vers[0] != (s->version >> 8))
- || (vers[1] != (s->version & 0xff))) {
- SSLerr(SSL_F_TLS_PROCESS_SERVER_HELLO, SSL_R_WRONG_SSL_VERSION);
- s->version = (s->version & 0xff00) | vers[1];
- al = SSL_AD_PROTOCOL_VERSION;
- goto f_err;
- }
+ protverr = ssl_choose_client_version(s, sversion);
+ if (protverr != 0) {
+ al = SSL_AD_PROTOCOL_VERSION;
+ SSLerr(SSL_F_TLS_PROCESS_SERVER_HELLO, protverr);
+ goto f_err;
}
/* load the server hello data */
diff --git a/ssl/statem/statem_lib.c b/ssl/statem/statem_lib.c
index 86d200f0b2..bf59eb3925 100644
--- a/ssl/statem/statem_lib.c
+++ b/ssl/statem/statem_lib.c
@@ -229,7 +229,7 @@ MSG_PROCESS_RETURN tls_process_change_cipher_spec(SSL *s, PACKET *pkt)
{
int al;
long remain;
-
+
remain = PACKET_remaining(pkt);
/*
* 'Change Cipher Spec' is just a single byte, which should already have
@@ -704,3 +704,386 @@ int ssl_allow_compression(SSL *s)
return 0;
return ssl_security(s, SSL_SECOP_COMPRESSION, 0, 0, NULL);
}
+
+static int version_cmp(SSL *s, int a, int b)
+{
+ int dtls = SSL_IS_DTLS(s);
+
+ if (a == b)
+ return 0;
+ if (!dtls)
+ return a < b ? -1 : 1;
+ return DTLS_VERSION_LT(a, b) ? -1 : 1;
+}
+
+typedef struct {
+ int version;
+ const SSL_METHOD *(*cmeth)(void);
+ const SSL_METHOD *(*smeth)(void);
+} version_info;
+
+#if TLS_MAX_VERSION != TLS1_2_VERSION
+# error Code needs update for TLS_method() support beyond TLS1_2_VERSION.
+#endif
+
+static const version_info tls_version_table[] = {
+ { TLS1_2_VERSION, TLSv1_2_client_method, TLSv1_2_server_method },
+ { TLS1_1_VERSION, TLSv1_1_client_method, TLSv1_1_server_method },
+ { TLS1_VERSION, TLSv1_client_method, TLSv1_server_method },
+#ifndef OPENSSL_NO_SSL3
+ { SSL3_VERSION, SSLv3_client_method, SSLv3_server_method },
+#endif
+ { 0, NULL, NULL },
+};
+
+#if DTLS_MAX_VERSION != DTLS1_2_VERSION
+# error Code needs update for DTLS_method() support beyond DTLS1_2_VERSION.
+#endif
+
+static const version_info dtls_version_table[] = {
+ { DTLS1_2_VERSION, DTLSv1_2_client_method, DTLSv1_2_server_method },
+ { DTLS1_VERSION, DTLSv1_client_method, DTLSv1_server_method },
+ { 0, NULL, NULL },
+};
+
+/*
+ * ssl_method_error - Check whether an SSL_METHOD is enabled.
+ *
+ * @s: The SSL handle for the candidate method
+ * @method: the intended method.
+ *
+ * Returns 0 on success, or an SSL error reason on failure.
+ */
+static int ssl_method_error(SSL *s, const SSL_METHOD *method)
+{
+ int version = method->version;
+
+ if ((s->min_proto_version != 0 &&
+ version_cmp(s, version, s->min_proto_version) < 0) ||
+ ssl_security(s, SSL_SECOP_VERSION, 0, version, NULL) == 0)
+ return SSL_R_VERSION_TOO_LOW;
+
+ if (s->max_proto_version != 0 &&
+ version_cmp(s, version, s->max_proto_version) > 0)
+ return SSL_R_VERSION_TOO_HIGH;
+
+ if ((s->options & method->mask) != 0)
+ return SSL_R_UNSUPPORTED_PROTOCOL;
+ if ((method->flags & SSL_METHOD_NO_SUITEB) != 0 && tls1_suiteb(s))
+ return SSL_R_AT_LEAST_TLS_1_2_NEEDED_IN_SUITEB_MODE;
+ else if ((method->flags & SSL_METHOD_NO_FIPS) != 0 && FIPS_mode())
+ return SSL_R_AT_LEAST_TLS_1_0_NEEDED_IN_FIPS_MODE;
+
+ return 0;
+}
+
+/*
+ * ssl_check_version_downgrade - In response to RFC7507 SCSV version
+ * fallback indication from a client check whether we're using the highest
+ * supported protocol version.
+ *
+ * @s server SSL handle.
+ *
+ * Returns 1 when using the highest enabled version, 0 otherwise.
+ */
+int ssl_check_version_downgrade(SSL *s)
+{
+ const version_info *vent;
+ const version_info *table;
+
+ /*
+ * Check that the current protocol is the highest enabled version
+ * (according to s->ctx->method, as version negotiation may have changed
+ * s->method).
+ */
+ if (s->version == s->ctx->method->version)
+ return 1;
+
+ /*
+ * Apparently we're using a version-flexible SSL_METHOD (not at its
+ * highest protocol version).
+ */
+ if (s->ctx->method->version == TLS_method()->version)
+ table = tls_version_table;
+ else if (s->ctx->method->version == DTLS_method()->version)
+ table = dtls_version_table;
+ else {
+ /* Unexpected state; fail closed. */
+ return 0;
+ }
+
+ for (vent = table; vent->version != 0; ++vent) {
+ if (vent->smeth != NULL &&
+ ssl_method_error(s, vent->smeth()) == 0)
+ return s->version == vent->version;
+ }
+ return 0;
+}
+
+/*
+ * ssl_set_version_bound - set an upper or lower bound on the supported (D)TLS
+ * protocols, provided the initial (D)TLS method is version-flexible. This
+ * function sanity-checks the proposed value and makes sure the method is
+ * version-flexible, then sets the limit if all is well.
+ *
+ * @method_version: The version of the current SSL_METHOD.
+ * @version: the intended limit.
+ * @bound: pointer to limit to be updated.
+ *
+ * Returns 1 on success, 0 on failure.
+ */
+int ssl_set_version_bound(int method_version, int version, int *bound)
+{
+ /*-
+ * Restrict TLS methods to TLS protocol versions.
+ * Restrict DTLS methods to DTLS protocol versions.
+ * Note, DTLS version numbers are decreasing, use comparison macros.
+ *
+ * Note that for both lower-bounds we use explicit versions, not
+ * (D)TLS_MIN_VERSION. This is because we don't want to break user
+ * configurations. If the MIN (supported) version ever rises, the user's
+ * "floor" remains valid even if no longer available. We don't expect the
+ * MAX ceiling to ever get lower, so making that variable makes sense.
+ */
+ switch (method_version) {
+ default:
+ /*
+ * XXX For fixed version methods, should we always fail and not set any
+ * bounds, always succeed and not set any bounds, or set the bounds and
+ * arrange to fail later if they are not met? At present fixed-version
+ * methods are not subject to controls that disable individual protocol
+ * versions.
+ */
+ return 0;
+
+ case TLS_ANY_VERSION:
+ if (version < SSL3_VERSION || version > TLS_MAX_VERSION)
+ return 0;
+ break;
+
+ case DTLS_ANY_VERSION:
+ if (DTLS_VERSION_GT(version, DTLS_MAX_VERSION) ||
+ DTLS_VERSION_LT(version, DTLS1_VERSION))
+ return 0;
+ break;
+ }
+
+ *bound = version;
+ return 1;
+}
+
+/*
+ * ssl_choose_server_version - Choose server (D)TLS version. Called when the
+ * client HELLO is received to select the final server protocol version and
+ * the version specific method.
+ *
+ * @s: server SSL handle.
+ *
+ * Returns 0 on success or an SSL error reason number on failure.
+ */
+int ssl_choose_server_version(SSL *s)
+{
+ /*-
+ * With version-flexible methods we have an initial state with:
+ *
+ * s->method->version == (D)TLS_ANY_VERSION,
+ * s->version == (D)TLS_MAX_VERSION.
+ *
+ * So we detect version-flexible methods via the method version, not the
+ * handle version.
+ */
+ int server_version = s->method->version;
+ int client_version = s->client_version;
+ const version_info *vent;
+ const version_info *table;
+ int disabled = 0;
+
+ switch (server_version) {
+ default:
+ if (version_cmp(s, client_version, s->version) < 0)
+ return SSL_R_WRONG_SSL_VERSION;
+ /*
+ * If this SSL handle is not from a version flexible method we don't
+ * (and never did) check min/max FIPS or Suite B constraints. Hope
+ * that's OK. It is up to the caller to not choose fixed protocol
+ * versions they don't want. If not, then easy to fix, just return
+ * ssl_method_error(s, s->method)
+ */
+ return 0;
+ case TLS_ANY_VERSION:
+ table = tls_version_table;
+ break;
+ case DTLS_ANY_VERSION:
+ table = dtls_version_table;
+ break;
+ }
+
+ for (vent = table; vent->version != 0; ++vent) {
+ const SSL_METHOD *method;
+
+ if (vent->smeth == NULL ||
+ version_cmp(s, client_version, vent->version) < 0)
+ continue;
+ method = vent->smeth();
+ if (ssl_method_error(s, method) == 0) {
+ s->version = vent->version;
+ s->method = method;
+ return 0;
+ }
+ disabled = 1;
+ }
+ return disabled ? SSL_R_UNSUPPORTED_PROTOCOL : SSL_R_VERSION_TOO_LOW;
+}
+
+/*
+ * ssl_choose_client_version - Choose client (D)TLS version. Called when the
+ * server HELLO is received to select the final client protocol version and
+ * the version specific method.
+ *
+ * @s: client SSL handle.
+ * @version: The proposed version from the server's HELLO.
+ *
+ * Returns 0 on success or an SSL error reason number on failure.
+ */
+int ssl_choose_client_version(SSL *s, int version)
+{
+ const version_info *vent;
+ const version_info *table;
+
+ switch (s->method->version) {
+ default:
+ if (version != s->version)
+ return SSL_R_WRONG_SSL_VERSION;
+ /*
+ * If this SSL handle is not from a version flexible method we don't
+ * (and never did) check min/max, FIPS or Suite B constraints. Hope
+ * that's OK. It is up to the caller to not choose fixed protocol
+ * versions they don't want. If not, then easy to fix, just return
+ * ssl_method_error(s, s->method)
+ */
+ s->session->ssl_version = s->version;
+ return 0;
+ case TLS_ANY_VERSION:
+ table = tls_version_table;
+ break;
+ case DTLS_ANY_VERSION:
+ table = dtls_version_table;
+ break;
+ }
+
+ for (vent = table; vent->version != 0; ++vent) {
+ const SSL_METHOD *method;
+ int err;
+
+ if (version != vent->version)
+ continue;
+ if (vent->cmeth == NULL)
+ break;
+ method = vent->cmeth();
+ err = ssl_method_error(s, method);
+ if (err != 0)
+ return err;
+ s->method = method;
+ s->session->ssl_version = s->version = version;
+ return 0;
+ }
+
+ return SSL_R_UNSUPPORTED_PROTOCOL;
+}
+
+/*-
+ * ssl_set_client_hello_version - Work out what version we should be using for
+ * the initial ClientHello if the version is initially (D)TLS_ANY_VERSION. We
+ * apply any explicit SSL_OP_NO_xxx options, the MinProtocol and MaxProtocol
+ * configuration commands, any Suite B or FIPS_mode() constraints and any floor
+ * imposed by the security level here, so we don't advertise the wrong protocol
+ * version to only reject the outcome later.
+ *
+ * Computing the right floor matters. If, e.g., TLS 1.0 and 1.2 are enabled,
+ * TLS 1.1 is disabled, but the security level, Suite-B and/or MinProtocol
+ * only allow TLS 1.2, we want to advertise TLS1.2, *not* TLS1.
+ *
+ * @s: client SSL handle.
+ *
+ * Returns 0 on success or an SSL error reason number on failure.
+ */
+int ssl_set_client_hello_version(SSL *s)
+{
+ int version;
+ int hole;
+ const SSL_METHOD *single = NULL;
+ const SSL_METHOD *method;
+ const version_info *table;
+ const version_info *vent;
+
+ switch (s->method->version) {
+ default:
+ /*
+ * If this SSL handle is not from a version flexible method we don't
+ * (and never did) check min/max FIPS or Suite B constraints. Hope
+ * that's OK. It is up to the caller to not choose fixed protocol
+ * versions they don't want. If not, then easy to fix, just return
+ * ssl_method_error(s, s->method)
+ */
+ s->client_version = s->version;
+ return 0;
+ case TLS_ANY_VERSION:
+ table = tls_version_table;
+ break;
+ case DTLS_ANY_VERSION:
+ table = dtls_version_table;
+ break;
+ }
+
+ /*
+ * SSL_OP_NO_X disables all protocols above X *if* there are some protocols
+ * below X enabled. This is required in order to maintain the "version
+ * capability" vector contiguous. Any versions with a NULL client method
+ * (protocol version client is disabled at compile-time) is also a "hole".
+ *
+ * Our initial state is hole == 1, version == 0. That is, versions above
+ * the first version in the method table are disabled (a "hole" above
+ * the valid protocol entries) and we don't have a selected version yet.
+ *
+ * Whenever "hole == 1", and we hit an enabled method, its version becomes
+ * the selected version, and the method becomes a candidate "single"
+ * method. We're no longer in a hole, so "hole" becomes 0.
+ *
+ * If "hole == 0" and we hit an enabled method, then "single" is cleared,
+ * as we support a contiguous range of at least two methods. If we hit
+ * a disabled method, then hole becomes true again, but nothing else
+ * changes yet, because all the remaining methods may be disabled too.
+ * If we again hit an enabled method after the new hole, it becomes
+ * selected, as we start from scratch.
+ */
+ version = 0;
+ hole = 1;
+ for (vent = table; vent->version != 0; ++vent) {
+ /*
+ * A table entry with a NULL client method is still a hole in the
+ * "version capability" vector.
+ */
+ if (vent->cmeth == NULL) {
+ hole = 1;
+ continue;
+ }
+ method = vent->cmeth();
+ if (ssl_method_error(s, method) != 0) {
+ hole = 1;
+ } else if (!hole) {
+ single = NULL;
+ } else {
+ version = (single = method)->version;
+ hole = 0;
+ }
+ }
+
+ /* Fail if everything is disabled */
+ if (version == 0)
+ return SSL_R_NO_PROTOCOLS_AVAILABLE;
+
+ if (single != NULL)
+ s->method = single;
+ s->client_version = s->version = version;
+ return 0;
+}
diff --git a/ssl/statem/statem_srvr.c b/ssl/statem/statem_srvr.c
index a1163ed986..604b36565b 100644
--- a/ssl/statem/statem_srvr.c
+++ b/ssl/statem/statem_srvr.c
@@ -970,7 +970,7 @@ MSG_PROCESS_RETURN tls_process_client_hello(SSL *s, PACKET *pkt)
SSL_COMP *comp = NULL;
#endif
STACK_OF(SSL_CIPHER) *ciphers = NULL;
- int protverr = 1;
+ int protverr;
/* |cookie| will only be initialized for DTLS. */
PACKET session_id, cipher_suites, compression, extensions, cookie;
int is_v2_record;
@@ -1037,76 +1037,21 @@ MSG_PROCESS_RETURN tls_process_client_hello(SSL *s, PACKET *pkt)
}
}
- /* Do SSL/TLS version negotiation if applicable */
+ /*
+ * Do SSL/TLS version negotiation if applicable. For DTLS we just check
+ * versions are potentially compatible. Version negotiation comes later.
+ */
if (!SSL_IS_DTLS(s)) {
- if (s->version != TLS_ANY_VERSION) {
- if (s->client_version >= s->version) {
- protverr = 0;
- }
- } else if (s->client_version >= SSL3_VERSION) {
- int max_version = TLS_MAX_VERSION;
-
- if (s->max_proto_version != 0)
- max_version = s->max_proto_version;
-
- switch(s->client_version) {
- default:
- case TLS1_2_VERSION:
- if(!(s->options & SSL_OP_NO_TLSv1_2) &&
- (max_version >= TLS1_2_VERSION) &&
- (s->min_proto_version <= TLS1_2_VERSION)) {
- s->version = TLS1_2_VERSION;
- s->method = TLSv1_2_server_method();
- protverr = 0;
- break;
- }
- /* Deliberately fall through */
- case TLS1_1_VERSION:
- if(!(s->options & SSL_OP_NO_TLSv1_1) &&
- (max_version >= TLS1_1_VERSION) &&
- (s->min_proto_version <= TLS1_1_VERSION)) {
- s->version = TLS1_1_VERSION;
- s->method = TLSv1_1_server_method();
- protverr = 0;
- break;
- }
- /* Deliberately fall through */
- case TLS1_VERSION:
- if(!(s->options & SSL_OP_NO_TLSv1) &&
- (max_version >= TLS1_VERSION) &&
- (s->min_proto_version <= TLS1_VERSION)) {
- s->version = TLS1_VERSION;
- s->method = TLSv1_server_method();
- protverr = 0;
- break;
- }
- /* Deliberately fall through */
- case SSL3_VERSION:
-#ifndef OPENSSL_NO_SSL3
- if(!(s->options & SSL_OP_NO_SSLv3) &&
- (max_version >= SSL3_VERSION) &&
- (s->min_proto_version <= SSL3_VERSION)) {
- s->version = SSL3_VERSION;
- s->method = SSLv3_server_method();
- protverr = 0;
- break;
- }
-#else
- break;
-#endif
- }
- }
- } else if (s->client_version <= s->version
- || s->method->version == DTLS_ANY_VERSION) {
- /*
- * For DTLS we just check versions are potentially compatible. Version
- * negotiation comes later.
- */
+ protverr = ssl_choose_server_version(s);
+ } else if (s->method->version != DTLS_ANY_VERSION &&
+ DTLS_VERSION_LT(s->client_version, s->version)) {
+ protverr = SSL_R_VERSION_TOO_LOW;
+ } else {
protverr = 0;
}
if (protverr) {
- SSLerr(SSL_F_TLS_PROCESS_CLIENT_HELLO, SSL_R_UNKNOWN_PROTOCOL);
+ SSLerr(SSL_F_TLS_PROCESS_CLIENT_HELLO, protverr);
if ((!s->enc_write_ctx && !s->write_hash)) {
/*
* similar to ssl3_get_record, send alert using remote version
@@ -1266,36 +1211,9 @@ MSG_PROCESS_RETURN tls_process_client_hello(SSL *s, PACKET *pkt)
s->d1->cookie_verified = 1;
}
if (s->method->version == DTLS_ANY_VERSION) {
- /* Select version to use */
- int max_version = DTLS_MAX_VERSION;
- int min_version = DTLS_MIN_VERSION;
-
- if (s->max_proto_version != 0)
- max_version = s->max_proto_version;
- if (s->min_proto_version != 0)
- min_version = s->min_proto_version;
-
- if (DTLS_VERSION_GE(s->client_version, DTLS1_2_VERSION) &&
- !(s->options & SSL_OP_NO_DTLSv1_2) &&
- DTLS_VERSION_GE(max_version, DTLS1_2_VERSION) &&
- DTLS_VERSION_LE(min_version, DTLS1_2_VERSION)) {
- s->version = DTLS1_2_VERSION;
- s->method = DTLSv1_2_server_method();
- } else if (tls1_suiteb(s)) {
- SSLerr(SSL_F_TLS_PROCESS_CLIENT_HELLO,
- SSL_R_ONLY_DTLS_1_2_ALLOWED_IN_SUITEB_MODE);
- s->version = s->client_version;
- al = SSL_AD_PROTOCOL_VERSION;
- goto f_err;
- } else if (DTLS_VERSION_GE(s->client_version, DTLS1_VERSION) &&
- !(s->options & SSL_OP_NO_DTLSv1) &&
- DTLS_VERSION_GE(max_version, DTLS1_VERSION) &&
- DTLS_VERSION_LE(min_version, DTLS1_VERSION)) {
- s->version = DTLS1_VERSION;
- s->method = DTLSv1_server_method();
- } else {
- SSLerr(SSL_F_TLS_PROCESS_CLIENT_HELLO,
- SSL_R_WRONG_VERSION_NUMBER);
+ protverr = ssl_choose_server_version(s);
+ if (protverr != 0) {
+ SSLerr(SSL_F_TLS_PROCESS_CLIENT_HELLO, protverr);
s->version = s->client_version;
al = SSL_AD_PROTOCOL_VERSION;
goto f_err;
@@ -3303,7 +3221,7 @@ STACK_OF(SSL_CIPHER) *ssl_bytes_to_cipher_list(SSL *s,
* version. Fail if the current version is an unexpected
* downgrade.
*/
- if (!SSL_ctrl(s, SSL_CTRL_CHECK_PROTO_VERSION, 0, NULL)) {
+ if (!ssl_check_version_downgrade(s)) {
SSLerr(SSL_F_SSL_BYTES_TO_CIPHER_LIST,
SSL_R_INAPPROPRIATE_FALLBACK);
*al = SSL_AD_INAPPROPRIATE_FALLBACK;