Skip to content

Commit dd0135f

Browse files
Jean-Daniel DupasJean-Daniel
authored andcommitted
Add Context.set_sigalgs_list() and Connection.get_sigalgs()
This is based on SSL_CTX_set1_sigalgs(3). It let the client limits the set of signature algorithms that should be used by the server for certificate selection.
1 parent 963ae03 commit dd0135f

File tree

2 files changed

+116
-0
lines changed

2 files changed

+116
-0
lines changed

src/OpenSSL/SSL.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1207,6 +1207,28 @@ def set_cipher_list(self, cipher_list):
12071207
],
12081208
)
12091209

1210+
def set_sigalgs_list(self, sigalgs_list):
1211+
"""
1212+
Set the list of signature algorithms to be used in this context.
1213+
1214+
See the OpenSSL manual for more information (e.g.
1215+
:manpage:`SSL_CTX_set1_sigalgs_list(3)`).
1216+
1217+
:param bytes sigalgs_list: An OpenSSL signature algorithms list.
1218+
:return: None
1219+
"""
1220+
sigalgs_list = _text_to_bytes_and_warn("sigalgs_list", sigalgs_list)
1221+
1222+
if not isinstance(sigalgs_list, bytes):
1223+
raise TypeError("sigalgs_list must be a byte string.")
1224+
1225+
if not _lib.Cryptography_HAS_SIGALGS:
1226+
return
1227+
1228+
_openssl_assert(
1229+
_lib.SSL_CTX_set1_sigalgs_list(self._context, sigalgs_list) == 1
1230+
)
1231+
12101232
def set_client_ca_list(self, certificate_authorities):
12111233
"""
12121234
Set the list of preferred client certificate signers for this server
@@ -2019,6 +2041,28 @@ def get_cipher_list(self):
20192041
ciphers.append(_native(_ffi.string(result)))
20202042
return ciphers
20212043

2044+
def get_sigalgs(self):
2045+
"""
2046+
Retrieve list of signature algorithms used by the Connection object.
2047+
Must be used after handshake only.
2048+
See :manpage:`SSL_get_sigalgs(3)`.
2049+
2050+
:return: A list of SignatureScheme (int) as defined by RFC 8446.
2051+
"""
2052+
sigalgs = []
2053+
if not _lib.Cryptography_HAS_SIGALGS:
2054+
return sigalgs
2055+
2056+
rsign = _ffi.new("unsigned char *")
2057+
rhash = _ffi.new("unsigned char *")
2058+
for i in count():
2059+
result = _lib.SSL_get_sigalgs(self._ssl, i, _ffi.NULL, _ffi.NULL,
2060+
_ffi.NULL, rsign, rhash)
2061+
if result == 0:
2062+
break
2063+
sigalgs.append(rsign[0] + (rhash[0] << 8))
2064+
return sigalgs
2065+
20222066
def get_client_ca_list(self):
20232067
"""
20242068
Get CAs whose certificates are suggested for client authentication.

tests/test_ssl.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,78 @@ def test_set_cipher_list_no_cipher_match(self, context):
457457
],
458458
)
459459

460+
@pytest.mark.parametrize("sigalgs_list", [
461+
b"RSA+SHA256:RSA+SHA384",
462+
u"RSA+SHA256:RSA+SHA384",
463+
])
464+
def test_set_sigalgs_list(self, context, sigalgs_list):
465+
"""
466+
`Context.set_sigalgs_list` accepts both byte and unicode strings
467+
for naming the signature algorithms which connections created
468+
with the context object will send to the server.
469+
"""
470+
context.set_sigalgs_list(sigalgs_list)
471+
472+
def test_set_sigalgs_list_wrong_type(self, context):
473+
"""
474+
`Context.set_cipher_list` raises `TypeError` when passed a non-string
475+
argument.
476+
"""
477+
with pytest.raises(TypeError):
478+
context.set_sigalgs_list(object())
479+
480+
if _lib.Cryptography_HAS_SIGALGS:
481+
def test_set_sigalgs_list_invalid_name(self, context):
482+
"""
483+
`Context.set_cipher_list` raises `OpenSSL.SSL.Error` with a
484+
`"no cipher match"` reason string regardless of the TLS
485+
version.
486+
"""
487+
with pytest.raises(Error):
488+
context.set_sigalgs_list(b"imaginary-sigalg")
489+
490+
def test_set_sigalgs_list_not_supported(self):
491+
"""
492+
If no signature algorithms supported by the server are set,
493+
the handshake fails with a `"no suitable signature algorithm"`
494+
reason string, or 'no shared cipher' on older OpenSSL releases.
495+
"""
496+
497+
def make_client(socket):
498+
context = Context(TLSv1_2_METHOD)
499+
context.set_sigalgs_list(b"ECDSA+SHA256:ECDSA+SHA384")
500+
c = Connection(context, socket)
501+
c.set_connect_state()
502+
return c
503+
504+
with pytest.raises(Error):
505+
loopback(client_factory=make_client)
506+
507+
def test_get_sigalgs(self):
508+
"""
509+
`Connection.get_sigalgs` returns the signature algorithms send by
510+
the client to the server. This is supported only in TLS1_2 and later.
511+
"""
512+
def make_client(socket):
513+
context = Context(TLSv1_2_METHOD)
514+
context.set_sigalgs_list(b"RSA+SHA256:ECDSA+SHA384")
515+
c = Connection(context, socket)
516+
c.set_connect_state()
517+
return c
518+
519+
srv, client = loopback(
520+
server_factory=lambda s: loopback_server_factory(s,
521+
TLSv1_2_METHOD),
522+
client_factory=make_client)
523+
524+
sigalgs = srv.get_sigalgs()
525+
if _lib.Cryptography_HAS_SIGALGS:
526+
assert 0x0401 in sigalgs # rsa_pkcs1_sha256
527+
assert 0x0503 in sigalgs # ecdsa_secp384r1_sha384
528+
else:
529+
# gracefully degrades on older OpenSSL versions
530+
assert len(sigalgs) == 0
531+
460532
def test_load_client_ca(self, context, ca_file):
461533
"""
462534
`Context.load_client_ca` works as far as we can tell.

0 commit comments

Comments
 (0)