在PyOpenSSL中验证客户端证书

16 投票
1 回答
9861 浏览
提问于 2025-04-17 12:05

我正在写一个应用程序,需要在客户端浏览器中安装一个证书。我在PyOpenSSL的文档中找到了关于“上下文”对象的内容,但我没有看到关于回调函数如何验证证书的具体说明,只是提到它应该以某种方式进行验证。

   set_verify(mode, callback)
      Set the verification flags for this Context object to mode and
      specify that callback should be used for verification callbacks.
      mode should be one of VERIFY_NONE and VERIFY_PEER. If
      VERIFY_PEER is used, mode can be OR:ed with
      VERIFY_FAIL_IF_NO_PEER_CERT and VERIFY_CLIENT_ONCE to further
      control the behaviour. callback should take five arguments: A
      Connection object, an X509 object, and three integer variables,
      which are in turn potential error number, error depth and return
      code. callback should return true if verification passes and
      false otherwise.

我告诉上下文对象我的(自签名的)密钥在哪里(见下文),所以我想我不明白为什么这对库来说还不够,无法检查客户端提供的证书是否有效。在这个回调函数中应该做些什么呢?

class SecureAJAXServer(PlainAJAXServer):
    def __init__(self, server_address, HandlerClass):
        BaseServer.__init__(self, server_address, HandlerClass)
        ctx = SSL.Context(SSL.SSLv23_METHOD)
        ctx.use_privatekey_file ('keys/server.key')
        ctx.use_certificate_file('keys/server.crt')
        ctx.set_session_id("My_experimental_AJAX_Server")
        ctx.set_verify( SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT | SSL.VERIFY_CLIENT_ONCE, callback_func )
        self.socket = SSL.Connection(ctx, socket.socket(self.address_family, self.socket_type))
        self.server_bind()
        self.server_activate()

注意:我只是为了好玩而编程,绝对不是专业人士,所以如果我的问题暴露了我在SSL方面的无知和基础理解的缺乏,请不要太苛刻!

谢谢 :)

罗杰

1 个回答

5

OpenSSL 的文档中,关于set_verify(),你需要关注的关键点是返回码

回调函数应该接受五个参数:一个连接对象,一个 X509 对象,以及三个整数变量,这三个变量分别代表可能的错误编号、错误深度和返回码。如果验证通过,回调函数应该返回 true;否则返回 false。

这里有一个完整的示例,基本上展示了你想要做的事情:客户端证书什么时候被验证?

简单来说,你可以忽略前四个参数,只需要检查第五个参数中的返回码的值,像这样:

from OpenSSL.SSL import Context, Connection, SSLv23_METHOD
from OpenSSL.SSL import VERIFY_PEER, VERIFY_FAIL_IF_NO_PEER_CERT, VERIFY_CLIENT_ONCE

class SecureAJAXServer(BaseServer):
    def verify_callback(connection, x509, errnum, errdepth, ok):
        if not ok:
            print "Bad Certs"
        else:
            print "Certs are fine"
        return ok

    def __init__(self, server_address, HandlerClass):
        BaseServer.__init__(self, server_address, HandlerClass)
        ctx = Context(SSLv23_METHOD)
        ctx.use_privatekey_file ('keys/server.key')
        ctx.use_certificate_file('keys/server.crt')
        ctx.set_session_id("My_experimental_AJAX_Server")
        ctx.set_verify( VERIFY_PEER | VERIFY_FAIL_IF_NO_PEER_CERT | VERIFY_CLIENT_ONCE, verify_callback )
        self.socket = Connection(ctx, socket.socket(self.address_family, self.socket_type))
        self.server_bind()
        self.server_activate()

注意:我还做了一个小改动,就是把from OpenSSL.SSL import ...改成了这样,以便在测试时让你的代码更简单,这样你就不需要在每个导入的符号前加上SSL.前缀了。

撰写回答