Repository: pyca/cryptography

Issue:
FFI interface to SSL_set_min_proto_version etc
OpenSSL 1.1.0 added a number of new functions for controlling the acceptable TLS protocol versions:

```
 int SSL_CTX_set_min_proto_version(SSL_CTX *ctx, int version);
 int SSL_CTX_set_max_proto_version(SSL_CTX *ctx, int version);
 int SSL_set_min_proto_version(SSL *ssl, int version);
 int SSL_set_max_proto_version(SSL *ssl, int version);
```

Additionally, OpenSSL 1.1.1 added getters for the same information:
```
 int SSL_CTX_get_min_proto_version(SSL_CTX *ctx);
 int SSL_CTX_get_max_proto_version(SSL_CTX *ctx);
 int SSL_get_min_proto_version(SSL *ssl);
 int SSL_get_max_proto_version(SSL *ssl);
```

See https://www.openssl.org/docs/man1.1.1/man3/SSL_set_max_proto_version.html for more details of these functions. 

The intention, as documented at https://www.openssl.org/docs/man1.1.1/man3/SSL_CTX_new.html, is that these functions should be used instead of SSL_CTX_set_options and SSL_set_options for this purpose.

Anyway: it appears that these functions are not exposed via the `cryptography.hazmat.bindings.openssl.binding.Binding.lib` FFI interface. Could they be added?
Added TLS_method bindings for OpenSSL and LibreSSL
Added bindings for TLS_method TLS_client_method and TLS_server_method with support for LibreSSL and OpenSSL
Added tests for TLS_method TLS_client_method and TLS_server_method with support for LibreSSL and OpenSSL


Discussion:
While I'm on the subject, would it also be possible to add support for the `TLS_method()`, `TLS_server_method()` and `TLS_client_method()` methods which were introduced in OpenSSL 1.1.0:

```
 const SSL_METHOD *TLS_method(void);
 const SSL_METHOD *TLS_server_method(void);
 const SSL_METHOD *TLS_client_method(void);
```

(see https://www.openssl.org/docs/man1.1.1/man3/SSL_CTX_new.html)

They are the same as `SSLv23_method()`, `SSLv23_server_method()`, and `SSLv23_client_method()` respectively, but those three have very confusing names (which is why they were deprecated, I believe).
I would like to second the update of the pyca-cryptography interface to provide the interfaces recommended by @richvdh  .

I would like to specifically note that `TLS_method()` and related interfaces should be included.  In spite of modern OpenSSL versions using a processor macro to map `SSLv23_method` to `TLS_method` it is important to note that the use of the old interface name makes systems vulnerable to malicious library/package replacement.  By forcing users to use `SSLv23_method` you are creating avenues of exploitation surrounding malicious insertion of deprecated/obsolete code into system libraries.  Since pyca-cryptography merely interfaces with the underlying system OpenSSL library, forcing a programmer to go through `SSLv23_method` does not guarantee that they will end up using `TLS_method` **even though the current source code has a macro that makes these methods equivalent.**
Adding these bindings is fine, but there are a few steps:

1) A PR to this repository adding them and handling the case where they aren't available (we support OpenSSL 1.0.2 for a few more releases so this is necessary). You can see examples of that handling in various binding files and the logic we use to remove null func pointers from the lib is in `_conditional.py`.
2) A PR to pyOpenSSL that adds methods to use these bindings (and switches to calling TLS_method in the tests probably). That PR will fail the tests (except for the cryptography master branch tests) until we do a release, but those master branch tests can confirm you've implemented it properly.

The latter is a requirement because maintaining an arbitrarily large set of bindings without knowing consumers turns out to be prohibitively difficult. We've recently stripped numerous methods due to the maintenance burden so we need to have a consumer in our downstream test suite.
@reaperhulk thanks for the information, I already have some stuff in the works, so I'll look into the issues you've raised.
@reaperhulk regarding `_conditional.py`, given that older versions of OpenSSL would not support TLSv1_3_method, what seems like an appropriate list of dependent functions?  I'm tempted to include up to TLSv1_2_method (since TLS_method first appears in major release OpenSSL 1.1.0), but I'm not sure if project guidelines stipulate something more stringent than this.  I'm also observing that TLSv1_3 support doesn't seem to always externalized (at least in the last version I pulled; can provide example if required).  Any thoughts before I proceed?
There are two things at play here:

1) To the extent that it is possible make the behavior between versions the same. For example, `TLS_method` and `SSLv23_method` are the same method, so on versions prior to `TLS_method` being defined we should just define it. That way it's always available.
2) For methods that are new capabilities (e.g. `SSL_CTX_set_max_proto_version`) bind them with the `_HAS_<NAME>` pattern we use and remove them in _conditional.py.

`TLSv1_2_method` is already bound so you shouldn't need to add that. (https://github.com/pyca/cryptography/blob/master/src/_cffi_src/openssl/ssl.py#L349)
@reaperhulk duly noted, thanks for the update.  I'm hoping to have something for you guys in the next day or so.  This is fairly straightforward since (as noted) SSLv23_method and TLS_method are defined by a macro to be equivalent.
Edit: looks like it didn't handle correctly on OpenSSL 1.0.2 for some reason, have to check my local environment.

@reaperhulk I've put #5428 in to address the **TLS_method** related issues.  It looks like some of the tests are failing, as you indicated earlier.  I'm hoping this tiny PR will help me figure out the odds and ends of doing a PR for this project.

Subsequent to this PR getting through (it's a simple method exposure) I plan on examining what to do about 
```
 int SSL_CTX_set_min_proto_version(SSL_CTX *ctx, int version);
 int SSL_CTX_set_max_proto_version(SSL_CTX *ctx, int version);
 int SSL_set_min_proto_version(SSL *ssl, int version);
 int SSL_set_max_proto_version(SSL *ssl, int version);
 int SSL_CTX_get_min_proto_version(SSL_CTX *ctx);
 int SSL_CTX_get_max_proto_version(SSL_CTX *ctx);
 int SSL_get_min_proto_version(SSL *ssl);
 int SSL_get_max_proto_version(SSL *ssl);

```
These functions require a peak at the compiler options, since there isn't a convenient way to handle them for OpenSSL 1.0.2 (will need to do something similar to how SSLv3_method is handled)
8bc6920444afcdaeb4307c365b718b3ddc9e6c0c dropped support for 1.0.2, so implementing this should be more straightforward now. :)
Got distracted and had to make a new PR, because I borked my branch too much for a clean Rebase.  

Current PR in relation to the `TLS_method*` interfaces is now #5483

I will then be able to proceed to adding the new SSL_CTX option bindings, as requested by @richvdh 

Kindly requesting a review at the PR.
**NOTE**: Build system cancelled 2 checks, so I need a contributor to "kick" those checks back on for the all-clear

Got distracted and had to make a new PR, because I borked my branch too much for a clean Rebase.  

Current PR in relation to the `TLS_method*` interfaces is now #5483

I will then be able to proceed to adding the new SSL_CTX option bindings, as requested by @richvdh 

Kindly requesting a review @reaperhulk  (this successfully passed all checks in https://github.com/pyca/cryptography/pull/5428 however, the commit log became very very mangled due to some careless mistakes I had made, so I have opted to sidestep Rebasing and simply create a new Branch and PR)
@reaperhulk keep it pending as long as you want (assuming it doesn't fail on Travis CI again)
Edit: detected a merge conflict, so I'm simply pulling the work into the active Pull Request to avoid unnecessary extra work.

@reaperhulk I'm going to move the rest of the fixes for #5379 into a separate PR.

Also, the Codecov check is failing on code unrelated to this PR, so I'm not sure what I ought to be doing with that.
This looks like a better version of the one I have here: https://github.com/pyca/cryptography/pull/4920

Resolving patch:
diff --git a/src/_cffi_src/openssl/ssl.py b/src/_cffi_src/openssl/ssl.py
--- a/src/_cffi_src/openssl/ssl.py
+++ b/src/_cffi_src/openssl/ssl.py
@@ -24,6 +24,7 @@
 static const long Cryptography_HAS_PSK;
 static const long Cryptography_HAS_VERIFIED_CHAIN;
 static const long Cryptography_HAS_KEYLOG;
+static const long Cryptography_HAS_GET_PROTO_VERSION;
 
 /* Internally invented symbol to tell us if SSL_MODE_RELEASE_BUFFERS is
  * supported
@@ -312,6 +313,16 @@
 long SSL_total_renegotiations(SSL *);
 long SSL_get_secure_renegotiation_support(SSL *);
 
+long SSL_CTX_set_min_proto_version(SSL_CTX *, int);
+long SSL_CTX_set_max_proto_version(SSL_CTX *, int);
+long SSL_set_min_proto_version(SSL *, int);
+long SSL_set_max_proto_version(SSL *, int);
+
+long SSL_CTX_get_min_proto_version(SSL_CTX *);
+long SSL_CTX_get_max_proto_version(SSL_CTX *);
+long SSL_get_min_proto_version(SSL *);
+long SSL_get_max_proto_version(SSL *);
+
 /* Defined as unsigned long because SSL_OP_ALL is greater than signed 32-bit
    and Windows defines long as 32-bit. */
 unsigned long SSL_CTX_set_options(SSL_CTX *, unsigned long);
@@ -330,10 +341,6 @@
 
 /*  methods */
 
-/*
- * TLSv1_1 and TLSv1_2 are recent additions.  Only sufficiently new versions of
- * OpenSSL support them.
- */
 const SSL_METHOD *TLSv1_1_method(void);
 const SSL_METHOD *TLSv1_1_server_method(void);
 const SSL_METHOD *TLSv1_1_client_method(void);
@@ -363,6 +370,10 @@
 const SSL_METHOD *SSLv23_server_method(void);
 const SSL_METHOD *SSLv23_client_method(void);
 
+const SSL_METHOD *TLS_method(void);
+const SSL_METHOD *TLS_server_method(void);
+const SSL_METHOD *TLS_client_method(void);
+
 /*- These aren't macros these arguments are all const X on openssl > 1.0.x -*/
 SSL_CTX *SSL_CTX_new(SSL_METHOD *);
 long SSL_CTX_get_timeout(const SSL_CTX *);
@@ -674,4 +685,15 @@
 #else
 static const long Cryptography_HAS_TLSv1_3 = 1;
 #endif
+
+#if CRYPTOGRAPHY_OPENSSL_LESS_THAN_111 && !CRYPTOGRAPHY_IS_LIBRESSL
+static const long Cryptography_HAS_GET_PROTO_VERSION = 0;
+
+long (*SSL_CTX_get_min_proto_version)(SSL_CTX *) = NULL;
+long (*SSL_CTX_get_max_proto_version)(SSL_CTX *) = NULL;
+long (*SSL_get_min_proto_version)(SSL *) = NULL;
+long (*SSL_get_max_proto_version)(SSL *) = NULL;
+#else
+static const long Cryptography_HAS_GET_PROTO_VERSION = 1;
+#endif
 """
diff --git a/src/cryptography/hazmat/bindings/openssl/_conditional.py b/src/cryptography/hazmat/bindings/openssl/_conditional.py
--- a/src/cryptography/hazmat/bindings/openssl/_conditional.py
+++ b/src/cryptography/hazmat/bindings/openssl/_conditional.py
@@ -262,6 +262,15 @@ def cryptography_has_srtp():
     ]
 
 
+def cryptography_has_get_proto_version():
+    return [
+        "SSL_CTX_get_min_proto_version",
+        "SSL_CTX_get_max_proto_version",
+        "SSL_get_min_proto_version",
+        "SSL_get_max_proto_version",
+    ]
+
+
 # This is a mapping of
 # {condition: function-returning-names-dependent-on-that-condition} so we can
 # loop over them and delete unsupported names at runtime. It will be removed
@@ -309,4 +318,5 @@ def cryptography_has_srtp():
     "Cryptography_HAS_ENGINE": cryptography_has_engine,
     "Cryptography_HAS_VERIFIED_CHAIN": cryptography_has_verified_chain,
     "Cryptography_HAS_SRTP": cryptography_has_srtp,
+    "Cryptography_HAS_GET_PROTO_VERSION": cryptography_has_get_proto_version,
 }


Test patch: