我试图用Python编写一个客户端脚本,该脚本访问Web应用程序并使用SSL客户端证书进行身份验证。尽管只要加载了客户端证书,我就可以从Firefox和Chrome都访问该应用程序,但是每当我通过Python'requests发送请求时,我都会收到以下响应:

400 No required SSL certificate was sent
nginx/1.4.6 (Ubuntu)


我还尝试了Python httplib,s_client和curl,并获得了相同的错误消息。对于所有测试,我都使用相同的客户端证书,对于Web浏览器,其格式为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。 vm上有多个虚拟主机,只有其中一个使用证书身份验证。

我可以找到的唯一其他可能相关的区别是,在Firefox中,连接保持活动状态,而在s_client中,连接处于关闭状态。我尝试在标题中设置Connection:keep-alive,结果相同。

最佳答案

vm上有多个虚拟主机,只有其中一个使用证书身份验证。


我认为这意味着您在同一个IP地址后面有多个证书,并且客户端必须使用SNI(服务器名称指示)在SSL握手中向服务器发送期望的主机名。 openssl s_client默认情况下不使用SNI,我也不知道python是否使用-它可能取决于您使用的python版本。

由于客户端仅在服务器告知可能的情况下才发送证书,因此由于缺少SNI,您会遇到错误的配置部分,这是具有其他证书且不要求客户端证书的默认部分。

我建议再次尝试使用openssl s_client,但使用命令行选项-servername(未在手册页中记录,但如果使用-h调用则显示),以明确设置所需的服务器名称。如果可行,您还需要找到一种在python中使用SNI的方法。如果这不起作用,请进行数据包转储,并使用Wireshark确保服务器确实要求客户端发送证书,而客户端确实不发送证书。

10-07 19:30
查看更多