在做同步指定时间的Garmin运动记录到不同账号的时候碰到一个诡异的报错,因为重装了系统就出现问题,让我觉得系统出现什么问题。
在编写python脚本的时候,需要提前获取一下Garmin账号密钥,于是执行下面的命令。
python run_page/get_garmin_secret.py 账号 "密码" --is-cn
报错啦。。。
Traceback (most recent call last):
File "/Users/xxx/PycharmProjects/workouts_page/venv313/lib/python3.13/site-packages/urllib3/connectionpool.py", line 464, in _make_request
self._validate_conn(conn)
~~~~~~~~~~~~~~~~~~~^^^^^^
File "/Users/xxx/PycharmProjects/workouts_page/venv313/lib/python3.13/site-packages/urllib3/connectionpool.py", line 1093, in _validate_conn
conn.connect()
~~~~~~~~~~~~^^
File "/Users/xxx/PycharmProjects/workouts_page/venv313/lib/python3.13/site-packages/urllib3/connection.py", line 790, in connect
sock_and_verified = _ssl_wrap_socket_and_match_hostname(
sock=sock,
...<14 lines>...
assert_fingerprint=self.assert_fingerprint,
)
File "/Users/xxx/PycharmProjects/workouts_page/venv313/lib/python3.13/site-packages/urllib3/connection.py", line 969, in _ssl_wrap_socket_and_match_hostname
ssl_sock = ssl_wrap_socket(
sock=sock,
...<8 lines>...
tls_in_tls=tls_in_tls,
)
File "/Users/xxx/PycharmProjects/workouts_page/venv313/lib/python3.13/site-packages/urllib3/util/ssl_.py", line 480, in ssl_wrap_socket
ssl_sock = _ssl_wrap_socket_impl(sock, context, tls_in_tls, server_hostname)
File "/Users/xxx/PycharmProjects/workouts_page/venv313/lib/python3.13/site-packages/urllib3/util/ssl_.py", line 524, in _ssl_wrap_socket_impl
return ssl_context.wrap_socket(sock, server_hostname=server_hostname)
~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/homebrew/Cellar/[email protected]/3.13.5/Frameworks/Python.framework/Versions/3.13/lib/python3.13/ssl.py", line 455, in wrap_socket
return self.sslsocket_class._create(
~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
sock=sock,
^^^^^^^^^^
...<5 lines>...
session=session
^^^^^^^^^^^^^^^
)
^
File "/opt/homebrew/Cellar/[email protected]/3.13.5/Frameworks/Python.framework/Versions/3.13/lib/python3.13/ssl.py", line 1076, in _create
self.do_handshake()
~~~~~~~~~~~~~~~~~^^
File "/opt/homebrew/Cellar/[email protected]/3.13.5/Frameworks/Python.framework/Versions/3.13/lib/python3.13/ssl.py", line 1372, in do_handshake
self._sslobj.do_handshake()
~~~~~~~~~~~~~~~~~~~~~~~~~^^
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1028)
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/Users/xxx/PycharmProjects/workouts_page/venv313/lib/python3.13/site-packages/urllib3/connectionpool.py", line 787, in urlopen
response = self._make_request(
conn,
...<10 lines>...
**response_kw,
)
File "/Users/xxx/PycharmProjects/workouts_page/venv313/lib/python3.13/site-packages/urllib3/connectionpool.py", line 488, in _make_request
raise new_e
urllib3.exceptions.SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1028)
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/Users/xxx/PycharmProjects/workouts_page/venv313/lib/python3.13/site-packages/requests/adapters.py", line 667, in send
resp = conn.urlopen(
method=request.method,
...<9 lines>...
chunked=chunked,
)
File "/Users/xxx/PycharmProjects/workouts_page/venv313/lib/python3.13/site-packages/urllib3/connectionpool.py", line 871, in urlopen
return self.urlopen(
~~~~~~~~~~~~^
method,
^^^^^^^
...<13 lines>...
**response_kw,
^^^^^^^^^^^^^^
)
^
File "/Users/xxx/PycharmProjects/workouts_page/venv313/lib/python3.13/site-packages/urllib3/connectionpool.py", line 871, in urlopen
return self.urlopen(
~~~~~~~~~~~~^
method,
^^^^^^^
...<13 lines>...
**response_kw,
^^^^^^^^^^^^^^
)
^
File "/Users/xxx/PycharmProjects/workouts_page/venv313/lib/python3.13/site-packages/urllib3/connectionpool.py", line 871, in urlopen
return self.urlopen(
~~~~~~~~~~~~^
method,
^^^^^^^
...<13 lines>...
**response_kw,
^^^^^^^^^^^^^^
)
^
File "/Users/xxx/PycharmProjects/workouts_page/venv313/lib/python3.13/site-packages/urllib3/connectionpool.py", line 841, in urlopen
retries = retries.increment(
method, url, error=new_e, _pool=self, _stacktrace=sys.exc_info()[2]
)
File "/Users/xxx/PycharmProjects/workouts_page/venv313/lib/python3.13/site-packages/urllib3/util/retry.py", line 519, in increment
raise MaxRetryError(_pool, url, reason) from reason # type: ignore[arg-type]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
urllib3.exceptions.MaxRetryError: HTTPSConnectionPool(host='sso.garmin.cn', port=443): Max retries exceeded with url: /sso/embed?id=gauth-widget&embedWidget=true&gauthHost=https%3A%2F%2Fsso.garmin.cn%2Fsso (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1028)')))
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/Users/xxx/PycharmProjects/workouts_page/run_page/get_garmin_secret.py", line 18, in <module>
garth.login(options.email, options.password)
~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/xxx/PycharmProjects/workouts_page/venv313/lib/python3.13/site-packages/garth/http.py", line 167, in login
self.oauth1_token, self.oauth2_token = sso.login(
~~~~~~~~~^
*args, **kwargs, client=self
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)
^
File "/Users/xxx/PycharmProjects/workouts_page/venv313/lib/python3.13/site-packages/garth/sso.py", line 92, in login
client.get("sso", "/sso/embed", params=SSO_EMBED_PARAMS)
~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/xxx/PycharmProjects/workouts_page/venv313/lib/python3.13/site-packages/garth/http.py", line 155, in get
return self.request("GET", *args, **kwargs)
~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/xxx/PycharmProjects/workouts_page/venv313/lib/python3.13/site-packages/garth/http.py", line 138, in request
self.last_resp = self.sess.request(
~~~~~~~~~~~~~~~~~^
method,
^^^^^^^
...<3 lines>...
**kwargs,
^^^^^^^^^
)
^
File "/Users/xxx/PycharmProjects/workouts_page/venv313/lib/python3.13/site-packages/requests/sessions.py", line 589, in request
resp = self.send(prep, **send_kwargs)
File "/Users/xxx/PycharmProjects/workouts_page/venv313/lib/python3.13/site-packages/requests/sessions.py", line 703, in send
r = adapter.send(request, **kwargs)
File "/Users/xxx/PycharmProjects/workouts_page/venv313/lib/python3.13/site-packages/requests/adapters.py", line 698, in send
raise SSLError(e, request=request)
requests.exceptions.SSLError: HTTPSConnectionPool(host='sso.garmin.cn', port=443): Max retries exceeded with url: /sso/embed?id=gauth-widget&embedWidget=true&gauthHost=https%3A%2F%2Fsso.garmin.cn%2Fsso (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1028)')))
问题扔给GPT,看看解决方法。
Round 1
GPT回复:
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1016)
这意味着Python 的 requests/urllib3 在访问 https://sso.garmin.cn 时,无法验证 SSL 证书,因为本地缺少根证书(CA 证书)。
按照提示检查 requests/certifi 版本和检查系统时间,都是正常。
pip install --upgrade requests certifi urllib3
Round 2
于是再次告诉GPT检查正常,后面怎么处理。
GPT回复:
你的 certifi
、requests
、urllib3
都是最新版,依然出现证书校验失败,说明不是包的版本问题,而是本地 Python 环境的 CA 证书链没有被正确加载。
1. 检查 CA 证书路径(核心)
在你的 Python 虚拟环境中,运行以下代码,看看 certifi
的 CA 文件路径:
import certifi
print(certifi.where())
输出的路径应该是类似:
/Users/xxx/PycharmProjects/workouts_page/venv311/lib/python3.11/site-packages/certifi/cacert.pem
2. 检查 requests 是否用到了正确的 CA 文件
再运行:
import requests
print(requests.certs.where())
输出应该和上面一致。
3. 手动测试 HTTPS 请求
在虚拟环境下,运行:
import requests
print(requests.get('https://www.baidu.com').status_code)
如果这也报 SSL 错,说明你的 Python 环境根本无法校验证书。
4. 强制指定 CA 证书(临时测试)
你可以试试这样写:
import requests
import certifi
r = requests.get('https://sso.garmin.cn', verify=certifi.where())
print(r.status_code)
如果这样能成功,说明就是证书链没被自动加载。
第1、2、3都能正常运行,第4输出类似上面的报错
Round 3
于是把结果信息和报错给GPT,接着处理。
你的测试结果非常关键:
- 访问
https://www.baidu.com
没问题(200)。 - 访问
https://sso.garmin.cn
依然报证书校验失败,即使用的是certifi.where()
。
这说明:
- 你的 Python 环境和
certifi
没问题(否则百度也会失败)。 - 问题很可能出在 Garmin 的证书链不被 certifi 信任,或者 Garmin 的证书链在中国大陆被污染/不完整。
1. 手动检查 Garmin 证书链
你可以用 openssl 检查 Garmin 的证书链:
openssl s_client -connect sso.garmin.cn:443 -showcerts -CAfile /Users/xxx/PycharmProjects/workouts_page/venv313/lib/python3.13/site-packages/certifi/cacert.pem
看下输出里的证书链是否完整,以及是否有 “Verify return code: 0 (ok)”。
在执行以上提示检查,有如下输出:
Connecting to 60.9.5.109
CONNECTED(00000005)
depth=2 C=US, O=SSL Corporation, CN=SSL.com TLS Transit ECC CA R2
verify error:num=20:unable to get local issuer certificate
verify return:1
depth=1 C=US, O=CLOUDFLARE, INC., CN=Cloudflare TLS Issuing ECC CA 1
verify return:1
depth=0 CN=sso.garmin.cn
verify return:1
---
Certificate chain
0 s:CN=sso.garmin.cn
i:C=US, O=CLOUDFLARE, INC., CN=Cloudflare TLS Issuing ECC CA 1
a:PKEY: EC, (prime256v1); sigalg: ecdsa-with-SHA256
v:NotBefore: May 30 20:35:58 2025 GMT; NotAfter: Aug 28 20:41:18 2025 GMT
-----BEGIN CERTIFICATE-----
MIID4zCCA4mgAwIBAgIQYr/luWpLXqfeS6LtCXrAcTAKBggqhkjOPQQDAjBSMQsw
CQYDVQQGEwJVUzEZMBcGA1UECgwQQ0xPVURGTEFSRSwgSU5DLjEoMCYGA1UEAwwf
Q2xvdWRmbGFyZSBUTFMgSXNzdWluZyBFQ0MgQ0EgMTAeFw0yNTA1MzAyMDM1NTha
Fw0yNTA4MjgyMDQxMThaMBgxFjAUBgNVBAMMDXNzby5nYXJtaW4uY24wWTATBgcq
hkjOPQIBBggqhkjOPQMBBwNCAAQHMqqkpF8gWRUi70UdkQOGg1vVvaNTWJQVAy1R
6gsAi2QtTLWg8DbIVFcLKroKB6td9KLy1SQQbTvbaF9fdU4no4ICeTCCAnUwDAYD
VR0TAQH/BAIwADAfBgNVHSMEGDAWgBScxAlyRxgXe6caibOSNdXhA4z+kjBsBggr
BgEFBQcBAQRgMF4wOQYIKwYBBQUHMAKGLWh0dHA6Ly9pLmNmLWIuc3NsLmNvbS9D
bG91ZGZsYXJlLVRMUy1JLUUxLmNlcjAhBggrBgEFBQcwAYYVaHR0cDovL28uY2Yt
Yi5zc2wuY29tMCkGA1UdEQQiMCCCDXNzby5nYXJtaW4uY26CDyouc3NvLmdhcm1p
bi5jbjAjBgNVHSAEHDAaMAgGBmeBDAECATAOBgwrBgEEAYKpMAEDAQEwHQYDVR0l
BBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMD4GA1UdHwQ3MDUwM6AxoC+GLWh0dHA6
Ly9jLmNmLWIuc3NsLmNvbS9DbG91ZGZsYXJlLVRMUy1JLUUxLmNybDAOBgNVHQ8B
Af8EBAMCB4AwDwYJKwYBBAGC2kssBAIFADCCAQQGCisGAQQB1nkCBAIEgfUEgfIA
8AB3AO08S9boBsKkogBX28sk4jgB31Ev7cSGxXAPIN23Pj/gAAABlyLxxpUAAAQD
AEgwRgIhANSfg4eJjx1RKc0zG3yN54SpDBvpOx46Jrafvv5HPmqvAiEA7a5n5u00
K7ijQIhTytgqGqdeK7WVbhYbx4I19SAc2fAAdQDM+w9qhXEJZf6Vm1PO6bJ8IumF
XA2XjbapflTA/kwNsAAAAZci8catAAAEAwBGMEQCIBfkAwShefUxMPkEaHyc/msz
sPSFFr9UOajTpSn+7Z3XAiBO3rvSeq3kbv+PDiWXX+QD8qp27PTy5Uz4601Z7pIO
kTAKBggqhkjOPQQDAgNIADBFAiBH6Tk0U4UuJtKzBkqGPqSjldg3DPRIXNcd5OR/
Ju8LSQIhAP3fpefdqffqldZ+wvLnxaNgMjYy6GyNJCP/P29PbtcI
-----END CERTIFICATE-----
1 s:C=US, O=CLOUDFLARE, INC., CN=Cloudflare TLS Issuing ECC CA 1
i:C=US, O=SSL Corporation, CN=SSL.com TLS Transit ECC CA R2
a:PKEY: EC, (prime256v1); sigalg: ecdsa-with-SHA384
v:NotBefore: Oct 31 17:17:49 2023 GMT; NotAfter: Oct 28 17:17:48 2033 GMT
-----BEGIN CERTIFICATE-----
MIIC5DCCAmqgAwIBAgIQLD+iaS9BE707f+W2BLSdTTAKBggqhkjOPQQDAzBPMQsw
CQYDVQQGEwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSYwJAYDVQQDDB1T
U0wuY29tIFRMUyBUcmFuc2l0IEVDQyBDQSBSMjAeFw0yMzEwMzExNzE3NDlaFw0z
MzEwMjgxNzE3NDhaMFIxCzAJBgNVBAYTAlVTMRkwFwYDVQQKDBBDTE9VREZMQVJF
LCBJTkMuMSgwJgYDVQQDDB9DbG91ZGZsYXJlIFRMUyBJc3N1aW5nIEVDQyBDQSAx
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEByHHIHytNSzTS+F3JA7hHMDGd2cp
cY9i3MLTKmE6DJTKc6JwvW50pwKodvd2Qj4RAAy2jSejsVgw5jeh6syt3KOCASMw
ggEfMBIGA1UdEwEB/wQIMAYBAf8CAQAwHwYDVR0jBBgwFoAUMqLH2FiL/3/APPJV
aTPszswfvJcwSAYIKwYBBQUHAQEEPDA6MDgGCCsGAQUFBzAChixodHRwOi8vY2Vy
dC5zc2wuY29tL1NTTC5jb20tVExTLVQtRUNDLVIyLmNlcjARBgNVHSAECjAIMAYG
BFUdIAAwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMD0GA1UdHwQ2MDQw
MqAwoC6GLGh0dHA6Ly9jcmxzLnNzbC5jb20vU1NMLmNvbS1UTFMtVC1FQ0MtUjIu
Y3JsMB0GA1UdDgQWBBScxAlyRxgXe6caibOSNdXhA4z+kjAOBgNVHQ8BAf8EBAMC
AYYwCgYIKoZIzj0EAwMDaAAwZQIxAL0Sk3RweR6uG1aSHF3JgHQptubP9xoZyUmz
HSa+SSdY5wTGSx5qAowrLPCpLio2PAIwXQGgYzf5QzD/1Bsu87WrUcIVtLixr5KQ
wKBaFAyIJ7OOiWgW0HV/NA1UeuSe0zmN
-----END CERTIFICATE-----
2 s:C=US, O=SSL Corporation, CN=SSL.com TLS Transit ECC CA R2
i:C=GB, ST=Greater Manchester, L=Salford, O=Comodo CA Limited, CN=AAA Certificate Services
a:PKEY: EC, (secp384r1); sigalg: sha256WithRSAEncryption
v:NotBefore: Jun 21 00:00:00 2024 GMT; NotAfter: Dec 31 23:59:59 2028 GMT
-----BEGIN CERTIFICATE-----
MIID0DCCArigAwIBAgIRAK2NLfZGgaDTZEfqqU+ic8EwDQYJKoZIhvcNAQELBQAw
ezELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
A1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNV
BAMMGEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczAeFw0yNDA2MjEwMDAwMDBaFw0y
ODEyMzEyMzU5NTlaME8xCzAJBgNVBAYTAlVTMRgwFgYDVQQKDA9TU0wgQ29ycG9y
YXRpb24xJjAkBgNVBAMMHVNTTC5jb20gVExTIFRyYW5zaXQgRUNDIENBIFIyMHYw
EAYHKoZIzj0CAQYFK4EEACIDYgAEZOd9mQNTXJEe6vjYI62hvyziY4nvKGj27dfw
7Ktorncr5HaXG1Dr21koLW+4NrmrjZfKTCKe7onZAj/9enM6kI0rzC86N4PaDbQt
RRtzcgllX3ghPeeLZj9H/Qkp1hQPo4IBJzCCASMwHwYDVR0jBBgwFoAUoBEKIz6W
8Qfs4q8p74Klf9AwpLQwHQYDVR0OBBYEFDKix9hYi/9/wDzyVWkz7M7MH7yXMA4G
A1UdDwEB/wQEAwIBhjASBgNVHRMBAf8ECDAGAQH/AgEBMB0GA1UdJQQWMBQGCCsG
AQUFBwMBBggrBgEFBQcDAjAjBgNVHSAEHDAaMAgGBmeBDAECATAOBgwrBgEEAYKp
MAEDAQEwQwYDVR0fBDwwOjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20v
QUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNAYIKwYBBQUHAQEEKDAmMCQGCCsG
AQUFBzABhhhodHRwOi8vb2NzcC5jb21vZG9jYS5jb20wDQYJKoZIhvcNAQELBQAD
ggEBAB4oL4ChKaKGZVZK8uAXjj8wvFdm45uvhU/t14QeH5bwETeKiQQXBga4/Nyz
zvpfuoEycantX+tHl/muwpmuHT0Z6IKYoICaMxOIktcTF4qHvxQW2WItHjOglrTj
qlXJXVL+3HCO60TEloSX8eUGsqfLQkc//z3Lb4gz117+fkDbnPt8+2REq3SCvaAG
hlh/lWWfHqTAiHed/qqzBSYqqvfjNlhIfXnPnhfAv/PpOUO1PmxCEAEYrg+VoS+O
+EBd1zkT0V7CfrPpj30cAMs2h+k4pPMwcLuB3Ku4TncBTRyt5K0gbJ3pQ0Rk9Hmu
wOz5QAZ+2n1q4TlApJzBfwFrCDg=
-----END CERTIFICATE-----
---
Server certificate
subject=CN=sso.garmin.cn
issuer=C=US, O=CLOUDFLARE, INC., CN=Cloudflare TLS Issuing ECC CA 1
---
No client certificate CA names sent
Peer signing digest: SHA256
Peer signature type: ecdsa_secp256r1_sha256
Negotiated TLS1.3 group: X25519MLKEM768
---
SSL handshake has read 4130 bytes and written 1626 bytes
Verification error: unable to get local issuer certificate
---
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Protocol: TLSv1.3
Server public key is 256 bit
This TLS version forbids renegotiation.
Compression: NONE
Expansion: NONE
No ALPN negotiated
Early data was not sent
Verify return code: 20 (unable to get local issuer certificate)
Round 4
把输出结果再次给GPT,进一步分析
你的 openssl s_client
输出非常有价值,可以确认问题点:
1. 证书链分析
你拿到的完整证书链如下:
站点证书:CN=sso.garmin.cn
- 颁发者:Cloudflare TLS Issuing ECC CA 1
中间证书:CN=Cloudflare TLS Issuing ECC CA 1
- 颁发者:SSL.com TLS Transit ECC CA R2
根证书:CN=SSL.com TLS Transit ECC CA R2
- 颁发者:AAA Certificate Services(Comodo CA Limited)
2. openssl 校验失败的原因
verify error:num=20:unable to get local issuer certificate
这说明本地缺少信任链中的某个根证书。
通常是SSL.com TLS Transit ECC CA R2这个根证书没有被 certifi 收录或 certifi 版本太新/太旧。
3. certifi 是否包含这个根证书?
你可以这样查找:
grep "SSL.com TLS Transit ECC CA R2" $(python -c "import certifi; print(certifi.where())")
如果没有输出,说明 certifi 没有收录这个根证书。
4. 解决方案
A. 手动追加根证书到 certifi
复制 openssl 输出中的
-----BEGIN CERTIFICATE----- ...(SSL.com TLS Transit ECC CA R2的内容)... -----END CERTIFICATE-----
也就是最后一个证书块(
depth=2
那个)。追加到
certifi.where()
路径下的cacert.pem
文件末尾。再试你的 requests 代码。
于是到https://www.ssl.com/repository/找到对应的PEM放到cacert.pem
文件末尾,运行获取Garmin密钥的脚本,还是报错。
Round 5
再来~
你的分析和操作都非常细致,但问题依然没有解决,说明有几个点需要进一步排查。我们来一步步定位:
1. 你追加的根证书内容没问题
你追加的这段证书确实就是 SSL.com TLS Transit ECC CA R2
。这一步是对的。
2. 为什么还是出现“unable to get issuer certificate”?
可能的原因有:
追加位置不对
证书必须追加到cacert.pem
文件的末尾,且不能有多余的空格或字符。格式不对
PEM 格式必须严格正确,不能有多余的换行或空格。Python、requests、certifi实际用到的文件不是你改的那个
你可以用如下命令确认requests实际用到的 cacert.pem 路径:import certifi print(certifi.where())
你已经做了这一步,路径是
/Users/xxx/PycharmProjects/workouts_page/venv311/lib/python3.11/site-packages/certifi/cacert.pem
你确认就是改的这个文件吗?(有多个虚拟环境时很容易搞错)
链条还缺上一级根? 你的
SSL.com TLS Transit ECC CA R2
的颁发者是AAA Certificate Services
,而不是自签名。这意味着它不是最顶级根证书,还需要再加上AAA Certificate Services
的根证书!
3. 解决办法
检查是否还需要追加“AAA Certificate Services”根证书
你的 SSL.com TLS Transit ECC CA R2
的颁发者是:
CN=AAA Certificate Services
这说明它不是自签名根证书,还需要上一层的根证书才完整。
于是把AAA Certificate Services
放到 cacert.pem
文件末尾。
到此终于可以正常获取Garmin Connect账号的密钥了。
再看看openssl 检查 Garmin 的证书链:
openssl s_client -connect sso.garmin.cn:443 -showcerts -CAfile /Users/xxx/PycharmProjects/workouts_page/venv313/lib/python3.13/site-packages/certifi/cacert.pem
正常的结果是这样的:
Connecting to 60.9.5.109
CONNECTED(00000005)
depth=3 C=GB, ST=Greater Manchester, L=Salford, O=Comodo CA Limited, CN=AAA Certificate Services
verify return:1
depth=2 C=US, O=SSL Corporation, CN=SSL.com TLS Transit ECC CA R2
verify return:1
depth=1 C=US, O=CLOUDFLARE, INC., CN=Cloudflare TLS Issuing ECC CA 1
verify return:1
depth=0 CN=sso.garmin.cn
verify return:1
---
Certificate chain
0 s:CN=sso.garmin.cn
i:C=US, O=CLOUDFLARE, INC., CN=Cloudflare TLS Issuing ECC CA 1
a:PKEY: EC, (prime256v1); sigalg: ecdsa-with-SHA256
v:NotBefore: May 30 20:35:58 2025 GMT; NotAfter: Aug 28 20:41:18 2025 GMT
-----BEGIN CERTIFICATE-----
MIID4zCCA4mgAwIBAgIQYr/luWpLXqfeS6LtCXrAcTAKBggqhkjOPQQDAjBSMQsw
CQYDVQQGEwJVUzEZMBcGA1UECgwQQ0xPVURGTEFSRSwgSU5DLjEoMCYGA1UEAwwf
Q2xvdWRmbGFyZSBUTFMgSXNzdWluZyBFQ0MgQ0EgMTAeFw0yNTA1MzAyMDM1NTha
Fw0yNTA4MjgyMDQxMThaMBgxFjAUBgNVBAMMDXNzby5nYXJtaW4uY24wWTATBgcq
hkjOPQIBBggqhkjOPQMBBwNCAAQHMqqkpF8gWRUi70UdkQOGg1vVvaNTWJQVAy1R
6gsAi2QtTLWg8DbIVFcLKroKB6td9KLy1SQQbTvbaF9fdU4no4ICeTCCAnUwDAYD
VR0TAQH/BAIwADAfBgNVHSMEGDAWgBScxAlyRxgXe6caibOSNdXhA4z+kjBsBggr
BgEFBQcBAQRgMF4wOQYIKwYBBQUHMAKGLWh0dHA6Ly9pLmNmLWIuc3NsLmNvbS9D
bG91ZGZsYXJlLVRMUy1JLUUxLmNlcjAhBggrBgEFBQcwAYYVaHR0cDovL28uY2Yt
Yi5zc2wuY29tMCkGA1UdEQQiMCCCDXNzby5nYXJtaW4uY26CDyouc3NvLmdhcm1p
bi5jbjAjBgNVHSAEHDAaMAgGBmeBDAECATAOBgwrBgEEAYKpMAEDAQEwHQYDVR0l
BBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMD4GA1UdHwQ3MDUwM6AxoC+GLWh0dHA6
Ly9jLmNmLWIuc3NsLmNvbS9DbG91ZGZsYXJlLVRMUy1JLUUxLmNybDAOBgNVHQ8B
Af8EBAMCB4AwDwYJKwYBBAGC2kssBAIFADCCAQQGCisGAQQB1nkCBAIEgfUEgfIA
8AB3AO08S9boBsKkogBX28sk4jgB31Ev7cSGxXAPIN23Pj/gAAABlyLxxpUAAAQD
AEgwRgIhANSfg4eJjx1RKc0zG3yN54SpDBvpOx46Jrafvv5HPmqvAiEA7a5n5u00
K7ijQIhTytgqGqdeK7WVbhYbx4I19SAc2fAAdQDM+w9qhXEJZf6Vm1PO6bJ8IumF
XA2XjbapflTA/kwNsAAAAZci8catAAAEAwBGMEQCIBfkAwShefUxMPkEaHyc/msz
sPSFFr9UOajTpSn+7Z3XAiBO3rvSeq3kbv+PDiWXX+QD8qp27PTy5Uz4601Z7pIO
kTAKBggqhkjOPQQDAgNIADBFAiBH6Tk0U4UuJtKzBkqGPqSjldg3DPRIXNcd5OR/
Ju8LSQIhAP3fpefdqffqldZ+wvLnxaNgMjYy6GyNJCP/P29PbtcI
-----END CERTIFICATE-----
1 s:C=US, O=CLOUDFLARE, INC., CN=Cloudflare TLS Issuing ECC CA 1
i:C=US, O=SSL Corporation, CN=SSL.com TLS Transit ECC CA R2
a:PKEY: EC, (prime256v1); sigalg: ecdsa-with-SHA384
v:NotBefore: Oct 31 17:17:49 2023 GMT; NotAfter: Oct 28 17:17:48 2033 GMT
-----BEGIN CERTIFICATE-----
MIIC5DCCAmqgAwIBAgIQLD+iaS9BE707f+W2BLSdTTAKBggqhkjOPQQDAzBPMQsw
CQYDVQQGEwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSYwJAYDVQQDDB1T
U0wuY29tIFRMUyBUcmFuc2l0IEVDQyBDQSBSMjAeFw0yMzEwMzExNzE3NDlaFw0z
MzEwMjgxNzE3NDhaMFIxCzAJBgNVBAYTAlVTMRkwFwYDVQQKDBBDTE9VREZMQVJF
LCBJTkMuMSgwJgYDVQQDDB9DbG91ZGZsYXJlIFRMUyBJc3N1aW5nIEVDQyBDQSAx
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEByHHIHytNSzTS+F3JA7hHMDGd2cp
cY9i3MLTKmE6DJTKc6JwvW50pwKodvd2Qj4RAAy2jSejsVgw5jeh6syt3KOCASMw
ggEfMBIGA1UdEwEB/wQIMAYBAf8CAQAwHwYDVR0jBBgwFoAUMqLH2FiL/3/APPJV
aTPszswfvJcwSAYIKwYBBQUHAQEEPDA6MDgGCCsGAQUFBzAChixodHRwOi8vY2Vy
dC5zc2wuY29tL1NTTC5jb20tVExTLVQtRUNDLVIyLmNlcjARBgNVHSAECjAIMAYG
BFUdIAAwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMD0GA1UdHwQ2MDQw
MqAwoC6GLGh0dHA6Ly9jcmxzLnNzbC5jb20vU1NMLmNvbS1UTFMtVC1FQ0MtUjIu
Y3JsMB0GA1UdDgQWBBScxAlyRxgXe6caibOSNdXhA4z+kjAOBgNVHQ8BAf8EBAMC
AYYwCgYIKoZIzj0EAwMDaAAwZQIxAL0Sk3RweR6uG1aSHF3JgHQptubP9xoZyUmz
HSa+SSdY5wTGSx5qAowrLPCpLio2PAIwXQGgYzf5QzD/1Bsu87WrUcIVtLixr5KQ
wKBaFAyIJ7OOiWgW0HV/NA1UeuSe0zmN
-----END CERTIFICATE-----
2 s:C=US, O=SSL Corporation, CN=SSL.com TLS Transit ECC CA R2
i:C=GB, ST=Greater Manchester, L=Salford, O=Comodo CA Limited, CN=AAA Certificate Services
a:PKEY: EC, (secp384r1); sigalg: sha256WithRSAEncryption
v:NotBefore: Jun 21 00:00:00 2024 GMT; NotAfter: Dec 31 23:59:59 2028 GMT
-----BEGIN CERTIFICATE-----
MIID0DCCArigAwIBAgIRAK2NLfZGgaDTZEfqqU+ic8EwDQYJKoZIhvcNAQELBQAw
ezELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
A1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNV
BAMMGEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczAeFw0yNDA2MjEwMDAwMDBaFw0y
ODEyMzEyMzU5NTlaME8xCzAJBgNVBAYTAlVTMRgwFgYDVQQKDA9TU0wgQ29ycG9y
YXRpb24xJjAkBgNVBAMMHVNTTC5jb20gVExTIFRyYW5zaXQgRUNDIENBIFIyMHYw
EAYHKoZIzj0CAQYFK4EEACIDYgAEZOd9mQNTXJEe6vjYI62hvyziY4nvKGj27dfw
7Ktorncr5HaXG1Dr21koLW+4NrmrjZfKTCKe7onZAj/9enM6kI0rzC86N4PaDbQt
RRtzcgllX3ghPeeLZj9H/Qkp1hQPo4IBJzCCASMwHwYDVR0jBBgwFoAUoBEKIz6W
8Qfs4q8p74Klf9AwpLQwHQYDVR0OBBYEFDKix9hYi/9/wDzyVWkz7M7MH7yXMA4G
A1UdDwEB/wQEAwIBhjASBgNVHRMBAf8ECDAGAQH/AgEBMB0GA1UdJQQWMBQGCCsG
AQUFBwMBBggrBgEFBQcDAjAjBgNVHSAEHDAaMAgGBmeBDAECATAOBgwrBgEEAYKp
MAEDAQEwQwYDVR0fBDwwOjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20v
QUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNAYIKwYBBQUHAQEEKDAmMCQGCCsG
AQUFBzABhhhodHRwOi8vb2NzcC5jb21vZG9jYS5jb20wDQYJKoZIhvcNAQELBQAD
ggEBAB4oL4ChKaKGZVZK8uAXjj8wvFdm45uvhU/t14QeH5bwETeKiQQXBga4/Nyz
zvpfuoEycantX+tHl/muwpmuHT0Z6IKYoICaMxOIktcTF4qHvxQW2WItHjOglrTj
qlXJXVL+3HCO60TEloSX8eUGsqfLQkc//z3Lb4gz117+fkDbnPt8+2REq3SCvaAG
hlh/lWWfHqTAiHed/qqzBSYqqvfjNlhIfXnPnhfAv/PpOUO1PmxCEAEYrg+VoS+O
+EBd1zkT0V7CfrPpj30cAMs2h+k4pPMwcLuB3Ku4TncBTRyt5K0gbJ3pQ0Rk9Hmu
wOz5QAZ+2n1q4TlApJzBfwFrCDg=
-----END CERTIFICATE-----
---
Server certificate
subject=CN=sso.garmin.cn
issuer=C=US, O=CLOUDFLARE, INC., CN=Cloudflare TLS Issuing ECC CA 1
---
No client certificate CA names sent
Peer signing digest: SHA256
Peer signature type: ecdsa_secp256r1_sha256
Negotiated TLS1.3 group: X25519MLKEM768
---
SSL handshake has read 4130 bytes and written 1626 bytes
Verification: OK
---
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Protocol: TLSv1.3
Server public key is 256 bit
This TLS version forbids renegotiation.
Compression: NONE
Expansion: NONE
No ALPN negotiated
Early data was not sent
Verify return code: 0 (ok)