Use TLS and Python for authentication

后端 未结 1 1624
忘掉有多难
忘掉有多难 2020-12-09 00:24

I want to make a little update script for a software that runs on a Raspberry Pi and works like a local server. That should connect to a master server in the web to get soft

相关标签:
1条回答
  • 2020-12-09 01:10

    In a word: yes, this is quite possible, and all the necessary stuff is ported to python 3 - I tested all the following under Python 3.4 on my Mac and it seems to work fine.

    The short answer is "use twisted.internet.ssl.Certificate.peerFromTransport" but given that a lot of set-up is required to get to the point where that is possible, I've constructed a fully working example that you should be able to try out and build upon.

    For posterity, you'll first need to generate a few client certificates all signed by the same CA. You've probably already done this, but so others can understand the answer and try it out on their own (and so I could test my answer myself ;-)), they'll need some code like this:

    # 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)
    

    With this program, you can create a few certificates like so:

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

    Now you should have a few files you can use:

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

    Then you'll want a client which uses one of these certificates, and sends some data:

    # 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:])
    

    And finally, a server which can distinguish between them:

    # 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, [])
    

    For simplicity's sake we'll just re-use the CA's own certificate for the server, but in a more realistic scenario you'd obviously want a more appropriate certificate.

    You can now run whichclient.py in one window, then python tlsclient.py a; python tlsclient.py b in another window, and see whichclient.py print out a and then b respectively, identifying the clients by the commonName field in their certificate's subject.

    The one caveat here is that you might initially want to put that call to Certificate.peerFromTransport into a connectionMade method; that won't work. Twisted does not presently have a callback for "TLS handshake complete"; hopefully it will eventually, but until it does, you have to wait until you've received some authenticated data from the peer to be sure the handshake has completed. For almost all applications, this is fine, since by the time you have received instructions to do anything (download updates, in your case) the peer must already have sent the certificate.

    0 讨论(0)
提交回复
热议问题