How do i establish two way encrypted server-client-connection in python? - python-3.x

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.

Related

How to communicate with a server using a TLS connection?

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.

Fetch a cert using python3 from a server that requires client auth without authenticating?

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!).

run logstash-forwarder in untrusted network-environment

I want to figure out a safe way to run logstash-forwarder respectively logstash with the lumberjack-input in an untrusted network-environment.
As far as I understand, the SSL-certificate ensures an encrypted connection between client and server und authenticates the server for the client (as in "ok, I know this server is the real logging-server"). How can I authenticate the client for the server (as in "ok, I know this client trying to send me events is one of my machines, not someone else")?
SSL certificates can work in bidirectional way. They can be used to authenticate the server ("ok, this server is the real logging-server") and also the other way around ("ok, I know this client is one of my machines"). For the second case you need to use client certificates.
Although Logstash Forwarder allows to configure a client certificate, logstash's lumberjack input does not support client certs. There is an open github issue regarding this feature.
To overcome this dilemma you can use an alternative log client and logstash's TCP input which supports client certs. The input will look like this:
input {
tcp {
port => 9999
ssl_cert => "/path/to/server.crt"
ssl_key => "/path/to/server.key"
ssl_cacert => "/path/to/ca.crt"
ssl_enable => true
ssl_verify => true
}
}
On the client side you can use several tools. I personally do this with NXLog. A proper NXLog output config would look like this:
<Output logstash>
Module om_ssl
Host yourhost
Port 9999
CAFile %CERTDIR%/ca.crt
CertFile %CERTDIR%/client.crt
CertKeyFile %CERTDIR%/client.key
</Output>
Unfortunately this is just a workaround with another software but I'm afraid there is no native lumberjack solution.

How to verify that a connection is actually TLS secured?

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.

nodejs tls session id

I am using TLS to create session using node.js library. Node.js does it provide a way to retrieve session id of TLS connection established. It is part of SSL ctx in openssl.
Can it be done without using connect, express or geddy?
Unfortunately I don't think that information is exposed from the SSL context for node connections.
You can access the node object representing the context as follows:
var con = tls.connect(..., ...);
con.pair.credentials.context
Unfortunately, the only methods available on that object are setKey, setCert, addCACert, addCRL, addRootCerts, setCiphers and setOptions.
That said, with a little bit of C++ and SSL know-how and come copy/pasting, you could probably patch node's node_crypto.cc and node_crypto.h files to add that lookup without TOO much work.
You can't get the session_id but you can get the session itself for caching / resume purposes by calling conn.getSession() once the connection is established.

Resources