Fixing public key lookup

master
Bob Mottram 2019-07-04 15:36:29 +01:00
parent 08134954bc
commit e12f0994cf
7 changed files with 50 additions and 61 deletions

View File

@ -16,7 +16,6 @@ from webfinger import webfingerMeta
from webfinger import webfingerLookup from webfinger import webfingerLookup
from webfinger import webfingerHandle from webfinger import webfingerHandle
from person import personLookup from person import personLookup
from person import personKeyLookup
from person import personOutboxJson from person import personOutboxJson
from posts import getPersonPubKey from posts import getPersonPubKey
from posts import outboxMessageCreateWrap from posts import outboxMessageCreateWrap
@ -24,6 +23,7 @@ from posts import savePostToOutbox
from inbox import inboxPermittedMessage from inbox import inboxPermittedMessage
from inbox import inboxMessageHasParams from inbox import inboxMessageHasParams
from inbox import runInboxQueue from inbox import runInboxQueue
from inbox import savePostToInboxQueue
from follow import getFollowingFeed from follow import getFollowingFeed
from auth import authorize from auth import authorize
from threads import threadWithTrace 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')) self.wfile.write("<html><head></head><body><h1>404 Not Found</h1></body></html>".encode('utf-8'))
def _webfinger(self) -> bool: def _webfinger(self) -> bool:
if self.server.debug:
print('DEBUG: WEBFINGER well-known')
if not self.path.startswith('/.well-known'): if not self.path.startswith('/.well-known'):
return False return False
if self.server.debug:
print('DEBUG: WEBFINGER well-known')
if self.server.debug: if self.server.debug:
print('DEBUG: WEBFINGER host-meta') print('DEBUG: WEBFINGER host-meta')
@ -225,13 +225,6 @@ class PubServer(BaseHTTPRequestHandler):
self.wfile.write(json.dumps(getPerson).encode('utf-8')) self.wfile.write(json.dumps(getPerson).encode('utf-8'))
self.server.GETbusy=False self.server.GETbusy=False
return 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 # check that a json file was requested
if not self.path.endswith('.json'): if not self.path.endswith('.json'):
if self.server.debug: if self.server.debug:
@ -366,9 +359,10 @@ class PubServer(BaseHTTPRequestHandler):
pprint(messageJson) pprint(messageJson)
if not headers.get('keyId'): if not self.headers.get('signature'):
if 'keyId=' not in self.headers['signature']:
if self.server.debug: if self.server.debug:
print('DEBUG: POST to inbox has no keyId in header') print('DEBUG: POST to inbox has no keyId in header signature parameter')
self.send_response(403) self.send_response(403)
self.end_headers() self.end_headers()
self.server.POSTbusy=False self.server.POSTbusy=False
@ -387,11 +381,10 @@ class PubServer(BaseHTTPRequestHandler):
cacheFilename = \ cacheFilename = \
savePostToInboxQueue(self.server.baseDir, \ savePostToInboxQueue(self.server.baseDir, \
self.server.httpPrefix, \ self.server.httpPrefix, \
headers['keyId'], \
self.postToNickname, \ self.postToNickname, \
self.server.domain, \ self.server.domain, \
messageJson, messageJson,
self.headers) self.headers['signature'])
if cacheFilename: if cacheFilename:
if cacheFilename not in self.server.inboxQueue: if cacheFilename not in self.server.inboxQueue:
self.server.inboxQueue.append(cacheFilename) self.server.inboxQueue.append(cacheFilename)
@ -410,7 +403,7 @@ class PubServer(BaseHTTPRequestHandler):
self.server.POSTbusy=False self.server.POSTbusy=False
return 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: if len(domain)==0:
domain='localhost' domain='localhost'
if '.' not in domain: if '.' not in domain:

View File

@ -133,4 +133,4 @@ if not os.path.isdir(baseDir+'/accounts/'+nickname+'@'+domain):
print('Creating default admin account '+nickname+'@'+domain) print('Creating default admin account '+nickname+'@'+domain)
privateKeyPem,publicKeyPem,person,wfEndpoint=createPerson(baseDir,nickname,domain,port,httpPrefix,True) 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)

View File

@ -24,7 +24,7 @@ def signPostHeaders(privateKeyPem: str, nickname: str, domain: str, \
if port!=80 and port!=443: if port!=80 and port!=443:
domain=domain+':'+str(port) domain=domain+':'+str(port)
keyID = httpPrefix+'://'+domain+'/users/'+nickname+'/main-key' keyID = httpPrefix+'://'+domain+'/users/'+nickname+'#main-key'
if not messageBodyJson: if not messageBodyJson:
headers = {'host': domain} headers = {'host': domain}
else: else:

View File

@ -18,6 +18,7 @@ from posts import getPersonPubKey
from httpsig import verifyPostHeaders from httpsig import verifyPostHeaders
from session import createSession from session import createSession
from follow import receiveFollowRequest from follow import receiveFollowRequest
from pprint import pprint
def inboxMessageHasParams(messageJson: {}) -> bool: def inboxMessageHasParams(messageJson: {}) -> bool:
"""Checks whether an incoming message contains expected parameters """Checks whether an incoming message contains expected parameters
@ -58,14 +59,12 @@ def validPublishedDate(published) -> bool:
return False return False
return True 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 """Saves the give json to the inbox queue for the person
keyId specifies the actor sending the post keyId specifies the actor sending the post
""" """
if ':' in domain: if ':' in domain:
domain=domain.split(':')[0] domain=domain.split(':')[0]
if not keyId:
return None
if not postJson.get('id'): if not postJson.get('id'):
return None return None
postId=postJson['id'].replace('/activity','') postId=postJson['id'].replace('/activity','')
@ -82,9 +81,8 @@ def savePostToInboxQueue(baseDir: str,httpPrefix: str,keyId: str,nickname: str,
return None return None
filename=inboxQueueDir+'/'+postId.replace('/','#')+'.json' filename=inboxQueueDir+'/'+postId.replace('/','#')+'.json'
newBufferItem = { newQueueItem = {
'published': published, 'published': published,
'keyId': keyid,
'headers': headers, 'headers': headers,
'post': postJson, 'post': postJson,
'filename': filename, 'filename': filename,
@ -128,7 +126,21 @@ def runInboxQueue(baseDir: str,httpPrefix: str,personCache: {},queue: [],domain:
# Try a few times to obtain teh public key # Try a few times to obtain teh public key
pubKey=None pubKey=None
for tries in range(5): 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 not pubKey:
if debug: if debug:
print('DEBUG: Retry '+str(tries+1)+' obtaining public key for '+queueJson['keyId']) print('DEBUG: Retry '+str(tries+1)+' obtaining public key for '+queueJson['keyId'])

View File

@ -71,7 +71,7 @@ def createPerson(baseDir: str,nickname: str,domain: str,port: int, \
'name': nickname, 'name': nickname,
'outbox': httpPrefix+'://'+domain+'/users/'+nickname+'/outbox', 'outbox': httpPrefix+'://'+domain+'/users/'+nickname+'/outbox',
'preferredUsername': ''+nickname, 'preferredUsername': ''+nickname,
'publicKey': {'id': httpPrefix+'://'+domain+'/users/'+nickname+'/main-key', 'publicKey': {'id': httpPrefix+'://'+domain+'/users/'+nickname+'#main-key',
'owner': httpPrefix+'://'+domain+'/users/'+nickname, 'owner': httpPrefix+'://'+domain+'/users/'+nickname,
'publicKeyPem': publicKeyPem, 'publicKeyPem': publicKeyPem,
'summary': '', 'summary': '',
@ -119,37 +119,12 @@ def validNickname(nickname: str) -> bool:
return False return False
return True 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) -> {}: def personLookup(domain: str,path: str,baseDir: str) -> {}:
"""Lookup the person for an given nickname """Lookup the person for an given nickname
""" """
notPersonLookup=['/inbox','/outbox','/outboxarchive', \ notPersonLookup=['/inbox','/outbox','/outboxarchive', \
'/followers','/following','/featured', \ '/followers','/following','/featured', \
'.png','.jpg','.gif','.mpv', \ '.png','.jpg','.gif','.mpv']
'#main-key','/main-key']
for ending in notPersonLookup: for ending in notPersonLookup:
if path.endswith(ending): if path.endswith(ending):
return None return None

View File

@ -103,18 +103,27 @@ def getPersonBox(session,wfRequest: {},personCache: {},boxName='inbox') -> (str,
return personJson[boxName],pubKeyId,pubKey,personId 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"'} asHeader = {'Accept': 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'}
if not personUrl: if not personUrl:
return None return None
personUrl=personUrl.replace('#main-key','')
personJson = getPersonFromCache(personUrl,personCache) personJson = getPersonFromCache(personUrl,personCache)
if not personJson: if not personJson:
print('************Obtaining public key for '+personUrl) if debug:
print('DEBUG: Obtaining public key for '+personUrl)
personJson = getJson(session,personUrl,asHeader,None) personJson = getJson(session,personUrl,asHeader,None)
pubKey=None pubKey=None
if personJson.get('publicKey'): if personJson.get('publicKey'):
if personJson['publicKey'].get('publicKeyPem'): if personJson['publicKey'].get('publicKeyPem'):
pubKey=personJson['publicKey']['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) storePersonInCache(personUrl,personJson,personCache)
return pubKey return pubKey

View File

@ -111,7 +111,7 @@ def createServerAlice(path: str,domain: str,port: int,federationList: []):
os.mkdir(path) os.mkdir(path)
os.chdir(path) os.chdir(path)
nickname='alice' nickname='alice'
httpPrefix=False httpPrefix='http'
useTor=False useTor=False
clientToServer=False clientToServer=False
privateKeyPem,publicKeyPem,person,wfEndpoint=createPerson(path,nickname,domain,port,httpPrefix,True) 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 global testServerAliceRunning
testServerAliceRunning = True testServerAliceRunning = True
print('Server running: Alice') 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: []): def createServerBob(path: str,domain: str,port: int,federationList: []):
print('Creating test server: Bob on port '+str(port)) print('Creating test server: Bob on port '+str(port))
@ -146,7 +146,7 @@ def createServerBob(path: str,domain: str,port: int,federationList: []):
global testServerBobRunning global testServerBobRunning
testServerBobRunning = True testServerBobRunning = True
print('Server running: Bob') print('Server running: Bob')
runDaemon(domain,port,httpPrefix,federationList,useTor,True) runDaemon(path,domain,port,httpPrefix,federationList,useTor,True)
def testPostMessageBetweenServers(): def testPostMessageBetweenServers():
print('Testing sending message from one server to the inbox of another') print('Testing sending message from one server to the inbox of another')