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.
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).
#!/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()
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:
Ps: J'ai fait imprimer au client la réponse du serveur.
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éthodeSSLContext.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.
ilario-pierbattista Réponse mais en python 3:
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()