Skip to content

Commit 79f9d24

Browse files
committed
gh-63284: Add support for TLS-PSK (pre-shared key) to the ssl module
1 parent dcd6f22 commit 79f9d24

File tree

10 files changed

+481
-1
lines changed

10 files changed

+481
-1
lines changed

Doc/library/ssl.rst

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1987,6 +1987,80 @@ to speed up repeated connections from the same clients.
19871987
>>> ssl.create_default_context().verify_mode # doctest: +SKIP
19881988
<VerifyMode.CERT_REQUIRED: 2>
19891989

1990+
.. method:: SSLContext.set_psk_client_callback(callback)
1991+
1992+
Enables TLS-PSK (pre-shared key) authentication on a client-side connection.
1993+
1994+
In general, certificate based authentication should be preferred over this method.
1995+
1996+
The parameter ``callback`` is a callable object with the signature:
1997+
``def callback(hint: str | None) -> tuple[str | None, bytes]``.
1998+
The ``hint`` parameter is an optional identity hint sent by the server.
1999+
The return value is a tuple in the form (client-identity, psk).
2000+
Client-identity is an optional string which may be used by the server to
2001+
select a corresponding PSK for the client. PSK is a
2002+
:term:`bytes-like object` representing the pre-shared key.
2003+
2004+
Setting ``callback`` to :const:`None` removes any existing callback.
2005+
2006+
Example usage::
2007+
2008+
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
2009+
context.check_hostname = False
2010+
context.verify_mode = ssl.CERT_NONE
2011+
context.maximum_version = ssl.TLSVersion.TLSv1_2
2012+
context.set_ciphers('PSK')
2013+
2014+
# A simple lambda:
2015+
psk = bytes.fromhex('deadbeef')
2016+
context.set_psk_client_callback(lambda hint: (None, psk))
2017+
2018+
# A table using the hint from the server:
2019+
psk_table = { 'ServerId_1': bytes.fromhex('deadbeef'),
2020+
'ServerId_2': bytes.fromhex('cafebabe')
2021+
}
2022+
def callback(hint):
2023+
return 'ClientId_1', psk_table[hint]
2024+
context.set_psk_client_callback(callback)
2025+
2026+
.. versionadded:: 3.12
2027+
2028+
.. method:: SSLContext.set_psk_server_callback(callback, identity_hint=None)
2029+
2030+
Enables TLS-PSK (pre-shared key) authentication on a server-side connection.
2031+
2032+
In general, certificate based authentication should be preferred over this method.
2033+
2034+
The parameter ``callback`` is a callable object with the signature:
2035+
``def callback(identity: str | None) -> bytes``.
2036+
The ``identity`` parameter is an optional identity sent by the client which can
2037+
be used to select a corresponding PSK.
2038+
The return value is a :term:`bytes-like object` representing the pre-shared key.
2039+
2040+
Setting ``callback`` to :const:`None` removes any existing callback.
2041+
2042+
The parameter ``identity_hint`` is an optional identity hint sent to the client.
2043+
2044+
Example usage::
2045+
2046+
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
2047+
context.maximum_version = ssl.TLSVersion.TLSv1_2
2048+
context.set_ciphers('PSK')
2049+
2050+
# A simple lambda:
2051+
psk = bytes.fromhex('deadbeef')
2052+
context.set_psk_server_callback(lambda identity: psk)
2053+
2054+
# A table using the identity of the client:
2055+
psk_table = { 'ClientId_1': bytes.fromhex('deadbeef'),
2056+
'ClientId_2': bytes.fromhex('cafed00d')
2057+
}
2058+
def callback(identity):
2059+
return psk_table[identity]
2060+
context.set_psk_server_callback(callback, 'ServerId_1')
2061+
2062+
.. versionadded:: 3.12
2063+
19902064
.. index:: single: certificates
19912065

19922066
.. index:: single: X509 certificate

Include/internal/pycore_global_objects_fini_generated.h

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/internal/pycore_global_strings.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,7 @@ struct _Py_global_strings {
301301
STRUCT_FOR_ID(call)
302302
STRUCT_FOR_ID(call_exception_handler)
303303
STRUCT_FOR_ID(call_soon)
304+
STRUCT_FOR_ID(callback)
304305
STRUCT_FOR_ID(cancel)
305306
STRUCT_FOR_ID(capath)
306307
STRUCT_FOR_ID(category)
@@ -440,6 +441,7 @@ struct _Py_global_strings {
440441
STRUCT_FOR_ID(hook)
441442
STRUCT_FOR_ID(id)
442443
STRUCT_FOR_ID(ident)
444+
STRUCT_FOR_ID(identity_hint)
443445
STRUCT_FOR_ID(ignore)
444446
STRUCT_FOR_ID(imag)
445447
STRUCT_FOR_ID(importlib)

Include/internal/pycore_runtime_init_generated.h

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/internal/pycore_unicodeobject_generated.h

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Lib/test/test_ssl.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4203,6 +4203,61 @@ def test_session_handling(self):
42034203
self.assertEqual(str(e.exception),
42044204
'Session refers to a different SSLContext.')
42054205

4206+
def test_psk(self):
4207+
psk = bytes.fromhex('deadbeef')
4208+
4209+
client_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
4210+
client_context.check_hostname = False
4211+
client_context.verify_mode = ssl.CERT_NONE
4212+
client_context.maximum_version = ssl.TLSVersion.TLSv1_2
4213+
client_context.set_ciphers('PSK')
4214+
client_context.set_psk_client_callback(lambda hint: (None, psk))
4215+
4216+
server_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
4217+
server_context.maximum_version = ssl.TLSVersion.TLSv1_2
4218+
server_context.set_ciphers('PSK')
4219+
server_context.set_psk_server_callback(lambda identity: psk)
4220+
4221+
# correct PSK should connect
4222+
server = ThreadedEchoServer(context=server_context)
4223+
with server:
4224+
with client_context.wrap_socket(socket.socket()) as s:
4225+
s.connect((HOST, server.port))
4226+
4227+
# incorrect PSK should fail
4228+
incorrect_psk = bytes.fromhex('cafebabe')
4229+
client_context.set_psk_client_callback(lambda hint: (None, incorrect_psk))
4230+
server = ThreadedEchoServer(context=server_context)
4231+
with server:
4232+
with client_context.wrap_socket(socket.socket()) as s:
4233+
with self.assertRaises(ssl.SSLError):
4234+
s.connect((HOST, server.port))
4235+
4236+
# identity_hint and client_identity should be sent to the other side
4237+
identity_hint = 'identity-hint'
4238+
client_identity = 'client-identity'
4239+
4240+
def client_callback(hint):
4241+
self.assertEqual(hint, identity_hint)
4242+
return client_identity, psk
4243+
4244+
def server_callback(identity):
4245+
self.assertEqual(identity, client_identity)
4246+
return psk
4247+
4248+
client_context.set_psk_client_callback(client_callback)
4249+
server_context.set_psk_server_callback(server_callback, identity_hint)
4250+
server = ThreadedEchoServer(context=server_context)
4251+
with server:
4252+
with client_context.wrap_socket(socket.socket()) as s:
4253+
s.connect((HOST, server.port))
4254+
4255+
# adding client callback to server or vice versa raises an exception
4256+
with self.assertRaisesRegex(ssl.SSLError, 'Cannot add PSK server callback'):
4257+
client_context.set_psk_server_callback(server_callback, identity_hint)
4258+
with self.assertRaisesRegex(ssl.SSLError, 'Cannot add PSK client callback'):
4259+
server_context.set_psk_client_callback(client_callback)
4260+
42064261

42074262
@unittest.skipUnless(has_tls_version('TLSv1_3'), "Test needs TLS 1.3")
42084263
class TestPostHandshakeAuth(unittest.TestCase):

Misc/ACKS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1466,6 +1466,7 @@ Ajith Ramachandran
14661466
Dhushyanth Ramasamy
14671467
Ashwin Ramaswami
14681468
Jeff Ramnani
1469+
Grant Ramsay
14691470
Bayard Randel
14701471
Varpu Rantala
14711472
Brodie Rao
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Added support for TLS-PSK (pre-shared key) to the ssl module

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy