mirror of https://gitlab.com/bashrc2/epicyon
				
				
				
			Improve support for hs2019 http signatures
							parent
							
								
									e48e2a0fd1
								
							
						
					
					
						commit
						0ce7573104
					
				|  | @ -450,6 +450,8 @@ class PubServer(BaseHTTPRequestHandler): | ||||||
|             # https://tools.ietf.org/html/ |             # https://tools.ietf.org/html/ | ||||||
|             # draft-ietf-httpbis-message-signatures-01 |             # draft-ietf-httpbis-message-signatures-01 | ||||||
|             return self.headers['Signature-Input'] |             return self.headers['Signature-Input'] | ||||||
|  |         elif self.headers.get('signature-input'): | ||||||
|  |             return self.headers['signature-input'] | ||||||
|         elif self.headers.get('signature'): |         elif self.headers.get('signature'): | ||||||
|             # Ye olde Masto http sig |             # Ye olde Masto http sig | ||||||
|             return self.headers['signature'] |             return self.headers['signature'] | ||||||
|  |  | ||||||
							
								
								
									
										83
									
								
								httpsig.py
								
								
								
								
							
							
						
						
									
										83
									
								
								httpsig.py
								
								
								
								
							|  | @ -11,7 +11,7 @@ __module_group__ = "Security" | ||||||
| # see https://tools.ietf.org/html/draft-cavage-http-signatures-06 | # see https://tools.ietf.org/html/draft-cavage-http-signatures-06 | ||||||
| # | # | ||||||
| # This might change in future | # 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.backends import default_backend | ||||||
| from cryptography.hazmat.primitives.serialization import load_pem_private_key | from cryptography.hazmat.primitives.serialization import load_pem_private_key | ||||||
|  | @ -116,11 +116,11 @@ def signPostHeadersNew(dateStr: str, privateKeyPem: str, | ||||||
|                        path: str, |                        path: str, | ||||||
|                        httpPrefix: str, |                        httpPrefix: str, | ||||||
|                        messageBodyJsonStr: 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 |     """Returns a raw signature strings that can be plugged into a header | ||||||
|     as "Signature-Input" and "Signature" |     as "Signature-Input" and "Signature" | ||||||
|     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-01 |     See https://tools.ietf.org/html/draft-ietf-httpbis-message-signatures | ||||||
|     """ |     """ | ||||||
|     domain = getFullDomain(domain, port) |     domain = getFullDomain(domain, port) | ||||||
| 
 | 
 | ||||||
|  | @ -137,18 +137,17 @@ def signPostHeadersNew(dateStr: str, privateKeyPem: str, | ||||||
|     keyID = localActorUrl(httpPrefix, nickname, domain) + '#main-key' |     keyID = localActorUrl(httpPrefix, nickname, domain) + '#main-key' | ||||||
|     if not messageBodyJsonStr: |     if not messageBodyJsonStr: | ||||||
|         headers = { |         headers = { | ||||||
|             '*request-target': f'post {path}', |             '@request-target': f'get {path}', | ||||||
|             '*created': str(secondsSinceEpoch), |             '@created': str(secondsSinceEpoch), | ||||||
|             'host': toDomain, |             'host': toDomain, | ||||||
|             'date': dateStr, |             'date': dateStr | ||||||
|             'content-type': 'application/json' |  | ||||||
|         } |         } | ||||||
|     else: |     else: | ||||||
|         bodyDigest = messageContentDigest(messageBodyJsonStr) |         bodyDigest = messageContentDigest(messageBodyJsonStr) | ||||||
|         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'SHA-256={bodyDigest}', | ||||||
|  | @ -164,6 +163,10 @@ def signPostHeadersNew(dateStr: str, privateKeyPem: str, | ||||||
|         signedHeaderText += f'{headerKey}: {headers[headerKey]}\n' |         signedHeaderText += f'{headerKey}: {headers[headerKey]}\n' | ||||||
|     signedHeaderText = signedHeaderText.strip() |     signedHeaderText = signedHeaderText.strip() | ||||||
| 
 | 
 | ||||||
|  |     if debug: | ||||||
|  |         print('\nsignPostHeadersNew signedHeaderText:\n' + | ||||||
|  |               signedHeaderText + '\nEND\n') | ||||||
|  | 
 | ||||||
|     # Sign the digest. Potentially other signing algorithms can be added here. |     # Sign the digest. Potentially other signing algorithms can be added here. | ||||||
|     signature = '' |     signature = '' | ||||||
|     if algorithm == 'rsa-sha512': |     if algorithm == 'rsa-sha512': | ||||||
|  | @ -298,8 +301,11 @@ def verifyPostHeaders(httpPrefix: str, publicKeyPem: str, headers: dict, | ||||||
|     pubkey = load_pem_public_key(publicKeyPem.encode('utf-8'), |     pubkey = load_pem_public_key(publicKeyPem.encode('utf-8'), | ||||||
|                                  backend=default_backend()) |                                  backend=default_backend()) | ||||||
|     # Build a dictionary of the signature values |     # Build a dictionary of the signature values | ||||||
|     if headers.get('Signature-Input'): |     if headers.get('Signature-Input') or headers.get('signature-input'): | ||||||
|         signatureHeader = headers['Signature-Input'] |         if headers.get('Signature-Input'): | ||||||
|  |             signatureHeader = headers['Signature-Input'] | ||||||
|  |         else: | ||||||
|  |             signatureHeader = headers['signature-input'] | ||||||
|         fieldSep2 = ',' |         fieldSep2 = ',' | ||||||
|         # split the signature input into separate fields |         # split the signature input into separate fields | ||||||
|         signatureDict = { |         signatureDict = { | ||||||
|  | @ -342,15 +348,23 @@ def verifyPostHeaders(httpPrefix: str, publicKeyPem: str, headers: dict, | ||||||
|             # original Mastodon http signature |             # original Mastodon http signature | ||||||
|             appendStr = f'(request-target): {method.lower()} {path}' |             appendStr = f'(request-target): {method.lower()} {path}' | ||||||
|             signedHeaderList.append(appendStr) |             signedHeaderList.append(appendStr) | ||||||
|         elif '*request-target' in signedHeader: |         elif '@request-target' in signedHeader: | ||||||
|             # https://tools.ietf.org/html/ |             # https://tools.ietf.org/html/ | ||||||
|             # draft-ietf-httpbis-message-signatures-01 |             # draft-ietf-httpbis-message-signatures | ||||||
|             appendStr = f'*request-target: {method.lower()} {path}' |             appendStr = f'@request-target: {method.lower()} {path}' | ||||||
|             # remove () |             signedHeaderList.append(appendStr) | ||||||
|             # if appendStr.startswith('('): |         elif '@created' in signedHeader: | ||||||
|             #     appendStr = appendStr.split('(')[1] |             if signatureDict.get('created'): | ||||||
|             #     if ')' in appendStr: |                 createdStr = str(signatureDict['created']) | ||||||
|             #         appendStr = appendStr.split(')')[0] |                 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) |             signedHeaderList.append(appendStr) | ||||||
|         elif signedHeader == 'algorithm': |         elif signedHeader == 'algorithm': | ||||||
|             if headers.get(signedHeader): |             if headers.get(signedHeader): | ||||||
|  | @ -430,14 +444,18 @@ def verifyPostHeaders(httpPrefix: str, publicKeyPem: str, headers: dict, | ||||||
|     # Now we have our header data digest |     # Now we have our header data digest | ||||||
|     signedHeaderText = '\n'.join(signedHeaderList) |     signedHeaderText = '\n'.join(signedHeaderList) | ||||||
|     if debug: |     if debug: | ||||||
|         print('signedHeaderText:\n' + signedHeaderText + 'END') |         print('\nverifyPostHeaders signedHeaderText:\n' + | ||||||
|  |               signedHeaderText + '\nEND\n') | ||||||
| 
 | 
 | ||||||
|     # Get the signature, verify with public key, return result |     # Get the signature, verify with public key, return result | ||||||
|     signature = None |     if (headers.get('Signature-Input') and headers.get('Signature')) or \ | ||||||
|     if headers.get('Signature-Input') and headers.get('Signature'): |        (headers.get('signature-input') and headers.get('signature')): | ||||||
|         # https://tools.ietf.org/html/ |         # https://tools.ietf.org/html/ | ||||||
|         # draft-ietf-httpbis-message-signatures-01 |         # draft-ietf-httpbis-message-signatures | ||||||
|         headersSig = headers['Signature'] |         if headers.get('Signature'): | ||||||
|  |             headersSig = headers['Signature'] | ||||||
|  |         else: | ||||||
|  |             headersSig = headers['signature'] | ||||||
|         # remove sig1=: |         # remove sig1=: | ||||||
|         if requestTargetKey + '=:' in headersSig: |         if requestTargetKey + '=:' in headersSig: | ||||||
|             headersSig = headersSig.split(requestTargetKey + '=:')[1] |             headersSig = headersSig.split(requestTargetKey + '=:')[1] | ||||||
|  | @ -445,10 +463,10 @@ def verifyPostHeaders(httpPrefix: str, publicKeyPem: str, headers: dict, | ||||||
|         signature = base64.b64decode(headersSig) |         signature = base64.b64decode(headersSig) | ||||||
|     else: |     else: | ||||||
|         # Original Mastodon signature |         # Original Mastodon signature | ||||||
|         signature = base64.b64decode(signatureDict['signature']) |         headersSig = signatureDict['signature'] | ||||||
|         if debug: |         signature = base64.b64decode(headersSig) | ||||||
|             print('signature: ' + algorithm + ' ' + |     if debug: | ||||||
|                   signatureDict['signature']) |         print('signature: ' + algorithm + ' ' + headersSig) | ||||||
| 
 | 
 | ||||||
|     # log unusual signing algorithms |     # log unusual signing algorithms | ||||||
|     if signatureDict.get('alg'): |     if signatureDict.get('alg'): | ||||||
|  | @ -457,11 +475,12 @@ def verifyPostHeaders(httpPrefix: str, publicKeyPem: str, headers: dict, | ||||||
|     # If extra signing algorithms need to be added then do it here |     # If extra signing algorithms need to be added then do it here | ||||||
|     if not signatureDict.get('alg'): |     if not signatureDict.get('alg'): | ||||||
|         alg = hazutils.Prehashed(hashes.SHA256()) |         alg = hazutils.Prehashed(hashes.SHA256()) | ||||||
|     elif signatureDict['alg'] == 'rsa-sha256': |     elif (signatureDict['alg'] == 'rsa-sha256' or | ||||||
|  |           signatureDict['alg'] == 'rsa-v1_5-sha256' or | ||||||
|  |           signatureDict['alg'] == 'hs2019'): | ||||||
|         alg = hazutils.Prehashed(hashes.SHA256()) |         alg = hazutils.Prehashed(hashes.SHA256()) | ||||||
|     elif signatureDict['alg'] == 'hs2019': |     elif (signatureDict['alg'] == 'rsa-sha512' or | ||||||
|         alg = hazutils.Prehashed(hashes.SHA256()) |           signatureDict['alg'] == 'rsa-pss-sha512'): | ||||||
|     elif signatureDict['alg'] == 'rsa-sha512': |  | ||||||
|         alg = hazutils.Prehashed(hashes.SHA512()) |         alg = hazutils.Prehashed(hashes.SHA512()) | ||||||
|     else: |     else: | ||||||
|         alg = hazutils.Prehashed(hashes.SHA256()) |         alg = hazutils.Prehashed(hashes.SHA256()) | ||||||
|  |  | ||||||
							
								
								
									
										250
									
								
								tests.py
								
								
								
								
							
							
						
						
									
										250
									
								
								tests.py
								
								
								
								
							|  | @ -392,8 +392,20 @@ def _testSignAndVerify() -> None: | ||||||
| 
 | 
 | ||||||
| def _testHttpSigNew(): | def _testHttpSigNew(): | ||||||
|     print('testHttpSigNew') |     print('testHttpSigNew') | ||||||
|  |     httpPrefix = 'https' | ||||||
|  |     port = 443 | ||||||
|  |     debug = True | ||||||
|     messageBodyJson = {"hello": "world"} |     messageBodyJson = {"hello": "world"} | ||||||
|     messageBodyJsonStr = json.dumps(messageBodyJson) |     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 = \ |     publicKeyPem = \ | ||||||
|         '-----BEGIN RSA PUBLIC KEY-----\n' + \ |         '-----BEGIN RSA PUBLIC KEY-----\n' + \ | ||||||
|         'MIIBCgKCAQEAhAKYdtoeoy8zcAcR874L8' + \ |         'MIIBCgKCAQEAhAKYdtoeoy8zcAcR874L8' + \ | ||||||
|  | @ -462,101 +474,49 @@ def _testHttpSigNew(): | ||||||
|         'EQeNC8fHGg4UXU8mhHnSBt3EA10qQJfRD' + \ |         'EQeNC8fHGg4UXU8mhHnSBt3EA10qQJfRD' + \ | ||||||
|         's15M38eG2cYwB1PZpDHScDnDA0=\n' + \ |         's15M38eG2cYwB1PZpDHScDnDA0=\n' + \ | ||||||
|         '-----END RSA PRIVATE KEY-----' |         '-----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 = { |     headers = { | ||||||
|         "host": domain, |         "host": domain, | ||||||
|         "date": dateStr, |         "date": dateStr, | ||||||
|         "digest": f'SHA-256={bodyDigest}', |         "digest": f'SHA-256={bodyDigest}', | ||||||
|         "content-type": "application/json", |         "content-type": contentType, | ||||||
|         "content-length": str(contentLength) |         "content-length": str(contentLength) | ||||||
|     } |     } | ||||||
|     signatureIndexHeader, signatureHeader = \ |     signatureIndexHeader, signatureHeader = \ | ||||||
|         signPostHeadersNew(dateStr, privateKeyPem, nickname, |         signPostHeadersNew(dateStr, privateKeyPem, nickname, | ||||||
|                            domain, port, |                            domain, port, | ||||||
|                            domain, port, |                            domain, port, | ||||||
|                            boxpath, httpPrefix, messageBodyJsonStr, |                            pathStr, httpPrefix, messageBodyJsonStr, | ||||||
|                            'rsa-sha256') |                            'rsa-sha256', debug) | ||||||
|     expectedIndexHeader = \ |     print('signatureIndexHeader1: ' + str(signatureIndexHeader)) | ||||||
|         'keyId="https://example.com/users/foo#main-key"; ' + \ |     print('signatureHeader1: ' + str(signatureHeader)) | ||||||
|         'alg=hs2019; created=' + str(secondsSinceEpoch) + '; ' + \ |     sigInput = "keyId=\"https://example.com/users/foo#main-key\"; " + \ | ||||||
|         'sig1=(*request-target, *created, host, date, ' + \ |         "alg=hs2019; created=1618884475; " + \ | ||||||
|         'digest, content-type, content-length)' |         "sig1=(@request-target, @created, host, date, digest, " + \ | ||||||
|     if signatureIndexHeader != expectedIndexHeader: |         "content-type, content-length)" | ||||||
|         print('Unexpected new http header: ' + signatureIndexHeader) |     assert signatureIndexHeader == sigInput | ||||||
|         print('Should be:                  ' + expectedIndexHeader) |     sig = "sig1=:NXAQ7AtDMR2iwhmH1qCwiZw5PVTjOw5+5kSu0Tsx/3gqz0D" + \ | ||||||
|     assert signatureIndexHeader == expectedIndexHeader |         "py7OQbWqFHrNB7MmS4TukX/vDyQOFdElY5yxnEhbgRwKACq0AP4QH9H" + \ | ||||||
|     assert signatureHeader == \ |         "CiRyCE8UXDdAkY4VUd6jrWjRHKRoqQN7I+Q5tb2Fu5cDfifw/PQc86Z" + \ | ||||||
|         'sig1=:euX3O1KSTYXN9/oR2qFezswWm9FbrjtRymK7xBpXNQvTs' + \ |         "NmMhPrg3OjUJ9Q2Gj29NhgJ+4el1ECg0cAy4yG1M9AQ3KvQooQFvlg1" + \ | ||||||
|         'XehtrNdD8nELZKzPXMvMz7PaJd6V+fjzpHoZ9upTdqqQLK2Iwml' + \ |         "vp0H2xfbJQjv8FsR/lKiRdaVHqGR2CKrvxvPRPaOsFANp2wzEtiMk3O" + \ | ||||||
|         'p4BlHqW6Aopd7sZFCWFq7/Amm5oaizpp3e0jb5XISS5m3cRKuoi' + \ |         "TrBTYU+Zb53mIspfEeLxsNtcGmBDmQKZ9Pud8f99XGJrP+uDd3zKtnr" + \ | ||||||
|         'LM0x+OudmAoYGi0TEEJk8bpnJAXfVCDfmOyL3XNqQeShQHeOANG' + \ |         "f3fUnRRqy37yhB7WVwkg==:" | ||||||
|         'okiKktj8ff+KLYLaPTAJkob1k/EhoPIkbw/YzAY8IZjWQNMkf+F' + \ |     assert signatureHeader == sig | ||||||
|         'JChApQ5HnDCQPwD5xV9eGzBpAf6D0G19xiTmQye4Hn6tAs3fy3V' + \ | 
 | ||||||
|         '/aYa/GhW2pSrctDnAKIi4imj9joppr3CB8gqgXZOPQ==:' |     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): | def _testHttpsigBase(withDigest: bool, baseDir: str): | ||||||
|  | @ -5920,6 +5880,124 @@ def _testValidEmojiContent() -> None: | ||||||
|     assert validEmojiContent('😄') |     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(): | def runAllTests(): | ||||||
|     baseDir = os.getcwd() |     baseDir = os.getcwd() | ||||||
|     print('Running tests...') |     print('Running tests...') | ||||||
|  | @ -5991,6 +6069,8 @@ def runAllTests(): | ||||||
|     _testHttpsig(baseDir) |     _testHttpsig(baseDir) | ||||||
|     _testHttpSignedGET(baseDir) |     _testHttpSignedGET(baseDir) | ||||||
|     _testHttpSigNew() |     _testHttpSigNew() | ||||||
|  |     _testHttpsigBaseNew(True, baseDir) | ||||||
|  |     _testHttpsigBaseNew(False, baseDir) | ||||||
|     _testCache() |     _testCache() | ||||||
|     _testThreads() |     _testThreads() | ||||||
|     _testCreatePerson(baseDir) |     _testCreatePerson(baseDir) | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue