Python 'requests'库或s_client未发送SSL客户端证书,但在网页浏览器中正常工作
我正在尝试用Python写一个客户端脚本,目的是访问一个网络应用,并使用SSL客户端证书进行身份验证。只要我在Firefox和Chrome中加载了客户端证书,就能顺利访问这个应用。但是,当我通过Python的'requests'发送请求时,却总是收到以下的响应:
400 No required SSL certificate was sent
nginx/1.4.6 (Ubuntu)
我还尝试过Python的httplib、s_client和curl,结果也都是同样的错误信息。我在所有测试中使用的是相同的客户端证书,浏览器用的是pkcs12格式,而命令行工具则是提取成证书和密钥的PEM文件。我的Python代码看起来是这样的:
import requests
CERT = r'/path/to/cert.crt' #Client certificate
KEY = r'/path/to/key.key' #Client private key
CACERT = r'/path/to/ca.crt' #Server certificate chain
session = requests.Session()
session.cert = (CERT, KEY)
resp = session.get('https://my.webapp.com/',
verify=CACERT)
print resp.content
session.close()
s_client提供了更多关于发生了什么的信息。以下是输出的简化版本:
$ openssl s_client -cert cert.crt -key key.key -CAfile ca.crt -connect <host>:<port>
CONNECTED(00000003)
depth=2 /C=US/O=GeoTrust Inc./CN=GeoTrust Global CA
verify return:1
depth=1 /C=US/O=GeoTrust, Inc./CN=RapidSSL CA
verify return:1
depth=0 /serialNumber=tgBIwyM-p18O/aDyvyWNKHDnOezzDJag/OU=GT89519184/OU=See www.rapidssl.com/resources/cps (c)13/OU=Domain Control Validated - RapidSSL(R)/CN=*.rexdb.us
verify return:1
---
Certificate chain
...
---
Server certificate
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
subject=...
---
No client certificate CA names sent
---
SSL handshake has read 3519 bytes and written 328 bytes
---
New, TLSv1/SSLv3, Cipher is DHE-RSA-AES128-SHA
Server public key is 4096 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
SSL-Session:
Protocol : TLSv1
Cipher : DHE-RSA-AES128-SHA
Session-ID: ...
Session-ID-ctx:
Master-Key: ...
Key-Arg : None
Start Time: 1409269239
Timeout : 300 (sec)
Verify return code: 0 (ok)
---
GET / HTTP/1.1
Host: my.webapp.com
HTTP/1.1 400 Bad Request
Server: nginx/1.4.6 (Ubuntu)
Date: Thu, 28 Aug 2014 23:41:04 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Connection: close
...
<head><title>400 No required SSL certificate was sent</title></head>
...
closed
我相当确定这不是服务器的问题,因为在浏览器中身份验证是可以正常工作的。然而,s_client的输出显示“没有发送客户端证书CA名称”,这听起来像是服务器的问题(例如,客户端证书没有被发送到服务器,因为服务器没有请求它)。以下是nginx配置的相关部分:
ssl on;
ssl_certificate_key /path/to/server/key.pem;
ssl_certificate /path/to/server/certificate.pem;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:ECDHE-RSA-RC4-SHA:ECDHE-ECDSA-RC4-SHA:AES128:AES256:RC4-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!3DES:!MD5:!PSK;
ssl_prefer_server_ciphers on;
ssl_session_timeout 5m;
ssl_client_certificate /path/to/client/ca/certificate.crt;
ssl_verify_depth 10;
ssl_verify_client on;
这个应用通过nginx使用uwsgi。虚拟机上有多个虚拟主机,只有这一台使用了证书身份验证。
我能找到的唯一其他可能相关的区别是,在Firefox中连接是保持活跃的,而在s_client中则是关闭的。我尝试在头部设置Connection: keep-alive,但结果还是一样。
1 个回答
虚拟机上有多个虚拟主机,只有这一台使用证书认证。
我猜这意味着你在同一个IP地址后面有多个证书,客户端需要使用SNI(服务器名称指示)来在SSL握手过程中告诉服务器它期望的主机名。openssl s_client
默认情况下不使用SNI,我不确定Python是否支持这项功能,这可能取决于你使用的Python版本。
因为客户端只有在服务器要求时才会发送证书,所以如果缺少SNI,你可能会遇到错误的配置部分,也就是默认的部分,它使用了另一个证书,并且不需要客户端证书。
我建议你再试一次使用openssl s_client
,但这次加上命令行选项-servername
(在手册中没有记录,但如果用-h
调用会显示)。这样可以明确设置期望的服务器名称。如果这样有效,你需要找到在Python中使用SNI的方法。如果这样不行,请做一个数据包捕获,并用Wireshark确认服务器确实要求客户端发送证书,而客户端确实没有发送证书。