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]

Reply via email to