| 
									
										
										
										
											2021-02-04 16:30:32 +00:00
										 |  |  | __filename__ = "httpsig.py" | 
					
						
							| 
									
										
										
										
											2020-04-03 12:05:30 +00:00
										 |  |  | __author__ = "Bob Mottram" | 
					
						
							|  |  |  | __credits__ = ['lamia'] | 
					
						
							|  |  |  | __license__ = "AGPL3+" | 
					
						
							| 
									
										
										
										
											2022-02-03 13:58:20 +00:00
										 |  |  | __version__ = "1.3.0" | 
					
						
							| 
									
										
										
										
											2020-04-03 12:05:30 +00:00
										 |  |  | __maintainer__ = "Bob Mottram" | 
					
						
							| 
									
										
										
										
											2021-09-10 16:14:50 +00:00
										 |  |  | __email__ = "bob@libreserver.org" | 
					
						
							| 
									
										
										
										
											2020-04-03 12:05:30 +00:00
										 |  |  | __status__ = "Production" | 
					
						
							| 
									
										
										
										
											2021-06-15 15:08:12 +00:00
										 |  |  | __module_group__ = "Security" | 
					
						
							| 
									
										
										
										
											2019-06-28 18:55:29 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-15 22:33:42 +00:00
										 |  |  | # see https://tools.ietf.org/html/draft-cavage-http-signatures-06 | 
					
						
							| 
									
										
										
										
											2021-02-21 22:51:08 +00:00
										 |  |  | # | 
					
						
							|  |  |  | # This might change in future | 
					
						
							| 
									
										
										
										
											2021-11-22 18:30:05 +00:00
										 |  |  | # see https://tools.ietf.org/html/draft-ietf-httpbis-message-signatures | 
					
						
							| 
									
										
										
										
											2019-08-15 22:33:42 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-04 16:30:32 +00:00
										 |  |  | 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_public_key | 
					
						
							|  |  |  | from cryptography.hazmat.primitives.asymmetric import padding | 
					
						
							|  |  |  | from cryptography.hazmat.primitives import hashes | 
					
						
							|  |  |  | from cryptography.hazmat.primitives.asymmetric import utils as hazutils | 
					
						
							| 
									
										
										
										
											2019-06-28 18:55:29 +00:00
										 |  |  | import base64 | 
					
						
							| 
									
										
										
										
											2019-08-15 09:08:18 +00:00
										 |  |  | from time import gmtime, strftime | 
					
						
							| 
									
										
										
										
											2019-08-23 11:20:20 +00:00
										 |  |  | import datetime | 
					
						
							| 
									
										
										
										
											2021-12-26 12:45:03 +00:00
										 |  |  | from utils import get_full_domain | 
					
						
							| 
									
										
										
										
											2021-12-26 12:13:46 +00:00
										 |  |  | from utils import get_sha_256 | 
					
						
							|  |  |  | from utils import get_sha_512 | 
					
						
							| 
									
										
										
										
											2021-12-26 10:19:59 +00:00
										 |  |  | from utils import local_actor_url | 
					
						
							| 
									
										
										
										
											2021-02-04 16:30:32 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  | def message_content_digest(message_body_json_str: str, | 
					
						
							|  |  |  |                            digest_algorithm: str) -> str: | 
					
						
							| 
									
										
										
										
											2021-11-23 11:41:40 +00:00
										 |  |  |     """Returns the digest for the message body
 | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |     msg = message_body_json_str.encode('utf-8') | 
					
						
							|  |  |  |     if digest_algorithm in ('rsa-sha512', 'rsa-pss-sha512'): | 
					
						
							|  |  |  |         hash_result = get_sha_512(msg) | 
					
						
							| 
									
										
										
										
											2021-11-23 11:41:40 +00:00
										 |  |  |     else: | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |         hash_result = get_sha_256(msg) | 
					
						
							|  |  |  |     return base64.b64encode(hash_result).decode('utf-8') | 
					
						
							| 
									
										
										
										
											2020-04-03 12:05:30 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-16 17:19:23 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  | def get_digest_prefix(digest_algorithm: str) -> str: | 
					
						
							| 
									
										
										
										
											2021-11-23 11:41:40 +00:00
										 |  |  |     """Returns the prefix for the message body digest
 | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |     if digest_algorithm in ('rsa-sha512', 'rsa-pss-sha512'): | 
					
						
							| 
									
										
										
										
											2021-11-23 11:41:40 +00:00
										 |  |  |         return 'SHA-512' | 
					
						
							|  |  |  |     return 'SHA-256' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  | def get_digest_algorithm_from_headers(http_headers: {}) -> str: | 
					
						
							| 
									
										
										
										
											2021-11-23 11:41:40 +00:00
										 |  |  |     """Returns the digest algorithm from http headers
 | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |     digest_str = None | 
					
						
							|  |  |  |     if http_headers.get('digest'): | 
					
						
							|  |  |  |         digest_str = http_headers['digest'] | 
					
						
							|  |  |  |     elif http_headers.get('Digest'): | 
					
						
							|  |  |  |         digest_str = http_headers['Digest'] | 
					
						
							|  |  |  |     if digest_str: | 
					
						
							|  |  |  |         if digest_str.startswith('SHA-512'): | 
					
						
							| 
									
										
										
										
											2021-11-23 11:41:40 +00:00
										 |  |  |             return 'rsa-sha512' | 
					
						
							|  |  |  |     return 'rsa-sha256' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  | def sign_post_headers(dateStr: str, private_key_pem: str, | 
					
						
							|  |  |  |                       nickname: str, domain: str, port: int, | 
					
						
							|  |  |  |                       to_domain: str, to_port: int, | 
					
						
							|  |  |  |                       path: str, http_prefix: str, | 
					
						
							|  |  |  |                       message_body_json_str: str, | 
					
						
							|  |  |  |                       content_type: str, algorithm: str, | 
					
						
							|  |  |  |                       digest_algorithm: str) -> str: | 
					
						
							| 
									
										
										
										
											2019-06-28 18:55:29 +00:00
										 |  |  |     """Returns a raw signature string that can be plugged into a header and
 | 
					
						
							|  |  |  |     used to verify the authenticity of an HTTP transmission. | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2021-12-26 12:45:03 +00:00
										 |  |  |     domain = get_full_domain(domain, port) | 
					
						
							| 
									
										
										
										
											2019-07-01 09:31:02 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |     to_domain = get_full_domain(to_domain, to_port) | 
					
						
							| 
									
										
										
										
											2019-08-16 13:47:01 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if not dateStr: | 
					
						
							| 
									
										
										
										
											2020-04-03 12:05:30 +00:00
										 |  |  |         dateStr = strftime("%a, %d %b %Y %H:%M:%S %Z", gmtime()) | 
					
						
							| 
									
										
										
										
											2021-09-08 10:05:45 +00:00
										 |  |  |     if nickname != domain and nickname.lower() != 'actor': | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |         key_id = local_actor_url(http_prefix, nickname, domain) | 
					
						
							| 
									
										
										
										
											2021-08-31 21:02:58 +00:00
										 |  |  |     else: | 
					
						
							|  |  |  |         # instance actor | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |         key_id = http_prefix + '://' + domain + '/actor' | 
					
						
							|  |  |  |     key_id += '#main-key' | 
					
						
							|  |  |  |     if not message_body_json_str: | 
					
						
							| 
									
										
										
										
											2020-04-03 12:05:30 +00:00
										 |  |  |         headers = { | 
					
						
							| 
									
										
										
										
											2021-09-01 18:46:28 +00:00
										 |  |  |             '(request-target)': f'get {path}', | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |             'host': to_domain, | 
					
						
							| 
									
										
										
										
											2020-04-03 12:05:30 +00:00
										 |  |  |             'date': dateStr, | 
					
						
							| 
									
										
										
										
											2021-12-26 15:32:00 +00:00
										 |  |  |             'accept': content_type | 
					
						
							| 
									
										
										
										
											2020-04-03 12:05:30 +00:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2019-06-28 18:55:29 +00:00
										 |  |  |     else: | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |         body_digest = \ | 
					
						
							|  |  |  |             message_content_digest(message_body_json_str, digest_algorithm) | 
					
						
							|  |  |  |         digest_prefix = get_digest_prefix(digest_algorithm) | 
					
						
							|  |  |  |         content_length = len(message_body_json_str) | 
					
						
							| 
									
										
										
										
											2020-04-03 12:05:30 +00:00
										 |  |  |         headers = { | 
					
						
							|  |  |  |             '(request-target)': f'post {path}', | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |             'host': to_domain, | 
					
						
							| 
									
										
										
										
											2020-04-03 12:05:30 +00:00
										 |  |  |             'date': dateStr, | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |             'digest': f'{digest_prefix}={body_digest}', | 
					
						
							| 
									
										
										
										
											2020-04-03 12:05:30 +00:00
										 |  |  |             'content-type': 'application/activity+json', | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |             'content-length': str(content_length) | 
					
						
							| 
									
										
										
										
											2020-04-03 12:05:30 +00:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |     key = load_pem_private_key(private_key_pem.encode('utf-8'), | 
					
						
							| 
									
										
										
										
											2021-02-04 16:30:32 +00:00
										 |  |  |                                None, backend=default_backend()) | 
					
						
							| 
									
										
										
										
											2020-04-03 12:05:30 +00:00
										 |  |  |     # headers.update({ | 
					
						
							|  |  |  |     #     '(request-target)': f'post {path}', | 
					
						
							|  |  |  |     # }) | 
					
						
							| 
									
										
										
										
											2019-06-28 18:55:29 +00:00
										 |  |  |     # build a digest for signing | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |     signed_header_keys = headers.keys() | 
					
						
							|  |  |  |     signed_header_text = '' | 
					
						
							|  |  |  |     for header_key in signed_header_keys: | 
					
						
							|  |  |  |         signed_header_text += f'{header_key}: {headers[header_key]}\n' | 
					
						
							| 
									
										
										
										
											2021-09-08 10:05:45 +00:00
										 |  |  |     # strip the trailing linefeed | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |     signed_header_text = signed_header_text.rstrip('\n') | 
					
						
							|  |  |  |     # signed_header_text.encode('ascii') matches | 
					
						
							|  |  |  |     header_digest = get_sha_256(signed_header_text.encode('ascii')) | 
					
						
							|  |  |  |     # print('header_digest2: ' + str(header_digest)) | 
					
						
							| 
									
										
										
										
											2019-06-28 18:55:29 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     # Sign the digest | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |     raw_signature = key.sign(header_digest, | 
					
						
							|  |  |  |                              padding.PKCS1v15(), | 
					
						
							|  |  |  |                              hazutils.Prehashed(hashes.SHA256())) | 
					
						
							|  |  |  |     signature = base64.b64encode(raw_signature).decode('ascii') | 
					
						
							| 
									
										
										
										
											2019-06-28 18:55:29 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     # Put it into a valid HTTP signature format | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |     signature_dict = { | 
					
						
							|  |  |  |         'keyId': key_id, | 
					
						
							| 
									
										
										
										
											2021-11-23 11:41:40 +00:00
										 |  |  |         'algorithm': algorithm, | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |         'headers': ' '.join(signed_header_keys), | 
					
						
							| 
									
										
										
										
											2019-06-28 18:55:29 +00:00
										 |  |  |         'signature': signature | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |     signature_header = ','.join( | 
					
						
							|  |  |  |         [f'{k}="{v}"' for k, v in signature_dict.items()]) | 
					
						
							|  |  |  |     return signature_header | 
					
						
							| 
									
										
										
										
											2019-06-28 18:55:29 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-03 12:05:30 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  | def sign_post_headers_new(dateStr: str, private_key_pem: str, | 
					
						
							| 
									
										
										
										
											2021-12-29 21:55:09 +00:00
										 |  |  |                           nickname: str, | 
					
						
							|  |  |  |                           domain: str, port: int, | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |                           to_domain: str, to_port: int, | 
					
						
							| 
									
										
										
										
											2021-12-29 21:55:09 +00:00
										 |  |  |                           path: str, | 
					
						
							|  |  |  |                           http_prefix: str, | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |                           message_body_json_str: str, | 
					
						
							|  |  |  |                           algorithm: str, digest_algorithm: str, | 
					
						
							| 
									
										
										
										
											2021-12-29 21:55:09 +00:00
										 |  |  |                           debug: bool) -> (str, str): | 
					
						
							| 
									
										
										
										
											2021-02-22 18:20:33 +00:00
										 |  |  |     """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. | 
					
						
							| 
									
										
										
										
											2021-11-22 18:30:05 +00:00
										 |  |  |     See https://tools.ietf.org/html/draft-ietf-httpbis-message-signatures | 
					
						
							| 
									
										
										
										
											2021-02-22 18:20:33 +00:00
										 |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2021-12-26 12:45:03 +00:00
										 |  |  |     domain = get_full_domain(domain, port) | 
					
						
							| 
									
										
										
										
											2021-02-22 18:20:33 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |     to_domain = get_full_domain(to_domain, to_port) | 
					
						
							| 
									
										
										
										
											2021-02-22 18:20:33 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |     time_format = "%a, %d %b %Y %H:%M:%S %Z" | 
					
						
							| 
									
										
										
										
											2021-02-22 18:20:33 +00:00
										 |  |  |     if not dateStr: | 
					
						
							| 
									
										
										
										
											2021-12-26 13:17:46 +00:00
										 |  |  |         curr_time = gmtime() | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |         dateStr = strftime(time_format, curr_time) | 
					
						
							| 
									
										
										
										
											2021-02-22 18:20:33 +00:00
										 |  |  |     else: | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |         curr_time = datetime.datetime.strptime(dateStr, time_format) | 
					
						
							|  |  |  |     seconds_since_epoch = \ | 
					
						
							| 
									
										
										
										
											2021-12-26 13:17:46 +00:00
										 |  |  |         int((curr_time - datetime.datetime(1970, 1, 1)).total_seconds()) | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |     key_id = local_actor_url(http_prefix, nickname, domain) + '#main-key' | 
					
						
							|  |  |  |     if not message_body_json_str: | 
					
						
							| 
									
										
										
										
											2021-02-22 18:20:33 +00:00
										 |  |  |         headers = { | 
					
						
							| 
									
										
										
										
											2021-11-22 18:30:05 +00:00
										 |  |  |             '@request-target': f'get {path}', | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |             '@created': str(seconds_since_epoch), | 
					
						
							|  |  |  |             'host': to_domain, | 
					
						
							| 
									
										
										
										
											2021-11-22 18:30:05 +00:00
										 |  |  |             'date': dateStr | 
					
						
							| 
									
										
										
										
											2021-02-22 18:20:33 +00:00
										 |  |  |         } | 
					
						
							|  |  |  |     else: | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |         body_digest = message_content_digest(message_body_json_str, | 
					
						
							|  |  |  |                                              digest_algorithm) | 
					
						
							|  |  |  |         digest_prefix = get_digest_prefix(digest_algorithm) | 
					
						
							|  |  |  |         content_length = len(message_body_json_str) | 
					
						
							| 
									
										
										
										
											2021-02-22 18:20:33 +00:00
										 |  |  |         headers = { | 
					
						
							| 
									
										
										
										
											2021-11-22 18:30:05 +00:00
										 |  |  |             '@request-target': f'post {path}', | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |             '@created': str(seconds_since_epoch), | 
					
						
							|  |  |  |             'host': to_domain, | 
					
						
							| 
									
										
										
										
											2021-02-22 18:20:33 +00:00
										 |  |  |             'date': dateStr, | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |             'digest': f'{digest_prefix}={body_digest}', | 
					
						
							| 
									
										
										
										
											2021-02-22 18:20:33 +00:00
										 |  |  |             'content-type': 'application/activity+json', | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |             'content-length': str(content_length) | 
					
						
							| 
									
										
										
										
											2021-02-22 18:20:33 +00:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |     key = load_pem_private_key(private_key_pem.encode('utf-8'), | 
					
						
							| 
									
										
										
										
											2021-02-22 18:20:33 +00:00
										 |  |  |                                None, backend=default_backend()) | 
					
						
							|  |  |  |     # build a digest for signing | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |     signed_header_keys = headers.keys() | 
					
						
							|  |  |  |     signed_header_text = '' | 
					
						
							|  |  |  |     for header_key in signed_header_keys: | 
					
						
							|  |  |  |         signed_header_text += f'{header_key}: {headers[header_key]}\n' | 
					
						
							|  |  |  |     signed_header_text = signed_header_text.strip() | 
					
						
							| 
									
										
										
										
											2021-02-22 18:20:33 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-22 18:30:05 +00:00
										 |  |  |     if debug: | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |         print('\nsign_post_headers_new signed_header_text:\n' + | 
					
						
							|  |  |  |               signed_header_text + '\nEND\n') | 
					
						
							| 
									
										
										
										
											2021-11-22 18:30:05 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-22 18:20:33 +00:00
										 |  |  |     # Sign the digest. Potentially other signing algorithms can be added here. | 
					
						
							|  |  |  |     signature = '' | 
					
						
							| 
									
										
										
										
											2021-09-08 10:05:45 +00:00
										 |  |  |     if algorithm == 'rsa-sha512': | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |         header_digest = get_sha_512(signed_header_text.encode('ascii')) | 
					
						
							|  |  |  |         raw_signature = key.sign(header_digest, | 
					
						
							|  |  |  |                                  padding.PKCS1v15(), | 
					
						
							|  |  |  |                                  hazutils.Prehashed(hashes.SHA512())) | 
					
						
							|  |  |  |         signature = base64.b64encode(raw_signature).decode('ascii') | 
					
						
							| 
									
										
										
										
											2021-09-08 10:05:45 +00:00
										 |  |  |     else: | 
					
						
							| 
									
										
										
										
											2021-11-18 14:04:45 +00:00
										 |  |  |         # default rsa-sha256 | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |         header_digest = get_sha_256(signed_header_text.encode('ascii')) | 
					
						
							|  |  |  |         raw_signature = key.sign(header_digest, | 
					
						
							|  |  |  |                                  padding.PKCS1v15(), | 
					
						
							|  |  |  |                                  hazutils.Prehashed(hashes.SHA256())) | 
					
						
							|  |  |  |         signature = base64.b64encode(raw_signature).decode('ascii') | 
					
						
							| 
									
										
										
										
											2021-02-22 18:20:33 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |     sig_key = 'sig1' | 
					
						
							| 
									
										
										
										
											2021-02-22 18:20:33 +00:00
										 |  |  |     # Put it into a valid HTTP signature format | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |     signature_input_dict = { | 
					
						
							|  |  |  |         'keyId': key_id, | 
					
						
							| 
									
										
										
										
											2021-02-22 18:20:33 +00:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |     signature_index_header = '; '.join( | 
					
						
							|  |  |  |         [f'{k}="{v}"' for k, v in signature_input_dict.items()]) | 
					
						
							|  |  |  |     signature_index_header += '; alg=hs2019' | 
					
						
							|  |  |  |     signature_index_header += '; created=' + str(seconds_since_epoch) | 
					
						
							|  |  |  |     signature_index_header += \ | 
					
						
							|  |  |  |         '; ' + sig_key + '=(' + ', '.join(signed_header_keys) + ')' | 
					
						
							|  |  |  |     signature_dict = { | 
					
						
							|  |  |  |         sig_key: signature | 
					
						
							| 
									
										
										
										
											2021-02-22 18:20:33 +00:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |     signature_header = '; '.join( | 
					
						
							|  |  |  |         [f'{k}=:{v}:' for k, v in signature_dict.items()]) | 
					
						
							|  |  |  |     return signature_index_header, signature_header | 
					
						
							| 
									
										
										
										
											2021-02-22 18:20:33 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  | def create_signed_header(dateStr: str, private_key_pem: str, nickname: str, | 
					
						
							| 
									
										
										
										
											2021-12-29 21:55:09 +00:00
										 |  |  |                          domain: str, port: int, | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |                          to_domain: str, to_port: int, | 
					
						
							| 
									
										
										
										
											2021-12-29 21:55:09 +00:00
										 |  |  |                          path: str, http_prefix: str, withDigest: bool, | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |                          message_body_json_str: str, | 
					
						
							| 
									
										
										
										
											2021-12-29 21:55:09 +00:00
										 |  |  |                          content_type: str) -> {}: | 
					
						
							| 
									
										
										
										
											2019-08-16 13:47:01 +00:00
										 |  |  |     """Note that the domain is the destination, not the sender
 | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2021-11-23 11:41:40 +00:00
										 |  |  |     algorithm = 'rsa-sha256' | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |     digest_algorithm = 'rsa-sha256' | 
					
						
							|  |  |  |     header_domain = get_full_domain(to_domain, to_port) | 
					
						
							| 
									
										
										
										
											2019-07-01 09:31:02 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-15 10:44:44 +00:00
										 |  |  |     # if no date is given then create one | 
					
						
							| 
									
										
										
										
											2021-09-14 21:22:49 +00:00
										 |  |  |     if not dateStr: | 
					
						
							|  |  |  |         dateStr = strftime("%a, %d %b %Y %H:%M:%S %Z", gmtime()) | 
					
						
							| 
									
										
										
										
											2021-09-15 10:44:44 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     # Content-Type or Accept header | 
					
						
							| 
									
										
										
										
											2021-12-26 15:32:00 +00:00
										 |  |  |     if not content_type: | 
					
						
							|  |  |  |         content_type = 'application/activity+json' | 
					
						
							| 
									
										
										
										
											2021-09-15 10:44:44 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-14 22:03:26 +00:00
										 |  |  |     if not withDigest: | 
					
						
							| 
									
										
										
										
											2020-04-03 12:05:30 +00:00
										 |  |  |         headers = { | 
					
						
							| 
									
										
										
										
											2021-09-01 20:57:54 +00:00
										 |  |  |             '(request-target)': f'get {path}', | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |             'host': header_domain, | 
					
						
							| 
									
										
										
										
											2021-09-01 20:57:54 +00:00
										 |  |  |             'date': dateStr, | 
					
						
							| 
									
										
										
										
											2021-12-26 15:32:00 +00:00
										 |  |  |             'accept': content_type | 
					
						
							| 
									
										
										
										
											2020-03-22 20:36:19 +00:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |         signature_header = \ | 
					
						
							|  |  |  |             sign_post_headers(dateStr, private_key_pem, nickname, | 
					
						
							|  |  |  |                               domain, port, to_domain, to_port, | 
					
						
							| 
									
										
										
										
											2021-12-29 21:55:09 +00:00
										 |  |  |                               path, http_prefix, None, content_type, | 
					
						
							|  |  |  |                               algorithm, None) | 
					
						
							| 
									
										
										
										
											2019-07-01 09:31:02 +00:00
										 |  |  |     else: | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |         body_digest = message_content_digest(message_body_json_str, | 
					
						
							|  |  |  |                                              digest_algorithm) | 
					
						
							|  |  |  |         digest_prefix = get_digest_prefix(digest_algorithm) | 
					
						
							|  |  |  |         content_length = len(message_body_json_str) | 
					
						
							| 
									
										
										
										
											2020-04-03 12:05:30 +00:00
										 |  |  |         headers = { | 
					
						
							| 
									
										
										
										
											2020-03-22 20:36:19 +00:00
										 |  |  |             '(request-target)': f'post {path}', | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |             'host': header_domain, | 
					
						
							| 
									
										
										
										
											2020-03-22 20:36:19 +00:00
										 |  |  |             'date': dateStr, | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |             'digest': f'{digest_prefix}={body_digest}', | 
					
						
							|  |  |  |             'content-length': str(content_length), | 
					
						
							| 
									
										
										
										
											2021-12-26 15:32:00 +00:00
										 |  |  |             'content-type': content_type | 
					
						
							| 
									
										
										
										
											2020-03-22 20:36:19 +00:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |         signature_header = \ | 
					
						
							|  |  |  |             sign_post_headers(dateStr, private_key_pem, nickname, | 
					
						
							| 
									
										
										
										
											2021-12-29 21:55:09 +00:00
										 |  |  |                               domain, port, | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |                               to_domain, to_port, | 
					
						
							|  |  |  |                               path, http_prefix, message_body_json_str, | 
					
						
							|  |  |  |                               content_type, algorithm, digest_algorithm) | 
					
						
							|  |  |  |     headers['signature'] = signature_header | 
					
						
							| 
									
										
										
										
											2019-07-01 09:31:02 +00:00
										 |  |  |     return headers | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-03 12:05:30 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  | def _verify_recent_signature(signed_date_str: str) -> bool: | 
					
						
							| 
									
										
										
										
											2019-08-23 11:31:46 +00:00
										 |  |  |     """Checks whether the given time taken from the header is within
 | 
					
						
							|  |  |  |     12 hours of the current time | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |     curr_date = datetime.datetime.utcnow() | 
					
						
							|  |  |  |     date_format = "%a, %d %b %Y %H:%M:%S %Z" | 
					
						
							|  |  |  |     signed_date = datetime.datetime.strptime(signed_date_str, date_format) | 
					
						
							|  |  |  |     time_diff_sec = (curr_date - signed_date).seconds | 
					
						
							| 
									
										
										
										
											2019-08-23 11:39:16 +00:00
										 |  |  |     # 12 hours tollerance | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |     if time_diff_sec > 43200: | 
					
						
							|  |  |  |         print('WARN: Header signed too long ago: ' + signed_date_str) | 
					
						
							|  |  |  |         print(str(time_diff_sec / (60 * 60)) + ' hours') | 
					
						
							| 
									
										
										
										
											2019-08-23 11:37:34 +00:00
										 |  |  |         return False | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |     if time_diff_sec < 0: | 
					
						
							|  |  |  |         print('WARN: Header signed in the future! ' + signed_date_str) | 
					
						
							|  |  |  |         print(str(time_diff_sec / (60 * 60)) + ' hours') | 
					
						
							| 
									
										
										
										
											2019-08-23 11:30:37 +00:00
										 |  |  |         return False | 
					
						
							|  |  |  |     return True | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-03 12:05:30 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-29 21:55:09 +00:00
										 |  |  | def verify_post_headers(http_prefix: str, | 
					
						
							|  |  |  |                         publicKeyPem: str, headers: dict, | 
					
						
							|  |  |  |                         path: str, GETmethod: bool, | 
					
						
							|  |  |  |                         messageBodyDigest: str, | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |                         message_body_json_str: str, debug: bool, | 
					
						
							| 
									
										
										
										
											2021-12-29 21:55:09 +00:00
										 |  |  |                         noRecencyCheck: bool = False) -> bool: | 
					
						
							| 
									
										
										
										
											2019-06-28 18:55:29 +00:00
										 |  |  |     """Returns true or false depending on if the key that we plugged in here
 | 
					
						
							|  |  |  |     validates against the headers, method, and path. | 
					
						
							|  |  |  |     publicKeyPem - the public key from an rsa key pair | 
					
						
							|  |  |  |     headers - should be a dictionary of request headers | 
					
						
							|  |  |  |     path - the relative url that was requested from this site | 
					
						
							|  |  |  |     GETmethod - GET or POST | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |     message_body_json_str - the received request body (used for digest) | 
					
						
							| 
									
										
										
										
											2019-06-28 18:55:29 +00:00
										 |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2019-08-23 11:20:20 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-28 18:55:29 +00:00
										 |  |  |     if GETmethod: | 
					
						
							| 
									
										
										
										
											2020-04-03 12:05:30 +00:00
										 |  |  |         method = 'GET' | 
					
						
							| 
									
										
										
										
											2019-06-28 18:55:29 +00:00
										 |  |  |     else: | 
					
						
							| 
									
										
										
										
											2020-04-03 12:05:30 +00:00
										 |  |  |         method = 'POST' | 
					
						
							| 
									
										
										
										
											2019-11-12 15:03:17 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if debug: | 
					
						
							| 
									
										
										
										
											2021-12-29 21:55:09 +00:00
										 |  |  |         print('DEBUG: verify_post_headers ' + method) | 
					
						
							|  |  |  |         print('verify_post_headers publicKeyPem: ' + str(publicKeyPem)) | 
					
						
							|  |  |  |         print('verify_post_headers headers: ' + str(headers)) | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |         print('verify_post_headers message_body_json_str: ' + | 
					
						
							|  |  |  |               str(message_body_json_str)) | 
					
						
							| 
									
										
										
										
											2020-03-22 21:16:02 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-04 16:30:32 +00:00
										 |  |  |     pubkey = load_pem_public_key(publicKeyPem.encode('utf-8'), | 
					
						
							|  |  |  |                                  backend=default_backend()) | 
					
						
							| 
									
										
										
										
											2019-06-28 18:55:29 +00:00
										 |  |  |     # Build a dictionary of the signature values | 
					
						
							| 
									
										
										
										
											2021-11-22 18:30:05 +00:00
										 |  |  |     if headers.get('Signature-Input') or headers.get('signature-input'): | 
					
						
							|  |  |  |         if headers.get('Signature-Input'): | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |             signature_header = headers['Signature-Input'] | 
					
						
							| 
									
										
										
										
											2021-11-22 18:30:05 +00:00
										 |  |  |         else: | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |             signature_header = headers['signature-input'] | 
					
						
							|  |  |  |         field_sep2 = ',' | 
					
						
							| 
									
										
										
										
											2021-02-22 14:03:24 +00:00
										 |  |  |         # split the signature input into separate fields | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |         signature_dict = { | 
					
						
							| 
									
										
										
										
											2021-02-22 14:03:24 +00:00
										 |  |  |             k.strip(): v.strip() | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |             for k, v in [i.split('=', 1) for i in signature_header.split(';')] | 
					
						
							| 
									
										
										
										
											2021-02-22 14:03:24 +00:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |         request_target_key = None | 
					
						
							|  |  |  |         request_target_str = None | 
					
						
							|  |  |  |         for key_str, value_str in signature_dict.items(): | 
					
						
							|  |  |  |             if value_str.startswith('('): | 
					
						
							|  |  |  |                 request_target_key = key_str | 
					
						
							|  |  |  |                 request_target_str = value_str[1:-1] | 
					
						
							|  |  |  |             elif value_str.startswith('"'): | 
					
						
							|  |  |  |                 signature_dict[key_str] = value_str[1:-1] | 
					
						
							|  |  |  |         if not request_target_key: | 
					
						
							| 
									
										
										
										
											2021-02-22 14:03:24 +00:00
										 |  |  |             return False | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |         signature_dict[request_target_key] = request_target_str | 
					
						
							| 
									
										
										
										
											2021-02-22 11:13:27 +00:00
										 |  |  |     else: | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |         request_target_key = 'headers' | 
					
						
							|  |  |  |         signature_header = headers['signature'] | 
					
						
							|  |  |  |         field_sep2 = ' ' | 
					
						
							| 
									
										
										
										
											2021-02-22 14:03:24 +00:00
										 |  |  |         # split the signature input into separate fields | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |         signature_dict = { | 
					
						
							| 
									
										
										
										
											2021-02-22 14:03:24 +00:00
										 |  |  |             k: v[1:-1] | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |             for k, v in [i.split('=', 1) for i in signature_header.split(',')] | 
					
						
							| 
									
										
										
										
											2021-02-22 14:03:24 +00:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2019-06-28 18:55:29 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-14 21:22:49 +00:00
										 |  |  |     if debug: | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |         print('signature_dict: ' + str(signature_dict)) | 
					
						
							| 
									
										
										
										
											2021-09-14 21:22:49 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-28 18:55:29 +00:00
										 |  |  |     # Unpack the signed headers and set values based on current headers and | 
					
						
							|  |  |  |     # body (if a digest was included) | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |     signed_header_list = [] | 
					
						
							| 
									
										
										
										
											2021-09-01 14:42:43 +00:00
										 |  |  |     algorithm = 'rsa-sha256' | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |     digest_algorithm = 'rsa-sha256' | 
					
						
							|  |  |  |     for signed_header in signature_dict[request_target_key].split(field_sep2): | 
					
						
							|  |  |  |         signed_header = signed_header.strip() | 
					
						
							| 
									
										
										
										
											2019-11-12 15:03:17 +00:00
										 |  |  |         if debug: | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |             print('DEBUG: verify_post_headers signed_header=' + signed_header) | 
					
						
							|  |  |  |         if signed_header == '(request-target)': | 
					
						
							| 
									
										
										
										
											2021-02-22 11:13:27 +00:00
										 |  |  |             # original Mastodon http signature | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |             append_str = f'(request-target): {method.lower()} {path}' | 
					
						
							|  |  |  |             signed_header_list.append(append_str) | 
					
						
							|  |  |  |         elif '@request-target' in signed_header: | 
					
						
							| 
									
										
										
										
											2021-02-22 11:13:27 +00:00
										 |  |  |             # https://tools.ietf.org/html/ | 
					
						
							| 
									
										
										
										
											2021-11-22 18:30:05 +00:00
										 |  |  |             # draft-ietf-httpbis-message-signatures | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |             append_str = f'@request-target: {method.lower()} {path}' | 
					
						
							|  |  |  |             signed_header_list.append(append_str) | 
					
						
							|  |  |  |         elif '@created' in signed_header: | 
					
						
							|  |  |  |             if signature_dict.get('created'): | 
					
						
							|  |  |  |                 created_str = str(signature_dict['created']) | 
					
						
							|  |  |  |                 append_str = f'@created: {created_str}' | 
					
						
							|  |  |  |                 signed_header_list.append(append_str) | 
					
						
							|  |  |  |         elif '@expires' in signed_header: | 
					
						
							|  |  |  |             if signature_dict.get('expires'): | 
					
						
							|  |  |  |                 expires_str = str(signature_dict['expires']) | 
					
						
							|  |  |  |                 append_str = f'@expires: {expires_str}' | 
					
						
							|  |  |  |                 signed_header_list.append(append_str) | 
					
						
							|  |  |  |         elif '@method' in signed_header: | 
					
						
							|  |  |  |             append_str = f'@expires: {method}' | 
					
						
							|  |  |  |             signed_header_list.append(append_str) | 
					
						
							|  |  |  |         elif '@scheme' in signed_header: | 
					
						
							|  |  |  |             signed_header_list.append('@scheme: http') | 
					
						
							|  |  |  |         elif '@authority' in signed_header: | 
					
						
							|  |  |  |             authority_str = None | 
					
						
							|  |  |  |             if signature_dict.get('authority'): | 
					
						
							|  |  |  |                 authority_str = str(signature_dict['authority']) | 
					
						
							|  |  |  |             elif signature_dict.get('Authority'): | 
					
						
							|  |  |  |                 authority_str = str(signature_dict['Authority']) | 
					
						
							|  |  |  |             if authority_str: | 
					
						
							|  |  |  |                 append_str = f'@authority: {authority_str}' | 
					
						
							|  |  |  |                 signed_header_list.append(append_str) | 
					
						
							|  |  |  |         elif signed_header == 'algorithm': | 
					
						
							|  |  |  |             if headers.get(signed_header): | 
					
						
							|  |  |  |                 algorithm = headers[signed_header] | 
					
						
							| 
									
										
										
										
											2021-11-22 11:52:55 +00:00
										 |  |  |                 if debug: | 
					
						
							|  |  |  |                     print('http signature algorithm: ' + algorithm) | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |         elif signed_header == 'digest': | 
					
						
							| 
									
										
										
										
											2019-08-16 17:19:23 +00:00
										 |  |  |             if messageBodyDigest: | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |                 body_digest = messageBodyDigest | 
					
						
							| 
									
										
										
										
											2019-08-16 17:19:23 +00:00
										 |  |  |             else: | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |                 body_digest = \ | 
					
						
							|  |  |  |                     message_content_digest(message_body_json_str, | 
					
						
							|  |  |  |                                            digest_algorithm) | 
					
						
							|  |  |  |             signed_header_list.append(f'digest: SHA-256={body_digest}') | 
					
						
							|  |  |  |         elif signed_header == 'content-length': | 
					
						
							|  |  |  |             if headers.get(signed_header): | 
					
						
							|  |  |  |                 append_str = f'content-length: {headers[signed_header]}' | 
					
						
							|  |  |  |                 signed_header_list.append(append_str) | 
					
						
							| 
									
										
										
										
											2021-09-01 14:22:11 +00:00
										 |  |  |             elif headers.get('Content-Length'): | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |                 content_length = headers['Content-Length'] | 
					
						
							|  |  |  |                 signed_header_list.append(f'content-length: {content_length}') | 
					
						
							| 
									
										
										
										
											2021-09-01 14:22:11 +00:00
										 |  |  |             elif headers.get('Content-length'): | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |                 content_length = headers['Content-length'] | 
					
						
							|  |  |  |                 append_str = f'content-length: {content_length}' | 
					
						
							|  |  |  |                 signed_header_list.append(append_str) | 
					
						
							| 
									
										
										
										
											2019-11-12 17:16:34 +00:00
										 |  |  |             else: | 
					
						
							| 
									
										
										
										
											2021-09-01 14:22:11 +00:00
										 |  |  |                 if debug: | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |                     print('DEBUG: verify_post_headers ' + signed_header + | 
					
						
							| 
									
										
										
										
											2021-09-01 14:22:11 +00:00
										 |  |  |                           ' not found in ' + str(headers)) | 
					
						
							| 
									
										
										
										
											2019-06-28 18:55:29 +00:00
										 |  |  |         else: | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |             if headers.get(signed_header): | 
					
						
							|  |  |  |                 if signed_header == 'date' and not noRecencyCheck: | 
					
						
							|  |  |  |                     if not _verify_recent_signature(headers[signed_header]): | 
					
						
							| 
									
										
										
										
											2019-11-12 15:03:17 +00:00
										 |  |  |                         if debug: | 
					
						
							| 
									
										
										
										
											2020-04-03 12:05:30 +00:00
										 |  |  |                             print('DEBUG: ' + | 
					
						
							| 
									
										
										
										
											2021-12-29 21:55:09 +00:00
										 |  |  |                                   'verify_post_headers date is not recent ' + | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |                                   headers[signed_header]) | 
					
						
							| 
									
										
										
										
											2019-08-23 11:30:37 +00:00
										 |  |  |                         return False | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |                 signed_header_list.append( | 
					
						
							|  |  |  |                     f'{signed_header}: {headers[signed_header]}') | 
					
						
							| 
									
										
										
										
											2019-08-15 21:34:25 +00:00
										 |  |  |             else: | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |                 if '-' in signed_header: | 
					
						
							| 
									
										
										
										
											2021-03-14 12:09:56 +00:00
										 |  |  |                     # capitalise with dashes | 
					
						
							|  |  |  |                     # my-header becomes My-Header | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |                     header_parts = signed_header.split('-') | 
					
						
							|  |  |  |                     signed_header_cap = None | 
					
						
							|  |  |  |                     for part in header_parts: | 
					
						
							|  |  |  |                         if signed_header_cap: | 
					
						
							|  |  |  |                             signed_header_cap += '-' + part.capitalize() | 
					
						
							| 
									
										
										
										
											2021-03-14 12:09:56 +00:00
										 |  |  |                         else: | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |                             signed_header_cap = part.capitalize() | 
					
						
							| 
									
										
										
										
											2021-03-14 11:53:13 +00:00
										 |  |  |                 else: | 
					
						
							| 
									
										
										
										
											2021-03-14 12:09:56 +00:00
										 |  |  |                     # header becomes Header | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |                     signed_header_cap = signed_header.capitalize() | 
					
						
							| 
									
										
										
										
											2021-03-14 12:09:56 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |                 if debug: | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |                     print('signed_header_cap: ' + signed_header_cap) | 
					
						
							| 
									
										
										
										
											2021-03-14 12:09:56 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |                 # if this is the date header then check it is recent | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |                 if signed_header_cap == 'Date': | 
					
						
							|  |  |  |                     signed_hdr_cap = headers[signed_header_cap] | 
					
						
							|  |  |  |                     if not _verify_recent_signature(signed_hdr_cap): | 
					
						
							| 
									
										
										
										
											2019-11-12 15:03:17 +00:00
										 |  |  |                         if debug: | 
					
						
							| 
									
										
										
										
											2020-04-03 12:05:30 +00:00
										 |  |  |                             print('DEBUG: ' + | 
					
						
							| 
									
										
										
										
											2021-12-29 21:55:09 +00:00
										 |  |  |                                   'verify_post_headers date is not recent ' + | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |                                   headers[signed_header]) | 
					
						
							| 
									
										
										
										
											2019-08-23 11:30:37 +00:00
										 |  |  |                         return False | 
					
						
							| 
									
										
										
										
											2021-03-14 12:09:56 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |                 # add the capitalised header | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |                 if headers.get(signed_header_cap): | 
					
						
							|  |  |  |                     signed_header_list.append( | 
					
						
							|  |  |  |                         f'{signed_header}: {headers[signed_header_cap]}') | 
					
						
							|  |  |  |                 elif '-' in signed_header: | 
					
						
							| 
									
										
										
										
											2021-03-14 18:34:30 +00:00
										 |  |  |                     # my-header becomes My-header | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |                     signed_header_cap = signed_header.capitalize() | 
					
						
							|  |  |  |                     if headers.get(signed_header_cap): | 
					
						
							|  |  |  |                         signed_header_list.append( | 
					
						
							|  |  |  |                             f'{signed_header}: {headers[signed_header_cap]}') | 
					
						
							| 
									
										
										
										
											2019-06-28 18:55:29 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     # Now we have our header data digest | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |     signed_header_text = '\n'.join(signed_header_list) | 
					
						
							| 
									
										
										
										
											2021-09-14 21:22:49 +00:00
										 |  |  |     if debug: | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |         print('\nverify_post_headers signed_header_text:\n' + | 
					
						
							|  |  |  |               signed_header_text + '\nEND\n') | 
					
						
							| 
									
										
										
										
											2019-06-28 18:55:29 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     # Get the signature, verify with public key, return result | 
					
						
							| 
									
										
										
										
											2021-11-22 18:30:05 +00:00
										 |  |  |     if (headers.get('Signature-Input') and headers.get('Signature')) or \ | 
					
						
							|  |  |  |        (headers.get('signature-input') and headers.get('signature')): | 
					
						
							| 
									
										
										
										
											2021-02-22 11:13:27 +00:00
										 |  |  |         # https://tools.ietf.org/html/ | 
					
						
							| 
									
										
										
										
											2021-11-22 18:30:05 +00:00
										 |  |  |         # draft-ietf-httpbis-message-signatures | 
					
						
							|  |  |  |         if headers.get('Signature'): | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |             headers_sig = headers['Signature'] | 
					
						
							| 
									
										
										
										
											2021-11-22 18:30:05 +00:00
										 |  |  |         else: | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |             headers_sig = headers['signature'] | 
					
						
							| 
									
										
										
										
											2021-02-22 11:13:27 +00:00
										 |  |  |         # remove sig1=: | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |         if request_target_key + '=:' in headers_sig: | 
					
						
							|  |  |  |             headers_sig = headers_sig.split(request_target_key + '=:')[1] | 
					
						
							|  |  |  |             headers_sig = headers_sig[:len(headers_sig)-1] | 
					
						
							|  |  |  |         signature = base64.b64decode(headers_sig) | 
					
						
							| 
									
										
										
										
											2021-02-22 11:13:27 +00:00
										 |  |  |     else: | 
					
						
							|  |  |  |         # Original Mastodon signature | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |         headers_sig = signature_dict['signature'] | 
					
						
							|  |  |  |         signature = base64.b64decode(headers_sig) | 
					
						
							| 
									
										
										
										
											2021-11-22 18:30:05 +00:00
										 |  |  |     if debug: | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |         print('signature: ' + algorithm + ' ' + headers_sig) | 
					
						
							| 
									
										
										
										
											2019-06-28 18:55:29 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-22 11:52:55 +00:00
										 |  |  |     # log unusual signing algorithms | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |     if signature_dict.get('alg'): | 
					
						
							|  |  |  |         print('http signature algorithm: ' + signature_dict['alg']) | 
					
						
							| 
									
										
										
										
											2021-11-22 11:52:55 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-01 14:42:43 +00:00
										 |  |  |     # If extra signing algorithms need to be added then do it here | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |     if not signature_dict.get('alg'): | 
					
						
							| 
									
										
										
										
											2021-11-22 11:52:55 +00:00
										 |  |  |         alg = hazutils.Prehashed(hashes.SHA256()) | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |     elif (signature_dict['alg'] == 'rsa-sha256' or | 
					
						
							|  |  |  |           signature_dict['alg'] == 'rsa-v1_5-sha256' or | 
					
						
							|  |  |  |           signature_dict['alg'] == 'hs2019'): | 
					
						
							| 
									
										
										
										
											2021-11-22 11:52:55 +00:00
										 |  |  |         alg = hazutils.Prehashed(hashes.SHA256()) | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |     elif (signature_dict['alg'] == 'rsa-sha512' or | 
					
						
							|  |  |  |           signature_dict['alg'] == 'rsa-pss-sha512'): | 
					
						
							| 
									
										
										
										
											2021-11-22 11:52:55 +00:00
										 |  |  |         alg = hazutils.Prehashed(hashes.SHA512()) | 
					
						
							|  |  |  |     else: | 
					
						
							| 
									
										
										
										
											2021-09-01 14:42:43 +00:00
										 |  |  |         alg = hazutils.Prehashed(hashes.SHA256()) | 
					
						
							| 
									
										
										
										
											2021-11-22 11:52:55 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |     if digest_algorithm == 'rsa-sha256': | 
					
						
							|  |  |  |         header_digest = get_sha_256(signed_header_text.encode('ascii')) | 
					
						
							|  |  |  |     elif digest_algorithm == 'rsa-sha512': | 
					
						
							|  |  |  |         header_digest = get_sha_512(signed_header_text.encode('ascii')) | 
					
						
							| 
									
										
										
										
											2021-09-01 14:42:43 +00:00
										 |  |  |     else: | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |         print('Unknown http digest algorithm: ' + digest_algorithm) | 
					
						
							|  |  |  |         header_digest = '' | 
					
						
							|  |  |  |     padding_str = padding.PKCS1v15() | 
					
						
							| 
									
										
										
										
											2021-09-01 14:42:43 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-28 18:55:29 +00:00
										 |  |  |     try: | 
					
						
							| 
									
										
										
										
											2022-01-02 15:41:05 +00:00
										 |  |  |         pubkey.verify(signature, header_digest, padding_str, alg) | 
					
						
							| 
									
										
										
										
											2019-06-28 18:55:29 +00:00
										 |  |  |         return True | 
					
						
							| 
									
										
										
										
											2021-02-04 16:30:32 +00:00
										 |  |  |     except BaseException: | 
					
						
							| 
									
										
										
										
											2019-11-12 15:03:17 +00:00
										 |  |  |         if debug: | 
					
						
							| 
									
										
										
										
											2021-12-29 21:55:09 +00:00
										 |  |  |             print('EX: verify_post_headers pkcs1_15 verify failure') | 
					
						
							| 
									
										
										
										
											2021-09-14 21:22:49 +00:00
										 |  |  |     return False |