Specify signing algorithm in tests

merge-requests/30/head
Bob Mottram 2021-11-23 11:41:40 +00:00
parent e732061e07
commit 1b9277e323
3 changed files with 94 additions and 28 deletions

View File

@ -28,12 +28,41 @@ from utils import getSHA512
from utils import localActorUrl from utils import localActorUrl
def messageContentDigest(messageBodyJsonStr: str) -> str: def messageContentDigest(messageBodyJsonStr: str, digestAlgorithm: str) -> str:
"""Returns the digest for the message body
"""
msg = messageBodyJsonStr.encode('utf-8') msg = messageBodyJsonStr.encode('utf-8')
hashResult = getSHA256(msg) if digestAlgorithm == 'rsa-sha512' or \
digestAlgorithm == 'rsa-pss-sha512':
hashResult = getSHA512(msg)
else:
hashResult = getSHA256(msg)
return base64.b64encode(hashResult).decode('utf-8') return base64.b64encode(hashResult).decode('utf-8')
def getDigestPrefix(digestAlgorithm: str) -> str:
"""Returns the prefix for the message body digest
"""
if digestAlgorithm == 'rsa-sha512' or \
digestAlgorithm == 'rsa-pss-sha512':
return 'SHA-512'
return 'SHA-256'
def getDigestAlgorithmFromHeaders(httpHeaders: {}) -> str:
"""Returns the digest algorithm from http headers
"""
digestStr = None
if httpHeaders.get('digest'):
digestStr = httpHeaders['digest']
elif httpHeaders.get('Digest'):
digestStr = httpHeaders['Digest']
if digestStr:
if digestStr.startswith('SHA-512'):
return 'rsa-sha512'
return 'rsa-sha256'
def signPostHeaders(dateStr: str, privateKeyPem: str, def signPostHeaders(dateStr: str, privateKeyPem: str,
nickname: str, nickname: str,
domain: str, port: int, domain: str, port: int,
@ -41,10 +70,15 @@ def signPostHeaders(dateStr: str, privateKeyPem: str,
path: str, path: str,
httpPrefix: str, httpPrefix: str,
messageBodyJsonStr: str, messageBodyJsonStr: str,
contentType: str) -> str: contentType: str,
algorithm: str) -> 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.
""" """
# it is assumed that the hash used for the digest will be the same
# as for the signature
digestAlgorithm = algorithm
domain = getFullDomain(domain, port) domain = getFullDomain(domain, port)
toDomain = getFullDomain(toDomain, toPort) toDomain = getFullDomain(toDomain, toPort)
@ -65,13 +99,15 @@ def signPostHeaders(dateStr: str, privateKeyPem: str,
'accept': contentType 'accept': contentType
} }
else: else:
bodyDigest = messageContentDigest(messageBodyJsonStr) bodyDigest = \
messageContentDigest(messageBodyJsonStr, digestAlgorithm)
digestPrefix = getDigestPrefix(digestAlgorithm)
contentLength = len(messageBodyJsonStr) contentLength = len(messageBodyJsonStr)
headers = { headers = {
'(request-target)': f'post {path}', '(request-target)': f'post {path}',
'host': toDomain, 'host': toDomain,
'date': dateStr, 'date': dateStr,
'digest': f'SHA-256={bodyDigest}', 'digest': f'{digestPrefix}={bodyDigest}',
'content-type': 'application/activity+json', 'content-type': 'application/activity+json',
'content-length': str(contentLength) 'content-length': str(contentLength)
} }
@ -100,7 +136,7 @@ def signPostHeaders(dateStr: str, privateKeyPem: str,
# Put it into a valid HTTP signature format # Put it into a valid HTTP signature format
signatureDict = { signatureDict = {
'keyId': keyID, 'keyId': keyID,
'algorithm': 'rsa-sha256', 'algorithm': algorithm,
'headers': ' '.join(signedHeaderKeys), 'headers': ' '.join(signedHeaderKeys),
'signature': signature 'signature': signature
} }
@ -122,6 +158,10 @@ def signPostHeadersNew(dateStr: str, privateKeyPem: str,
used to verify the authenticity of an HTTP transmission. used to verify the authenticity of an HTTP transmission.
See https://tools.ietf.org/html/draft-ietf-httpbis-message-signatures See https://tools.ietf.org/html/draft-ietf-httpbis-message-signatures
""" """
# it is assumed that the hash used for the digest will be the same
# as for the signature
digestAlgorithm = algorithm
domain = getFullDomain(domain, port) domain = getFullDomain(domain, port)
toDomain = getFullDomain(toDomain, toPort) toDomain = getFullDomain(toDomain, toPort)
@ -143,14 +183,15 @@ def signPostHeadersNew(dateStr: str, privateKeyPem: str,
'date': dateStr 'date': dateStr
} }
else: else:
bodyDigest = messageContentDigest(messageBodyJsonStr) bodyDigest = messageContentDigest(messageBodyJsonStr, digestAlgorithm)
digestPrefix = getDigestPrefix(digestAlgorithm)
contentLength = len(messageBodyJsonStr) contentLength = len(messageBodyJsonStr)
headers = { headers = {
'@request-target': f'post {path}', '@request-target': f'post {path}',
'@created': str(secondsSinceEpoch), '@created': str(secondsSinceEpoch),
'host': toDomain, 'host': toDomain,
'date': dateStr, 'date': dateStr,
'digest': f'SHA-256={bodyDigest}', 'digest': f'{digestPrefix}={bodyDigest}',
'content-type': 'application/activity+json', 'content-type': 'application/activity+json',
'content-length': str(contentLength) 'content-length': str(contentLength)
} }
@ -210,6 +251,8 @@ def createSignedHeader(dateStr: str, privateKeyPem: str, nickname: str,
contentType: str) -> {}: contentType: str) -> {}:
"""Note that the domain is the destination, not the sender """Note that the domain is the destination, not the sender
""" """
algorithm = 'rsa-sha256'
digestAlgorithm = 'rsa-sha256'
headerDomain = getFullDomain(toDomain, toPort) headerDomain = getFullDomain(toDomain, toPort)
# if no date is given then create one # if no date is given then create one
@ -230,15 +273,17 @@ def createSignedHeader(dateStr: str, privateKeyPem: str, nickname: str,
signatureHeader = \ signatureHeader = \
signPostHeaders(dateStr, privateKeyPem, nickname, signPostHeaders(dateStr, privateKeyPem, nickname,
domain, port, toDomain, toPort, domain, port, toDomain, toPort,
path, httpPrefix, None, contentType) path, httpPrefix, None, contentType,
algorithm)
else: else:
bodyDigest = messageContentDigest(messageBodyJsonStr) bodyDigest = messageContentDigest(messageBodyJsonStr, digestAlgorithm)
digestPrefix = getDigestPrefix(digestAlgorithm)
contentLength = len(messageBodyJsonStr) contentLength = len(messageBodyJsonStr)
headers = { headers = {
'(request-target)': f'post {path}', '(request-target)': f'post {path}',
'host': headerDomain, 'host': headerDomain,
'date': dateStr, 'date': dateStr,
'digest': f'SHA-256={bodyDigest}', 'digest': f'{digestPrefix}={bodyDigest}',
'content-length': str(contentLength), 'content-length': str(contentLength),
'content-type': contentType 'content-type': contentType
} }
@ -247,7 +292,7 @@ def createSignedHeader(dateStr: str, privateKeyPem: str, nickname: str,
domain, port, domain, port,
toDomain, toPort, toDomain, toPort,
path, httpPrefix, messageBodyJsonStr, path, httpPrefix, messageBodyJsonStr,
contentType) contentType, algorithm)
headers['signature'] = signatureHeader headers['signature'] = signatureHeader
return headers return headers
@ -341,6 +386,7 @@ def verifyPostHeaders(httpPrefix: str,
# body (if a digest was included) # body (if a digest was included)
signedHeaderList = [] signedHeaderList = []
algorithm = 'rsa-sha256' algorithm = 'rsa-sha256'
digestAlgorithm = 'rsa-sha256'
for signedHeader in signatureDict[requestTargetKey].split(fieldSep2): for signedHeader in signatureDict[requestTargetKey].split(fieldSep2):
signedHeader = signedHeader.strip() signedHeader = signedHeader.strip()
if debug: if debug:
@ -387,7 +433,8 @@ def verifyPostHeaders(httpPrefix: str,
if messageBodyDigest: if messageBodyDigest:
bodyDigest = messageBodyDigest bodyDigest = messageBodyDigest
else: else:
bodyDigest = messageContentDigest(messageBodyJsonStr) bodyDigest = \
messageContentDigest(messageBodyJsonStr, digestAlgorithm)
signedHeaderList.append(f'digest: SHA-256={bodyDigest}') signedHeaderList.append(f'digest: SHA-256={bodyDigest}')
elif signedHeader == 'content-length': elif signedHeader == 'content-length':
if headers.get(signedHeader): if headers.get(signedHeader):

View File

@ -60,6 +60,7 @@ from utils import localActorUrl
from utils import hasObjectStringType from utils import hasObjectStringType
from categories import getHashtagCategories from categories import getHashtagCategories
from categories import setHashtagCategory from categories import setHashtagCategory
from httpsig import getDigestAlgorithmFromHeaders
from httpsig import verifyPostHeaders from httpsig import verifyPostHeaders
from session import createSession from session import createSession
from follow import followerApprovalActive from follow import followerApprovalActive
@ -549,7 +550,8 @@ def savePostToInboxQueue(baseDir: str, httpPrefix: str,
sharedInboxItem = True sharedInboxItem = True
digestStartTime = time.time() digestStartTime = time.time()
digest = messageContentDigest(messageBytes) digestAlgorithm = getDigestAlgorithmFromHeaders(httpHeaders)
digest = messageContentDigest(messageBytes, digestAlgorithm)
timeDiffStr = str(int((time.time() - digestStartTime) * 1000)) timeDiffStr = str(int((time.time() - digestStartTime) * 1000))
if debug: if debug:
while len(timeDiffStr) < 6: while len(timeDiffStr) < 6:

View File

@ -23,6 +23,8 @@ from shutil import copyfile
from random import randint from random import randint
from time import gmtime, strftime from time import gmtime, strftime
from pprint import pprint from pprint import pprint
from httpsig import getDigestAlgorithmFromHeaders
from httpsig import getDigestPrefix
from httpsig import createSignedHeader from httpsig import createSignedHeader
from httpsig import signPostHeaders from httpsig import signPostHeaders
from httpsig import signPostHeadersNew from httpsig import signPostHeadersNew
@ -401,8 +403,11 @@ def _testHttpSigNew():
pathStr = "/" + nickname + "?param=value&pet=dog HTTP/1.1" pathStr = "/" + nickname + "?param=value&pet=dog HTTP/1.1"
domain = 'example.com' domain = 'example.com'
dateStr = 'Tue, 20 Apr 2021 02:07:55 GMT' dateStr = 'Tue, 20 Apr 2021 02:07:55 GMT'
digestStr = 'SHA-256=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=' algorithm = 'rsa-sha256'
bodyDigest = messageContentDigest(messageBodyJsonStr) digestAlgorithm = 'rsa-sha256'
digestPrefix = getDigestPrefix(digestAlgorithm)
digestStr = digestPrefix + '=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE='
bodyDigest = messageContentDigest(messageBodyJsonStr, digestAlgorithm)
assert bodyDigest in digestStr assert bodyDigest in digestStr
contentLength = 18 contentLength = 18
contentType = 'application/activity+json' contentType = 'application/activity+json'
@ -477,7 +482,7 @@ def _testHttpSigNew():
headers = { headers = {
"host": domain, "host": domain,
"date": dateStr, "date": dateStr,
"digest": f'SHA-256={bodyDigest}', "digest": f'{digestPrefix}={bodyDigest}',
"content-type": contentType, "content-type": contentType,
"content-length": str(contentLength) "content-length": str(contentLength)
} }
@ -486,7 +491,7 @@ def _testHttpSigNew():
domain, port, domain, port,
domain, port, domain, port,
pathStr, httpPrefix, messageBodyJsonStr, pathStr, httpPrefix, messageBodyJsonStr,
'rsa-sha256', debug) algorithm, debug)
print('signatureIndexHeader1: ' + str(signatureIndexHeader)) print('signatureIndexHeader1: ' + str(signatureIndexHeader))
print('signatureHeader1: ' + str(signatureHeader)) print('signatureHeader1: ' + str(signatureHeader))
sigInput = "keyId=\"https://example.com/users/foo#main-key\"; " + \ sigInput = "keyId=\"https://example.com/users/foo#main-key\"; " + \
@ -528,6 +533,8 @@ def _testHttpsigBase(withDigest: bool, baseDir: str):
os.mkdir(path) os.mkdir(path)
os.chdir(path) os.chdir(path)
algorithm = 'rsa-sha256'
digestAlgorithm = 'rsa-sha256'
contentType = 'application/activity+json' contentType = 'application/activity+json'
nickname = 'socrates' nickname = 'socrates'
hostDomain = 'someother.instance' hostDomain = 'someother.instance'
@ -563,23 +570,26 @@ def _testHttpsigBase(withDigest: bool, baseDir: str):
signPostHeaders(dateStr, privateKeyPem, nickname, signPostHeaders(dateStr, privateKeyPem, nickname,
domain, port, domain, port,
hostDomain, port, hostDomain, port,
boxpath, httpPrefix, None, contentType) boxpath, httpPrefix, None, contentType,
algorithm)
else: else:
bodyDigest = messageContentDigest(messageBodyJsonStr) digestPrefix = getDigestPrefix(digestAlgorithm)
bodyDigest = messageContentDigest(messageBodyJsonStr, digestAlgorithm)
contentLength = len(messageBodyJsonStr) contentLength = len(messageBodyJsonStr)
headers = { headers = {
'host': headersDomain, 'host': headersDomain,
'date': dateStr, 'date': dateStr,
'digest': f'SHA-256={bodyDigest}', 'digest': f'{digestPrefix}={bodyDigest}',
'content-type': contentType, 'content-type': contentType,
'content-length': str(contentLength) 'content-length': str(contentLength)
} }
assert getDigestAlgorithmFromHeaders(headers) == digestAlgorithm
signatureHeader = \ signatureHeader = \
signPostHeaders(dateStr, privateKeyPem, nickname, signPostHeaders(dateStr, privateKeyPem, nickname,
domain, port, domain, port,
hostDomain, port, hostDomain, port,
boxpath, httpPrefix, messageBodyJsonStr, boxpath, httpPrefix, messageBodyJsonStr,
contentType) contentType, algorithm)
headers['signature'] = signatureHeader headers['signature'] = signatureHeader
GETmethod = not withDigest GETmethod = not withDigest
@ -612,14 +622,16 @@ def _testHttpsigBase(withDigest: bool, baseDir: str):
'{"a key": "a value", "another key": "Fake GNUs", ' + \ '{"a key": "a value", "another key": "Fake GNUs", ' + \
'"yet another key": "More Fake GNUs"}' '"yet another key": "More Fake GNUs"}'
contentLength = len(messageBodyJsonStr) contentLength = len(messageBodyJsonStr)
bodyDigest = messageContentDigest(messageBodyJsonStr) digestPrefix = getDigestPrefix(digestAlgorithm)
bodyDigest = messageContentDigest(messageBodyJsonStr, digestAlgorithm)
headers = { headers = {
'host': domain, 'host': domain,
'date': dateStr, 'date': dateStr,
'digest': f'SHA-256={bodyDigest}', 'digest': f'{digestPrefix}={bodyDigest}',
'content-type': contentType, 'content-type': contentType,
'content-length': str(contentLength) 'content-length': str(contentLength)
} }
assert getDigestAlgorithmFromHeaders(headers) == digestAlgorithm
headers['signature'] = signatureHeader headers['signature'] = signatureHeader
assert verifyPostHeaders(httpPrefix, publicKeyPem, headers, assert verifyPostHeaders(httpPrefix, publicKeyPem, headers,
boxpath, not GETmethod, None, boxpath, not GETmethod, None,
@ -5891,6 +5903,7 @@ def _testHttpsigBaseNew(withDigest: bool, baseDir: str,
os.mkdir(path) os.mkdir(path)
os.chdir(path) os.chdir(path)
digestAlgorithm = algorithm
contentType = 'application/activity+json' contentType = 'application/activity+json'
nickname = 'socrates' nickname = 'socrates'
hostDomain = 'someother.instance' hostDomain = 'someother.instance'
@ -5929,15 +5942,17 @@ def _testHttpsigBaseNew(withDigest: bool, baseDir: str,
boxpath, httpPrefix, messageBodyJsonStr, boxpath, httpPrefix, messageBodyJsonStr,
algorithm, debug) algorithm, debug)
else: else:
bodyDigest = messageContentDigest(messageBodyJsonStr) digestPrefix = getDigestPrefix(digestAlgorithm)
bodyDigest = messageContentDigest(messageBodyJsonStr, digestAlgorithm)
contentLength = len(messageBodyJsonStr) contentLength = len(messageBodyJsonStr)
headers = { headers = {
'host': headersDomain, 'host': headersDomain,
'date': dateStr, 'date': dateStr,
'digest': f'SHA-256={bodyDigest}', 'digest': f'{digestPrefix}={bodyDigest}',
'content-type': contentType, 'content-type': contentType,
'content-length': str(contentLength) 'content-length': str(contentLength)
} }
assert getDigestAlgorithmFromHeaders(headers) == digestAlgorithm
signatureIndexHeader, signatureHeader = \ signatureIndexHeader, signatureHeader = \
signPostHeadersNew(dateStr, privateKeyPem, nickname, signPostHeadersNew(dateStr, privateKeyPem, nickname,
domain, port, domain, port,
@ -5980,14 +5995,16 @@ def _testHttpsigBaseNew(withDigest: bool, baseDir: str,
'{"a key": "a value", "another key": "Fake GNUs", ' + \ '{"a key": "a value", "another key": "Fake GNUs", ' + \
'"yet another key": "More Fake GNUs"}' '"yet another key": "More Fake GNUs"}'
contentLength = len(messageBodyJsonStr) contentLength = len(messageBodyJsonStr)
bodyDigest = messageContentDigest(messageBodyJsonStr) digestPrefix = getDigestPrefix(digestAlgorithm)
bodyDigest = messageContentDigest(messageBodyJsonStr, digestAlgorithm)
headers = { headers = {
'host': domain, 'host': domain,
'date': dateStr, 'date': dateStr,
'digest': f'SHA-256={bodyDigest}', 'digest': f'{digestPrefix}={bodyDigest}',
'content-type': contentType, 'content-type': contentType,
'content-length': str(contentLength) 'content-length': str(contentLength)
} }
assert getDigestAlgorithmFromHeaders(headers) == digestAlgorithm
headers['signature'] = signatureHeader headers['signature'] = signatureHeader
headers['signature-input'] = signatureIndexHeader headers['signature-input'] = signatureIndexHeader
pprint(headers) pprint(headers)