From d3c13ace0dba79e9d4df7218105ca51a9f99b88a Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Wed, 11 May 2022 15:39:35 +0200 Subject: [PATCH 1/5] add `Connection.use_(certificate|privatekey)` --- CHANGELOG.rst | 2 ++ src/OpenSSL/SSL.py | 32 ++++++++++++++++++++++++++++++++ tests/test_ssl.py | 27 +++++++++++++++++++++++++++ 3 files changed, 61 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e1546f7a2..f23f6082d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -25,6 +25,8 @@ Changes: - Add ``OpenSSL.SSL.Connection.set_verify`` and ``OpenSSL.SSL.Connection.get_verify_mode`` to override the context object's verification flags. `#1073 `_ +- Add ``OpenSSL.SSL.Connection.use_certificate`` and ``OpenSSL.SSL.Connection.use_privatekey`` + to set a certificate per connection (and not just per context) `#1121 `_. 22.0.0 (2022-01-29) ------------------- diff --git a/src/OpenSSL/SSL.py b/src/OpenSSL/SSL.py index 6a3318cda..cf0ac0f55 100644 --- a/src/OpenSSL/SSL.py +++ b/src/OpenSSL/SSL.py @@ -954,6 +954,7 @@ def use_certificate(self, cert): :param cert: The X509 object :return: None """ + # Mirrored at Connection.use_certificate if not isinstance(cert, X509): raise TypeError("cert must be an X509 instance") @@ -1015,6 +1016,7 @@ def use_privatekey(self, pkey): :param pkey: The PKey object :return: None """ + # Mirrored at Connection.use_privatekey if not isinstance(pkey, PKey): raise TypeError("pkey must be a PKey instance") @@ -1780,6 +1782,36 @@ def get_verify_mode(self): """ return _lib.SSL_get_verify_mode(self._ssl) + def use_certificate(self, cert): + """ + Load a certificate from a X509 object + + :param cert: The X509 object + :return: None + """ + # Mirrored from Context.use_certificate + if not isinstance(cert, X509): + raise TypeError("cert must be an X509 instance") + + use_result = _lib.SSL_use_certificate(self._ssl, cert._x509) + if not use_result: + _raise_current_error() + + def use_privatekey(self, pkey): + """ + Load a private key from a PKey object + + :param pkey: The PKey object + :return: None + """ + # Mirrored from Context.use_privatekey + if not isinstance(pkey, PKey): + raise TypeError("pkey must be a PKey instance") + + use_result = _lib.SSL_use_PrivateKey(self._ssl, pkey._pkey) + if not use_result: + self._context._raise_passphrase_exception() + def set_ciphertext_mtu(self, mtu): """ For DTLS, set the maximum UDP payload size (*not* including IP/UDP diff --git a/tests/test_ssl.py b/tests/test_ssl.py index 5e69acee0..0d6a9417f 100644 --- a/tests/test_ssl.py +++ b/tests/test_ssl.py @@ -625,6 +625,7 @@ def test_use_privatekey(self): """ `Context.use_privatekey` takes an `OpenSSL.crypto.PKey` instance. """ + # Mirrored at TestConnection.test_use_privatekey key = PKey() key.generate_key(TYPE_RSA, 1024) ctx = Context(SSLv23_METHOD) @@ -709,6 +710,7 @@ def test_use_certificate(self): `Context.use_certificate` sets the certificate which will be used to identify connections created using the context. """ + # Mirrored at TestConnection.test_use_certificate # TODO # Hard to assert anything. But we could set a privatekey then ask # OpenSSL if the cert and key agree using check_privatekey. Then as @@ -2206,6 +2208,31 @@ def test_type(self): ctx = Context(SSLv23_METHOD) assert is_consistent_type(Connection, "Connection", ctx, None) + def test_use_privatekey(self): + """ + `Connection.use_privatekey` takes an `OpenSSL.crypto.PKey` instance. + """ + # Mirrored from TestContext.test_use_privatekey + key = PKey() + key.generate_key(TYPE_RSA, 1024) + ctx = Context(SSLv23_METHOD) + connection = Connection(ctx, None) + connection.use_privatekey(key) + with pytest.raises(TypeError): + connection.use_privatekey("") + + def test_use_certificate(self): + """ + `Connection.use_certificate` sets the certificate which will be + used to identify connections created using the context. + """ + # Mirrored from TestContext.test_use_certificate + ctx = Context(SSLv23_METHOD) + connection = Connection(ctx, None) + connection.use_certificate( + load_certificate(FILETYPE_PEM, root_cert_pem) + ) + @pytest.mark.parametrize("bad_context", [object(), "context", None, 1]) def test_wrong_args(self, bad_context): """ From d4afbb67bb1b16574878dec31e4de753f5347af6 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Wed, 7 Sep 2022 12:56:15 +0200 Subject: [PATCH 2/5] bump minimum cryptography version --- setup.py | 2 +- tox.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index b30c15910..df11bb69c 100755 --- a/setup.py +++ b/setup.py @@ -96,7 +96,7 @@ def find_meta(meta): package_dir={"": "src"}, install_requires=[ # Fix cryptographyMinimum in tox.ini when changing this! - "cryptography>=37.0.2", + "cryptography>=38.0.0", ], extras_require={ "test": ["flaky", "pretend", "pytest>=3.0.1"], diff --git a/tox.ini b/tox.ini index 4d69405b4..76368d0ff 100644 --- a/tox.ini +++ b/tox.ini @@ -10,7 +10,7 @@ extras = deps = coverage>=4.2 cryptographyMain: git+https://github.com/pyca/cryptography.git - cryptographyMinimum: cryptography==37.0.2 + cryptographyMinimum: cryptography==38.0.0 randomorder: pytest-randomly setenv = # Do not allow the executing environment to pollute the test environment From 48ec4d93afc918919735a6b7187f2690724f9b75 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Wed, 7 Sep 2022 13:28:58 +0200 Subject: [PATCH 3/5] deduplicate tests --- tests/test_ssl.py | 134 ++++++++++++++++++++++------------------------ 1 file changed, 65 insertions(+), 69 deletions(-) diff --git a/tests/test_ssl.py b/tests/test_ssl.py index 0d6a9417f..108c26abc 100644 --- a/tests/test_ssl.py +++ b/tests/test_ssl.py @@ -29,6 +29,7 @@ socket, ) from sys import getfilesystemencoding, platform +from typing import Union from warnings import simplefilter from weakref import ref @@ -621,18 +622,6 @@ def test_type(self): """ assert is_consistent_type(Context, "Context", TLSv1_METHOD) - def test_use_privatekey(self): - """ - `Context.use_privatekey` takes an `OpenSSL.crypto.PKey` instance. - """ - # Mirrored at TestConnection.test_use_privatekey - key = PKey() - key.generate_key(TYPE_RSA, 1024) - ctx = Context(SSLv23_METHOD) - ctx.use_privatekey(key) - with pytest.raises(TypeError): - ctx.use_privatekey("") - def test_use_privatekey_file_missing(self, tmpfile): """ `Context.use_privatekey_file` raises `OpenSSL.SSL.Error` when passed @@ -686,38 +675,6 @@ def test_use_privatekey_file_unicode(self, tmpfile): FILETYPE_PEM, ) - def test_use_certificate_wrong_args(self): - """ - `Context.use_certificate_wrong_args` raises `TypeError` when not passed - exactly one `OpenSSL.crypto.X509` instance as an argument. - """ - ctx = Context(SSLv23_METHOD) - with pytest.raises(TypeError): - ctx.use_certificate("hello, world") - - def test_use_certificate_uninitialized(self): - """ - `Context.use_certificate` raises `OpenSSL.SSL.Error` when passed a - `OpenSSL.crypto.X509` instance which has not been initialized - (ie, which does not actually have any certificate data). - """ - ctx = Context(SSLv23_METHOD) - with pytest.raises(Error): - ctx.use_certificate(X509()) - - def test_use_certificate(self): - """ - `Context.use_certificate` sets the certificate which will be - used to identify connections created using the context. - """ - # Mirrored at TestConnection.test_use_certificate - # TODO - # Hard to assert anything. But we could set a privatekey then ask - # OpenSSL if the cert and key agree using check_privatekey. Then as - # long as check_privatekey works right we're good... - ctx = Context(SSLv23_METHOD) - ctx.use_certificate(load_certificate(FILETYPE_PEM, root_cert_pem)) - def test_use_certificate_file_wrong_args(self): """ `Context.use_certificate_file` raises `TypeError` if the first @@ -2182,6 +2139,70 @@ def test_construction(self): assert isinstance(new_session, Session) +@pytest.fixture(params=["context", "connection"]) +def ctx_or_conn(request) -> Union[Context, Connection]: + ctx = Context(SSLv23_METHOD) + if request.param == "context": + return ctx + else: + return Connection(ctx, None) + + +class TestContextConnection: + """ + Unit test for methods that are exposed both by Connection and Context objects. + """ + def test_use_privatekey(self, ctx_or_conn): + """ + `use_privatekey` takes an `OpenSSL.crypto.PKey` instance. + """ + key = PKey() + key.generate_key(TYPE_RSA, 1024) + + ctx_or_conn.use_privatekey(key) + with pytest.raises(TypeError): + ctx_or_conn.use_privatekey("") + + def test_use_privatekey_wrong_key(self, ctx_or_conn): + """ + `use_privatekey` raises `OpenSSL.SSL.Error` when passed a + `OpenSSL.crypto.PKey` instance which has not been initialized. + """ + key = PKey() + key.generate_key(TYPE_RSA, 1024) + ctx_or_conn.use_certificate(load_certificate(FILETYPE_PEM, root_cert_pem)) + with pytest.raises(Error): + ctx_or_conn.use_privatekey(key) + + def test_use_certificate(self, ctx_or_conn): + """ + `use_certificate` sets the certificate which will be + used to identify connections created using the context. + """ + # TODO + # Hard to assert anything. But we could set a privatekey then ask + # OpenSSL if the cert and key agree using check_privatekey. Then as + # long as check_privatekey works right we're good... + ctx_or_conn.use_certificate(load_certificate(FILETYPE_PEM, root_cert_pem)) + + def test_use_certificate_wrong_args(self, ctx_or_conn): + """ + `use_certificate_wrong_args` raises `TypeError` when not passed + exactly one `OpenSSL.crypto.X509` instance as an argument. + """ + with pytest.raises(TypeError): + ctx_or_conn.use_certificate("hello, world") + + def test_use_certificate_uninitialized(self, ctx_or_conn): + """ + `use_certificate` raises `OpenSSL.SSL.Error` when passed a + `OpenSSL.crypto.X509` instance which has not been initialized + (ie, which does not actually have any certificate data). + """ + with pytest.raises(Error): + ctx_or_conn.use_certificate(X509()) + + class TestConnection: """ Unit tests for `OpenSSL.SSL.Connection`. @@ -2208,31 +2229,6 @@ def test_type(self): ctx = Context(SSLv23_METHOD) assert is_consistent_type(Connection, "Connection", ctx, None) - def test_use_privatekey(self): - """ - `Connection.use_privatekey` takes an `OpenSSL.crypto.PKey` instance. - """ - # Mirrored from TestContext.test_use_privatekey - key = PKey() - key.generate_key(TYPE_RSA, 1024) - ctx = Context(SSLv23_METHOD) - connection = Connection(ctx, None) - connection.use_privatekey(key) - with pytest.raises(TypeError): - connection.use_privatekey("") - - def test_use_certificate(self): - """ - `Connection.use_certificate` sets the certificate which will be - used to identify connections created using the context. - """ - # Mirrored from TestContext.test_use_certificate - ctx = Context(SSLv23_METHOD) - connection = Connection(ctx, None) - connection.use_certificate( - load_certificate(FILETYPE_PEM, root_cert_pem) - ) - @pytest.mark.parametrize("bad_context", [object(), "context", None, 1]) def test_wrong_args(self, bad_context): """ From 8d9514a48a8c71a211069ecbbc897a08c3555c48 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Wed, 7 Sep 2022 13:30:40 +0200 Subject: [PATCH 4/5] black! --- tests/test_ssl.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/test_ssl.py b/tests/test_ssl.py index 108c26abc..c143516bb 100644 --- a/tests/test_ssl.py +++ b/tests/test_ssl.py @@ -2152,6 +2152,7 @@ class TestContextConnection: """ Unit test for methods that are exposed both by Connection and Context objects. """ + def test_use_privatekey(self, ctx_or_conn): """ `use_privatekey` takes an `OpenSSL.crypto.PKey` instance. @@ -2170,7 +2171,9 @@ def test_use_privatekey_wrong_key(self, ctx_or_conn): """ key = PKey() key.generate_key(TYPE_RSA, 1024) - ctx_or_conn.use_certificate(load_certificate(FILETYPE_PEM, root_cert_pem)) + ctx_or_conn.use_certificate( + load_certificate(FILETYPE_PEM, root_cert_pem) + ) with pytest.raises(Error): ctx_or_conn.use_privatekey(key) @@ -2183,7 +2186,9 @@ def test_use_certificate(self, ctx_or_conn): # Hard to assert anything. But we could set a privatekey then ask # OpenSSL if the cert and key agree using check_privatekey. Then as # long as check_privatekey works right we're good... - ctx_or_conn.use_certificate(load_certificate(FILETYPE_PEM, root_cert_pem)) + ctx_or_conn.use_certificate( + load_certificate(FILETYPE_PEM, root_cert_pem) + ) def test_use_certificate_wrong_args(self, ctx_or_conn): """ From f562223d9ed40d735ac90d63ab4a1bee8fb03822 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Wed, 7 Sep 2022 13:32:45 +0200 Subject: [PATCH 5/5] max line length --- tests/test_ssl.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_ssl.py b/tests/test_ssl.py index c143516bb..39d3af525 100644 --- a/tests/test_ssl.py +++ b/tests/test_ssl.py @@ -2150,7 +2150,8 @@ def ctx_or_conn(request) -> Union[Context, Connection]: class TestContextConnection: """ - Unit test for methods that are exposed both by Connection and Context objects. + Unit test for methods that are exposed both by Connection and Context + objects. """ def test_use_privatekey(self, ctx_or_conn):