p≡p for Python
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

131 lines
4.7 KiB

#/!usr/bin/env python3
# Copyleft 2020, p≡p Security
# Copyleft 2020, Hartmut Goebel <h.goebel@crazy-compilers.com>
# This file is under GNU General Public License 3.0
This examples shows how to transport pEp messages within a
simple XML based message format which is capable for attachments.
pEp message elements loosely follow the "pEp for XML specification" and
IMPORTANT: In this example, no error checking is done. Neither is
the "pEp for XML" specification enforced.
Structure of the simple XML based message:
<body>text of the messgage (must not be XML)</bod>
<attachment>textual data</attachment>
from lxml import etree
import email.utils
import pEp
__all__ = ["serialize_pEp_message", "parse_serialized_message"]
CT2TAG = {
'application/pgp-keys': 'keydata',
'application/pEp.sync': 'sync',
'application/pEp.distribution': 'distribution',
'application/pgp-signature': 'signature',
TAG2CT = dict((v,k)for (k,v) in CT2TAG.items())
PEP_NAMESPACE = "https://pEp.software/ns/pEp-1.0.xsd"
def serialize_pEp_message(msg):
root = etree.Element("msg", nsmap=NSMAP)
etree.SubElement(root, "to").text = str(msg.to[0])
etree.SubElement(root, "from").text = str(msg.from_)
if msg.enc_format == UNENCRYPTED:
# unencrypted
etree.SubElement(root, "body").text = msg.longmsg
attachments = etree.SubElement(root, "attachments")
# FIXME: Namespace
attachments = etree.SubElement(attachments,
PEP_NS + "attachments",
# TODO: opt_fields, esp. auto-consume
# TODO: Order pEp attachments by type
for attach in msg.attachments:
# no need to base64-encode, attachement are ascii-armoured
# already
#attachment = base64_encode(attachment)
# FIXME: Namespace
a = etree.SubElement(attachments,
PEP_NS + CT2TAG[attach.mime_type])
a.text = attach.decode("ascii")
else: # encrypted
# forget about longmsg and original body
# encrypted message is an attachment and there might be
# further attachments, e.g. new keys
# build a new message out of these attachments
etree.SubElement(root, "body") # emptry body
attachments = etree.SubElement(root, "attachments")
assert len(msg.attachments) == 2
# first attachment is "Version: 1"
attach = msg.attachments[1]
# no need to base64-encode, attachements are ascii-armoured
# already
n = etree.SubElement(root, PEP_NS + "message")
n.text = attach.decode("ascii")
return etree.tostring(root)
def parse_serialized_message(transport_message):
def addr2identity(text):
name, addr = email.utils.parseaddr(text)
ident = pEp.Identity(addr, name)
return ident
# parse the XML text, fetch from and to
root = etree.fromstring(transport_message)
from_ = addr2identity(root.xpath("./from/text()")[0])
msg1 = pEp.Message(INCOMING, from_)
enc_msg = root.find("{%s}message" % PEP_NAMESPACE)
if enc_msg is not None:
# this is an encrypted message, ignore all but the encrypted message
msg1.attachments = [
# As of Engine r4652 the encrypted message must be the second
# attachment
pEp.Blob(b"Version: 1", "application/pgp-encrypted"),
pEp.Blob(enc_msg.text.encode(), "application/xxpgp-encrypted")]
# this is an unencrypted message, might contain pEp attachments
msg1.longmsg = root.findtext("body")
pEp_attachments = None
attachments = root.find("attachments")
if attachments is not None:
pEp_attachments = attachments.find("{%s}attachments" % PEP_NAMESPACE)
if pEp_attachments is not None:
msg1.opt_fields['X-pEp-Version'] = pEp_attachments.attrib["version"]
pEp_attachs = []
for tagname in ("keydata", "signature", "sync", "distribution"):
for attach in pEp_attachments.iterfind(
"{%s}%s" % (PEP_NAMESPACE, tagname)):
pEp.Blob(attach.text.encode(), TAG2CT[tagname]))
msg1.attachments = pEp_attachs
msg2, keys, rating, flags = msg1.decrypt()
return msg2, rating