Skip to content

Commit 14dfe18

Browse files
authored
Merge pull request #124 from nabla-c0d3/pr-118
Add functions to get/set signature algorithms
2 parents ba0d542 + aa7466f commit 14dfe18

File tree

4 files changed

+155
-1
lines changed

4 files changed

+155
-1
lines changed

nassl/_nassl/nassl_SSL.c

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -819,6 +819,66 @@ static PyObject* nassl_SSL_set_ciphersuites(nassl_SSL_Object *self, PyObject *ar
819819
}
820820

821821

822+
// SSL_set1_sigalgs() is only available in OpenSSL 1.1.1
823+
static PyObject* nassl_SSL_set1_sigalgs(nassl_SSL_Object *self, PyObject *args)
824+
{
825+
int i = 0;
826+
PyObject *pyListOfOpensslNids;
827+
Py_ssize_t nidsCount = 0;
828+
int *listOfNids;
829+
830+
// Parse the Python list
831+
if (!PyArg_ParseTuple(args, "O!", &PyList_Type, &pyListOfOpensslNids))
832+
{
833+
return NULL;
834+
}
835+
836+
// Extract each NID int from the list
837+
nidsCount = PyList_Size(pyListOfOpensslNids);
838+
listOfNids = (int *) PyMem_Malloc(nidsCount * sizeof(int));
839+
if (listOfNids == NULL)
840+
{
841+
return PyErr_NoMemory();
842+
}
843+
844+
for (i=0; i<nidsCount; i++)
845+
{
846+
PyObject *pyNid;
847+
int nid;
848+
849+
pyNid = PyList_GetItem(pyListOfOpensslNids, i);
850+
if ((pyNid == NULL) || (!PyLong_Check(pyNid)))
851+
{
852+
PyMem_Free(listOfNids);
853+
return NULL;
854+
}
855+
nid = PyLong_AsSize_t(pyNid);
856+
listOfNids[i] = nid;
857+
}
858+
859+
if (SSL_set1_sigalgs(self->ssl, listOfNids, nidsCount) != 1)
860+
{
861+
PyMem_Free(listOfNids);
862+
return raise_OpenSSL_error();
863+
}
864+
865+
PyMem_Free(listOfNids);
866+
Py_RETURN_NONE;
867+
}
868+
869+
870+
static PyObject* nassl_get_peer_signature_nid(nassl_SSL_Object *self)
871+
{
872+
int psig_nid;
873+
874+
if(SSL_get_peer_signature_nid(self->ssl, &psig_nid) != 1)
875+
{
876+
return raise_OpenSSL_error();
877+
}
878+
return PyLong_FromUnsignedLong((long)psig_nid);
879+
}
880+
881+
822882
static PyObject* nassl_SSL_get0_verified_chain(nassl_SSL_Object *self, PyObject *args)
823883
{
824884
STACK_OF(X509) *verifiedCertChain = NULL;
@@ -1187,6 +1247,12 @@ static PyMethodDef nassl_SSL_Object_methods[] =
11871247
{"set_ciphersuites", (PyCFunction)nassl_SSL_set_ciphersuites, METH_VARARGS,
11881248
"OpenSSL's SSL_set_ciphersuites()."
11891249
},
1250+
{"set1_sigalgs", (PyCFunction)nassl_SSL_set1_sigalgs, METH_VARARGS,
1251+
"OpenSSL's SSL_set1_sigalgs()."
1252+
},
1253+
{"get_peer_signature_nid", (PyCFunction)nassl_get_peer_signature_nid, METH_NOARGS,
1254+
"OpenSSL's get_peer_signature_nid(). Returns a digest NID"
1255+
},
11901256
{"get0_verified_chain", (PyCFunction)nassl_SSL_get0_verified_chain, METH_NOARGS,
11911257
"OpenSSL's SSL_get0_verified_chain(). Returns an array of _nassl.X509 objects."
11921258
},

nassl/ephemeral_key_info.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ class OpenSslEvpPkeyEnum(IntEnum):
1212
EC = 408
1313
X25519 = 1034
1414
X448 = 1035
15+
RSA = 6
16+
DSA = 116
17+
RSA_PSS = 912
1518

1619

1720
class OpenSslEcNidEnum(IntEnum):
@@ -84,6 +87,9 @@ def get_supported_by_ssl_client(cls) -> List["OpenSslEcNidEnum"]:
8487
OpenSslEvpPkeyEnum.EC: "ECDH",
8588
OpenSslEvpPkeyEnum.X25519: "ECDH",
8689
OpenSslEvpPkeyEnum.X448: "ECDH",
90+
OpenSslEvpPkeyEnum.RSA: "RSA",
91+
OpenSslEvpPkeyEnum.DSA: "DSA",
92+
OpenSslEvpPkeyEnum.RSA_PSS: "RSA-PSS",
8793
}
8894

8995

nassl/ssl_client.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from nassl._nassl import WantReadError, OpenSSLError, WantX509LookupError
77

88
from enum import IntEnum
9-
from typing import List, Any
9+
from typing import List, Any, Tuple
1010

1111
from typing import Protocol
1212

@@ -32,6 +32,17 @@ class OpenSslVerifyEnum(IntEnum):
3232
CLIENT_ONCE = 4
3333

3434

35+
class OpenSslDigestNidEnum(IntEnum):
36+
"""SSL digest algorithms used for the signature algorithm, per obj_mac.h."""
37+
38+
MD5 = 4
39+
SHA1 = 64
40+
SHA224 = 675
41+
SHA256 = 672
42+
SHA384 = 673
43+
SHA512 = 674
44+
45+
3546
class OpenSslVersionEnum(IntEnum):
3647
"""SSL version constants."""
3748

@@ -455,6 +466,17 @@ def set_ciphersuites(self, cipher_suites: str) -> None:
455466
# TODO(AD): Eventually merge this method with get/set_cipher_list()
456467
self._ssl.set_ciphersuites(cipher_suites)
457468

469+
def set_signature_algorithms(self, algorithms: List[Tuple[OpenSslDigestNidEnum, OpenSslEvpPkeyEnum]]) -> None:
470+
"""Set the enabled signature algorithms for the key exchange.
471+
472+
The algorithms parameter is a list of a public key algorithm and a digest."""
473+
flattened_sigalgs = [item for sublist in algorithms for item in sublist]
474+
self._ssl.set1_sigalgs(flattened_sigalgs)
475+
476+
def get_peer_signature_nid(self) -> OpenSslDigestNidEnum:
477+
"""Get the digest used for TLS message signing."""
478+
return OpenSslDigestNidEnum(self._ssl.get_peer_signature_nid())
479+
458480
def set_groups(self, supported_groups: List[OpenSslEcNidEnum]) -> None:
459481
"""Specify elliptic curves or DH groups that are supported by the client in descending order."""
460482
self._ssl.set1_groups(supported_groups)

tests/ssl_client_test.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
SslClient,
1414
OpenSSLError,
1515
OpenSslEarlyDataStatusEnum,
16+
OpenSslDigestNidEnum,
1617
)
1718
from nassl.ephemeral_key_info import (
1819
OpenSslEvpPkeyEnum,
@@ -218,6 +219,7 @@ def test_get_verified_chain(self) -> None:
218219

219220
# And when requesting the verified certificate chain, it returns it
220221
assert ssl_client.get_verified_chain()
222+
221223
finally:
222224
ssl_client.shutdown()
223225

@@ -361,16 +363,20 @@ def test_set_groups_curve_x448(self) -> None:
361363
assert len(dh_info.public_bytes) == 56
362364

363365
def test_get_extended_master_secret_not_used(self) -> None:
366+
# Given a TLS server that does NOT support the Extended Master Secret extension
364367
with LegacyOpenSslServer() as server:
365368
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
366369
sock.settimeout(5)
367370
sock.connect((server.hostname, server.port))
368371

372+
# When a client connects to it
369373
ssl_client = SslClient(
370374
ssl_version=OpenSslVersionEnum.TLSV1_2,
371375
underlying_socket=sock,
372376
ssl_verify=OpenSslVerifyEnum.NONE,
373377
)
378+
379+
# Then, before the handshake, the client cannot tell if Extended Master Secret was used
374380
exms_support_before_handshake = ssl_client.get_extended_master_secret_support()
375381
assert exms_support_before_handshake == ExtendedMasterSecretSupportEnum.UNKNOWN
376382

@@ -379,29 +385,83 @@ def test_get_extended_master_secret_not_used(self) -> None:
379385
finally:
380386
ssl_client.shutdown()
381387

388+
# And after the handshake, the client can tell that Extended Master Secret was NOT used
382389
exms_support = ssl_client.get_extended_master_secret_support()
383390
assert exms_support == ExtendedMasterSecretSupportEnum.NOT_USED_IN_CURRENT_SESSION
384391

385392
def test_get_extended_master_secret_used(self) -> None:
393+
# Given a TLS server that DOES support the Extended Master Secret extension
386394
with ModernOpenSslServer() as server:
387395
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
388396
sock.settimeout(5)
389397
sock.connect((server.hostname, server.port))
390398

399+
# When a client connects to it
391400
ssl_client = SslClient(
392401
ssl_version=OpenSslVersionEnum.TLSV1_2,
393402
underlying_socket=sock,
394403
ssl_verify=OpenSslVerifyEnum.NONE,
395404
)
396405

406+
# Then, before the handshake, the client cannot tell if Extended Master Secret was used
407+
exms_support_before_handshake = ssl_client.get_extended_master_secret_support()
408+
assert exms_support_before_handshake == ExtendedMasterSecretSupportEnum.UNKNOWN
409+
397410
try:
398411
ssl_client.do_handshake()
399412
finally:
400413
ssl_client.shutdown()
401414

415+
# And after the handshake, the client can tell that Extended Master Secret was used
402416
exms_support = ssl_client.get_extended_master_secret_support()
403417
assert exms_support == ExtendedMasterSecretSupportEnum.USED_IN_CURRENT_SESSION
404418

419+
def test_set_signature_algorithms(self) -> None:
420+
# Given a TLS server
421+
with ModernOpenSslServer() as server:
422+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
423+
sock.settimeout(5)
424+
sock.connect((server.hostname, server.port))
425+
426+
# And a client
427+
ssl_client = SslClient(
428+
ssl_version=OpenSslVersionEnum.TLSV1_2,
429+
underlying_socket=sock,
430+
ssl_verify=OpenSslVerifyEnum.NONE,
431+
)
432+
# That's configured to use a specific signature algorithm
433+
ssl_client.set_signature_algorithms([(OpenSslDigestNidEnum.SHA256, OpenSslEvpPkeyEnum.RSA)])
434+
435+
# When the client connects to the server, it succeeds
436+
try:
437+
ssl_client.do_handshake()
438+
finally:
439+
ssl_client.shutdown()
440+
441+
# And the configured signature algorithm was used
442+
assert ssl_client.get_peer_signature_nid() == OpenSslDigestNidEnum.SHA256
443+
444+
def test_set_signature_algorithms_but_not_supported(self) -> None:
445+
# Given a TLS server
446+
with ModernOpenSslServer() as server:
447+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
448+
sock.settimeout(5)
449+
sock.connect((server.hostname, server.port))
450+
451+
# And a client
452+
ssl_client = SslClient(
453+
ssl_version=OpenSslVersionEnum.TLSV1_3,
454+
underlying_socket=sock,
455+
ssl_verify=OpenSslVerifyEnum.NONE,
456+
)
457+
# That's configured to use signature algorithms that are NOT supported
458+
ssl_client.set_signature_algorithms([(OpenSslDigestNidEnum.SHA512, OpenSslEvpPkeyEnum.EC)])
459+
460+
# Then, when the client connects to the server, the handshake fails
461+
with pytest.raises(OpenSSLError, match="handshake failure"):
462+
ssl_client.do_handshake()
463+
ssl_client.shutdown()
464+
405465

406466
class TestLegacySslClientOnline:
407467
def test_ssl_2(self) -> None:

0 commit comments

Comments
 (0)