Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

[BUG] extract_peer_certificate() raises AttributeError with pyOpenSSL >= 26.2.0 when the peer certificate has extensions

Aperta
#2,014 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

@oberstet ci sta già lavorando.

Dal 20/10/2026.

  • #2015 di @oberstet — aperta

Valutazione

Difficoltà
4/5
Tempo stimato
3-5 giorni
Idoneità per principianti
35/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
cryptography, python

Direzione di ricerca

Start with extract_peer_certificate() and create_transport_details() in the Twisted and asyncio paths, then read twisted/websocket.py and twisted/rawsocket.py at their handshake callbacks. The issue identifies pyOpenSSL's removed get_extension() call and describes the expected certificate fields and transport behavior. Done means certificate extraction works with current pyOpenSSL, peer_cert is populated after TLS handshakes, and the listed real-certificate tests pass.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

bug wamp websocket

Analysis across autobahn-python (master b7c3646d) and Crossbar.io (master 4d2a46fb + fix_2048), with pyOpenSSL 26.4.0, Twisted 26.4.0, cryptography 50.0.2.

Two bugs, not one

A. extract_peer_certificate() raises on every real certificate (this issue). It calls X509.get_extension(i), removed in pyOpenSSL 26.2.0 (get_extension_count() is still there, so the loop is entered). It also calls X509.get_subject() / get_issuer(), deprecated in pyOpenSSL 26.x ("in preparation for deprecating pyOpenSSL's X509 entirely"). Only a certificate without any extensions gets through, which in practice is none (SAN, key usage, basic constraints).

B. On WebSocket and RawSocket (Twisted), peer_cert is never filled. create_transport_details() runs in connectionMade(), and Twisted calls the wrapped protocol's connectionMade() on TCP connect, before the TLS handshake. After the handshake, _onOpen() (twisted/websocket.py) and _on_handshake_complete() (twisted/rawsocket.py) refresh channel_id, but not peer_cert. Bug B hides bug A on these transports. Checked with a TLS server that requires a client certificate, on both TLS 1.2 and TLS 1.3:

connectionMade     peer_cert: None   channel_id: 00000000...
handshakeCompleted EXC AttributeError 'X509' object has no attribute 'get_extension'
dataReceived       EXC AttributeError 'X509' object has no attribute 'get_extension'

Both bugs date from bbdadcb5 (#1552, 2022-04-27, "add TLS extract_peer_certificate helper; fill in transport details"). Before that, Crossbar.io read the client certificate itself.

Who is affected

Caller Effect
autobahn twisted/websocket.py, twisted/rawsocket.py → create_transport_details() in connectionMade() peer_cert always None (bug B)
autobahn asyncio/util.py create_transport_details() peer_cert hard-coded None (asyncio never extracts it)
Crossbar.io router/auth/tls.py (PendingAuthTLS, authmethod tls), reads transport_details.peer_cert["sha1"] over WebSocket / RawSocket: always "no client certificate presented" (bug B), since 22.x
Crossbar.io dynamic authenticators: details["transport"]["peer_cert"] (via TransportDetails.marshal(), router/auth/pending.py) same, always null
Crossbar.io MQTT bridge WampTransport (bridge/mqtt/wamp.py, crossbar #2048): builds its transport details after the handshake AttributeError, the connection fails (bug A); the two MQTT TLS tests are skipped for now

So TLS client-certificate authentication in Crossbar.io does not work on any transport today, and no test catches it: neither project has a test with a real TLS client certificate. Crossbar.io's test_authorize.py::test_authextra_tls uses mocks.

Other pyOpenSSL X509 uses in Crossbar.io are unaffected: crypto.load_certificate() in common/twisted/endpoint.py and common/twisted/tlsctx.py still exists.

Recommended fix (autobahn 26.10.2)

  1. Re-implement the extraction on cryptography.x509 (a core autobahn dependency, cryptography>=50), as a backend-neutral helper that takes a cryptography.x509.Certificate:

    • Twisted: transport.getPeerCertificate().to_cryptography() (to_cryptography() exists since pyOpenSSL 17.1, the floor is 20.0.1).
    • asyncio: ssl_object.getpeercert(binary_form=True) → x509.load_der_x509_certificate(). This fills peer_cert for asyncio too, so both backends behave the same.
  2. Keep the returned dict as it is. It is an interface: dynamic authenticators get it, and Crossbar.io's tls authmethod keys on "sha1". Field by field, checked against the current pyOpenSSL output:

    field replacement same as before?
    md5, sha1, sha256 ":".join(f"{b:02X}" for b in cert.fingerprint(hashes.SHA1())) yes, byte-identical
    serial, version cert.serial_number, cert.version.value yes
    not_before, not_after cert.not_valid_before_utc.strftime("%Y%m%d%H%M%SZ") yes
    expired now > cert.not_valid_after_utc yes
    signature_algorithm cert.signature_algorithm_oid._name yes for RSA and ECDSA; ed25519 instead of ED25519
    subject, issuer per attribute: rfc4514_attribute_name (C, ST, L, O, OU, CN), else (emailAddress has no RFC 4514 name) oid._name; lowercased yes, including emailaddress
    hash X509.subject_name_hash() (OpenSSL X509_NAME_hash; not deprecated, no cryptography equivalent) yes on Twisted; None on asyncio
    extensions[].name, .critical ext.oid._name, ext.critical yes for the standard extensions
    extensions[].value was OpenSSL's text (DNS:localhost, IP Address:127.0.0.1); cryptography has no such printer changes: SAN can be rendered OpenSSL-style, other extensions as str(ext.value)
  3. Never raise on the peer's certificate. This runs on data the peer controls, before authentication. cryptography rejects for example duplicate extensions when cert.extensions is read; catch that, log it, and return the other fields with extensions: [], so the connection still gets to authentication.

  4. Fix bug B: in _onOpen() (WebSocket) and _on_handshake_complete() (RawSocket) set peer_cert next to channel_id. TLS application data only arrives after the handshake, so the certificate is there by then.

  5. Tests with real TLS client certificates, generated at test time: the extraction on a certificate with SAN, key usage, EKU and an email address, comparing the dict field by field; WebSocket and RawSocket over TLS deliver peer_cert to the session; a malformed certificate does not raise; the asyncio path.

Then Crossbar.io 26.10.1

  • Raise the floor to autobahn>=26.10.2, relock.
  • Un-skip the two MQTT TLS tests (#2048).
  • Add a router test: authmethod tls over RawSocket and over WebSocket with a real client certificate, both accepted and denied, so this cannot regress unnoticed again.
  • Changelog: TLS client-certificate authentication works again on all transports.

Not in scope: tls-unique is not defined for TLS 1.3 (RFC 9266 tls-exporter), see crossbar #2037.

This analysis was done with AI assistance (Claude Code).

Lingua principale
Python
Stelle
2.5k
Fork
767
Merge medio
1g 1h
PR unite (30g)
23

Preparare l'ambiente

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di crossbario/autobahn-python

Tutte le issue di crossbario/autobahn-python

Issue simili

Altre issue su Python

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.