Mutual ssl authentication in simple ECHO client/server [Python / sockets / ssl modules], ssl.SSLEOFError: EOF occurred in violation of protocol
Basically the server need to share with the client his certificate and vice versa (look the ca_certs
parameter). The main problem with your code is that the handshake were never executed. Also, the Common Name
string position depends on how many field did specified in the certificate. I had been lazy, so my subject
has only 4 fiels, and Common Name
is the last of them.
Now it works (feel free to ask for further details).
Server
#!/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()
Take a look:
Ps: I made the client print the server response.
Response to comments
On client's side you never used the context variable I've created. Does it mean it's unnecessary here?
Documentation says:
For more sophisticated applications, the
ssl.SSLContext
class helps manage settings and certificates, which can then be inherited by SSL sockets created through theSSLContext.wrap_socket()
method.
I've updated the code to show you the differences: the server uses ssl.wrap_socket()
, the client ssl.SSLContext.wrap_socket()
.
Second, what's the point in checking if ssl.HAS_SNI when the socket creation looks the same in if and else? With your approach I cant use server_hostname=HOST in socket wrapping method.
You are right, in the updated code I used server_hostname=HOST
.
Another thing: you're using ca_certs instead of using load_verify_locations in context I created. Why? Are those 2 methods identical?
My fault, I was using ca_cert
as parameter of ssl.wrap_socket()
, so I didn't used the context
at all. Now I use it.
And another thing: do you really need to call
secure_sock.do_handshake()
by yourself?
Nope, I forgot to remove it :)
The output is exactly the same.