3

How would you force mechanize to use SSLv3 for HTTPS URLs that require it? If I try to use mechanize with all SSLv3-only URLs, I get the error:

URLError: <urlopen error [Errno 1] _ssl.c:504: error:140773E8:SSL routines:SSL23_GET_SERVER_HELLO:reason(1000)>
Cerin
  • 60,957
  • 96
  • 316
  • 522
  • 1
    From the following bug report, this may help: http://bugs.python.org/issue11220 Also I think there should be a ``verify_mode`` option somewhere, but I can't find it in the ``mechanize`` docs :/. and ``def add_client_certificate(self, url, key_file, cert_file):`` in mechanize/_useragent.py might help, but sorry I can't find anything definite using right now :( – Eiyrioü von Kauyf Jul 29 '13 at 15:30
  • @EiyrioüvonKauyf, Yeah, I stumbled across that page myself. That's why I'm trying to force it to use SSLv3, "The problem is the server strictly accepts SSLv3 only and urllib and http.client send SSLv23 protocol." They even provide a workaround for urllib in the form of a custom opener, but I don't know how to adapt it to mechanize. – Cerin Jul 29 '13 at 19:01

3 Answers3

2

A dirty answer... not requiring patching.

import ssl
from ssl import PROTOCOL_SSLv23, PROTOCOL_SSLv3, CERT_NONE, SSLSocket

def monkey_wrap_socket(sock, keyfile=None, certfile=None,
                server_side=False, cert_reqs=CERT_NONE,
                ssl_version=PROTOCOL_SSLv23, ca_certs=None,
                do_handshake_on_connect=True,
                suppress_ragged_eofs=True, ciphers=None):
    ssl_version=PROTOCOL_SSLv3
    return SSLSocket(sock, keyfile=keyfile, certfile=certfile,
                     server_side=server_side, cert_reqs=cert_reqs,
                     ssl_version=ssl_version, ca_certs=ca_certs,
                     do_handshake_on_connect=do_handshake_on_connect,
                     suppress_ragged_eofs=suppress_ragged_eofs,
                     ciphers=ciphers)

ssl.wrap_socket = monkey_wrap_socket

... before your code.

Pietro Battiston
  • 7,930
  • 3
  • 42
  • 45
1

The last comment on the Python issue Eiyrioü von Kauyf mentions above is the solution I implemented in my forked version of mechanize. The diff of mechanize/_opener.py follows. It fixes mechanize.urlopen(), but not mechanize.Browser()'s open() method:

diff --git a/mechanize/_opener.py b/mechanize/_opener.py
index ad8412d..e6d1ebc 100644
--- a/mechanize/_opener.py
+++ b/mechanize/_opener.py
@@ -25,9 +25,27 @@ import _rfc3986
 import _sockettimeout
 import _urllib2_fork
 from _util import isstringlike
+import ssl, socket

 open_file = open

+class HTTPSConnectionV3(httplib.HTTPSConnection):
+    def __init__(self, *args, **kwargs):
+        httplib.HTTPSConnection.__init__(self, *args, **kwargs)
+
+    def connect(self):
+        sock = socket.create_connection((self.host, self.port), self.timeout)
+        if self._tunnel_host:
+            self.sock = sock
+            self._tunnel()
+        try:
+            self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file, ssl_version=ssl.PROTOCOL_SSLv3)
+        except ssl.SSLError, e:
+            self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file, ssl_version=ssl.PROTOCOL_SSLv23)
+
+class HTTPSHandlerV3(urllib2.HTTPSHandler):
+    def https_open(self, req):
+        return self.do_open(HTTPSConnectionV3, req)

 class ContentTooShortError(urllib2.URLError):
     def __init__(self, reason, result):
@@ -370,7 +388,7 @@ class OpenerFactory:
         _urllib2_fork.HTTPErrorProcessor,
         ]
     if hasattr(httplib, 'HTTPS'):
-        default_classes.append(_urllib2_fork.HTTPSHandler)
+        default_classes.append(HTTPSHandlerV3)
     handlers = []
     replacement_handlers = []
Michael Cramer
  • 5,080
  • 1
  • 20
  • 16
0

You can monkey-patch ssl.wrap_socket() to use TLSv1, which, after the Poodle vulnerability, seems to be the only viable left. This will force all SSL connections to use TLSv1 regardless of the higher level library.

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)
chnrxn
  • 1,349
  • 1
  • 16
  • 17