[Openvpn-devel,1/3,v4] Fix --tls-version-min and --tls-version-max for OpenSSL 1.1+

Message ID 20180119212721.25177-1-steffan@karger.me
State Accepted
Headers show
Series [Openvpn-devel,1/3,v4] Fix --tls-version-min and --tls-version-max for OpenSSL 1.1+ | expand

Commit Message

Steffan Karger Jan. 19, 2018, 10:27 a.m. UTC
As described in <80e6b449-c536-dc87-7215-3693872bce5a@birkenwald.de> on
the openvpn-devel mailing list, --tls-version-min no longer works with
OpenSSL 1.1.  Kurt Roeckx posted in a debian bug report:

"This is marked as important because if you switch to openssl 1.1.0
the defaults minimum version in Debian is currently TLS 1.2 and
you can't override it with the options that you're currently using
(and are deprecated)."

This patch is loosely based on the original patch by Kurt, but solves the
issue by adding functions to openssl-compat.h, like we also did for all
other openssl 1.1. breakage.  This results in not having to add more ifdefs
in ssl_openssl.c and thus cleaner code.

Signed-off-by: Steffan Karger <steffan@karger.me>
---
v2: fix define name, obey system lib default minimum version
v3: add error handling, fix bug in setting default minimum
v4: also update compat layer for v3 changes

 src/openvpn/openssl_compat.h |  67 +++++++++++++++++++++++++++++
 src/openvpn/ssl.c            |   5 ++-
 src/openvpn/ssl_backend.h    |   4 +-
 src/openvpn/ssl_mbedtls.c    |   3 +-
 src/openvpn/ssl_openssl.c    | 100 +++++++++++++++++++++++++++----------------
 5 files changed, 140 insertions(+), 39 deletions(-)

Comments

Selva Nair Jan. 19, 2018, 10:58 a.m. UTC | #1
Hi,

Thanks for last and final v4 :)

On Fri, Jan 19, 2018 at 4:27 PM, Steffan Karger <steffan@karger.me> wrote:

> As described in <80e6b449-c536-dc87-7215-3693872bce5a@birkenwald.de> on
> the openvpn-devel mailing list, --tls-version-min no longer works with
> OpenSSL 1.1.  Kurt Roeckx posted in a debian bug report:
>
> "This is marked as important because if you switch to openssl 1.1.0
> the defaults minimum version in Debian is currently TLS 1.2 and
> you can't override it with the options that you're currently using
> (and are deprecated)."
>
> This patch is loosely based on the original patch by Kurt, but solves the
> issue by adding functions to openssl-compat.h, like we also did for all
> other openssl 1.1. breakage.  This results in not having to add more ifdefs
> in ssl_openssl.c and thus cleaner code.
>
> Signed-off-by: Steffan Karger <steffan@karger.me>
> ---
> v2: fix define name, obey system lib default minimum version
> v3: add error handling, fix bug in setting default minimum
> v4: also update compat layer for v3 changes
>
>  src/openvpn/openssl_compat.h |  67 +++++++++++++++++++++++++++++
>  src/openvpn/ssl.c            |   5 ++-
>  src/openvpn/ssl_backend.h    |   4 +-
>  src/openvpn/ssl_mbedtls.c    |   3 +-
>  src/openvpn/ssl_openssl.c    | 100 +++++++++++++++++++++++++++---
> -------------
>  5 files changed, 140 insertions(+), 39 deletions(-)
>
> diff --git a/src/openvpn/openssl_compat.h b/src/openvpn/openssl_compat.h
> index 05ec4e95..9f1e92a1 100644
> --- a/src/openvpn/openssl_compat.h
> +++ b/src/openvpn/openssl_compat.h
> @@ -661,4 +661,71 @@ EC_GROUP_order_bits(const EC_GROUP *group)
>  #define RSA_F_RSA_OSSL_PRIVATE_ENCRYPT
>  RSA_F_RSA_EAY_PRIVATE_ENCRYPT
>  #endif
>
> +#ifndef SSL_CTX_get_min_proto_version
> +/** Dummy SSL_CTX_get_min_proto_version for OpenSSL < 1.1 (not really
> needed) */
> +static inline int
> +SSL_CTX_get_min_proto_version(SSL_CTX *ctx)
> +{
> +    return 0;
> +}
> +#endif /* SSL_CTX_get_min_proto_version */
> +
> +#ifndef SSL_CTX_set_min_proto_version
> +/** Mimics SSL_CTX_set_min_proto_version for OpenSSL < 1.1 */
> +static inline int
> +SSL_CTX_set_min_proto_version(SSL_CTX *ctx, long tls_ver_min)
> +{
> +    long sslopt = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3; /* Never do < TLS
> 1.0 */
> +
> +    if (tls_ver_min > TLS1_VERSION)
> +    {
> +        sslopt |= SSL_OP_NO_TLSv1;
> +    }
> +#ifdef SSL_OP_NO_TLSv1_1
> +    if (tls_ver_min > TLS1_1_VERSION)
> +    {
> +        sslopt |= SSL_OP_NO_TLSv1_1;
> +    }
> +#endif
> +#ifdef SSL_OP_NO_TLSv1_2
> +    if (tls_ver_min > TLS1_2_VERSION)
> +    {
> +        sslopt |= SSL_OP_NO_TLSv1_2;
> +    }
> +#endif
> +    SSL_CTX_set_options(ctx, sslopt);
> +
> +    return 1;
> +}
> +#endif /* SSL_CTX_set_min_proto_version */
> +
> +#ifndef SSL_CTX_set_max_proto_version
> +/** Mimics SSL_CTX_set_max_proto_version for OpenSSL < 1.1 */
> +static inline int
> +SSL_CTX_set_max_proto_version(SSL_CTX *ctx, long tls_ver_max)
> +{
> +    long sslopt = 0;
> +
> +    if (tls_ver_max < TLS1_VERSION)
> +    {
> +        sslopt |= SSL_OP_NO_TLSv1;
> +    }
> +#ifdef SSL_OP_NO_TLSv1_1
> +    if (tls_ver_max < TLS1_1_VERSION)
> +    {
> +        sslopt |= SSL_OP_NO_TLSv1_1;
> +    }
> +#endif
> +#ifdef SSL_OP_NO_TLSv1_2
> +    if (tls_ver_max < TLS1_2_VERSION)
> +    {
> +        sslopt |= SSL_OP_NO_TLSv1_2;
> +    }
> +#endif
> +    SSL_CTX_set_options(ctx, sslopt);
> +
> +    return 1;
> +}
> +#endif /* SSL_CTX_set_max_proto_version */
> +
>  #endif /* OPENSSL_COMPAT_H_ */
> diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c
> index 7b428455..6c27050f 100644
> --- a/src/openvpn/ssl.c
> +++ b/src/openvpn/ssl.c
> @@ -622,7 +622,10 @@ init_ssl(const struct options *options, struct
> tls_root_ctx *new_ctx)
>       * cipher restrictions before loading certificates */
>      tls_ctx_restrict_ciphers(new_ctx, options->cipher_list);
>
> -    tls_ctx_set_options(new_ctx, options->ssl_flags);
> +    if (!tls_ctx_set_options(new_ctx, options->ssl_flags))
> +    {
> +        goto err;
> +    }
>
>      if (options->pkcs12_file)
>      {
> diff --git a/src/openvpn/ssl_backend.h b/src/openvpn/ssl_backend.h
> index 7cf5d830..444fb2f9 100644
> --- a/src/openvpn/ssl_backend.h
> +++ b/src/openvpn/ssl_backend.h
> @@ -162,8 +162,10 @@ bool tls_ctx_initialised(struct tls_root_ctx *ctx);
>   *
>   * @param ctx           TLS context to set options on
>   * @param ssl_flags     SSL flags to set
> + *
> + * @return true on success, false otherwise.
>   */
> -void tls_ctx_set_options(struct tls_root_ctx *ctx, unsigned int
> ssl_flags);
> +bool tls_ctx_set_options(struct tls_root_ctx *ctx, unsigned int
> ssl_flags);
>
>  /**
>   * Restrict the list of ciphers that can be used within the TLS context.
> diff --git a/src/openvpn/ssl_mbedtls.c b/src/openvpn/ssl_mbedtls.c
> index 8ac52d55..d503162a 100644
> --- a/src/openvpn/ssl_mbedtls.c
> +++ b/src/openvpn/ssl_mbedtls.c
> @@ -206,9 +206,10 @@ key_state_export_keying_material(struct
> key_state_ssl *ssl,
>  {
>  }
>
> -void
> +bool
>  tls_ctx_set_options(struct tls_root_ctx *ctx, unsigned int ssl_flags)
>  {
> +    return true;
>  }
>
>  static const char *
> diff --git a/src/openvpn/ssl_openssl.c b/src/openvpn/ssl_openssl.c
> index 86318d4c..50d68280 100644
> --- a/src/openvpn/ssl_openssl.c
> +++ b/src/openvpn/ssl_openssl.c
> @@ -206,16 +206,65 @@ info_callback(INFO_CALLBACK_SSL_CONST SSL *s, int
> where, int ret)
>  int
>  tls_version_max(void)
>  {
> -#if defined(SSL_OP_NO_TLSv1_2)
> +#if defined(TLS1_2_VERSION) || defined(SSL_OP_NO_TLSv1_2)
>      return TLS_VER_1_2;
> -#elif defined(SSL_OP_NO_TLSv1_1)
> +#elif defined(TLS1_1_VERSION) || defined(SSL_OP_NO_TLSv1_1)
>      return TLS_VER_1_1;
>  #else
>      return TLS_VER_1_0;
>  #endif
>  }
>
> -void
> +/** Convert internal version number to openssl version number */
> +static int
> +openssl_tls_version(int ver)
> +{
> +    if (ver == TLS_VER_1_0)
> +    {
> +        return TLS1_VERSION;
> +    }
> +    else if (ver == TLS_VER_1_1)
> +    {
> +        return TLS1_1_VERSION;
> +    }
> +    else if (ver == TLS_VER_1_2)
> +    {
> +        return TLS1_2_VERSION;
> +    }
> +    return 0;
> +}
> +
> +static bool
> +tls_ctx_set_tls_versions(struct tls_root_ctx *ctx, unsigned int
> ssl_flags)
> +{
> +    int tls_ver_min = openssl_tls_version(
> +        (ssl_flags >> SSLF_TLS_VERSION_MIN_SHIFT) &
> SSLF_TLS_VERSION_MIN_MASK);
> +    int tls_ver_max = openssl_tls_version(
> +        (ssl_flags >> SSLF_TLS_VERSION_MAX_SHIFT) &
> SSLF_TLS_VERSION_MAX_MASK);
> +
> +    if (!tls_ver_min)
> +    {
> +        /* Enforce at least TLS 1.0 */
> +        int cur_min = SSL_CTX_get_min_proto_version(ctx->ctx);
> +        tls_ver_min = cur_min < TLS1_VERSION ? TLS1_VERSION : cur_min;
> +    }
> +
> +    if (!SSL_CTX_set_min_proto_version(ctx->ctx, tls_ver_min))
> +    {
> +        msg(D_TLS_ERRORS, "%s: failed to set minimum TLS version",
> __func__);
> +        return false;
> +    }
> +
> +    if (tls_ver_max && !SSL_CTX_set_max_proto_version(ctx->ctx,
> tls_ver_max))
> +    {
> +        msg(D_TLS_ERRORS, "%s: failed to set maximum TLS version",
> __func__);
> +        return false;
> +    }
> +
> +    return true;
> +}
> +
> +bool
>  tls_ctx_set_options(struct tls_root_ctx *ctx, unsigned int ssl_flags)
>  {
>      ASSERT(NULL != ctx);
> @@ -223,41 +272,18 @@ tls_ctx_set_options(struct tls_root_ctx *ctx,
> unsigned int ssl_flags)
>      /* default certificate verification flags */
>      int flags = SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
>
> -    /* process SSL options including minimum TLS version we will accept
> from peer */
> -    {
> -        long sslopt = SSL_OP_SINGLE_DH_USE | SSL_OP_NO_TICKET |
> SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3;
> -        int tls_ver_max = TLS_VER_UNSPEC;
> -        const int tls_ver_min =
> -            (ssl_flags >> SSLF_TLS_VERSION_MIN_SHIFT) &
> SSLF_TLS_VERSION_MIN_MASK;
> -
> -        tls_ver_max =
> -            (ssl_flags >> SSLF_TLS_VERSION_MAX_SHIFT) &
> SSLF_TLS_VERSION_MAX_MASK;
> -        if (tls_ver_max <= TLS_VER_UNSPEC)
> -        {
> -            tls_ver_max = tls_version_max();
> -        }
> -
> -        if (tls_ver_min > TLS_VER_1_0 || tls_ver_max < TLS_VER_1_0)
> -        {
> -            sslopt |= SSL_OP_NO_TLSv1;
> -        }
> -#ifdef SSL_OP_NO_TLSv1_1
> -        if (tls_ver_min > TLS_VER_1_1 || tls_ver_max < TLS_VER_1_1)
> -        {
> -            sslopt |= SSL_OP_NO_TLSv1_1;
> -        }
> -#endif
> -#ifdef SSL_OP_NO_TLSv1_2
> -        if (tls_ver_min > TLS_VER_1_2 || tls_ver_max < TLS_VER_1_2)
> -        {
> -            sslopt |= SSL_OP_NO_TLSv1_2;
> -        }
> -#endif
> +    /* process SSL options */
> +    long sslopt = SSL_OP_SINGLE_DH_USE | SSL_OP_NO_TICKET;
>  #ifdef SSL_OP_CIPHER_SERVER_PREFERENCE
> -        sslopt |= SSL_OP_CIPHER_SERVER_PREFERENCE;
> +    sslopt |= SSL_OP_CIPHER_SERVER_PREFERENCE;
>  #endif
> -        sslopt |= SSL_OP_NO_COMPRESSION;
> -        SSL_CTX_set_options(ctx->ctx, sslopt);
> +    sslopt |= SSL_OP_NO_COMPRESSION;
> +
> +    SSL_CTX_set_options(ctx->ctx, sslopt);
> +
> +    if (!tls_ctx_set_tls_versions(ctx, ssl_flags))
> +    {
> +        return false;
>      }
>
>  #ifdef SSL_MODE_RELEASE_BUFFERS
> @@ -280,6 +306,8 @@ tls_ctx_set_options(struct tls_root_ctx *ctx, unsigned
> int ssl_flags)
>      SSL_CTX_set_verify(ctx->ctx, flags, verify_callback);
>
>      SSL_CTX_set_info_callback(ctx->ctx, info_callback);
> +
> +    return true;
>  }
>

Now I can tease patchwork by

Acked-by: Selva Nair <selva.nair@gmail.com>
Reviewed-by: Selva Nair <selva.nair@gmail.com>
Tested-by: Selva Nair <selva.nair@gmail.com>

Selva
<div dir="ltr">Hi,<div><br></div><div class="gmail_extra">Thanks for last and final v4 :)</div><div class="gmail_extra"><br><div class="gmail_quote">On Fri, Jan 19, 2018 at 4:27 PM, Steffan Karger <span dir="ltr">&lt;<a href="mailto:steffan@karger.me" target="_blank">steffan@karger.me</a>&gt;</span> wrote:<br><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><span class="m_-3626978827928249090gmail-">As described in &lt;<a href="mailto:80e6b449-c536-dc87-7215-3693872bce5a@birkenwald.de" target="_blank">80e6b449-c536-dc87-7215-36938<wbr>72bce5a@birkenwald.de</a>&gt; on<br>
the openvpn-devel mailing list, --tls-version-min no longer works with<br>
OpenSSL 1.1.  Kurt Roeckx posted in a debian bug report:<br>
<br>
&quot;This is marked as important because if you switch to openssl 1.1.0<br>
the defaults minimum version in Debian is currently TLS 1.2 and<br>
you can&#39;t override it with the options that you&#39;re currently using<br>
(and are deprecated).&quot;<br>
<br>
This patch is loosely based on the original patch by Kurt, but solves the<br>
issue by adding functions to openssl-compat.h, like we also did for all<br>
other openssl 1.1. breakage.  This results in not having to add more ifdefs<br>
in ssl_openssl.c and thus cleaner code.<br>
<br>
Signed-off-by: Steffan Karger &lt;<a href="mailto:steffan@karger.me" target="_blank">steffan@karger.me</a>&gt;<br>
---<br>
v2: fix define name, obey system lib default minimum version<br>
v3: add error handling, fix bug in setting default minimum<br>
</span>v4: also update compat layer for v3 changes<br>
<br>
 src/openvpn/openssl_compat.h |  67 +++++++++++++++++++++++++++++<br>
<span class="m_-3626978827928249090gmail-"> src/openvpn/ssl.c            |   5 ++-<br>
 src/openvpn/ssl_backend.h    |   4 +-<br>
 src/openvpn/ssl_mbedtls.c    |   3 +-<br>
 src/openvpn/ssl_openssl.c    | 100 +++++++++++++++++++++++++++---<wbr>-------------<br>
</span> 5 files changed, 140 insertions(+), 39 deletions(-)<br>
<br>
diff --git a/src/openvpn/openssl_compat.h b/src/openvpn/openssl_compat.h<br>
index 05ec4e95..9f1e92a1 100644<br>
--- a/src/openvpn/openssl_compat.h<br>
+++ b/src/openvpn/openssl_compat.h<br>
@@ -661,4 +661,71 @@ EC_GROUP_order_bits(const EC_GROUP *group)<br>
<span class="m_-3626978827928249090gmail-"> #define RSA_F_RSA_OSSL_PRIVATE_<wbr>ENCRYPT       RSA_F_RSA_EAY_PRIVATE_ENCRYPT<br>
 #endif<br>
<br>
+#ifndef SSL_CTX_get_min_proto_version<br>
+/** Dummy SSL_CTX_get_min_proto_version for OpenSSL &lt; 1.1 (not really needed) */<br>
+static inline int<br>
+SSL_CTX_get_min_proto_version<wbr>(SSL_CTX *ctx)<br>
+{<br>
+    return 0;<br>
+}<br>
+#endif /* SSL_CTX_get_min_proto_version */<br>
+<br>
+#ifndef SSL_CTX_set_min_proto_version<br>
+/** Mimics SSL_CTX_set_min_proto_version for OpenSSL &lt; 1.1 */<br>
</span>+static inline int<br>
+SSL_CTX_set_min_proto_version<wbr>(SSL_CTX *ctx, long tls_ver_min)<br>
<span class="m_-3626978827928249090gmail-">+{<br>
+    long sslopt = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3; /* Never do &lt; TLS 1.0 */<br>
+<br>
+    if (tls_ver_min &gt; TLS1_VERSION)<br>
+    {<br>
+        sslopt |= SSL_OP_NO_TLSv1;<br>
+    }<br>
+#ifdef SSL_OP_NO_TLSv1_1<br>
+    if (tls_ver_min &gt; TLS1_1_VERSION)<br>
+    {<br>
+        sslopt |= SSL_OP_NO_TLSv1_1;<br>
+    }<br>
+#endif<br>
+#ifdef SSL_OP_NO_TLSv1_2<br>
+    if (tls_ver_min &gt; TLS1_2_VERSION)<br>
+    {<br>
+        sslopt |= SSL_OP_NO_TLSv1_2;<br>
+    }<br>
+#endif<br>
+    SSL_CTX_set_options(ctx, sslopt);<br>
</span>+<br>
+    return 1;<br>
<span class="m_-3626978827928249090gmail-">+}<br>
+#endif /* SSL_CTX_set_min_proto_version */<br>
+<br>
+#ifndef SSL_CTX_set_max_proto_version<br>
+/** Mimics SSL_CTX_set_max_proto_version for OpenSSL &lt; 1.1 */<br>
</span>+static inline int<br>
+SSL_CTX_set_max_proto_version<wbr>(SSL_CTX *ctx, long tls_ver_max)<br>
<span class="m_-3626978827928249090gmail-">+{<br>
+    long sslopt = 0;<br>
+<br>
+    if (tls_ver_max &lt; TLS1_VERSION)<br>
+    {<br>
+        sslopt |= SSL_OP_NO_TLSv1;<br>
+    }<br>
+#ifdef SSL_OP_NO_TLSv1_1<br>
+    if (tls_ver_max &lt; TLS1_1_VERSION)<br>
+    {<br>
+        sslopt |= SSL_OP_NO_TLSv1_1;<br>
+    }<br>
+#endif<br>
+#ifdef SSL_OP_NO_TLSv1_2<br>
+    if (tls_ver_max &lt; TLS1_2_VERSION)<br>
+    {<br>
+        sslopt |= SSL_OP_NO_TLSv1_2;<br>
+    }<br>
+#endif<br>
+    SSL_CTX_set_options(ctx, sslopt);<br>
</span>+<br>
+    return 1;<br>
<span class="m_-3626978827928249090gmail-im m_-3626978827928249090gmail-HOEnZb">+}<br>
+#endif /* SSL_CTX_set_max_proto_version */<br>
+<br>
</span><div class="m_-3626978827928249090gmail-HOEnZb"><div class="m_-3626978827928249090gmail-h5"> #endif /* OPENSSL_COMPAT_H_ */<br>
diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c<br>
index 7b428455..6c27050f 100644<br>
--- a/src/openvpn/ssl.c<br>
+++ b/src/openvpn/ssl.c<br>
@@ -622,7 +622,10 @@ init_ssl(const struct options *options, struct tls_root_ctx *new_ctx)<br>
      * cipher restrictions before loading certificates */<br>
     tls_ctx_restrict_ciphers(new_<wbr>ctx, options-&gt;cipher_list);<br>
<br>
-    tls_ctx_set_options(new_ctx, options-&gt;ssl_flags);<br>
+    if (!tls_ctx_set_options(new_ctx, options-&gt;ssl_flags))<br>
+    {<br>
+        goto err;<br>
+    }<br>
<br>
     if (options-&gt;pkcs12_file)<br>
     {<br>
diff --git a/src/openvpn/ssl_backend.h b/src/openvpn/ssl_backend.h<br>
index 7cf5d830..444fb2f9 100644<br>
--- a/src/openvpn/ssl_backend.h<br>
+++ b/src/openvpn/ssl_backend.h<br>
@@ -162,8 +162,10 @@ bool tls_ctx_initialised(struct tls_root_ctx *ctx);<br>
  *<br>
  * @param ctx           TLS context to set options on<br>
  * @param ssl_flags     SSL flags to set<br>
+ *<br>
+ * @return true on success, false otherwise.<br>
  */<br>
-void tls_ctx_set_options(struct tls_root_ctx *ctx, unsigned int ssl_flags);<br>
+bool tls_ctx_set_options(struct tls_root_ctx *ctx, unsigned int ssl_flags);<br>
<br>
 /**<br>
  * Restrict the list of ciphers that can be used within the TLS context.<br>
diff --git a/src/openvpn/ssl_mbedtls.c b/src/openvpn/ssl_mbedtls.c<br>
index 8ac52d55..d503162a 100644<br>
--- a/src/openvpn/ssl_mbedtls.c<br>
+++ b/src/openvpn/ssl_mbedtls.c<br>
@@ -206,9 +206,10 @@ key_state_export_keying_materi<wbr>al(struct key_state_ssl *ssl,<br>
 {<br>
 }<br>
<br>
-void<br>
+bool<br>
 tls_ctx_set_options(struct tls_root_ctx *ctx, unsigned int ssl_flags)<br>
 {<br>
+    return true;<br>
 }<br>
<br>
 static const char *<br>
diff --git a/src/openvpn/ssl_openssl.c b/src/openvpn/ssl_openssl.c<br>
index 86318d4c..50d68280 100644<br>
--- a/src/openvpn/ssl_openssl.c<br>
+++ b/src/openvpn/ssl_openssl.c<br>
@@ -206,16 +206,65 @@ info_callback(INFO_CALLBACK_SS<wbr>L_CONST SSL *s, int where, int ret)<br>
 int<br>
 tls_version_max(void)<br>
 {<br>
-#if defined(SSL_OP_NO_TLSv1_2)<br>
+#if defined(TLS1_2_VERSION) || defined(SSL_OP_NO_TLSv1_2)<br>
     return TLS_VER_1_2;<br>
-#elif defined(SSL_OP_NO_TLSv1_1)<br>
+#elif defined(TLS1_1_VERSION) || defined(SSL_OP_NO_TLSv1_1)<br>
     return TLS_VER_1_1;<br>
 #else<br>
     return TLS_VER_1_0;<br>
 #endif<br>
 }<br>
<br>
-void<br>
</div></div><span class="m_-3626978827928249090gmail-im m_-3626978827928249090gmail-HOEnZb">+/** Convert internal version number to openssl version number */<br>
+static int<br>
+openssl_tls_version(int ver)<br>
+{<br>
+    if (ver == TLS_VER_1_0)<br>
+    {<br>
+        return TLS1_VERSION;<br>
+    }<br>
+    else if (ver == TLS_VER_1_1)<br>
+    {<br>
+        return TLS1_1_VERSION;<br>
+    }<br>
+    else if (ver == TLS_VER_1_2)<br>
+    {<br>
+        return TLS1_2_VERSION;<br>
+    }<br>
+    return 0;<br>
+}<br>
</span><div class="m_-3626978827928249090gmail-HOEnZb"><div class="m_-3626978827928249090gmail-h5">+<br>
+static bool<br>
+tls_ctx_set_tls_versions(stru<wbr>ct tls_root_ctx *ctx, unsigned int ssl_flags)<br>
+{<br>
+    int tls_ver_min = openssl_tls_version(<br>
+        (ssl_flags &gt;&gt; SSLF_TLS_VERSION_MIN_SHIFT) &amp; SSLF_TLS_VERSION_MIN_MASK);<br>
+    int tls_ver_max = openssl_tls_version(<br>
+        (ssl_flags &gt;&gt; SSLF_TLS_VERSION_MAX_SHIFT) &amp; SSLF_TLS_VERSION_MAX_MASK);<br>
+<br>
+    if (!tls_ver_min)<br>
+    {<br>
+        /* Enforce at least TLS 1.0 */<br>
+        int cur_min = SSL_CTX_get_min_proto_version(<wbr>ctx-&gt;ctx);<br>
+        tls_ver_min = cur_min &lt; TLS1_VERSION ? TLS1_VERSION : cur_min;<br>
+    }<br>
+<br>
+    if (!SSL_CTX_set_min_proto_versio<wbr>n(ctx-&gt;ctx, tls_ver_min))<br>
+    {<br>
+        msg(D_TLS_ERRORS, &quot;%s: failed to set minimum TLS version&quot;, __func__);<br>
+        return false;<br>
+    }<br>
+<br>
+    if (tls_ver_max &amp;&amp; !SSL_CTX_set_max_proto_version<wbr>(ctx-&gt;ctx, tls_ver_max))<br>
+    {<br>
+        msg(D_TLS_ERRORS, &quot;%s: failed to set maximum TLS version&quot;, __func__);<br>
+        return false;<br>
+    }<br>
+<br>
+    return true;<br>
+}<br>
+<br>
+bool<br>
 tls_ctx_set_options(struct tls_root_ctx *ctx, unsigned int ssl_flags)<br>
 {<br>
     ASSERT(NULL != ctx);<br>
@@ -223,41 +272,18 @@ tls_ctx_set_options(struct tls_root_ctx *ctx, unsigned int ssl_flags)<br>
     /* default certificate verification flags */<br>
     int flags = SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CER<wbr>T;<br>
<br>
-    /* process SSL options including minimum TLS version we will accept from peer */<br>
-    {<br>
-        long sslopt = SSL_OP_SINGLE_DH_USE | SSL_OP_NO_TICKET | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3;<br>
-        int tls_ver_max = TLS_VER_UNSPEC;<br>
-        const int tls_ver_min =<br>
-            (ssl_flags &gt;&gt; SSLF_TLS_VERSION_MIN_SHIFT) &amp; SSLF_TLS_VERSION_MIN_MASK;<br>
-<br>
-        tls_ver_max =<br>
-            (ssl_flags &gt;&gt; SSLF_TLS_VERSION_MAX_SHIFT) &amp; SSLF_TLS_VERSION_MAX_MASK;<br>
-        if (tls_ver_max &lt;= TLS_VER_UNSPEC)<br>
-        {<br>
-            tls_ver_max = tls_version_max();<br>
-        }<br>
-<br>
-        if (tls_ver_min &gt; TLS_VER_1_0 || tls_ver_max &lt; TLS_VER_1_0)<br>
-        {<br>
-            sslopt |= SSL_OP_NO_TLSv1;<br>
-        }<br>
-#ifdef SSL_OP_NO_TLSv1_1<br>
-        if (tls_ver_min &gt; TLS_VER_1_1 || tls_ver_max &lt; TLS_VER_1_1)<br>
-        {<br>
-            sslopt |= SSL_OP_NO_TLSv1_1;<br>
-        }<br>
-#endif<br>
-#ifdef SSL_OP_NO_TLSv1_2<br>
-        if (tls_ver_min &gt; TLS_VER_1_2 || tls_ver_max &lt; TLS_VER_1_2)<br>
-        {<br>
-            sslopt |= SSL_OP_NO_TLSv1_2;<br>
-        }<br>
-#endif<br>
+    /* process SSL options */<br>
+    long sslopt = SSL_OP_SINGLE_DH_USE | SSL_OP_NO_TICKET;<br>
 #ifdef SSL_OP_CIPHER_SERVER_PREFERENC<wbr>E<br>
-        sslopt |= SSL_OP_CIPHER_SERVER_PREFERENC<wbr>E;<br>
+    sslopt |= SSL_OP_CIPHER_SERVER_PREFERENC<wbr>E;<br>
 #endif<br>
-        sslopt |= SSL_OP_NO_COMPRESSION;<br>
-        SSL_CTX_set_options(ctx-&gt;ctx, sslopt);<br>
+    sslopt |= SSL_OP_NO_COMPRESSION;<br>
+<br>
+    SSL_CTX_set_options(ctx-&gt;ctx, sslopt);<br>
+<br>
+    if (!tls_ctx_set_tls_versions(ctx<wbr>, ssl_flags))<br>
+    {<br>
+        return false;<br>
     }<br>
<br>
 #ifdef SSL_MODE_RELEASE_BUFFERS<br>
@@ -280,6 +306,8 @@ tls_ctx_set_options(struct tls_root_ctx *ctx, unsigned int ssl_flags)<br>
     SSL_CTX_set_verify(ctx-&gt;ctx, flags, verify_callback);<br>
<br>
     SSL_CTX_set_info_callback(<wbr>ctx-&gt;ctx, info_callback);<br>
+<br>
+    return true;<br>
 }<br></div></div></blockquote><div><br></div><div>Now I can tease patchwork by</div><div><br></div><div>Acked-by: Selva Nair &lt;<a href="mailto:selva.nair@gmail.com" target="_blank">selva.nair@gmail.com</a>&gt;<br>Reviewed-by: Selva Nair &lt;<a href="mailto:selva.nair@gmail.com" target="_blank">selva.nair@gmail.com</a>&gt;</div><div>Tested-by: Selva Nair &lt;<a href="mailto:selva.nair@gmail.com" target="_blank">selva.nair@gmail.com</a>&gt;<br><br></div><div>Selva </div></div></div></div>
------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot
fragmentux Jan. 19, 2018, 3:22 p.m. UTC | #2
hello

On 19/01/18 21:58, Selva Nair wrote:
> Hi,
> 
> Thanks for last and final v4 :)
> 
> On Fri, Jan 19, 2018 at 4:27 PM, Steffan Karger <steffan@karger.me> wrote:
> 
>> As described in <80e6b449-c536-dc87-7215-3693872bce5a@birkenwald.de> on
>> the openvpn-devel mailing list, --tls-version-min no longer works with
>> OpenSSL 1.1.  Kurt Roeckx posted in a debian bug report:
>>
>> "This is marked as important because if you switch to openssl 1.1.0
>> the defaults minimum version in Debian is currently TLS 1.2 and
>> you can't override it with the options that you're currently using
>> (and are deprecated)."
>>
>> This patch is loosely based on the original patch by Kurt, but solves the
>> issue by adding functions to openssl-compat.h, like we also did for all
>> other openssl 1.1. breakage.  This results in not having to add more ifdefs
>> in ssl_openssl.c and thus cleaner code.
>>
>> Signed-off-by: Steffan Karger <steffan@karger.me>
>> ---
>> v2: fix define name, obey system lib default minimum version
>> v3: add error handling, fix bug in setting default minimum
>> v4: also update compat layer for v3 changes
>>
>>   src/openvpn/openssl_compat.h |  67 +++++++++++++++++++++++++++++
>>   src/openvpn/ssl.c            |   5 ++-
>>   src/openvpn/ssl_backend.h    |   4 +-
>>   src/openvpn/ssl_mbedtls.c    |   3 +-
>>   src/openvpn/ssl_openssl.c    | 100 +++++++++++++++++++++++++++---
>> -------------
>>   5 files changed, 140 insertions(+), 39 deletions(-)
>>
>> diff --git a/src/openvpn/openssl_compat.h b/src/openvpn/openssl_compat.h
>> index 05ec4e95..9f1e92a1 100644
>> --- a/src/openvpn/openssl_compat.h
>> +++ b/src/openvpn/openssl_compat.h
>> @@ -661,4 +661,71 @@ EC_GROUP_order_bits(const EC_GROUP *group)
>>   #define RSA_F_RSA_OSSL_PRIVATE_ENCRYPT
>>   RSA_F_RSA_EAY_PRIVATE_ENCRYPT
>>   #endif
>>
>> +#ifndef SSL_CTX_get_min_proto_version
>> +/** Dummy SSL_CTX_get_min_proto_version for OpenSSL < 1.1 (not really
>> needed) */
>> +static inline int
>> +SSL_CTX_get_min_proto_version(SSL_CTX *ctx)
>> +{
>> +    return 0;
>> +}
>> +#endif /* SSL_CTX_get_min_proto_version */
>> +
>> +#ifndef SSL_CTX_set_min_proto_version
>> +/** Mimics SSL_CTX_set_min_proto_version for OpenSSL < 1.1 */
>> +static inline int
>> +SSL_CTX_set_min_proto_version(SSL_CTX *ctx, long tls_ver_min)
>> +{
>> +    long sslopt = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3; /* Never do < TLS
>> 1.0 */
>> +
>> +    if (tls_ver_min > TLS1_VERSION)
>> +    {
>> +        sslopt |= SSL_OP_NO_TLSv1;
>> +    }
>> +#ifdef SSL_OP_NO_TLSv1_1
>> +    if (tls_ver_min > TLS1_1_VERSION)
>> +    {
>> +        sslopt |= SSL_OP_NO_TLSv1_1;
>> +    }
>> +#endif
>> +#ifdef SSL_OP_NO_TLSv1_2
>> +    if (tls_ver_min > TLS1_2_VERSION)
>> +    {
>> +        sslopt |= SSL_OP_NO_TLSv1_2;
>> +    }
>> +#endif
>> +    SSL_CTX_set_options(ctx, sslopt);
>> +
>> +    return 1;
>> +}
>> +#endif /* SSL_CTX_set_min_proto_version */
>> +
>> +#ifndef SSL_CTX_set_max_proto_version
>> +/** Mimics SSL_CTX_set_max_proto_version for OpenSSL < 1.1 */
>> +static inline int
>> +SSL_CTX_set_max_proto_version(SSL_CTX *ctx, long tls_ver_max)
>> +{
>> +    long sslopt = 0;
>> +
>> +    if (tls_ver_max < TLS1_VERSION)
>> +    {
>> +        sslopt |= SSL_OP_NO_TLSv1;
>> +    }
>> +#ifdef SSL_OP_NO_TLSv1_1
>> +    if (tls_ver_max < TLS1_1_VERSION)
>> +    {
>> +        sslopt |= SSL_OP_NO_TLSv1_1;
>> +    }
>> +#endif
>> +#ifdef SSL_OP_NO_TLSv1_2
>> +    if (tls_ver_max < TLS1_2_VERSION)
>> +    {
>> +        sslopt |= SSL_OP_NO_TLSv1_2;
>> +    }
>> +#endif
>> +    SSL_CTX_set_options(ctx, sslopt);
>> +
>> +    return 1;
>> +}
>> +#endif /* SSL_CTX_set_max_proto_version */
>> +
>>   #endif /* OPENSSL_COMPAT_H_ */
>> diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c
>> index 7b428455..6c27050f 100644
>> --- a/src/openvpn/ssl.c
>> +++ b/src/openvpn/ssl.c
>> @@ -622,7 +622,10 @@ init_ssl(const struct options *options, struct
>> tls_root_ctx *new_ctx)
>>        * cipher restrictions before loading certificates */
>>       tls_ctx_restrict_ciphers(new_ctx, options->cipher_list);
>>
>> -    tls_ctx_set_options(new_ctx, options->ssl_flags);
>> +    if (!tls_ctx_set_options(new_ctx, options->ssl_flags))
>> +    {
>> +        goto err;
>> +    }
>>
>>       if (options->pkcs12_file)
>>       {
>> diff --git a/src/openvpn/ssl_backend.h b/src/openvpn/ssl_backend.h
>> index 7cf5d830..444fb2f9 100644
>> --- a/src/openvpn/ssl_backend.h
>> +++ b/src/openvpn/ssl_backend.h
>> @@ -162,8 +162,10 @@ bool tls_ctx_initialised(struct tls_root_ctx *ctx);
>>    *
>>    * @param ctx           TLS context to set options on
>>    * @param ssl_flags     SSL flags to set
>> + *
>> + * @return true on success, false otherwise.
>>    */
>> -void tls_ctx_set_options(struct tls_root_ctx *ctx, unsigned int
>> ssl_flags);
>> +bool tls_ctx_set_options(struct tls_root_ctx *ctx, unsigned int
>> ssl_flags);
>>
>>   /**
>>    * Restrict the list of ciphers that can be used within the TLS context.
>> diff --git a/src/openvpn/ssl_mbedtls.c b/src/openvpn/ssl_mbedtls.c
>> index 8ac52d55..d503162a 100644
>> --- a/src/openvpn/ssl_mbedtls.c
>> +++ b/src/openvpn/ssl_mbedtls.c
>> @@ -206,9 +206,10 @@ key_state_export_keying_material(struct
>> key_state_ssl *ssl,
>>   {
>>   }
>>
>> -void
>> +bool
>>   tls_ctx_set_options(struct tls_root_ctx *ctx, unsigned int ssl_flags)
>>   {
>> +    return true;
>>   }
>>
>>   static const char *
>> diff --git a/src/openvpn/ssl_openssl.c b/src/openvpn/ssl_openssl.c
>> index 86318d4c..50d68280 100644
>> --- a/src/openvpn/ssl_openssl.c
>> +++ b/src/openvpn/ssl_openssl.c
>> @@ -206,16 +206,65 @@ info_callback(INFO_CALLBACK_SSL_CONST SSL *s, int
>> where, int ret)
>>   int
>>   tls_version_max(void)
>>   {
>> -#if defined(SSL_OP_NO_TLSv1_2)
>> +#if defined(TLS1_2_VERSION) || defined(SSL_OP_NO_TLSv1_2)
>>       return TLS_VER_1_2;
>> -#elif defined(SSL_OP_NO_TLSv1_1)
>> +#elif defined(TLS1_1_VERSION) || defined(SSL_OP_NO_TLSv1_1)
>>       return TLS_VER_1_1;
>>   #else
>>       return TLS_VER_1_0;
>>   #endif
>>   }
>>
>> -void
>> +/** Convert internal version number to openssl version number */
>> +static int
>> +openssl_tls_version(int ver)
>> +{
>> +    if (ver == TLS_VER_1_0)
>> +    {
>> +        return TLS1_VERSION;
>> +    }
>> +    else if (ver == TLS_VER_1_1)
>> +    {
>> +        return TLS1_1_VERSION;
>> +    }
>> +    else if (ver == TLS_VER_1_2)
>> +    {
>> +        return TLS1_2_VERSION;
>> +    }
>> +    return 0;
>> +}
>> +
>> +static bool
>> +tls_ctx_set_tls_versions(struct tls_root_ctx *ctx, unsigned int
>> ssl_flags)
>> +{
>> +    int tls_ver_min = openssl_tls_version(
>> +        (ssl_flags >> SSLF_TLS_VERSION_MIN_SHIFT) &
>> SSLF_TLS_VERSION_MIN_MASK);
>> +    int tls_ver_max = openssl_tls_version(
>> +        (ssl_flags >> SSLF_TLS_VERSION_MAX_SHIFT) &
>> SSLF_TLS_VERSION_MAX_MASK);
>> +
>> +    if (!tls_ver_min)
>> +    {
>> +        /* Enforce at least TLS 1.0 */
>> +        int cur_min = SSL_CTX_get_min_proto_version(ctx->ctx);
>> +        tls_ver_min = cur_min < TLS1_VERSION ? TLS1_VERSION : cur_min;
>> +    }
>> +
>> +    if (!SSL_CTX_set_min_proto_version(ctx->ctx, tls_ver_min))
>> +    {
>> +        msg(D_TLS_ERRORS, "%s: failed to set minimum TLS version",
>> __func__);
>> +        return false;
>> +    }
>> +
>> +    if (tls_ver_max && !SSL_CTX_set_max_proto_version(ctx->ctx,
>> tls_ver_max))
>> +    {
>> +        msg(D_TLS_ERRORS, "%s: failed to set maximum TLS version",
>> __func__);
>> +        return false;
>> +    }
>> +
>> +    return true;
>> +}
>> +
>> +bool
>>   tls_ctx_set_options(struct tls_root_ctx *ctx, unsigned int ssl_flags)
>>   {
>>       ASSERT(NULL != ctx);
>> @@ -223,41 +272,18 @@ tls_ctx_set_options(struct tls_root_ctx *ctx,
>> unsigned int ssl_flags)
>>       /* default certificate verification flags */
>>       int flags = SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
>>
>> -    /* process SSL options including minimum TLS version we will accept
>> from peer */
>> -    {
>> -        long sslopt = SSL_OP_SINGLE_DH_USE | SSL_OP_NO_TICKET |
>> SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3;
>> -        int tls_ver_max = TLS_VER_UNSPEC;
>> -        const int tls_ver_min =
>> -            (ssl_flags >> SSLF_TLS_VERSION_MIN_SHIFT) &
>> SSLF_TLS_VERSION_MIN_MASK;
>> -
>> -        tls_ver_max =
>> -            (ssl_flags >> SSLF_TLS_VERSION_MAX_SHIFT) &
>> SSLF_TLS_VERSION_MAX_MASK;
>> -        if (tls_ver_max <= TLS_VER_UNSPEC)
>> -        {
>> -            tls_ver_max = tls_version_max();
>> -        }
>> -
>> -        if (tls_ver_min > TLS_VER_1_0 || tls_ver_max < TLS_VER_1_0)
>> -        {
>> -            sslopt |= SSL_OP_NO_TLSv1;
>> -        }
>> -#ifdef SSL_OP_NO_TLSv1_1
>> -        if (tls_ver_min > TLS_VER_1_1 || tls_ver_max < TLS_VER_1_1)
>> -        {
>> -            sslopt |= SSL_OP_NO_TLSv1_1;
>> -        }
>> -#endif
>> -#ifdef SSL_OP_NO_TLSv1_2
>> -        if (tls_ver_min > TLS_VER_1_2 || tls_ver_max < TLS_VER_1_2)
>> -        {
>> -            sslopt |= SSL_OP_NO_TLSv1_2;
>> -        }
>> -#endif
>> +    /* process SSL options */
>> +    long sslopt = SSL_OP_SINGLE_DH_USE | SSL_OP_NO_TICKET;
>>   #ifdef SSL_OP_CIPHER_SERVER_PREFERENCE
>> -        sslopt |= SSL_OP_CIPHER_SERVER_PREFERENCE;
>> +    sslopt |= SSL_OP_CIPHER_SERVER_PREFERENCE;
>>   #endif
>> -        sslopt |= SSL_OP_NO_COMPRESSION;
>> -        SSL_CTX_set_options(ctx->ctx, sslopt);
>> +    sslopt |= SSL_OP_NO_COMPRESSION;
>> +
>> +    SSL_CTX_set_options(ctx->ctx, sslopt);
>> +
>> +    if (!tls_ctx_set_tls_versions(ctx, ssl_flags))
>> +    {
>> +        return false;
>>       }
>>
>>   #ifdef SSL_MODE_RELEASE_BUFFERS
>> @@ -280,6 +306,8 @@ tls_ctx_set_options(struct tls_root_ctx *ctx, unsigned
>> int ssl_flags)
>>       SSL_CTX_set_verify(ctx->ctx, flags, verify_callback);
>>
>>       SSL_CTX_set_info_callback(ctx->ctx, info_callback);
>> +
>> +    return true;
>>   }
>>
> 
> Now I can tease patchwork by
> 
> Acked-by: Selva Nair <selva.nair@gmail.com>
> Reviewed-by: Selva Nair <selva.nair@gmail.com>
> Tested-by: Selva Nair <selva.nair@gmail.com>
> 

Purely to see what patchworks makes of this (i did what i could)

Also

Tested-by: Richard Bonhomme <fragmentux@g,mail.com>


------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot
Gert Doering Jan. 20, 2018, 12:12 a.m. UTC | #3
Your patch has been applied to the master branch.

I have added a pointer to the Debian bug report for completeness.

commit 0e8a30c0b05c1e2b59a1dea0a6eab5daa1d9d9a1
Author: Steffan Karger
Date:   Fri Jan 19 22:27:21 2018 +0100

     Fix --tls-version-min and --tls-version-max for OpenSSL 1.1+

     Signed-off-by: Steffan Karger <steffan@karger.me>
     Acked-by: Selva Nair <selva.nair@gmail.com>
     Message-Id: <20180119212721.25177-1-steffan@karger.me>
     URL: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=873302
     URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg16284.html
     Signed-off-by: Gert Doering <gert@greenie.muc.de>


--
kind regards,

Gert Doering


------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot

Patch

diff --git a/src/openvpn/openssl_compat.h b/src/openvpn/openssl_compat.h
index 05ec4e95..9f1e92a1 100644
--- a/src/openvpn/openssl_compat.h
+++ b/src/openvpn/openssl_compat.h
@@ -661,4 +661,71 @@  EC_GROUP_order_bits(const EC_GROUP *group)
 #define RSA_F_RSA_OSSL_PRIVATE_ENCRYPT       RSA_F_RSA_EAY_PRIVATE_ENCRYPT
 #endif
 
+#ifndef SSL_CTX_get_min_proto_version
+/** Dummy SSL_CTX_get_min_proto_version for OpenSSL < 1.1 (not really needed) */
+static inline int
+SSL_CTX_get_min_proto_version(SSL_CTX *ctx)
+{
+    return 0;
+}
+#endif /* SSL_CTX_get_min_proto_version */
+
+#ifndef SSL_CTX_set_min_proto_version
+/** Mimics SSL_CTX_set_min_proto_version for OpenSSL < 1.1 */
+static inline int
+SSL_CTX_set_min_proto_version(SSL_CTX *ctx, long tls_ver_min)
+{
+    long sslopt = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3; /* Never do < TLS 1.0 */
+
+    if (tls_ver_min > TLS1_VERSION)
+    {
+        sslopt |= SSL_OP_NO_TLSv1;
+    }
+#ifdef SSL_OP_NO_TLSv1_1
+    if (tls_ver_min > TLS1_1_VERSION)
+    {
+        sslopt |= SSL_OP_NO_TLSv1_1;
+    }
+#endif
+#ifdef SSL_OP_NO_TLSv1_2
+    if (tls_ver_min > TLS1_2_VERSION)
+    {
+        sslopt |= SSL_OP_NO_TLSv1_2;
+    }
+#endif
+    SSL_CTX_set_options(ctx, sslopt);
+
+    return 1;
+}
+#endif /* SSL_CTX_set_min_proto_version */
+
+#ifndef SSL_CTX_set_max_proto_version
+/** Mimics SSL_CTX_set_max_proto_version for OpenSSL < 1.1 */
+static inline int
+SSL_CTX_set_max_proto_version(SSL_CTX *ctx, long tls_ver_max)
+{
+    long sslopt = 0;
+
+    if (tls_ver_max < TLS1_VERSION)
+    {
+        sslopt |= SSL_OP_NO_TLSv1;
+    }
+#ifdef SSL_OP_NO_TLSv1_1
+    if (tls_ver_max < TLS1_1_VERSION)
+    {
+        sslopt |= SSL_OP_NO_TLSv1_1;
+    }
+#endif
+#ifdef SSL_OP_NO_TLSv1_2
+    if (tls_ver_max < TLS1_2_VERSION)
+    {
+        sslopt |= SSL_OP_NO_TLSv1_2;
+    }
+#endif
+    SSL_CTX_set_options(ctx, sslopt);
+
+    return 1;
+}
+#endif /* SSL_CTX_set_max_proto_version */
+
 #endif /* OPENSSL_COMPAT_H_ */
diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c
index 7b428455..6c27050f 100644
--- a/src/openvpn/ssl.c
+++ b/src/openvpn/ssl.c
@@ -622,7 +622,10 @@  init_ssl(const struct options *options, struct tls_root_ctx *new_ctx)
      * cipher restrictions before loading certificates */
     tls_ctx_restrict_ciphers(new_ctx, options->cipher_list);
 
-    tls_ctx_set_options(new_ctx, options->ssl_flags);
+    if (!tls_ctx_set_options(new_ctx, options->ssl_flags))
+    {
+        goto err;
+    }
 
     if (options->pkcs12_file)
     {
diff --git a/src/openvpn/ssl_backend.h b/src/openvpn/ssl_backend.h
index 7cf5d830..444fb2f9 100644
--- a/src/openvpn/ssl_backend.h
+++ b/src/openvpn/ssl_backend.h
@@ -162,8 +162,10 @@  bool tls_ctx_initialised(struct tls_root_ctx *ctx);
  *
  * @param ctx           TLS context to set options on
  * @param ssl_flags     SSL flags to set
+ *
+ * @return true on success, false otherwise.
  */
-void tls_ctx_set_options(struct tls_root_ctx *ctx, unsigned int ssl_flags);
+bool tls_ctx_set_options(struct tls_root_ctx *ctx, unsigned int ssl_flags);
 
 /**
  * Restrict the list of ciphers that can be used within the TLS context.
diff --git a/src/openvpn/ssl_mbedtls.c b/src/openvpn/ssl_mbedtls.c
index 8ac52d55..d503162a 100644
--- a/src/openvpn/ssl_mbedtls.c
+++ b/src/openvpn/ssl_mbedtls.c
@@ -206,9 +206,10 @@  key_state_export_keying_material(struct key_state_ssl *ssl,
 {
 }
 
-void
+bool
 tls_ctx_set_options(struct tls_root_ctx *ctx, unsigned int ssl_flags)
 {
+    return true;
 }
 
 static const char *
diff --git a/src/openvpn/ssl_openssl.c b/src/openvpn/ssl_openssl.c
index 86318d4c..50d68280 100644
--- a/src/openvpn/ssl_openssl.c
+++ b/src/openvpn/ssl_openssl.c
@@ -206,16 +206,65 @@  info_callback(INFO_CALLBACK_SSL_CONST SSL *s, int where, int ret)
 int
 tls_version_max(void)
 {
-#if defined(SSL_OP_NO_TLSv1_2)
+#if defined(TLS1_2_VERSION) || defined(SSL_OP_NO_TLSv1_2)
     return TLS_VER_1_2;
-#elif defined(SSL_OP_NO_TLSv1_1)
+#elif defined(TLS1_1_VERSION) || defined(SSL_OP_NO_TLSv1_1)
     return TLS_VER_1_1;
 #else
     return TLS_VER_1_0;
 #endif
 }
 
-void
+/** Convert internal version number to openssl version number */
+static int
+openssl_tls_version(int ver)
+{
+    if (ver == TLS_VER_1_0)
+    {
+        return TLS1_VERSION;
+    }
+    else if (ver == TLS_VER_1_1)
+    {
+        return TLS1_1_VERSION;
+    }
+    else if (ver == TLS_VER_1_2)
+    {
+        return TLS1_2_VERSION;
+    }
+    return 0;
+}
+
+static bool
+tls_ctx_set_tls_versions(struct tls_root_ctx *ctx, unsigned int ssl_flags)
+{
+    int tls_ver_min = openssl_tls_version(
+        (ssl_flags >> SSLF_TLS_VERSION_MIN_SHIFT) & SSLF_TLS_VERSION_MIN_MASK);
+    int tls_ver_max = openssl_tls_version(
+        (ssl_flags >> SSLF_TLS_VERSION_MAX_SHIFT) & SSLF_TLS_VERSION_MAX_MASK);
+
+    if (!tls_ver_min)
+    {
+        /* Enforce at least TLS 1.0 */
+        int cur_min = SSL_CTX_get_min_proto_version(ctx->ctx);
+        tls_ver_min = cur_min < TLS1_VERSION ? TLS1_VERSION : cur_min;
+    }
+
+    if (!SSL_CTX_set_min_proto_version(ctx->ctx, tls_ver_min))
+    {
+        msg(D_TLS_ERRORS, "%s: failed to set minimum TLS version", __func__);
+        return false;
+    }
+
+    if (tls_ver_max && !SSL_CTX_set_max_proto_version(ctx->ctx, tls_ver_max))
+    {
+        msg(D_TLS_ERRORS, "%s: failed to set maximum TLS version", __func__);
+        return false;
+    }
+
+    return true;
+}
+
+bool
 tls_ctx_set_options(struct tls_root_ctx *ctx, unsigned int ssl_flags)
 {
     ASSERT(NULL != ctx);
@@ -223,41 +272,18 @@  tls_ctx_set_options(struct tls_root_ctx *ctx, unsigned int ssl_flags)
     /* default certificate verification flags */
     int flags = SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
 
-    /* process SSL options including minimum TLS version we will accept from peer */
-    {
-        long sslopt = SSL_OP_SINGLE_DH_USE | SSL_OP_NO_TICKET | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3;
-        int tls_ver_max = TLS_VER_UNSPEC;
-        const int tls_ver_min =
-            (ssl_flags >> SSLF_TLS_VERSION_MIN_SHIFT) & SSLF_TLS_VERSION_MIN_MASK;
-
-        tls_ver_max =
-            (ssl_flags >> SSLF_TLS_VERSION_MAX_SHIFT) & SSLF_TLS_VERSION_MAX_MASK;
-        if (tls_ver_max <= TLS_VER_UNSPEC)
-        {
-            tls_ver_max = tls_version_max();
-        }
-
-        if (tls_ver_min > TLS_VER_1_0 || tls_ver_max < TLS_VER_1_0)
-        {
-            sslopt |= SSL_OP_NO_TLSv1;
-        }
-#ifdef SSL_OP_NO_TLSv1_1
-        if (tls_ver_min > TLS_VER_1_1 || tls_ver_max < TLS_VER_1_1)
-        {
-            sslopt |= SSL_OP_NO_TLSv1_1;
-        }
-#endif
-#ifdef SSL_OP_NO_TLSv1_2
-        if (tls_ver_min > TLS_VER_1_2 || tls_ver_max < TLS_VER_1_2)
-        {
-            sslopt |= SSL_OP_NO_TLSv1_2;
-        }
-#endif
+    /* process SSL options */
+    long sslopt = SSL_OP_SINGLE_DH_USE | SSL_OP_NO_TICKET;
 #ifdef SSL_OP_CIPHER_SERVER_PREFERENCE
-        sslopt |= SSL_OP_CIPHER_SERVER_PREFERENCE;
+    sslopt |= SSL_OP_CIPHER_SERVER_PREFERENCE;
 #endif
-        sslopt |= SSL_OP_NO_COMPRESSION;
-        SSL_CTX_set_options(ctx->ctx, sslopt);
+    sslopt |= SSL_OP_NO_COMPRESSION;
+
+    SSL_CTX_set_options(ctx->ctx, sslopt);
+
+    if (!tls_ctx_set_tls_versions(ctx, ssl_flags))
+    {
+        return false;
     }
 
 #ifdef SSL_MODE_RELEASE_BUFFERS
@@ -280,6 +306,8 @@  tls_ctx_set_options(struct tls_root_ctx *ctx, unsigned int ssl_flags)
     SSL_CTX_set_verify(ctx->ctx, flags, verify_callback);
 
     SSL_CTX_set_info_callback(ctx->ctx, info_callback);
+
+    return true;
 }
 
 void