Использование TLS и Python для аутентификации

Я хочу сделать небольшой скрипт обновления для программного обеспечения, которое работает на Raspberry Pi и работает как локальный сервер. Это должно подключиться к главному серверу в интернете для получения обновлений программного обеспечения, а также для проверки лицензии программного обеспечения. Для этого я настроил два скрипта python. Я хочу, чтобы они подключались через сокет TLS. Затем клиент проверяет сертификат сервера, а сервер проверяет, является ли он одним из авторизованных клиентов. Я нашел решение для этого, используя twisted on этот страница.

теперь осталась проблема. Я хочу знать клиента (в зависимости от сертификата) устанавливает соединение. Есть ли способ сделать это в Python 3 с Витой?

Я доволен каждым ответом.

1 ответов


слова: да, это вполне возможно, и все необходимые вещи портирован на python 3 - я протестировал все следующее под Python 3.4 на моем Mac, и кажется, что работать отлично.

короткий ответ "использовать 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 распечатать a а то b соответственно, определения клиенты по commonName поле в теме сертификата.

одно предостережение здесь в том, что вы можете изначально захотеть поставить этот вызов Certificate.peerFromTransport на connectionMade метод; это не будет работа. Twisted в настоящее время не имеет обратного вызова для "рукопожатия TLS complete"; надеюсь, в конце концов это произойдет, но пока это не произойдет, вам придется подождать, пока вы получил некоторые аутентифицированные данные от однорангового узла, чтобы убедиться, что рукопожатие завершенный. Для почти всех применений, это нормально, так как к тому времени ты получили инструкции сделать что-нибудь (загрузить обновления, в вашем случае) Пэр уже отправил сертификат.