Взаимная аутентификация ssl в простом клиенте/сервере ECHO [Python/sockets/ssl modules], ssl.SSLEOFError: EOF произошел с нарушением протокола
Я хотел бы иметь взаимную аутентификацию в своей программе echo client/server. Я использую python 2.7.12 and the
ssl` модуль на
Distributor ID: Ubuntu
Description: Ubuntu 14.04.5 LTS
Release: 14.04
Codename: trusty
Я создал сертификаты и ключи клиентов и серверов с помощью команд 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
Я хочу, чтобы клиент аутентифицировал сервер, и я хочу, чтобы сервер аутентифицировал клиента. Однако приведенный ниже код показывает некоторые ошибки на стороне сервера:
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)
На стороне клиента:
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'
Код сервера:
#!/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()
Клиентский код:
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()
Спасибо.
Ответы
Ответ 1
По сути, серверу необходимо предоставить клиенту его сертификат и наоборот (смотрите параметр ca_certs
). Основная проблема с вашим кодом состоит в том, что рукопожатие никогда не выполнялось. Кроме того, позиция строки Common Name
зависит от того, сколько полей указано в сертификате. Я был ленив, поэтому у моего subject
всего 4 поля, а Common Name
- последнее из них.
Теперь это работает (не стесняйтесь спрашивать более подробную информацию).
сервер
#!/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()
Взглянуть:
![Proof]()
PS: я заставил клиента распечатать ответ сервера.
Ответ на комментарии
На стороне клиента вы никогда не использовали контекстную переменную, которую я создал. Значит ли это, что здесь нет необходимости?
Документация гласит:
Для более сложных приложений класс ssl.SSLContext
помогает управлять настройками и сертификатами, которые затем могут наследоваться сокетами SSL, созданными с помощью метода SSLContext.wrap_socket()
.
Я обновил код, чтобы показать вам различия: сервер использует ssl.wrap_socket()
, клиент ssl.SSLContext.wrap_socket()
.
Во-вторых, какой смысл проверять, если ssl.HAS_SNI при создании сокета выглядит одинаково в if и else? При вашем подходе я не могу использовать server_hostname = HOST в методе переноса сокетов.
Вы правы, в обновленном коде я использовал server_hostname=HOST
.
Другое дело: вы используете ca_certs вместо load_verify_locations в контексте, который я создал. Зачем? Эти 2 метода идентичны?
По моей вине я использовал ca_cert
качестве параметра ssl.wrap_socket()
, поэтому я вообще не использовал context
. Сейчас я им пользуюсь.
И еще одна вещь: вам действительно нужно вызвать secure_sock.do_handshake()
самостоятельно?
Нет, я забыл удалить его :)
Выход точно такой же.
Ответ 2
ilario-pierbattista Ответ, но в питоне 3:
- Проверьте функцию печати
- Проверьте secure_sock.write(b'hello ') в байтах
- Проверьте аргумент функции (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()