【问题标题】:Use TLS and Python for authentication使用 TLS 和 Python 进行身份验证
【发布时间】:2015-07-21 12:15:59
【问题描述】:

我想为在 Raspberry Pi 上运行并像本地服务器一样工作的软件制作一个小更新脚本。这应该连接到网络中的主服务器以获取软件更新并验证软件的许可证。 为此,我设置了两个 python 脚本。我希望这些通过 TLS 套接字连接。然后客户端检查服务器证书,服务器检查它是否是授权客户端之一。我在this page 上使用twisted 找到了解决方案。

现在还有一个问题。我想知道哪个客户端(取决于证书)正在建立连接。有没有办法在 Python 3 中使用 twisted 做到这一点?

我对每个答案都很满意。

【问题讨论】:

    标签: python authentication ssl twisted


    【解决方案1】:

    一句话:是的,这是完全可能的,所有必要的东西都是 移植到 python 3 - 我在我的 Mac 上测试了 Python 3.4 下的所有以下内容,它似乎 工作正常。

    简短的回答是 "use twisted.internet.ssl.Certificate.peerFromTransport" 但鉴于需要进行大量设置才能达到目标 可能,我已经构建了一个完整的工作示例,您应该能够 尝试并以此为基础。

    为了后代,您首先需要生成一些客户端证书 由同一 CA 签署。您可能已经这样做了,但其他人也可以 理解答案并自己尝试(这样我就可以测试我的 回答我自己;-)),他们需要一些这样的代码:

    # newcert.py
    from twisted.python.filepath import FilePath
    from twisted.internet.ssl import PrivateCertificate, KeyPair, DN
    
    def getCAPrivateCert():
        privatePath = FilePath(b"ca-private-cert.pem")
        if privatePath.exists():
            return PrivateCertificate.loadPEM(privatePath.getContent())
        else:
            caKey = KeyPair.generate(size=4096)
            caCert = caKey.selfSignedCert(1, CN="the-authority")
            privatePath.setContent(caCert.dumpPEM())
            return caCert
    
    def clientCertFor(name):
        signingCert = getCAPrivateCert()
        clientKey = KeyPair.generate(size=4096)
        csr = clientKey.requestObject(DN(CN=name), "sha1")
        clientCert = signingCert.signRequestObject(
            csr, serialNumber=1, digestAlgorithm="sha1")
        return PrivateCertificate.fromCertificateAndKeyPair(clientCert, clientKey)
    
    if __name__ == '__main__':
        import sys
        name = sys.argv[1]
        pem = clientCertFor(name.encode("utf-8")).dumpPEM()
        FilePath(name.encode("utf-8") + b".client.private.pem").setContent(pem)
    

    使用这个程序,您可以像这样创建一些证书:

    $ python newcert.py a
    $ python newcert.py b
    

    现在你应该有几个可以使用的文件了:

    $ ls -1 *.pem
    a.client.private.pem
    b.client.private.pem
    ca-private-cert.pem
    

    然后你会想要一个使用这些证书之一的客户端,并发送一些 数据:

    # tlsclient.py
    from twisted.python.filepath import FilePath
    from twisted.internet.endpoints import SSL4ClientEndpoint
    from twisted.internet.ssl import (
        PrivateCertificate, Certificate, optionsForClientTLS)
    from twisted.internet.defer import Deferred, inlineCallbacks
    from twisted.internet.task import react
    from twisted.internet.protocol import Protocol, Factory
    
    class SendAnyData(Protocol):
        def connectionMade(self):
            self.deferred = Deferred()
            self.transport.write(b"HELLO\r\n")
        def connectionLost(self, reason):
            self.deferred.callback(None)
    
    
    @inlineCallbacks
    def main(reactor, name):
        pem = FilePath(name.encode("utf-8") + b".client.private.pem").getContent()
        caPem = FilePath(b"ca-private-cert.pem").getContent()
        clientEndpoint = SSL4ClientEndpoint(
            reactor, u"localhost", 4321,
            optionsForClientTLS(u"the-authority", Certificate.loadPEM(caPem),
                                PrivateCertificate.loadPEM(pem)),
        )
        proto = yield clientEndpoint.connect(Factory.forProtocol(SendAnyData))
        yield proto.deferred
    
    import sys
    react(main, sys.argv[1:])
    

    最后,一个可以区分它们的服务器:

    # whichclient.py
    from twisted.python.filepath import FilePath
    from twisted.internet.endpoints import SSL4ServerEndpoint
    from twisted.internet.ssl import PrivateCertificate, Certificate
    from twisted.internet.defer import Deferred
    from twisted.internet.task import react
    from twisted.internet.protocol import Protocol, Factory
    
    class ReportWhichClient(Protocol):
        def dataReceived(self, data):
            peerCertificate = Certificate.peerFromTransport(self.transport)
            print(peerCertificate.getSubject().commonName.decode('utf-8'))
            self.transport.loseConnection()
    
    def main(reactor):
        pemBytes = FilePath(b"ca-private-cert.pem").getContent()
        certificateAuthority = Certificate.loadPEM(pemBytes)
        myCertificate = PrivateCertificate.loadPEM(pemBytes)
        serverEndpoint = SSL4ServerEndpoint(
            reactor, 4321, myCertificate.options(certificateAuthority)
        )
        serverEndpoint.listen(Factory.forProtocol(ReportWhichClient))
        return Deferred()
    
    react(main, [])
    

    为简单起见,我们将重新使用 CA 自己的证书 服务器,但在更现实的情况下,您显然想要更多 适当的证书。

    您现在可以在一个窗口中运行whichclient.py,然后在另一个窗口中运行python tlsclient.py a; python tlsclient.py b,并查看whichclient.py 打印输出 ab 分别通过commonName 识别客户端 证书主题中的字段。

    这里需要注意的是,您最初可能希望将该调用 Certificate.peerFromTransport 转换为 connectionMade 方法;那不会 工作。 Twisted does not presently have a callback for "TLS handshake complete"; 希望它最终会,但在它发生之前,你必须等到你已经 从对等方接收到一些经过身份验证的数据,以确保握手已 完全的。对于几乎所有应用程序,这都很好,因为当您 已收到执行任何操作的指示(下载更新,在您的情况下) peer 必须已经发送了证书。

    【讨论】:

    • 感谢您提供这个出色而完整的解决方案以及您的快速帮助。这是向我的脚本最终解决方案迈出的一大步。
    • @Glyph 是否可以让客户端在 Windows 上使用 pyinstaller 之类的东西?还是twisted不再支持windows?您网站的 windows 部分已经过时了 :(
    • @nsij22 Twisted 在 Windows 上仍然完全受支持,事实上我最近一直在投入大量精力。例如,最新版本在 Windows 上更容易“pip install”。是的,像 pyinstaller 这样的东西应该可以工作。如果没有,请报告该问题并在 Twisted 列表中讨论(或在此处提出另一个问题,一个问题的 cmets 太小,无法适当讨论)。
    • @Glyph 删除了我的旧评论并发了一个新帖子stackoverflow.com/questions/30160108/…
    猜你喜欢
    • 2016-10-16
    • 2010-10-28
    • 2011-09-15
    • 1970-01-01
    • 1970-01-01
    • 2011-10-17
    • 1970-01-01
    • 2019-01-07
    相关资源
    最近更新 更多