Implementing the SSL client

You have probably come across the discussion around secure web communication using SSL, or more precisely TLS, which is adopted by many other high-level protocols. Let's see how we can wrap a plain sockets connection with SSL. Python has a built-in SSL module that serves this purpose.

In the following example, we would like to create a plain TCP socket and connect to an HTTPS-enabled web server. We can establish that connection using the SSL module and check the various properties of the connection. For example, to check the identity of the remote web server, we can see whether the hostname is the same in the SSL certificate, as we expect it to be. The following is an example of a secure socket-based client.

You can find the following code in the ssl_client.py file:

#!/usr/bin/python3

import socket
import ssl

from ssl import wrap_socket, CERT_NONE, PROTOCOL_TLSv1, SSLError
from ssl import SSLContext
from ssl import HAS_SNI
from pprint import pprint

TARGET_HOST = 'www.google.com'
SSL_PORT = 443

# Use the path of CA certificate file in your system
CA_CERT_PATH = 'certfiles.crt'

def ssl_wrap_socket(sock, keyfile=None, certfile=None,cert_reqs=None, ca_certs=None, server_hostname=None,ssl_version=None):
context = SSLContext(ssl_version)
context.verify_mode = cert_reqs
if ca_certs:
try:
context.load_verify_locations(ca_certs)
except Exception as e:
raise SSLError(e)
if certfile:
context.load_cert_chain(certfile, keyfile)
if HAS_SNI: # OpenSSL enabled SNI
return context.wrap_socket(sock,server_hostname=server_hostname)
return context.wrap_socket(sock)

In the preceding code, we have declared our ssl_wrap_socket() method, which accepts the socket as a parameter and information about the certificate. This method internally checks the certificate and loads the information in the context object to return the SSLContext object. The following code is our main program, which asks the user for the destination host and calls the previous method for extracting remote host certificate details with the getpeercert() method from the SSLContext object:

if __name__ == '__main__':
hostname = input("Enter target host:") or TARGET_HOST
client_sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
client_sock.connect((hostname, 443))
ssl_socket = ssl_wrap_socket(client_sock,ssl_version=PROTOCOL_TLSv1,
cert_reqs=ssl.CERT_REQUIRED,
ca_certs=CA_CERT_PATH,server_hostname=hostname)
print(ssl_socket.cipher())
print("Extracting remote host certificate details:")
cert = ssl_socket.getpeercert()
pprint(cert)
if not cert or ('commonName', TARGET_HOST) not in cert['subject'][4]:
raise Exception("Invalid SSL cert for host %s. Check if this is a man-in-the-middle attack!" )
ssl_socket.write('GET / '.encode('utf-8')
pprint(ssl_socket.recv(1024).split(b" "))
ssl_socket.close()
client_sock.close()

If you run the preceding example, you will see the details of the SSL certificate of a remote web server, such as www.google.com. Here, we have created a TCP socket and connected it to HTTPS port 443. Then, that socket connection is wrapped into SSL packets using our ssl_wrap_socket() function. This function takes the following parameters as arguments:

  • sock: TCP socket
  • keyfile: SSL private key file path
  • certfile: SSL public certificate path
  • cert_reqs: Confirmation of whether certificate is required from the other side to make a connection and whether a validation test is required
  • ca_certs: Public certificate authority certificate path
  • server_hostname: The target's remote server's hostname
  • ssl_version: The intended SSL version to be used by the client

At the beginning of the SSL socket-wrapping process, we created an SSL context using the SSLContext() class. This is necessary to set up the SSL connection-specific properties. Instead of using a custom context, we could also use a default context, which is supplied by default with the SSL module, using the create_default_context() function. You can specify whether you'd like to create client- or server-side sockets using a constant. The following is an example for creating a client-side socket: context = ssl.create_default_context(Purpose.SERVER_AUTH).

The SSLContext object takes the SSL version argument, which in our example is set to PROTOCOL_TLSv1, or you should use the latest version. Note that SSLv2 and SSLv3 are broken and they should not be used in any production server because it could cause serious security problems.

In the preceding example, CERT_REQUIRED indicates that the server certificate is necessary for the connection to continue, and that this certificate will be validated later.

If the CA certificate parameter has been presented with a certificate path, the load_verify_locations() method is used to load the CA certificate files.

This will be used to verify the peer server certificates. If you'd like to use the default certificate path on your system, you'd probably call another context method: load_default_certs(purpose=Purpose.SERVER_AUTH).

When we operate on the server side, the load_cert_chain() method is usually used to load the key and certificate file so that clients can verify the server's authenticity.

Finally, the wrap_socket() method is called to return an SSL-wrapped socket. Note that if the OpenSSL library comes with Server Name Indication (SNI) support enabled, you can pass the remote server's host name while wrapping the socket. This is useful when the remote server uses different SSL certificates for different secure services using a single IP address, for example, name-based virtual hosting.

If you run the preceding SSL client code, you will see the cipher type by calling the cipher() method and the properties of the SSL certificate of the remote server, as shown in the following screenshot. This is used to verify the authenticity of the remote server by calling the getpeercert() method and comparing it with the returned hostname.

In the following screenshot, we can see the execution of the previous script for getting information about the certificate that is using a specific domain:

Interestingly, if any other fake web server wants to pretend to be Google's web server, it simply can't do that. At this point we could verify that the SSL certificate is signed by an accredited certification authority and check if accredited CA has been compromised/subverted. This form of attack to your web browser is commonly referred to as a man-in-the-middle (MITM) attack.

In the following example, we are using the wrap_socket() method to get the SSL socket and we use the match_hostname() method from that socket to check the certificate.

You can find the following code in the ssl_client_check_certificate.py file:

#!/usr/bin/env python3

import socket, ssl, sys
from pprint import pprint

TARGET_HOST = 'www.google.com'
SSL_PORT = 443

#Use the path of CA certificate file in your system
CA_CERT_PATH = 'certfiles.crt'

if __name__ == '__main__':
hostname = input("Enter target host:") or TARGET_HOST
client_sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
client_sock.connect((hostname, 443))
# Turn the socket over to the SSL library
ssl_socket = ssl.wrap_socket(client_sock,
ssl_version=ssl.PROTOCOL_TLSv1,cert_reqs=ssl.CERT_REQUIRED, ca_certs=CA_CERT_PATH)
print(ssl_socket.cipher())
try:
ssl.match_hostname(ssl_socket.getpeercert(), hostname)
except ssl.CertificateError as ce:
print('Certificate error:', str(ce))
sys.exit(1)
print("Extracting remote host certificate details:")
cert = ssl_socket.getpeercert()
pprint(cert)
ssl_socket.close()
client_sock.close()

If the hostname doesn't match the name that appears on the certificate, the script execution will throw an exception of the ssl.CertificateError type:

Enter target host:www.google.com
('ECDHE-RSA-AES128-SHA', 'TLSv1/SSLv3', 128)
Certificate error: hostname 'other_hostname' doesn't match 'www.google.com'
In the previous examples, we used CA_CERT_PATH = 'certfiles.crt', which contains the path of the CA certificate file in your system. You can also generate your own certificate using specific tools, such as OpenSSL (https://www.openssl.org). There are other methods we can use to generate a certificate for a specific domain, such as the web service (http://www.selfsignedcertificate.com/).
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset