From 142bba7a6d4aefd039fbee044936501144628112 Mon Sep 17 00:00:00 2001 From: buttoj3 Date: Mon, 11 Apr 2011 12:12:16 +0100 Subject: [PATCH 1/4] Work towards the security stuff described at http://code.google.com/p/webfinger/wiki/WebFingerProtocol The code now looks for the host-meta file via https. If it doesn't find it it falls back to http, but causes the response's 'insecure' attribute to be set to True. The 'insecure' attribute also gets set to True if the user's XRD URL is not an https:// url. This doesn't do any checking of certificate validity, or check whether the XML is signed. --- pywebfinger.py | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/pywebfinger.py b/pywebfinger.py index d960652..886265a 100644 --- a/pywebfinger.py +++ b/pywebfinger.py @@ -22,7 +22,8 @@ class WebFingerExpection(Exception): class WebFingerResponse(object): - def __init__(self, xrd): + def __init__(self, xrd, insecure): + self.insecure = insecure self._xrd = xrd def __getattr__(self, name): @@ -32,9 +33,8 @@ class WebFingerResponse(object): class WebFingerClient(object): - def __init__(self, host, secure=False): + def __init__(self, host): self._host = host - self._secure = secure self._opener = urllib2.build_opener(urllib2.HTTPRedirectHandler()) self._opener.addheaders = [('User-agent', 'python-webfinger')] @@ -47,39 +47,48 @@ class WebFingerClient(object): conn.close() return response if raw else XRD.parse(response) - def hostmeta(self): - protocol = "https" if self._secure else "http" + def hostmeta(self, protocol): hostmeta_url = "%s://%s/.well-known/host-meta" % (protocol, self._host) return self.xrd(hostmeta_url) def finger(self, username): - - hm = self.hostmeta() + try: + hm = self.hostmeta('https') + insecure = False + except (urllib2.URLError, urllib2.HTTPError): + hm = self.hostmeta('http') + insecure = True + hm_hosts = self._hm_hosts(hm) if self._host not in hm_hosts: raise WebFingerExpection("hostmeta host did not match account host") template = hm.find_link(WEBFINGER_TYPES, attr='template') + if not template.startswith('https://'): + insecure = True xrd_url = template.replace('{uri}', urllib.quote_plus('acct:%s@%s' % (username, self._host))) - return WebFingerResponse(self.xrd(xrd_url)) + data = self.xrd(xrd_url) + return WebFingerResponse(data, insecure) -def finger(identifier, secure=False): +def finger(identifier): if identifier.startswith('acct:'): (acct, identifier) = identifier.split(':', 1) (username, host) = identifier.split('@') - client = WebFingerClient(host, secure) + client = WebFingerClient(host) return client.finger(username) # example main method if __name__ == '__main__': import sys - wf = finger(sys.argv[1], True) + wf = finger(sys.argv[1]) print "Avatar: ", wf.avatar print "HCard: ", wf.hcard print "OpenID: ", wf.open_id print "Profile:", wf.profile print "XFN: ", wf.find_link('http://gmpg.org/xfn/11', attr='href') + if wf.insecure: + print "Warning: Data was retrieved over an insecure connection" From aa748195bf6e474b66b486240d6a07d6df021029 Mon Sep 17 00:00:00 2001 From: Leberwurscht Date: Wed, 4 Apr 2012 01:34:45 +0200 Subject: [PATCH 2/4] remove whitespace --- pywebfinger.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pywebfinger.py b/pywebfinger.py index 886265a..13770e1 100644 --- a/pywebfinger.py +++ b/pywebfinger.py @@ -21,36 +21,36 @@ class WebFingerExpection(Exception): pass class WebFingerResponse(object): - + def __init__(self, xrd, insecure): self.insecure = insecure self._xrd = xrd - + def __getattr__(self, name): if name in RELS: return self._xrd.find_link(RELS[name], attr='href') return getattr(self._xrd, name) class WebFingerClient(object): - + def __init__(self, host): self._host = host self._opener = urllib2.build_opener(urllib2.HTTPRedirectHandler()) self._opener.addheaders = [('User-agent', 'python-webfinger')] - + def _hm_hosts(self, xrd): return [e.value for e in xrd.elements if e.name == 'hm:Host'] - + def xrd(self, url, raw=False): conn = self._opener.open(url) response = conn.read() conn.close() return response if raw else XRD.parse(response) - + def hostmeta(self, protocol): hostmeta_url = "%s://%s/.well-known/host-meta" % (protocol, self._host) return self.xrd(hostmeta_url) - + def finger(self, username): try: hm = self.hostmeta('https') @@ -60,16 +60,16 @@ class WebFingerClient(object): insecure = True hm_hosts = self._hm_hosts(hm) - + if self._host not in hm_hosts: raise WebFingerExpection("hostmeta host did not match account host") - + template = hm.find_link(WEBFINGER_TYPES, attr='template') if not template.startswith('https://'): insecure = True xrd_url = template.replace('{uri}', urllib.quote_plus('acct:%s@%s' % (username, self._host))) - + data = self.xrd(xrd_url) return WebFingerResponse(data, insecure) From 0770e98895bbfa1eaa84748af34274bd08ef5aa2 Mon Sep 17 00:00:00 2001 From: Leberwurscht Date: Wed, 4 Apr 2012 01:38:59 +0200 Subject: [PATCH 3/4] fix typo --- pywebfinger.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pywebfinger.py b/pywebfinger.py index 13770e1..5f51bd4 100644 --- a/pywebfinger.py +++ b/pywebfinger.py @@ -17,7 +17,7 @@ WEBFINGER_TYPES = ( 'http://webfinger.info/rel/service', # deprecated on 09/17/2009 ) -class WebFingerExpection(Exception): +class WebFingerException(Exception): pass class WebFingerResponse(object): @@ -62,7 +62,7 @@ class WebFingerClient(object): hm_hosts = self._hm_hosts(hm) if self._host not in hm_hosts: - raise WebFingerExpection("hostmeta host did not match account host") + raise WebFingerException("hostmeta host did not match account host") template = hm.find_link(WEBFINGER_TYPES, attr='template') if not template.startswith('https://'): From 4bf4fa97d1b27354ab2d93f6e20feedb56a0c982 Mon Sep 17 00:00:00 2001 From: Leberwurscht Date: Wed, 4 Apr 2012 02:42:59 +0200 Subject: [PATCH 4/4] allow timeout --- pywebfinger.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pywebfinger.py b/pywebfinger.py index 5f51bd4..3340921 100644 --- a/pywebfinger.py +++ b/pywebfinger.py @@ -33,16 +33,18 @@ class WebFingerResponse(object): class WebFingerClient(object): - def __init__(self, host): + def __init__(self, host, timeout=None): self._host = host self._opener = urllib2.build_opener(urllib2.HTTPRedirectHandler()) self._opener.addheaders = [('User-agent', 'python-webfinger')] + self._timeout = timeout + def _hm_hosts(self, xrd): return [e.value for e in xrd.elements if e.name == 'hm:Host'] def xrd(self, url, raw=False): - conn = self._opener.open(url) + conn = self._opener.open(url, timeout=self._timeout) response = conn.read() conn.close() return response if raw else XRD.parse(response) @@ -73,11 +75,11 @@ class WebFingerClient(object): data = self.xrd(xrd_url) return WebFingerResponse(data, insecure) -def finger(identifier): +def finger(identifier, timeout=None): if identifier.startswith('acct:'): (acct, identifier) = identifier.split(':', 1) (username, host) = identifier.split('@') - client = WebFingerClient(host) + client = WebFingerClient(host, timeout=timeout) return client.finger(username) # example main method