# -*- coding: utf-8 -*- import ssl import http.client import urllib.request, urllib.error, urllib.parse import logging import socks import socket import settings import state from utils import get_service_id logger = logging.getLogger(__name__) class InvalidCertificateException(http.client.HTTPException, urllib.error.URLError): def __init__(self, service_id, cert, reason): http.client.HTTPException.__init__(self) self._service_id = service_id self._cert_service_id = get_service_id(cert=cert) self.reason = reason def __str__(self): return ('%s (local) != %s (remote) (%s)\n' % (self._service_id, self._cert_service_id, self.reason)) def is_local(host): return len([p for p in host.split(':')[0].split('.') if p.isdigit()]) == 4 def getaddrinfo(*args): return [(socket.AF_INET, socket.SOCK_STREAM, 6, '', (args[0], args[1]))] def create_tor_connection(address, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, source_address=None): host, port = address err = None af = socket.AF_INET socktype = socket.SOCK_STREAM proto = 6 sa = address sock = None try: sock = socks.socksocket(af, socktype, proto) if timeout is not socket._GLOBAL_DEFAULT_TIMEOUT: sock.settimeout(timeout) socks_port = state.tor.socks_port if state.tor else 9150 sock.set_proxy(socks.SOCKS5, "localhost", socks_port, True) if source_address: sock.bind(source_address) sock.connect(sa) return sock except socket.error as _: err = _ if sock is not None: sock.close() if err is not None: raise err else: raise sock.error("getaddrinfo returns an empty list") class TorHTTPSConnection(http.client.HTTPSConnection): def __init__(self, host, port=None, service_id=None, check_hostname=None, context=None, **kwargs): self._service_id = service_id if self._service_id: if hasattr(ssl, '_create_default_https_context'): context = ssl._create_default_https_context() elif hasattr(ssl, '_create_stdlib_context'): context = ssl._create_stdlib_context() if context: context.check_hostname = False context.verify_mode = ssl.CERT_NONE # tor keys are still 1024 bit, debian started to require 2048 by default, # try to lower requirements to 1024 if needed try: context.load_cert_chain(settings.ssl_cert_path, settings.ssl_key_path) except ssl.SSLError: context.set_ciphers('DEFAULT@SECLEVEL=1') context.load_cert_chain(settings.ssl_cert_path, settings.ssl_key_path) context.load_default_certs() http.client.HTTPSConnection.__init__(self, host, port, check_hostname=check_hostname, context=context, **kwargs) if not is_local(host): self._create_connection = create_tor_connection def _check_service_id(self, cert): service_id = get_service_id(cert=cert) if service_id != self._service_id: logger.debug('service_id mismatch: %s expected: %s', service_id, self._service_id) return service_id == self._service_id def connect(self): http.client.HTTPSConnection.connect(self) if self._service_id: cert = self.sock.getpeercert(binary_form=True) if not self._check_service_id(cert): raise InvalidCertificateException(self._service_id, cert, 'service_id mismatch') #logger.debug('CIPHER %s VERSION %s', self.sock.cipher(), self.sock.ssl_version) class TorHTTPSHandler(urllib.request.HTTPSHandler): def __init__(self, debuglevel=0, context=None, check_hostname=None, service_id=None): urllib.request.AbstractHTTPHandler.__init__(self, debuglevel) self._context = context self._check_hostname = check_hostname self._service_id = service_id def https_open(self, req): return self.do_open(TorHTTPSConnection, req, context=self._context, check_hostname=self._check_hostname, service_id=self._service_id) class TorHTTPConnection(http.client.HTTPConnection): def __init__(self, host, port=None, **kwargs): http.client.HTTPConnection.__init__(self, host, port, **kwargs) if not is_local(host): self._create_connection = create_tor_connection class TorHTTPHandler(urllib.request.HTTPHandler): def http_open(self, req): return self.do_open(TorHTTPConnection, req) def get_opener(service_id=None): handler = TorHTTPSHandler(service_id=service_id) opener = urllib.request.build_opener(handler, TorHTTPHandler()) return opener