在PyOpenSSL中验证客户端证书

16

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

   set_verify(mode, callback)
      将此上下文对象的验证标志设置为mode,并指定应将callback用于验证回调。
      mode应为VERIFY_NONE和VERIFY_PEER之一。如果使用VERIFY_PEER,则可以使用
      VERIFY_FAIL_IF_NO_PEER_CERT和VERIFY_CLIENT_ONCE与mode进行OR:运算,以进一步控制行为。
      回调函数应该接受五个参数:连接对象、X509对象和三个整数变量,依次为潜在的错误号码、
      错误深度和返回代码。如果验证通过,则回调应返回true;否则返回false。

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

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方面的完全无能、幼稚和/或基本缺乏理解,请不要太苛刻!

谢谢:)

Roger

1个回答

5
OpenSSL 文档set_verify() 中,你需要关心的键是返回代码

callback 应该带有五个参数:一个 Connection 对象,一个 X509 对象和三个整数变量,这些变量依次是潜在错误编号、错误深度和返回代码。如果验证通过,则 callback 应该返回 true,否则返回 false。

还有一个完整的工作示例,基本上展示了你想要做的事情: 客户端证书何时验证?

本质上,你可以忽略前 4 个参数,只需检查第五个参数中的返回代码的值,就像这样:

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.前缀。


1
在这个例子中,实际上没有进行任何验证。事实上,在这个例子中,您可以完全省略 set_verify 并具有完全相同的安全特性。问题是 ok 实际上是预验证的“返回代码”(即 VERIFY_PEER | VERIFY_FAIL_IF_NO_PEER_CERT | VERIFY_CLIENT_ONCE 的结果),但是在回调中没有实现客户端证书验证 - 它只是原样返回。如果您不尝试实际实现客户端身份验证验证,则可以只执行 ctx.verify_mode 而不是 ctx.set_verify - Stof
@stof 如果我理解你的意思,那么请用 ctx.verify_mode = ssl.CERT_NONE 替换 ctx.setverify(...)(并删除不再需要的 verify_callback()),正如这个答案中所指出的:https://dev59.com/L2Ik5IYBdhLWcg3wdd0l#28048260 - aculich
正确的做法是按照所述进行操作,如果您没有尝试实际实现客户端身份验证验证,则我可以为您编写一个SO答案。如果您更清楚地了解所需的安全结果,那么我可以为您提供更好的帮助。目前似乎您想要进行客户端验证,但又不想有任何安全措施,即不会因验证失败而断开连接。这非常矛盾,无法回答。 - Stof

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