HTTPS proxy tunneling with the ssl module - python

I'd like to manually (using the socket and ssl modules) make an HTTPS request through a proxy which itself uses HTTPS.
I can perform the initial CONNECT exchange just fine:
import ssl, socket
PROXY_ADDR = ("proxy-addr", 443)
CONNECT = "CONNECT example.com:443 HTTP/1.1\r\n\r\n"
sock = socket.create_connection(PROXY_ADDR)
sock = ssl.wrap_socket(sock)
sock.sendall(CONNECT)
s = ""
while s[-4:] != "\r\n\r\n":
s += sock.recv(1)
print repr(s)
The above code prints HTTP/1.1 200 Connection established plus some headers, which is what I expect. So now I should be ready to make the request, e.g.
sock.sendall("GET / HTTP/1.1\r\n\r\n")
but the above code returns
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>400 Bad Request</title>
</head><body>
<h1>Bad Request</h1>
<p>Your browser sent a request that this server could not understand.<br />
Reason: You're speaking plain HTTP to an SSL-enabled server port.<br />
Instead use the HTTPS scheme to access this URL, please.<br />
</body></html>
This makes sense too, since I still need to do an SSL handshake with the example.com server to which I'm tunneling. However, if instead of immediately sending the GET request I say
sock = ssl.wrap_socket(sock)
to do the handshake with the remote server, then I get an exception:
Traceback (most recent call last):
File "so_test.py", line 18, in <module>
ssl.wrap_socket(sock)
File "/usr/lib/python2.6/ssl.py", line 350, in wrap_socket
suppress_ragged_eofs=suppress_ragged_eofs)
File "/usr/lib/python2.6/ssl.py", line 118, in __init__
self.do_handshake()
File "/usr/lib/python2.6/ssl.py", line 293, in do_handshake
self._sslobj.do_handshake()
ssl.SSLError: [Errno 1] _ssl.c:480: error:140770FC:SSL routines:SSL23_GET_SERVER_HELLO:unknown protocol
So how can I do the SSL handshake with the remote example.com server?
EDIT: I'm pretty sure that no additional data is available before my second call to wrap_socket because calling sock.recv(1) blocks indefinitely.

This should work if the CONNECT string is rewritten as follows:
CONNECT = "CONNECT %s:%s HTTP/1.0\r\nConnection: close\r\n\r\n" % (server, port)
Not sure why this works, but maybe it has something to do with the proxy I'm using. Here's an example code:
from OpenSSL import SSL
import socket
def verify_cb(conn, cert, errun, depth, ok):
return True
server = 'mail.google.com'
port = 443
PROXY_ADDR = ("proxy.example.com", 3128)
CONNECT = "CONNECT %s:%s HTTP/1.0\r\nConnection: close\r\n\r\n" % (server, port)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(PROXY_ADDR)
s.send(CONNECT)
print s.recv(4096)
ctx = SSL.Context(SSL.SSLv23_METHOD)
ctx.set_verify(SSL.VERIFY_PEER, verify_cb)
ss = SSL.Connection(ctx, s)
ss.set_connect_state()
ss.do_handshake()
cert = ss.get_peer_certificate()
print cert.get_subject()
ss.shutdown()
ss.close()
Note how the socket is first opened and then open socket placed in SSL context. Then I manually initialize SSL handshake. And output:
HTTP/1.1 200 Connection established
<X509Name object '/C=US/ST=California/L=Mountain View/O=Google Inc/CN=mail.google.com'>
It's based on pyOpenSSL because I needed to fetch invalid certificates too and Python built-in ssl module will always try to verify the certificate if it's received.

Judging from the API of the OpenSSL and GnuTLS library, stacking a SSLSocket onto a SSLSocket is actually not straightforwardly possible as they provide special read/write functions to implement the encryption, which they are not able to use themselves when wrapping a pre-existing SSLSocket.
The error is therefore caused by the inner SSLSocket directly reading from the system socket and not from the outer SSLSocket. This ends in sending data not belonging to the outer SSL session, which ends badly and for sure never returns a valid ServerHello.
Concluding from that, I would say there is no simple way to implement what you (and actually myself) would like to accomplish.

Finally I got somewhere expanding on #kravietz and #02strich answers.
Here's the code
import threading
import select
import socket
import ssl
server = 'mail.google.com'
port = 443
PROXY = ("localhost", 4433)
CONNECT = "CONNECT %s:%s HTTP/1.0\r\nConnection: close\r\n\r\n" % (server, port)
class ForwardedSocket(threading.Thread):
def __init__(self, s, **kwargs):
threading.Thread.__init__(self)
self.dest = s
self.oursraw, self.theirsraw = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
self.theirs = socket.socket(_sock=self.theirsraw)
self.start()
self.ours = ssl.wrap_socket(socket.socket(_sock=self.oursraw), **kwargs)
def run(self):
rl, wl, xl = select.select([self.dest, self.theirs], [], [], 1)
print rl, wl, xl
# FIXME write may block
if self.theirs in rl:
self.dest.send(self.theirs.recv(4096))
if self.dest in rl:
self.theirs.send(self.dest.recv(4096))
def recv(self, *args):
return self.ours.recv(*args)
def send(self, *args):
return self.outs.recv(*args)
def test():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(PROXY)
s = ssl.wrap_socket(s, ciphers="ALL:aNULL:eNULL")
s.send(CONNECT)
resp = s.read(4096)
print (resp, )
fs = ForwardedSocket(s, ciphers="ALL:aNULL:eNULL")
fs.send("foobar")
Don't mind custom cihpers=, that only because I didn't want to deal with certificates.
And there's depth-1 ssl output, showing CONNECT, my response to it ssagd and depth-2 ssl negotiation and binary rubbish:
[dima#bmg ~]$ openssl s_server -nocert -cipher "ALL:aNULL:eNULL"
Using default temp DH parameters
Using default temp ECDH parameters
ACCEPT
-----BEGIN SSL SESSION PARAMETERS-----
MHUCAQECAgMDBALAGQQgmn6XfJt8ru+edj6BXljltJf43Sz6AmacYM/dSmrhgl4E
MOztEauhPoixCwS84DL29MD/OxuxuvG5tnkN59ikoqtfrnCKsk8Y9JtUU9zuaDFV
ZaEGAgRSnJ81ogQCAgEspAYEBAEAAAA=
-----END SSL SESSION PARAMETERS-----
Shared ciphers: [snipped]
CIPHER is AECDH-AES256-SHA
Secure Renegotiation IS supported
CONNECT mail.google.com:443 HTTP/1.0
Connection: close
sagq
�u\�0�,�(�$��
�"�!��kj98���� �m:��2�.�*�&���=5�����
��/�+�'�#�� ����g#32��ED���l4�F�1�-�)�%���</�A������
�� ������
�;��A��q�J&O��y�l

It doesn't sound like there's anything wrong with what you're doing; it's certainly possible to call wrap_socket() on an existing SSLSocket.
The 'unknown protocol' error can occur (amongst other reasons) if there's extra data waiting to be read on the socket at the point you call wrap_socket(), for instance an extra \r\n or an HTTP error (due to a missing cert on the server end, for instance). Are you certain you've read everything available at that point?
If you can force the first SSL channel to use a "plain" RSA cipher (i.e. non-Diffie-Hellman) then you may be able to use Wireshark to decrypt the stream to see what's going on.

Building on #kravietz answer. Here is a version that works in Python3 through a Squid proxy:
from OpenSSL import SSL
import socket
def verify_cb(conn, cert, errun, depth, ok):
return True
server = 'mail.google.com'
port = 443
PROXY_ADDR = ("<proxy_server>", 3128)
CONNECT = "CONNECT %s:%s HTTP/1.0\r\nConnection: close\r\n\r\n" % (server, port)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(PROXY_ADDR)
s.send(str.encode(CONNECT))
s.recv(4096)
ctx = SSL.Context(SSL.SSLv23_METHOD)
ctx.set_verify(SSL.VERIFY_PEER, verify_cb)
ss = SSL.Connection(ctx, s)
ss.set_connect_state()
ss.do_handshake()
cert = ss.get_peer_certificate()
print(cert.get_subject())
ss.shutdown()
ss.close()
This works in Python 2 also.

Related

What details about the host machine does python send to the web server while establishing a connection through sockets?

I am building a passive reconnaissance tool and one of it functionality is to get certificate info about a domain.
What details of my machine are being to sent to the webserver?
The below code is used to get the certificate info.
from OpenSSL import SSL
from cryptography import x509
from cryptography.x509.oid import NameOID
import idna
from socket import socket
from collections import namedtuple
HostInfo = namedtuple(field_names='cert hostname peername', typename='HostInfo')
HOSTS = [
('google.com', 443),
('yahoo.com', 443),
('yahoo.com', 443),
]
def get_certificate(hostname, port):
hostname_idna = idna.encode(hostname)
sock = socket()
sock.connect((hostname, port))
peername = sock.getpeername()
ctx = SSL.Context(SSL.SSLv23_METHOD) # most compatible
ctx.check_hostname = False
ctx.verify_mode = SSL.VERIFY_NONE
sock_ssl = SSL.Connection(ctx, sock)
sock_ssl.set_connect_state()
sock_ssl.set_tlsext_host_name(hostname_idna)
sock_ssl.do_handshake()
cert = sock_ssl.get_peer_certificate()
crypto_cert = cert.to_cryptography()
sock_ssl.close()
sock.close()
return HostInfo(cert=crypto_cert, peername=peername, hostname=hostname)
import concurrent.futures
if __name__ == '__main__':
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as e:
for hostinfo in e.map(lambda x: get_certificate(x[0], x[1]), HOSTS):
print_basic_info(hostinfo)
Is my IP address being sent?
Is python sending any user-agent and what other details are being sent while establishing a connection?
While Python doesn't explicitly send your IP, it is always sent with any TCP request. If it isn't sent, you cannot receive the response from the server.
According to http://evanhahn.com/python-requests-library-useragent, the useragent is
python-requests/{package version} {runtime}/{runtime version} {uname}/{uname -r} when using python-requests, but you can override it by setting the request header.
However, you are not using python-requests but raw sockets, so no data is transferred except what you tell it to (and your IP and some metadata for the TCP connection)

Python ssl unable to connect to TLS1.2 server with TLS1.2 client

I'm working on a python3 socket + ssl server to use for data exchange between a server and a client. I have made a client that works with Google.com, python.org and my own apache2 web server.
When I fire up my server and try to connect via open ssl with
openssl s_client -connect myserver.com:8443
it returns:
CONNECTED(00000003)
140035580617152:error:1408F10B:SSL routines:ssl3_get_record:wrong version number:../ssl/record/ssl3_record.c:252:
---
no peer certificate available
---
No client certificate CA names sent
---
SSL handshake has read 5 bytes and written 176 bytes
Verification: OK
---
New, (NONE), Cipher is (NONE)
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
Protocol : TLSv1.2
Cipher : 0000
Session-ID:
Session-ID-ctx:
Master-Key:
PSK identity: None
PSK identity hint: None
SRP username: None
Start Time: 1547929149
Timeout : 7200 (sec)
Verify return code: 0 (ok)
Extended master secret: no
---
I'm not sure about this but it looks like TLSv1.2 is supported.
When I try to connect to it with my client though I get the following error
Traceback (most recent call last):
File "client-1.py", line 38, in <module>
sock.connect((HOST, PORT))
File "/usr/lib/python3.6/ssl.py", line 1109, in connect
self._real_connect(addr, False)
File "/usr/lib/python3.6/ssl.py", line 1100, in _real_connect
self.do_handshake()
File "/usr/lib/python3.6/ssl.py", line 1077, 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: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:847)
The weird part is then when I use my client to connect to, for example, my own apache2 server (with ssl) it returns TLSv1.2 which really makes me wonder whether the problem lies with my client or my server
I have already tried to use different TLS / SSL versions on the server and the client but none of then have worked so far.
One of the other things I tried was updating OpenSSL and as of 1/19/2019 it is on the newest version available on Ubuntu 18.04
My server looks as follows
import socket
import sys
from _thread import *
import ssl
context = ssl.SSLContext()
ssl.PROTOCOL_TLS_SERVER
#context.load_cert_chain(certfile="ssl/localhost/localhost.crt", keyfile="ssl/localhost/localhost.key")
context.load_cert_chain(certfile="ssl/certificate.crt", keyfile="ssl/private.key")
host = ''
port = 8443
print(port)
buffer = 134217700 #128 MiB
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
def log_client_to_file(ip, port, data, file):
#context manager
with open(file, 'a') as f:
f.write("User sent data from:\n\tIP: %s\n\tPort: %s\nData:\n%s\n\n" % (ip, port, data))
#close file
f.close()
def conn_process(buffer):
data_tmp = conn.recv(buffer)
data = str.encode('')
while len(data_tmp) > 2:
data += data_tmp
data_tmp = conn.recv(buffer
if len(data_tmp) < 2:
data += data_tmp
break
return data
try:
s.bind((host,port))
except socket.error as e:
print(str(e))
s.listen(4)
print('Server is up and waiting for connection')
def client_threaded(conn, ip, port, file):
conn.send(str.encode('Connected'))
while True:
data = conn_process(buffer)
reply = 'Server output: %s' % data.decode('utf-8')
if not data:
break
conn.sendall(str.encode(reply))
log_client_to_file(ip, port, data, file)
while True:
conn, addr = s.accept()
print('connected to: %s:%s' % (addr[0], str(addr[1])))
start_new_thread(client_threaded, (conn, addr[0], str(addr[1]), 'connections.log'))
s.close()
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Now, my client is built up like this,
import socket
import ssl
HOST = 'myserver.com'
PORT = 8443
args = ssl.SSLContext()
ssl.PROTOCOL_TLS_CLIENT
args.verify_mode = ssl.CERT_NONE
args.check_hostname = False
#ssl.ca_certs="ssl/ca_bundle.crt",
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock = args.wrap_socket(s, server_hostname=HOST)
sock.connect((HOST, PORT))
print(sock.version())
Note: since I am working with self signed certificates for testing purposes I do not validate them yet
Since Both the client and the server use TLS I expected the connection not to be an issue, but I keep getting the aforementioned error,
ssl.SSLError: [SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:847) which surprises me since I can't find any errors.
Maybe you guys know what I am doing wrong and how I can fix it
Note, I am using python 3.6 and OpenSSL 1.1.0g 2 Nov 2017
I'm not sure about this but it looks like TLSv1.2 is supported.
No it doesn't.
... ssl3_get_record:wrong version number:../ssl/record/ssl3_record.c:252:
...
SSL handshake has read 5 bytes and written 176 bytes
...
Protocol : TLSv1.2
Cipher : 0000
This shows that the clients starts the TLS handshake with a ClientHello (176 bytes) and gets only 5 bytes back from the server, which is too short for the expected response inside the TLS handshake. And these 5 bytes don't contain a TLS version number and that's why it croaks with wrong version number. This is also indicated by no common cipher: Cipher : 0000.
Looking at the code of your server it seems to me that you are setting up some SSL context at the beginning (and the shown code seems to be broken too) but never actually use it, i.e. your server is plain TCP only. No wonder the client croaks about it.

How can a Python proxy server (using SSL socket) pretend to be an HTTPS server and specify my own keys to get decrypted data?

I'm trying to handle HTTPS connections from web browsers in decrypted data using Python SSL socket. I thought I can do this like a man-in-the-middle attack via my Python proxy server program, by pretending to be an HTTPS server, and I've already generated my keys using OpenSSL. However, browser treats my program as a proxy server, not as an HTTPS server. How can my program pretend to be an HTTPS server and specify my own keys to communicate with the browser?
I'm using Chrome and a Chrome extension SwitchySharp to forward requests to another local port (HTTP to 127.0.0.1:8080, and HTTPS to 127.0.0.1:8081). It works well for HTTP requests, but doesn't work for HTTPS.
Background Knowledge
When handling HTTPS with a proxy server, the browser first sends a CONNECT request to proxy:
CONNECT xxx.xxx:443 HTTP/1.1
Host: xxx.xxx:443
Proxy-Connection: keep-alive
(some other headers)
and proxy creates a tunnel to target server, and then browser sends encrypted data via this tunnel. Therefore, the proxy server never gets decrypted data.
To get decrypted data, I've tried two ways.
First, I tried ssl.wrap_socket():
import socket
import ssl
import thread
def handle(conn):
request = conn.recv(4096)
print request
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('127.0.0.1', 8081))
sock.listen(10)
sock = ssl.wrap_socket(sock, 'pkey.pem', 'cert.pem', True)
while True:
conn, addr = sock.accept()
thread.start_new_thread(handle, (conn,))
But I got the following error:
Traceback (most recent call last):
File "https.py", line 12, in <module>
conn, addr = sock.accept()
File "C:\Python27\lib\ssl.py", line 898, in accept
server_side=True)
File "C:\Python27\lib\ssl.py", line 369, in wrap_socket
_context=self)
File "C:\Python27\lib\ssl.py", line 617, in __init__
self.do_handshake()
File "C:\Python27\lib\ssl.py", line 846, in do_handshake
self._sslobj.do_handshake()
ssl.SSLError: [SSL: HTTPS_PROXY_REQUEST] https proxy request (_ssl.c:726)
The cause of this error is that the CONNECT request is not encrypted so ssl can't handle it. I have no idea how to get rid of this error.
Then, I tried the following code:
import socket
import thread
def handler(conn, tunn):
while True:
data = conn.recv(40960)
if data:
tunn.sendall(data)
else:
break
def handle(conn):
request = conn.recv(40960)
print request
i = request.find(' ') + 1
j = request.find(' ', i)
host, port = request[i : j].split(':')
port = int(port)
tunn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tunn.connect((host, port))
conn.sendall('HTTP/1.1 200 Connection Established\r\n\r\n')
thread.start_new_thread(handler, (conn, tunn))
thread.start_new_thread(handler, (tunn, conn))
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('127.0.0.1', 8081))
sock.listen(10)
while True:
conn, addr = sock.accept()
thread.start_new_thread(handle, (conn,))
It worked (without ssl module), but I can't specify my own keys and therefore can't get decrypted data.
It seems that my program has to pretend to be an HTTPS server to "directly" communicate with browser, so I can specify my own key and get decrypted data. But I really have no idea how to pretend to be an HTTPS server and specify my own keys.
Can anyone help me? Thanks in advance.
UPD: I finally solved this problem using ssl module.
I was wrong --- I thought that after connection established the browser sends encrypted data. But it doesn't. In fact, those unreadable data are just SSL handshake. So what I need to do is just to use ssl.wrap_socket() after connection established. Besides, I have to additionally call do_handshake() of the ssl-wrapped socket.
import socket
import ssl
import thread
def handle(tid, conn):
request = conn.recv(40960)
i = request.find(' ') + 1
j = request.find(' ', i)
if request[i : j].find(':') != -1:
host, port = request[i : j].split(':')
else:
host, port = request[i : j], 80
port = int(port)
tunn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tunn.connect((host, port))
conn.sendall('HTTP/1.1 200 Connection Established\r\n\r\n')
conn_s = ssl.wrap_socket(conn, keyfile = 'cert/userkey.pem', certfile = 'cert/usercert.pem', server_side = True, do_handshake_on_connect = False)
conn_s.do_handshake()
data = conn_s.recv(40960)
print data
conn_s.sendall('HTTP/1.1 200 OK\r\n\r\n<h1>Hello, world!</h1>')
conn_s.close()
tunn.close()
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('127.0.0.1', 8081))
sock.listen(10)
buf = {}
while True:
conn, addr = sock.accept()
thread.start_new_thread(handle, (conn,))
Anyway, it doesn't pretend to be an HTTPS server -- it still behaves as a proxy server. I just misunderstood those unreadable data.
Thanks to those who helped me in the comments.

Python SSL verification fails

I am trying to setup a Python TCP client-server session, but the client side is throwing this error: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:777)
I am using a freshly generated LetsEncrypt certificate. I've stored the fullchain.pem (public certificate) on my computer which I'm running the client from, and yet it can't verify the authenticity. I am sure that fullchain.pem is the public key and privkey.pem is the private key, so I don't see why this isn't working. I've also tried using cert.pem for public too, which does not work either. Is anyone able to provide some insight on as to why the verification fails? Below is the client and server programs.
Client:
import sys
import socket
import os
import ssl
from backports.ssl_match_hostname import match_hostname, CertificateError
def connect(hostname, port, message):
"""
Connects to the hostname on the specified port and sends a message
:param hostname:
:param port:
:param message:
:return:
"""
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # create a TCP socket
try:
s.connect((hostname, port)) # establish a TCP connection to the host
#ca_certs_path = os.path.join(os.path.dirname(sys.argv[0]), 'fullchain.pem')
ctx = ssl.create_default_context(purpose=ssl.Purpose.SERVER_AUTH)
#sslsock = ctx.wrap_socket(s, ssl_version=ssl.PROTOCOL_SSLv23, cert_reqs=ssl.CERT_REQUIRED, ca_certs=ca_certs_path)
sslsock = ctx.wrap_socket(s, server_hostname='encryptio.tk')
# Check if the server really matches the hostname to which we are trying to connect
match_hostname(sslsock.getpeercert(), hostname)
sslsock.send(message.encode('utf-8')) # send the message
except Exception as e:
sys.exit('[-]' + str(e))
def main():
connect('encryptio.tk', 12000, 'Client says hello') # replace '' with the hostname, 12000 as the port, and 'Hello' as the message
if __name__ == '__main__':
main()
Server:
import sys
import socket
import ssl
from backports.ssl_match_hostname import match_hostname, CertificateError
# Servers do not care whether clients connect with certificates
def listen(port):
"""
Listens on the specified port and accepts incoming connections
:param port:
:return:
"""
# server_socket - welcoming socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind(('', port)) # establish welcoming socket
ca_certs_path = '/etc/letsencrypt/live/encryptio.tk/fullchain.pem'
priv_certs_path = '/etc/letsencrypt/live/encryptio.tk/privkey.pem'
# listen for TCP connection requests.
server_socket.listen(1) # parameter specifies the maximum number of queued connections (at least 1)
ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
ctx.load_cert_chain(certfile=ca_certs_path, keyfile=priv_certs_path)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
print('[*] The server is ready to receive')
while 1:
try:
# When a client knocks on this door, the program invokes the accept() method for
# server_socket, which creates a new socket in the server, called connec-
# tion_ocket, dedicated to this particular client. The client and server then complete
# the handshaking, creating a TCP connection between the client’s client_socket
# and the server’s connection_socket
#connection_socket, addr = server_socket.accept()
#sslsock = ssl.wrap_socket(server_socket, ssl_version=ssl.PROTOCOL_SSLv23, cert_reqs=ssl.CERT_REQUIRED, server_side=True, certfile=ca_certs_path, keyfile=priv_certs_path)
connection_socket, addr = server_socket.accept()
sslsock = ctx.wrap_socket(connection_socket, server_side=True)
data = sslsock.read()
#message = connection_socket.recv(1500)
print('[+] From' + str(addr) + ': ' +
data.decode('utf-8'))
except Exception as e:
sys.exit('[-] ' + str(e))
#connection_socket.close()
sslsock.shutdown(socket.SHUT_RDWR)
sslsock.close()
def main():
listen(12000)
if __name__ == '__main__':
main()
EDIT:
I tried setting the CA in the client side program to a root certificate, and now I get this error:
[X509] PEM lib (_ssl.c:3053)
In your client you have this:
ca_certs_path = os.path.join(os.path.dirname(sys.argv[0]), 'fullchain.pem')
sslsock = ssl.wrap_socket(s, ssl_version=ssl.PROTOCOL_SSLv23,
cert_reqs=ssl.CERT_REQUIRED, ca_certs=ca_certs_path)
The default for ca_certs is None in ssl.wrap_socket which means that you have to provide a useful path if you use the non-default ssl.CERT_REQUIRED. Only, the path you provide seems to be the certificate chain you use in your server, i.e. server certificate and chain certificates but not the root certificate. Instead the ca_certs on the client side should only contain the root certificates, since this is the trust anchor which is used to build the trust chain using the certificates sent by the server (i.e. server certificate and chain).
Instead of using your own CA store you might also use the system wide store. With current versions of Python you can simply use the following code which creates a SSL context using the default CA store and with certificate and hostname validation enabled and then upgrades the socket to SSL and checks the certificate properly:
s.connect((hostname, port))
ctx = ssl.create_default_context()
sslsock = ctx.wrap_socket(s, server_hostname='example.com')

"ssl.SSLError: ... No shared cipher" when trying to handle HTTPS requests webserver

I'm trying to extend a web server I made to handle HTTPS requests via SSL.
My prof said we should use ssl.wrap_socket and gave us the cipher to use.
Here is what I have so far:
from socket import *
import ssl
serverSocket = socket(AF_INET, SOCK_STREAM)
serverPort = 443
serverSocket.bind(("", serverPort))
serverSocket.listen(1)
while True:
print ('Ready to serve...')
connectionSocket, addr = serverSocket.accept()
connectionSocket = ssl.wrap_socket(connectionSocket,
keyfile="./server.key",
certfile="./server.pem",
server_side=True,
cert_reqs=ssl.CERT_NONE,
ssl_version=ssl.PROTOCOL_SSLv23,
ca_certs=None,
do_handshake_on_connect=True,
suppress_ragged_eofs=True,
ciphers="AES128-SHA256")
try:
message = (connectionSocket.recv(1024)).decode('utf-8')
filename = message.split()[1]
f = open(filename[1:],'rb')
outputdata = f.read()
f.close()
connectionSocket.send(b'HTTP/1.1 200 OK\r\n\r\n')
connectionSocket.send(outputdata)
connectionSocket.send(b'\r\n')
connectionSocket.shutdown(SHUT_RDWR)
connectionSocket.close()
except IOError:
connectionSocket.send(b'HTTP/1.1 404 Not Found\r\n\r\n')
connectionSocket.send(b'<html><head></head><body><h1>404 Not Found</h1></body></html>\r\n')
connectionSocket.shutdown(SHUT_RDWR)
connectionSocket.close()
serverSocket.close()
I've tested this in the command line with the following code and it seems to work. It shows me the right info about the SSL sessions such as protocol, cipher, session-ID, Master-key and the contents of index.html:
openssl s_client -connect localhost:443
GET /index.html
For the next section of the assignment I have to put "https://localhost:443/index.html" into my browser, but my webserver crashes with this error:
ssl.SSLError: [SSL: NO_SHARED_CIPHER] no shared cipher (_ssl.c:661)
What is wrong with my code?
ciphers="AES128-SHA256")
According to SSLLabs client tests AES128-SHA256 (called TLS_RSA_WITH_AES_128_CBC_SHA256 in the standards) is not supported by major browsers like Chrome or Firefox. Accepting only this single cipher on the server which is not supported by major clients makes that no common cipher can be found, i.e.
ssl.SSLError: [SSL: NO_SHARED_CIPHER] no shared cipher (_ssl.c:661)
The fix is to accept more ciphers on the server to include ciphers supported by the browser. See mozilla wiki for useful settings which can not only applied to major web servers but also your small server.

Categories

Resources