python / python/cpython

SSLSocket.getpeercert returns none if cert isn't valid

オープン
#122,962 コメント 0 件 リアクション 3 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

stdlib topic-SSL type-bug
主要言語
Python
スター
77.2k
フォーク
35.9k
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. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

調査の方向性

ssl.SSLSocket.getpeercert の文書化された動作と SSLContext の検証設定から始め、次に期限切れの証明書と CERT_NONE を使用して報告された接続フローを再現します。SSLCertVerificationError 後のソケットの状態を、getpeercert(binary_form=True) の結果と比較します。動作が実装バグなのかドキュメントの問題なのかを明らかにし、対応する回帰テストまたはドキュメントの明確化を追加できれば完了です。

索引モデルが issue の本文から書いたものです。

評価

技術スタック
python
領域
networking, security
issue の種類
バグ
難易度
4/5
見積もり時間
3〜5日
活発さ
停滞
明瞭さ
おおむね明確
初心者へのやさしさ
35/100

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。