-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtls_utils.py
More file actions
96 lines (80 loc) · 4.24 KB
/
tls_utils.py
File metadata and controls
96 lines (80 loc) · 4.24 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
"""
TLS (``ssl.SSLContext``) helpers for the educational client/server.
Why this module exists
----------------------
Plain TCP sends everything in the clear. For a labbing environment on
loopback that is fine, but the moment packets leave the host, anyone on the
same network can read the commands, the shell output, and the screenshots.
``ssl`` is how you fix that in stdlib Python without pulling in a third
party library.
This module provides two tiny helpers:
- ``make_server_context(cert, key)`` builds an ``SSLContext`` configured for
a TLS 1.2+ server with a self-signed certificate.
- ``make_client_context(cafile)`` builds an ``SSLContext`` configured for a
TLS 1.2+ client that pins the known CA (which in our lab is the same
self-signed cert the server uses).
Educational notes
-----------------
- ``PROTOCOL_TLS_SERVER`` / ``PROTOCOL_TLS_CLIENT`` are the recommended
entry points as of Python 3.10+. The older ``wrap_socket`` module-level
function was deprecated in Python 3.7 and **removed entirely in
Python 3.12** (which is what this project targets). It also lacked SNI
support and picked weak cipher defaults.
- ``minimum_version = ssl.TLSVersion.TLSv1_2`` refuses the legacy TLS 1.0
and 1.1 versions that are broken against modern attackers.
- ``check_hostname = False`` is set on the client context because the
self-signed cert produced by ``scripts/gen_selfsigned_cert.py`` cannot
pass hostname verification for two reasons:
1. **SAN requirement**: Python 3.7+ requires the server certificate to
have a Subject Alternative Name (SAN) extension for hostname matching,
not just a Common Name (CN). A cert with only ``CN=localhost`` and no
SAN will be rejected even when connecting to ``localhost``.
2. **IP vs. name mismatch**: connecting via an IP address (e.g.
``127.0.0.1``) would not match a CN/SAN of ``localhost`` anyway.
The ``gen_selfsigned_cert.py`` script *does* add SAN entries for
``localhost``, ``127.0.0.1``, and ``::1``, which is why the TLS
roundtrip test in ``test_tls_utils.py`` passes. We still disable
``check_hostname`` so that students who generate certs by other means
(e.g. a plain ``openssl req`` without a SAN config) are not immediately
blocked. A production deployment should re-enable hostname verification
and always include the correct SAN in the certificate.
- ``verify_mode = ssl.CERT_REQUIRED`` means the client MUST validate the
server's certificate against the pinned CA file. If the server presents
a different cert the connection is refused.
If you want to take this further in your own repo, the next steps are:
- Mutual TLS: add ``context.load_cert_chain`` on the client side and
``context.verify_mode = ssl.CERT_REQUIRED`` on the server side.
- ECDSA certificates (faster than RSA): pass ``-keytype ecdsa`` to the
cert generator.
- TLS 1.3 only: ``context.maximum_version = ssl.TLSVersion.TLSv1_3`` and
``minimum_version`` the same. Hides the downgrade surface completely.
"""
from __future__ import annotations
import ssl
from pathlib import Path
def make_server_context(cert_path: Path, key_path: Path) -> ssl.SSLContext:
"""
Build an ``SSLContext`` for the server side of the lab.
The server loads its own cert chain from ``cert_path`` + ``key_path``.
No client certificate is required in this baseline; a student who
wants to add mutual TLS only needs to flip ``verify_mode`` and set
``load_verify_locations`` with the client CA.
"""
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
context.minimum_version = ssl.TLSVersion.TLSv1_2
context.load_cert_chain(certfile=str(cert_path), keyfile=str(key_path))
return context
def make_client_context(cafile: Path) -> ssl.SSLContext:
"""
Build an ``SSLContext`` for the client side of the lab.
Pins the server's certificate (or a CA that signs it) via
``load_verify_locations``. Refuses any TLS version older than 1.2.
Hostname verification is disabled. See the module docstring for the
rationale and the upgrade path.
"""
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
context.minimum_version = ssl.TLSVersion.TLSv1_2
context.check_hostname = False
context.verify_mode = ssl.CERT_REQUIRED
context.load_verify_locations(cafile=str(cafile))
return context