Clemens Vasters created PROTON-2934:
---------------------------------------
Summary: TLS 1.3 unsupported in Windows python-qpid-proton wheel
Key: PROTON-2934
URL: https://issues.apache.org/jira/browse/PROTON-2934
Project: Qpid Proton
Issue Type: Bug
Components: proton-c, python-binding
Environment: Windows 10 / 11 / Server 2019+, any Python
`cp3x-win_amd64` wheel of `python-qpid-proton`
Reporter: Clemens Vasters
The pre-built Windows wheel of `python-qpid-proton` (and, by extension, every
`proton-c` build on Windows that uses the bundled SChannel TLS backend) cannot
negotiate **TLS 1.3**. It tops out at TLS 1.2. This makes it impossible to
connect from Windows to any AMQP 1.0 broker that enforces a TLS-1.3 minimum,
e.g. an Azure Service Bus namespace with `minimumTlsVersion = 1.3`.
`c/src/ssl/schannel.cpp` (the implementation selected at build time when
`os.name == 'nt'` in `python/ext_build.py`) initialises its SChannel
credentials with the **legacy** `SCHANNEL_CRED` structure:
```cpp
// c/src/ssl/schannel.cpp ~ L237-256 (main branch, checked 2026-05-22)
SCHANNEL_CRED descriptor;
memset(&descriptor, 0, sizeof(descriptor));
descriptor.dwVersion = SCHANNEL_CRED_VERSION;
descriptor.dwFlags = SCH_CRED_NO_DEFAULT_CREDS |
SCH_CRED_MANUAL_CRED_VALIDATION;
...
*status = AcquireCredentialsHandle(NULL, UNISP_NAME, direction, NULL,
&descriptor, NULL, NULL, &tmp_handle,
&expiry);
```
Microsoft's own SChannel documentation states that:
1. `SCHANNEL_CRED` is **deprecated**; "Applications should use the
`SCH_CREDENTIALS` structure instead."
https://learn.microsoft.com/en-us/windows/win32/api/schannel/ns-schannel-schannel_cred
2. TLS 1.3 was introduced in Windows 11 and Windows Server 2022, **but** the
protocol can only be negotiated when an application uses the new
`SCH_CREDENTIALS` structure together with `TLS_PARAMETERS`. Code that still
calls `AcquireCredentialsHandle()` with `SCHANNEL_CRED` will at most negotiate
TLS 1.2 regardless of what the underlying Windows version supports.
https://learn.microsoft.com/en-us/windows/win32/api/schannel/ns-schannel-sch_credentials
https://learn.microsoft.com/en-us/windows-server/security/tls/tls-schannel
A grep across `c/src/ssl/schannel.cpp` on `main` (2026-05-22) confirms there is
currently **no** code path using `SCH_CREDENTIALS`, no `TLS_PARAMETERS` block,
and `grbitEnabledProtocols` is never set:
```
SCH_CREDENTIALS : 0 occurrences
grbitEnabledProtocols : 0 occurrences
TLS_PARAMETERS : 0 occurrences
SCHANNEL_CRED : 2 occurrences (declaration + version assignment)
```
So the Windows wheel literally has no way to ask SChannel for TLS 1.3 today.
## Evidence on the released wheel
Tested with `python-qpid-proton == 0.40.0`, wheel tag `cp311-cp311-win_amd64`,
on Windows 11 x64, Python 3.11.9.
1. Binary string scan of the installed `cproton_ffi.pyd` (size 463 872 bytes):
| Pattern | Matches |
|-----------|---------|
| `TLSv1.0` | 0 |
| `TLSv1.1` | 1 |
| `TLSv1.2` | 1 |
| `TLSv1.3` | **0** |
| `OpenSSL ` | 0 |
(No OpenSSL banner strings present, consistent with the wheel using SChannel
rather than OpenSSL.)
2. `proton.SSLDomain` exposes no API to pin the minimum/maximum TLS protocol
version. Public methods on `SSLDomain` are limited to: `set_credentials`,
`set_peer_authentication`, `set_trusted_ca_db`, `allow_unsecured_client`. There
is no equivalent of OpenSSL's `SSL_CTX_set_min_proto_version` /
`SSL_CTX_set_max_proto_version` reachable from the Python or C API.
3. Live reproduction against an Azure Service Bus namespace pinned to
`minimumTlsVersion = 1.3`: the connection fails at the AMQP open handshake with:
```
amqp:unauthorized-access "Invalid TLS version"
```
The TCP/TLS handshake itself completes at TLS 1.2; the broker then closes
the AMQP session because the negotiated TLS version is below the namespace
policy. Repeating the same test from Linux (where the wheel uses OpenSSL)
succeeds.
## Minimal reproducer
Requires:
- Windows 10 1809 or newer (TLS 1.3 SChannel needs Win 11 / Server 2022 for
full client negotiation)
- An Azure Service Bus namespace with `minimumTlsVersion = 1.3`. Equivalent:
any AMQP 1.0 broker behind a TLS terminator (e.g. nginx / haproxy / Envoy)
configured to require TLS 1.3.
```python
# pip install python-qpid-proton==0.40.0 azure-identity
import os
from proton import Message
from proton.utils import BlockingConnection
from proton.reactor import Container
# Replace with your namespace
HOST = "<your-ns>.servicebus.windows.net"
URL = f"amqps://\{HOST}:5671"
conn = BlockingConnection(URL, sasl_enabled=True, allowed_mechs="ANONYMOUS",
ssl_domain=None) # will fail before SASL on Win
```
Expected (against a TLS-1.3-required broker): `proton.ConnectionException:
amqp:unauthorized-access`. On Linux against the same broker the connection
succeeds.
## Impact
- All Python users on Windows are locked out of any AMQP 1.0 broker that
requires TLS 1.3.
- TLS 1.3 is increasingly a baseline corporate / regulatory requirement
(PCI-DSS, FedRAMP High, several CSP "secure defaults" policies). For example,
Azure Service Bus customers under the `CloudGov_ServBus_TLS` policy cannot
lower `minimumTlsVersion` below 1.3.
- The same code path is used by every C-binding language that links proton-c on
Windows, so the bug surface is wider than just the Python wheel.
- Workarounds today require either (a) switching to a non-proton AMQP stack on
Windows, (b) running the Windows code under WSL/Linux, or (c) lowering the
broker's TLS minimum — all of which are unacceptable for many users.
## Suggested fix
Switch the SChannel credential acquisition path in `c/src/ssl/schannel.cpp` to
use the modern `SCH_CREDENTIALS` structure, with a fallback to `SCHANNEL_CRED`
only when running on a Windows release that pre-dates `SCH_CREDENTIALS` support
(Windows 10 1809 / Server 2019).
Outline (in `win_credential_cred_handle`):
```cpp
#ifdef SCH_CREDENTIALS_VERSION // present in modern Windows 10 SDKs
TLS_PARAMETERS tls_params = \{0};
// leave grbitDisabledProtocols = 0 → let SChannel pick the strongest
mutually-supported
// version, including TLS 1.3 when the peer offers it.
SCH_CREDENTIALS sch_creds = \{0};
sch_creds.dwVersion = SCH_CREDENTIALS_VERSION;
sch_creds.dwFlags = SCH_CRED_NO_DEFAULT_CREDS |
SCH_CRED_MANUAL_CRED_VALIDATION;
sch_creds.cTlsParameters = 1;
sch_creds.pTlsParameters = &tls_params;
if (cred->cert_context) {
sch_creds.paCred = &cred->cert_context;
sch_creds.cCreds = 1;
}
*status = AcquireCredentialsHandle(
NULL, UNISP_NAME, direction, NULL,
&sch_creds, NULL, NULL, &tmp_handle, &expiry);
if (*status == SEC_E_UNSUPPORTED_FUNCTION) {
// fallback path: old SCHANNEL_CRED for pre-1809 Windows
... existing legacy code ...
}
#else
... existing legacy code ...
#endif
```
## References
- Microsoft Learn — `SCHANNEL_CRED` (deprecated):
https://learn.microsoft.com/en-us/windows/win32/api/schannel/ns-schannel-schannel_cred
- Microsoft Learn — `SCH_CREDENTIALS` (replacement, supports TLS 1.3):
https://learn.microsoft.com/en-us/windows/win32/api/schannel/ns-schannel-sch_credentials
- Microsoft Learn — `TLS_PARAMETERS`:
https://learn.microsoft.com/en-us/windows/win32/api/schannel/ns-schannel-tls_parameters
- Microsoft Learn — TLS protocol support in SChannel (TLS 1.3 added in Win 11 /
Server 2022):
https://learn.microsoft.com/en-us/windows-server/security/tls/tls-schannel
- Related (already resolved) PROTON-1989 "TLS Configuration does not support
TLSv1_3 in OpenSSL v1.1.1" — fixed the OpenSSL path only.
- Related PROTON-2397 "Update default client TLS defaults for verifying
outbound connections to AMQP servers" — also OpenSSL-only.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]