forked from indymedia/epicyon
Fixing http signatures
parent
174e166769
commit
d2394b3a69
|
@ -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
|
||||||
|
|
81
httpsig.py
81
httpsig.py
|
@ -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
|
||||||
|
|
8
inbox.py
8
inbox.py
|
@ -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")
|
||||||
|
|
13
posts.py
13
posts.py
|
@ -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:
|
||||||
|
|
10
tests.py
10
tests.py
|
@ -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, \
|
||||||
|
|
Loading…
Reference in New Issue