From e12f0994cf8f74001de3398e4c2ad3226886616a Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Thu, 4 Jul 2019 15:36:29 +0100 Subject: [PATCH] Fixing public key lookup --- daemon.py | 35 ++++++++++++++--------------------- epicyon.py | 2 +- httpsig.py | 2 +- inbox.py | 24 ++++++++++++++++++------ person.py | 29 ++--------------------------- posts.py | 13 +++++++++++-- tests.py | 6 +++--- 7 files changed, 50 insertions(+), 61 deletions(-) diff --git a/daemon.py b/daemon.py index e2cde88d6..a4dd5b2f5 100644 --- a/daemon.py +++ b/daemon.py @@ -16,7 +16,6 @@ from webfinger import webfingerMeta from webfinger import webfingerLookup from webfinger import webfingerHandle from person import personLookup -from person import personKeyLookup from person import personOutboxJson from posts import getPersonPubKey from posts import outboxMessageCreateWrap @@ -24,6 +23,7 @@ from posts import savePostToOutbox from inbox import inboxPermittedMessage from inbox import inboxMessageHasParams from inbox import runInboxQueue +from inbox import savePostToInboxQueue from follow import getFollowingFeed from auth import authorize from threads import threadWithTrace @@ -67,10 +67,10 @@ class PubServer(BaseHTTPRequestHandler): self.wfile.write("

404 Not Found

".encode('utf-8')) def _webfinger(self) -> bool: - if self.server.debug: - print('DEBUG: WEBFINGER well-known') if not self.path.startswith('/.well-known'): return False + if self.server.debug: + print('DEBUG: WEBFINGER well-known') if self.server.debug: print('DEBUG: WEBFINGER host-meta') @@ -216,7 +216,7 @@ class PubServer(BaseHTTPRequestHandler): self._set_headers('application/json') self.wfile.write(json.dumps(followers).encode('utf-8')) self.server.GETbusy=False - return + return # look up a person getPerson = personLookup(self.server.domain,self.path, \ self.server.baseDir) @@ -225,13 +225,6 @@ class PubServer(BaseHTTPRequestHandler): self.wfile.write(json.dumps(getPerson).encode('utf-8')) self.server.GETbusy=False return - personKey = personKeyLookup(self.server.domain,self.path, \ - self.server.baseDir) - if personKey: - self._set_headers('text/html; charset=utf-8') - self.wfile.write(personKey.encode('utf-8')) - self.server.GETbusy=False - return # check that a json file was requested if not self.path.endswith('.json'): if self.server.debug: @@ -366,13 +359,14 @@ class PubServer(BaseHTTPRequestHandler): pprint(messageJson) - if not headers.get('keyId'): - if self.server.debug: - print('DEBUG: POST to inbox has no keyId in header') - self.send_response(403) - self.end_headers() - self.server.POSTbusy=False - return + if not self.headers.get('signature'): + if 'keyId=' not in self.headers['signature']: + if self.server.debug: + print('DEBUG: POST to inbox has no keyId in header signature parameter') + self.send_response(403) + self.end_headers() + self.server.POSTbusy=False + return if self.server.debug: print('DEBUG: POST saving to inbox cache') @@ -387,11 +381,10 @@ class PubServer(BaseHTTPRequestHandler): cacheFilename = \ savePostToInboxQueue(self.server.baseDir, \ self.server.httpPrefix, \ - headers['keyId'], \ self.postToNickname, \ self.server.domain, \ messageJson, - self.headers) + self.headers['signature']) if cacheFilename: if cacheFilename not in self.server.inboxQueue: self.server.inboxQueue.append(cacheFilename) @@ -410,7 +403,7 @@ class PubServer(BaseHTTPRequestHandler): self.server.POSTbusy=False return -def runDaemon(domain: str,port=80,httpPrefix='https',fedList=[],useTor=False,debug=False) -> None: +def runDaemon(baseDir: str,domain: str,port=80,httpPrefix='https',fedList=[],useTor=False,debug=False) -> None: if len(domain)==0: domain='localhost' if '.' not in domain: diff --git a/epicyon.py b/epicyon.py index a99a9256c..dfcca5014 100644 --- a/epicyon.py +++ b/epicyon.py @@ -133,4 +133,4 @@ if not os.path.isdir(baseDir+'/accounts/'+nickname+'@'+domain): print('Creating default admin account '+nickname+'@'+domain) privateKeyPem,publicKeyPem,person,wfEndpoint=createPerson(baseDir,nickname,domain,port,httpPrefix,True) -runDaemon(domain,port,httpPrefix,federationList,useTor,debug) +runDaemon(baseDir,domain,port,httpPrefix,federationList,useTor,debug) diff --git a/httpsig.py b/httpsig.py index 46da19cc8..ab0d7537b 100644 --- a/httpsig.py +++ b/httpsig.py @@ -24,7 +24,7 @@ def signPostHeaders(privateKeyPem: str, nickname: str, domain: str, \ if port!=80 and port!=443: domain=domain+':'+str(port) - keyID = httpPrefix+'://'+domain+'/users/'+nickname+'/main-key' + keyID = httpPrefix+'://'+domain+'/users/'+nickname+'#main-key' if not messageBodyJson: headers = {'host': domain} else: diff --git a/inbox.py b/inbox.py index 8dd3bc85c..b3442dac4 100644 --- a/inbox.py +++ b/inbox.py @@ -18,6 +18,7 @@ from posts import getPersonPubKey from httpsig import verifyPostHeaders from session import createSession from follow import receiveFollowRequest +from pprint import pprint def inboxMessageHasParams(messageJson: {}) -> bool: """Checks whether an incoming message contains expected parameters @@ -58,14 +59,12 @@ def validPublishedDate(published) -> bool: return False return True -def savePostToInboxQueue(baseDir: str,httpPrefix: str,keyId: str,nickname: str, domain: str,postJson: {},headers: {}) -> str: +def savePostToInboxQueue(baseDir: str,httpPrefix: str,nickname: str, domain: str,postJson: {},headers: str) -> str: """Saves the give json to the inbox queue for the person keyId specifies the actor sending the post """ if ':' in domain: domain=domain.split(':')[0] - if not keyId: - return None if not postJson.get('id'): return None postId=postJson['id'].replace('/activity','') @@ -82,9 +81,8 @@ def savePostToInboxQueue(baseDir: str,httpPrefix: str,keyId: str,nickname: str, return None filename=inboxQueueDir+'/'+postId.replace('/','#')+'.json' - newBufferItem = { + newQueueItem = { 'published': published, - 'keyId': keyid, 'headers': headers, 'post': postJson, 'filename': filename, @@ -128,7 +126,21 @@ def runInboxQueue(baseDir: str,httpPrefix: str,personCache: {},queue: [],domain: # Try a few times to obtain teh public key pubKey=None for tries in range(5): - pubKey=getPersonPubKey(session,queueJson['keyId'],personCache) + keyId=None + signatureParams=queueJson['headers'].split(',') + for signatureItem in signatureParams: + if signatureItem.startswith('keyId='): + if '"' in signatureItem: + keyId=signatureItem.split('"')[1] + break + if not keyId: + if debug: + print('DEBUG: No keyId in signature: '+queueJson['headers']['signature']) + os.remove(queueFilename) + queue.pop(0) + continue + + pubKey=getPersonPubKey(session,keyId,personCache,debug) if not pubKey: if debug: print('DEBUG: Retry '+str(tries+1)+' obtaining public key for '+queueJson['keyId']) diff --git a/person.py b/person.py index e4b9cd24f..011683143 100644 --- a/person.py +++ b/person.py @@ -71,7 +71,7 @@ def createPerson(baseDir: str,nickname: str,domain: str,port: int, \ 'name': nickname, 'outbox': httpPrefix+'://'+domain+'/users/'+nickname+'/outbox', 'preferredUsername': ''+nickname, - 'publicKey': {'id': httpPrefix+'://'+domain+'/users/'+nickname+'/main-key', + 'publicKey': {'id': httpPrefix+'://'+domain+'/users/'+nickname+'#main-key', 'owner': httpPrefix+'://'+domain+'/users/'+nickname, 'publicKeyPem': publicKeyPem, 'summary': '', @@ -118,38 +118,13 @@ def validNickname(nickname: str) -> bool: if c in nickname: return False return True - -def personKeyLookup(domain: str,path: str,baseDir: str) -> str: - """Lookup the public key of the person with a given nickname - """ - if not path.endswith('/main-key'): - return None - if not path.startswith('/users/'): - return None - nickname=path.replace('/users/','',1).replace('/main-key','') - if not validNickname(nickname): - return None - if ':' in domain: - domain=domain.split(':')[0] - handle=nickname.lower()+'@'+domain.lower() - filename=baseDir+'/accounts/'+handle.lower()+'.json' - if not os.path.isfile(filename): - return None - personJson={"user": "unknown"} - with open(filename, 'r') as fp: - personJson=commentjson.load(fp) - if personJson.get('publicKey'): - if personJson['publicKey'].get('publicKeyPem'): - return personJson['publicKey']['publicKeyPem'] - return None def personLookup(domain: str,path: str,baseDir: str) -> {}: """Lookup the person for an given nickname """ notPersonLookup=['/inbox','/outbox','/outboxarchive', \ '/followers','/following','/featured', \ - '.png','.jpg','.gif','.mpv', \ - '#main-key','/main-key'] + '.png','.jpg','.gif','.mpv'] for ending in notPersonLookup: if path.endswith(ending): return None diff --git a/posts.py b/posts.py index 6309c502b..69aa163ce 100644 --- a/posts.py +++ b/posts.py @@ -103,18 +103,27 @@ def getPersonBox(session,wfRequest: {},personCache: {},boxName='inbox') -> (str, return personJson[boxName],pubKeyId,pubKey,personId -def getPersonPubKey(session,personUrl: str,personCache: {}) -> str: +def getPersonPubKey(session,personUrl: str,personCache: {},debug: bool) -> str: asHeader = {'Accept': 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'} if not personUrl: return None + personUrl=personUrl.replace('#main-key','') personJson = getPersonFromCache(personUrl,personCache) if not personJson: - print('************Obtaining public key for '+personUrl) + if debug: + print('DEBUG: Obtaining public key for '+personUrl) personJson = getJson(session,personUrl,asHeader,None) pubKey=None if personJson.get('publicKey'): if personJson['publicKey'].get('publicKeyPem'): pubKey=personJson['publicKey']['publicKeyPem'] + else: + if personJson.get('publicKeyPem'): + pubKey=personJson['publicKeyPem'] + + if not pubKey: + if debug: + print('DEBUG: Public key not found for '+personUrl) storePersonInCache(personUrl,personJson,personCache) return pubKey diff --git a/tests.py b/tests.py index d5f3a3abe..dd1f31a9f 100644 --- a/tests.py +++ b/tests.py @@ -111,7 +111,7 @@ def createServerAlice(path: str,domain: str,port: int,federationList: []): os.mkdir(path) os.chdir(path) nickname='alice' - httpPrefix=False + httpPrefix='http' useTor=False clientToServer=False privateKeyPem,publicKeyPem,person,wfEndpoint=createPerson(path,nickname,domain,port,httpPrefix,True) @@ -124,7 +124,7 @@ def createServerAlice(path: str,domain: str,port: int,federationList: []): global testServerAliceRunning testServerAliceRunning = True print('Server running: Alice') - runDaemon(domain,port,httpPrefix,federationList,useTor,True) + runDaemon(path,domain,port,httpPrefix,federationList,useTor,True) def createServerBob(path: str,domain: str,port: int,federationList: []): print('Creating test server: Bob on port '+str(port)) @@ -146,7 +146,7 @@ def createServerBob(path: str,domain: str,port: int,federationList: []): global testServerBobRunning testServerBobRunning = True print('Server running: Bob') - runDaemon(domain,port,httpPrefix,federationList,useTor,True) + runDaemon(path,domain,port,httpPrefix,federationList,useTor,True) def testPostMessageBetweenServers(): print('Testing sending message from one server to the inbox of another')