惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
U
Unit 42
GbyAI
GbyAI
M
MIT News - Artificial intelligence
美团技术团队
罗磊的独立博客
雷峰网
雷峰网
量子位
博客园 - 【当耐特】
Last Week in AI
Last Week in AI
D
Docker
小众软件
小众软件
S
SegmentFault 最新的问题
Blog — PlanetScale
Blog — PlanetScale
阮一峰的网络日志
阮一峰的网络日志
宝玉的分享
宝玉的分享
T
Tailwind CSS Blog
WordPress大学
WordPress大学
V
V2EX
博客园_首页
腾讯CDC
The Cloudflare Blog
A
About on SuperTechFans
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC

alexwlchan’s notes

What is WS11 1DB? Blocking referrers with Caddy How to type a Spanish question mark (¿) on a Mac Non-overlapping type comparisons and Python type checkers Why does t.Setenv panic after t.Parallel? Use Path.glob() and Path.rglob() for typed versions of glob.glob() Curious clocks and colourful eyes Track which templates are used by Jinja2 Archeologists distinguish between “sherds” and “shards” A single command to test all my changed Go packages Disable the new message animations in WhatsApp Finding high-churn folders that bother Backblaze Always-on SSH agent forwarding with my Git pushes Managing the caption of a photo with AppleScript (but not PhotoKit) Goodhart’s and Campbell’s Law are different Notes from The Cornishman No. 176 (Spring 2026) Notes from The Cornishman No. 176 (Spring 2026) GitUp can’t diff text files larger than 8MB Home Testing the width of a page on a mobile device using Playwright Disable AirPods charging notifications Start a Caddy server in a subprocess during a Python session Filter a list of JSON object based on a list of tags HOME_GET_ME_HOME is a Citymapper Shortcuts action The FileExistsError exception exposes a filename attribute The red-lined bubble snail Useful type hints for Python How to truncate the middle of long command output AirPlay Receiver can interfere with Flask apps What’s the main prefix in SQLite queries?
Why can’t Python connect to example.com?
A mistrusted certificate and Authority Information Access · 2026-03-28 · via alexwlchan’s notes

The Python SSL libraries only know about the certificates sent by the server and in my local store. They can’t retrieve missing certificates.

I’ve been experimenting with Python HTTP libraries, and I ran into an unexpected error connecting to example.com:

>>> import certifi, ssl, urllib.request
>>> ssl_context = ssl.create_default_context(cafile=certifi.where())
>>> urllib.request.urlopen("https://example.com", context=ssl_context)
Traceback (most recent call last):
  […]
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1081)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  […]
urllib.error.URLError: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1081)>

I get similar errors if I use httpx or requests.

If you look up this error, the usual advice is to make sure you’re using certifi, you have the latest version installed, run Install Certificates.command, and so on. Everything looks fine on my system, and I can connect to other websites just fine:

>>> certifi.__version__
'2026.02.25'
>>> certifi.where()
'/tmp/tmp.ZzvjQtkeZT/.venv/lib/python3.14/site-packages/certifi/cacert.pem'
>>> urllib.request.urlopen("https://alexwlchan.net", context=ssl_context)
<http.client.HTTPResponse object at 0x1056722f0>

I can also open example.com in my web browser, but not in Python – what’s up?

I found a certifi issue filed by Clément Beaujoin which describes this exact issue:

As of February 14, 2026, many automated tests and features relying on example.com began failing with ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1016).

This is caused by example.com (Cloudflare) transitioning to a new certificate chain that roots into AAA Certificate Services, which was officially distrusted by major certificate stores (including certifi) in early February 2026. Because Python’s requests/urllib3 does not support AIA (Authority Information Access) to fetch missing intermediates, the verification fails in environments with updated root stores, even though browsers (which support AIA) show the site as secure.

Alex Gaynor, one of the maintainers, explained this isn’t something certifi is going to change:

If example.com is not shipping the necessary intermediates, that’s a bug in their TLS serving configuration impacting all non-browser clients, not something we’re going to work around.

This explanation sounds right to me, but I wanted to understand more. Is there a way to print the certificate chain sent by the server, so I can see the missing intermediate and the AIA that tells my client where to fetch the missing intermediates? How could I have worked this out myself?

I tried running various openssl commands and Python scripts that were supposedly printing the certificate chain, but I don’t understand TLS well enough to really know what’s going on.

I was able to see that certifi no longer trusts AAA Certificate Services, and when. I tried older versions of certifi, and example.com loads with 2025.1.31 but not with 2025.4.26. Then I looked at the diff for 2025.4.26, and I can see a certificate with the same name being removed:

128-# Issuer: CN=AAA Certificate Services O=Comodo CA Limited

129-# Subject: CN=AAA Certificate Services O=Comodo CA Limited

130-# Label: "Comodo AAA Services root"

131-# Serial: 1

I also found a CPython issue where Authority Information Access is mentioned where the topic is discussed, and Alex explained that it’s unlikely to be added to Python:

No, and at this point [the issue] should probably be wontfix’d (IMO), as AIA chasing is relatively out of favor compared to intermediate preloading.

I still don’t really understand HTTPS or TLS certificates and I’m not sure how to fix this if I encounter another misconfigured website – but I only use example.com for testing, so for now I can just pick another website to test instead.

Why wasn’t this caught by my tests?

I use vcrpy to test my HTTP code, but it doesn’t do anything with TLS certificates, just unencrypted HTTP responses. I didn’t catch this until I tried regenerating my recorded cassettes, and discovered that the HTTPS certificate issues meant I could no longer do so.

Perhaps I need a procedure for regenerating vcrpy cassettes when I upgrade my dependencies, or on a fixed schedule?