From 5189b1e177f66602280adacafc919a942efd526c Mon Sep 17 00:00:00 2001 From: Sasha Romijn Date: Mon, 1 Apr 2024 20:22:54 +0200 Subject: [PATCH 1/3] Add set_sigalgs to ssl_client --- nassl/_nassl/nassl_SSL.c | 21 +++++++++++++++++++++ nassl/ssl_client.py | 4 ++++ 2 files changed, 25 insertions(+) diff --git a/nassl/_nassl/nassl_SSL.c b/nassl/_nassl/nassl_SSL.c index f898360..a2d22b7 100755 --- a/nassl/_nassl/nassl_SSL.c +++ b/nassl/_nassl/nassl_SSL.c @@ -813,6 +813,24 @@ static PyObject* nassl_SSL_set_ciphersuites(nassl_SSL_Object *self, PyObject *ar } +// SSL_set1_sigalgs_list() is only available in OpenSSL 1.1.1 +static PyObject* nassl_SSL_set1_sigalgs_list(nassl_SSL_Object *self, PyObject *args) +{ + char *sigalgList; + if (!PyArg_ParseTuple(args, "s", &sigalgList)) + { + return NULL; + } + + if (!SSL_set1_sigalgs_list(self->ssl, sigalgList)) + { + return raise_OpenSSL_error(); + } + + Py_RETURN_NONE; +} + + static PyObject* nassl_SSL_get0_verified_chain(nassl_SSL_Object *self, PyObject *args) { STACK_OF(X509) *verifiedCertChain = NULL; @@ -1181,6 +1199,9 @@ static PyMethodDef nassl_SSL_Object_methods[] = {"set_ciphersuites", (PyCFunction)nassl_SSL_set_ciphersuites, METH_VARARGS, "OpenSSL's SSL_set_ciphersuites()." }, + {"set1_sigalgs_list", (PyCFunction)nassl_SSL_set1_sigalgs_list, METH_VARARGS, + "OpenSSL's SSL_set1_sigalgs_list()." + }, {"get0_verified_chain", (PyCFunction)nassl_SSL_get0_verified_chain, METH_NOARGS, "OpenSSL's SSL_get0_verified_chain(). Returns an array of _nassl.X509 objects." }, diff --git a/nassl/ssl_client.py b/nassl/ssl_client.py index 0d38de0..810a099 100755 --- a/nassl/ssl_client.py +++ b/nassl/ssl_client.py @@ -449,6 +449,10 @@ def set_ciphersuites(self, cipher_suites: str) -> None: # TODO(AD): Eventually merge this method with get/set_cipher_list() self._ssl.set_ciphersuites(cipher_suites) + def set_sigalgs(self, cipher_suites: str) -> None: + """Set the enabled signature algorithms, e.g. 'ECDSA+SHA256:RSA+SHA256'""" + self._ssl.set1_sigalgs_list(cipher_suites) + def set_groups(self, supported_groups: List[OpenSslEcNidEnum]) -> None: """Specify elliptic curves or DH groups that are supported by the client in descending order.""" self._ssl.set1_groups(supported_groups) From ff2bdae06a55c5761ebe97aa2548bdfb20fe324a Mon Sep 17 00:00:00 2001 From: Sasha Romijn Date: Mon, 1 Apr 2024 20:47:13 +0200 Subject: [PATCH 2/3] Improved implementation for set_sigalgs --- nassl/_nassl/nassl_SSL.c | 44 +++++++++++++++++++++++++++++++------ nassl/ephemeral_key_info.py | 3 +++ nassl/ssl_client.py | 20 +++++++++++++---- tests/ssl_client_test.py | 22 ++++++++++++++++++- 4 files changed, 77 insertions(+), 12 deletions(-) diff --git a/nassl/_nassl/nassl_SSL.c b/nassl/_nassl/nassl_SSL.c index a2d22b7..1b5e3d5 100755 --- a/nassl/_nassl/nassl_SSL.c +++ b/nassl/_nassl/nassl_SSL.c @@ -813,20 +813,50 @@ static PyObject* nassl_SSL_set_ciphersuites(nassl_SSL_Object *self, PyObject *ar } -// SSL_set1_sigalgs_list() is only available in OpenSSL 1.1.1 -static PyObject* nassl_SSL_set1_sigalgs_list(nassl_SSL_Object *self, PyObject *args) +// SSL_set1_sigalgs() is only available in OpenSSL 1.1.1 +static PyObject* nassl_SSL_set1_sigalgs(nassl_SSL_Object *self, PyObject *args) { - char *sigalgList; - if (!PyArg_ParseTuple(args, "s", &sigalgList)) + int i = 0; + PyObject *pyListOfOpensslNids; + Py_ssize_t nidsCount = 0; + int *listOfNids; + + // Parse the Python list + if (!PyArg_ParseTuple(args, "O!", &PyList_Type, &pyListOfOpensslNids)) { return NULL; } - if (!SSL_set1_sigalgs_list(self->ssl, sigalgList)) + // Extract each NID int from the list + nidsCount = PyList_Size(pyListOfOpensslNids); + listOfNids = (int *) PyMem_Malloc(nidsCount * sizeof(int)); + if (listOfNids == NULL) + { + return PyErr_NoMemory(); + } + + for (i=0; issl, listOfNids, nidsCount) != 1) { + PyMem_Free(listOfNids); return raise_OpenSSL_error(); } + PyMem_Free(listOfNids); Py_RETURN_NONE; } @@ -1199,8 +1229,8 @@ static PyMethodDef nassl_SSL_Object_methods[] = {"set_ciphersuites", (PyCFunction)nassl_SSL_set_ciphersuites, METH_VARARGS, "OpenSSL's SSL_set_ciphersuites()." }, - {"set1_sigalgs_list", (PyCFunction)nassl_SSL_set1_sigalgs_list, METH_VARARGS, - "OpenSSL's SSL_set1_sigalgs_list()." + {"set1_sigalgs", (PyCFunction)nassl_SSL_set1_sigalgs, METH_VARARGS, + "OpenSSL's SSL_set1_sigalgs()." }, {"get0_verified_chain", (PyCFunction)nassl_SSL_get0_verified_chain, METH_NOARGS, "OpenSSL's SSL_get0_verified_chain(). Returns an array of _nassl.X509 objects." diff --git a/nassl/ephemeral_key_info.py b/nassl/ephemeral_key_info.py index 8d7fcfd..d33d1be 100644 --- a/nassl/ephemeral_key_info.py +++ b/nassl/ephemeral_key_info.py @@ -12,6 +12,9 @@ class OpenSslEvpPkeyEnum(IntEnum): EC = 408 X25519 = 1034 X448 = 1035 + RSA = 6 + DSA = 116 + RSA_PSS = 912 class OpenSslEcNidEnum(IntEnum): diff --git a/nassl/ssl_client.py b/nassl/ssl_client.py index 810a099..7d833f5 100755 --- a/nassl/ssl_client.py +++ b/nassl/ssl_client.py @@ -6,7 +6,7 @@ from nassl._nassl import WantReadError, OpenSSLError, WantX509LookupError from enum import IntEnum -from typing import List, Any +from typing import List, Any, Tuple from typing import Protocol @@ -32,6 +32,17 @@ class OpenSslVerifyEnum(IntEnum): CLIENT_ONCE = 4 +class OpenSslDigestNidEnum(IntEnum): + """SSL digest algorithms used for the signature algorithm, per obj_mac.h.""" + + MD5 = 4 + SHA1 = 64 + SHA224 = 675 + SHA256 = 672 + SHA384 = 673 + SHA512 = 674 + + class OpenSslVersionEnum(IntEnum): """SSL version constants.""" @@ -449,9 +460,10 @@ def set_ciphersuites(self, cipher_suites: str) -> None: # TODO(AD): Eventually merge this method with get/set_cipher_list() self._ssl.set_ciphersuites(cipher_suites) - def set_sigalgs(self, cipher_suites: str) -> None: - """Set the enabled signature algorithms, e.g. 'ECDSA+SHA256:RSA+SHA256'""" - self._ssl.set1_sigalgs_list(cipher_suites) + def set_sigalgs(self, sigalgs: List[Tuple[OpenSslDigestNidEnum, OpenSslEvpPkeyEnum]]) -> None: + """Set the enabled signature algorithms for the key exchange.""" + flattened_sigalgs = [item for sublist in sigalgs for item in sublist] + self._ssl.set1_sigalgs(flattened_sigalgs) def set_groups(self, supported_groups: List[OpenSslEcNidEnum]) -> None: """Specify elliptic curves or DH groups that are supported by the client in descending order.""" diff --git a/tests/ssl_client_test.py b/tests/ssl_client_test.py index 4cf9d6a..c2a7680 100644 --- a/tests/ssl_client_test.py +++ b/tests/ssl_client_test.py @@ -11,7 +11,7 @@ OpenSslVerifyEnum, SslClient, OpenSSLError, - OpenSslEarlyDataStatusEnum, + OpenSslEarlyDataStatusEnum, OpenSslDigestNidEnum, ) from nassl.ephemeral_key_info import ( OpenSslEvpPkeyEnum, @@ -424,6 +424,26 @@ def test_set_ciphersuites(self): # And client's cipher suite was used assert "TLS_CHACHA20_POLY1305_SHA256" == ssl_client.get_current_cipher_name() + def test_set_sigalgs(self): + with ModernOpenSslServer() as server: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(5) + sock.connect((server.hostname, server.port)) + + ssl_client = SslClient( + ssl_version=OpenSslVersionEnum.TLSV1_3, + underlying_socket=sock, + ssl_verify=OpenSslVerifyEnum.NONE, + ) + # These signature algorithms are unsupported + ssl_client.set_sigalgs([ + (OpenSslDigestNidEnum.SHA512, OpenSslEvpPkeyEnum.EC) + ]) + + with pytest.raises(OpenSSLError): + ssl_client.do_handshake() + ssl_client.shutdown() + @staticmethod def _create_tls_1_3_session(server_host: str, server_port: int) -> _nassl.SSL_SESSION: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) From 4a73fb59b2d8c1e789c664f73d0b8298dd73aec3 Mon Sep 17 00:00:00 2001 From: Sasha Romijn Date: Mon, 9 Dec 2024 20:30:37 +0100 Subject: [PATCH 3/3] Add get_peer_signature_nid to ssl_client --- nassl/_nassl/nassl_SSL.c | 15 +++++++++++++++ nassl/ssl_client.py | 4 ++++ tests/ssl_client_test.py | 2 ++ 3 files changed, 21 insertions(+) diff --git a/nassl/_nassl/nassl_SSL.c b/nassl/_nassl/nassl_SSL.c index 1b5e3d5..29127b5 100755 --- a/nassl/_nassl/nassl_SSL.c +++ b/nassl/_nassl/nassl_SSL.c @@ -861,6 +861,18 @@ static PyObject* nassl_SSL_set1_sigalgs(nassl_SSL_Object *self, PyObject *args) } +static PyObject* nassl_get_peer_signature_nid(nassl_SSL_Object *self) +{ + int psig_nid; + + if(SSL_get_peer_signature_nid(self->ssl, &psig_nid) != 1) + { + return raise_OpenSSL_error(); + } + return PyLong_FromUnsignedLong((long)psig_nid); +} + + static PyObject* nassl_SSL_get0_verified_chain(nassl_SSL_Object *self, PyObject *args) { STACK_OF(X509) *verifiedCertChain = NULL; @@ -1232,6 +1244,9 @@ static PyMethodDef nassl_SSL_Object_methods[] = {"set1_sigalgs", (PyCFunction)nassl_SSL_set1_sigalgs, METH_VARARGS, "OpenSSL's SSL_set1_sigalgs()." }, + {"get_peer_signature_nid", (PyCFunction)nassl_get_peer_signature_nid, METH_NOARGS, + "OpenSSL's get_peer_signature_nid(). Returns a digest NID" + }, {"get0_verified_chain", (PyCFunction)nassl_SSL_get0_verified_chain, METH_NOARGS, "OpenSSL's SSL_get0_verified_chain(). Returns an array of _nassl.X509 objects." }, diff --git a/nassl/ssl_client.py b/nassl/ssl_client.py index 7d833f5..58651f9 100755 --- a/nassl/ssl_client.py +++ b/nassl/ssl_client.py @@ -465,6 +465,10 @@ def set_sigalgs(self, sigalgs: List[Tuple[OpenSslDigestNidEnum, OpenSslEvpPkeyEn flattened_sigalgs = [item for sublist in sigalgs for item in sublist] self._ssl.set1_sigalgs(flattened_sigalgs) + def get_peer_signature_nid(self) -> OpenSslDigestNidEnum: + """Get the digest used for TLS message signing.""" + return OpenSslDigestNidEnum(self._ssl.get_peer_signature_nid()) + def set_groups(self, supported_groups: List[OpenSslEcNidEnum]) -> None: """Specify elliptic curves or DH groups that are supported by the client in descending order.""" self._ssl.set1_groups(supported_groups) diff --git a/tests/ssl_client_test.py b/tests/ssl_client_test.py index c2a7680..8d43d7a 100644 --- a/tests/ssl_client_test.py +++ b/tests/ssl_client_test.py @@ -217,6 +217,8 @@ def test_get_verified_chain(self): # And when requesting the verified certificate chain, it returns it assert ssl_client.get_verified_chain() + + assert ssl_client.get_peer_signature_nid() == OpenSslDigestNidEnum.SHA256 finally: ssl_client.shutdown()