Question

I'm trying to use Python to test a web server. I have nearly no experience with Python, but encouraged to use it because its easy to learn and simple to do things with (someone else's opinion, not mine at the moment). The script I am using is:

s1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s2 = ssl.wrap_socket(s1,
                     ca_certs="./pki/signing-dss-cert.pem",
                     cert_reqs=ssl.CERT_REQUIRED,
                     ssl_version=ssl.PROTOCOL_TLSv1,
                     server_hostname="localhost")

s2.connect( ("localhost", 8443) )

s2.send("GET / ")
time.sleep(1)
s2.send("HTTP/1.1")

The error is:

Traceback (most recent call last):
  File "./fetch.sh", line 10, in <module>
    server_hostname="localhost")
TypeError: wrap_socket() got an unexpected keyword argument 'server_hostname'

I've also tried using servername, name, hostname and sni with no joy.

The Python docs don't mention SNI (TLS/SSL wrapper for socket objects and SSL wiki page). But I know the patch for SNI and server_hostname was incorporated 4 years ago in 2010 (Add a *server_hostname* argument to SSLContext.wrap_socket, changeset 65593:846c0e1342d0).

The equivalent OpenSSL call I need access to is SSL_set_tlsext_host_name.

How do I specify the SNI hostname? (Eventually, I'll need to set it to an arbitrary name because I am testing a proxy).

Was it helpful?

Solution

The patch you're mentioning is for Python 3.2, and you're using Python 2.7. Issue 5639 also seems to indicate there is no plan to back-port SNI support for Python 2.7.

You could wrap the socket with pyOpenSSL instead (its Connection class has a set_tlsext_host_name since version 0.13. (I'm not sure which version comes with Debian 7.3, you might want to set up a virtualenv and upgrade to a newer version locally, if needed.)

There is an SNI example is the pyOpenSSL repository.

If you want your usage of wrap_socket to be more compatible with the trick were you replace the value of sock in an httplib connection, you could have a look at how urllib3 does this with pyOpenSSL. Essentially, it creates an OpenSSL.SSL.Connection from an existing socket, but since that connection isn't compatible with a socket, it wraps it into a class that implements the required method.

(By the way, in Python 2.7, urllib, urllib2 and httpconnection don't do any certificate verification at all, unless you implement it yourself by wrapping their sockets.)

EDIT:

Here is a version of your code that should work with Python 3.2. Unfortunately, the server_name parameter isn't in the plain ssl.wrap_socket, only in SSLContext.wrap_socket, but you can use SSLSocket directly.

import socket
import ssl

CA_BUNDLE_FILE="/etc/ssl/certs/ca-certificates.crt"


HOST = "sni.velox.ch"
s1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s2 = ssl.SSLSocket(sock=s1, ca_certs=CA_BUNDLE_FILE,
                     cert_reqs=ssl.CERT_REQUIRED,
                     ssl_version=ssl.PROTOCOL_TLSv1,
                     server_hostname=HOST)

s2.connect((HOST, 443))

s2.send(bytes("GET / HTTP/1.1\n", "UTF-8"))
# This might need to be modified when using another port
s2.send(bytes("Host: %s\n" % (HOST,), "UTF-8"))
s2.send(bytes("\n", "UTF-8"))

# Certainly not the best way to read the response, but it works.
while True:
    x = s2.read()
    if not x:
        break
    print(x)

OTHER TIPS

First connect, then wrap the socket.

import socket, ssl
sock = socket.create_connection( ('localhost', 443) )
sock = ssl.wrap_socket(sock, ca_certs="./pki/signing-dss-cert.pem", cert_reqs=ssl.CERT_REQUIRED, ssl_version=ssl.PROTOCOL_TLSv1)

In case of python2 I also use the following hack sometimes (because the httplib.HTTPSConnection documentation says that it doesn't perform any kind of checking on the https server cert):

import urllib, httplib, ssl

PATH = urllib.quote(u'/'.encode('utf-8'))
conn = httplib.HTTPConnection('www.google.com', 443)
conn.connect()
try:
    conn.sock = ssl.wrap_socket(conn.sock, cert_reqs=ssl.CERT_REQUIRED, ca_certs=CA_BUNDLE_FILE, ssl_version=VERSION)
    conn.request('GET', PATH)
    resp = conn.getresponse()
    print resp.status
    print resp.read()
finally:
    conn.close()

Note that if you want to communicate with the server then using a http client is often much easier than playing around with raw sockets.

My env is:requests==2.7.0, python-2.7.5-34.el7.x86_64, gevent==1.0.2

Change python version to python-2.7.5-18.el7_1.1.x86_64, problem is solved.

on CentOS:

sudo rpm -Uvh --oldpackage python-devel-2.7.5-18.el7_1.1.x86_64.rpm  python-libs-2.7.5-18.el7_1.1.x86_64.rpm  python-2.7.5-18.el7_1.1.x86_64.rpm

pakages can search on google.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top