Usa TLS y Python para la autenticación

Quiero hacer un pequeño script de actualización para un software que se ejecuta en una Raspberry Pi y funciona como un servidor local. Eso debería conectarse a un servidor maestro en la web para obtener actualizaciones de software y también para verificar la licencia del software. Para eso instalé dos scripts de python. Quiero que estos se conecten a través de un socket TLS. Luego, el cliente comprueba el certificado del servidor y el servidor comprueba si es uno de los clientes autorizados. Encontré una solución para esto utilizando torcido en esta página .

Ahora queda un problema. Quiero saber qué cliente (según el certificado) está estableciendo la conexión. ¿Hay una manera de hacer esto en Python 3 con retorcido?

Estoy feliz con cada respuesta.

En una palabra: , esto es bastante posible, y todas las cosas necesarias se portan a python 3: probé todo lo siguiente en Python 3.4 en mi Mac y parece que funciona bien.

La respuesta corta es ” use twisted.internet.ssl.Certificate.peerFromTransport ” pero dado que se requiere una gran cantidad de configuraciones para llegar al punto en que eso sea posible, he construido un ejemplo totalmente funcional que debería poder para probar y construir sobre.

Para la posteridad, primero deberá generar algunos certificados de cliente todos firmados por la misma CA. Probablemente ya hayas hecho esto, pero para que otros puedan entender la respuesta y probarla por sí mismos (y así yo mismo podría probar mi respuesta ;-)), necesitarán un código como este:

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

Con este progtwig, puedes crear algunos certificados así:

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

Ahora debes tener algunos archivos que puedes usar:

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

Entonces querrá un cliente que use uno de estos certificados y envíe algunos datos:

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

Y finalmente, un servidor que puede distinguir entre ellos:

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

Para simplificar, simplemente reutilizaremos el propio certificado de CA para el servidor, pero en un escenario más realista, obviamente querría un certificado más apropiado.

Ahora puede ejecutar whichclient.py en una ventana, luego python tlsclient.py a; python tlsclient.py b python tlsclient.py a; python tlsclient.py b en otra ventana, y vea whichclient.py imprime a y luego b respectivamente, identificando a los clientes por el campo commonName en el asunto de su certificado.

La única advertencia aquí es que, inicialmente, es posible que desee poner esa llamada a Certificate.peerFromTransport en un método connectionMade ; eso no funcionara Twisted no tiene actualmente una callback para “TLS handshake complete” ; es de esperar que eventualmente lo haga, pero hasta que lo haga, tendrá que esperar hasta que haya recibido algunos datos autenticados del par para asegurarse de que el saludo se haya completado. Para casi todas las aplicaciones, esto está bien, ya que para cuando haya recibido instrucciones para hacer algo (actualizaciones de descarga, en su caso) el par ya debe haber enviado el certificado.