Skip to content

Commit 3181379

Browse files
author
Jean-Daniel Dupas
committed
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 bd02327 commit 3181379

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
@@ -1229,6 +1229,28 @@ def set_cipher_list(self, cipher_list):
12291229
],
12301230
)
12311231

1232+
def set_sigalgs_list(self, sigalgs_list):
1233+
"""
1234+
Set the list of signature algorithms to be used in this context.
1235+
1236+
See the OpenSSL manual for more information (e.g.
1237+
:manpage:`SSL_CTX_set1_sigalgs_list(3)`).
1238+
1239+
:param bytes sigalgs_list: An OpenSSL signature algorithms list.
1240+
:return: None
1241+
"""
1242+
sigalgs_list = _text_to_bytes_and_warn("sigalgs_list", sigalgs_list)
1243+
1244+
if not isinstance(sigalgs_list, bytes):
1245+
raise TypeError("sigalgs_list must be a byte string.")
1246+
1247+
if not _lib.Cryptography_HAS_SIGALGS:
1248+
return
1249+
1250+
_openssl_assert(
1251+
_lib.SSL_CTX_set1_sigalgs_list(self._context, sigalgs_list) == 1
1252+
)
1253+
12321254
def set_client_ca_list(self, certificate_authorities):
12331255
"""
12341256
Set the list of preferred client certificate signers for this server
@@ -2164,6 +2186,28 @@ def get_cipher_list(self):
21642186
ciphers.append(_ffi.string(result).decode("utf-8"))
21652187
return ciphers
21662188

2189+
def get_sigalgs(self):
2190+
"""
2191+
Retrieve list of signature algorithms used by the Connection object.
2192+
Must be used after handshake only.
2193+
See :manpage:`SSL_get_sigalgs(3)`.
2194+
2195+
:return: A list of SignatureScheme (int) as defined by RFC 8446.
2196+
"""
2197+
sigalgs = []
2198+
if not _lib.Cryptography_HAS_SIGALGS:
2199+
return sigalgs
2200+
2201+
rsign = _ffi.new("unsigned char *")
2202+
rhash = _ffi.new("unsigned char *")
2203+
for i in count():
2204+
result = _lib.SSL_get_sigalgs(self._ssl, i, _ffi.NULL, _ffi.NULL,
2205+
_ffi.NULL, rsign, rhash)
2206+
if result == 0:
2207+
break
2208+
sigalgs.append(rsign[0] + (rhash[0] << 8))
2209+
return sigalgs
2210+
21672211
def get_client_ca_list(self):
21682212
"""
21692213
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
@@ -532,6 +532,78 @@ def test_set_cipher_list_no_cipher_match(self, context):
532532
),
533533
]
534534

535+
@pytest.mark.parametrize("sigalgs_list", [
536+
b"RSA+SHA256:RSA+SHA384",
537+
u"RSA+SHA256:RSA+SHA384",
538+
])
539+
def test_set_sigalgs_list(self, context, sigalgs_list):
540+
"""
541+
`Context.set_sigalgs_list` accepts both byte and unicode strings
542+
for naming the signature algorithms which connections created
543+
with the context object will send to the server.
544+
"""
545+
context.set_sigalgs_list(sigalgs_list)
546+
547+
def test_set_sigalgs_list_wrong_type(self, context):
548+
"""
549+
`Context.set_cipher_list` raises `TypeError` when passed a non-string
550+
argument.
551+
"""
552+
with pytest.raises(TypeError):
553+
context.set_sigalgs_list(object())
554+
555+
if _lib.Cryptography_HAS_SIGALGS:
556+
def test_set_sigalgs_list_invalid_name(self, context):
557+
"""
558+
`Context.set_cipher_list` raises `OpenSSL.SSL.Error` with a
559+
`"no cipher match"` reason string regardless of the TLS
560+
version.
561+
"""
562+
with pytest.raises(Error):
563+
context.set_sigalgs_list(b"imaginary-sigalg")
564+
565+
def test_set_sigalgs_list_not_supported(self):
566+
"""
567+
If no signature algorithms supported by the server are set,
568+
the handshake fails with a `"no suitable signature algorithm"`
569+
reason string, or 'no shared cipher' on older OpenSSL releases.
570+
"""
571+
572+
def make_client(socket):
573+
context = Context(TLSv1_2_METHOD)
574+
context.set_sigalgs_list(b"ECDSA+SHA256:ECDSA+SHA384")
575+
c = Connection(context, socket)
576+
c.set_connect_state()
577+
return c
578+
579+
with pytest.raises(Error):
580+
loopback(client_factory=make_client)
581+
582+
def test_get_sigalgs(self):
583+
"""
584+
`Connection.get_sigalgs` returns the signature algorithms send by
585+
the client to the server. This is supported only in TLS1_2 and later.
586+
"""
587+
def make_client(socket):
588+
context = Context(TLSv1_2_METHOD)
589+
context.set_sigalgs_list(b"RSA+SHA256:ECDSA+SHA384")
590+
c = Connection(context, socket)
591+
c.set_connect_state()
592+
return c
593+
594+
srv, client = loopback(
595+
server_factory=lambda s: loopback_server_factory(s,
596+
TLSv1_2_METHOD),
597+
client_factory=make_client)
598+
599+
sigalgs = srv.get_sigalgs()
600+
if _lib.Cryptography_HAS_SIGALGS:
601+
assert 0x0401 in sigalgs # rsa_pkcs1_sha256
602+
assert 0x0503 in sigalgs # ecdsa_secp384r1_sha384
603+
else:
604+
# gracefully degrades on older OpenSSL versions
605+
assert len(sigalgs) == 0
606+
535607
def test_load_client_ca(self, context, ca_file):
536608
"""
537609
`Context.load_client_ca` works as far as we can tell.

0 commit comments

Comments
 (0)