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

推荐订阅源

罗磊的独立博客
G
Google Developers Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
腾讯CDC
有赞技术团队
有赞技术团队
Vercel News
Vercel News
MongoDB | Blog
MongoDB | Blog
M
MIT News - Artificial intelligence
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
B
Blog RSS Feed
I
InfoQ
Blog — PlanetScale
Blog — PlanetScale
博客园_首页
The Cloudflare Blog
B
Blog
C
Check Point Blog
Stack Overflow Blog
Stack Overflow Blog
IT之家
IT之家
U
Unit 42
D
Docker
月光博客
月光博客
aimingoo的专栏
aimingoo的专栏
博客园 - Franky
A
About on SuperTechFans

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?