Example #1
0
def UnwrapPlainTextCrypto(part,
                          protocols=None,
                          si=None,
                          ei=None,
                          charsets=None):
    """
    This method will replace encrypted and signed parts with their
    contents and set part attributes describing the security properties
    instead.
    """
    payload = part.get_payload(None, True).strip()

    for crypto_cls in protocols.values():
        crypto = crypto_cls()

        if (payload.startswith(crypto.ARMOR_BEGIN_ENCRYPTED)
                and payload.endswith(crypto.ARMOR_END_ENCRYPTED)):
            si, ei, text = crypto.decrypt(payload)
            _update_text_payload(part, text, charsets=charsets)
            break

        elif (payload.startswith(crypto.ARMOR_BEGIN_SIGNED)
              and payload.endswith(crypto.ARMOR_END_SIGNED)):
            si = crypto.verify(payload)
            text = crypto.remove_armor(payload)
            _update_text_payload(part, text, charsets=charsets)
            break

    part.signature_info = si or SignatureInfo()
    part.encryption_info = ei or EncryptionInfo()
Example #2
0
    def __init__(self,
                 config,
                 event=None,
                 cleaner=None,
                 sender=None,
                 recipients=None,
                 use_html_wrapper=False,
                 wrapped_headers=None,
                 obscured_headers=None):
        from mailpile.mailutils.emails import MakeBoundary
        self.config = config
        self.event = event
        self.sender = sender
        self.cleaner = cleaner
        self.recipients = recipients or []
        self.use_html_wrapper = use_html_wrapper
        self.container = c = MIMEMultipart(boundary=MakeBoundary())

        self.wrapped_headers = self.WRAPPED_HEADERS
        if wrapped_headers is not None:
            self.wrapped_headers = wrapped_headers or ()

        self.obscured_headers = self.OBSCURED_HEADERS
        if obscured_headers is not None:
            self.obscured_headers = obscured_headers or {}

        c.set_type(self.CONTAINER_TYPE)
        c.signature_info = SignatureInfo(bubbly=False)
        c.encryption_info = EncryptionInfo(bubbly=False)
        if self.cleaner:
            self.cleaner(self.container)
        for pn, pv in self.CONTAINER_PARAMS:
            self.container.set_param(pn, pv)
Example #3
0
    def evaluate_pgp(self, tree, check_sigs=True, decrypt=False):
        if 'text_parts' not in tree:
            return tree

        pgpdata = []
        for part in tree['text_parts']:
            if 'crypto' not in part:
                part['crypto'] = {}

            ei = si = None
            if check_sigs:
                if part['type'] == 'pgpbeginsigned':
                    pgpdata = [part]
                elif part['type'] == 'pgpsignedtext':
                    pgpdata.append(part)
                elif part['type'] == 'pgpsignature':
                    pgpdata.append(part)
                    try:
                        gpg = GnuPG()
                        message = ''.join([p['data'].encode(p['charset'])
                                           for p in pgpdata])
                        si = pgpdata[1]['crypto']['signature'
                                                  ] = gpg.verify(message)
                        pgpdata[0]['data'] = ''
                        pgpdata[2]['data'] = ''

                    except Exception, e:
                        print e

            if decrypt:
                if part['type'] in ('pgpbegin', 'pgptext'):
                    pgpdata.append(part)
                elif part['type'] == 'pgpend':
                    pgpdata.append(part)

                    gpg = GnuPG()
                    (signature_info, encryption_info, text
                     ) = gpg.decrypt(''.join([p['data'] for p in pgpdata]))

                    # FIXME: If the data is binary, we should provide some
                    #        sort of download link or maybe leave the PGP
                    #        blob entirely intact, undecoded.
                    text, charset = self.decode_text(text, binary=False)

                    ei = pgpdata[1]['crypto']['encryption'] = encryption_info
                    si = pgpdata[1]['crypto']['signature'] = signature_info
                    if encryption_info["status"] == "decrypted":
                        pgpdata[1]['data'] = text
                        pgpdata[0]['data'] = ""
                        pgpdata[2]['data'] = ""

            # Bubbling up!
            if (si or ei) and 'crypto' not in tree:
                tree['crypto'] = {'signature': SignatureInfo(),
                                  'encryption': EncryptionInfo()}
            if si:
                tree['crypto']['signature'].mix(si)
            if ei:
                tree['crypto']['encryption'].mix(ei)
Example #4
0
    def TransformOutgoing(self, sender, rcpts, msg, **kwargs):
        matched = False
        gnupg = None

        sender_keyid = None
        if self.config.prefs.openpgp_header:
            try:
                gnupg = gnupg or GnuPG(self.config)
                seckeys = dict([(uid["email"], fp) for fp, key
                                in gnupg.list_secret_keys().iteritems()
                                if key["capabilities_map"].get("encrypt")
                                and key["capabilities_map"].get("sign")
                                for uid in key["uids"]])
                sender_keyid = seckeys.get(sender)
            except (KeyError, TypeError, IndexError, ValueError):
                traceback.print_exc()

        if sender_keyid and self.config.prefs.openpgp_header:
            msg["OpenPGP"] = ("id=%s; preference=%s"
                              % (sender_keyid,
                                 self.config.prefs.openpgp_header))

        if ('attach-pgp-pubkey' in msg and
                msg['attach-pgp-pubkey'][:3].lower() in ('yes', 'tru')):
            # FIXME: Check attach_pgp_pubkey for instructions on which key(s)
            #        to attach. Attaching all of them may be a bit lame.
            gnupg = gnupg or GnuPG(self.config)
            keys = gnupg.address_to_keys(ExtractEmails(sender)[0])
            key_count = 0
            for fp, key in keys.iteritems():
                if not any(key["capabilities_map"].values()):
                    continue
                # We should never really hit this more than once. But if we
                # do, should still be fine.
                keyid = key["keyid"]
                data = gnupg.get_pubkey(keyid)

                try:
                    from_name = key["uids"][0]["name"]
                    filename = _('Encryption key for %s.asc') % from_name
                except:
                    filename = _('My encryption key.asc')
                att = MIMEBase('application', 'pgp-keys')
                att.set_payload(data)
                encoders.encode_base64(att)
                del att['MIME-Version']
                att.add_header('Content-Id', MakeContentID())
                att.add_header('Content-Disposition', 'attachment',
                               filename=filename)
                att.signature_info = SignatureInfo(parent=msg.signature_info)
                att.encryption_info = EncryptionInfo(parent=msg.encryption_info)
                msg.attach(att)
                key_count += 1

            if key_count > 0:
                msg['x-mp-internal-pubkeys-attached'] = "Yes"

        return sender, rcpts, msg, matched, True
Example #5
0
def ParseMessage(fd, pgpmime=True):
    message = email.parser.Parser().parse(fd)
    if pgpmime and GnuPG:
        UnwrapMimeCrypto(message, protocols={'openpgp': GnuPG})
    else:
        for part in message.walk():
            part.signature_info = SignatureInfo()
            part.encryption_info = EncryptionInfo()
    return message
Example #6
0
def UnwrapPlainTextCrypto(part,
                          protocols=None,
                          psi=None,
                          pei=None,
                          charsets=None,
                          require_MDC=True,
                          depth=0):
    """
    This method will replace encrypted and signed parts with their
    contents and set part attributes describing the security properties
    instead.
    """
    payload = part.get_payload(None, True).strip()
    si = SignatureInfo(parent=psi)
    ei = EncryptionInfo(parent=pei)
    for crypto_cls in protocols.values():
        crypto = crypto_cls()

        if (payload.startswith(crypto.ARMOR_BEGIN_ENCRYPTED)
                and payload.endswith(crypto.ARMOR_END_ENCRYPTED)):
            try:
                si, ei, text = crypto.decrypt(payload, require_MDC=require_MDC)
                _update_text_payload(part, text, charsets=charsets)
            except (IOError, OSError, ValueError, IndexError, KeyError):
                ei = EncryptionInfo()
                ei["status"] = "error"
            break

        elif (payload.startswith(crypto.ARMOR_BEGIN_SIGNED)
              and payload.endswith(crypto.ARMOR_END_SIGNED)):
            try:
                si = crypto.verify(payload)
            except (IOError, OSError, ValueError, IndexError, KeyError):
                si = SignatureInfo()
                si["status"] = "error"
            _update_text_payload(part,
                                 crypto.remove_armor(payload),
                                 charsets=charsets)
            break

    part.signature_info = si
    part.signature_info.bubble_up(psi)
    part.encryption_info = ei
    part.encryption_info.bubble_up(pei)
Example #7
0
 def __init__(self, config, cleaner=None, sender=None, recipients=None):
     self.config = config
     self.sender = sender
     self.cleaner = cleaner
     self.recipients = recipients or []
     self.container = c = MIMEMultipart()
     c.set_type(self.CONTAINER_TYPE)
     c.signature_info = SignatureInfo(bubbly=False)
     c.encryption_info = EncryptionInfo(bubbly=False)
     if self.cleaner:
         self.cleaner(self.container)
     for pn, pv in self.CONTAINER_PARAMS:
         self.container.set_param(pn, pv)
Example #8
0
    def attach(self, part):
        c = self.container
        c.attach(part)

        if not hasattr(part, 'signature_info'):
            part.signature_info = SignatureInfo(parent=c.signature_info)
            part.encryption_info = EncryptionInfo(parent=c.encryption_info)
        else:
            part.signature_info.parent = c.signature_info
            part.signature_info.bubbly = True
            part.encryption_info.parent = c.encryption_info
            part.encryption_info.bubbly = True

        if self.cleaner:
            self.cleaner(part)
        del part['MIME-Version']
        return self
Example #9
0
 def __init__(self, config,
              event=None, cleaner=None,
              sender=None, recipients=None):
     from mailpile.mailutils import MakeBoundary
     self.config = config
     self.event = event
     self.sender = sender
     self.cleaner = cleaner
     self.recipients = recipients or []
     self.container = c = MIMEMultipart(boundary=MakeBoundary())
     c.set_type(self.CONTAINER_TYPE)
     c.signature_info = SignatureInfo(bubbly=False)
     c.encryption_info = EncryptionInfo(bubbly=False)
     if self.cleaner:
         self.cleaner(self.container)
     for pn, pv in self.CONTAINER_PARAMS:
         self.container.set_param(pn, pv)
Example #10
0
def UnwrapMimeCrypto(part,
                     protocols=None,
                     psi=None,
                     pei=None,
                     charsets=None,
                     unwrap_attachments=True,
                     require_MDC=True,
                     depth=0):
    """
    This method will replace encrypted and signed parts with their
    contents and set part attributes describing the security properties
    instead.
    """

    # Guard against maliciously constructed emails
    if depth > 6:
        return

    part.signature_info = SignatureInfo(parent=psi)
    part.encryption_info = EncryptionInfo(parent=pei)

    part.signed_headers = set([])
    part.encrypted_headers = set([])

    mimetype = part.get_content_type() or 'text/plain'
    disposition = part['content-disposition'] or ""
    encoding = part['content-transfer-encoding'] or ""

    # FIXME: Check the protocol. PGP? Something else?
    # FIXME: This is where we add hooks for other MIME encryption
    #        schemes, so route to callbacks by protocol.
    crypto_cls = protocols['openpgp']

    if part.is_multipart():
        # Containers are by default not bubbly
        part.signature_info.bubbly = False
        part.encryption_info.bubbly = False

    if part.is_multipart() and mimetype == 'multipart/signed':
        try:
            boundary = part.get_boundary()
            payload, signature = part.get_payload()

            # The Python get_payload() method likes to rewrite headers,
            # which breaks signature verification. So we manually parse
            # out the raw payload here.
            head, raw_payload, junk = part.as_string().replace(
                '\r\n', '\n').split('\n--%s\n' % boundary, 2)

            part.signature_info = crypto_cls().verify(Normalize(raw_payload),
                                                      signature.get_payload())
            part.signature_info.bubble_up(psi)

            # Reparent the contents up, removing the signature wrapper
            hdrs = MimeReplacePart(part,
                                   payload,
                                   keep_old_headers='MH-Renamed')
            part.signed_headers = hdrs

            # Try again, in case we just unwrapped another layer
            # of multipart/something.
            UnwrapMimeCrypto(part,
                             protocols=protocols,
                             psi=part.signature_info,
                             pei=part.encryption_info,
                             charsets=charsets,
                             unwrap_attachments=unwrap_attachments,
                             require_MDC=require_MDC,
                             depth=depth + 1)

        except (IOError, OSError, ValueError, IndexError, KeyError):
            part.signature_info = SignatureInfo()
            part.signature_info["status"] = "error"
            part.signature_info.bubble_up(psi)

    elif part.is_multipart() and mimetype == 'multipart/encrypted':
        try:
            preamble, payload = part.get_payload()

            (part.signature_info, part.encryption_info,
             decrypted) = (crypto_cls().decrypt(payload.as_string(),
                                                require_MDC=require_MDC))
        except (IOError, OSError, ValueError, IndexError, KeyError):
            part.encryption_info = EncryptionInfo()
            part.encryption_info["status"] = "error"

        part.signature_info.bubble_up(psi)
        part.encryption_info.bubble_up(pei)

        if part.encryption_info['status'] == 'decrypted':
            newpart = email.parser.Parser().parsestr(decrypted)

            # Reparent the contents up, removing the encryption wrapper
            hdrs = MimeReplacePart(part,
                                   newpart,
                                   keep_old_headers='MH-Renamed')

            # Is there a Memory-Hole force-display part?
            pl = part.get_payload()
            if hdrs and isinstance(pl, (list, )):
                if (pl[0]['content-type'].startswith('text/rfc822-headers;')
                        and 'protected-headers' in pl[0]['content-type']):
                    # Parse these headers as well and override the top level,
                    # again. This is to be sure we see the same thing as
                    # everyone else (same algo as enigmail).
                    data = email.parser.Parser().parsestr(pl[0].get_payload(),
                                                          headersonly=True)
                    for h in data.keys():
                        if h in part:
                            del part[h]
                        part[h] = data[h]
                        hdrs.add(h)

                    # Finally just delete the part, we're done with it!
                    del pl[0]

            part.encrypted_headers = hdrs
            if part.signature_info["status"] != 'none':
                part.signed_headers = hdrs

            # Try again, in case we just unwrapped another layer
            # of multipart/something.
            UnwrapMimeCrypto(part,
                             protocols=protocols,
                             psi=part.signature_info,
                             pei=part.encryption_info,
                             charsets=charsets,
                             unwrap_attachments=unwrap_attachments,
                             require_MDC=require_MDC,
                             depth=depth + 1)

    # If we are still multipart after the above shenanigans (perhaps due
    # to an error state), recurse into our subparts and unwrap them too.
    elif part.is_multipart():
        for sp in part.get_payload():
            UnwrapMimeCrypto(sp,
                             protocols=protocols,
                             psi=part.signature_info,
                             pei=part.encryption_info,
                             charsets=charsets,
                             unwrap_attachments=unwrap_attachments,
                             require_MDC=require_MDC,
                             depth=depth + 1)

    elif disposition.startswith('attachment'):
        # The sender can attach signed/encrypted/key files without following
        # rules for naming or mime type.
        # So - sniff to detect parts that need processing and identify protocol.
        kind = ''
        for protocol in protocols:
            crypto_cls = protocols[protocol]
            kind = crypto_cls().sniff(part.get_payload(), encoding)
            if kind:
                break

        if unwrap_attachments and ('encrypted' in kind or 'signature' in kind):
            # Messy! The PGP decrypt operation is also needed for files which
            # are encrypted and signed, and files that are signed only.
            payload = part.get_payload(None, True)
            try:
                (part.signature_info, part.encryption_info,
                 decrypted) = (crypto_cls().decrypt(payload,
                                                    require_MDC=require_MDC))
            except (IOError, OSError, ValueError, IndexError, KeyError):
                part.encryption_info = EncryptionInfo()
                part.encryption_info["status"] = "error"

            part.signature_info.bubble_up(psi)
            part.encryption_info.bubble_up(pei)

            if (part.encryption_info['status'] == 'decrypted'
                    or part.signature_info['status'] == 'verified'):

                # Force base64 encoding and application/octet-stream type
                newpart = MIMEBase('application', 'octet-stream')
                newpart.set_payload(decrypted)
                encoders.encode_base64(newpart)

                # Add Content-Disposition with appropriate filename.
                MimeAttachmentDisposition(part, kind, newpart)

                MimeReplacePart(part, newpart)

                # Is there another layer to unwrap?
                UnwrapMimeCrypto(part,
                                 protocols=protocols,
                                 psi=part.signature_info,
                                 pei=part.encryption_info,
                                 charsets=charsets,
                                 unwrap_attachments=unwrap_attachments,
                                 require_MDC=require_MDC,
                                 depth=depth + 1)
            else:
                # FIXME: Best action for unsuccessful attachment processing?
                pass

    elif mimetype == 'text/plain':
        return UnwrapPlainTextCrypto(part,
                                     protocols=protocols,
                                     psi=psi,
                                     pei=pei,
                                     charsets=charsets,
                                     require_MDC=require_MDC,
                                     depth=depth + 1)

    else:
        # FIXME: This is where we would handle cryptoschemes that don't
        #        appear as multipart/...
        pass

    # Mix in our bubbles
    part.signature_info.mix_bubbles()
    part.encryption_info.mix_bubbles()

    # Bubble up!
    part.signature_info.bubble_up(psi)
    part.encryption_info.bubble_up(pei)
Example #11
0
    def TransformOutgoing(self, sender, rcpts, msg, **kwargs):
        matched = False
        gnupg = None
        sender_keyid = None

        # Prefer to just get everything from the profile VCard, in the
        # common case...
        profile = self.config.vcards.get_vcard(sender)
        if profile:
            sender_keyid = profile.pgp_key
            crypto_format = profile.crypto_format or 'none'
        else:
            crypto_format = 'none'

        # Parse the openpgp_header data from the crypto_format
        openpgp_header = [
            p.split(':')[-1] for p in crypto_format.split('+')
            if p.startswith('openpgp_header:')
        ]
        if not openpgp_header:
            openpgp_header = self.config.prefs.openpgp_header and ['CFG']

        if openpgp_header[0] != 'N' and not sender_keyid:
            # This is a fallback: this shouldn't happen much in normal use
            try:
                gnupg = gnupg or GnuPG(self.config, event=GetThreadEvent())
                seckeys = dict([
                    (uid["email"], fp)
                    for fp, key in gnupg.list_secret_keys().iteritems()
                    if key["capabilities_map"].get("encrypt")
                    and key["capabilities_map"].get("sign")
                    for uid in key["uids"]
                ])
                sender_keyid = seckeys.get(sender)
            except (KeyError, TypeError, IndexError, ValueError):
                traceback.print_exc()

        if sender_keyid and openpgp_header:
            preference = {
                'ES': 'signencrypt',
                'SE': 'signencrypt',
                'E': 'encrypt',
                'S': 'sign',
                'N': 'unprotected',
                'CFG': self.config.prefs.openpgp_header
            }[openpgp_header[0].upper()]
            msg["OpenPGP"] = ("id=%s; preference=%s" %
                              (sender_keyid, preference))

        if ('attach-pgp-pubkey' in msg
                and msg['attach-pgp-pubkey'][:3].lower() in ('yes', 'tru')):
            gnupg = gnupg or GnuPG(self.config, event=GetThreadEvent())
            if sender_keyid:
                keys = gnupg.list_keys(selectors=[sender_keyid])
            else:
                keys = gnupg.address_to_keys(ExtractEmails(sender)[0])

            key_count = 0
            for fp, key in keys.iteritems():
                if not any(key["capabilities_map"].values()):
                    continue
                # We should never really hit this more than once. But if we
                # do, should still be fine.
                keyid = key["keyid"]
                data = gnupg.get_pubkey(keyid)

                try:
                    from_name = key["uids"][0]["name"]
                    filename = _('Encryption key for %s.asc') % from_name
                except:
                    filename = _('My encryption key.asc')
                att = MIMEBase('application', 'pgp-keys')
                att.set_payload(data)
                encoders.encode_base64(att)
                del att['MIME-Version']
                att.add_header('Content-Id', MakeContentID())
                att.add_header('Content-Disposition',
                               'attachment',
                               filename=filename)
                att.signature_info = SignatureInfo(parent=msg.signature_info)
                att.encryption_info = EncryptionInfo(
                    parent=msg.encryption_info)
                msg.attach(att)
                key_count += 1

            if key_count > 0:
                msg['x-mp-internal-pubkeys-attached'] = "Yes"

        return sender, rcpts, msg, matched, True
Example #12
0
    def Create(cls,
               idx,
               mbox_id,
               mbx,
               msg_to=None,
               msg_cc=None,
               msg_bcc=None,
               msg_from=None,
               msg_subject=None,
               msg_text=None,
               msg_references=None,
               save=True,
               ephemeral_mid='not-saved'):
        msg = MIMEMultipart()
        msg.signature_info = SignatureInfo()
        msg.encryption_info = EncryptionInfo()
        msg_ts = int(time.time())
        if not msg_from:
            msg_from = idx.config.get_profile().get('email', None)
            from_name = idx.config.get_profile().get('name', None)
            if msg_from and from_name:
                msg_from = '%s <%s>' % (from_name, msg_from)
        if not msg_from:
            raise NoFromAddressError()
        msg['From'] = cls.encoded_hdr(None, 'from', value=msg_from)
        msg['Date'] = email.utils.formatdate(msg_ts)
        msg['Message-Id'] = email.utils.make_msgid('mailpile')
        msg_subj = (msg_subject or '')
        msg['Subject'] = cls.encoded_hdr(None, 'subject', value=msg_subj)
        if msg_to:
            msg['To'] = cls.encoded_hdr(None,
                                        'to',
                                        value=', '.join(set(msg_to)))
        if msg_cc:
            msg['Cc'] = cls.encoded_hdr(None,
                                        'cc',
                                        value=', '.join(set(msg_cc)))
        if msg_bcc:
            msg['Bcc'] = cls.encoded_hdr(None,
                                         'bcc',
                                         value=', '.join(set(msg_bcc)))
        if msg_references:
            msg['In-Reply-To'] = msg_references[-1]
            msg['References'] = ', '.join(msg_references)

        if msg_text:
            try:
                msg_text.encode('us-ascii')
                charset = 'us-ascii'
            except UnicodeEncodeError:
                charset = 'utf-8'
            textpart = MIMEText(msg_text, _subtype='plain', _charset=charset)
            textpart.signature_info = SignatureInfo()
            textpart.encryption_info = EncryptionInfo()
            msg.attach(textpart)
            del textpart['MIME-Version']

        if save:
            msg_key = mbx.add(msg)
            msg_to = msg_cc = []
            msg_ptr = mbx.get_msg_ptr(mbox_id, msg_key)
            msg_id = idx.get_msg_id(msg, msg_ptr)
            msg_idx, msg_info = idx.add_new_msg(msg_ptr, msg_id, msg_ts,
                                                msg_from, msg_to, msg_cc, 0,
                                                msg_subj, '', [])
            idx.set_conversation_ids(msg_info[idx.MSG_MID],
                                     msg,
                                     subject_threading=False)
            return cls(idx, msg_idx)
        else:
            msg_info = idx.edit_msg_info(idx.BOGUS_METADATA[:],
                                         msg_mid=ephemeral_mid or '',
                                         msg_id=msg['Message-ID'],
                                         msg_ts=msg_ts,
                                         msg_subject=msg_subj,
                                         msg_from=msg_from,
                                         msg_to=msg_to,
                                         msg_cc=msg_cc)
            return cls(idx,
                       -1,
                       msg_info=msg_info,
                       msg_parsed=msg,
                       msg_parsed_pgpmime=msg,
                       ephemeral_mid=ephemeral_mid)
Example #13
0
def UnwrapMimeCrypto(part, protocols=None, psi=None, pei=None, charsets=None):
    """
    This method will replace encrypted and signed parts with their
    contents and set part attributes describing the security properties
    instead.
    """
    part.signature_info = SignatureInfo(parent=psi)
    part.encryption_info = EncryptionInfo(parent=pei)
    mimetype = part.get_content_type() or 'text/plain'
    if part.is_multipart():

        # FIXME: Check the protocol. PGP? Something else?
        # FIXME: This is where we add hooks for other MIME encryption
        #        schemes, so route to callbacks by protocol.
        crypto_cls = protocols['openpgp']

        # Containers are by default not bubbly
        part.signature_info.bubbly = False
        part.encryption_info.bubbly = False

        if mimetype == 'multipart/signed':
            try:
                boundary = part.get_boundary()
                payload, signature = part.get_payload()

                # The Python get_payload() method likes to rewrite headers,
                # which breaks signature verification. So we manually parse
                # out the raw payload here.
                head, raw_payload, junk = part.as_string().replace(
                    '\r\n', '\n').split('\n--%s\n' % boundary, 2)

                part.signature_info = crypto_cls().verify(
                    Normalize(raw_payload), signature.get_payload())
                part.signature_info.bubble_up(psi)

                # Reparent the contents up, removing the signature wrapper
                part.set_payload(payload.get_payload())
                for h in payload.keys():
                    del part[h]
                if 'content-type' in part:
                    del part['content-type']  # May be missing from child
                for h, v in payload.items():
                    part.add_header(h, v)

                # Try again, in case we just unwrapped another layer
                # of multipart/something.
                UnwrapMimeCrypto(part,
                                 protocols=protocols,
                                 psi=part.signature_info,
                                 pei=part.encryption_info,
                                 charsets=charsets)

            except (IOError, OSError, ValueError, IndexError, KeyError):
                part.signature_info = SignatureInfo()
                part.signature_info["status"] = "error"
                part.signature_info.bubble_up(psi)

        elif mimetype == 'multipart/encrypted':
            try:
                preamble, payload = part.get_payload()

                (part.signature_info, part.encryption_info,
                 decrypted) = crypto_cls().decrypt(payload.as_string())
            except (IOError, OSError, ValueError, IndexError, KeyError):
                part.encryption_info = EncryptionInfo()
                part.encryption_info["status"] = "error"

            part.signature_info.bubble_up(psi)
            part.encryption_info.bubble_up(pei)

            if part.encryption_info['status'] == 'decrypted':
                newpart = email.parser.Parser().parse(
                    StringIO.StringIO(decrypted))

                # Reparent the contents up, removing the encryption wrapper
                part.set_payload(newpart.get_payload())
                for h in newpart.keys():
                    del part[h]
                if 'content-type' in part:
                    del part['content-type']  # May be missing from child
                for h, v in newpart.items():
                    part.add_header(h, v)

                # Try again, in case we just unwrapped another layer
                # of multipart/something.
                UnwrapMimeCrypto(part,
                                 protocols=protocols,
                                 psi=part.signature_info,
                                 pei=part.encryption_info,
                                 charsets=charsets)

        # If we are still multipart after the above shenanigans (perhaps due
        # to an error state), recurse into our subparts and unwrap them too.
        elif part.is_multipart():
            for sp in part.get_payload():
                UnwrapMimeCrypto(sp,
                                 protocols=protocols,
                                 psi=part.signature_info,
                                 pei=part.encryption_info,
                                 charsets=charsets)

    elif mimetype == 'text/plain':
        return UnwrapPlainTextCrypto(part,
                                     protocols=protocols,
                                     psi=psi,
                                     pei=pei,
                                     charsets=charsets)

    else:
        # FIXME: This is where we would handle cryptoschemes that don't
        #        appear as multipart/...
        pass

    # Mix in our bubbles
    part.signature_info.mix_bubbles()
    part.encryption_info.mix_bubbles()

    # Bubble up!
    part.signature_info.bubble_up(psi)
    part.encryption_info.bubble_up(pei)
Example #14
0
def UnwrapMimeCrypto(part, protocols=None, psi=None, pei=None, charsets=None, depth = 0):
    """
    This method will replace encrypted and signed parts with their
    contents and set part attributes describing the security properties
    instead.
    """

    # Guard against maliciously constructed emails
    if depth > 6:
        return

    part.signature_info = SignatureInfo(parent=psi)
    part.encryption_info = EncryptionInfo(parent=pei)
    mimetype = part.get_content_type() or 'text/plain'
    disposition = part['content-disposition'] or ""
    encoding = part['content-transfer-encoding'] or ""

    # FIXME: Check the protocol. PGP? Something else?
    # FIXME: This is where we add hooks for other MIME encryption
    #        schemes, so route to callbacks by protocol.
    crypto_cls = protocols['openpgp']

    if part.is_multipart():
        # Containers are by default not bubbly
        part.signature_info.bubbly = False
        part.encryption_info.bubbly = False

    if part.is_multipart() and mimetype == 'multipart/signed':
        try:
            boundary = part.get_boundary()
            payload, signature = part.get_payload()

            # The Python get_payload() method likes to rewrite headers,
            # which breaks signature verification. So we manually parse
            # out the raw payload here.
            head, raw_payload, junk = part.as_string(
                ).replace('\r\n', '\n').split('\n--%s\n' % boundary, 2)

            part.signature_info = crypto_cls().verify(
                Normalize(raw_payload), signature.get_payload())
            part.signature_info.bubble_up(psi)

            # Reparent the contents up, removing the signature wrapper
            MimeReplacePart(part, payload)

            # Try again, in case we just unwrapped another layer
            # of multipart/something.
            UnwrapMimeCrypto(part,
                             protocols=protocols,
                             psi=part.signature_info,
                             pei=part.encryption_info,
                             charsets=charsets,
                             depth = depth + 1 )

        except (IOError, OSError, ValueError, IndexError, KeyError):
            part.signature_info = SignatureInfo()
            part.signature_info["status"] = "error"
            part.signature_info.bubble_up(psi)

    elif part.is_multipart() and mimetype == 'multipart/encrypted':
        try:
            preamble, payload = part.get_payload()

            (part.signature_info, part.encryption_info, decrypted
             ) = crypto_cls().decrypt(payload.as_string())
        except (IOError, OSError, ValueError, IndexError, KeyError):
            part.encryption_info = EncryptionInfo()
            part.encryption_info["status"] = "error"

        part.signature_info.bubble_up(psi)
        part.encryption_info.bubble_up(pei)

        if part.encryption_info['status'] == 'decrypted':
            newpart = email.parser.Parser().parse(
                StringIO.StringIO(decrypted))

            # Reparent the contents up, removing the encryption wrapper
            MimeReplacePart(part, newpart)

            # Try again, in case we just unwrapped another layer
            # of multipart/something.
            UnwrapMimeCrypto(part,
                             protocols=protocols,
                             psi=part.signature_info,
                             pei=part.encryption_info,
                             charsets=charsets,
                             depth = depth + 1 )

    # If we are still multipart after the above shenanigans (perhaps due
    # to an error state), recurse into our subparts and unwrap them too.
    elif part.is_multipart():
        for sp in part.get_payload():
            UnwrapMimeCrypto(sp,
                             protocols=protocols,
                             psi=part.signature_info,
                             pei=part.encryption_info,
                             charsets=charsets,
                             depth = depth + 1 )

    elif disposition.startswith('attachment'):
        # The sender can attach signed/encrypted/key files without following
        # rules for naming or mime type.
        # So - sniff to detect parts that need processing and identify protocol.
        for protocol in protocols:
            crypto_cls = protocols[protocol]
            kind = crypto_cls().sniff(part.get_payload(), encoding)
            if kind:
                break

        if 'encrypted' in kind or 'signature' in kind:
            # Messy! The PGP decrypt operation is also needed for files which
            # are encrypted and signed, and files that are signed only.
            payload = part.get_payload( None, True )
            try:
                (part.signature_info, part.encryption_info, decrypted
                 ) = crypto_cls().decrypt(payload)
            except (IOError, OSError, ValueError, IndexError, KeyError):
                part.encryption_info = EncryptionInfo()
                part.encryption_info["status"] = "error"

            part.signature_info.bubble_up(psi)
            part.encryption_info.bubble_up(pei)

            if (part.encryption_info['status'] == 'decrypted' or
                    part.signature_info['status'] == 'verified'):
                newpart = email.parser.Parser().parse(
                    StringIO.StringIO(decrypted))

                # Use the original file name if available, otherwise
                # delete .gpg or .asc extension from attachment file name.
                if part.encryption_info.filename:
                    disposition = MimeReplaceFilename(disposition,
                                        part.encryption_info.filename)
                elif 'armored' in kind:
                    disposition = MimeTrimFilename(disposition, 'asc')
                else:
                    disposition = MimeTrimFilename(disposition, 'gpg')
                newpart.add_header('content-disposition', disposition)

                MimeReplacePart(part, newpart)

                # Is there another layer to unwrap?
                UnwrapMimeCrypto(part,
                                 protocols=protocols,
                                 psi=part.signature_info,
                                 pei=part.encryption_info,
                                 charsets=charsets,
                                 depth = depth + 1 )
            else:
                # FIXME: Best action for unsuccessful attachment processing?
                pass

    elif mimetype == 'text/plain':
        return UnwrapPlainTextCrypto(part,
                                     protocols=protocols,
                                     psi=psi,
                                     pei=pei,
                                     charsets=charsets,
                                     depth = depth + 1 )

    else:
        # FIXME: This is where we would handle cryptoschemes that don't
        #        appear as multipart/...
        pass


    # Mix in our bubbles
    part.signature_info.mix_bubbles()
    part.encryption_info.mix_bubbles()

    # Bubble up!
    part.signature_info.bubble_up(psi)
    part.encryption_info.bubble_up(pei)
Example #15
0
def _AddCryptoState(part, src=None):
    part.signature_info = src.signature_info if src else SignatureInfo()
    part.encryption_info = src.encryption_info if src else EncryptionInfo()
    return part
Example #16
0
def UnwrapMimeCrypto(part, protocols=None, si=None, ei=None):
    """
    This method will replace encrypted and signed parts with their
    contents and set part attributes describing the security properties
    instead.
    """
    part.signature_info = si or SignatureInfo()
    part.encryption_info = ei or EncryptionInfo()
    mimetype = part.get_content_type()
    if part.is_multipart():

        # FIXME: Check the protocol. PGP? Something else?
        # FIXME: This is where we add hooks for other MIME encryption
        #        schemes, so route to callbacks by protocol.
        crypto_cls = protocols['openpgp']

        if mimetype == 'multipart/signed':
            try:
                gpg = crypto_cls()
                boundary = part.get_boundary()
                payload, signature = part.get_payload()

                # The Python get_payload() method likes to rewrite headers,
                # which breaks signature verification. So we manually parse
                # out the raw payload here.
                head, raw_payload, junk = part.as_string(
                    ).replace('\r\n', '\n').split('\n--%s\n' % boundary, 2)

                part.signature_info = gpg.verify(
                    Normalize(raw_payload), signature.get_payload())

                # Reparent the contents up, removing the signature wrapper
                part.set_payload(payload.get_payload())
                for h in payload.keys():
                    del part[h]
                for h, v in payload.items():
                    part.add_header(h, v)

                # Try again, in case we just unwrapped another layer
                # of multipart/something.
                return UnwrapMimeCrypto(part,
                                        protocols=protocols,
                                        si=part.signature_info,
                                        ei=part.encryption_info)

            except (IOError, OSError, ValueError, IndexError, KeyError):
                part.signature_info = SignatureInfo()
                part.signature_info["status"] = "error"

        elif mimetype == 'multipart/encrypted':
            try:
                gpg = crypto_cls()
                preamble, payload = part.get_payload()

                (part.signature_info, part.encryption_info, decrypted
                 ) = gpg.decrypt(payload.as_string())
            except (IOError, OSError, ValueError, IndexError, KeyError):
                part.encryption_info = EncryptionInfo()
                part.encryption_info["status"] = "error"

            if part.encryption_info['status'] == 'decrypted':
                newpart = email.parser.Parser().parse(
                    StringIO.StringIO(decrypted))

                # Reparent the contents up, removing the encryption wrapper
                part.set_payload(newpart.get_payload())
                for h in newpart.keys():
                    del part[h]
                for h, v in newpart.items():
                    part.add_header(h, v)

                # Try again, in case we just unwrapped another layer
                # of multipart/something.
                return UnwrapMimeCrypto(part,
                                        protocols=protocols,
                                        si=part.signature_info,
                                        ei=part.encryption_info)

        # If we are still multipart after the above shenanigans, recurse
        # into our subparts and unwrap them too.
        if part.is_multipart():
            for subpart in part.get_payload():
                UnwrapMimeCrypto(subpart,
                                 protocols=protocols,
                                 si=part.signature_info,
                                 ei=part.encryption_info)

    else:
        # FIXME: This is where we would handle cryptoschemes that don't
        #        appear as multipart/...
        pass