Commit 2a48641
committed
fix(smtp): the EMAIL and DIRECT TLS hops were encrypted but unauthenticated (#323, layers 1-2)
smtplib takes no context by default and falls back to ssl._create_stdlib_context, which IS
ssl._create_unverified_context -- measured on this project's required interpreter (CPython
3.14.6): verify_mode=CERT_NONE, check_hostname=False. So use_tls=true bought encryption
without authentication on every SMTP send, and any certificate was accepted.
That is worse than a plain gap because three shipped controls asserted the opposite:
* transports/email.py registered a RevocationHopGuard on the hop, whose own definition in
tls_policy.py says "the caller has already built a verifying context". An enforcing
production-PHI instance therefore REFUSED TO START over a possibly-REVOKED certificate,
on a hop that never validated a certificate at all.
* the same file's comment claimed STARTTLS/SMTP_SSL "verifies the server cert".
* the AUTH refusal keyed only on use_tls=false, so with TLS "on" the password went over
the unauthenticated hop.
WHAT LANDS (2 of the 3 cells):
config/tls_policy.py build_smtp_tls_context() -- the shared verifying-context factory,
mirroring remotefile.py's _ftps_ssl_context step for step (TLS 1.2 floor, harden_kex_groups,
harden_cipher_suites, harden_verify_flags on the verify path). It lives in config/ rather
than transports/ because pipeline/alert_sinks.py is the third caller and a transport must
not import pipeline/ (ADR 0029's one-way rule).
transports/email.py, transports/direct.py a three-arm branch (cleartext / verify-off /
verifying) and context= on both smtplib arms. The verify-off arm refuses unless the
CLAMPED weakened_tls_escape_permitted_here() allows it, and refuses AUTH outright.
config/wiring.py tls_verify / tls_ca_file / tls_check_hostname on Email() and Direct().
Trust config, not verification-off, is the escape: [tls].internal_ca_file is ALREADY threaded
onto every Destination and was simply never read here, so an estate that pinned its internal CA
for MLLP/FTPS needs no change at all.
SEPARABLE FIX, called out rather than folded in silently: direct.py's cleartext arm read the
UNCLAMPED insecure_tls_allowed() while its sibling one branch away read the clamped form. It now
reads the clamped one -- strictly ADDS refusals (ADR 0092 decision 5). Partially closes #329.
VERIFICATION -- the part that matters. The pre-existing tests asserted "STARTTLS was issued",
which was true the whole time it was insecure; that assertion could never have caught this. The
eight new tests assert the CONTEXT (CERT_REQUIRED, check_hostname, TLS1.2 floor, CERT_NONE only
under the escape, the clamp under enforcing PHI, and that a per-connection CA pins to ONLY that
CA). Negative control run: with the code change stashed and the tests kept, all eight go RED.
ruff + format clean; mypy unchanged at its 21-error pre-existing baseline (missing pynetdicom /
webauthn extras, none in touched files); 437 targeted tests green.
DELIBERATELY NOT DONE -- the alerts cell (pipeline/alert_sinks.py:384) still calls starttls()
bare. It needs an acknowledgment switch rather than the clamp, because the contextvar hop posture
is never stamped for that cell. Tracked as the residual on #323. #139's "verifying context by
design" claim therefore remains FALSE and is not corrected here.
BLOCKED, needs one follow-up commit: adding `ssl` to transports/{email,direct}.py reds the
required crypto-inventory gate until scripts/security/crypto_inventory_check.py documents it.
That file is checked out live in another session; the collision gate refused the edit and I
asked that session for the two lines rather than clobbering their work. docs/BACKLOG.md (#323's
banner, #139) is held by two other sessions for the same reason.1 parent 3389aa2 commit 2a48641
9 files changed
Lines changed: 391 additions & 37 deletions
File tree
- messagefoundry
- config
- transports
- tests
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
62 | 62 | | |
63 | 63 | | |
64 | 64 | | |
| 65 | + | |
65 | 66 | | |
66 | 67 | | |
67 | 68 | | |
| |||
919 | 920 | | |
920 | 921 | | |
921 | 922 | | |
| 923 | + | |
| 924 | + | |
| 925 | + | |
| 926 | + | |
| 927 | + | |
| 928 | + | |
| 929 | + | |
| 930 | + | |
| 931 | + | |
| 932 | + | |
| 933 | + | |
| 934 | + | |
| 935 | + | |
| 936 | + | |
| 937 | + | |
| 938 | + | |
| 939 | + | |
| 940 | + | |
| 941 | + | |
| 942 | + | |
| 943 | + | |
| 944 | + | |
| 945 | + | |
| 946 | + | |
| 947 | + | |
| 948 | + | |
| 949 | + | |
| 950 | + | |
| 951 | + | |
| 952 | + | |
| 953 | + | |
| 954 | + | |
| 955 | + | |
| 956 | + | |
| 957 | + | |
| 958 | + | |
| 959 | + | |
| 960 | + | |
| 961 | + | |
| 962 | + | |
| 963 | + | |
| 964 | + | |
| 965 | + | |
| 966 | + | |
| 967 | + | |
| 968 | + | |
| 969 | + | |
| 970 | + | |
| 971 | + | |
| 972 | + | |
| 973 | + | |
| 974 | + | |
| 975 | + | |
| 976 | + | |
| 977 | + | |
| 978 | + | |
| 979 | + | |
| 980 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1785 | 1785 | | |
1786 | 1786 | | |
1787 | 1787 | | |
| 1788 | + | |
| 1789 | + | |
| 1790 | + | |
1788 | 1791 | | |
1789 | 1792 | | |
1790 | 1793 | | |
| |||
1793 | 1796 | | |
1794 | 1797 | | |
1795 | 1798 | | |
1796 | | - | |
1797 | | - | |
1798 | | - | |
| 1799 | + | |
| 1800 | + | |
| 1801 | + | |
| 1802 | + | |
| 1803 | + | |
| 1804 | + | |
| 1805 | + | |
| 1806 | + | |
| 1807 | + | |
| 1808 | + | |
| 1809 | + | |
| 1810 | + | |
1799 | 1811 | | |
1800 | 1812 | | |
1801 | 1813 | | |
| |||
1810 | 1822 | | |
1811 | 1823 | | |
1812 | 1824 | | |
| 1825 | + | |
| 1826 | + | |
| 1827 | + | |
1813 | 1828 | | |
1814 | 1829 | | |
1815 | 1830 | | |
| |||
1836 | 1851 | | |
1837 | 1852 | | |
1838 | 1853 | | |
| 1854 | + | |
| 1855 | + | |
| 1856 | + | |
1839 | 1857 | | |
1840 | 1858 | | |
1841 | 1859 | | |
| |||
1844 | 1862 | | |
1845 | 1863 | | |
1846 | 1864 | | |
1847 | | - | |
| 1865 | + | |
| 1866 | + | |
| 1867 | + | |
| 1868 | + | |
| 1869 | + | |
| 1870 | + | |
| 1871 | + | |
| 1872 | + | |
1848 | 1873 | | |
1849 | 1874 | | |
1850 | 1875 | | |
| |||
1865 | 1890 | | |
1866 | 1891 | | |
1867 | 1892 | | |
| 1893 | + | |
| 1894 | + | |
| 1895 | + | |
1868 | 1896 | | |
1869 | 1897 | | |
1870 | 1898 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
46 | 46 | | |
47 | 47 | | |
48 | 48 | | |
| 49 | + | |
49 | 50 | | |
50 | 51 | | |
51 | 52 | | |
| |||
57 | 58 | | |
58 | 59 | | |
59 | 60 | | |
60 | | - | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
61 | 66 | | |
62 | 67 | | |
63 | 68 | | |
| |||
140 | 145 | | |
141 | 146 | | |
142 | 147 | | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
143 | 155 | | |
144 | 156 | | |
145 | 157 | | |
| |||
167 | 179 | | |
168 | 180 | | |
169 | 181 | | |
170 | | - | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
171 | 188 | | |
172 | 189 | | |
173 | | - | |
174 | | - | |
| 190 | + | |
| 191 | + | |
175 | 192 | | |
176 | 193 | | |
177 | 194 | | |
| |||
183 | 200 | | |
184 | 201 | | |
185 | 202 | | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
186 | 237 | | |
187 | 238 | | |
188 | 239 | | |
| |||
317 | 368 | | |
318 | 369 | | |
319 | 370 | | |
320 | | - | |
| 371 | + | |
| 372 | + | |
| 373 | + | |
| 374 | + | |
321 | 375 | | |
322 | 376 | | |
323 | | - | |
| 377 | + | |
324 | 378 | | |
325 | 379 | | |
326 | 380 | | |
| |||
0 commit comments