[BUG] extract_peer_certificate() raises AttributeError with pyOpenSSL >= 26.2.0 when the peer certificate has extensions
I maintainer di solito rispondono entro 1 giorno
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
- Ambito
- api, authentication, backend
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
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)
-
Re-implement the extraction on
cryptography.x509(a core autobahn dependency,cryptography>=50), as a backend-neutral helper that takes acryptography.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 fillspeer_certfor asyncio too, so both backends behave the same.
- Twisted:
-
Keep the returned dict as it is. It is an interface: dynamic authenticators get it, and Crossbar.io's
tlsauthmethod 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,versioncert.serial_number,cert.version.valueyes not_before,not_aftercert.not_valid_before_utc.strftime("%Y%m%d%H%M%SZ")yes expirednow > cert.not_valid_after_utcyes signature_algorithmcert.signature_algorithm_oid._nameyes for RSA and ECDSA; ed25519instead ofED25519subject,issuerper attribute: rfc4514_attribute_name(C,ST,L,O,OU,CN), else (emailAddresshas no RFC 4514 name)oid._name; lowercasedyes, including emailaddresshashX509.subject_name_hash()(OpenSSLX509_NAME_hash; not deprecated, no cryptography equivalent)yes on Twisted; Noneon asyncioextensions[].name,.criticalext.oid._name,ext.criticalyes for the standard extensions extensions[].valuewas OpenSSL's text ( DNS:localhost, IP Address:127.0.0.1); cryptography has no such printerchanges: SAN can be rendered OpenSSL-style, other extensions as str(ext.value) -
Never raise on the peer's certificate. This runs on data the peer controls, before authentication. cryptography rejects for example duplicate extensions when
cert.extensionsis read; catch that, log it, and return the other fields withextensions: [], so the connection still gets to authentication. -
Fix bug B: in
_onOpen()(WebSocket) and_on_handshake_complete()(RawSocket) setpeer_certnext tochannel_id. TLS application data only arrives after the handshake, so the certificate is there by then. -
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_certto 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
tlsover 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
- Nessun Dockerfile né file Docker Compose
- Ha un modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di crossbario/autobahn-python
-
CI-CD enhancement
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
crossbario/autobahn-python#1945 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
bug CI-CD
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
crossbario/autobahn-python#1932 ·
I maintainer di solito rispondono entro 1 giorno
-
CI-CD enhancement
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
crossbario/autobahn-python#1829 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
bug CI-CD
Difficoltà 4/5 3-5 giorni Idoneità per principianti 52/100
crossbario/autobahn-python#2013 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 5/5 Più di una settimana Idoneità per principianti 25/100
crossbario/autobahn-python#1963 · 2 commenti ·
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di crossbario/autobahn-python
Issue simili
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
-
changelog investigate
Difficoltà 2/5 1-3 ore Idoneità per principianti 65/100
ramnes/notion-sdk-py#423 ·
-
type-bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 77/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
I maintainer di solito rispondono entro 1 giorno