Fix http signature with port number

master
Bob Mottram 2019-07-01 10:31:02 +01:00
parent b0a85e6b4e
commit d9d5ce94dc
3 changed files with 53 additions and 24 deletions

View File

@ -7,7 +7,6 @@ __maintainer__ = "Bob Mottram"
__email__ = "bob@freedombone.net" __email__ = "bob@freedombone.net"
__status__ = "Production" __status__ = "Production"
from person import createPerson
from Crypto.PublicKey import RSA from Crypto.PublicKey import RSA
from Crypto.Hash import SHA256 from Crypto.Hash import SHA256
#from Crypto.Signature import PKCS1_v1_5 #from Crypto.Signature import PKCS1_v1_5
@ -16,14 +15,18 @@ from requests.auth import AuthBase
import base64 import base64
import json 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 """Returns a raw signature string that can be plugged into a header and
used to verify the authenticity of an HTTP transmission. used to verify the authenticity of an HTTP transmission.
""" """
prefix='https' prefix='https'
if not https: if not https:
prefix='http' prefix='http'
keyID = prefix+'://'+domain+'/'+username+'#main-key'
if port!=80 and port!=443:
domain=domain+':'+str(port)
keyID = prefix+'://'+domain+'/users/'+username+'/main-key'
if not messageBodyJson: if not messageBodyJson:
headers = {'host': domain} headers = {'host': domain}
else: 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()]) [f'{k}="{v}"' for k, v in signatureDict.items()])
return signatureHeader 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 """Returns true or false depending on if the key that we plugged in here
validates against the headers, method, and path. validates against the headers, method, and path.
publicKeyPem - the public key from an rsa key pair publicKeyPem - the public key from an rsa key pair
headers - should be a dictionary of request headers headers - should be a dictionary of request headers
path - the relative url that was requested from this site path - the relative url that was requested from this site
GETmethod - GET or POST GETmethod - GET or POST
messageBodyJson - the received request body (used for digest) messageBodyJsonStr - the received request body (used for digest)
""" """
if GETmethod: if GETmethod:
method='GET' method='GET'
@ -90,7 +110,7 @@ def verifyPostHeaders(https: bool, publicKeyPem: str, headers: dict, path: str,
signedHeaderList.append( signedHeaderList.append(
f'(request-target): {method.lower()} {path}') f'(request-target): {method.lower()} {path}')
elif signedHeader == 'digest': 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}') signedHeaderList.append(f'digest: SHA-256={bodyDigest}')
else: else:
signedHeaderList.append( signedHeaderList.append(

View File

@ -24,6 +24,7 @@ from random import randint
from session import getJson from session import getJson
from session import postJson from session import postJson
from webfinger import webfingerHandle from webfinger import webfingerHandle
from httpsig import createSignedHeader
try: try:
from BeautifulSoup import BeautifulSoup from BeautifulSoup import BeautifulSoup
except ImportError: except ImportError:
@ -342,6 +343,8 @@ def sendPost(session,baseDir,username: str, domain: str, port: int, toUsername:
if not https: if not https:
prefix='http' prefix='http'
withDigest=True
if toPort!=80 and toPort!=443: if toPort!=80 and toPort!=443:
toDomain=toDomain+':'+str(toPort) toDomain=toDomain+':'+str(toPort)
@ -369,14 +372,14 @@ def sendPost(session,baseDir,username: str, domain: str, port: int, toUsername:
return 5 return 5
# construct the http header # construct the http header
signatureHeader = signPostHeaders(privateKeyPem, username, domain, '/inbox', https, postJsonObject) signatureHeaderJson = createSignedHeader(privateKeyPem, username, domain, port, '/inbox', https, withDigest, postJsonObject)
signatureHeader['Content-type'] = 'application/json' signatureHeaderJson['Content-type'] = 'application/json'
print("*************signatureHeaderJson "+str(signatureHeaderJson))
# Keep the number of threads being used small # Keep the number of threads being used small
while len(sendThreads)>10: while len(sendThreads)>10:
sendThreads[0].kill() sendThreads[0].kill()
sendThreads.pop(0) 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) sendThreads.append(thr)
thr.start() thr.start()
return 0 return 0

View File

@ -34,31 +34,37 @@ def testHttpsigBase(withDigest):
username='socrates' username='socrates'
domain='argumentative.social' domain='argumentative.social'
https=True https=True
port=80 port=5576
baseDir=os.getcwd() baseDir=os.getcwd()
privateKeyPem,publicKeyPem,person,wfEndpoint=createPerson(baseDir,username,domain,port,https,False) 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: if not withDigest:
headers = {'host': domain} headers = {'host': headersDomain}
else: else:
bodyDigest = base64.b64encode(SHA256.new(messageBodyJson.encode()).digest()) bodyDigest = base64.b64encode(SHA256.new(messageBodyJsonStr.encode()).digest())
headers = {'host': domain, 'digest': f'SHA-256={bodyDigest}'} headers = {'host': headersDomain, 'digest': f'SHA-256={bodyDigest}'}
path='/inbox' path='/inbox'
signatureHeader = signPostHeaders(privateKeyPem, username, domain, path, https, None) signatureHeader = signPostHeaders(privateKeyPem, username, domain, port, path, https, None)
headers['signature'] = signatureHeader headers['signature'] = signatureHeader
assert verifyPostHeaders(https, publicKeyPem, headers, '/inbox' ,False, messageBodyJson) assert verifyPostHeaders(https, publicKeyPem, headers, '/inbox' ,False, messageBodyJsonStr)
assert verifyPostHeaders(https, publicKeyPem, headers, '/parambulator/inbox', False , messageBodyJson) == False assert verifyPostHeaders(https, publicKeyPem, headers, '/parambulator/inbox', False , messageBodyJsonStr) == False
assert verifyPostHeaders(https, publicKeyPem, headers, '/inbox', True, messageBodyJson) == False assert verifyPostHeaders(https, publicKeyPem, headers, '/inbox', True, messageBodyJsonStr) == False
if not withDigest: if not withDigest:
# fake domain # fake domain
headers = {'host': 'bogon.domain'} headers = {'host': 'bogon.domain'}
else: else:
# correct domain but fake message # correct domain but fake message
messageBodyJson = '{"a key": "a value", "another key": "Fake GNUs"}' messageBodyJsonStr = '{"a key": "a value", "another key": "Fake GNUs"}'
bodyDigest = base64.b64encode(SHA256.new(messageBodyJson.encode()).digest()) bodyDigest = base64.b64encode(SHA256.new(messageBodyJsonStr.encode()).digest())
headers = {'host': domain, 'digest': f'SHA-256={bodyDigest}'} headers = {'host': domain, 'digest': f'SHA-256={bodyDigest}'}
headers['signature'] = signatureHeader headers['signature'] = signatureHeader
assert verifyPostHeaders(https, publicKeyPem, headers, '/inbox', True, messageBodyJson) == False assert verifyPostHeaders(https, publicKeyPem, headers, '/inbox', True, messageBodyJsonStr) == False
def testHttpsig(): def testHttpsig():
testHttpsigBase(False) 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) 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)) print('sendResult: '+str(sendResult))
time.sleep(3) time.sleep(5)
# stop the servers # stop the servers
thrAlice.kill() thrAlice.kill()