Hacktoberfest 2026: los issues que los mantenedores marcaron para octubre, abiertos y aptos para principiantes. Explorar issues de Hacktoberfest

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

Abierto
#2,014 0 comentarios 0 reacciones 0 asignados Ver en GitHub

Los mantenedores suelen responder en 1 día

@oberstet ya está trabajando en esto.

Desde el 20/10/2026.

  • #2015 de @oberstet — abierto

Evaluación

Dificultad
4/5
Tiempo estimado
3-5 días
Aptitud para principiantes
35/100
Tipo de issue
Error
Claridad
Bien especificado
Estado de actividad
Activo
Stack tecnológico
cryptography, python

Línea de trabajo

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.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

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).

Lenguaje dominante
Python
Estrellas
2.5k
Forks
767
Merge medio
1 d 1 h
PR fusionados (30 d)
23

Preparar el entorno

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de crossbario/autobahn-python

Todos los issues de crossbario/autobahn-python

Issues similares

Más issues de Python

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.