Urllib.urlopen() works on SSLv3 urls with Python 2.6.6 on 1 machine, but not with 2.6.7/2.7.2 on another

StackOverflow https://stackoverflow.com/questions/9835506

Question

Spent a good part of a day on this, and I'm realllly at my wit's end. I have 1 machine "A" with Python 2.6.6/2.7.2 installed, and another machine "B" with Python 2.6.7/2.7.2 installed.

On machine A, I can get a SSLv3-encrypted website with urllib2.urlopen('https://fed.princeton.edu') using Python 2.6.6 but not 2.7.2.

On machine B, I can't get that website using either Python version.

By can't get, I mean that I get the error:

Traceback:
File "/usr/local/lib/python2.7/dist-packages/Django-1.3.1-py2.7.egg/django/core/handlers/base.py" in get_response
  111.                         response = callback(request, *callback_args, **callback_kwargs)
File "/usr/local/lib/python2.7/dist-packages/django_cas-2.0.3-py2.7.egg/django_cas/views.py" in login
  78.         user = auth.authenticate(ticket=ticket, service=service)
File "/usr/local/lib/python2.7/dist-packages/Django-1.3.1-py2.7.egg/django/contrib/auth/__init__.py" in authenticate
  55.             user = backend.authenticate(**credentials)
File "/usr/local/lib/python2.7/dist-packages/django_cas-2.0.3-py2.7.egg/django_cas/backends.py" in authenticate
  72.         username = _verify(ticket, service)
File "/usr/local/lib/python2.7/dist-packages/django_cas-2.0.3-py2.7.egg/django_cas/backends.py" in _verify_cas2
  46.     page = urlopen(url)
File "/usr/lib/python2.7/urllib.py" in urlopen
  84.         return opener.open(url)
File "/usr/lib/python2.7/urllib.py" in open
  205.                 return getattr(self, name)(url)
File "/usr/lib/python2.7/urllib.py" in open_https
  435.             h.endheaders(data)
File "/usr/lib/python2.7/httplib.py" in endheaders
  954.         self._send_output(message_body)
File "/usr/lib/python2.7/httplib.py" in _send_output
  814.         self.send(msg)
File "/usr/lib/python2.7/httplib.py" in send
  776.                 self.connect()
File "/usr/lib/python2.7/httplib.py" in connect
  1161.             self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file)
File "/usr/lib/python2.7/ssl.py" in wrap_socket
  372.                      ciphers=ciphers)
File "/usr/lib/python2.7/ssl.py" in __init__
  134.                 self.do_handshake()
File "/usr/lib/python2.7/ssl.py" in do_handshake
  296.         self._sslobj.do_handshake()

Exception Type: IOError at /login
Exception Value: [Errno socket error] [Errno 1] _ssl.c:503: error:140773F2:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert unexpected message

First, I'm confused that something that works on an earlier Python version doesn't work on the later on Machine A. I'm also very confused that something that works on 2.6.6 doesn't work on 2.6.7 (albeit on different machines). Why would this be?

Now I'm not sure the configuration for Python is the exact same on both, but import _ssl and import httplib; httplib.HTTPSConnection work for all versions on both machines. I've also tried curl -v https://fed.princeton.edu and openssl fed.princeton.edu:https on both machines, and these commands all work.

I've also done some research and found How to use urllib2 to get a webpage using SSLv3 encryption where the author seemed to have given up on urllib for libCurl (I'd rather not since I'm using django-cas, which uses urllib and I don't want to fiddle with that code too much).


Note: I just found http://bugs.python.org/issue11220, and the last post's solution allows me to use urlopen to open up the website. But how can I use their solution (which seems to be to use urllib2.install_opener(urllib2.build_opener(HTTPSHandlerV3()))?) to resolve the my urlopen() in django-cas?

Was it helpful?

Solution

After a bit more experimenting I've just accepted that Python 2.6.6 is OK, but 2.6.7+ has this bug of not being able to fetch SSLv3-encrypted pages via urllib.urlopen().

I solved my problem by simply using the urllib2.install_opener trick at http://bugs.python.org/issue11220, and modded django_cas so that this opener was installed before any urlopen() call.

OTHER TIPS

You can monkey-patch ssl.wrap_socket() by overriding the ssl_version keyword parameter. The following code can be used as-is. Put this before urlopen().

import ssl
from functools import wraps
def sslwrap(func):
    @wraps(func)
    def bar(*args, **kw):
        kw['ssl_version'] = ssl.PROTOCOL_TLSv1
        return func(*args, **kw)
    return bar

ssl.wrap_socket = sslwrap(ssl.wrap_socket)

EDIT: I've updated the code above after realizing that functools.partial doesn't actually return a function, and would not be suitable in this case. Chunky as it might look, the above code is still the best solution I know so far.

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