mirror of https://gitlab.com/bashrc2/epicyon
http signature fixes
parent
ed050d48f5
commit
977160eecf
|
@ -400,6 +400,7 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
messageJson,
|
messageJson,
|
||||||
self.headers['host'],
|
self.headers['host'],
|
||||||
self.headers['signature'],
|
self.headers['signature'],
|
||||||
|
self.headers,
|
||||||
'/'+self.path.split('/')[-1],
|
'/'+self.path.split('/')[-1],
|
||||||
self.server.debug)
|
self.server.debug)
|
||||||
if queueFilename:
|
if queueFilename:
|
||||||
|
|
47
httpsig.py
47
httpsig.py
|
@ -28,20 +28,21 @@ def signPostHeaders(privateKeyPem: str, nickname: str, domain: str, \
|
||||||
dateStr=strftime("%a, %d %b %Y %H:%M:%S %Z", gmtime())
|
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 = {'host': domain}
|
headers = {'(request-target)': f'post {path}','host': domain,'date': dateStr,'content-type': 'application/json'}
|
||||||
else:
|
else:
|
||||||
bodyDigest = \
|
bodyDigest = \
|
||||||
base64.b64encode(SHA256.new(messageBodyJson.encode()).digest())
|
base64.b64encode(SHA256.new(messageBodyJson.encode()).digest())
|
||||||
headers = {'host': domain,'digest': f'SHA-256={bodyDigest}'}
|
headers = {'(request-target)': f'post {path}','host': domain,'date': dateStr,'digest': f'SHA-256={bodyDigest}','content-type': 'application/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}',
|
||||||
})
|
#})
|
||||||
# build a digest for signing
|
# build a digest for signing
|
||||||
signedHeaderKeys = headers.keys()
|
signedHeaderKeys = headers.keys()
|
||||||
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]}')
|
||||||
signedHeaderText = signedHeaderText.strip()
|
signedHeaderText = signedHeaderText.strip()
|
||||||
headerDigest = SHA256.new(signedHeaderText.encode('ascii'))
|
headerDigest = SHA256.new(signedHeaderText.encode('ascii'))
|
||||||
|
|
||||||
|
@ -53,13 +54,11 @@ def signPostHeaders(privateKeyPem: str, nickname: str, domain: str, \
|
||||||
signatureDict = {
|
signatureDict = {
|
||||||
'keyId': keyID,
|
'keyId': keyID,
|
||||||
'algorithm': 'rsa-sha256',
|
'algorithm': 'rsa-sha256',
|
||||||
# 'date': dateStr,
|
|
||||||
'headers': ' '.join(signedHeaderKeys),
|
'headers': ' '.join(signedHeaderKeys),
|
||||||
'signature': signature
|
'signature': signature
|
||||||
}
|
}
|
||||||
signatureHeader = ','.join(
|
signatureHeader = ','.join(
|
||||||
[f'{k}="{v}"' for k, v in signatureDict.items()])
|
[f'{k}="{v}"' for k, v in signatureDict.items()])
|
||||||
# print('signatureHeader: '+str(signatureHeader))
|
|
||||||
return signatureHeader
|
return signatureHeader
|
||||||
|
|
||||||
def createSignedHeader(privateKeyPem: str,nickname: str,domain: str,port: int, \
|
def createSignedHeader(privateKeyPem: str,nickname: str,domain: str,port: int, \
|
||||||
|
@ -67,27 +66,27 @@ def createSignedHeader(privateKeyPem: str,nickname: str,domain: str,port: int, \
|
||||||
messageBodyJson: {}) -> {}:
|
messageBodyJson: {}) -> {}:
|
||||||
headerDomain=domain
|
headerDomain=domain
|
||||||
|
|
||||||
if port!=80 and port!=443:
|
if port:
|
||||||
headerDomain=headerDomain+':'+str(port)
|
if port!=80 and port!=443:
|
||||||
|
headerDomain=headerDomain+':'+str(port)
|
||||||
|
|
||||||
dateStr=strftime("%a, %d %b %Y %H:%M:%S %Z", gmtime())
|
dateStr=strftime("%a, %d %b %Y %H:%M:%S %Z", gmtime())
|
||||||
|
path='/inbox'
|
||||||
if not withDigest:
|
if not withDigest:
|
||||||
headers = {'host': headerDomain}
|
headers = {'(request-target)': f'post {path}','host': headerDomain,'date': dateStr}
|
||||||
else:
|
else:
|
||||||
messageBodyJsonStr=json.dumps(messageBodyJson)
|
messageBodyJsonStr=json.dumps(messageBodyJson)
|
||||||
bodyDigest = \
|
bodyDigest = \
|
||||||
base64.b64encode(SHA256.new(messageBodyJsonStr.encode()).digest())
|
base64.b64encode(SHA256.new(messageBodyJsonStr.encode()).digest())
|
||||||
headers = {'host': headerDomain, 'digest': f'SHA-256={bodyDigest}'}
|
headers = {'(request-target)': f'post {path}','host': headerDomain,'date': dateStr,'digest': f'SHA-256={bodyDigest}','content-type': 'application/json'}
|
||||||
path='/inbox'
|
|
||||||
signatureHeader = signPostHeaders(privateKeyPem, nickname, domain, port, \
|
signatureHeader = signPostHeaders(privateKeyPem, nickname, domain, port, \
|
||||||
path, httpPrefix, None)
|
path, httpPrefix, None)
|
||||||
headers['date'] = dateStr
|
|
||||||
headers['signature'] = signatureHeader
|
headers['signature'] = signatureHeader
|
||||||
headers['Content-type'] = 'application/json'
|
#print('******************************************************http headers: '+str(headers))
|
||||||
return headers
|
return headers
|
||||||
|
|
||||||
def verifyPostHeaders(httpPrefix: str, publicKeyPem: str, headers: dict, \
|
def verifyPostHeaders(httpPrefix: str,publicKeyPem: str,headers: dict, \
|
||||||
path: str, GETmethod: bool, \
|
path: str,GETmethod: bool, \
|
||||||
messageBodyJsonStr: str) -> 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.
|
||||||
|
@ -109,6 +108,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('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)
|
||||||
|
@ -117,21 +118,19 @@ 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}')
|
||||||
elif signedHeader.lower() == 'content-type':
|
|
||||||
continue
|
|
||||||
elif signedHeader == 'date':
|
|
||||||
continue
|
|
||||||
elif signedHeader == 'digest':
|
elif signedHeader == 'digest':
|
||||||
bodyDigest = \
|
bodyDigest = \
|
||||||
base64.b64encode(SHA256.new(messageBodyJsonStr.encode()).digest())
|
base64.b64encode(SHA256.new(messageBodyJsonStr.encode()).digest())
|
||||||
signedHeaderList.append(f'digest: SHA-256={bodyDigest}')
|
signedHeaderList.append(f'digest: SHA-256={bodyDigest}')
|
||||||
else:
|
else:
|
||||||
try:
|
if headers.get(signedHeader):
|
||||||
signedHeaderList.append(
|
signedHeaderList.append(
|
||||||
f'{signedHeader}: {headers[signedHeader]}')
|
f'{signedHeader}: {headers[signedHeader]}')
|
||||||
except Exception as e:
|
else:
|
||||||
print('http signature check failure')
|
signedHeaderCap=signedHeader.capitalize()
|
||||||
print(e)
|
if headers.get(signedHeaderCap):
|
||||||
|
signedHeaderList.append(
|
||||||
|
f'{signedHeader}: {headers[signedHeaderCap]}')
|
||||||
|
|
||||||
# Now we have our header data digest
|
# Now we have our header data digest
|
||||||
signedHeaderText = '\n'.join(signedHeaderList)
|
signedHeaderText = '\n'.join(signedHeaderList)
|
||||||
|
|
11
inbox.py
11
inbox.py
|
@ -164,7 +164,7 @@ def validPublishedDate(published) -> bool:
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def savePostToInboxQueue(baseDir: str,httpPrefix: str,nickname: str, domain: str,postJsonObject: {},host: str,headers: str,postPath: str,debug: bool) -> str:
|
def savePostToInboxQueue(baseDir: str,httpPrefix: str,nickname: str, domain: str,postJsonObject: {},host: str,headers: str,httpHeaders: str,postPath: str,debug: bool) -> str:
|
||||||
"""Saves the give json to the inbox queue for the person
|
"""Saves the give json to the inbox queue for the person
|
||||||
keyId specifies the actor sending the post
|
keyId specifies the actor sending the post
|
||||||
"""
|
"""
|
||||||
|
@ -225,6 +225,7 @@ def savePostToInboxQueue(baseDir: str,httpPrefix: str,nickname: str, domain: str
|
||||||
'published': published,
|
'published': published,
|
||||||
'host': host,
|
'host': host,
|
||||||
'headers': headers,
|
'headers': headers,
|
||||||
|
'httpHeaders': httpHeaders,
|
||||||
'path': postPath,
|
'path': postPath,
|
||||||
'post': postJsonObject,
|
'post': postJsonObject,
|
||||||
'filename': filename,
|
'filename': filename,
|
||||||
|
@ -1135,15 +1136,11 @@ def runInboxQueue(projectVersion: str, \
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# check the signature
|
# check the signature
|
||||||
verifyHeaders={
|
|
||||||
'host': queueJson['host'],
|
|
||||||
'signature': queueJson['headers']
|
|
||||||
}
|
|
||||||
if debug:
|
if debug:
|
||||||
print('DEBUG: checking http headers')
|
print('DEBUG: checking http headers')
|
||||||
pprint(verifyHeaders)
|
pprint(queueJson['headers'])
|
||||||
if not verifyPostHeaders(httpPrefix, \
|
if not verifyPostHeaders(httpPrefix, \
|
||||||
pubKey,verifyHeaders, \
|
pubKey,queueJson['headers'], \
|
||||||
queueJson['path'],False, \
|
queueJson['path'],False, \
|
||||||
json.dumps(queueJson['post'])):
|
json.dumps(queueJson['post'])):
|
||||||
if debug:
|
if debug:
|
||||||
|
|
|
@ -40,7 +40,7 @@ def getJson(session,url: str,headers: {},params: {}, \
|
||||||
print('WARN: no session specified for getJson')
|
print('WARN: no session specified for getJson')
|
||||||
session.cookies.clear()
|
session.cookies.clear()
|
||||||
try:
|
try:
|
||||||
result=session.get(url, headers=sessionHeaders, params=sessionParams, allow_redirects=True)
|
result=session.get(url, headers=sessionHeaders, params=sessionParams)
|
||||||
return result.json()
|
return result.json()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print('ERROR: getJson failed')
|
print('ERROR: getJson failed')
|
||||||
|
@ -62,7 +62,7 @@ def postJson(session,postJsonObject: {},federationList: [],inboxUrl: str,headers
|
||||||
print('postJson: '+inboxUrl+' not permitted')
|
print('postJson: '+inboxUrl+' not permitted')
|
||||||
return None
|
return None
|
||||||
|
|
||||||
postResult = session.post(url = inboxUrl, data = json.dumps(postJsonObject), headers=headers, allow_redirects=True)
|
postResult = session.post(url = inboxUrl, data = json.dumps(postJsonObject), headers=headers)
|
||||||
return postResult.text
|
return postResult.text
|
||||||
|
|
||||||
def postImage(session,attachImageFilename: str,federationList: [],inboxUrl: str,headers: {},capability: str) -> str:
|
def postImage(session,attachImageFilename: str,federationList: [],inboxUrl: str,headers: {},capability: str) -> str:
|
||||||
|
|
28
tests.py
28
tests.py
|
@ -82,7 +82,7 @@ def testHttpsigBase(withDigest):
|
||||||
privateKeyPem,publicKeyPem,person,wfEndpoint= \
|
privateKeyPem,publicKeyPem,person,wfEndpoint= \
|
||||||
createPerson(path,nickname,domain,port,httpPrefix,False,password)
|
createPerson(path,nickname,domain,port,httpPrefix,False,password)
|
||||||
assert privateKeyPem
|
assert privateKeyPem
|
||||||
messageBodyJsonStr = '{"a key": "a value", "another key": "A string","yet another key": "A string"}'
|
messageBodyJsonStr = '{"a key": "a value", "another key": "A string","yet another key": "Another string"}'
|
||||||
|
|
||||||
headersDomain=domain
|
headersDomain=domain
|
||||||
if port!=80 and port !=443:
|
if port!=80 and port !=443:
|
||||||
|
@ -90,39 +90,43 @@ def testHttpsigBase(withDigest):
|
||||||
|
|
||||||
dateStr=strftime("%a, %d %b %Y %H:%M:%S %Z", gmtime())
|
dateStr=strftime("%a, %d %b %Y %H:%M:%S %Z", gmtime())
|
||||||
if not withDigest:
|
if not withDigest:
|
||||||
headers = {'host': headersDomain}
|
headers = {'host': headersDomain,'date': dateStr,'content-type': 'application/json'}
|
||||||
else:
|
else:
|
||||||
bodyDigest = \
|
bodyDigest = \
|
||||||
base64.b64encode(SHA256.new(messageBodyJsonStr.encode()).digest())
|
base64.b64encode(SHA256.new(messageBodyJsonStr.encode()).digest())
|
||||||
headers = {'host': headersDomain, 'date': dateStr, 'digest': f'SHA-256={bodyDigest}'}
|
headers = {'host': headersDomain,'date': dateStr,'digest': f'SHA-256={bodyDigest}','content-type': 'application/json'}
|
||||||
|
|
||||||
boxpath='/inbox'
|
boxpath='/inbox'
|
||||||
signatureHeader = \
|
signatureHeader = \
|
||||||
signPostHeaders(privateKeyPem, nickname, domain, port, boxpath, httpPrefix, None)
|
signPostHeaders(privateKeyPem, nickname, domain, port, boxpath, httpPrefix, None)
|
||||||
headers['signature'] = signatureHeader
|
headers['signature'] = signatureHeader
|
||||||
assert verifyPostHeaders(httpPrefix, publicKeyPem, headers, \
|
assert verifyPostHeaders(httpPrefix, publicKeyPem, headers, \
|
||||||
'/inbox' ,False, messageBodyJsonStr)
|
'/inbox' ,False, \
|
||||||
|
messageBodyJsonStr)
|
||||||
assert verifyPostHeaders(httpPrefix, publicKeyPem, headers, \
|
assert verifyPostHeaders(httpPrefix, publicKeyPem, headers, \
|
||||||
'/parambulator/inbox', False , messageBodyJsonStr) == False
|
'/parambulator/inbox',False, \
|
||||||
|
messageBodyJsonStr) == False
|
||||||
assert verifyPostHeaders(httpPrefix, publicKeyPem, headers, \
|
assert verifyPostHeaders(httpPrefix, publicKeyPem, headers, \
|
||||||
'/inbox', True, messageBodyJsonStr) == False
|
'/inbox',True, \
|
||||||
|
messageBodyJsonStr) == False
|
||||||
if not withDigest:
|
if not withDigest:
|
||||||
# fake domain
|
# fake domain
|
||||||
headers = {'host': 'bogon.domain'}
|
headers = {'host': 'bogon.domain','date': dateStr,'content-type': 'application/json'}
|
||||||
else:
|
else:
|
||||||
# correct domain but fake message
|
# correct domain but fake message
|
||||||
messageBodyJsonStr = '{"a key": "a value", "another key": "Fake GNUs", "yet another key": "Fake GNUs"}'
|
messageBodyJsonStr = '{"a key": "a value", "another key": "Fake GNUs", "yet another key": "More Fake GNUs"}'
|
||||||
bodyDigest = base64.b64encode(SHA256.new(messageBodyJsonStr.encode()).digest())
|
bodyDigest = base64.b64encode(SHA256.new(messageBodyJsonStr.encode()).digest())
|
||||||
headers = {'host': domain, 'date': dateStr, 'digest': f'SHA-256={bodyDigest}'}
|
headers = {'host': domain,'date': dateStr,'digest': f'SHA-256={bodyDigest}','content-type': 'application/json'}
|
||||||
headers['signature'] = signatureHeader
|
headers['signature'] = signatureHeader
|
||||||
assert verifyPostHeaders(httpPrefix, publicKeyPem, headers, \
|
assert verifyPostHeaders(httpPrefix,publicKeyPem,headers, \
|
||||||
'/inbox', True, messageBodyJsonStr) == False
|
'/inbox',True, \
|
||||||
|
messageBodyJsonStr) == False
|
||||||
os.chdir(baseDir)
|
os.chdir(baseDir)
|
||||||
shutil.rmtree(path)
|
shutil.rmtree(path)
|
||||||
|
|
||||||
def testHttpsig():
|
def testHttpsig():
|
||||||
testHttpsigBase(False)
|
|
||||||
testHttpsigBase(True)
|
testHttpsigBase(True)
|
||||||
|
testHttpsigBase(False)
|
||||||
|
|
||||||
def testCache():
|
def testCache():
|
||||||
print('testCache')
|
print('testCache')
|
||||||
|
|
Loading…
Reference in New Issue