源起
在Windows 10系统中安装 Python 3.11.5版本(目前最新版)并安装模块 (比如flask),安装步骤很简单:
- 到官方下载安装档https://www.python.org/downloads/
- 点击安装文件安装Python
- 到命令行执行
pip install packagename
安装扩展的模块
在一般的环境下, 这都没什么问题,到时在企业内部环境中, https的根证书是自行颁布的,虽然在浏览器中访问这个地址正常,但是使用 pip install 命令就是无法安装, 报的错误就是证书不对, 握手失败,无法建立连接, 完整的错误信息如下:
WARNING: Retrying (Retry(total=4, connect=None, read=None, redirect=None, status=None)) after connection broken by 'SSLError(SSLError(1, '[SSL: UNSAFE_LEGACY_RENEGOTIATION_DISABLED] unsafe legacy renegotiation disabled (_ssl.c:1006)'))': /simple/flask/
WARNING: Retrying (Retry(total=3, connect=None, read=None, redirect=None, status=None)) after connection broken by 'SSLError(SSLError(1, '[SSL: UNSAFE_LEGACY_RENEGOTIATION_DISABLED] unsafe legacy renegotiation disabled (_ssl.c:1006)'))': /simple/flask/
WARNING: Retrying (Retry(total=2, connect=None, read=None, redirect=None, status=None)) after connection broken by 'SSLError(SSLError(1, '[SSL: UNSAFE_LEGACY_RENEGOTIATION_DISABLED] unsafe legacy renegotiation disabled (_ssl.c:1006)'))': /simple/flask/
WARNING: Retrying (Retry(total=1, connect=None, read=None, redirect=None, status=None)) after connection broken by 'SSLError(SSLError(1, '[SSL: UNSAFE_LEGACY_RENEGOTIATION_DISABLED] unsafe legacy renegotiation disabled (_ssl.c:1006)'))': /simple/flask/
WARNING: Retrying (Retry(total=0, connect=None, read=None, redirect=None, status=None)) after connection broken by 'SSLError(SSLError(1, '[SSL: UNSAFE_LEGACY_RENEGOTIATION_DISABLED] unsafe legacy renegotiation disabled (_ssl.c:1006)'))': /simple/flask/
Could not fetch URL https://pypi.org/simple/flask/: There was a problem confirming the ssl certificate: HTTPSConnectionPool(host='pypi.org', port=443): Max retries exceeded with url: /simple/flask/ (Caused by SSLError(SSLError(1, '[SSL: UNSAFE_LEGACY_RENEGOTIATION_DISABLED] unsafe legacy renegotiation disabled (_ssl.c:1006)'))) - skipping
ERROR: Could not find a version that satisfies the requirement flask (from versions: none)
ERROR: No matching distribution found for flask
Could not fetch URL https://pypi.org/simple/pip/: There was a problem confirming the ssl certificate: HTTPSConnectionPool(host='pypi.org', port=443): Max retries exceeded with url: /simple/pip/ (Caused by SSLError(SSLError(1, '[SSL: UNSAFE_LEGACY_RENEGOTIATION_DISABLED] unsafe legacy renegotiation disabled (_ssl.c:1006)'))) - skipping
原因分析与方法探求
从上面的错误可以看到, pip 在安装扩展包的时候会从 https://pypi.org/simple/flask/ 下载对应的包的问题, 但是pip 访问https站点的时候又没有正确的证书。
在当地机器的浏览器中https://pypi.org/simple/flask/打开这个地址, 可以正常访问。
忽略证书访问:
于是就想: 是否可以忽略证书访问呢? 找了一下,是可以通过 --trusted-host
命令选项设置, 于是使用如下命令:
pip install --trusted-host pypi.org flash
不行, 是站点不够吗?于是使用如下:
pip install --trusted-host pypi.org --trusted-host pypi.python.org --trusted-host=files.pythonhosted.org flash
还是不行, 改方式宣告失败
添加证书访问
Python有自身的证书库文件, 位于安装目录的: Lib\site-packages\certifi\cacert.pem
,所以有种思路就是把站点的证书附加到这个证书库文件中,于是到浏览器中下载证书,保存证书名为: pypi.crt。
如何在流量器下载证书, 可以参考 : 如何在浏览器中下载网站的https证书
添加的方式可以有两种:
- 使用命令行:
type pypi.crt >> Python\Python311\Lib\site-packages\certifi\cacert.pem
- 直接用记事本打开这两个文件, 把pypi.crt 的内容复制到cacert.pem 中
复制完成之后, 执行pip install flash
还是失败, 是pip 找不到这个证书库吗 ?于是分别尝试下面两种方式显示指定证书库文件
- 设置证书库文件的环境变量
set PIP_CERT=C:\Python\Python311\Lib\site-packages\certifi\cacert.pem
- 使用
--cert
命令选项指定证书:类似:pip --cert \Lib\site-packages\pip\_vendor\certifi\cacert.pem install flask
还是失败。
又猜想是不是pip 的版本原因,于是使用 pip3 又试了一轮, 还是不行。
确认证书
到这, 就有点手足无措了, 难道是内部封装的这个站点的证书有什么不一样吗? 联想到在Java开发时, 使用Maven下载远端库的时候也有相同的问题,当时的解决方法就是: 在浏览器下载证书, 导入到Java的证书库中, 之后就可以了。 于是想到把https://pypi.org/ 的证书导入到Java证书库中试试。
这里有提供一个Java类工具:
SSLPoke.class:可以验证https 的站点是否可以访问
可以到如下地址下载:
https://download.csdn.net/download/oscar999/18855774
于是先用Java测试: java SSLPoke pypi.org 443
,
果然无法访问:
sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at sun.security.validator.PKIXValidator.doBuild(Unknown Source)
at sun.security.validator.PKIXValidator.engineValidate(Unknown Source)
at sun.security.validator.Validator.validate(Unknown Source)
使用Java的keytool 工具导入 pypi站点的https证书,
导入命令如下:
keytool -import -trustcacerts -alias pypi -file pypi.cer -keystore %JAVA_HOME%/jre/lib/security/cacerts -storepass changeit
关于如何导入https到Java证书库, 可以参考:
Java如何安装https证书
导入之后, 使用Java连接成功, 如下显示:
java SSLPoke pypi.org 443
Successfully connected
操作系统的证书
到这又产生了一个想法: Windows 本 身也有证书库 ,是否导入操作系统本身的证书库文件就可以了呢。
在运行输入 certmgr.msc
打开证书库管理。
打开后的界面如下:
在这里,尝试把pypi 的证书导入了根证书以及企业信任等目录, 发现都无效, 也就是导入后, Java依旧无法访问该https站点, 同理, pip 应该也就不行。
临时方案
无奈之下,就找了一个临时方案:
- 找一台非企业颁发证书的机器下载pip 的包的文件, 文件的后缀名是 *.whl, 因为有依赖关系, 会存在多个whl 文件。下载的命令是:
pip download flask
- 将这些文件复制到目标机器的某个目录,
- 执行
pip install *.whl
安装所有的包。 但是这里会失败,因为windows 的命令行不认识*
的通配符。这里的解决方法是使用 Git 的bash 进行安装。(因为该机器安装了Git , 所以可以直接使用Git Bash, 也就是说, 在Linux系统中,pip install的命令是认识*
通配符的)。