Skip to content

Commit e889343

Browse files
committed
Add support for converting sessions to / from ASN1 representation
1 parent 43a2e36 commit e889343

File tree

5 files changed

+160
-5
lines changed

5 files changed

+160
-5
lines changed

CHANGELOG.rst

+15
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,21 @@ Changelog
44
Versions are year-based with a strict backward-compatibility policy.
55
The third digit is only for regressions.
66

7+
24.4.0 (UNRELEASED)
8+
-------------------
9+
10+
Backward-incompatible changes:
11+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
12+
13+
Deprecations:
14+
^^^^^^^^^^^^^
15+
16+
Changes:
17+
^^^^^^^^
18+
19+
* Added ``OpenSSL.SSL.Session.i2d`` to convert session objects to ASN1. Updated ``OpenSSL.SSL.Session`` constructor to support conversion from ASN1. `#1373 <https://github.com/pyca/pyopenssl/pull/1373>`_.
20+
* ``cryptography`` minimum version is now 44.0.x.
21+
722
24.3.0 (2024-11-27)
823
-------------------
924

doc/api/ssl.rst

+4-1
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ Context, Connection.
154154
:noindex:
155155

156156
.. autoclass:: Session
157+
:noindex:
157158

158159

159160
.. py:class:: Connection(context, socket)
@@ -247,8 +248,10 @@ Context objects have the following methods:
247248
Session objects
248249
---------------
249250

250-
Session objects have no methods.
251+
Session objects have the following methods:
251252

253+
.. autoclass:: OpenSSL.SSL.Session
254+
:members:
252255

253256
.. _openssl-connection:
254257

src/OpenSSL/SSL.py

+42-1
Original file line numberDiff line numberDiff line change
@@ -821,10 +821,51 @@ class Session:
821821
parameters which may be re-used to speed up the setup of subsequent
822822
connections.
823823
824+
:param data: An optional bytes object of an ASN1 encoded Session.
825+
824826
.. versionadded:: 0.14
825827
"""
826828

827-
_session: Any
829+
_session: Any = None
830+
831+
def __init__(self, data: bytes | None = None) -> None:
832+
if data is None:
833+
return
834+
835+
p = _ffi.new("unsigned char[]", data)
836+
pp = _ffi.new("unsigned char **")
837+
pp[0] = p
838+
length = _ffi.cast("long", len(data))
839+
840+
session = _lib.d2i_SSL_SESSION(_ffi.NULL, pp, length)
841+
if session == _ffi.NULL:
842+
_raise_current_error()
843+
844+
self._session = _ffi.gc(session, _lib.SSL_SESSION_free)
845+
846+
def i2d(self) -> bytes:
847+
"""
848+
Convert the Session object to an ASN1 encoded bytes object.
849+
850+
:return A bytes object representing the ASN1 encoded session.
851+
"""
852+
853+
if self._session is None:
854+
raise ValueError("Not a valid session")
855+
856+
length = _lib.i2d_SSL_SESSION(self._session, _ffi.NULL)
857+
if length == 0:
858+
raise ValueError("Not a valid session")
859+
860+
pp = _ffi.new("unsigned char **")
861+
p = _ffi.new("unsigned char[]", length)
862+
pp[0] = p
863+
864+
length = _lib.i2d_SSL_SESSION(self._session, pp)
865+
if length == 0:
866+
raise ValueError("Not a valid session")
867+
868+
return _ffi.buffer(p, length)[:]
828869

829870

830871
class Context:

tests/test_ssl.py

+98-2
Original file line numberDiff line numberDiff line change
@@ -321,17 +321,21 @@ def _create_certificate_chain():
321321
]
322322

323323

324-
def loopback_client_factory(socket, version=SSLv23_METHOD):
324+
def loopback_client_factory(socket, version=SSLv23_METHOD, session_data=None):
325325
client = Connection(Context(version), socket)
326+
if session_data is not None:
327+
client.set_session(Session(session_data))
326328
client.set_connect_state()
327329
return client
328330

329331

330-
def loopback_server_factory(socket, version=SSLv23_METHOD):
332+
def loopback_server_factory(socket, version=SSLv23_METHOD, session_data=None):
331333
ctx = Context(version)
332334
ctx.use_privatekey(load_privatekey(FILETYPE_PEM, server_key_pem))
333335
ctx.use_certificate(load_certificate(FILETYPE_PEM, server_cert_pem))
334336
server = Connection(ctx, socket)
337+
if session_data is not None:
338+
server.set_session(Session(session_data))
335339
server.set_accept_state()
336340
return server
337341

@@ -2176,6 +2180,98 @@ def test_construction(self):
21762180
new_session = Session()
21772181
assert isinstance(new_session, Session)
21782182

2183+
def test_d2i_fail(self):
2184+
with pytest.raises(Error) as e:
2185+
Session(b"abc" * 1000)
2186+
2187+
assert e.value.args[0][0] in [
2188+
# 1.1.x
2189+
(
2190+
"asn1 encoding routines",
2191+
"asn1_check_tlen",
2192+
"wrong tag",
2193+
),
2194+
# 3.0.x
2195+
(
2196+
"asn1 encoding routines",
2197+
"",
2198+
"wrong tag",
2199+
),
2200+
]
2201+
2202+
assert e.value.args[0][1] in [
2203+
# 1.1.x
2204+
(
2205+
"asn1 encoding routines",
2206+
"asn1_item_embed_d2i",
2207+
"nested asn1 error",
2208+
),
2209+
# 3.0.x
2210+
(
2211+
"asn1 encoding routines",
2212+
"",
2213+
"nested asn1 error",
2214+
),
2215+
]
2216+
2217+
def test_session_success(self):
2218+
session_id = (
2219+
b"\x51\x6d\x1d\x18\xc3\xb5\x86\x81\xc6\x79\x89\x2c\x89\x3e\x56\x33"
2220+
b"\xa7\x9c\xcd\x9b\x87\xbb\xb3\xdc\xf6\x76\x70\xf9\xc0\xdd\xf4\xef"
2221+
)
2222+
2223+
master_key = (
2224+
b"\x0f\xb2\x51\xe3\x15\x60\x2d\xef\x6e\x6d\xd2\x94\x2d\xe5\x37\x96"
2225+
b"\x72\xfa\xce\xb0\x39\xcc\x8d\xdf\xab\x32\xcc\x75\x0c\x66\xf9\xfd"
2226+
b"\xef\xbc\xc6\x2a\x8f\x9c\x35\x16\xfd\x4d\x38\xd9\xf9\xeb\x1d\xe4"
2227+
)
2228+
2229+
session_data = (
2230+
# sequence length=0x71
2231+
b"\x30\x71"
2232+
# integer (version)
2233+
b"\x02\x01\x01"
2234+
# integer (SSL version)
2235+
b"\x02\x02\x03\x03"
2236+
# octet-string (cipher suite)
2237+
b"\x04\x02\xc0\x30"
2238+
# octet-string length=0x20 (session id)
2239+
b"\x04\x20"
2240+
+ session_id
2241+
+
2242+
# octet-string length=0x30 (master secret)
2243+
b"\x04\x30"
2244+
+ master_key
2245+
+
2246+
# application (1), integer (time)
2247+
b"\xa1\x06\x02\x04"
2248+
+ b"\x66\xec\x4c\x2d"
2249+
+
2250+
# application (2), integer (timeout)
2251+
b"\xa2\x04\x02\x02"
2252+
+ b"\x02\x58"
2253+
+
2254+
# application (4), octet-string (session id context)
2255+
b"\xa4\x02\x04"
2256+
+ b"\x00"
2257+
)
2258+
serverSocket, clientSocket = socket_pair()
2259+
2260+
client = loopback_client_factory(
2261+
clientSocket, session_data=session_data
2262+
)
2263+
server = loopback_server_factory(
2264+
serverSocket, session_data=session_data
2265+
)
2266+
2267+
assert client.master_key() == master_key
2268+
assert server.master_key() == master_key
2269+
2270+
handshake(client, server)
2271+
2272+
client.send(b"hello world")
2273+
assert b"hello world" == server.recv(len(b"hello world"))
2274+
21792275

21802276
@pytest.fixture(params=["context", "connection"])
21812277
def ctx_or_conn(request) -> Union[Context, Connection]:

tox.ini

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ extras =
1818
test
1919
deps =
2020
coverage>=4.2
21-
cryptographyMinimum: cryptography==41.0.5
21+
cryptographyMinimum: cryptography==44.0.0
2222
randomorder: pytest-randomly
2323
setenv =
2424
# Do not allow the executing environment to pollute the test environment

0 commit comments

Comments
 (0)