Fixing http signatures

master
Bob Mottram 2019-08-16 14:47:01 +01:00
parent 174e166769
commit d2394b3a69
5 changed files with 79 additions and 34 deletions

View File

@ -417,7 +417,6 @@ class PubServer(BaseHTTPRequestHandler):
messageJson, messageJson,
headersDict, headersDict,
self.path, self.path,
#'/'+self.path.split('/')[-1],
self.server.debug) self.server.debug)
if queueFilename: if queueFilename:
# add json to the queue # add json to the queue

View File

@ -17,25 +17,37 @@ from requests.auth import AuthBase
import base64 import base64
import json import json
from time import gmtime, strftime from time import gmtime, strftime
from pprint import pprint
def signPostHeaders(privateKeyPem: str, nickname: str, domain: str, \ def signPostHeaders(dateStr: str,privateKeyPem: str, \
port: int,path: 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 """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.
""" """
if port!=80 and port!=443: if port:
domain=domain+':'+str(port) if port!=80 and port!=443:
if ':' not in domain:
domain=domain+':'+str(port)
dateStr=strftime("%a, %d %b %Y %H:%M:%S %Z", gmtime()) if toPort:
if toPort!=80 and toPort!=443:
if ':' not in toDomain:
toDomain=toDomain+':'+str(port)
if not dateStr:
dateStr=strftime("%a, %d %b %Y %H:%M:%S %Z", gmtime())
keyID = httpPrefix+'://'+domain+'/users/'+nickname+'#main-key' keyID = httpPrefix+'://'+domain+'/users/'+nickname+'#main-key'
if not messageBodyJson: if not messageBodyJson:
headers = {'(request-target)': f'post {path}','host': domain,'date': dateStr,'content-type': 'application/json'} headers = {'(request-target)': f'post {path}','host': toDomain,'date': dateStr,'content-type': 'application/json'}
else: else:
messageBodyJsonStr=json.dumps(messageBodyJson) messageBodyJsonStr=json.dumps(messageBodyJson)
bodyDigest = \ bodyDigest = \
base64.b64encode(SHA256.new(messageBodyJsonStr.encode()).digest()).decode('utf-8') base64.b64encode(SHA256.new(messageBodyJsonStr.encode()).digest()).decode('utf-8')
headers = {'(request-target)': f'post {path}','host': domain,'date': dateStr,'digest': f'SHA-256={bodyDigest}','content-type': 'application/activity+json'} 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) privateKeyPem = RSA.import_key(privateKeyPem)
#headers.update({ #headers.update({
# '(request-target)': f'post {path}', # '(request-target)': f'post {path}',
@ -45,8 +57,9 @@ def signPostHeaders(privateKeyPem: str, nickname: str, domain: str, \
signedHeaderText = '' signedHeaderText = ''
for headerKey in signedHeaderKeys: for headerKey in signedHeaderKeys:
signedHeaderText += f'{headerKey}: {headers[headerKey]}\n' signedHeaderText += f'{headerKey}: {headers[headerKey]}\n'
#print(f'headerKey: {headerKey}: {headers[headerKey]}') print(f'*********************signing: headerKey: {headerKey}: {headers[headerKey]}')
signedHeaderText = signedHeaderText.strip() signedHeaderText = signedHeaderText.strip()
print('******************************Send: signedHeaderText: '+signedHeaderText)
headerDigest = SHA256.new(signedHeaderText.encode('ascii')) headerDigest = SHA256.new(signedHeaderText.encode('ascii'))
# Sign the digest # Sign the digest
@ -64,31 +77,44 @@ def signPostHeaders(privateKeyPem: str, nickname: str, domain: str, \
[f'{k}="{v}"' for k, v in signatureDict.items()]) [f'{k}="{v}"' for k, v in signatureDict.items()])
return signatureHeader return signatureHeader
def createSignedHeader(privateKeyPem: str,nickname: str,domain: str,port: int, \ def createSignedHeader(privateKeyPem: str,nickname: str, \
domain: str,port: int, \
toDomain: str,toPort: int, \
path: str,httpPrefix: str,withDigest: bool, \ path: str,httpPrefix: str,withDigest: bool, \
messageBodyJson: {}) -> {}: messageBodyJson: {}) -> {}:
headerDomain=domain """Note that the domain is the destination, not the sender
"""
contentType='application/activity+json'
headerDomain=toDomain
if port: if toPort:
if port!=80 and port!=443: if toPort!=80 and toPort!=443:
headerDomain=headerDomain+':'+str(port) if ':' not in headerDomain:
headerDomain=headerDomain+':'+str(toPort)
dateStr=strftime("%a, %d %b %Y %H:%M:%S %Z", gmtime()) dateStr=strftime("%a, %d %b %Y %H:%M:%S %Z", gmtime())
path='/inbox'
print('Testing 123 '+str(withDigest))
if not withDigest: if not withDigest:
headers = {'(request-target)': f'post {path}','host': headerDomain,'date': dateStr} headers = {'(request-target)': f'post {path}','host': headerDomain,'date': dateStr}
signatureHeader = \ signatureHeader = \
signPostHeaders(privateKeyPem, nickname, domain, port, \ signPostHeaders(dateStr,privateKeyPem,nickname, \
path, httpPrefix, None) domain,port,toDomain,toPort, \
path,httpPrefix,None)
else: else:
messageBodyJsonStr=json.dumps(messageBodyJson) messageBodyJsonStr=json.dumps(messageBodyJson)
bodyDigest = \ bodyDigest = \
base64.b64encode(SHA256.new(messageBodyJsonStr.encode()).digest()).decode('utf-8') base64.b64encode(SHA256.new(messageBodyJsonStr.encode()).digest()).decode('utf-8')
headers = {'(request-target)': f'post {path}','host': headerDomain,'date': dateStr,'digest': f'SHA-256={bodyDigest}','content-type': 'application/activity+json'} print('***************************Send (request-target): post '+path)
print('***************************Send host: '+headerDomain)
print('***************************Send date: '+dateStr)
print('***************************Send digest: '+bodyDigest)
print('***************************Send Content-type: '+contentType)
print('***************************Send messageBodyJsonStr: '+messageBodyJsonStr)
headers = {'(request-target)': f'post {path}','host': headerDomain,'date': dateStr,'digest': f'SHA-256={bodyDigest}','content-type': contentType}
signatureHeader = \ signatureHeader = \
signPostHeaders(privateKeyPem, nickname, domain, port, \ signPostHeaders(dateStr,privateKeyPem,nickname, \
path, httpPrefix, messageBodyJson) domain,port, \
toDomain,toPort, \
path,httpPrefix,messageBodyJson)
headers['signature'] = signatureHeader headers['signature'] = signatureHeader
return headers return headers
@ -115,8 +141,8 @@ def verifyPostHeaders(httpPrefix: str,publicKeyPem: str,headers: dict, \
k: v[1:-1] k: v[1:-1]
for k, v in [i.split('=', 1) for i in signatureHeader.split(',')] for k, v in [i.split('=', 1) for i in signatureHeader.split(',')]
} }
#print('signatureHeader: '+str(signatureHeader)) print('********************signatureHeader: '+str(signatureHeader))
#print('signatureDict: '+str(signatureDict)) print('********************signatureDict: '+str(signatureDict))
# Unpack the signed headers and set values based on current headers and # Unpack the signed headers and set values based on current headers and
# body (if a digest was included) # body (if a digest was included)
@ -125,23 +151,30 @@ def verifyPostHeaders(httpPrefix: str,publicKeyPem: str,headers: dict, \
if signedHeader == '(request-target)': if signedHeader == '(request-target)':
signedHeaderList.append( signedHeaderList.append(
f'(request-target): {method.lower()} {path}') f'(request-target): {method.lower()} {path}')
print('***************************Verify (request-target): '+method.lower()+' '+path)
elif signedHeader == 'digest': elif signedHeader == 'digest':
bodyDigest = \ bodyDigest = \
base64.b64encode(SHA256.new(messageBodyJsonStr.encode()).digest()).decode('utf-8') base64.b64encode(SHA256.new(messageBodyJsonStr.strip().encode()).digest()).decode('utf-8')
signedHeaderList.append(f'digest: SHA-256={bodyDigest}') signedHeaderList.append(f'digest: SHA-256={bodyDigest}')
print('***************************Verify digest: SHA-256='+bodyDigest)
print('***************************Verify messageBodyJsonStr: '+messageBodyJsonStr)
else: else:
if headers.get(signedHeader): if headers.get(signedHeader):
print('***************************Verify '+signedHeader+': '+headers[signedHeader])
signedHeaderList.append( signedHeaderList.append(
f'{signedHeader}: {headers[signedHeader]}') f'{signedHeader}: {headers[signedHeader]}')
else: else:
signedHeaderCap=signedHeader.capitalize() signedHeaderCap=signedHeader.capitalize()
print('***************************Verify '+signedHeaderCap+': '+headers[signedHeaderCap])
if headers.get(signedHeaderCap): if headers.get(signedHeaderCap):
signedHeaderList.append( signedHeaderList.append(
f'{signedHeader}: {headers[signedHeaderCap]}') f'{signedHeader}: {headers[signedHeaderCap]}')
#print('signedHeaderList: '+str(signedHeaderList)) print('***********************signedHeaderList: ')
pprint(signedHeaderList)
# Now we have our header data digest # Now we have our header data digest
signedHeaderText = '\n'.join(signedHeaderList) signedHeaderText = '\n'.join(signedHeaderList)
print('***********************Verify: signedHeaderText: '+signedHeaderText)
headerDigest = SHA256.new(signedHeaderText.encode('ascii')) headerDigest = SHA256.new(signedHeaderText.encode('ascii'))
# Get the signature, verify with public key, return result # Get the signature, verify with public key, return result

View File

@ -79,6 +79,7 @@ def validInboxFilenames(baseDir: str,nickname: str,domain: str, \
print('filename: '+filename) print('filename: '+filename)
return False return False
if not expectedStr in filename: if not expectedStr in filename:
print('Expected: '+expectedStr)
print('Invalid filename: '+filename) print('Invalid filename: '+filename)
return False return False
return True return True
@ -198,8 +199,11 @@ def savePostToInboxQueue(baseDir: str,httpPrefix: str,nickname: str, domain: str
originalPostId=postJsonObject['id'].replace('/activity','') originalPostId=postJsonObject['id'].replace('/activity','')
statusNumber,published = getStatusNumber() statusNumber,published = getStatusNumber()
postId=httpPrefix+'://'+originalDomain+'/users/'+nickname+'/statuses/'+statusNumber if actor:
postJsonObject['id']=postId postId=actor+'/statuses/'+statusNumber
else:
postId=httpPrefix+'://'+originalDomain+'/users/'+nickname+'/statuses/'+statusNumber
# NOTE: don't change postJsonObject['id'] before signature check
currTime=datetime.datetime.utcnow() currTime=datetime.datetime.utcnow()
published=currTime.strftime("%Y-%m-%dT%H:%M:%SZ") published=currTime.strftime("%Y-%m-%dT%H:%M:%SZ")

View File

@ -971,12 +971,14 @@ def sendPost(projectVersion: str, \
if toDomain not in inboxUrl: if toDomain not in inboxUrl:
return 7 return 7
postPath='/'+inboxUrl.split('/')[-1] #postPath='/'+inboxUrl.split('/')[-1]
postPath=inboxUrl.split(toDomain)[1]
# construct the http header # construct the http header
signatureHeaderJson = \ signatureHeaderJson = \
createSignedHeader(privateKeyPem, nickname, domain, port, \ createSignedHeader(privateKeyPem,nickname,domain,port, \
postPath, httpPrefix, withDigest, postJsonObject) toDomain,toPort, \
postPath,httpPrefix,withDigest,postJsonObject)
# Keep the number of threads being used small # Keep the number of threads being used small
while len(sendThreads)>10: while len(sendThreads)>10:
@ -1198,8 +1200,9 @@ def sendSignedJson(postJsonObject: {},session,baseDir: str, \
# construct the http header # construct the http header
signatureHeaderJson = \ signatureHeaderJson = \
createSignedHeader(privateKeyPem, nickname, domain, port, \ createSignedHeader(privateKeyPem,nickname,domain,port, \
postPath, httpPrefix, withDigest, postJsonObject) toDomain,toPort, \
postPath,httpPrefix,withDigest,postJsonObject)
# Keep the number of threads being used small # Keep the number of threads being used small
while len(sendThreads)>10: while len(sendThreads)>10:

View File

@ -96,13 +96,19 @@ def testHttpsigBase(withDigest):
if not withDigest: if not withDigest:
headers = {'host': headersDomain,'date': dateStr,'content-type': 'application/json'} headers = {'host': headersDomain,'date': dateStr,'content-type': 'application/json'}
signatureHeader = \ signatureHeader = \
signPostHeaders(privateKeyPem, nickname, domain, port, boxpath, httpPrefix, None) signPostHeaders(dateStr,privateKeyPem, nickname, \
domain, port, \
domain, port, \
boxpath, httpPrefix, None)
else: else:
bodyDigest = \ bodyDigest = \
base64.b64encode(SHA256.new(messageBodyJsonStr.encode()).digest()).decode('utf-8') base64.b64encode(SHA256.new(messageBodyJsonStr.encode()).digest()).decode('utf-8')
headers = {'host': headersDomain,'date': dateStr,'digest': f'SHA-256={bodyDigest}','content-type': contentType} headers = {'host': headersDomain,'date': dateStr,'digest': f'SHA-256={bodyDigest}','content-type': contentType}
signatureHeader = \ signatureHeader = \
signPostHeaders(privateKeyPem, nickname, domain, port, boxpath, httpPrefix, messageBodyJson) signPostHeaders(dateStr,privateKeyPem, nickname, \
domain, port, \
domain, port, \
boxpath, httpPrefix, messageBodyJson)
headers['signature'] = signatureHeader headers['signature'] = signatureHeader
assert verifyPostHeaders(httpPrefix, publicKeyPem, headers, \ assert verifyPostHeaders(httpPrefix, publicKeyPem, headers, \