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()
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)
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)
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
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
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)
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)
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
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)
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)
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
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)
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)
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)
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
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