Browse Source

Fixing public key lookup

Bob Mottram 1 month ago
parent
commit
e12f0994cf
7 changed files with 50 additions and 61 deletions
  1. 14 21
      daemon.py
  2. 1 1
      epicyon.py
  3. 1 1
      httpsig.py
  4. 18 6
      inbox.py
  5. 2 27
      person.py
  6. 11 2
      posts.py
  7. 3 3
      tests.py

+ 14 - 21
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("<html><head></head><body><h1>404 Not Found</h1></body></html>".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:

+ 1 - 1
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)

+ 1 - 1
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:

+ 18 - 6
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'])

+ 2 - 27
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

+ 11 - 2
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

+ 3 - 3
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')