#!/usr/bin/env python3 import json import gnupg """ Now rewrite all this doodoo into a proper class """ gnupg_homedir = "./gnupg_home/" keyring_file = "pubring.kbx" json_filename = 'json_stuff.txt' gpg = gnupg.GPG(gnupghome=gnupg_homedir, keyring=gnupg_homedir+keyring_file) def get_fingerprint_by_email(email): for key in gpg.list_keys(): if email in key["uids"][0]: return key["fingerprint"] return None def get_keyid_by_email(email): for key in gpg.list_keys(): if email in key["uids"][0]: return key["keyid"] return None def read_gpg_file(filename: str, verify: bool = None) -> dict: """ Opposite function of write_gpg_file() splits message, signature and hash from input file. reads file line by line and uses a nasty state machine looking for magic strings to determine what goes where. :param filename: path to file to be read, usually ends with .pgp :param verify: Should :return: dict -> {"json": str, "pgp_signature": str, "hash": str, "verified": bool} json = json payload form file as string, can be loaded to dict with json.loads(read_gpg_file(filename)["json"]) pgp_signature = string representation of signature, use this to verify signature. hash = hash function used. Might be useful, might not. verified = Whenever signature verification was successful """ # Magic strings to look for gpg_msg_start = "-----BEGIN PGP SIGNED MESSAGE-----\n" gpg_sig_start = "-----BEGIN PGP SIGNATURE-----\n" gpg_sig_end = "-----END PGP SIGNATURE-----\n" # Empty strings to prepare return. complete_msg = "" out_string = "" hash = "" out_signature = "" verified = None # Behold... the mighty state machine msg_start_found = False sig_start_found = False with open(filename, "r") as f: for line in f.readlines(): complete_msg += line if not msg_start_found: if line == gpg_msg_start: msg_start_found = True continue if line.startswith("Hash"): hash += line.split(" ")[1].rstrip() # Remove unused whitespace continue if line == gpg_sig_start: sig_start_found = True continue if not sig_start_found: out_string += line.lstrip().rstrip() # Remove unused whitespace if sig_start_found and msg_start_found and line != gpg_sig_end: out_signature += line.lstrip().rstrip() # Remove unused whitespace if line == gpg_sig_end: break if verify: verified = gpg.verify(complete_msg).status else: verified = None return {"json": out_string, "pgp_signature": out_signature, "hash": hash, "verified": verified} def write_gpg_file(payload_to_sign: dict, filename: str, fingerprint=None) -> bool: """ :param payload_to_sign: dictonary that should be signed and written to disk. :param filename: target filename WITHOUT .gpg extention! :param fingerprint: OPTIONAL fingerprint (string) (Not yet implemented) :return: True if any data was written, else prints error message and returns False """ if type(payload_to_sign) != dict: print("Could not write {}, invalid payload. \n" "Ensure to only pass object type: \"dict\"".format(filename)) return False with open(filename+".gpg", "w") as f: json.dump(payload_to_sign, f) with open(filename+".gpg", "rb") as f: signed_string = gpg.sign_file(f, keyid=fingerprint) print("Signing with {}".format(fingerprint)) with open(filename+".gpg", "w") as f: if f.write(str(signed_string)): return True else: print("Could not write {}, 0 bytes written!\n" "This message should be impossible to reach, well done.".format(filename)) return False def verify_gpg_json(payload: str, signature: str) -> bool: """ Placeholder function to verify if pgp signed json is valid. :param payload: Complete pgp message with headers and signature :param signature: :return: """ return True