I am using python and SSL module to establish a TLS connection to a server. Then I would like to send some data to this server. To do this, I have a CA-Certificate, Client Certificate, and Client Certificate Key in a file called "cert.pem". Although my server code works fine, my client code fails to connect to the server. I just mimicked the SSL module example and I do not understand why it fails.
The server code that works fine:
import socket, ssl
cert_chain = 'cert.pem'
host_addr = socket.gethostbyname(socket.gethostname())
host_port = 10023
context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
context.load_cert_chain(certfile=cert_chain)
bindsocket = socket.socket()
bindsocket.bind((host_addr, host_port))
bindsocket.listen(5)
while True:
print("Waiting for client")
newsocket, fromaddr = bindsocket.accept()
conn = context.wrap_socket(newsocket, server_side=True)
print("SSL established. Peer: {}".format(conn.getpeercert()))
buf = b'' # Buffer to hold received client data
try:
while True:
data = conn.recv(4096)
if data: # Client sent us data. Append to buffer
buf += data
else: # No more data from client. Show buffer and close connection.
print("Received:", buf)
break
finally:
print("Closing connection")
conn.shutdown(socket.SHUT_RDWR)
conn.close()
The client code that fails:
import socket, ssl
server_addr = '**.***.***.***'
server_port = 3335
cert_chain = 'cert.pem'
context = ssl.create_default_context()
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
context.load_verify_locations("/etc/ssl/certs/ca-bundle.crt")
conn = context.wrap_socket(socket.socket(socket.AF_INET), server_hostname=server_addr)
conn.connect((server_addr, server_port))
conn.send(b"Hello, world!")
conn.close()
When I run the client code, I get this error:
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1091)
However when I simply use this command, I am able to send data:
openssl s_client -connect 127.0.0.1:10023
Questions
What is wrong with my client code?
Why the openssl s_client command works but the python code does not?
I highly appreciate any suggestions to improve the code.
I see two problems here:
a) According to your code, your server listens on port 10023, but your client tries to connect to 3335. But that is probably just a problem in the code you posted. If it was in the code you run, you would not have been able to establish a connection at all.
b) You get a CERTIFICATE_VERIFY_FAILED when the server certificate can not be verified by the client. For example, when you browse to https://stackoverflow.com you will see some sort of lock-symbol in your address bar, indicating, that your connection is secure. That is because the server supplies a certificate that is valid and trusted for the domain stackoverflow.com. In your case, your certificate is most probably neither valid, nor trusted.
For that to work, the certificate would need to have the IP address or hostname (whatever you supply as server_hostname in the wrap_socket method) as SAN and the certificate would need to be signed by a trusted CA. As you are probably using a self-signed certificate, this will not be the case, because your trust-store is /etc/ssl/certs/ca-bundle.crt. When you issue your self-signed certificate, you can add the IP/hostname to SAN and CN and in your code, you can try to add the certificate to /etc/ssl/certs/ca-bundle.crt.
For development purposes, you can set verify_mode to CERT_NONE (see doc) which should solve all your problems for the moment. This should not be used in production, though.
Related
I’m doing a simple Python (3.8) implementation of Secure WebSocket, using the examples in the python docs for WebSocket. This is between a Windows 10 computer and an Ubuntu 18.04.2 Linux Machine connected to a switch/hub. The rough server and client code are below:
Server:
# WSS (WS over TLS) server example, with a self-signed certificate
import asyncio
import pathlib
import ssl
import websockets
async def hello(websocket, path):
name = await websocket.recv()
print(f"< {name}")
greeting = f"Hello {name}!"
await websocket.send(greeting)
print(f"> {greeting}")
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
localhost_pem = pathlib.Path(__file__).with_name("localhost.pem")
private_key = pathlib.Path(__file__).with_name("private.key")
ssl_context.load_cert_chain(localhost_pem,private_key)
start_server = websockets.serve(
hello, "164.123.456.2", 1234, ssl=ssl_context)
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()
Client:
# WSS (WS over TLS) client example, with a self-signed certificate
import asyncio
import pathlib
import ssl
import websockets
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
localhost_pem = pathlib.Path(__file__).with_name("localhost.pem")
private_key = pathlib.Path(__file__).with_name("private.key")
ssl_context.load_verify_locations(localhost_pem, private_key )
async def hello():
uri = "wss://164.123.456.2:1234"
async with websockets.connect(
uri, ssl=ssl_context
) as websocket:
name = input("What's your name? ")
await websocket.send(name)
print(f"> {name}")
greeting = await websocket.recv()
print(f"< {greeting}")
asyncio.get_event_loop().run_until_complete(hello())
I was able to get this implementation to work with a self-signed certificate (created using openssl) with a Subject Alternate name (SAN) field for the IP address. I want to use a certificate that is based on a hostname instead of an IP address. I’ve added ‘DNS Name =Hostname’ to the SAN certificate field, but this hasn’t worked. It resulted in the following error:
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: IP address mismatch, certificate is not valid for “164.123.456.2” (_ssl.c:1123)
Is this possible? What fields should be added to the certificate to make this work? Should any changes be made to the python Code?
uri = "wss://164.123.456.2:1234"
The certificate must match the domain/IP used in the URL, in this case 164.123.456.2. If you want to use a domain you have to have the domain name both in the certificate and also use it in the URL. Of course the domain name must resolve to the correct IP address on the client systems.
For a project, I need a server and a client. Not a problem so far. The difficulty is for me, that the connection must be encrypted since sensible information will be sent. You could use RSA encryption. I just don't know yet how to exchange the keys so nobody could intercept them or get any other chance to reach them.
Since I don't know, how to do it in general, I did not try anything so far.
Here is a TLS connection implementation in Python. All key exchanging and encrypting data is done within the protocol.
import socket
import ssl
def main():
#Define Host Name And Port (Port 443 Is Typical Encrypted Web Connection Port)
host_name = "www.google.com"
host_port = 443
#Create Unencrypted Connection And Then Encrypted It By Wrapping It
unencrypted_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
unencrypted_socket.settimeout(10)
encrypted_socket = ssl.wrap_socket(unencrypted_socket,ssl_version=ssl.PROTOCOL_TLSv1) #Optional Ciphers Spec Parameter Too
#Connect To The Host, Send Data And Wait To Recieve Data
encrypted_socket.connect((host_name,host_port))
encrypted_socket.send(b"Hello")
response = encrypted_socket.recv(512)
#Close The Connection
encrypted_socket.close()
main()
Note: I am using Python 3.6, and I think that a newer version of TLS is available to use as of Python 3.7.
Is it possible to only fetch a cert using python3 from a server that requires client auth without authenticating?
There's a server in my environment requiring a client ssl cert to authenticate. If you don't have one, when you connect with openssl you'll be able to fetch the certificate but get an error: Post with details. The error occurs late in the handshake.
If I try with python using the ssl module:
hostname = 'serverwithclientauth'
# PROTOCOL_TLS_CLIENT requires valid cert chain and hostname
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
context.load_verify_locations('path/to/cabundle.pem')
with socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) as sock:
with context.wrap_socket(sock, server_hostname=hostname) as ssock:
print(ssock.version())
(which is basically copy/paste from: Python.org)), wrap_socket fails. I am wondering if the ssl sequence can be broken down into smaller chunks such that I can terminate after the server sends the cert to the client.
I have tried just going for:
ssl.get_server_certificate((host,port))
but even just that bit fails for this host (others without client auth requirement work great!).
I am trying to connect to an SSL socket hosted on an external server, send data and the service should respond which is what I am attempting to capture. However the program will wait for a response and then timeout.
At a basic level I am trying to replicate the below openssl connection. I am able to send a ping and the server responds pong so that I know everything is working.
openssl s_client -quiet -connect server.server:443
import socket
import ssl
hostname = "server.server"
context = ssl.create_default_context()
with socket.create_connection((hostname, 4433)) as sock:
sock.settimeout(5)
with context.wrap_socket(sock, server_hostname=hostname) as ssock:
print(ssock.version())
ssock.send(b'ping')
response = ssock.recv(1024)
print(response)
sock.close()
print("SSL Socket Closed")
There is nothing known about the server program. But my guess is that the server will read a line and only respond after the line was read. When trying with openssl s_client you enter ping followed by the <Enter> key - which will send ping followed by a newline character ('\n') to the server. With ssock.send(b'ping') in your code you will instead only send ping to the server and the newline character which is likely expected by the server. Because of this the server will hang while still trying to read a full line (which should end with \n). Thus, try to send b'ping\n' instead of only b'ping'.
I have created a TLS server and an appropriate TLS client in Node.js. Obviously they both work with each other, but I would like to verify it.
Basically, I think of something such as inspecting the connection, or manually connecting to the server and inspecting what it sends, or something like that ...
The relevant code of the server is:
var tlsOptions = {
key: fs.readFileSync('key.pem'),
cert: fs.readFileSync('server.pem')
};
tls.createServer(tlsOptions, function (tlsConnection) {
var d = dnode({
// [...]
});
tlsConnection.pipe(d).pipe(tlsConnection);
}).listen(3000);
The appropriate client code is:
var d = dnode();
d.on('remote', function (remote) {
// [...]
});
var tlsConnection = tls.connect({
host: '192.168.178.31',
port: 3000
});
tlsConnection.pipe(d).pipe(tlsConnection);
How could I do that?
Wireshark will tell you if the data is TLS encrypted, but it will not tell you if the connection is actually secure against Man-in-the-Middle attacks. For this, you need to test if your client refuses to connect to a server that provides a certificate not signed by a trusted CA, a certificate only valid for a different host name, a certificate not valid anymore, a revoked certificate, ...
If your server.pem is not a certificate from a real/trusted CA, and your client doesn't refuse to connect to the server (and you didn't explicitly provide server.pem to the client), then your client is very probably insecure. Given that you are connecting to an IP, not a host name, no trusted CA should have issued a certificate for it, so I assume you use a selfsigned one and are vulnerable. You probably need to specify rejectUnauthorized when connect()ing. (Rant: As this is a pretty common mistake, I think it is extremely irresponsible to make no verification the default.)
Basically, I think of something such as inspecting the connection, or manually connecting to the server and inspecting what it sends, or something like that ...
You can use tools such as Wireshark to see the data they are transmitting.