153 lines
5.0 KiB
Python
153 lines
5.0 KiB
Python
import http.client
|
|
import socket
|
|
import urllib.request, urllib.error, urllib.parse
|
|
import ssl
|
|
import hashlib
|
|
|
|
from .utils import valid
|
|
|
|
from . import settings
|
|
|
|
import logging
|
|
logger = logging.getLogger('link')
|
|
|
|
|
|
class InvalidCertificateException(http.client.HTTPException, urllib.error.URLError):
|
|
def __init__(self, fingerprint, cert, reason):
|
|
http.client.HTTPException.__init__(self)
|
|
self.fingerprint = fingerprint
|
|
self.cert_fingerprint = hashlib.sha1(cert).hexdigest()
|
|
self.reason = reason
|
|
|
|
def __str__(self):
|
|
return ('%s (local) != %s (remote) (%s)\n' %
|
|
(self.fingerprint, self.cert_fingerprint, self.reason))
|
|
|
|
class CertValidatingHTTPSConnection(http.client.HTTPConnection):
|
|
default_port = http.client.HTTPS_PORT
|
|
|
|
def __init__(self, host, port=None, fingerprint=None, strict=None, **kwargs):
|
|
http.client.HTTPConnection.__init__(self, host, port, strict, **kwargs)
|
|
self.fingerprint = fingerprint
|
|
if self.fingerprint:
|
|
self.cert_reqs = ssl.CERT_REQUIRED
|
|
else:
|
|
self.cert_reqs = ssl.CERT_NONE
|
|
self.cert_reqs = ssl.CERT_NONE
|
|
|
|
def _ValidateCertificateFingerprint(self, cert):
|
|
fingerprint = hashlib.sha1(cert).hexdigest()
|
|
return fingerprint == self.fingerprint
|
|
|
|
def connect(self):
|
|
sock = socket.create_connection((self.host, self.port))
|
|
self.sock = ssl.wrap_socket(sock, cert_reqs=self.cert_reqs)
|
|
#if self.cert_reqs & ssl.CERT_REQUIRED:
|
|
if self.fingerprint:
|
|
cert = self.sock.getpeercert(binary_form=True)
|
|
if not self._ValidateCertificateFingerprint(cert):
|
|
raise InvalidCertificateException(self.fingerprint, cert,
|
|
'fingerprint mismatch')
|
|
#logger.debug('CIPHER %s VERSION %s', self.sock.cipher(), self.sock.ssl_version)
|
|
|
|
class VerifiedHTTPSHandler(urllib.request.HTTPSHandler):
|
|
def __init__(self, **kwargs):
|
|
urllib.request.AbstractHTTPHandler.__init__(self)
|
|
self._connection_args = kwargs
|
|
|
|
def https_open(self, req):
|
|
def http_class_wrapper(host, **kwargs):
|
|
full_kwargs = dict(self._connection_args)
|
|
full_kwargs.update(kwargs)
|
|
if 'timeout' in full_kwargs:
|
|
del full_kwargs['timeout']
|
|
return CertValidatingHTTPSConnection(host, **full_kwargs)
|
|
|
|
try:
|
|
return self.do_open(http_class_wrapper, req)
|
|
except urllib.error.URLError as e:
|
|
if type(e.reason) == ssl.SSLError and e.reason.args[0] == 1:
|
|
raise InvalidCertificateException(self.fingerprint, '',
|
|
e.reason.args[1])
|
|
raise
|
|
|
|
https_request = urllib.request.HTTPSHandler.do_request_
|
|
|
|
def get_opener(fingerprint):
|
|
handler = VerifiedHTTPSHandler(fingerprint=fingerprint)
|
|
opener = urllib.request.build_opener(handler)
|
|
return opener
|
|
|
|
class Response(object):
|
|
headers = {}
|
|
error = None
|
|
body = None
|
|
data = None
|
|
user = None
|
|
code = 200
|
|
|
|
def read(url, body=None, headers={}, fingerprint=None, timeout=None):
|
|
if not timeout:
|
|
timeout = settings.TIMEOUT
|
|
if not body:
|
|
body = None
|
|
opener = get_opener(fingerprint)
|
|
headers = dict(headers)
|
|
request = urllib.request.Request(url, data=body, headers=headers)
|
|
response = Response()
|
|
logger.debug('open %s [%s]', url, fingerprint)
|
|
logger.debug('headers: %s', headers)
|
|
try:
|
|
r = opener.open(request, timeout=timeout)
|
|
except urllib.error.HTTPError as e:
|
|
response.code = e.code
|
|
if e.code >= 500:
|
|
logger.debug('urllib.error.HTTPError %s %s', e, e.code)
|
|
response.error = e
|
|
else:
|
|
response.headers = e.headers
|
|
response.body = e.read()
|
|
return response
|
|
except urllib.error.URLError as e:
|
|
logger.debug('urllib.error.URLError %s', e)
|
|
response.error = e
|
|
response.code = 500
|
|
return response
|
|
except:
|
|
logger.debug('unknown url error', exc_info=1)
|
|
response.error = 'unkown url error'
|
|
response.code = 500
|
|
return response
|
|
|
|
response.headers = r.headers
|
|
response.code = r.getcode()
|
|
sig = r.headers.get('X-Ed25519-Signature')
|
|
if sig:
|
|
key = r.headers.get('X-Ed25519-Key')
|
|
data = r.read()
|
|
if valid(key, data, sig):
|
|
response.data = data
|
|
response.user = key
|
|
else:
|
|
response.error = 'Invalid Signature'
|
|
response.code = 500
|
|
else:
|
|
response.body = r
|
|
logger.debug('response headers: %s', dict(r.headers))
|
|
return response
|
|
|
|
def node_url(node):
|
|
host = node['host']
|
|
port = node['port']
|
|
if ':' in host:
|
|
url = 'https://[%s]:%s' % (host, port)
|
|
else:
|
|
url = 'https://%s:%s' % (host, port)
|
|
return url
|
|
|
|
def can_connect(data):
|
|
r = read(node_url(data), fingerprint=data['cert'], timeout=1)
|
|
if r.error:
|
|
return False
|
|
return True
|