Hacktoberfest 2026:维护者为十月标记出来的 issue,仍然开放、适合新手。 浏览 Hacktoberfest issue

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

未关闭
#2,014 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

维护者通常 1 天内回复

@oberstet 已经在做这个了。

开始于 2026年10月20日。

  • #2015 来自 @oberstet —— 未关闭

评估

难度
4/5
预计耗时
3-5 天
新手友好度
35/100
Issue 类型
缺陷
描述清晰度
描述清楚
活跃度
活跃
技术栈
cryptography, python

调研方向

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.

由索引模型根据 Issue 内容生成。

描述

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

主要语言
Python
星标
2.5k
派生
767
平均合并
1 天 1 小时
30 天内合并 PR
23

环境准备

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

crossbario/autobahn-python 的其他 Issue

查看 crossbario/autobahn-python 的全部 Issue

相似的 Issue

更多 Python Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。