def sign(self, confirm=False): url, headers = self.merge_and_headers(self.url) try: check_result = self.simple_check( url, token=headers.get("X-TOKEN"), session=self.session, ) attestation = check_result["attestation"] errored = set(map(lambda x: x[0], check_result["errors"])) key_list = check_result["key_list"] key_hashes = set(map(lambda x: x[0], key_list)) except CheckError as exc: if not exc.key_list or not exc.errored or not exc.attestation: raise exc attestation = exc.attestation errored = set(map(lambda x: x[0], exc.errored)) key_list = exc.key_list key_hashes = set(map(lambda x: x[0], key_list)) if self.hash_key_public not in key_hashes: raise CheckError("Key is not part of chain") if not confirm: return (self.hash_key_public in errored, key_list) # change to update url postbox_update = merge_get_url(replace_action(self.url, "update/"), raw="embed", search="\x1etype=PostBox\x1e") # retrieve csrftoken response = self.session.get(postbox_update, headers=headers) graph = Graph() graph.parse(data=response.content, format="html") csrftoken = list( graph.objects(predicate=spkcgraph["csrftoken"]))[0].toPython() fields = dict( map( lambda x: (x[0].toPython(), x[1].toPython()), graph.query( """ SELECT DISTINCT ?fieldname ?value WHERE { ?base spkc:fieldname ?fieldname ; spkc:value ?value . } """, initNs={"spkc": spkcgraph}, ))) fields["signatures"] = [] own_signature = None for key in self.client_list: if key[0] != self.hash_key_public: signature = key[2] else: # currently only one priv key is supported signature = self.priv_key.sign( attestation, padding.PSS(mgf=padding.MGF1(self.hash_algo), salt_length=padding.PSS.MAX_LENGTH), self.hash_algo) if signature: fields["signatures"].append({ "hash": f"{self.hash_algo.name}={key[0].hex()}", "signature": "{}={}".format(self.hash_algo.name, base64.b64encode(signature).decode("ascii")) }) if key[0] == self.hash_key_public: own_signature = fields["signatures"][-1]["signature"] fields["signatures"] = json.dumps(fields["signatures"]) # update response = self.session.post(postbox_update, data=fields, headers={ "X-CSRFToken": csrftoken, **headers }) try: response.raise_for_status() graph = Graph() graph.parse(data=response.content, format="html") vals = set(extract_property(graph, "signature").values()) breakpoint() if own_signature not in vals: raise SrcException("could not update signature", own_signature) except SrcException as exc: raise exc except Exception as exc: raise SrcException("could not update signature", own_signature) from exc return (self.hash_key_public in errored, key_list)
def send(self, inp, receivers, headers=b"\n", mode=SendMethod.shared, aes_key=None): if not self.ok: raise NotReady() if isinstance(receivers, str): receivers = [receivers] if isinstance(inp, (bytes, memoryview)): inp = io.BytesIO(bytes(inp)) elif isinstance(inp, str): inp = io.BytesIO(inp.encode("utf8")) # 256 bit if not aes_key: aes_key = os.urandom(32) assert len(aes_key) == 32 nonce = os.urandom(13) fencryptor = Cipher(algorithms.AES(aes_key), modes.GCM(nonce), backend=default_backend()).encryptor() src_key_list = {} if mode == SendMethod.stealth: pass elif mode == SendMethod.private: enc = self.priv_key.public_key().encrypt( aes_key, padding.OAEP(mgf=padding.MGF1(algorithm=self.hash_algo), algorithm=self.hash_algo, label=None)) # encrypt decryption key src_key_list["%s=%s" % (self.hash_algo.name, self.pub_key_hash )] = base64.b64encode(enc).decode("ascii") elif mode == SendMethod.shared: for k in self.client_list: enc = k[1].encrypt( aes_key, padding.OAEP(mgf=padding.MGF1(algorithm=self.hash_algo), algorithm=self.hash_algo, label=None)) # encrypt decryption key src_key_list["%s=%s" % (self.hash_algo.name, k[0].hex() )] = base64.b64encode(enc).decode("ascii") else: raise NotImplementedError() # remove raw as we parse html message_create_url, src_headers = self.merge_and_headers( replace_action(self.component_url, "add/MessageContent/"), raw=None) response = self.session.get(message_create_url, headers=src_headers) try: response.raise_for_status() except Exception as exc: raise SrcException("retrieval csrftoken failed", response.text) from exc g = Graph() g.parse(data=response.content, format="html") csrftoken = list(g.objects(predicate=spkcgraph["csrftoken"]))[0] # create message object response = self.session.post( message_create_url, data={ "own_hash": self.hash_key_public, "key_list": json.dumps(src_key_list), "amount_tokens": len(receivers) }, headers={ "X-CSRFToken": csrftoken, **src_headers # only for src }, files={ "encrypted_content": EncryptedFile(fencryptor, inp, nonce, headers) }) try: response.raise_for_status() except Exception as exc: raise SrcException("Message creation failed", response.text) from exc if message_create_url == response.url: raise SrcException("Message creation failed", response.text) g = Graph() g.parse(data=response.content, format="html") fetch_url = list( map( lambda x: x.value, g.query(""" SELECT ?value WHERE { ?property spkc:name ?name ; spkc:value ?value . } """, initNs={"spkc": spkcgraph}, initBindings={ "name": Literal("fetch_url", datatype=XSD.string) }))) tokens = list( map( lambda x: x.value, g.query(""" SELECT ?value WHERE { ?property spkc:name ?name ; spkc:value ?value . } """, initNs={"spkc": spkcgraph}, initBindings={ "name": Literal("tokens", datatype=XSD.string) }))) if not fetch_url or not tokens: raise SrcException("Message creation failed", response.text) fetch_url = fetch_url[0].toPython() exceptions = [] final_fetch_urls = [] for receiver, token in zip(receivers, tokens): furl = merge_get_url(fetch_url, token=token.toPython()) try: self._send_dest(aes_key, furl, receiver) final_fetch_urls.append(furl) except Exception as exc: exceptions.append(exc) # for autoremoval simulate access self.session.get(furl) return exceptions, final_fetch_urls, aes_key
def receive(self, message_id, outfp=None, access_method=AccessMethod.view, extra_key_hashes=None, max_size=None): if not self.ok: raise NotReady() merged_url, headers = self.merge_and_headers(self.url, raw="embed") response = self.session.get(merge_get_url(self.url, raw="embed"), headers=headers) response.raise_for_status() graph = Graph() graph.parse(data=response.content, format="turtle") self.retrieve_missing(graph, merged_url) result = list( graph.query( """ SELECT DISTINCT ?base ?hash_algorithm ?type WHERE { ?base a <https://spkcspider.net/static/schemes/spkcgraph#spkc:Content> ; spkc:type ?type ; spkc:properties ?propalg , ?propid . ?propid spkc:name ?idname ; spkc:value ?idvalue . ?propalg spkc:name ?algname ; spkc:value ?hash_algorithm . } """, # noqa E501 initNs={"spkc": spkcgraph}, initBindings={ "idvalue": Literal(message_id), "algname": Literal("hash_algorithm", datatype=XSD.string), "idname": Literal("id", datatype=XSD.string), })) if not result or result[0].type.toPython() not in { "WebReference", "MessageContent" }: raise SrcException("No Message") # every object has it's own copy of the hash algorithm, used hash_algo = getattr(hashes, result[0].hash_algorithm.upper())() if not outfp: outfp = tempfile.TempFile() if hash_algo == self.hash_algo: pub_key_hashalg = "%s=%s" % (hash_algo.name, self.hash_key_public.hex()) else: digest = hashes.Hash(hash_algo, backend=default_backend()) digest.update(self.pem_key_public) pub_key_hashalg = "%s=%s" % (hash_algo.name, digest.finalize().hex()) key_hashes = list() if extra_key_hashes: extra_key_hashes = set(extra_key_hashes) extra_key_hashes.discard(pub_key_hashalg) for key in self.client_list: if key[0] in extra_key_hashes: if hash_algo == self.hash_algo: key_hashalg = "%s=%s" % (hash_algo.name, key[0]) else: digest = hashes.Hash(hash_algo, backend=default_backend()) digest.update(key[1]) key_hashalg = "%s=%s" % (hash_algo.name, digest.finalize().hex()) key_hashes.append(key_hashalg) if access_method == AccessMethod.view: key_hashes.append(pub_key_hashalg) retrieve_url, headers = self.merge_and_headers( replace_action( result[0].base, "bypass/" if (access_method == AccessMethod.bypass) else "message/")) data = {} if access_method != AccessMethod.bypass: data.update({"max_size": max_size or "", "keyhash": key_hashes}) response = self.session.post(retrieve_url, stream=True, headers=headers, data=data) try: response.raise_for_status() except Exception as exc: raise DestException("Message retrieval failed", response.text) from exc key_list = json.loads(response.headers["X-KEYLIST"]) key = key_list.get(pub_key_hashalg, None) if not key: raise WrongRecipient("message not for me") decrypted_key = self.priv_key.decrypt( base64.b64decode(key), padding.OAEP(mgf=padding.MGF1(algorithm=hash_algo), algorithm=hash_algo, label=None)) headblock = b"" fdecryptor = None eparser = emailparser.BytesFeedParser(policy=policy.default) headers = None for chunk in response.iter_content(chunk_size=256): blob = None if not fdecryptor: headblock = b"%b%b" % (headblock, chunk) if b"\0" in headblock: nonce, headblock = headblock.split(b"\0", 1) nonce = base64.b64decode(nonce) fdecryptor = Cipher(algorithms.AES(decrypted_key), modes.GCM(nonce), backend=default_backend()).decryptor() blob = fdecryptor.update(headblock[:-16]) headblock = headblock[-16:] else: continue else: blob = fdecryptor.update(b"%b%b" % (headblock, chunk[:-16])) headblock = chunk[-16:] if not headers: if b"\n\n" not in blob: eparser.feed(blob) continue headersrest, blob = blob.split(b"\n\n", 1) eparser.feed(headersrest) headers = eparser.close() # check what to do t = headers.get("SPKC-Type", MessageType.email) if t == MessageType.email: outfp.write( headers.as_bytes(unixfrom=True, policy=policy.SMTP)) outfp.write(blob) outfp.write(fdecryptor.finalize_with_tag(headblock)) return outfp, headers, decrypted_key
def _send_dest(self, aes_key, fetch_url, dest): dest_url = merge_get_url(dest, raw="embed", search="\x1etype=PostBox\x1e") response_dest = self.session.get(dest_url, timeout=60) try: response_dest.raise_for_status() g_dest = Graph() g_dest.parse(data=response_dest.content, format="turtle") self.retrieve_missing(g_dest, dest_url) except Exception as exc: raise DestException("postbox retrieval failed") from exc dest_postboxes = get_postboxes(g_dest) if len(dest_postboxes) != 1: raise DestException("No postbox found/more than one found") dest_postbox_url, dest_options = next(iter(dest_postboxes.items())) webref_url = replace_action(dest_postbox_url, "push_webref/") attestation = dest_options["attestation"] bdomain = dest_postbox_url.split("?", 1)[0] result_dest, errors, dest_keys = self.attestation_checker.check( bdomain, map(lambda x: (x["key"], x["signature"]), dest_options["signatures"].values()), attestation=attestation, algo=dest_options["hash_algorithm"], auto_add=True) if result_dest == AttestationResult.domain_unknown: logger.info("add domain: %s", bdomain) self.attestation_checker.attestation.add( bdomain, dest_keys, algo=dest_options["hash_algorithm"], embed=True) elif result_dest == AttestationResult.error: if len(dest_keys) == 0: raise DestException("No keys found") raise DestSecurityException("dest contains invalid keys", errors) dest_key_list = {} for k in dest_keys: enc = k[1].encrypt( aes_key, padding.OAEP( mgf=padding.MGF1(algorithm=dest_options["hash_algorithm"]), algorithm=dest_options["hash_algorithm"], label=None)) # encrypt decryption key dest_key_list["%s=%s" % (dest_options["hash_algorithm"].name, k[0].hex())] = base64.b64encode(enc).decode("ascii") try: response_dest = self.session.post(webref_url, data={ "url": fetch_url, "key_list": json.dumps(dest_key_list) }, timeout=60) response_dest.raise_for_status() except Exception as exc: raise DestException("post webref failed") from exc
def update_content(self, inp, newob, update_url=None, x_token=None, timeout=60, session=None, hash_algo=None): """ [summary] Arguments: inp {url,graph,keys} -- [description] update_url {[type]} -- [description] newob {[type]} -- [description] Keyword Arguments: x_token {[type]} -- [description] (default: {None}) timeout {int} -- [description] (default: {60}) session {[type]} -- [description] (default: {None}) hash_algo {} -- (default: {None}) """ if not session: session = requests.Session() if isinstance(inp, (list, tuple)): # be careful to not include thirdparty keys if incompatible keys = set(inp) assert hash_algo and update_url else: graph, update_url = self.retrieve_missing( inp, ["\x1etype=PublicKey\x1e"], x_token=x_token, timeout=timeout, session=session) info = extract_property(graph) allow_thirdparty = "\x1eunlisted\x1e" not in info keys, hash_algo = map_keys(graph, hash_algo=hash_algo or None) if not allow_thirdparty: keys = filter(lambda x: x["thirdparty"], keys.values()) keys = list(map(lambda x: x["pubkey"], keys)) update_url = replace_action(update_url, "update/") update_graph = self.retrieve_missing(update_url, [], x_token=None, timeout=timeout, session=session) csrftoken = list(update_graph.objects( predicate=spkcgraph["csrftoken"]))[0].toPython() body = dict(newob["unencrypted"]["values"]) files = dict(newob["unencrypted"]["files"]) nonce = os.urandom(13) nonce_b64 = base64.b64encode(nonce) aes_key = newob.get("aes_key") or os.urandom(32) nonce_name = newob.get("nonce_name") key_list_name = newob.get("key_list_name", "key_list") body[nonce_name] = nonce cipher = Cipher(algorithms.AES(aes_key), modes.GCM(nonce), backend=default_backend()) for k, item in newob["encrypted"]["values"].items(): assert nonce_name != k e = cipher.encryptor() if not isinstance(item, (list, set, tuple)): item = [item] body[k] = [] for el in item: if isinstance(el, str): el = el.encode("utf8") el = e.update(el) if not nonce_name: el = b"%s\0%s" % (nonce_b64, el) body[k].append(el) for k, item in newob["encrypted"]["files"].items(): assert nonce_name != k files[k] = EncryptedFile(cipher.encryptor(), item, nonce=nonce if nonce_name else None) body[key_list_name] = {} for k in keys: enc = k[1].encrypt( aes_key, padding.OAEP(mgf=padding.MGF1(algorithm=hash_algo), algorithm=hash_algo, label=None)) # encrypt decryption key body[key_list_name]["%s=%s" % (hash_algo.name, k[0].hex() )] = base64.b64encode(enc).decode("ascii") body[key_list_name] = json.dumps(body[key_list_name]) # create message object response = session.post(update_url, data=body, headers={ "X-CSRFToken": csrftoken, }, files=files) try: response.raise_for_status() except Exception as exc: raise HttpError("Message creation failed", response.text) from exc
def action_send(argv, priv_key, pub_key_hash, src_keys, session, g_src): postboxes = get_postboxes(g_src) if len(postboxes) != 1: parser.exit(1, "Source cannot create messages, logged in?") component_uriref, src_options = next(postboxes.items()) dest_url = merge_get_url( argv.dest, raw="embed", search="\x1etype=PostBox\x1e" ) response_dest = session.get(dest_url) if not response_dest.ok: logger.info("Dest returned error: %s", response_dest.text) parser.exit(1, "retrieval failed, invalid url?\n") g_dest = Graph() g_dest.parse(data=response_dest.content, format="turtle") for page in get_pages(g_dest)[1]: with session.get( merge_get_url(dest_url, page=page) ) as response: response.raise_for_status() g_dest.parse(data=response.content, format="turtle") dest_postboxes = get_postboxes(g_dest) if len(dest_postboxes) != 1: parser.exit(1, "Dest has no/too many postboxes") dest_postbox_url, dest_options = next(dest_postboxes.items()) webref_url = replace_action(dest_postbox_url, "push_webref/") dest_hash_algo = dest_options["hash_algorithm"] attestation = dest_options["attestation"] bdomain = dest_postbox_url.split("?", 1)[0] result_dest, _, dest_keys = argv.attestation.check( bdomain, map( lambda x: (x["key"], x["signature"]), dest_options["signatures"].values() ), attestation=attestation, algo=dest_hash_algo ) if result_dest == AttestationResult.domain_unknown: logger.info("add domain: %s", bdomain) argv.attestation.add( bdomain, dest_keys, algo=dest_hash_algo ) elif result_dest == AttestationResult.error: logger.critical("Dest base url contains invalid keys.") parser.exit(1, "dest contains invalid keys\n") # 256 bit aes_key = os.urandom(32) nonce = os.urandom(13) fencryptor = Cipher( algorithms.AES(aes_key), modes.GCM(nonce), backend=default_backend() ).encryptor() src_key_list = {} dest_key_list = {} if argv.stealth: pass elif src_options["shared"]: for k in src_keys: enc = k[1].encrypt( aes_key, padding.OAEP( mgf=padding.MGF1(algorithm=argv.src_hash_algo), algorithm=argv.src_hash_algo, label=None ) ) # encrypt decryption key src_key_list[ "%s=%s" % (argv.src_hash_algo.name, k[0].hex()) ] = base64.b64encode(enc).decode("ascii") else: enc = priv_key.public_key().encrypt( aes_key, padding.OAEP( mgf=padding.MGF1(algorithm=argv.src_hash_algo), algorithm=argv.src_hash_algo, label=None ) ) # encrypt decryption key src_key_list[ "%s=%s" % (argv.src_hash_algo.name, pub_key_hash) ] = base64.b64encode(enc).decode("ascii") for k in dest_keys: enc = k[1].encrypt( aes_key, padding.OAEP( mgf=padding.MGF1(algorithm=dest_hash_algo), algorithm=dest_hash_algo, label=None ) ) # encrypt decryption key dest_key_list[ "%s=%s" % (dest_hash_algo.name, k[0].hex()) ] = base64.b64encode(enc).decode("ascii") headers = b"SPKC-Type: %b\n" % MessageType.file # remove raw as we parse html message_create_url = merge_get_url( replace_action(str(component_uriref), "add/MessageContent/"), raw=None ) response = session.get( message_create_url, headers={ "X-TOKEN": argv.token } ) if not response.ok: logger.error("retrieval csrftoken failed: %s", response.text) parser.exit(1, "retrieval csrftoken failed: %s" % response.text) g = Graph() g.parse(data=response.content, format="html") csrftoken = list(g.objects(predicate=spkcgraph["csrftoken"]))[0] # create message object response = session.post( message_create_url, data={ "own_hash": pub_key_hash, "key_list": json.dumps(src_key_list), "amount_tokens": 1 }, headers={ "X-CSRFToken": csrftoken, "X-TOKEN": argv.token # only for src }, files={ "encrypted_content": EncryptedFile( fencryptor, argv.file, nonce, headers ) } ) if not response.ok or message_create_url == response.url: logger.error("Message creation failed: %s", response.text) parser.exit(1, "Message creation failed: %s" % response.text) g = Graph() g.parse(data=response.content, format="html") fetch_url = list(map(lambda x: x.value, g.query( """ SELECT ?value WHERE { ?property spkc:name ?name ; spkc:value ?value . } """, initNs={"spkc": spkcgraph}, initBindings={ "name": Literal( "fetch_url", datatype=XSD.string ) } ))) tokens = list(map(lambda x: x.value, g.query( """ SELECT ?value WHERE { ?property spkc:name ?name ; spkc:value ?value . } """, initNs={"spkc": spkcgraph}, initBindings={ "name": Literal( "tokens", datatype=XSD.string ) } ))) if not fetch_url or not tokens: logger.error("Message creation failed: %s", response.text) parser.exit(1, "Message creation failed") # extract url response_dest = session.post( webref_url, data={ "url": merge_get_url(fetch_url[0], token=str(tokens[0])), "key_list": json.dumps(dest_key_list) } ) if not response_dest.ok: logger.error("Sending message failed: %s", response_dest.text) parser.exit(1, "Sending message failed")
def action_check(argv, priv_key, pub_key_hash, session, g): src = {} postbox = None for i in g.query( """ SELECT DISTINCT ?postbox ?key_base ?key_base_prop ?key_name ?key_value WHERE { ?postbox spkc:properties ?postbox_prop . ?postbox_prop spkc:name ?postbox_prop_name . ?postbox_prop spkc:value ?key_base . ?key_base spkc:properties ?key_base_prop . ?key_base_prop spkc:name ?key_name ; spkc:value ?key_value . } """, initNs={"spkc": spkcgraph}, initBindings={ "postbox_prop_name": Literal( "signatures", datatype=XSD.string ) } ): postbox = i.postbox src.setdefault(str(i.key_base), {}) src[str(i.key_base)][str(i.key_name)] = i.key_value src_activator_value, errored, key_list = argv.attestation.check_signatures( map( lambda x: (x["key"], x["signature"]), src.values() ), algo=argv.src_hash_algo ) tmp = list(g.query( """ SELECT DISTINCT ?value WHERE { ?base spkc:name ?name . ?base spkc:value ?value . } """, initNs={"spkc": spkcgraph}, initBindings={ "name": Literal( "attestation", datatype=XSD.string ), } )) if tmp[0][0].toPython() != src_activator_value: return "activator doesn't match shown activator" if errored: pub_key_hash_bin = bytes.fromhex(pub_key_hash) can_fix = set(filter( lambda x: x == pub_key_hash_bin, map(lambda x: x[0], errored) )) if not argv.fix: if can_fix and postbox: print("Can fix signature") else: if not can_fix or not postbox: return ", ".join(map(lambda x: x.hex(), errored)) postbox_update = replace_action( postbox, "update/" ) # retrieve token response = session.get( postbox_update, headers={ "X-TOKEN": argv.token } ) g_token = Graph() g_token.parse(data=response.content, format="html") csrftoken = list(g_token.objects( predicate=spkcgraph["csrftoken"]) )[0].toPython() fields = dict(map( lambda x: (x[0].toPython(), x[1].toPython()), g_token.query( """ SELECT DISTINCT ?fieldname ?value WHERE { ?base spkc:fieldname ?fieldname . ?base spkc:value ?value . } """, initNs={"spkc": spkcgraph}, ) )) fields["signatures"] = [] for key in key_list: if key[0] not in can_fix: signature = key[2] else: # currently only one priv key is supported signature = priv_key.sign( src_activator_value, padding.PSS( mgf=padding.MGF1(argv.src_hash_algo), salt_length=padding.PSS.MAX_LENGTH ), argv.src_hash_algo ) if signature: fields["signatures"].append( { "hash": f"{argv.src_hash_algo.name}={key[0].hex()}", "signature": "{}={}".format( argv.src_hash_algo.name, base64.b64encode( signature ).decode("ascii") ) } ) fields["signatures"] = json.dumps(fields["signatures"]) # update response = session.post( postbox_update, data=fields, headers={ "X-CSRFToken": csrftoken, "X-TOKEN": argv.token } ) if not response.ok: logger.error("Repair failed: %s", response.text) return ", ".join(map(lambda x: x[0].hex(), errored)) logger.info("Repair succeeded") errored = list(filter(lambda x: x not in can_fix, errored)) if errored == 0: return True return ", ".join(map(lambda x: x[0].hex(), errored)) return True
def action_view(argv, priv_key, pem_public, own_url, session, g_message): if argv.message_id is not None: assert isinstance(argv.message_id, int) result = list(g_message.query( """ SELECT DISTINCT ?base ?hash_algorithm ?type WHERE { ?base a <https://spkcspider.net/static/schemes/spkcgraph#spkc:Content> ; spkc:type ?type ; spkc:properties ?propalg , ?propid . ?propid spkc:name ?idname ; spkc:value ?idvalue . ?propalg spkc:name ?algname ; spkc:value ?hash_algorithm . } """, # noqa E501 initNs={"spkc": spkcgraph}, initBindings={ "idvalue": Literal(argv.message_id), "algname": Literal( "hash_algorithm", datatype=XSD.string ), "idname": Literal( "id", datatype=XSD.string ), } )) if not result or result[0].type.toPython() not in { "WebReference", "MessageContent" }: parser.exit(0, "message not found\n") pub_key_hasher = getattr( hashes, result[0].hash_algorithm.upper() )() digest = hashes.Hash(pub_key_hasher, backend=default_backend()) digest.update(pem_public) pub_key_hashalg = "%s=%s" % ( pub_key_hasher.name, digest.finalize().hex() ) retrieve_url = merge_get_url( replace_action( result[0].base, "message/" ) ) data = { "max_size": argv.max } if argv.action == "view": data["keyhash"] = pub_key_hashalg response = session.post( retrieve_url, stream=True, headers={ "X-TOKEN": argv.token }, data=data ) if not response.ok: logger.info("Message retrieval failed: %s", response.text) parser.exit(0, "message could not be fetched\n") key_list = json.loads(response.headers["X-KEYLIST"]) key = key_list.get(pub_key_hashalg, None) if not key: parser.exit(0, "message not for me\n") decrypted_key = priv_key.decrypt( base64.b64decode(key), padding.OAEP( mgf=padding.MGF1(algorithm=argv.src_hash_algo), algorithm=argv.src_hash_algo, label=None ) ) headblock = b"" fdecryptor = None eparser = emailparser.BytesFeedParser(policy=policy.default) headers = None for chunk in response.iter_content(chunk_size=256): blob = None if not fdecryptor: headblock = b"%b%b" % (headblock, chunk) if b"\0" in headblock: nonce, headblock = headblock.split(b"\0", 1) nonce = base64.b64decode(nonce) fdecryptor = Cipher( algorithms.AES(decrypted_key), modes.GCM(nonce), backend=default_backend() ).decryptor() blob = fdecryptor.update(headblock[:-16]) headblock = headblock[-16:] else: continue else: blob = fdecryptor.update( b"%b%b" % (headblock, chunk[:-16]) ) headblock = chunk[-16:] if not headers: if b"\n\n" not in blob: eparser.feed(blob) continue headersrest, blob = blob.split(b"\n\n", 1) eparser.feed(headersrest) headers = eparser.close() # check what to do t = headers.get("SPKC-Type", MessageType.email) if t == MessageType.email: argv.file.write(headers.as_bytes( unixfrom=True, policy=policy.SMTP )) argv.file.write(blob) argv.file.write(fdecryptor.finalize_with_tag(headblock)) else: queried_webrefs = {} queried_messages = {} for i in g_message.query( """ SELECT DISTINCT ?base ?idvalue ?namevalue ?type WHERE { ?base a <https://spkcspider.net/static/schemes/spkcgraph#spkc:Content> ; spkc:type ?type ; spkc:properties ?propname , ?propid . ?propid spkc:name ?idname ; spkc:value ?idvalue . ?propname spkc:name ?namename ; spkc:value ?namevalue . } """, # noqa E501 initNs={"spkc": spkcgraph}, initBindings={ "idname": Literal( "id", datatype=XSD.string ), "namename": Literal( "name", datatype=XSD.string ), } ): if i.type.toPython() == "WebReference": queried = queried_webrefs elif i.type.toPython() == "MessageContent": queried = queried_messages else: continue queried.setdefault(str(i.base), {}) queried[str(i.base)]["id"] = i.idvalue queried[str(i.base)]["name"] = i.namevalue print("Received Messages:") for i in sorted(queried_webrefs.values(), key=lambda x: x["id"]): print(i["id"], i["name"]) print("Own Messages:") for i in sorted(queried_messages.values(), key=lambda x: x["id"]): print(i["id"], i["name"])