[BUG] extract_peer_certificate() raises AttributeError with pyOpenSSL >= 26.2.0 when the peer certificate has extensions
维护者通常 1 天内回复
评估
- 难度
- 4/5
- 预计耗时
- 3-5 天
- 新手友好度
- 35/100
- Issue 类型
- 缺陷
- 描述清晰度
- 描述清楚
- 活跃度
- 活跃
- 技术栈
- cryptography, python
- 领域
- api, authentication, backend
调研方向
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 内容生成。
描述
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).
- 主要语言
- Python
- 星标
- 2.5k
- 派生
- 767
- 平均合并
- 1 天 1 小时
- 30 天内合并 PR
- 23
环境准备
- 没有 Dockerfile 或 Docker Compose 文件
- 有 Pull Request 模板
- 阅读贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
crossbario/autobahn-python 的其他 Issue
-
CI-CD enhancement
难度 2/5 1-3 小时 新手友好度 78/100
crossbario/autobahn-python#1945 · 1 条评论 ·
维护者通常 1 天内回复
-
bug CI-CD
难度 2/5 1-3 小时 新手友好度 76/100
crossbario/autobahn-python#1932 ·
维护者通常 1 天内回复
-
CI-CD enhancement
难度 2/5 1-3 小时 新手友好度 72/100
crossbario/autobahn-python#1829 · 1 条评论 ·
维护者通常 1 天内回复
-
bug CI-CD
难度 4/5 3-5 天 新手友好度 52/100
crossbario/autobahn-python#2013 ·
维护者通常 1 天内回复
-
难度 5/5 一周以上 新手友好度 25/100
crossbario/autobahn-python#1963 · 2 条评论 ·
维护者通常 1 天内回复
查看 crossbario/autobahn-python 的全部 Issue
相似的 Issue
-
[Bug] Unit tests reach real Bedrock call paths without patching the model可能已有人在做 @proton0210 今天认领。 未关闭bug
难度 2/5 1-3 小时 新手友好度 67/100
aws-samples/sample-aws-genai-db-modernizer#1318 · 1 条评论 ·
维护者通常 1 天内回复
-
pyarrow-25.0.1-cp310-cp310-manylinux_2_28_x86_64.whl: 1 vulnerabilities (highest severity is: 5.3)未关闭Mend: dependency security vulnerability
难度 2/5 1-3 小时 新手友好度 62/100
capitalone/locopy#394 ·
-
bug
难度 2/5 1-3 小时 新手友好度 76/100
维护者通常 1 天内回复
-
sdk-typescript: unquoted COPY globs in shell form are silently dropped from the image build context未关闭bug javascript
难度 2/5 1-3 小时 新手友好度 78/100
维护者通常 1 天内回复
-
enhancement
难度 2/5 1-3 小时 新手友好度 65/100
ietf-tools/datatracker#11922 · 1 条评论 ·
维护者通常 1 天内回复