Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use TLS and Python for authentication

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 software updates and also to verify the license of the software. For that I set up two python scripts. I want these to connect via a TLS socket. Then the client checks the server certificate and the server checks if it's one of the authorized clients. I found a solution for this using twisted on this page.

Now there is a problem left. I want to know which client (depending on the certificate) is establishing the connection. Is there a way to do this in Python 3 with twisted?

I'm happy with every answer.

like image 638
pBuch Avatar asked Feb 23 '15 15:02

pBuch


People also ask

Can TLS be used for authentication?

The Transport Layer Security (TLS) is a protocol designed to provide secure communication over the Internet and includes authentication, confidentiality and integrity. When a TLS connection is established the server provides a certificate that the client validates before trusting the server's identity.

Does Python use TLS?

By default on PythonAnywhere, websites that we host are only available over TLS 1.2 and 1.3. The reason we do not support older TLS versions is that an attacker can (in theory) alter a connection so that it switches over to using an older, broken version. This is known as a protocol downgrade attack.

What is TLS Python?

Transport Layer Security (TLS) is a cryptographic protocol designed to provide communication security over the Internet. This is an open source Python implementation of TLS 1.2, using the Python Cryptographic Authority's (PyCA's) Cryptography libraries for all cryptographic primitives (e.g. AES , RSA , etc.).

How do I enable TLS authentication?

Set Up TLS Server Mode AuthenticationIn TLS Server mode, clients must verify the server's certificate. The client must be able to access the certificate authority file, and the SSLCertificate and SSLPrivateKey parameters must be set on the server. In TLS Mutual mode, servers must also verify the client's certificate.


1 Answers

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.

like image 132
Glyph Avatar answered Sep 29 '22 11:09

Glyph