Reputation: 1739
I'm currently reading Foundations of Python Network Programming and came across the following example, demonstrating how to use Python's ssl
module:
Listing 6-3: Securing a Socket with TLS for Both Client and Server in Python 3.4 or Newer
#!/usr/bin/env python3
# Foundations of Python Network Programming, Third Edition
# https://github.com/brandon-rhodes/fopnp/blob/m/py3/chapter06/safe_tls.py
# Simple TLS client and server using safe configuration defaults
import argparse, socket, ssl
def client(host, port, cafile=None):
purpose = ssl.Purpose.SERVER_AUTH
context = ssl.create_default_context(purpose, cafile=cafile)
raw_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
raw_sock.connect((host, port))
print('Connected to host {!r} and port {}'.format(host, port))
ssl_sock = context.wrap_socket(raw_sock, server_hostname=host)
while True:
data = ssl_sock.recv(1024)
if not data:
break
print(repr(data))
def server(host, port, certfile, cafile=None):
purpose = ssl.Purpose.CLIENT_AUTH
context = ssl.create_default_context(purpose, cafile=cafile)
context.load_cert_chain(certfile)
listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
listener.bind((host, port))
listener.listen(1)
print('Listening at interface {!r} and port {}'.format(host, port))
raw_sock, address = listener.accept()
print('Connection from host {!r} and port {}'.format(*address))
ssl_sock = context.wrap_socket(raw_sock, server_side=True)
ssl_sock.sendall('Simple is better than complex.'.encode('ascii'))
ssl_sock.close()
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Safe TLS client and server')
parser.add_argument('host', help='hostname or IP address')
parser.add_argument('port', type=int, help='TCP port number')
parser.add_argument('-a', metavar='cafile', default=None,
help='authority: path to CA certificate PEM file')
parser.add_argument('-s', metavar='certfile', default=None,
help='run as server: path to server PEM file')
args = parser.parse_args()
if args.s:
server(args.host, args.port, args.s, args.a)
else:
client(args.host, args.port, args.a)
The book says to download the following two files, which are a self-signed CA and a second CA (signed by the self-signed CA) for the hostname, "localhost":
The code is, then, supposed to be run as follows:
Server
python safe_tls.py -s localhost.pem '' 1060
Client
python safe_tls.py -a ca.crt localhost 1060
However, whenever I run the server and client together, I get the following error for each:
Server
Listening at interface '' and port 1060
Connection from host '127.0.0.1' and port 35148
Traceback (most recent call last):
File "safe_tls.py", line 50, in <module>
server(args.host, args.port, args.s, args.a)
File "safe_tls.py", line 35, in server
ssl_sock = context.wrap_socket(raw_sock, server_side=True)
File "/usr/lib/python3.6/ssl.py", line 407, in wrap_socket
_context=self, _session=session)
File "/usr/lib/python3.6/ssl.py", line 814, in __init__
self.do_handshake()
File "/usr/lib/python3.6/ssl.py", line 1068, in do_handshake
self._sslobj.do_handshake()
File "/usr/lib/python3.6/ssl.py", line 689, in do_handshake
self._sslobj.do_handshake()
ssl.SSLError: [SSL: TLSV1_ALERT_UNKNOWN_CA] tlsv1 alert unknown ca (_ssl.c:833)
Client
Connected to host 'localhost' and port 1060
Traceback (most recent call last):
File "safe_tls.py", line 52, in <module>
client(args.host, args.port, args.a)
File "safe_tls.py", line 15, in client
ssl_sock = context.wrap_socket(raw_sock, server_hostname=host)
File "/usr/lib/python3.6/ssl.py", line 407, in wrap_socket
_context=self, _session=session)
File "/usr/lib/python3.6/ssl.py", line 814, in __init__
self.do_handshake()
File "/usr/lib/python3.6/ssl.py", line 1068, in do_handshake
self._sslobj.do_handshake()
File "/usr/lib/python3.6/ssl.py", line 689, in do_handshake
self._sslobj.do_handshake()
ssl.SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:833)
I already tried generating my own self-signed CA and secondary CA (signed by the self-signed CA) for the hostname, "localhost", but I still get the same errors when running the client and server.
Could anyone please tell me what I am doing wrong?
Thank you.
P.S. I am running Python 3.6.5 on Ubuntu 18.04 LTS in case it matters...
I was able to get the code above to work without the secondary certificate, but rather with a single self-signed certificate which I generated as follows:
$ openssl req -x509 -nodes -newkey rsa:4096 -keyout localhost.key -new -out localhost.crt
$ cat localhost.crt localhost.key > localhost.pem
Then, I ran the safe_tls.py
program exactly as described above, except with localhost.crt
instead of ca.crt
.
Still, I would like to be able to sign one CA with another, and then use the secondary CA as I was trying to originally. Any help figuring out how to do this would be greatly appreciated!
Upvotes: 1
Views: 4214
Reputation: 1739
Alright, so I ended up finding the answer to my own question, so I'll post it here.
I don't know what is wrong with the ca.crt
and localhost.pem
files provided at the public repo of Foundations of Python Network Programming; however, I managed to generate and sign my own CAs, getting it to work as I had originally wanted.
Here are the commands I had to type:
$ # I don't know what these files are, but OpenSSL complains if they do not exist...
$ mkdir demoCA
$ touch demoCA/index.txt demoCA/index.txt.attr
$ echo '01' > demoCA/serial
$ # Create a self-signed certificate and private key.
$ openssl req -x509 -nodes -newkey rsa:4096 -keyout ca.key -new -out ca.crt
$ # Create a certificate signing request for server, "localhost", and a private key.
$ openssl req -nodes -newkey rsa:4096 -keyout localhost.key -new -out localhost.csr
$ # Sign the certificate request.
$ openssl ca -keyfile ca.key -cert ca.crt -in localhost.csr -outdir . -out localhost.crt
$ cat localhost.crt localhost.key > localhost.pem
NOTE:
For each of the openssl req ...
commands, executed above, I had to fill in some information at the command-line (since I did not create a configuration file). All the information, except for the host name and email must be the same across the two certificates, otherwise the last command, where we sign the certificate request, will fail.
The server and client can now be successfully run as originally shown:
Server
python safe_tls.py -s localhost.pem '' 1060
Client
python safe_tls.py -a ca.crt localhost 1060
Upvotes: 1