双向TLS认证 - SSLV3_ALERT_UNSUPPORTED_CERTIFICATE

3
我目前正在尝试在客户端和服务器之间实现双向传输层安全(mutual TLS)认证。我遇到了一个SSL错误,但是错误信息并不是很明确。由于大多数情况下互联网上只使用单向传输层安全(one-way TLS),因此StackOverflow上也没有太多相关问题。然而,从我所见,这个错误发生的原因是客户端证书存在问题,因此我在下面附上了与此相关的信息。如果这不是问题的原因,请告诉我。

生成客户端证书

我的客户端配置文件(foo.config)如下(替换了所有敏感信息):

[ req ]
default_bits = 2048
default_md = sha256
prompt = no
req_extensions = req_ext
distinguished_name = dn
encrypt_key = no

[ dn ]
O=Bar
OU=User
CN=myuser@foo.com

[ req_ext ]
subjectAltName = email:myuser@foo.com
nsCertType = client, email, objsign
keyUsage = nonRepudiation, digitalSignature, keyEncipherment

我使用openssl来传递这个配置文件以创建csr:

openssl req -new -sha256 -key foo.key -out foo.csr -config foo.config

然后我将其发送到内部基础设施管理API以签署x509证书。 我将客户端证书保存为foo.cert(其中包含直至CA的信任链)。

我的客户端如何发送证书

当我向服务器发送urllib打开请求时,我通过我的HTTPSConnection发送ssl上下文:

... # Inside some handler code
context = ssl.create_default_context(purpose=ssl.Purpose.SERVER_AUTH,
            cafile=ROOT_CA)
context.load_cert_chain(certfile=CERT_DEST, keyfile=KEY_DEST)
context.verify_mode = ssl.CERT_REQUIRED
context.check_hostname = True
return http.client.HTTPSConnection(host, context=context)

我的Flask服务器如何设置

当我的测试Flask服务器接收SSL上下文如下:

executor = Flask(__name__)
context = ssl.create_default_context(purpose=ssl.Purpose.CLIENT_AUTH, cafile=TEST_CA_CERT)
context.load_cert_chain(certfile=TEST_CERT, keyfile=TEST_KEY)
context.verify_mode = ssl.CERT_REQUIRED
context.check_hostname = False
executor.run(host=host, port=port, ssl_context=context)

它设置了自己的证书和密钥文件。它还设置了客户端身份验证请求。

完整错误回溯

在运行时,在调用期间,我得到了这个回溯(替换了所有敏感信息):

Traceback (most recent call last):
  File "/auto/foo/python3-rhel6/lib/python3.6/urllib/request.py", line 1318, in do_open
    encode_chunked=req.has_header('Transfer-encoding'))
  File "/auto/foo/python3-rhel6/lib/python3.6/http/client.py", line 1239, in request
    self._send_request(method, url, body, headers, encode_chunked)
  File "/auto/foo/python3-rhel6/lib/python3.6/http/client.py", line 1285, in _send_request
    self.endheaders(body, encode_chunked=encode_chunked)
  File "/auto/foo/python3-rhel6/lib/python3.6/http/client.py", line 1234, in endheaders
    self._send_output(message_body, encode_chunked=encode_chunked)
  File "/auto/foo/python3-rhel6/lib/python3.6/http/client.py", line 1026, in _send_output
    self.send(msg)
  File "/auto/foo/python3-rhel6/lib/python3.6/http/client.py", line 964, in send
    self.connect()
  File "/auto/foo/python3-rhel6/lib/python3.6/http/client.py", line 1400, in connect
    server_hostname=server_hostname)
  File "/auto/foo/python3-rhel6/lib/python3.6/ssl.py", line 407, in wrap_socket
    _context=self, _session=session)
  File "/auto/foo/python3-rhel6/lib/python3.6/ssl.py", line 814, in __init__
    self.do_handshake()
  File "/auto/foo/python3-rhel6/lib/python3.6/ssl.py", line 1068, in do_handshake
    self._sslobj.do_handshake()
  File "/auto/foo/python3-rhel6/lib/python3.6/ssl.py", line 689, in do_handshake
    self._sslobj.do_handshake()
ssl.SSLError: [SSL: SSLV3_ALERT_UNSUPPORTED_CERTIFICATE] sslv3 alert unsupported certificate (_ssl.c:833)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "./bar.py", line 375, in <module>
    start(args, overrides, threadlock)
  File "./bar.py", line 332, in start
    approved, alloc_message = Executor.approve(flow.the_portal.inst)
  File "/home/myuser/dev/bar/Executor.py", line 638, in approve
    with opener.open(request, json_request) as response:
  File "/auto/foo/python3-rhel6/lib/python3.6/urllib/request.py", line 526, in open
    response = self._open(req, data)
  File "/auto/foo/python3-rhel6/lib/python3.6/urllib/request.py", line 544, in _open
    '_open', req)
  File "/auto/foo/python3-rhel6/lib/python3.6/urllib/request.py", line 504, in _call_chain
    result = func(*args)
  File "/home/myuser/dev/bar/Auth.py", line 77, in https_open
    return self.do_open(self.getConnection, req)
  File "/auto/foo/python3-rhel6/lib/python3.6/urllib/request.py", line 1320, in do_open
    raise URLError(err)
urllib.error.URLError: <urlopen error [SSL: SSLV3_ALERT_UNSUPPORTED_CERTIFICATE] sslv3 alert unsupported certificate (_ssl.c:833)>

如果需要更多信息,请不要犹豫,让我知道。任何帮助都将不胜感激。
编辑: 这是证书(已经打码):
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            ...
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: DC=com, DC=foo, CN=Foo Issuing CA - XXX
        Validity
            Not Before: ... 2018 GMT
            Not After : ... GMT
        Subject: O=Bar, OU=User, CN=myuser@foo.com
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                ...
        X509v3 extensions:
            X509v3 Key Usage:
                Key Encipherment, Data Encipherment
            X509v3 Subject Alternative Name:
                email:myuser@foo.com
            X509v3 Subject Key Identifier:
                ...
            X509v3 Authority Key Identifier:
                ...

            X509v3 CRL Distribution Points:

                Full Name:
                    ...
            Authority Information Access:
                CA Issuers - ...
                CA Issuers - ...

            X509v3 Extended Key Usage:
                TLS Web Server Authentication, TLS Web Client Authentication

使用我的证书捆绑包验证此文件的结果为:

openssl verify -CAfile /.../certification_bundle.pem foo.cert
foo.cert: OK

我正在使用OpenSSL v.1.0.1e-fips。这个版本应该支持TLSv1.2。

CSR的相关性比实际证书要小,因为CA无需使证书看起来像您所请求的那样。您能否在您的问题中包含实际的证书(即从openssl x509 -text -in foo.cert 输出的结果,也许需要屏蔽一些细节)? - Steffen Ullrich
@SteffenUllrich 我稍后会更新帖子并附上证书细节(部分已打码)。请耐心等待。 - OneRaynyDay
@SteffenUllrich 我已经包含了一个已编辑的证书。 - OneRaynyDay
2个回答

4
       X509v3 Key Usage:
            Key Encipherment, Data Encipherment
客户端证书是通过客户端签署某些挑战并由服务器验证签名来验证的。为了签署此挑战,证书必须具有数字签名的密钥用途。您的证书没有这个密钥用途,这使得它无法用于客户端身份验证。
另请参见客户端证书建议的密钥用途
有趣的是,您的CSR可能包含了适当的密钥用途,至少您已经尝试添加它:
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
因此,CA可能以错误的方式颁发了证书,即与您请求的方式不同。

你好,有没有办法禁用 digitalSignature 字段的使用?我获得了一个不包含该字段的客户端证书。如果没有,那没关系。谢谢解释,我几乎可以确定这就是问题所在,但我无法获得带有正确密钥用途的证书来重现解决方案。 - OneRaynyDay
客户端证书的验证是深入 OpenSSL 代码中的。我认为您无法从 Python 中更改此行为,而且这也不是一个好主意。另外,看起来您已经正确请求了证书,但 CA 没有给您想要的密钥用途(请参见编辑后的答案)。 - Steffen Ullrich
是的,我联系了CA,他们说我无法制作具有正确字段的客户端证书。因此,CA没有尊重我的扩展字段。不幸的是,这意味着我们可能无法使用此CA来制作客户端证书。 - OneRaynyDay

1
抱歉,我不能发表评论,但是看起来您的证书签名请求缺少扩展密钥用途(EKU)扩展。您可能需要添加:
extendedKeyUsage = clientAuth

请进入req_ext部分。

然后重新生成CSR,并使用openssl req -in foo.csr -text进行验证,确保签名请求包含EKU扩展:

...
        X509v3 Extended Key Usage:
            TLS Web Client Authentication
...

我已经在我的证书中添加了“TLS Web客户端身份验证”字段。(我刚刚添加它) - OneRaynyDay

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接