Skip to content

Commit

Permalink
Add SSL_CTX_set_min_proto_version/SSL_CTX_set_max_proto_version bindi…
Browse files Browse the repository at this point in the history
…ngs (#985)

* add Context.set_*_proto_version, fix #860

* docs: add new openssl tls methods

* accept the fact that nothing can be taken for granted

* bump minimum required cryptography version to 3.3

* drop support for Python 3.5

* use binary wheels for cryptography

* Revert "use binary wheels for cryptography"

This reverts commit 91a04c6.

* docker ci: compile cryptography with rust
  • Loading branch information
mhils committed Mar 10, 2021
1 parent d290855 commit 5dc6988
Show file tree
Hide file tree
Showing 8 changed files with 115 additions and 15 deletions.
5 changes: 2 additions & 3 deletions .github/workflows/ci.yml
Expand Up @@ -11,7 +11,6 @@ jobs:
PYTHON:
# Base builds
- {VERSION: "2.7", TOXENV: "py27"}
- {VERSION: "3.5", TOXENV: "py35"}
- {VERSION: "3.6", TOXENV: "py36"}
- {VERSION: "3.7", TOXENV: "py37"}
- {VERSION: "3.8", TOXENV: "py38"}
Expand All @@ -26,7 +25,6 @@ jobs:
- {VERSION: "pypy3", TOXENV: "pypy3-cryptographyMaster"}
# -cryptographyMinimum
- {VERSION: "2.7", TOXENV: "py27-cryptographyMinimum"}
- {VERSION: "3.5", TOXENV: "py35-cryptographyMinimum"}
- {VERSION: "3.6", TOXENV: "py36-cryptographyMinimum"}
- {VERSION: "3.7", TOXENV: "py37-cryptographyMinimum"}
- {VERSION: "3.8", TOXENV: "py38-cryptographyMinimum"}
Expand Down Expand Up @@ -66,13 +64,14 @@ jobs:
matrix:
TEST:
- {CONTAINER: "stretch", TOXENV: "py27"}
- {CONTAINER: "stretch", TOXENV: "py35"}
- {CONTAINER: "ubuntu-bionic", TOXENV: "py36"}
name: "${{ matrix.TEST.TOXENV }} on ${{ matrix.TEST.CONTAINER }}"
steps:
- uses: actions/checkout@v2
- run: tox -v
env:
TOXENV: ${{ matrix.TEST.TOXENV }}
RUSTUP_HOME: /root/.rustup
- name: Upload coverage
run: |
curl -o codecov.sh -f https://codecov.io/bash || curl -o codecov.sh -f https://codecov.io/bash || curl -o codecov.sh -f https://codecov.io/bash
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.rst
Expand Up @@ -10,6 +10,9 @@ The third digit is only for regressions.
Backward-incompatible changes:
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

- The minimum ``cryptography`` version is now 3.3.
- Drop support for Python 3.5

Deprecations:
^^^^^^^^^^^^^

Expand All @@ -18,6 +21,8 @@ Changes:

- Raise an error when an invalid ALPN value is set.
`#993 <https://github.com/pyca/pyopenssl/pull/993>`_
- Added ``OpenSSL.SSL.Context.set_min_proto_version`` and ``OpenSSL.SSL.Context.set_max_proto_version``
to set the minimum and maximum supported TLS version `#985 <https://github.com/pyca/pyopenssl/pull/985>`_.

20.0.1 (2020-12-15)
-------------------
Expand Down
19 changes: 16 additions & 3 deletions doc/api/ssl.rst
Expand Up @@ -10,19 +10,32 @@
This module handles things specific to SSL. There are two objects defined:
Context, Connection.

.. py:data:: SSLv2_METHOD
.. py:data:: TLS_METHOD
TLS_SERVER_METHOD
TLS_CLIENT_METHOD
SSLv2_METHOD
SSLv3_METHOD
SSLv23_METHOD
TLSv1_METHOD
TLSv1_1_METHOD
TLSv1_2_METHOD
These constants represent the different SSL methods to use when creating a
context object. If the underlying OpenSSL build is missing support for any
of these protocols, constructing a :py:class:`Context` using the
context object. New code should only use ``TLS_METHOD``, ``TLS_SERVER_METHOD``,
or ``TLS_CLIENT_METHOD``. If the underlying OpenSSL build is missing support
for any of these protocols, constructing a :py:class:`Context` using the
corresponding :py:const:`*_METHOD` will raise an exception.


.. py:data:: SSL3_VERSION
TLS1_VERSION
TLS1_1_VERSION
TLS1_2_VERSION
TLS1_3_VERSION
These constants represent the different TLS versions to use when
setting the minimum or maximum TLS version.

.. py:data:: VERIFY_NONE
VERIFY_PEER
VERIFY_FAIL_IF_NO_PEER_CERT
Expand Down
2 changes: 1 addition & 1 deletion doc/introduction.rst
Expand Up @@ -14,7 +14,7 @@ Other OpenSSL wrappers for Python at the time were also limited, though in diffe
Later it was maintained by `Jean-Paul Calderone`_ who among other things managed to make pyOpenSSL a pure Python project which the current maintainers are *very* grateful for.

Over the time the standard library's ``ssl`` module improved, never reaching the completeness of pyOpenSSL's API coverage.
Despite `PEP 466`_ many useful features remain Python 3-only and pyOpenSSL remains the only alternative for full-featured TLS code across all noteworthy Python versions from 2.7 through 3.5 and PyPy_.
Despite `PEP 466`_ many useful features remain Python 3-only and pyOpenSSL remains the only alternative for full-featured TLS code across all noteworthy Python versions from 2.7 through 3.6 and PyPy_.


Development
Expand Down
7 changes: 4 additions & 3 deletions setup.py
Expand Up @@ -79,7 +79,6 @@ def find_meta(meta):
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
Expand All @@ -90,12 +89,14 @@ def find_meta(meta):
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: System :: Networking",
],
python_requires=">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*",
python_requires=(
">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*"
),
packages=find_packages(where="src"),
package_dir={"": "src"},
install_requires=[
# Fix cryptographyMinimum in tox.ini when changing this!
"cryptography>=3.2",
"cryptography>=3.3",
"six>=1.5.2",
],
extras_require={
Expand Down
60 changes: 58 additions & 2 deletions src/OpenSSL/SSL.py
Expand Up @@ -44,6 +44,14 @@
"TLSv1_METHOD",
"TLSv1_1_METHOD",
"TLSv1_2_METHOD",
"TLS_METHOD",
"TLS_SERVER_METHOD",
"TLS_CLIENT_METHOD",
"SSL3_VERSION",
"TLS1_VERSION",
"TLS1_1_VERSION",
"TLS1_2_VERSION",
"TLS1_3_VERSION",
"OP_NO_SSLv2",
"OP_NO_SSLv3",
"OP_NO_TLSv1",
Expand Down Expand Up @@ -139,6 +147,24 @@ class _buffer(object):
TLSv1_METHOD = 4
TLSv1_1_METHOD = 5
TLSv1_2_METHOD = 6
TLS_METHOD = 7
TLS_SERVER_METHOD = 8
TLS_CLIENT_METHOD = 9

try:
SSL3_VERSION = _lib.SSL3_VERSION
TLS1_VERSION = _lib.TLS1_VERSION
TLS1_1_VERSION = _lib.TLS1_1_VERSION
TLS1_2_VERSION = _lib.TLS1_2_VERSION
TLS1_3_VERSION = _lib.TLS1_3_VERSION
except AttributeError:
# Hardcode constants for cryptography < 3.4, see
# https://github.com/pyca/pyopenssl/pull/985#issuecomment-775186682
SSL3_VERSION = 768
TLS1_VERSION = 769
TLS1_1_VERSION = 770
TLS1_2_VERSION = 771
TLS1_3_VERSION = 772

OP_NO_SSLv2 = _lib.SSL_OP_NO_SSLv2
OP_NO_SSLv3 = _lib.SSL_OP_NO_SSLv3
Expand Down Expand Up @@ -603,8 +629,9 @@ class Context(object):
:class:`OpenSSL.SSL.Context` instances define the parameters for setting
up new SSL connections.
:param method: One of SSLv2_METHOD, SSLv3_METHOD, SSLv23_METHOD, or
TLSv1_METHOD.
:param method: One of TLS_METHOD, TLS_CLIENT_METHOD, or TLS_SERVER_METHOD.
SSLv23_METHOD, TLSv1_METHOD, etc. are deprecated and should
not be used.
"""

_methods = {
Expand All @@ -614,6 +641,9 @@ class Context(object):
TLSv1_METHOD: "TLSv1_method",
TLSv1_1_METHOD: "TLSv1_1_method",
TLSv1_2_METHOD: "TLSv1_2_method",
TLS_METHOD: "TLS_method",
TLS_SERVER_METHOD: "TLS_server_method",
TLS_CLIENT_METHOD: "TLS_client_method",
}
_methods = dict(
(identifier, getattr(_lib, name))
Expand Down Expand Up @@ -661,6 +691,32 @@ def __init__(self, method):

self.set_mode(_lib.SSL_MODE_ENABLE_PARTIAL_WRITE)

def set_min_proto_version(self, version):
"""
Set the minimum supported protocol version. Setting the minimum
version to 0 will enable protocol versions down to the lowest version
supported by the library.
If the underlying OpenSSL build is missing support for the selected
version, this method will raise an exception.
"""
_openssl_assert(
_lib.SSL_CTX_set_min_proto_version(self._context, version) == 1
)

def set_max_proto_version(self, version):
"""
Set the maximum supported protocol version. Setting the maximum
version to 0 will enable protocol versions up to the highest version
supported by the library.
If the underlying OpenSSL build is missing support for the selected
version, this method will raise an exception.
"""
_openssl_assert(
_lib.SSL_CTX_set_max_proto_version(self._context, version) == 1
)

def load_verify_locations(self, cafile, capath=None):
"""
Let SSL know where we can find trusted certificates for the certificate
Expand Down
28 changes: 27 additions & 1 deletion tests/test_ssl.py
Expand Up @@ -48,7 +48,14 @@
from OpenSSL.crypto import dump_certificate, load_certificate
from OpenSSL.crypto import get_elliptic_curves

from OpenSSL.SSL import OPENSSL_VERSION_NUMBER, SSLEAY_VERSION, SSLEAY_CFLAGS
from OpenSSL.SSL import (
OPENSSL_VERSION_NUMBER,
SSLEAY_VERSION,
SSLEAY_CFLAGS,
TLS_METHOD,
TLS1_2_VERSION,
TLS1_1_VERSION,
)
from OpenSSL.SSL import SSLEAY_PLATFORM, SSLEAY_DIR, SSLEAY_BUILT_ON
from OpenSSL.SSL import SENT_SHUTDOWN, RECEIVED_SHUTDOWN
from OpenSSL.SSL import (
Expand Down Expand Up @@ -1039,6 +1046,25 @@ def keylog(conn, line):
assert all(isinstance(conn, Connection) for conn, line in called)
assert all(b"CLIENT_RANDOM" in line for conn, line in called)

def test_set_proto_version(self):
server_context = Context(TLS_METHOD)
server_context.use_certificate(
load_certificate(FILETYPE_PEM, root_cert_pem)
)
server_context.use_privatekey(
load_privatekey(FILETYPE_PEM, root_key_pem)
)
server_context.set_min_proto_version(TLS1_2_VERSION)

client_context = Context(TLS_METHOD)
client_context.set_max_proto_version(TLS1_1_VERSION)

with pytest.raises(Error, match="unsupported protocol"):
self._handshake_test(server_context, client_context)

client_context.set_max_proto_version(0)
self._handshake_test(server_context, client_context)

def _load_verify_locations_test(self, *args):
"""
Create a client context which will verify the peer certificate and call
Expand Down
4 changes: 2 additions & 2 deletions tox.ini
@@ -1,5 +1,5 @@
[tox]
envlist = {pypy,pypy3,py27,py35,py36,py37,py38,py39}{,-cryptographyMaster,-cryptographyMinimum}{,-randomorder},py37-twistedMaster,pypi-readme,check-manifest,flake8,docs,coverage-report
envlist = {pypy,pypy3,py27,py36,py37,py38,py39}{,-cryptographyMaster,-cryptographyMinimum}{,-randomorder},py37-twistedMaster,pypi-readme,check-manifest,flake8,docs,coverage-report

[testenv]
whitelist_externals =
Expand All @@ -10,7 +10,7 @@ extras =
deps =
coverage>=4.2
cryptographyMaster: git+https://github.com/pyca/cryptography.git
cryptographyMinimum: cryptography==3.2
cryptographyMinimum: cryptography==3.3
randomorder: pytest-randomly
setenv =
# Do not allow the executing environment to pollute the test environment
Expand Down

0 comments on commit 5dc6988

Please sign in to comment.