From d9d5ce94dc93326184111f2791e8ae4eeeed33e4 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Mon, 1 Jul 2019 10:31:02 +0100 Subject: [PATCH] Fix http signature with port number --- httpsig.py | 34 +++++++++++++++++++++++++++------- posts.py | 11 +++++++---- tests.py | 32 +++++++++++++++++++------------- 3 files changed, 53 insertions(+), 24 deletions(-) diff --git a/httpsig.py b/httpsig.py index 6e756417..88f5b0d4 100644 --- a/httpsig.py +++ b/httpsig.py @@ -7,7 +7,6 @@ __maintainer__ = "Bob Mottram" __email__ = "bob@freedombone.net" __status__ = "Production" -from person import createPerson from Crypto.PublicKey import RSA from Crypto.Hash import SHA256 #from Crypto.Signature import PKCS1_v1_5 @@ -16,14 +15,18 @@ from requests.auth import AuthBase import base64 import json -def signPostHeaders(privateKeyPem: str, username: str, domain: str, path: str, https: bool, messageBodyJson) -> str: +def signPostHeaders(privateKeyPem: str, username: str, domain: str, port: int,path: str, https: bool, messageBodyJson) -> str: """Returns a raw signature string that can be plugged into a header and used to verify the authenticity of an HTTP transmission. """ prefix='https' if not https: - prefix='http' - keyID = prefix+'://'+domain+'/'+username+'#main-key' + prefix='http' + + if port!=80 and port!=443: + domain=domain+':'+str(port) + + keyID = prefix+'://'+domain+'/users/'+username+'/main-key' if not messageBodyJson: headers = {'host': domain} else: @@ -56,14 +59,31 @@ def signPostHeaders(privateKeyPem: str, username: str, domain: str, path: str, h [f'{k}="{v}"' for k, v in signatureDict.items()]) return signatureHeader -def verifyPostHeaders(https: bool, publicKeyPem: str, headers: dict, path: str, GETmethod: bool, messageBodyJson: str) -> bool: +def createSignedHeader(privateKeyPem: str,username: str,domain: str,port: int,path: str,https: bool,withDigest: bool,messageBodyJson) -> {}: + headerDomain=domain + + if port!=80 and port!=443: + headerDomain=headerDomain+':'+str(port) + + if not withDigest: + headers = {'host': headerDomain} + else: + messageBodyJsonStr=json.dumps(messageBodyJson) + bodyDigest = base64.b64encode(SHA256.new(messageBodyJsonStr.encode()).digest()) + headers = {'host': headerDomain, 'digest': f'SHA-256={bodyDigest}'} + path='/inbox' + signatureHeader = signPostHeaders(privateKeyPem, username, domain, port, path, https, None) + headers['signature'] = signatureHeader + return headers + +def verifyPostHeaders(https: bool, publicKeyPem: str, headers: dict, path: str, GETmethod: bool, messageBodyJsonStr: str) -> bool: """Returns true or false depending on if the key that we plugged in here validates against the headers, method, and path. publicKeyPem - the public key from an rsa key pair headers - should be a dictionary of request headers path - the relative url that was requested from this site GETmethod - GET or POST - messageBodyJson - the received request body (used for digest) + messageBodyJsonStr - the received request body (used for digest) """ if GETmethod: method='GET' @@ -90,7 +110,7 @@ def verifyPostHeaders(https: bool, publicKeyPem: str, headers: dict, path: str, signedHeaderList.append( f'(request-target): {method.lower()} {path}') elif signedHeader == 'digest': - bodyDigest = base64.b64encode(SHA256.new(messageBodyJson.encode()).digest()) + bodyDigest = base64.b64encode(SHA256.new(messageBodyJsonStr.encode()).digest()) signedHeaderList.append(f'digest: SHA-256={bodyDigest}') else: signedHeaderList.append( diff --git a/posts.py b/posts.py index f996683c..15c5e30a 100644 --- a/posts.py +++ b/posts.py @@ -24,6 +24,7 @@ from random import randint from session import getJson from session import postJson from webfinger import webfingerHandle +from httpsig import createSignedHeader try: from BeautifulSoup import BeautifulSoup except ImportError: @@ -342,6 +343,8 @@ def sendPost(session,baseDir,username: str, domain: str, port: int, toUsername: if not https: prefix='http' + withDigest=True + if toPort!=80 and toPort!=443: toDomain=toDomain+':'+str(toPort) @@ -369,14 +372,14 @@ def sendPost(session,baseDir,username: str, domain: str, port: int, toUsername: return 5 # construct the http header - signatureHeader = signPostHeaders(privateKeyPem, username, domain, '/inbox', https, postJsonObject) - signatureHeader['Content-type'] = 'application/json' - + signatureHeaderJson = createSignedHeader(privateKeyPem, username, domain, port, '/inbox', https, withDigest, postJsonObject) + signatureHeaderJson['Content-type'] = 'application/json' + print("*************signatureHeaderJson "+str(signatureHeaderJson)) # Keep the number of threads being used small while len(sendThreads)>10: sendThreads[0].kill() sendThreads.pop(0) - thr = threadWithTrace(target=threadSendPost,args=(session,postJsonObject.copy(),federationList,inboxUrl,baseDir,signatureHeader.copy(),postLog),daemon=True) + thr = threadWithTrace(target=threadSendPost,args=(session,postJsonObject.copy(),federationList,inboxUrl,baseDir,signatureHeaderJson.copy(),postLog),daemon=True) sendThreads.append(thr) thr.start() return 0 diff --git a/tests.py b/tests.py index 3e12a39f..8dc945b0 100644 --- a/tests.py +++ b/tests.py @@ -34,31 +34,37 @@ def testHttpsigBase(withDigest): username='socrates' domain='argumentative.social' https=True - port=80 + port=5576 baseDir=os.getcwd() privateKeyPem,publicKeyPem,person,wfEndpoint=createPerson(baseDir,username,domain,port,https,False) - messageBodyJson = '{"a key": "a value", "another key": "A string"}' + messageBodyJsonStr = '{"a key": "a value", "another key": "A string"}' + + headersDomain=domain + if port!=80 and port !=443: + headersDomain=domain+':'+str(port) + if not withDigest: - headers = {'host': domain} + headers = {'host': headersDomain} else: - bodyDigest = base64.b64encode(SHA256.new(messageBodyJson.encode()).digest()) - headers = {'host': domain, 'digest': f'SHA-256={bodyDigest}'} + bodyDigest = base64.b64encode(SHA256.new(messageBodyJsonStr.encode()).digest()) + headers = {'host': headersDomain, 'digest': f'SHA-256={bodyDigest}'} + path='/inbox' - signatureHeader = signPostHeaders(privateKeyPem, username, domain, path, https, None) + signatureHeader = signPostHeaders(privateKeyPem, username, domain, port, path, https, None) headers['signature'] = signatureHeader - assert verifyPostHeaders(https, publicKeyPem, headers, '/inbox' ,False, messageBodyJson) - assert verifyPostHeaders(https, publicKeyPem, headers, '/parambulator/inbox', False , messageBodyJson) == False - assert verifyPostHeaders(https, publicKeyPem, headers, '/inbox', True, messageBodyJson) == False + assert verifyPostHeaders(https, publicKeyPem, headers, '/inbox' ,False, messageBodyJsonStr) + assert verifyPostHeaders(https, publicKeyPem, headers, '/parambulator/inbox', False , messageBodyJsonStr) == False + assert verifyPostHeaders(https, publicKeyPem, headers, '/inbox', True, messageBodyJsonStr) == False if not withDigest: # fake domain headers = {'host': 'bogon.domain'} else: # correct domain but fake message - messageBodyJson = '{"a key": "a value", "another key": "Fake GNUs"}' - bodyDigest = base64.b64encode(SHA256.new(messageBodyJson.encode()).digest()) + messageBodyJsonStr = '{"a key": "a value", "another key": "Fake GNUs"}' + bodyDigest = base64.b64encode(SHA256.new(messageBodyJsonStr.encode()).digest()) headers = {'host': domain, 'digest': f'SHA-256={bodyDigest}'} headers['signature'] = signatureHeader - assert verifyPostHeaders(https, publicKeyPem, headers, '/inbox', True, messageBodyJson) == False + assert verifyPostHeaders(https, publicKeyPem, headers, '/inbox', True, messageBodyJsonStr) == False def testHttpsig(): testHttpsigBase(False) @@ -179,7 +185,7 @@ def testPostMessageBetweenServers(): sendResult = sendPost(sessionAlice,aliceDir,'alice', '127.0.0.1', alicePort, 'bob', '127.0.0.1', bobPort, '', https, 'Why is a mouse when it spins?', False, True, federationList, aliceSendThreads, alicePostLog, inReplyTo, inReplyToAtomUri, subject) print('sendResult: '+str(sendResult)) - time.sleep(3) + time.sleep(5) # stop the servers thrAlice.kill()