Factor out remaining version-related functions.

Now SSLv23 and DTLS_ANY_VERSION share version-related helper functions.
ssl3_get_method is temporary until the method switch is no longer necessary.

Put them all together so there's one place to refactor them when we add a new
version or implement min_version/max_version controls.

Change-Id: Ic28a145cad22db08a87fdb854480b22886c451c6
Reviewed-on: https://boringssl-review.googlesource.com/2580
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/ssl/s23_clnt.c b/ssl/s23_clnt.c
index b0d6b69..ca46d8d 100644
--- a/ssl/s23_clnt.c
+++ b/ssl/s23_clnt.c
@@ -447,37 +447,15 @@
 	         ((p[0] == SSL3_RT_HANDSHAKE && p[5] == SSL3_MT_SERVER_HELLO) ||
 	          (p[0] == SSL3_RT_ALERT && p[3] == 0 && p[4] == 2)))
 		{
-		/* we have sslv3 or tls1 (server hello or alert) */
-
-		if ((p[2] == SSL3_VERSION_MINOR) &&
-			!(s->options & SSL_OP_NO_SSLv3))
-			{
-			s->version=SSL3_VERSION;
-			s->method=SSLv3_client_method();
-			}
-		else if ((p[2] == TLS1_VERSION_MINOR) &&
-			!(s->options & SSL_OP_NO_TLSv1))
-			{
-			s->version=TLS1_VERSION;
-			s->method=TLSv1_client_method();
-			}
-		else if ((p[2] == TLS1_1_VERSION_MINOR) &&
-			!(s->options & SSL_OP_NO_TLSv1_1))
-			{
-			s->version=TLS1_1_VERSION;
-			s->method=TLSv1_1_client_method();
-			}
-		else if ((p[2] == TLS1_2_VERSION_MINOR) &&
-			!(s->options & SSL_OP_NO_TLSv1_2))
-			{
-			s->version=TLS1_2_VERSION;
-			s->method=TLSv1_2_client_method();
-			}
-		else
+		uint16_t version = (p[1] << 8) | p[2];
+		if (!ssl3_is_version_enabled(s, version))
 			{
 			OPENSSL_PUT_ERROR(SSL, ssl23_get_server_hello, SSL_R_UNSUPPORTED_PROTOCOL);
 			goto err;
 			}
+		s->version = version;
+		s->method = ssl3_get_method(version);
+		assert(s->method != NULL);
 
 		if (p[0] == SSL3_RT_ALERT && p[5] != SSL3_AL_WARNING)
 			{
diff --git a/ssl/s23_srvr.c b/ssl/s23_srvr.c
index 06916ed..08d063a 100644
--- a/ssl/s23_srvr.c
+++ b/ssl/s23_srvr.c
@@ -121,20 +121,6 @@
 static int ssl23_get_client_hello(SSL *s);
 static int ssl23_get_v2_client_hello(SSL *s);
 
-static const SSL_METHOD *ssl23_get_server_method(int ver)
-	{
-	if (ver == SSL3_VERSION)
-		return(SSLv3_server_method());
-	else if (ver == TLS1_VERSION)
-		return(TLSv1_server_method());
-	else if (ver == TLS1_1_VERSION)
-		return(TLSv1_1_server_method());
-	else if (ver == TLS1_2_VERSION)
-		return(TLSv1_2_server_method());
-	else
-		return(NULL);
-	}
-
 int ssl23_accept(SSL *s)
 	{
 	BUF_MEM *buf;
@@ -213,7 +199,7 @@
 				}
 
 			s->state = SSL3_ST_SR_CLNT_HELLO_A;
-			s->method = ssl23_get_server_method(s->version);
+			s->method = ssl3_get_method(s->version);
 			assert(s->method != NULL);
 			s->handshake_func = s->method->ssl_accept;
 			s->init_num = 0;
@@ -246,35 +232,6 @@
 	return(ret);
 	}
 
-/* ssl23_get_mutual_version determines the highest supported version for a
- * client which reports a highest version of |client_version|. On success, it
- * returns 1 and sets |*out_version| to the negotiated version. Otherwise, it
- * returns 0. */
-static int ssl23_get_mutual_version(SSL *s, int *out_version, uint16_t client_version)
-	{
-	if (client_version >= TLS1_2_VERSION && !(s->options & SSL_OP_NO_TLSv1_2))
-		{
-		*out_version = TLS1_2_VERSION;
-		return 1;
-		}
-	if (client_version >= TLS1_1_VERSION && !(s->options & SSL_OP_NO_TLSv1_1))
-		{
-		*out_version = TLS1_1_VERSION;
-		return 1;
-		}
-	if (client_version >= TLS1_VERSION && !(s->options & SSL_OP_NO_TLSv1))
-		{
-		*out_version = TLS1_VERSION;
-		return 1;
-		}
-	if (client_version >= SSL3_VERSION && !(s->options & SSL_OP_NO_SSLv3))
-		{
-		*out_version = SSL3_VERSION;
-		return 1;
-		}
-	return 0;
-	}
-
 static int ssl23_get_client_hello(SSL *s)
 	{
 	uint8_t *p;
@@ -336,11 +293,13 @@
 		/* This is a V2ClientHello. Determine the version to
 		 * use. */
 		uint16_t client_version = (p[3] << 8) | p[4];
-		if (!ssl23_get_mutual_version(s, &s->version, client_version))
+		uint16_t version = ssl3_get_mutual_version(s, client_version);
+		if (version == 0)
 			{
 			OPENSSL_PUT_ERROR(SSL, ssl23_get_client_hello, SSL_R_UNSUPPORTED_PROTOCOL);
 			goto err;
 			}
+		s->version = version;
 		/* Parse the entire V2ClientHello. */
 		s->state = SSL23_ST_SR_V2_CLNT_HELLO;
 		}
@@ -355,7 +314,7 @@
 		 * this, so we simply reject such connections to avoid
 		 * protocol version downgrade attacks. */
 		uint16_t record_length = (p[3] << 8) | p[4];
-		uint16_t client_version;
+		uint16_t client_version, version;
 		if (record_length < 6)
 			{
 			OPENSSL_PUT_ERROR(SSL, ssl23_get_client_hello, SSL_R_RECORD_TOO_SMALL);
@@ -363,11 +322,13 @@
 			}
 
 		client_version = (p[9] << 8) | p[10];
-		if (!ssl23_get_mutual_version(s, &s->version, client_version))
+		version = ssl3_get_mutual_version(s, client_version);
+		if (version == 0)
 			{
 			OPENSSL_PUT_ERROR(SSL, ssl23_get_client_hello, SSL_R_UNSUPPORTED_PROTOCOL);
 			goto err;
 			}
+		s->version = version;
 
                 /* Reset the record-layer state for SSL3. */
                 assert(s->rstate == SSL_ST_READ_HEADER);
diff --git a/ssl/s3_clnt.c b/ssl/s3_clnt.c
index 19af3cc..c7ae0ee 100644
--- a/ssl/s3_clnt.c
+++ b/ssl/s3_clnt.c
@@ -580,49 +580,6 @@
 	return(ret);
 	}
 
-uint16_t ssl3_get_max_client_version(SSL *s)
-	{
-	unsigned long options = s->options;
-	uint16_t version = 0;
-
-	/* OpenSSL's API for controlling versions entails blacklisting
-	 * individual protocols. This has two problems. First, on the client,
-	 * the protocol can only express a contiguous range of versions. Second,
-	 * a library consumer trying to set a maximum version cannot disable
-	 * protocol versions that get added in a future version of the library.
-	 *
-	 * To account for both of these, OpenSSL interprets the client-side
-	 * bitmask as a min/max range by picking the lowest contiguous non-empty
-	 * range of enabled protocols. Note that this means it is impossible to
-	 * set a maximum version of TLS 1.2 in a future-proof way.
-	 *
-	 * By this scheme, the maximum version is the lowest version V such that
-	 * V is enabled and V+1 is disabled or unimplemented.
-	 *
-	 * TODO(davidben): Deprecate this API in favor of more sensible
-	 * min_version/max_version settings. */
-	if (SSL_IS_DTLS(s))
-		{
-		if (!(options & SSL_OP_NO_DTLSv1_2))
-			version = DTLS1_2_VERSION;
-		if (!(options & SSL_OP_NO_DTLSv1) && (options & SSL_OP_NO_DTLSv1_2))
-			version = DTLS1_VERSION;
-		}
-	else
-		{
-		if (!(options & SSL_OP_NO_TLSv1_2))
-			version = TLS1_2_VERSION;
-		if (!(options & SSL_OP_NO_TLSv1_1) && (options & SSL_OP_NO_TLSv1_2))
-			version = TLS1_1_VERSION;
-		if (!(options & SSL_OP_NO_TLSv1) && (options & SSL_OP_NO_TLSv1_1))
-			version = TLS1_VERSION;
-		if (!(options & SSL_OP_NO_SSLv3) && (options & SSL_OP_NO_TLSv1))
-			version = SSL3_VERSION;
-		}
-
-	return version;
-	}
-
 int ssl3_send_client_hello(SSL *s)
 	{
 	unsigned char *buf;
@@ -816,22 +773,16 @@
 
 	if (s->method->version == DTLS_ANY_VERSION)
 		{
-		/* Work out correct protocol version to use */
-		int options = s->options;
-		if (server_version == DTLS1_2_VERSION
-			&& !(options & SSL_OP_NO_DTLSv1_2))
-			s->method = DTLSv1_2_client_method();
-		else if (server_version == DTLS1_VERSION
-			&& !(options & SSL_OP_NO_DTLSv1))
-			s->method = DTLSv1_client_method();
-		else
+		if (!ssl3_is_version_enabled(s, server_version))
 			{
 			OPENSSL_PUT_ERROR(SSL, ssl3_get_server_hello, SSL_R_WRONG_SSL_VERSION);
 			s->version = server_version;
 			al = SSL_AD_PROTOCOL_VERSION;
 			goto f_err;
 			}
-		s->version = s->method->version;
+		s->version = server_version;
+		s->method = ssl3_get_method(server_version);
+		assert(s->method != NULL);
 		}
 
 	if (server_version != s->version)
diff --git a/ssl/s3_srvr.c b/ssl/s3_srvr.c
index 86e8744..8a204ce 100644
--- a/ssl/s3_srvr.c
+++ b/ssl/s3_srvr.c
@@ -854,25 +854,17 @@
 		if (s->method->version == DTLS_ANY_VERSION)
 			{
 			/* Select version to use */
-			if (s->client_version <= DTLS1_2_VERSION &&
-				!(s->options & SSL_OP_NO_DTLSv1_2))
-				{
-				s->version = DTLS1_2_VERSION;
-				s->method = DTLSv1_2_server_method();
-				}
-			else if (s->client_version <= DTLS1_VERSION &&
-				!(s->options & SSL_OP_NO_DTLSv1))
-				{
-				s->version = DTLS1_VERSION;
-				s->method = DTLSv1_server_method();
-				}
-			else
+			uint16_t version = ssl3_get_mutual_version(s, client_version);
+			if (version == 0)
 				{
 				OPENSSL_PUT_ERROR(SSL, ssl3_get_client_hello, SSL_R_WRONG_VERSION_NUMBER);
 				s->version = s->client_version;
 				al = SSL_AD_PROTOCOL_VERSION;
 				goto f_err;
 				}
+			s->version = version;
+			s->method = ssl3_get_method(version);
+			assert(s->method != NULL);
 			}
 		}
 
diff --git a/ssl/ssl_lib.c b/ssl/ssl_lib.c
index 61c8dc9..0456cf6 100644
--- a/ssl/ssl_lib.c
+++ b/ssl/ssl_lib.c
@@ -1498,7 +1498,9 @@
 		/* Check for FALLBACK_SCSV. */
 		if (s->s3 && cipher_suite == (SSL3_CK_FALLBACK_SCSV & 0xffff))
 			{
-			if (s->version < ssl_get_max_version(s))
+			uint16_t max_version = ssl3_get_max_server_version(s);
+			if (SSL_IS_DTLS(s) ? (uint16_t)s->version > max_version
+				: (uint16_t)s->version < max_version)
 				{
 				OPENSSL_PUT_ERROR(SSL, ssl_bytes_to_cipher_list, SSL_R_INAPPROPRIATE_FALLBACK);
 				ssl3_send_alert(s, SSL3_AL_FATAL, SSL3_AD_INAPPROPRIATE_FALLBACK);
@@ -3046,13 +3048,37 @@
 	return 1;
 	}
 
-/* ssl_get_max_version returns the maximum SSL/TLS version number supported by
- * |s|, or zero if all versions are disabled. */
-int ssl_get_max_version(const SSL *s)
+const SSL_METHOD *ssl3_get_method(uint16_t version)
 	{
-	/* Only one version supported for DTLS. */
-	if (s->version == DTLS1_VERSION)
-		return DTLS1_VERSION;
+	switch (version)
+		{
+	case SSL3_VERSION:
+		return SSLv3_method();
+	case TLS1_VERSION:
+		return TLSv1_method();
+	case TLS1_1_VERSION:
+		return TLSv1_1_method();
+	case TLS1_2_VERSION:
+		return TLSv1_2_method();
+	case DTLS1_VERSION:
+		return DTLSv1_method();
+	case DTLS1_2_VERSION:
+		return DTLSv1_2_method();
+	default:
+		return NULL;
+		}
+	}
+
+uint16_t ssl3_get_max_server_version(const SSL *s)
+	{
+	if (SSL_IS_DTLS(s))
+		{
+		if (!(s->options & SSL_OP_NO_DTLSv1_2))
+			return DTLS1_2_VERSION;
+		if (!(s->options & SSL_OP_NO_DTLSv1))
+			return DTLS1_VERSION;
+		return 0;
+		}
 
 	if (!(s->options & SSL_OP_NO_TLSv1_2))
 		return TLS1_2_VERSION;
@@ -3065,6 +3091,105 @@
 	return 0;
 	}
 
+uint16_t ssl3_get_mutual_version(SSL *s, uint16_t client_version)
+	{
+	if (SSL_IS_DTLS(s))
+		{
+		if (client_version <= DTLS1_2_VERSION && !(s->options & SSL_OP_NO_DTLSv1_2))
+			return DTLS1_2_VERSION;
+		if (client_version <= DTLS1_VERSION && !(s->options & SSL_OP_NO_DTLSv1))
+			return DTLS1_VERSION;
+		return 0;
+		}
+	else
+		{
+		if (client_version >= TLS1_2_VERSION && !(s->options & SSL_OP_NO_TLSv1_2))
+			return TLS1_2_VERSION;
+		if (client_version >= TLS1_1_VERSION && !(s->options & SSL_OP_NO_TLSv1_1))
+			return TLS1_1_VERSION;
+		if (client_version >= TLS1_VERSION && !(s->options & SSL_OP_NO_TLSv1))
+			return TLS1_VERSION;
+		if (client_version >= SSL3_VERSION && !(s->options & SSL_OP_NO_SSLv3))
+			return SSL3_VERSION;
+		return 0;
+		}
+	}
+
+uint16_t ssl3_get_max_client_version(SSL *s)
+	{
+	unsigned long options = s->options;
+	uint16_t version = 0;
+
+	/* OpenSSL's API for controlling versions entails blacklisting
+	 * individual protocols. This has two problems. First, on the client,
+	 * the protocol can only express a contiguous range of versions. Second,
+	 * a library consumer trying to set a maximum version cannot disable
+	 * protocol versions that get added in a future version of the library.
+	 *
+	 * To account for both of these, OpenSSL interprets the client-side
+	 * bitmask as a min/max range by picking the lowest contiguous non-empty
+	 * range of enabled protocols. Note that this means it is impossible to
+	 * set a maximum version of TLS 1.2 in a future-proof way.
+	 *
+	 * By this scheme, the maximum version is the lowest version V such that
+	 * V is enabled and V+1 is disabled or unimplemented.
+	 *
+	 * TODO(davidben): Deprecate this API in favor of more sensible
+	 * min_version/max_version settings. */
+	if (SSL_IS_DTLS(s))
+		{
+		if (!(options & SSL_OP_NO_DTLSv1_2))
+			version = DTLS1_2_VERSION;
+		if (!(options & SSL_OP_NO_DTLSv1) && (options & SSL_OP_NO_DTLSv1_2))
+			version = DTLS1_VERSION;
+		}
+	else
+		{
+		if (!(options & SSL_OP_NO_TLSv1_2))
+			version = TLS1_2_VERSION;
+		if (!(options & SSL_OP_NO_TLSv1_1) && (options & SSL_OP_NO_TLSv1_2))
+			version = TLS1_1_VERSION;
+		if (!(options & SSL_OP_NO_TLSv1) && (options & SSL_OP_NO_TLSv1_1))
+			version = TLS1_VERSION;
+		if (!(options & SSL_OP_NO_SSLv3) && (options & SSL_OP_NO_TLSv1))
+			version = SSL3_VERSION;
+		}
+
+	return version;
+	}
+
+int ssl3_is_version_enabled(SSL *s, uint16_t version)
+	{
+	if (SSL_IS_DTLS(s))
+		{
+		switch (version)
+			{
+		case DTLS1_VERSION:
+			return !(s->options & SSL_OP_NO_DTLSv1);
+		case DTLS1_2_VERSION:
+			return !(s->options & SSL_OP_NO_DTLSv1_2);
+		default:
+			return 0;
+			}
+		}
+	else
+		{
+		switch (version)
+			{
+		case SSL3_VERSION:
+			return !(s->options & SSL_OP_NO_SSLv3);
+		case TLS1_VERSION:
+			return !(s->options & SSL_OP_NO_TLSv1);
+		case TLS1_1_VERSION:
+			return !(s->options & SSL_OP_NO_TLSv1_1);
+		case TLS1_2_VERSION:
+			return !(s->options & SSL_OP_NO_TLSv1_2);
+		default:
+			return 0;
+			}
+		}
+	}
+
 /* Allocates new EVP_MD_CTX and sets pointer to it into given pointer
  * vairable, freeing  EVP_MD_CTX previously stored in that variable, if
  * any. If EVP_MD pointer is passed, initializes ctx with this md
diff --git a/ssl/ssl_locl.h b/ssl/ssl_locl.h
index 1c81370..9940eb0 100644
--- a/ssl/ssl_locl.h
+++ b/ssl/ssl_locl.h
@@ -929,13 +929,6 @@
 void dtls1_hm_fragment_free(hm_fragment *frag);
 
 /* some client-only functions */
-
-/* ssl3_get_max_client_version returns the maximum protocol version configured
- * for the client. It is guaranteed that the set of allowed versions at or below
- * this maximum version is contiguous. If all versions are disabled, it returns
- * zero. */
-uint16_t ssl3_get_max_client_version(SSL *s);
-
 int ssl3_send_client_hello(SSL *s);
 int ssl3_get_server_hello(SSL *s);
 int ssl3_get_certificate_request(SSL *s);
@@ -1078,7 +1071,30 @@
 	const uint8_t *master, size_t master_len);
 
 int ssl3_can_cutthrough(const SSL *s);
-int ssl_get_max_version(const SSL *s);
+
+/* ssl3_get_method returns the version-locked SSL_METHOD corresponding
+ * to |version|. */
+const SSL_METHOD *ssl3_get_method(uint16_t version);
+
+/* ssl3_get_max_server_version returns the maximum SSL/TLS version number
+ * supported by |s| as a server, or zero if all versions are disabled. */
+uint16_t ssl3_get_max_server_version(const SSL *s);
+
+/* ssl3_get_mutual_version selects the protocol version on |s| for a client
+ * which advertises |client_version|. If no suitable version exists, it returns
+ * zero. */
+uint16_t ssl3_get_mutual_version(SSL *s, uint16_t client_version);
+
+/* ssl3_get_max_client_version returns the maximum protocol version configured
+ * for the client. It is guaranteed that the set of allowed versions at or below
+ * this maximum version is contiguous. If all versions are disabled, it returns
+ * zero. */
+uint16_t ssl3_get_max_client_version(SSL *s);
+
+/* ssl3_is_version_enabled returns one if |version| is an enabled protocol
+ * version for |s| and zero otherwise. */
+int ssl3_is_version_enabled(SSL *s, uint16_t version);
+
 EVP_MD_CTX* ssl_replace_hash(EVP_MD_CTX **hash,const EVP_MD *md) ;
 void ssl_clear_hash_ctx(EVP_MD_CTX **hash);
 int ssl_add_serverhello_renegotiate_ext(SSL *s, unsigned char *p, int *len,