web-dev-qa-db-fra.com

Authentification SSL mutuelle dans un client / serveur ECHO simple [modules Python / sockets / ssl], ssl.SSLEOFError: EOF s'est produit en violation du protocole

Je voudrais avoir une authentification mutuelle dans mon programme client/serveur d'écho. J'utilise le module python 2.7.12 and the Ssl` sur

Distributor ID: Ubuntu
Description:    Ubuntu 14.04.5 LTS
Release:        14.04
Codename:       trusty

J'ai généré les certificats et clés du client et du serveur à l'aide des commandes openssl:

openssl req -new -x509 -days 365 -nodes -out client.pem -keyout client.key
openssl req -new -x509 -days 365 -nodes -out server.pem -keyout server.key

Je veux que le client authentifie le serveur et je veux que le serveur authentifie le client. Cependant, le code ci-dessous montre quelques erreurs, côté serveur:

Traceback (most recent call last):
  File "ssl_server.py", line 18, in <module>
    secure_sock = ssl.wrap_socket(client, server_side=True, certfile="server.pem", keyfile="server.key")
  File "/usr/lib/python2.7/ssl.py", line 933, in wrap_socket
    ciphers=ciphers)
  File "/usr/lib/python2.7/ssl.py", line 601, in __init__
    self.do_handshake()
  File "/usr/lib/python2.7/ssl.py", line 830, in do_handshake
    self._sslobj.do_handshake()
ssl.SSLEOFError: EOF occurred in violation of protocol (_ssl.c:590)

Côté client:

Traceback (most recent call last):
  File "ssl_client.py", line 18, in <module>
    secure_sock = context.wrap_socket(sock, server_hostname=Host, server_side=False, certfile="client.pem", keyfile="client.key")
TypeError: wrap_socket() got an unexpected keyword argument 'certfile'

Code du serveur:

#!/bin/usr/env python
import socket
import ssl
import pprint

#server
if __name__ == '__main__':

    Host = '127.0.0.1'
    PORT = 1234

    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    server_socket.bind((Host, PORT))
    server_socket.listen(10)

    client, fromaddr = server_socket.accept()
    secure_sock = ssl.wrap_socket(client, server_side=True, certfile="server.pem", keyfile="server.key")

    print repr(secure_sock.getpeername())
    print secure_sock.cipher()
    print pprint.pformat(secure_sock.getpeercert())
    cert = secure_sock.getpeercert()
    print cert

    # verify client
    if not cert or ('commonName', 'test') not in cert['subject'][4]: raise Exception("ERROR")

    try:
        data = secure_sock.read(1024)
        secure_sock.write(data)
    finally:
        secure_sock.close()
        server_socket.close()

Code client:

import socket
import ssl

# client
if __name__ == '__main__':

    Host = '127.0.0.1'
    PORT = 1234

    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect((Host, PORT))

    context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
    context.verify_mode = ssl.CERT_REQUIRED
    context.load_verify_locations('server.pem')

    if ssl.HAS_SNI:
        secure_sock = context.wrap_socket(sock, server_hostname=Host, server_side=False, certfile="client.pem", keyfile="client.key")
    else:
        secure_sock = context.wrap_socket(sock, server_side=False, certfile="client.pem", keyfile="client.key")

    cert = secure_sock.getpeercert()
    print cert

    # verify server
    if not cert or ('commonName', 'test') not in cert['subject'][4]: raise Exception("ERROR")

    secure_sock.write('hello')
    secure_sock.read(1024)

    secure_sock.close()
    sock.close()

Je vous remercie.

14
yak

Fondamentalement, le serveur doit partager avec le client son certificat et vice versa (regardez le paramètre ca_certs). Le principal problème avec votre code est que la poignée de main n'a jamais été exécutée. De plus, la position de la chaîne Common Name Dépend du nombre de champs spécifiés dans le certificat. J'avais été paresseux, donc mon subject n'a que 4 fiels, et Common Name Est le dernier d'entre eux.

Maintenant ça marche (n'hésitez pas à demander plus de détails).

Serveur

#!/bin/usr/env python
import socket
import ssl
import pprint

#server
if __name__ == '__main__':

    Host = '127.0.0.1'
    PORT = 1234

    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    server_socket.bind((Host, PORT))
    server_socket.listen(10)

    client, fromaddr = server_socket.accept()
    secure_sock = ssl.wrap_socket(client, server_side=True, ca_certs = "client.pem", certfile="server.pem", keyfile="server.key", cert_reqs=ssl.CERT_REQUIRED,
                           ssl_version=ssl.PROTOCOL_TLSv1_2)

    print repr(secure_sock.getpeername())
    print secure_sock.cipher()
    print pprint.pformat(secure_sock.getpeercert())
    cert = secure_sock.getpeercert()
    print cert

    # verify client
    if not cert or ('commonName', 'test') not in cert['subject'][3]: raise Exception("ERROR")

    try:
        data = secure_sock.read(1024)
        secure_sock.write(data)
    finally:
        secure_sock.close()
        server_socket.close()

Client

import socket
import ssl

# client
if __name__ == '__main__':

    Host = '127.0.0.1'
    PORT = 1234

    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.setblocking(1);
    sock.connect((Host, PORT))

    context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
    context.verify_mode = ssl.CERT_REQUIRED
    context.load_verify_locations('server.pem')
    context.load_cert_chain(certfile="client.pem", keyfile="client.key")

    if ssl.HAS_SNI:
        secure_sock = context.wrap_socket(sock, server_side=False, server_hostname=Host)
    else:
        secure_sock = context.wrap_socket(sock, server_side=False)

    cert = secure_sock.getpeercert()
    print cert

    # verify server
    if not cert or ('commonName', 'test') not in cert['subject'][3]: raise Exception("ERROR")

    secure_sock.write('hello')
    print secure_sock.read(1024)

    secure_sock.close()
    sock.close()

Regarde:

Proof

Ps: J'ai fait imprimer au client la réponse du serveur.

Réponse aux commentaires

Côté client, vous n'avez jamais utilisé la variable de contexte que j'ai créée. Cela signifie-t-il que ce n'est pas nécessaire ici?

La documentation dit:

Pour les applications plus sophistiquées, la classe ssl.SSLContext Permet de gérer les paramètres et les certificats, qui peuvent ensuite être hérités par des sockets SSL créés via la méthode SSLContext.wrap_socket().

J'ai mis à jour le code pour vous montrer les différences: le serveur utilise ssl.wrap_socket(), le client ssl.SSLContext.wrap_socket().

Deuxièmement, quel est l'intérêt de vérifier si ssl.HAS_SNI lorsque la création de socket est identique dans if et else? Avec votre approche, je ne peux pas utiliser server_hostname = Host dans la méthode d'emballage de socket.

Vous avez raison, dans le code mis à jour, j'ai utilisé server_hostname=Host.

Autre chose: vous utilisez ca_certs au lieu d'utiliser load_verify_locations dans le contexte que j'ai créé. Pourquoi? Ces 2 méthodes sont-elles identiques?

Ma faute, j'utilisais ca_cert Comme paramètre de ssl.wrap_socket(), donc je n'ai pas du tout utilisé le context. Maintenant je l'utilise.

Et une autre chose: avez-vous vraiment besoin d'appeler secure_sock.do_handshake() par vous-même?

Non, j'ai oublié de le retirer :)

La sortie est exactement la même.

9

ilario-pierbattista Réponse mais en python 3:

  • Vérifier la fonction d'impression
  • Vérifiez secure_sock.write (b'hello ') en octets
  • Vérifier l'argument de la fonction (config)
def start_client_side(config):
    Host = config['Host']
    PORT = config['port']
    pemServer = config['serverpem']
    keyClient = config['clientkey']
    pemClient = config['clientpem']

    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.setblocking(1);
    sock.connect((Host, PORT))

    context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
    context.verify_mode = ssl.CERT_REQUIRED
    context.load_verify_locations(pemServer)
    context.load_cert_chain(certfile=pemClient, keyfile=keyClient)

    if ssl.HAS_SNI:
        secure_sock = context.wrap_socket(sock, server_side=False, server_hostname=Host)
    else:
        secure_sock = context.wrap_socket(sock, server_side=False)

    cert = secure_sock.getpeercert()
    print(pprint.pformat(cert))

    # verify server
    if not cert or ('commonName', 'server.utester.local') not in itertools.chain(*cert['subject']): raise Exception("ERROR")

    secure_sock.write(b'hello')
    print(secure_sock.read(1024))

    secure_sock.close()
    sock.close()

def start_server_side(config):
    Host = config['Host']
    PORT = config['port']
    pemServer = config['serverpem']
    keyServer = config['serverkey']
    pemClient = config['clientpem']

    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    server_socket.bind((Host, PORT))
    server_socket.listen(10)

    client, fromaddr = server_socket.accept()
    secure_sock = ssl.wrap_socket(client, server_side=True, ca_certs=pemClient, certfile=pemServer,
                                  keyfile=keyServer, cert_reqs=ssl.CERT_REQUIRED,
                                  ssl_version=ssl.PROTOCOL_TLSv1_2)

    print(repr(secure_sock.getpeername()))
    print(secure_sock.cipher())
    cert = secure_sock.getpeercert()
    print(pprint.pformat(cert))

    # verify client
    if not cert or ('commonName', 'client.utester.local') not in itertools.chain(*cert['subject']): raise Exception("ERROR")

    try:
        data = secure_sock.read(1024)
        secure_sock.write(data)
    finally:
        secure_sock.close()
        server_socket.close()
1
Agustincl