def send_fingerprint_mail(self, my_DHIs, my_id, other_DHIs, other_id): self.logger.info("sending fingerprint to %s" % my_id) my_segs = my_id.split("@", 1) my_name = my_segs[0] my_host = my_segs[1] other_segs = other_id.split('@', 1) other_name = other_segs[0] other_host = other_segs[1] drunk = GetFPrintMail(my_DHIs, my_name, other_DHIs, other_name) mfp = MIMEText("<html><body><p>Hello %(my_name)s,</p>\n\n<p>Your e-mail conversation with %(other_id)s was encrypted via the awesome Axonaut e-mail encryption service. To verify the identity of %(other_name)s, compare the following fingerprints with your partner.</p>\n\n<p>They should match with the corresponding fingerprints we sent to %(other_name)s otherwise you might be a victim of an active attack!</p>\n\n<pre>%(drunk)s</pre>\n\n<p>Best regards,<br/>\nyour friendly Axonauts</p>\n</body></html>" % {"my_name": my_name, "other_name": other_name, "other_id": other_id, "drunk": drunk}, "html") mfp["From"] = "mailadmin@%s" % my_host mfp["To"] = my_id mfp["Subject"] = "Axonaut Key-Fingerprints for %s" % other_id sendmimemail(mfp, mfp["From"], my_id)
def encrypt_and_send_mail(self, mail, axolotl): self.logger.info("encrypting message %s" % mail["id"]) # Assemble the message that we shall encrypt. msg = MIMEText(mail["body"]) for k, v in mail["headers"]: msg[k] = v raw = msg.as_string() self.logger.debug(raw) # Encrypt the message and wrap it up in a new envelope, then send it to # the recipient. encoded = binascii.b2a_base64(axolotl.encrypt(raw)) menv = MIMEText(encoded) menv["From"] = mail["from"] menv["To"] = mail["to"] menv["Subject"] = "Axolotl-encrypted Message" menv["Content-Type"] = "message/x-axonaut" sendmimemail(menv, mail["from"], mail["to"])
def process_inbound(self, in_mail): """ Function to decrypt an incoming mail. Responds to a key request if necessary. """ my_id = in_mail["to"] other_id = in_mail["from"] self.logger.info("inbound mail from %s to %s" % (my_id, other_id)) content_type = None msg_id = None for k, v in in_mail["headers"]: kl = k.lower() if kl == "content-type": content_type = v.lower() if kl == "message-id": msg_id = v in_mail["id"] = msg_id self.logger.debug("content_type = %s" % content_type) self.logger.debug("message_id = %s" % in_mail["id"]) conv_hash = hashlib.sha1(my_id + ":" + other_id).hexdigest() handshake_path = self.handshakes_dir + "/" + conv_hash queue_path = self.queues_dir + "/" + conv_hash # Encrypted messages targeted at a local user need to be decrypted # before they are relayed. The encryption status is indicated by a # special content type. Two cases are possible: Either the Axolotl # handshake has been completed, in which case loadState() succeeds and # the message may be decrypted normally. Or the handshake is in progress # or has not been started at all, in which case no decryption is # possible. In the latter case, we might want to inform the sender about # the situation. if content_type == "message/x-axonaut": try: a = self.makeAxolotl(my_id) a.loadState(my_id, other_id) self.decrypt_and_send_mail(in_mail, a) a.saveState() except: self.logger.warning("unable to decrypt message: %s" % in_mail["id"]) msg = MIMEMultipart() msg["Subject"] = "Message cannot be decrypted, return to sender" msg["From"] = my_id msg["To"] = other_id msg_txt = MIMEText("The attached message was received by the sender, but cannot be decrypted. This indicates that no secure Axolotl conversation has been established beforehand.", "plain") raw_msg = MIMEText(in_mail["body"]) for k, v in in_mail["headers"]: raw_msg[k] = v raw = raw_msg.as_string() mret = MIMEText(raw) mret["Content-Type"] = "message/rfc822" mret["Content-Disposition"] = "attachment" msg.attach(msg_txt) msg.attach(mret) sendmimemail(msg, my_id, other_id) # If we receive a key response, we have initiated the key exchange and # may now finish it with the information provided by our peer. To ensure # secrecy, truncate the temporary pre-keys that were stored for the # handshake to zero. elif content_type == "message/x-axonaut+keyrsp": self.logger.debug("received keyrsp from %s" % other_id) a = self.makeAxolotl(my_id) hs = pickle.load(open(handshake_path, "r")) a.state = hs["state"] a.handshakePKey = hs["pub"] a.handshakeKey = hs["priv"] segments = in_mail["body"].split('\n') DHIs = binascii.a2b_base64(segments[0].strip()) DHRs = binascii.a2b_base64(segments[1].strip()) if segments[1].strip() != "none" else None handshakePKey = binascii.a2b_base64(segments[2].strip()) a.initState(other_id, DHIs, handshakePKey, DHRs, verify=False) # This part is simply informing the user on our end with the hashes # of both identity keys. These must be compared through a secure # second channel of communication to ensure all security properties. self.send_fingerprint_mail(a.state['DHIs'], my_id, DHIs, other_id) if os.path.isdir(queue_path): for d in os.listdir(queue_path): msg = pickle.load(open(queue_path + "/" + d)) self.encrypt_and_send_mail(msg, a) shutil.rmtree(queue_path) a.saveState(); open(handshake_path, "w").truncate() # If we receive a key request, we are able to finalize the key exchange # and initialize the Axolotl state. In addition to that, we have to mark # this combination of sender/receiver as established by touching the # corresponding file in the handshakes directory. elif content_type == "message/x-axonaut+keyreq": try: segments = in_mail["body"].split('\n') DHIs = binascii.a2b_base64(segments[0].strip()) DHRs = binascii.a2b_base64(segments[1].strip()) handshakePKey = binascii.a2b_base64(segments[2].strip()) self.logger.debug("received keyreq from %s" % other_id) except Exception as e: self.logger.exception("invalid keyreq received: %s" % e) return try: a = self.makeAxolotl(my_id) a.loadState(my_id, other_id) self.logger.warning("received keyreq event though already exchanged") except: a = self.makeAxolotl(my_id) a.initState(other_id, DHIs, handshakePKey, DHRs, verify=False) # This part is simply informing the user on our end with the # hashes of both identity keys. These must be compared through a # secure second channel of communication to ensure all security # properties. self.send_fingerprint_mail(a.state['DHIs'], my_id, DHIs, other_id) out_mail_body = "%s\n%s\n%s" % ( binascii.b2a_base64(a.state["DHIs"]).strip(), binascii.b2a_base64(a.state["DHRs"]).strip() if a.state["DHRs"] != None else "none", binascii.b2a_base64(a.handshakePKey).strip()) self.logger.info("sending keyrsp to %s" % other_id) krsp_msg = MIMEText(out_mail_body) krsp_msg["From"] = my_id krsp_msg["To"] = other_id krsp_msg["Subject"] = "Axolotl Key Response" krsp_msg["Content-Type"] = "message/x-axonaut+keyrsp" sendmimemail(krsp_msg, my_id, other_id) a.saveState() os.mknod(handshake_path) return
def process_outbound(self, in_mail): """ Function to encrypt an outgoing mail. Also makes a new key request if the partner key is not available. """ my_id = in_mail["from"] other_id = in_mail["to"] self.logger.info("outbound mail from %s to %s" % (my_id, other_id)) content_type = None msg_id = None for k, v in in_mail["headers"]: kl = k.lower() if kl == "content-type": content_type = v.lower() if kl == "message-id": msg_id = v in_mail["id"] = msg_id self.logger.debug("content_type = %s" % content_type) self.logger.debug("message_id = %s" % in_mail["id"]) conv_hash = hashlib.sha1(my_id + ":" + other_id).hexdigest() handshake_path = self.handshakes_dir + "/" + conv_hash queue_path = self.queues_dir + "/" + conv_hash # Encrypt all messages that are not already encrypted. if content_type != "message/x-axonaut": # Figure out the next queue file name in case we need to keep the # message around for later delivery. i = 1; path = None while path == None or os.path.exists(path): path = "%s/%04i" % (queue_path, i) i = i + 1 # Check whether we already have established a handshake. If this is # not the case, send a keyreq message to the recipient that contains # our half of the key exchange information. The message that was # originally intended for dispatch is stored in a queue for later # delivery as soon as the encryption keys have been negotiated. if not os.path.exists(handshake_path): self.logger.debug("sending keyreq to %s" % other_id) a = self.makeAxolotl(my_id) out_mail_body = "%s\n%s\n%s" % ( binascii.b2a_base64(a.state["DHIs"]).strip(), binascii.b2a_base64(a.state["DHRs"]).strip(), binascii.b2a_base64(a.handshakePKey).strip()) self.logger.debug("queuing message %s" % in_mail["id"]) if not os.path.exists(queue_path): os.makedirs(queue_path) pickle.dump(in_mail, open(path, "w")) kreq_msg = MIMEText(out_mail_body) kreq_msg["From"] = my_id kreq_msg["To"] = other_id kreq_msg["Subject"] = "Axolotl Key Request" kreq_msg["Content-Type"] = "message/x-axonaut+keyreq" sendmimemail(kreq_msg, my_id, other_id) # The following is an ugly hack: pyaxo expects an Axolotl object # to be created and the keys negotiated before the object is # destroyed and the state saved to disk. This does not work in # our case, since we need to wait for a potentially long period # of time until we can finalize the handshake. Therefore we # serialize the Axolotl state and especially the handshake pre- # keys to disk, such that we may resume the handshake at a later # stage. pickle.dump({ "state": a.state, "pub": a.handshakePKey, "priv": a.handshakeKey }, open(handshake_path, "w")) else: # If we've come this far, the handshake has been initiated. That # is, at least the keyreq message has been sent to the peer. Two # things may now happen: Either the Axolotl conversation is # already initialized, in which case loadState() will succeed # and we may continue to encrypt our message. Or the state has # not yet been initialized, and an exception is thrown. In case # of the exception we store the message in the queue for later # encryption, as soon as the handshake terminates. try: a = self.makeAxolotl(my_id) a.loadState(my_id, other_id) self.encrypt_and_send_mail(in_mail, a) a.saveState() except: self.logger.info("queuing message %s (key response pending)" % in_mail["id"]) if not os.path.exists(queue_path): os.makedirs(queue_path) pickle.dump(in_mail, open(path, "w"))