使用TLS和Python进行身份验证

pBu*_*uch 8 python authentication ssl twisted

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

现在还有一个问题.我想知道哪个客户端(取决于证书)正在建立连接.有没有办法在Python 3中使用twisted进行此操作?

我很满意每一个答案.

Gly*_*yph 15

总而言之:是的,这很有可能,所有必要的东西都被移植到python 3 - 我在Mac上的Python 3.4下测试了以下所有内容,它似乎工作正常.

简短的回答是" 使用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)
Run Code Online (Sandbox Code Playgroud)

使用此程序,您可以创建一些证书,如下所示:

$ python newcert.py a
$ python newcert.py b
Run Code Online (Sandbox Code Playgroud)

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

$ ls -1 *.pem
a.client.private.pem
b.client.private.pem
ca-private-cert.pem
Run Code Online (Sandbox Code Playgroud)

然后,您将需要一个使用其中一个证书的客户端,并发送一些数据:

# 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:])
Run Code Online (Sandbox Code Playgroud)

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

# 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, [])
Run Code Online (Sandbox Code Playgroud)

为简单起见,我们只需重新使用CA自己的服务器证书,但在更现实的情况下,您显然需要更合适的证书.

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

这里需要注意的是,您最初可能希望将该调用 Certificate.peerFromTransport放入connectionMade方法中; 这是行不通的. Twisted目前没有"TLS握手完成"的回调 ; 希望它最终能够实现,但在此之前,您必须等到收到来自对等方的一些经过身份验证的数据,以确保握手已完成.对于几乎所有应用程序,这很好,因为当您收到执行任何操作的指令时(在您的情况下下载更新),对等方必须已经发送了证书.