18

I would like to have a mutual authentication in my echo client/server program. I'm using python 2.7.12 and thessl` module on

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

I've generated client's and server's certificates and keys using the openssl commands:

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

I want the client to authenticate the server, and I want the server to authenticate the client. However, the code below shows some errors, at server's side:

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)

At client' side:

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'

Server's code:

#!/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()

Client's code:

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()

Thank you.

jww
  • 97,681
  • 90
  • 411
  • 885
yak
  • 3,770
  • 19
  • 60
  • 111
  • 3
    `ssl.wrap_socket()` has `certfile` parameter. `SSLContext.wrap_socket()` does not. You're confusing the two classes. – pbuck Jun 05 '17 at 15:51
  • @pbuck: I can't find the difference between them. Could you plase explain it a bit? Which of them should I use and why? Are they capable of doing the same things? – yak Jun 07 '17 at 10:07
  • 1
    my point was just a simple bug / typo on the client side which was throwing the exception. Looks like you've got a much more thorough answer from @ilario-pierbattista below! – pbuck Jun 07 '17 at 15:55

2 Answers2

23

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:

Proof

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 the SSLContext.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.

Ilario Pierbattista
  • 3,175
  • 2
  • 31
  • 41
  • 1
    Fineeee, but ... On client's side you never used the `context` variable I've created. Does it mean it's unnecessary here? 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. Another thing: you're using `ca_certs` instead of using `load_verify_locations` in context I created. Why? Are those 2 methods identical? Please don't get me wrong, I asked about those things because I do not understand them. – yak Jun 07 '17 at 09:54
  • 1
    And another thing: do you really need to call `secure_sock.do_handshake()` by yourself? – yak Jun 07 '17 at 10:00
  • 1
    Thank you for your questions, I've update my answer. I hope that it's clear – Ilario Pierbattista Jun 07 '17 at 10:20
  • Thank YOU! :) I need some time to rethink all this. – yak Jun 07 '17 at 10:45
  • If you're coming here from Google, you may have to include `context.load_default_certs()` after setting the verify mode. – technogeek1995 Feb 03 '21 at 22:34
2

ilario-pierbattista Answer but in python 3:

  • Check print function
  • Check secure_sock.write(b'hello') in bytes
  • Check function argument (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()
Agustincl
  • 154
  • 1
  • 5