python / python/cpython

SSLSocket.getpeercert returns none if cert isn't valid

未關閉
#122,962 0 則留言 3 個 reaction 已指派 0 人 在 GitHub 檢視

還沒有人認領這個 Issue。

stdlib topic-SSL type-bug
主要語言
Python
星號
77.2k
分支
36k
PR 合併指標
PR 指標待擷取

描述

Bug report

Bug description:

It's possible I'm misunderstanding, but it seems the documentation around SSLSocket.getpeercert is incorrect and/or the function is bugged. This was run on Fedora 40 with python 3.12.5. I'm trying to bypass DNS and pull the SSL cert straight from an IP for a specific set of Hosts.

the docs I'm referencing: https://docs.python.org/3/library/ssl.html#ssl.SSLSocket.getpeercert

Site and HostIP are variables representing a site URL and a Apache server's direct IP address. This is inside a function call.

the code:

	#empty cert info dict
	SiteCertInfo = {}

	#create SSL context and socket
	#this overrides "DNS" so we can pull the SSL cert as it is on the server
	sslContext = ssl.create_default_context()
	sslSock = socket.socket()
	sslConn = sslContext.wrap_socket(sslSock, server_hostname=Site)

	#try the connection
	try:
		sslConn.connect((HostIp, 443))
 
	#ssl cert can't be verified / doesn't match
	except ssl.SSLCertVerificationError as ex:
		#show error reasons
		print(Site + " site SSL did not verify. reason: " + ex.verify_message, file=sys.stderr)

		#continue
		pass
	


	#pull the cert from the connection
	sslCert = sslConn.getpeercert()

The error:

    sslCert = sslConn.getpeercert()
              ^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib64/python3.12/ssl.py", line 1129, in getpeercert
    return self._sslobj.getpeercert(binary_form)
           ^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'getpeercert'

The issue:
The documentation states "for a client SSL socket, the server will always provide a certificate, regardless of whether validation was required;" I'm reading that as my python "client" connecting to the remote server should always get a cert even if it fails validation.

However, if the certificate is expired I'm catching the error and continuing on via the pass call. The sslConn.getpeercert() call fails as sslConn is None. If I don't catch the error then it just fails as normal.

If I try and by-pass validation by doing:

	sslContext = ssl.SSLContext(protocol = ssl.PROTOCOL_TLS_CLIENT)
	sslContext.check_hostname = False
	sslContext.verify_mode = ssl.CERT_NONE
	sslContext.set_default_verify_paths()

This just fails and pulls a "empty" dict for sslCert. If I set verify_mode back to CERT_REQUIRED I'm back to square one.

So, there doesn't seem to be a way to "catch" the validation error, but still continue to load the cert if the cert is expired or to disable validation via CERT_NONE and still pull the cert data and manually "validate" it.

CPython versions tested on:

3.12

Operating systems tested on:

Linux

貢獻指南

開啟貢獻指南

從這裡開始

  1. 先讀完整個 Issue,再讀專案的貢獻指南。
  2. 在 Issue 下留言說明你要接手 —— 這能避免兩個人做同樣的事。
  3. Fork 儲存庫,在一個分支上完成修改。
  4. 送出 Pull Request,並在描述裡引用這個 Issue 編號。

研究方向

先從 ssl.SSLSocket.getpeercert 的文件行為和 SSLContext 的驗證設定著手,接著使用過期憑證和 CERT_NONE 重現回報的連線流程。比較 SSLCertVerificationError 之後的 socket 狀態與 getpeercert(binary_form=True) 的結果。完成的標準是確認該行為屬於實作錯誤還是文件問題,並加入相應的回歸測試或文件說明。

由索引模型根據 Issue 內容生成。

評估

技術堆疊
python
領域
networking, security
Issue 類型
缺陷
難度
4/5
預估耗時
3-5 天
活躍度
停滯
描述清晰度
基本清楚
新手友好度
35/100

把新 issue 寄到你的電子郵件信箱

精選適合新手參與的 GitHub issue 摘要。