classification
Title: LibreSSL does not tolerate setting minimum_version greater than maximum_version
Type: behavior Stage: patch review
Components: SSL, Tests Versions: Python 3.8, Python 3.7
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: christian.heimes Nosy List: Alan.Huang, alex, christian.heimes, dstufft, janssen, matrixise, twouters
Priority: normal Keywords: patch

Created on 2018-06-29 16:09 by Alan.Huang, last changed 2019-09-11 17:59 by matrixise.

Pull Requests
URL Status Linked Edit
PR 8055 open Alan.Huang, 2018-07-02 21:04
PR 13783 merged christian.heimes, 2019-06-03 18:48
PR 15997 merged miss-islington, 2019-09-11 17:25
Messages (4)
msg320722 - (view) Author: Alan Huang (Alan.Huang) * Date: 2018-06-29 16:09
LibreSSL has a function called `ssl_clamp_version_range` that is called before attempting to set the minimum and maximum protocol versions in `ssl_version_set_{min,max}`. The function disallows setting ranges that are invalid (i.e., where minimum_version > maximum_version). OpenSSL does not disallow this behavior.

As a result, attempting to set a minimum_version greater than a maximum_version results in a ValueError when Python is built with LibreSSL.

There are two things that might need fixing here:
1. Replace the ValueError "Unsupported protocol version 0x%x" message with a more generic one. The assumption that the only way the operation can fail is if the underlying library does not support the protocol version is incorrect.
   This can be done by either making the message more generic or by introducing another error message to handle this case.
2. Change test_min_max_version lines 3575-3576 to set the maximum_version before the minimum_version.

Here's some Python code to reproduce the above-mentioned error:
```
import ssl

ctx = ssl.SSLContext()
ctx.maximum_version = ssl.TLSVersion.TLSv1_1
ctx.minimum_version = ssl.TLSVersion.TLSv1_2

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/alan/src/cpython/Lib/ssl.py", line 491, in minimum_version
    super(SSLContext, SSLContext).minimum_version.__set__(self, value)
ValueError: Unsupported protocol version 0x303
```

Here's some example C code:
```
#include <openssl/ssl.h>
#include <openssl/ossl_typ.h>
#include <stdio.h>

int main(){
    SSL_CTX *ctx = NULL;
    ctx = SSL_CTX_new(TLS_method());

    printf("setting max to TLSv1.1: ");
    if(SSL_CTX_set_max_proto_version(ctx, TLS1_1_VERSION)){
        printf("success\n");
    }   
    else{
        printf("failure\n");
    }   
    printf("setting min to TLSv1.2: ");
    if(SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION)){
        printf("success\n");
    }   
    else{
        printf("failure\n");
    }   

    printf("min ver: %d\n", SSL_CTX_get_min_proto_version(ctx));
    printf("max ver: %d\n", SSL_CTX_get_max_proto_version(ctx));
    return 0;
}
```

Under LibreSSL 2.7.4, this produces:
```
setting max to TLSv1.1: success
setting min to TLSv1.2: failure
min ver: 769
max ver: 770
```

Under OpenSSL 1.1.0g, this produces:
```
setting max to TLSv1.1: success
setting min to TLSv1.2: success
min ver: 771
max ver: 770
```

The test that failed:
======================================================================
ERROR: test_min_max_version (test.test_ssl.ThreadedTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/alan/src/cpython/Lib/test/test_ssl.py", line 3575, in test_min_max_version
    server_context.minimum_version = ssl.TLSVersion.TLSv1_2
  File "/home/alan/src/cpython/Lib/ssl.py", line 491, in minimum_version
    super(SSLContext, SSLContext).minimum_version.__set__(self, value)
ValueError: Unsupported protocol version 0x303
msg320887 - (view) Author: Alan Huang (Alan.Huang) * Date: 2018-07-02 14:38
Strangely, LibreSSL's `ssl_clamp_version_range` function is perfectly happy to accept minimum protocol versions lower than the lowest supported protocol version, and likewise is happy to accept maximum protocol versions higher than the highest supported protocol version.
In said case, the minimum/maximum protocol version is set to clamp_min/clamp_max (i.e., the minimum/maximum protocol version supported by the internal method).

As such, the assertion test `ctx.minimum_version = 42` on line 1127 in test_min_max_version in test_ssl.py fails.

A possible fix would be to add another check in `set_min_max_proto_version` _ssl.c that checks if the current set protocol version is equal to the value passed (with exceptions for the magic constants of `MINIMUM_SUPPORTED` and `MAXIMUM_SUPPORTED`), and if not, raise a ValueError as well.

One dilemma is whether to reset the respective version back to what it was before the attempt, which I think should be done.
msg351990 - (view) Author: Thomas Wouters (twouters) * (Python committer) Date: 2019-09-11 17:24
New changeset c9bc49c5f6e26a7c958307c2ac338951a7534d9a by T. Wouters (Christian Heimes) in branch 'master':
bpo-34001: Fix test_ssl with LibreSSL (GH-13783)
https://github.com/python/cpython/commit/c9bc49c5f6e26a7c958307c2ac338951a7534d9a
msg352006 - (view) Author: Stéphane Wirtel (matrixise) * (Python committer) Date: 2019-09-11 17:59
New changeset d6ac67f48f5079efc3fa4be3316a9578edb56e0d by Stéphane Wirtel (Miss Islington (bot)) in branch '3.8':
bpo-34001: Fix test_ssl with LibreSSL (GH-13783) (#15997)
https://github.com/python/cpython/commit/d6ac67f48f5079efc3fa4be3316a9578edb56e0d
History
Date User Action Args
2019-09-11 17:59:15matrixisesetnosy: + matrixise
messages: + msg352006
2019-09-11 17:25:01miss-islingtonsetpull_requests: + pull_request15624
2019-09-11 17:24:50twouterssetnosy: + twouters
messages: + msg351990
2019-06-03 18:48:29christian.heimessetpull_requests: + pull_request13668
2018-07-02 21:04:24Alan.Huangsetkeywords: + patch
stage: patch review
pull_requests: + pull_request7663
2018-07-02 14:38:32Alan.Huangsetmessages: + msg320887
2018-06-29 16:09:13Alan.Huangcreate