From 1b1810ff8a50ad1a02ab49d43082630335edd459 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Fri, 16 Aug 2019 18:19:23 +0100 Subject: [PATCH] Calculate message body digest from incoming bytes to avoid any json conversion issues --- daemon.py | 15 ++++++++------- httpsig.py | 19 ++++++++++++------- inbox.py | 5 ++++- tests.py | 14 +++++++------- 4 files changed, 31 insertions(+), 22 deletions(-) diff --git a/daemon.py b/daemon.py index 5d693d8b4..1c36e7b00 100644 --- a/daemon.py +++ b/daemon.py @@ -383,7 +383,7 @@ class PubServer(BaseHTTPRequestHandler): self.server.projectVersion) return True - def _updateInboxQueue(self,nickname: str,messageJson: {}) -> int: + def _updateInboxQueue(self,nickname: str,messageJson: {},messageBytes: str) -> int: """Update the inbox queue """ # Check if the queue is full @@ -410,11 +410,12 @@ class PubServer(BaseHTTPRequestHandler): # save the json for later queue processing queueFilename = \ - savePostToInboxQueue(self.server.baseDir, \ - self.server.httpPrefix, \ - nickname, \ - self.server.domainFull, \ + savePostToInboxQueue(self.server.baseDir, + self.server.httpPrefix, + nickname, + self.server.domainFull, messageJson, + messageBytes.decode('utf-8'), headersDict, self.path, self.server.debug) @@ -2750,7 +2751,7 @@ class PubServer(BaseHTTPRequestHandler): else: self.postToNickname=pathUsersSection.split('/')[0] if self.postToNickname: - queueStatus=self._updateInboxQueue(self.postToNickname,messageJson) + queueStatus=self._updateInboxQueue(self.postToNickname,messageJson,messageBytes) if queueStatus==0: self.send_response(200) self.end_headers() @@ -2771,7 +2772,7 @@ class PubServer(BaseHTTPRequestHandler): else: if self.path == '/sharedInbox' or self.path == '/inbox': print('DEBUG: POST to shared inbox') - queueStatus=self._updateInboxQueue('inbox',messageJson) + queueStatus=self._updateInboxQueue('inbox',messageJson,messageBytes) if queueStatus==0: self.send_response(200) self.end_headers() diff --git a/httpsig.py b/httpsig.py index abc21099a..044a6a03e 100644 --- a/httpsig.py +++ b/httpsig.py @@ -19,12 +19,16 @@ import json from time import gmtime, strftime from pprint import pprint +def messageContentDigest(messageBodyJsonStr: str) -> str: + return base64.b64encode(SHA256.new(messageBodyJsonStr.encode()).digest()).decode('utf-8') + def signPostHeaders(dateStr: str,privateKeyPem: str, \ nickname: str, \ domain: str,port: int, \ toDomain: str,toPort: int, \ path: str, \ - httpPrefix: str, messageBodyJson: {}) -> str: + httpPrefix: str, \ + messageBodyJson: {}) -> str: """Returns a raw signature string that can be plugged into a header and used to verify the authenticity of an HTTP transmission. """ @@ -45,8 +49,7 @@ def signPostHeaders(dateStr: str,privateKeyPem: str, \ headers = {'(request-target)': f'post {path}','host': toDomain,'date': dateStr,'content-type': 'application/json'} else: messageBodyJsonStr=json.dumps(messageBodyJson) - bodyDigest = \ - base64.b64encode(SHA256.new(messageBodyJsonStr.encode()).digest()).decode('utf-8') + bodyDigest=messageContentDigest(messageBodyJsonStr) headers = {'(request-target)': f'post {path}','host': toDomain,'date': dateStr,'digest': f'SHA-256={bodyDigest}','content-type': 'application/activity+json'} privateKeyPem = RSA.import_key(privateKeyPem) #headers.update({ @@ -101,8 +104,7 @@ def createSignedHeader(privateKeyPem: str,nickname: str, \ path,httpPrefix,None) else: messageBodyJsonStr=json.dumps(messageBodyJson) - bodyDigest = \ - base64.b64encode(SHA256.new(messageBodyJsonStr.encode()).digest()).decode('utf-8') + bodyDigest=messageContentDigest(messageBodyJsonStr) print('***************************Send (request-target): post '+path) print('***************************Send host: '+headerDomain) print('***************************Send date: '+dateStr) @@ -120,6 +122,7 @@ def createSignedHeader(privateKeyPem: str,nickname: str, \ def verifyPostHeaders(httpPrefix: str,publicKeyPem: str,headers: dict, \ path: str,GETmethod: bool, \ + messageBodyDigest: str, \ messageBodyJsonStr: str) -> bool: """Returns true or false depending on if the key that we plugged in here validates against the headers, method, and path. @@ -153,8 +156,10 @@ def verifyPostHeaders(httpPrefix: str,publicKeyPem: str,headers: dict, \ f'(request-target): {method.lower()} {path}') print('***************************Verify (request-target): '+method.lower()+' '+path) elif signedHeader == 'digest': - bodyDigest = \ - base64.b64encode(SHA256.new(messageBodyJsonStr.strip().encode()).digest()).decode('utf-8') + if messageBodyDigest: + bodyDigest=messageBodyDigest + else: + bodyDigest = messageContentDigest(messageBodyJsonStr) signedHeaderList.append(f'digest: SHA-256={bodyDigest}') print('***************************Verify digest: SHA-256='+bodyDigest) print('***************************Verify messageBodyJsonStr: '+messageBodyJsonStr) diff --git a/inbox.py b/inbox.py index 69acdede7..695c00ab6 100644 --- a/inbox.py +++ b/inbox.py @@ -41,6 +41,7 @@ from like import undoLikesCollectionEntry from blocking import isBlocked from filters import isFiltered from announce import updateAnnounceCollection +from httpsig import messageContentDigest def validInbox(baseDir: str,nickname: str,domain: str) -> bool: """Checks whether files were correctly saved to the inbox @@ -165,7 +166,7 @@ def validPublishedDate(published) -> bool: return False return True -def savePostToInboxQueue(baseDir: str,httpPrefix: str,nickname: str, domain: str,postJsonObject: {},httpHeaders: {},postPath: str,debug: bool) -> str: +def savePostToInboxQueue(baseDir: str,httpPrefix: str,nickname: str, domain: str,postJsonObject: {},messageBytes: str,httpHeaders: {},postPath: str,debug: bool) -> str: """Saves the give json to the inbox queue for the person keyId specifies the actor sending the post """ @@ -241,6 +242,7 @@ def savePostToInboxQueue(baseDir: str,httpPrefix: str,nickname: str, domain: str 'httpHeaders': httpHeaders, 'path': postPath, 'post': postJsonObject, + 'digest': messageContentDigest(messageBytes), 'filename': filename, 'destination': destination } @@ -1156,6 +1158,7 @@ def runInboxQueue(projectVersion: str, \ pubKey, \ queueJson['httpHeaders'], \ queueJson['path'],False, \ + queueJson['digest'], \ json.dumps(queueJson['post'])): if debug: print('DEBUG: Header signature check failed') diff --git a/tests.py b/tests.py index 939ba830c..d7f2c1a5d 100644 --- a/tests.py +++ b/tests.py @@ -111,14 +111,14 @@ def testHttpsigBase(withDigest): boxpath, httpPrefix, messageBodyJson) headers['signature'] = signatureHeader - assert verifyPostHeaders(httpPrefix, publicKeyPem, headers, \ - boxpath,False, \ + assert verifyPostHeaders(httpPrefix,publicKeyPem,headers, \ + boxpath,False,None, \ messageBodyJsonStr) - assert verifyPostHeaders(httpPrefix, publicKeyPem, headers, \ - '/parambulator'+boxpath,False, \ + assert verifyPostHeaders(httpPrefix,publicKeyPem,headers, \ + '/parambulator'+boxpath,False,None, \ messageBodyJsonStr) == False - assert verifyPostHeaders(httpPrefix, publicKeyPem, headers, \ - boxpath,True, \ + assert verifyPostHeaders(httpPrefix,publicKeyPem,headers, \ + boxpath,True,None, \ messageBodyJsonStr) == False if not withDigest: # fake domain @@ -130,7 +130,7 @@ def testHttpsigBase(withDigest): headers = {'host': domain,'date': dateStr,'digest': f'SHA-256={bodyDigest}','content-type': contentType} headers['signature'] = signatureHeader assert verifyPostHeaders(httpPrefix,publicKeyPem,headers, \ - boxpath,True, \ + boxpath,True,None, \ messageBodyJsonStr) == False os.chdir(baseDir) shutil.rmtree(path)