Skip to content

curl/OpenSSL clients fail TLS handshake with custom TLS 1.3 stack #15

Description

@MichaelDecent

Summary

External TLS clients (curl, OpenSSL s_client) cannot complete the TLS 1.3 handshake with tigrcorn's custom pure-Python TLS record layer. The error from curl is:

OpenSSL SSL_read: error:0A0001BB:SSL routines::bad record type

Environment

  • macOS (darwin-arm64)
  • curl 8.x with OpenSSL 3.6.1
  • Python 3.14.2 with OpenSSL 3.5.4
  • tigrcorn master (076aa46)

Root cause

tigrcorn uses a custom pure-Python TLS 1.3 implementation (src/tigrcorn/security/tls.py + src/tigrcorn/security/tls13/) rather than Python's stdlib ssl module. The handshake completes successfully when the client is:

  • tigrcorn's own QuicTlsHandshakeDriver (internal driver-to-driver)
  • Python's asyncio.open_connection(ssl=...) (verified working)

But fails with:

  • curl (any version using OpenSSL 3.5+)
  • Any external OpenSSL-based client

The bad record type error indicates curl's OpenSSL rejects something in the TLS record format that tigrcorn sends. The record headers appear correct (type=23, version=0x0303, valid length), so the issue is likely in the encrypted record content or framing that newer OpenSSL versions are stricter about.

Reproduction

# Generate Ed25519 certs with proper extensions (AKI, SKI, KeyUsage)
# Start server
tigrcorn app:app --host 127.0.0.1 --port 8443 \
  --ssl-certfile server.pem \
  --ssl-keyfile server-key.pem

# Fails
curl --cacert ca.pem https://localhost:8443/
# OpenSSL SSL_read: error:0A0001BB:SSL routines::bad record type

Proposed fix

Add a --ssl-backend CLI flag (custom | stdlib). The TCPListener.start() method (lines 55-67 of listeners/tcp.py) already supports two code paths:

  • custom (default): ServerTLSContext → pure-Python TLS 1.3 handshake
  • stdlib: ssl.SSLContext → passed directly to asyncio.start_server(ssl=...)

The fix is to allow build_server_ssl_context() to return a stdlib ssl.SSLContext when --ssl-backend=stdlib is specified. No changes needed to TCPListener itself.

Workaround

Pass a stdlib ssl.SSLContext to TCPListener directly via a launcher script:

import ssl
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ctx.load_cert_chain('server.pem', 'server-key.pem')
listener = TCPListener('127.0.0.1', 8443, ssl=ctx)

Additional findings

Demo certs also needed X.509 extensions (Authority Key Identifier, Subject Key Identifier, Key Usage) that OpenSSL 3.5+ requires for certificate chain validation. Without these, even Python's ssl module rejects the certs.

Expected behavior

External TLS clients like curl should be able to connect to tigrcorn over HTTPS.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions