How to make a TLS connection using python?
Asked Answered
T

1

11

I would like to create a TLS connection to a server. Then, I want to send some encrypted data to the server. I know the hostname and port and I have the certificate. Surprisingly, I also received the private key of the server. However, I think it is not normal that I received the private key.

The first question is that, do I really need the private key to make a TLS connection?

By the way, I am using this python script

import socket
import ssl

server_addr = '**.**.**.**'
server_port = ****
server_cert = 'server.crt'
server_key  = 'server.key'        # I use the private key

context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
context.verify_mode = ssl.CERT_REQUIRED
context.load_cert_chain(certfile=server_cert, keyfile=server_key)

bindsocket = socket.socket()
bindsocket.connect((server_addr, server_port))

I am using the private key in the above script. It works without any error. However, when I try to bind() instead of connect(), i.e.,

bindsocket.bind((server_addr, server_port))

I get the following error:

OSError: [Errno 99] Cannot assign requested address

I have read many related questions about the above error, however, I still do not understand why this happens. Since, I have the hostname, port, certificate, and the key, I expect to create a TLS connection successfully.

The second question is that how can I establish a TLS connection? Is my script correct?

I highly appreciate any comment to improve the script.

Thunderstruck answered 3/8, 2020 at 9:7 Comment(0)
T
14

So, first of all, you should absolutely not have the private key! As the name says, it is private and not necessary to establish a connection.
You could have the public key, but even that is not necessary as long as you use standard SSL and you trust the CA that signed the servers certificate.
Are you sure, it is the private key? Does the file begin with -----BEGIN PRIVATE KEY-----? Check with openssl rsa -noout -text -in server.key.
Refer to the wikipedia article and this post for more on asymmetric cryptography.

Further along the way:
With socket.bind() you bind a socket to a port on your local machine. This is not possible, as your machine does not have the address (you provide a server address).
From your code, it looks like you are trying to open the socket as a server. You will need the private key for that, but then you will be accepting connections and not connect to other machines yourself. I have a feeling, that you are mixing up two things here.
Refer to the python documentation of socket.bind() and to this question as this seems to be closely related.
Also check out the python documentation on ssl. I took the example, that does what you are asking for from said documentation:

import socket, ssl, pprint

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# require a certificate from the server
ssl_sock = ssl.wrap_socket(s,
                           ca_certs="/etc/ca_certs_file",
                           cert_reqs=ssl.CERT_REQUIRED)
ssl_sock.connect(('www.verisign.com', 443))

pprint.pprint(ssl_sock.getpeercert())
# note that closing the SSLSocket will also close the underlying socket
ssl_sock.close()

Also have a look on the example on how to open a SSL socket in server mode.

Further thoughts:
Do you really need to do all that TLS stuff yourself? If the server, for example, uses HTTPS (SSL encrypted HTTP), you can just use the http.client library.

Feel free to ask, if you need me to clarify something. I'll update my answer accordingly.

EDIT:
As you indicated, you want to open a port in server mode, I made an example for you (it heavily leans on the python documentation example):

import socket, ssl

context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
context.load_cert_chain(certfile="cert.pem", keyfile="key.pem")

bindsocket = socket.socket()
bindsocket.bind(('127.0.0.1', 10023))
bindsocket.listen(5)

def deal_with_client(connstream):
    data = connstream.recv(1024)
    # empty data means the client is finished with us
    while data:
        print(data)
        data = connstream.recv(1024)

while True:
    newsocket, fromaddr = bindsocket.accept()
    connstream = context.wrap_socket(newsocket, server_side=True)
    try:
        deal_with_client(connstream)
    finally:
        connstream.shutdown(socket.SHUT_RDWR)
        connstream.close()

Running it:

% python3 ssltest.py
b'hello server!\n'
b'this is data\n'

The client side:

% openssl s_client -connect 127.0.0.1:10023
CONNECTED(00000005)
depth=0 C = SE, ST = Some-State, O = Internet Widgits Pty Ltd
verify error:num=18:self signed certificate
verify return:1
depth=0 C = SE, ST = Some-State, O = Internet Widgits Pty Ltd
verify return:1
---
Certificate chain
 0 s:C = SE, ST = Some-State, O = Internet Widgits Pty Ltd
   i:C = SE, ST = Some-State, O = Internet Widgits Pty Ltd
---
Server certificate
-----BEGIN CERTIFICATE-----
 ... certificate ...
-----END CERTIFICATE-----
subject=C = SE, ST = Some-State, O = Internet Widgits Pty Ltd

issuer=C = SE, ST = Some-State, O = Internet Widgits Pty Ltd

---
No client certificate CA names sent
Peer signing digest: SHA256
Peer signature type: RSA-PSS
Server Temp Key: X25519, 253 bits
---
SSL handshake has read 2272 bytes and written 404 bytes
Verification error: self signed certificate
---
New, TLSv1.2, Cipher is ECDHE-RSA-AES256-GCM-SHA384
Server public key is 4096 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
    Protocol  : TLSv1.2
    Cipher    : ECDHE-RSA-AES256-GCM-SHA384
    ... session stuff ...
    Extended master secret: yes
---
hello server!
this is data
^C

If you use some usual protocol like HTTP, you should use a library though. Here is a example with flask:

>>> from flask import Flask
>>> app = Flask(__name__)
>>> 
>>> @app.route("/")
... def hello():
...     return "Hello World!"
... 
>>> if __name__ == "__main__":
...     app.run(ssl_context=('cert.pem', 'key.pem'))
... 
 * Serving Flask app "__main__" (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on https://127.0.0.1:5000/ (Press CTRL+C to quit)
127.0.0.1 - - [06/Aug/2020 11:45:50] "GET / HTTP/1.1" 200 -
Teresita answered 6/8, 2020 at 6:43 Comment(4)
Thanks a lot for the answer. I think I misunderstood "server-side" and "client-side" operations. Since I have the private key, I am supposed to do server operations. I still have a problem with authentication and handshake procedures. Are they part of a wrap_socket() function?Thunderstruck
Authentication is not necessarily part of SSL, except if you have the client authenticate using his own certificate. The handshake is done when a client connects to the server, but that is implemented in the ssl library, so you don't really need to worry about it. The wrap socket function just puts the SSL layer on top of the normal network layer. After that, you should be able to use the socket like a normal network socket. Does that make sense?Teresita
@Thunderstruck I added an example on how to open an SSL socketTeresita
Thank you for the detailed response. When I use openssl s_client command as you suggested, everything works fine and I can send data to the server. However, when I try a python code (client-side) to send some data, It fails. Here is the python code that sends some data to the server: https://mcmap.net/q/1017207/-how-to-communicate-with-a-server-using-a-tls-connection/9696715Thunderstruck

© 2022 - 2024 — McMap. All rights reserved.