mirror of https://gitlab.com/bashrc2/epicyon
Merge branch 'main' of gitlab.com:bashrc2/epicyon
commit
ff3fba4c79
12
daemon.py
12
daemon.py
|
@ -242,7 +242,6 @@ from like import updateLikesCollection
|
|||
from reaction import updateReactionCollection
|
||||
from utils import undoReactionCollectionEntry
|
||||
from utils import getNewPostEndpoints
|
||||
from utils import malformedCiphertext
|
||||
from utils import hasActor
|
||||
from utils import setReplyIntervalHours
|
||||
from utils import canReplyTo
|
||||
|
@ -451,6 +450,8 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
# https://tools.ietf.org/html/
|
||||
# draft-ietf-httpbis-message-signatures-01
|
||||
return self.headers['Signature-Input']
|
||||
elif self.headers.get('signature-input'):
|
||||
return self.headers['signature-input']
|
||||
elif self.headers.get('signature'):
|
||||
# Ye olde Masto http sig
|
||||
return self.headers['signature']
|
||||
|
@ -728,7 +729,9 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
return False
|
||||
|
||||
# verify the GET request without any digest
|
||||
if verifyPostHeaders(self.server.httpPrefix, pubKey, self.headers,
|
||||
if verifyPostHeaders(self.server.httpPrefix,
|
||||
self.server.domainFull,
|
||||
pubKey, self.headers,
|
||||
self.path, True, None, '', self.server.debug):
|
||||
return True
|
||||
|
||||
|
@ -1503,11 +1506,6 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
# save the json for later queue processing
|
||||
messageBytesDecoded = messageBytes.decode('utf-8')
|
||||
|
||||
if malformedCiphertext(messageBytesDecoded):
|
||||
print('WARN: post contains malformed ciphertext ' +
|
||||
str(originalMessageJson))
|
||||
return 4
|
||||
|
||||
if containsInvalidLocalLinks(messageBytesDecoded):
|
||||
print('WARN: post contains invalid local links ' +
|
||||
str(originalMessageJson))
|
||||
|
|
112
httpsig.py
112
httpsig.py
|
@ -11,7 +11,7 @@ __module_group__ = "Security"
|
|||
# see https://tools.ietf.org/html/draft-cavage-http-signatures-06
|
||||
#
|
||||
# This might change in future
|
||||
# see https://tools.ietf.org/html/draft-ietf-httpbis-message-signatures-01
|
||||
# see https://tools.ietf.org/html/draft-ietf-httpbis-message-signatures
|
||||
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives.serialization import load_pem_private_key
|
||||
|
@ -116,11 +116,11 @@ def signPostHeadersNew(dateStr: str, privateKeyPem: str,
|
|||
path: str,
|
||||
httpPrefix: str,
|
||||
messageBodyJsonStr: str,
|
||||
algorithm: str) -> (str, str):
|
||||
algorithm: str, debug: bool) -> (str, str):
|
||||
"""Returns a raw signature strings that can be plugged into a header
|
||||
as "Signature-Input" and "Signature"
|
||||
used to verify the authenticity of an HTTP transmission.
|
||||
See https://tools.ietf.org/html/draft-ietf-httpbis-message-signatures-01
|
||||
See https://tools.ietf.org/html/draft-ietf-httpbis-message-signatures
|
||||
"""
|
||||
domain = getFullDomain(domain, port)
|
||||
|
||||
|
@ -137,18 +137,17 @@ def signPostHeadersNew(dateStr: str, privateKeyPem: str,
|
|||
keyID = localActorUrl(httpPrefix, nickname, domain) + '#main-key'
|
||||
if not messageBodyJsonStr:
|
||||
headers = {
|
||||
'*request-target': f'post {path}',
|
||||
'*created': str(secondsSinceEpoch),
|
||||
'@request-target': f'get {path}',
|
||||
'@created': str(secondsSinceEpoch),
|
||||
'host': toDomain,
|
||||
'date': dateStr,
|
||||
'content-type': 'application/json'
|
||||
'date': dateStr
|
||||
}
|
||||
else:
|
||||
bodyDigest = messageContentDigest(messageBodyJsonStr)
|
||||
contentLength = len(messageBodyJsonStr)
|
||||
headers = {
|
||||
'*request-target': f'post {path}',
|
||||
'*created': str(secondsSinceEpoch),
|
||||
'@request-target': f'post {path}',
|
||||
'@created': str(secondsSinceEpoch),
|
||||
'host': toDomain,
|
||||
'date': dateStr,
|
||||
'digest': f'SHA-256={bodyDigest}',
|
||||
|
@ -164,6 +163,10 @@ def signPostHeadersNew(dateStr: str, privateKeyPem: str,
|
|||
signedHeaderText += f'{headerKey}: {headers[headerKey]}\n'
|
||||
signedHeaderText = signedHeaderText.strip()
|
||||
|
||||
if debug:
|
||||
print('\nsignPostHeadersNew signedHeaderText:\n' +
|
||||
signedHeaderText + '\nEND\n')
|
||||
|
||||
# Sign the digest. Potentially other signing algorithms can be added here.
|
||||
signature = ''
|
||||
if algorithm == 'rsa-sha512':
|
||||
|
@ -269,7 +272,8 @@ def _verifyRecentSignature(signedDateStr: str) -> bool:
|
|||
return True
|
||||
|
||||
|
||||
def verifyPostHeaders(httpPrefix: str, publicKeyPem: str, headers: dict,
|
||||
def verifyPostHeaders(httpPrefix: str,
|
||||
publicKeyPem: str, headers: dict,
|
||||
path: str, GETmethod: bool,
|
||||
messageBodyDigest: str,
|
||||
messageBodyJsonStr: str, debug: bool,
|
||||
|
@ -298,8 +302,11 @@ def verifyPostHeaders(httpPrefix: str, publicKeyPem: str, headers: dict,
|
|||
pubkey = load_pem_public_key(publicKeyPem.encode('utf-8'),
|
||||
backend=default_backend())
|
||||
# Build a dictionary of the signature values
|
||||
if headers.get('Signature-Input') or headers.get('signature-input'):
|
||||
if headers.get('Signature-Input'):
|
||||
signatureHeader = headers['Signature-Input']
|
||||
else:
|
||||
signatureHeader = headers['signature-input']
|
||||
fieldSep2 = ','
|
||||
# split the signature input into separate fields
|
||||
signatureDict = {
|
||||
|
@ -312,7 +319,8 @@ def verifyPostHeaders(httpPrefix: str, publicKeyPem: str, headers: dict,
|
|||
if v.startswith('('):
|
||||
requestTargetKey = k
|
||||
requestTargetStr = v[1:-1]
|
||||
break
|
||||
elif v.startswith('"'):
|
||||
signatureDict[k] = v[1:-1]
|
||||
if not requestTargetKey:
|
||||
return False
|
||||
signatureDict[requestTargetKey] = requestTargetStr
|
||||
|
@ -341,19 +349,40 @@ def verifyPostHeaders(httpPrefix: str, publicKeyPem: str, headers: dict,
|
|||
# original Mastodon http signature
|
||||
appendStr = f'(request-target): {method.lower()} {path}'
|
||||
signedHeaderList.append(appendStr)
|
||||
elif '*request-target' in signedHeader:
|
||||
elif '@request-target' in signedHeader:
|
||||
# https://tools.ietf.org/html/
|
||||
# draft-ietf-httpbis-message-signatures-01
|
||||
appendStr = f'*request-target: {method.lower()} {path}'
|
||||
# remove ()
|
||||
# if appendStr.startswith('('):
|
||||
# appendStr = appendStr.split('(')[1]
|
||||
# if ')' in appendStr:
|
||||
# appendStr = appendStr.split(')')[0]
|
||||
# draft-ietf-httpbis-message-signatures
|
||||
appendStr = f'@request-target: {method.lower()} {path}'
|
||||
signedHeaderList.append(appendStr)
|
||||
elif '@created' in signedHeader:
|
||||
if signatureDict.get('created'):
|
||||
createdStr = str(signatureDict['created'])
|
||||
appendStr = f'@created: {createdStr}'
|
||||
signedHeaderList.append(appendStr)
|
||||
elif '@expires' in signedHeader:
|
||||
if signatureDict.get('expires'):
|
||||
expiresStr = str(signatureDict['expires'])
|
||||
appendStr = f'@expires: {expiresStr}'
|
||||
signedHeaderList.append(appendStr)
|
||||
elif '@method' in signedHeader:
|
||||
appendStr = f'@expires: {method}'
|
||||
signedHeaderList.append(appendStr)
|
||||
elif '@scheme' in signedHeader:
|
||||
signedHeaderList.append('@scheme: http')
|
||||
elif '@authority' in signedHeader:
|
||||
authorityStr = None
|
||||
if signatureDict.get('authority'):
|
||||
authorityStr = str(signatureDict['authority'])
|
||||
elif signatureDict.get('Authority'):
|
||||
authorityStr = str(signatureDict['Authority'])
|
||||
if authorityStr:
|
||||
appendStr = f'@authority: {authorityStr}'
|
||||
signedHeaderList.append(appendStr)
|
||||
elif signedHeader == 'algorithm':
|
||||
if headers.get(signedHeader):
|
||||
algorithm = headers[signedHeader]
|
||||
if debug:
|
||||
print('http signature algorithm: ' + algorithm)
|
||||
elif signedHeader == 'digest':
|
||||
if messageBodyDigest:
|
||||
bodyDigest = messageBodyDigest
|
||||
|
@ -427,14 +456,18 @@ def verifyPostHeaders(httpPrefix: str, publicKeyPem: str, headers: dict,
|
|||
# Now we have our header data digest
|
||||
signedHeaderText = '\n'.join(signedHeaderList)
|
||||
if debug:
|
||||
print('signedHeaderText:\n' + signedHeaderText + 'END')
|
||||
print('\nverifyPostHeaders signedHeaderText:\n' +
|
||||
signedHeaderText + '\nEND\n')
|
||||
|
||||
# Get the signature, verify with public key, return result
|
||||
signature = None
|
||||
if headers.get('Signature-Input') and headers.get('Signature'):
|
||||
if (headers.get('Signature-Input') and headers.get('Signature')) or \
|
||||
(headers.get('signature-input') and headers.get('signature')):
|
||||
# https://tools.ietf.org/html/
|
||||
# draft-ietf-httpbis-message-signatures-01
|
||||
# draft-ietf-httpbis-message-signatures
|
||||
if headers.get('Signature'):
|
||||
headersSig = headers['Signature']
|
||||
else:
|
||||
headersSig = headers['signature']
|
||||
# remove sig1=:
|
||||
if requestTargetKey + '=:' in headersSig:
|
||||
headersSig = headersSig.split(requestTargetKey + '=:')[1]
|
||||
|
@ -442,25 +475,36 @@ def verifyPostHeaders(httpPrefix: str, publicKeyPem: str, headers: dict,
|
|||
signature = base64.b64decode(headersSig)
|
||||
else:
|
||||
# Original Mastodon signature
|
||||
signature = base64.b64decode(signatureDict['signature'])
|
||||
headersSig = signatureDict['signature']
|
||||
signature = base64.b64decode(headersSig)
|
||||
if debug:
|
||||
print('signature: ' + algorithm + ' ' +
|
||||
signatureDict['signature'])
|
||||
print('signature: ' + algorithm + ' ' + headersSig)
|
||||
|
||||
# log unusual signing algorithms
|
||||
if signatureDict.get('alg'):
|
||||
print('http signature algorithm: ' + signatureDict['alg'])
|
||||
|
||||
# If extra signing algorithms need to be added then do it here
|
||||
if algorithm == 'rsa-sha256':
|
||||
headerDigest = getSHA256(signedHeaderText.encode('ascii'))
|
||||
paddingStr = padding.PKCS1v15()
|
||||
if not signatureDict.get('alg'):
|
||||
alg = hazutils.Prehashed(hashes.SHA256())
|
||||
elif algorithm == 'rsa-sha512':
|
||||
headerDigest = getSHA512(signedHeaderText.encode('ascii'))
|
||||
paddingStr = padding.PKCS1v15()
|
||||
elif (signatureDict['alg'] == 'rsa-sha256' or
|
||||
signatureDict['alg'] == 'rsa-v1_5-sha256' or
|
||||
signatureDict['alg'] == 'hs2019'):
|
||||
alg = hazutils.Prehashed(hashes.SHA256())
|
||||
elif (signatureDict['alg'] == 'rsa-sha512' or
|
||||
signatureDict['alg'] == 'rsa-pss-sha512'):
|
||||
alg = hazutils.Prehashed(hashes.SHA512())
|
||||
else:
|
||||
print('Unknown http signature algorithm: ' + algorithm)
|
||||
paddingStr = padding.PKCS1v15()
|
||||
alg = hazutils.Prehashed(hashes.SHA256())
|
||||
|
||||
if algorithm == 'rsa-sha256' or algorithm == 'hs2019':
|
||||
headerDigest = getSHA256(signedHeaderText.encode('ascii'))
|
||||
elif algorithm == 'rsa-sha512':
|
||||
headerDigest = getSHA512(signedHeaderText.encode('ascii'))
|
||||
else:
|
||||
print('Unknown http signature algorithm: ' + algorithm)
|
||||
headerDigest = ''
|
||||
paddingStr = padding.PKCS1v15()
|
||||
|
||||
try:
|
||||
pubkey.verify(signature, headerDigest, paddingStr, alg)
|
||||
|
|
4
inbox.py
4
inbox.py
|
@ -17,6 +17,7 @@ from languages import understoodPostLanguage
|
|||
from like import updateLikesCollection
|
||||
from reaction import updateReactionCollection
|
||||
from reaction import validEmojiContent
|
||||
from utils import invalidCiphertext
|
||||
from utils import removeHtml
|
||||
from utils import fileLastModified
|
||||
from utils import hasObjectString
|
||||
|
@ -2258,6 +2259,9 @@ def _validPostContent(baseDir: str, nickname: str, domain: str,
|
|||
print('REJECT: reply to post which does not ' +
|
||||
'allow comments: ' + originalPostId)
|
||||
return False
|
||||
if invalidCiphertext(messageJson['object']['content']):
|
||||
print('REJECT: malformed ciphertext in content')
|
||||
return False
|
||||
if debug:
|
||||
print('ACCEPT: post content is valid')
|
||||
return True
|
||||
|
|
9
posts.py
9
posts.py
|
@ -32,6 +32,7 @@ from webfinger import webfingerHandle
|
|||
from httpsig import createSignedHeader
|
||||
from siteactive import siteIsActive
|
||||
from languages import understoodPostLanguage
|
||||
from utils import invalidCiphertext
|
||||
from utils import hasObjectStringType
|
||||
from utils import removeIdEnding
|
||||
from utils import replaceUsersWithAt
|
||||
|
@ -4602,6 +4603,14 @@ def downloadAnnounce(session, baseDir: str, httpPrefix: str,
|
|||
recentPostsCache)
|
||||
return None
|
||||
|
||||
if invalidCiphertext(contentStr):
|
||||
_rejectAnnounce(announceFilename,
|
||||
baseDir, nickname, domain, postId,
|
||||
recentPostsCache)
|
||||
print('WARN: Invalid ciphertext within announce ' +
|
||||
str(announcedJson))
|
||||
return None
|
||||
|
||||
# remove any long words
|
||||
contentStr = removeLongWords(contentStr, 40, [])
|
||||
|
||||
|
|
253
tests.py
253
tests.py
|
@ -392,8 +392,20 @@ def _testSignAndVerify() -> None:
|
|||
|
||||
def _testHttpSigNew():
|
||||
print('testHttpSigNew')
|
||||
httpPrefix = 'https'
|
||||
port = 443
|
||||
debug = True
|
||||
messageBodyJson = {"hello": "world"}
|
||||
messageBodyJsonStr = json.dumps(messageBodyJson)
|
||||
nickname = 'foo'
|
||||
pathStr = "/" + nickname + "?param=value&pet=dog HTTP/1.1"
|
||||
domain = 'example.com'
|
||||
dateStr = 'Tue, 20 Apr 2021 02:07:55 GMT'
|
||||
digestStr = 'SHA-256=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE='
|
||||
bodyDigest = messageContentDigest(messageBodyJsonStr)
|
||||
assert bodyDigest in digestStr
|
||||
contentLength = 18
|
||||
contentType = 'application/activity+json'
|
||||
publicKeyPem = \
|
||||
'-----BEGIN RSA PUBLIC KEY-----\n' + \
|
||||
'MIIBCgKCAQEAhAKYdtoeoy8zcAcR874L8' + \
|
||||
|
@ -462,101 +474,49 @@ def _testHttpSigNew():
|
|||
'EQeNC8fHGg4UXU8mhHnSBt3EA10qQJfRD' + \
|
||||
's15M38eG2cYwB1PZpDHScDnDA0=\n' + \
|
||||
'-----END RSA PRIVATE KEY-----'
|
||||
sigInput = \
|
||||
'sig1=(date); alg=rsa-sha256; keyId="test-key-b"'
|
||||
sig = \
|
||||
'sig1=:HtXycCl97RBVkZi66ADKnC9c5eSSlb57GnQ4KFqNZplOpNfxqk62' + \
|
||||
'JzZ484jXgLvoOTRaKfR4hwyxlcyb+BWkVasApQovBSdit9Ml/YmN2IvJDPncrlhPD' + \
|
||||
'VDv36Z9/DiSO+RNHD7iLXugdXo1+MGRimW1RmYdenl/ITeb7rjfLZ4b9VNnLFtVWw' + \
|
||||
'rjhAiwIqeLjodVImzVc5srrk19HMZNuUejK6I3/MyN3+3U8tIRW4LWzx6ZgGZUaEE' + \
|
||||
'P0aBlBkt7Fj0Tt5/P5HNW/Sa/m8smxbOHnwzAJDa10PyjzdIbywlnWIIWtZKPPsoV' + \
|
||||
'oKVopUWEU3TNhpWmaVhFrUL/O6SN3w==:'
|
||||
# "hs2019", using RSASSA-PSS [RFC8017] and SHA-512 [RFC6234]
|
||||
# sigInput = \
|
||||
# 'sig1=(*request-target, *created, host, date, ' + \
|
||||
# 'cache-control, x-empty-header, x-example); keyId="test-key-a"; ' + \
|
||||
# 'alg=hs2019; created=1402170695; expires=1402170995'
|
||||
# sig = \
|
||||
# 'sig1=:K2qGT5srn2OGbOIDzQ6kYT+ruaycnDAAUpKv+ePFfD0RAxn/1BUe' + \
|
||||
# 'Zx/Kdrq32DrfakQ6bPsvB9aqZqognNT6be4olHROIkeV879RrsrObury8L9SCEibe' + \
|
||||
# 'oHyqU/yCjphSmEdd7WD+zrchK57quskKwRefy2iEC5S2uAH0EPyOZKWlvbKmKu5q4' + \
|
||||
# 'CaB8X/I5/+HLZLGvDiezqi6/7p2Gngf5hwZ0lSdy39vyNMaaAT0tKo6nuVw0S1MVg' + \
|
||||
# '1Q7MpWYZs0soHjttq0uLIA3DIbQfLiIvK6/l0BdWTU7+2uQj7lBkQAsFZHoA96ZZg' + \
|
||||
# 'FquQrXRlmYOh+Hx5D9fJkXcXe5tmAg==:'
|
||||
nickname = 'foo'
|
||||
boxpath = '/' + nickname
|
||||
# headers = {
|
||||
# "*request-target": "get " + boxpath,
|
||||
# "*created": "1402170695",
|
||||
# "host": "example.org",
|
||||
# "date": "Tue, 07 Jun 2014 20:51:35 GMT",
|
||||
# "cache-control": "max-age=60, must-revalidate",
|
||||
# "x-emptyheader": "",
|
||||
# "x-example": "Example header with some whitespace.",
|
||||
# "x-dictionary": "b=2",
|
||||
# "x-dictionary": "a=1",
|
||||
# "x-list": "(a, b, c)",
|
||||
# "Signature-Input": sigInput,
|
||||
# "Signature": sig
|
||||
# }
|
||||
dateStr = "Tue, 07 Jun 2014 20:51:35 GMT"
|
||||
secondsSinceEpoch = 1402174295
|
||||
domain = "example.com"
|
||||
port = 443
|
||||
headers = {
|
||||
"*created": str(secondsSinceEpoch),
|
||||
"*request-target": "post /foo?param=value&pet=dog",
|
||||
"host": domain,
|
||||
"date": dateStr,
|
||||
"content-type": "application/json",
|
||||
"digest": "SHA-256=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=",
|
||||
"content-length": "18",
|
||||
"Signature-Input": sigInput,
|
||||
"Signature": sig
|
||||
}
|
||||
httpPrefix = 'https'
|
||||
debug = False
|
||||
assert verifyPostHeaders(httpPrefix, publicKeyPem, headers,
|
||||
boxpath, False, None,
|
||||
messageBodyJsonStr, debug, True)
|
||||
# make a deliberate mistake
|
||||
headers['Signature'] = headers['Signature'].replace('V', 'B')
|
||||
assert not verifyPostHeaders(httpPrefix, publicKeyPem, headers,
|
||||
boxpath, False, None,
|
||||
messageBodyJsonStr, debug, True)
|
||||
# test signing
|
||||
bodyDigest = messageContentDigest(messageBodyJsonStr)
|
||||
contentLength = len(messageBodyJsonStr)
|
||||
headers = {
|
||||
"host": domain,
|
||||
"date": dateStr,
|
||||
"digest": f'SHA-256={bodyDigest}',
|
||||
"content-type": "application/json",
|
||||
"content-type": contentType,
|
||||
"content-length": str(contentLength)
|
||||
}
|
||||
signatureIndexHeader, signatureHeader = \
|
||||
signPostHeadersNew(dateStr, privateKeyPem, nickname,
|
||||
domain, port,
|
||||
domain, port,
|
||||
boxpath, httpPrefix, messageBodyJsonStr,
|
||||
'rsa-sha256')
|
||||
expectedIndexHeader = \
|
||||
'keyId="https://example.com/users/foo#main-key"; ' + \
|
||||
'alg=hs2019; created=' + str(secondsSinceEpoch) + '; ' + \
|
||||
'sig1=(*request-target, *created, host, date, ' + \
|
||||
'digest, content-type, content-length)'
|
||||
if signatureIndexHeader != expectedIndexHeader:
|
||||
print('Unexpected new http header: ' + signatureIndexHeader)
|
||||
print('Should be: ' + expectedIndexHeader)
|
||||
assert signatureIndexHeader == expectedIndexHeader
|
||||
assert signatureHeader == \
|
||||
'sig1=:euX3O1KSTYXN9/oR2qFezswWm9FbrjtRymK7xBpXNQvTs' + \
|
||||
'XehtrNdD8nELZKzPXMvMz7PaJd6V+fjzpHoZ9upTdqqQLK2Iwml' + \
|
||||
'p4BlHqW6Aopd7sZFCWFq7/Amm5oaizpp3e0jb5XISS5m3cRKuoi' + \
|
||||
'LM0x+OudmAoYGi0TEEJk8bpnJAXfVCDfmOyL3XNqQeShQHeOANG' + \
|
||||
'okiKktj8ff+KLYLaPTAJkob1k/EhoPIkbw/YzAY8IZjWQNMkf+F' + \
|
||||
'JChApQ5HnDCQPwD5xV9eGzBpAf6D0G19xiTmQye4Hn6tAs3fy3V' + \
|
||||
'/aYa/GhW2pSrctDnAKIi4imj9joppr3CB8gqgXZOPQ==:'
|
||||
pathStr, httpPrefix, messageBodyJsonStr,
|
||||
'rsa-sha256', debug)
|
||||
print('signatureIndexHeader1: ' + str(signatureIndexHeader))
|
||||
print('signatureHeader1: ' + str(signatureHeader))
|
||||
sigInput = "keyId=\"https://example.com/users/foo#main-key\"; " + \
|
||||
"alg=hs2019; created=1618884475; " + \
|
||||
"sig1=(@request-target, @created, host, date, digest, " + \
|
||||
"content-type, content-length)"
|
||||
assert signatureIndexHeader == sigInput
|
||||
sig = "sig1=:NXAQ7AtDMR2iwhmH1qCwiZw5PVTjOw5+5kSu0Tsx/3gqz0D" + \
|
||||
"py7OQbWqFHrNB7MmS4TukX/vDyQOFdElY5yxnEhbgRwKACq0AP4QH9H" + \
|
||||
"CiRyCE8UXDdAkY4VUd6jrWjRHKRoqQN7I+Q5tb2Fu5cDfifw/PQc86Z" + \
|
||||
"NmMhPrg3OjUJ9Q2Gj29NhgJ+4el1ECg0cAy4yG1M9AQ3KvQooQFvlg1" + \
|
||||
"vp0H2xfbJQjv8FsR/lKiRdaVHqGR2CKrvxvPRPaOsFANp2wzEtiMk3O" + \
|
||||
"TrBTYU+Zb53mIspfEeLxsNtcGmBDmQKZ9Pud8f99XGJrP+uDd3zKtnr" + \
|
||||
"f3fUnRRqy37yhB7WVwkg==:"
|
||||
assert signatureHeader == sig
|
||||
|
||||
debug = True
|
||||
headers['path'] = pathStr
|
||||
headers['signature'] = sig
|
||||
headers['signature-input'] = sigInput
|
||||
assert verifyPostHeaders(httpPrefix, publicKeyPem, headers,
|
||||
pathStr, False, None,
|
||||
messageBodyJsonStr, debug, True)
|
||||
|
||||
# make a deliberate mistake
|
||||
debug = False
|
||||
headers['signature'] = headers['signature'].replace('V', 'B')
|
||||
assert not verifyPostHeaders(httpPrefix, publicKeyPem, headers,
|
||||
pathStr, False, None,
|
||||
messageBodyJsonStr, debug, True)
|
||||
|
||||
|
||||
def _testHttpsigBase(withDigest: bool, baseDir: str):
|
||||
|
@ -623,9 +583,10 @@ def _testHttpsigBase(withDigest: bool, baseDir: str):
|
|||
|
||||
headers['signature'] = signatureHeader
|
||||
GETmethod = not withDigest
|
||||
debug = True
|
||||
assert verifyPostHeaders(httpPrefix, publicKeyPem, headers,
|
||||
boxpath, GETmethod, None,
|
||||
messageBodyJsonStr, False)
|
||||
messageBodyJsonStr, debug)
|
||||
if withDigest:
|
||||
# everything correct except for content-length
|
||||
headers['content-length'] = str(contentLength + 2)
|
||||
|
@ -5919,6 +5880,124 @@ def _testValidEmojiContent() -> None:
|
|||
assert validEmojiContent('😄')
|
||||
|
||||
|
||||
def _testHttpsigBaseNew(withDigest: bool, baseDir: str):
|
||||
print('testHttpsigNew(' + str(withDigest) + ')')
|
||||
|
||||
debug = True
|
||||
path = baseDir + '/.testHttpsigBaseNew'
|
||||
if os.path.isdir(path):
|
||||
shutil.rmtree(path, ignore_errors=False, onerror=None)
|
||||
os.mkdir(path)
|
||||
os.chdir(path)
|
||||
|
||||
contentType = 'application/activity+json'
|
||||
nickname = 'socrates'
|
||||
hostDomain = 'someother.instance'
|
||||
domain = 'argumentative.social'
|
||||
httpPrefix = 'https'
|
||||
port = 5576
|
||||
password = 'SuperSecretPassword'
|
||||
privateKeyPem, publicKeyPem, person, wfEndpoint = \
|
||||
createPerson(path, nickname, domain, port, httpPrefix,
|
||||
False, False, password)
|
||||
assert privateKeyPem
|
||||
if withDigest:
|
||||
messageBodyJson = {
|
||||
"a key": "a value",
|
||||
"another key": "A string",
|
||||
"yet another key": "Another string"
|
||||
}
|
||||
messageBodyJsonStr = json.dumps(messageBodyJson)
|
||||
else:
|
||||
messageBodyJsonStr = ''
|
||||
|
||||
headersDomain = getFullDomain(hostDomain, port)
|
||||
|
||||
dateStr = strftime("%a, %d %b %Y %H:%M:%S %Z", gmtime())
|
||||
boxpath = '/inbox'
|
||||
if not withDigest:
|
||||
headers = {
|
||||
'host': headersDomain,
|
||||
'date': dateStr,
|
||||
'accept': contentType
|
||||
}
|
||||
signatureIndexHeader, signatureHeader = \
|
||||
signPostHeadersNew(dateStr, privateKeyPem, nickname,
|
||||
domain, port,
|
||||
hostDomain, port,
|
||||
boxpath, httpPrefix, messageBodyJsonStr,
|
||||
'rsa-sha256', debug)
|
||||
else:
|
||||
bodyDigest = messageContentDigest(messageBodyJsonStr)
|
||||
contentLength = len(messageBodyJsonStr)
|
||||
headers = {
|
||||
'host': headersDomain,
|
||||
'date': dateStr,
|
||||
'digest': f'SHA-256={bodyDigest}',
|
||||
'content-type': contentType,
|
||||
'content-length': str(contentLength)
|
||||
}
|
||||
signatureIndexHeader, signatureHeader = \
|
||||
signPostHeadersNew(dateStr, privateKeyPem, nickname,
|
||||
domain, port,
|
||||
hostDomain, port,
|
||||
boxpath, httpPrefix, messageBodyJsonStr,
|
||||
'rsa-sha256', debug)
|
||||
|
||||
headers['signature'] = signatureHeader
|
||||
headers['signature-input'] = signatureIndexHeader
|
||||
print('headers: ' + str(headers))
|
||||
|
||||
GETmethod = not withDigest
|
||||
debug = True
|
||||
assert verifyPostHeaders(httpPrefix, publicKeyPem, headers,
|
||||
boxpath, GETmethod, None,
|
||||
messageBodyJsonStr, debug)
|
||||
debug = False
|
||||
if withDigest:
|
||||
# everything correct except for content-length
|
||||
headers['content-length'] = str(contentLength + 2)
|
||||
assert verifyPostHeaders(httpPrefix, publicKeyPem, headers,
|
||||
boxpath, GETmethod, None,
|
||||
messageBodyJsonStr, debug) is False
|
||||
assert verifyPostHeaders(httpPrefix, publicKeyPem, headers,
|
||||
'/parambulator' + boxpath, GETmethod, None,
|
||||
messageBodyJsonStr, debug) is False
|
||||
assert verifyPostHeaders(httpPrefix, publicKeyPem, headers,
|
||||
boxpath, not GETmethod, None,
|
||||
messageBodyJsonStr, debug) is False
|
||||
if not withDigest:
|
||||
# fake domain
|
||||
headers = {
|
||||
'host': 'bogon.domain',
|
||||
'date': dateStr,
|
||||
'content-type': contentType
|
||||
}
|
||||
else:
|
||||
# correct domain but fake message
|
||||
messageBodyJsonStr = \
|
||||
'{"a key": "a value", "another key": "Fake GNUs", ' + \
|
||||
'"yet another key": "More Fake GNUs"}'
|
||||
contentLength = len(messageBodyJsonStr)
|
||||
bodyDigest = messageContentDigest(messageBodyJsonStr)
|
||||
headers = {
|
||||
'host': domain,
|
||||
'date': dateStr,
|
||||
'digest': f'SHA-256={bodyDigest}',
|
||||
'content-type': contentType,
|
||||
'content-length': str(contentLength)
|
||||
}
|
||||
headers['signature'] = signatureHeader
|
||||
headers['signature-input'] = signatureIndexHeader
|
||||
pprint(headers)
|
||||
assert verifyPostHeaders(httpPrefix, publicKeyPem, headers,
|
||||
boxpath, not GETmethod, None,
|
||||
messageBodyJsonStr, False) is False
|
||||
|
||||
os.chdir(baseDir)
|
||||
shutil.rmtree(path, ignore_errors=False, onerror=None)
|
||||
|
||||
|
||||
def runAllTests():
|
||||
baseDir = os.getcwd()
|
||||
print('Running tests...')
|
||||
|
@ -5990,6 +6069,8 @@ def runAllTests():
|
|||
_testHttpsig(baseDir)
|
||||
_testHttpSignedGET(baseDir)
|
||||
_testHttpSigNew()
|
||||
_testHttpsigBaseNew(True, baseDir)
|
||||
_testHttpsigBaseNew(False, baseDir)
|
||||
_testCache()
|
||||
_testThreads()
|
||||
_testCreatePerson(baseDir)
|
||||
|
|
4
utils.py
4
utils.py
|
@ -2653,8 +2653,8 @@ def isPGPEncrypted(content: str) -> bool:
|
|||
return False
|
||||
|
||||
|
||||
def malformedCiphertext(content: str) -> bool:
|
||||
"""Returns true if the given content contains a malformed key
|
||||
def invalidCiphertext(content: str) -> bool:
|
||||
"""Returns true if the given content contains an invalid key
|
||||
"""
|
||||
if '----BEGIN ' in content or '----END ' in content:
|
||||
if not containsPGPPublicKey(content) and \
|
||||
|
|
Loading…
Reference in New Issue