def unbundle_wrapped_messages(self): ''' Unbundle messages and send them to their intended recipients. ''' result_ok = False self.log_message('unbundling wrapped messages') if self.crypto_message is None: self.log_message('no crypto message to unbundle') else: message = self.crypto_message.get_email_message().get_message() if self.DEBUGGING: self.log_message('DEBUG: logged wrapped headers in goodcrypto.message.utils.log') utils.log_message_headers(message, tag='wrapped headers') if message.get_content_type() == mime_constants.MULTIPART_MIXED_TYPE: result_ok = self.split_and_send_messages(message) # no need to re-inject this message as we've already sent the inner messages to users self.crypto_message.set_processed(True) result_ok = True else: result_ok = False self.crypto_message.drop() self.log_message('dropping message because there are no valid bundled messages; content type: {}'.format( message.get_content_type())) return result_ok
def decrypt_message(self, filter_msg=True): ''' Decrypt a message and add a tag if unsuccessful (internal use only). ''' encryption_names = self._get_recipient_encryption_software() if encryption_names is None or len( encryption_names) < 1 or self.crypto_message is None: decrypted = False decrypted_with = [] self.log_message('unable to decrypt message when missing data') else: try: self.log_message( 'trying to decrypt with: {}'.format(encryption_names)) decrypted, decrypted_with = self._decrypt_with_all_encryption( encryption_names) if decrypted: self.crypto_message.set_crypted_with(decrypted_with) # if the message is still encrypted, log it and tell the user elif self.crypto_message.get_email_message().is_probably_pgp(): if len(encryption_names) > 1: software = encryption_names.__str__() else: software = str(encryption_names[0]) log_msg = "Failed to decrypt with {}".format(software) self.log_message(log_msg) self.log_message( 'logged failed message headers in goodcrypto.message.utils.log' ) utils.log_message_headers(self.crypto_message, 'failed message headers') record_exception(message=log_msg) tag = i18n( 'Unable to decrypt message with {encryption}'.format( encryption=software)) self.crypto_message.add_error_tag_once(tag) # don't filter bundled messages; each message will be filtered separately if filter_msg and options.filter_html(): self._filter_html() else: self.log_message("html filter disabled") except CryptoException as crypto_exception: raise CryptoException(crypto_exception.value) except Exception: decrypted = False record_exception() self.log_message( 'EXCEPTION - see syr.exception.log for details') return decrypted, decrypted_with
def _decrypt_open_pgp_mime(self, from_user, crypto, passcode): ''' Decrypt an open PGP MIME message (internal use only). ''' decrypted = False plaintext = None encrypted_part = None try: self.log_message("message is in OpenPGP MIME format") if self.DEBUGGING: self.log_message( 'logged OpenPGP mime headers in goodcrypto.message.utils.log' ) utils.log_message_headers(self.crypto_message, tag='OpenPGP mime headers') # remove any clear signed section before decrypting message self.crypto_message.get_email_message( ).remove_pgp_signature_blocks() payloads = self.crypto_message.get_email_message().get_message( ).get_payload() self.log_message("{} parts in message".format(len(payloads))) encrypted_part = payloads[self.ENCRYPTED_BODY_PART_INDEX] if isinstance(encrypted_part, Message): encrypted_part = encrypted_part.get_payload() if Decrypt.DEBUGGING: self.log_message("encrypted_part\n{}".format(encrypted_part)) charset, __ = get_charset(encrypted_part) self.log_message('encrypted part char set: {}'.format(charset)) plaintext = self._decrypt(from_user, encrypted_part, charset, crypto, passcode) except CryptoException as crypto_exception: raise CryptoException(crypto_exception.value) except Exception: record_exception() self.log_message('EXCEPTION - see syr.exception.log for details') if plaintext == None or encrypted_part is None or plaintext == encrypted_part: decrypted = False self.log_message("unable to decrypt message") else: filtered = self._extract_embedded_message(plaintext) self.crypto_message.set_filtered(filtered) decrypted = self.crypto_message.is_crypted() return decrypted
def _extract_embedded_message(self, plaintext): ''' Extract an embedded message. If the message includes an Open PGP header, then save the plaintext in the email message. Otherwise, create a new email message from the embedded message. ''' extracted_embedded_message = False try: if self.DEBUGGING: self.log_message('embbedded message:\n{}'.format(plaintext)) encrypted_type = get_first_header( self.crypto_message.get_email_message().get_message(), PGP_ENCRYPTED_CONTENT_TYPE) if encrypted_type is None: old_message = self.crypto_message.get_email_message( ).get_message() new_message = plaintext_to_message(old_message, plaintext) self.crypto_message.get_email_message().set_message( new_message) self.crypto_message.set_crypted(True) self.log_message("created a new message from the plaintext") if self.DEBUGGING: self.log_message( 'logged final embedded message headers in goodcrypto.message.utils.log' ) utils.log_message_headers( self.crypto_message, tag='final embedded message headers') else: # this assumes an embedded mime message self.log_message( "openpgp mime type: {}".format(encrypted_type)) embedded_message = EmailMessage(plaintext) self.crypto_message.set_email_message(embedded_message) self.crypto_message.set_crypted(True) extracted_embedded_message = True self.log_message("embedded message type is {}".format( embedded_message.get_message().get_content_type())) except Exception: record_exception() self.log_message('EXCEPTION - see syr.exception.log for details') return extracted_embedded_message
def decrypt_message(self, filter_msg=True): ''' Decrypt a message and add a tag if unsuccessful (internal use only). ''' encryption_names = self._get_recipient_encryption_software() if encryption_names is None or len(encryption_names) < 1 or self.crypto_message is None: decrypted = False decrypted_with = [] self.log_message('unable to decrypt message when missing data') else: try: self.log_message('trying to decrypt with: {}'.format(encryption_names)) decrypted, decrypted_with = self._decrypt_with_all_encryption(encryption_names) if decrypted: self.crypto_message.set_crypted_with(decrypted_with) # if the message is still encrypted, log it and tell the user elif self.crypto_message.get_email_message().is_probably_pgp(): if len(encryption_names) > 1: software = encryption_names.__str__() else: software = str(encryption_names[0]) log_msg = "Failed to decrypt with {}".format(software) self.log_message(log_msg) self.log_message('logged failed message headers in goodcrypto.message.utils.log') utils.log_message_headers(self.crypto_message, 'failed message headers') record_exception(message=log_msg) tag = i18n('Unable to decrypt message with {encryption}'.format(encryption=software)) self.crypto_message.add_error_tag_once(tag) # don't filter bundled messages; each message will be filtered separately if filter_msg and options.filter_html(): self._filter_html() else: self.log_message("html filter disabled") except CryptoException as crypto_exception: raise CryptoException(crypto_exception.value) except Exception: decrypted = False record_exception() self.log_message('EXCEPTION - see syr.exception.log for details') return decrypted, decrypted_with
def _protect_metadata(self, from_user, to_user, inner_encrypted_with): ''' Protect the message's metadata and resist traffic analysis. ''' if options.bundle_and_pad(): tags_added = add_encrypted_tags_to_message(self.crypto_message) self._log_message('tags added to encrypted message before bundling: {}'.format(tags_added)) # add the DKIM signature if user opted for it self.crypto_message = encrypt_utils.add_dkim_sig_optionally(self.crypto_message) message_name = packetize( self.crypto_message, inner_encrypted_with, self.verification_code) if os.stat(message_name).st_size > options.bundled_message_max_size(): self._log_message('Message too large to bundle so throwing MessageException') if os.path.exists(message_name): os.remove(message_name) error_message = self.MESSAGE_TOO_LARGE.format(kb_size=options.bundle_message_kb()) self._log_message(error_message) raise MessageException(value=error_message) else: self._log_message('message waiting for bundling: {}'.format( self.crypto_message.is_processed())) else: self._log_message('protecting metadata') # add the original sender and recipient in the header self.crypto_message.get_email_message().add_header(constants.ORIGINAL_FROM, from_user) self.crypto_message.get_email_message().add_header(constants.ORIGINAL_TO, to_user) # add the DKIM signature to the inner message if user opted for it self.crypto_message = encrypt_utils.add_dkim_sig_optionally(self.crypto_message) self._log_message('DEBUG: logged headers before encrypting with metadata key in goodcrypto.message.utils.log') log_message_headers(self.crypto_message, tag='headers before encrypting with metadata key') # even if the inner message isn't encrypted, encrypt # the entire message to protect the metadata message_id = get_message_id(self.crypto_message.get_email_message()) self.crypto_message = encrypt_utils.create_protected_message( self.crypto_message.smtp_sender(), self.crypto_message.smtp_recipient(), self.crypto_message.get_email_message().to_string(), message_id) self._log_message('added protective layer for metadata')
def _decrypt_open_pgp_mime(self, from_user, crypto, passcode): ''' Decrypt an open PGP MIME message (internal use only). ''' decrypted = False plaintext = None encrypted_part = None try: self.log_message("message is in OpenPGP MIME format") if self.DEBUGGING: self.log_message('logged OpenPGP mime headers in goodcrypto.message.utils.log') utils.log_message_headers(self.crypto_message, tag='OpenPGP mime headers') # remove any clear signed section before decrypting message self.crypto_message.get_email_message().remove_pgp_signature_blocks() payloads = self.crypto_message.get_email_message().get_message().get_payload() self.log_message("{} parts in message".format(len(payloads))) encrypted_part = payloads[self.ENCRYPTED_BODY_PART_INDEX] if isinstance(encrypted_part, Message): encrypted_part = encrypted_part.get_payload() if Decrypt.DEBUGGING: self.log_message("encrypted_part\n{}".format(encrypted_part)) charset, __ = get_charset(encrypted_part) self.log_message('encrypted part char set: {}'.format(charset)) plaintext = self._decrypt(from_user, encrypted_part, charset, crypto, passcode) except CryptoException as crypto_exception: raise CryptoException(crypto_exception.value) except Exception: record_exception() self.log_message('EXCEPTION - see syr.exception.log for details') if plaintext == None or encrypted_part is None or plaintext == encrypted_part: decrypted = False self.log_message("unable to decrypt message") else: filtered = self._extract_embedded_message(plaintext) self.crypto_message.set_filtered(filtered) decrypted = self.crypto_message.is_crypted() return decrypted
def split_and_send_messages(self, message): ''' Split a message apart and send each to the intended recipient. ''' result_ok = False self.messages_sent = 0 try: for part in message.walk(): try: if part.get_content_type() == mime_constants.APPLICATION_ALT_TYPE: content = b64decode(part.get_payload(decode=True)) inner_crypto_message = self.create_inner_message(content) if inner_crypto_message is not None: self.log_message('logged inner message headers in goodcrypto.message.utils.log') utils.log_message_headers(inner_crypto_message, tag='inner message headers') ok, __ = self.decrypt_and_send_message(inner_crypto_message) if ok: self.messages_sent += 1 except CryptoException as crypto_exception: raise MessageException(value=crypto_exception.value) except MessageException as message_exception: raise MessageException(value=message_exception.value) except: self.log_message('bad part of message discarded') self.log_message('EXCEPTION - see syr.exception.log for details') record_exception() result_ok = True self.log_message('good bundled message contains {} inner message(s)'.format(self.messages_sent)) except CryptoException as crypto_exception: raise MessageException(value=crypto_exception.value) except MessageException as message_exception: raise MessageException(value=message_exception.value) except: record_exception() self.log_message('EXCEPTION - see syr.exception.log for details') result_ok = False return result_ok
def add_tags_to_message(crypto_message, tags): ''' Add tags to a message. >>> add_tags_to_message(None, None) False ''' # update the tags if tags is None or len(tags) <= 0: tags_added = False log_message('no tags need to be added to message') else: tags_added = crypto_message.add_tags_to_message(tags) log_message('tags added to message: {}'.format(tags_added)) if tags_added: log_message(tags) if DEBUGGING: log_message('DEBUG: logged taggged message headers in goodcrypto.message.utils.log') log_message_headers(crypto_message, tag='tagged message headers') return tags_added
def get_bundled_message(self, crypto_message): ''' Get the message which contains one or more bundled messages. ''' try: self.log_message('getting message which contains one or more messages') if self.DEBUGGING: self.log_message('DEBUG: logged bundled crypto headers in goodcrypto.message.utils.log') utils.log_message_headers(crypto_message, 'bundled crypto headers') if self.DEBUGGING: self.log_message('crypto message before getting inner message\n{}'.format(crypto_message.get_email_message().to_string())) inner_message = crypto_message.get_email_message().get_content() if self.DEBUGGING: self.log_message('raw inner message\n{}'.format(inner_message)) inner_crypto_message = CryptoMessage(email_message=EmailMessage(inner_message)) if options.verify_dkim_sig(): # verify dkim sig before any changes to message happen inner_crypto_message, dkim_sig_verified = decrypt_utils.verify_dkim_sig(inner_crypto_message) if options.dkim_delivery_policy() == DKIM_DROP_POLICY: self.log_message('verified bundled dkim signature ok: {}'.format(dkim_sig_verified)) elif dkim_sig_verified: self.log_message('verified bundled dkim signature') else: self.log_message('unable to verify bundled dkim signature, but dkim policy is to just warn') if self.DEBUGGING: self.log_message('DEBUG: logged bundled inner headers in goodcrypto.message.utils.log') utils.log_message_headers(crypto_message, 'bundled inner headers') original_sender = inner_crypto_message.get_email_message().get_header(ORIGINAL_FROM) original_recipient = inner_crypto_message.get_email_message().get_header(ORIGINAL_TO) original_subject = inner_crypto_message.get_email_message().get_header(mime_constants.SUBJECT_KEYWORD) # if this message is an internal message with a subject, then send it to the admin if (original_sender == inner_crypto_message.smtp_sender() and original_recipient == inner_crypto_message.smtp_recipient() and original_subject is not None): admin = get_admin_email() inner_crypto_message.set_smtp_recipient(admin) else: inner_crypto_message.set_smtp_sender(original_sender) inner_crypto_message.set_smtp_recipient(original_recipient) # remove the original keywords from the message inner_crypto_message.get_email_message().delete_header(ORIGINAL_FROM) inner_crypto_message.get_email_message().delete_header(ORIGINAL_TO) if self.DEBUGGING: self.log_message('DEBUG: logged inner crypto headers in goodcrypto.message.utils.log') utils.log_message_headers(inner_crypto_message, 'inner crypto headers') except Exception: record_exception() inner_crypto_message = None self.log_message('EXCEPTION - see syr.exception.log for details') return inner_crypto_message
def _extract_embedded_message(self, plaintext): ''' Extract an embedded message. If the message includes an Open PGP header, then save the plaintext in the email message. Otherwise, create a new email message from the embedded message. ''' extracted_embedded_message = False try: if self.DEBUGGING: self.log_message('embbedded message:\n{}'.format(plaintext)) encrypted_type = get_first_header( self.crypto_message.get_email_message().get_message(), PGP_ENCRYPTED_CONTENT_TYPE) if encrypted_type is None: old_message = self.crypto_message.get_email_message().get_message() new_message = plaintext_to_message(old_message, plaintext) self.crypto_message.get_email_message().set_message(new_message) self.crypto_message.set_crypted(True) self.log_message("created a new message from the plaintext") if self.DEBUGGING: self.log_message('logged final embedded message headers in goodcrypto.message.utils.log') utils.log_message_headers(self.crypto_message, tag='final embedded message headers') else: # this assumes an embedded mime message self.log_message("openpgp mime type: {}".format(encrypted_type)) embedded_message = EmailMessage(plaintext) self.crypto_message.set_email_message(embedded_message) self.crypto_message.set_crypted(True) extracted_embedded_message = True self.log_message("embedded message type is {}".format( embedded_message.get_message().get_content_type())) except Exception: record_exception() self.log_message('EXCEPTION - see syr.exception.log for details') return extracted_embedded_message
def add_history_and_remove(self, to_domain): ''' Add history records for the messages sent and then remove the associated file. ''' def get_addendum_value(addendum, keyword): value = addendum[keyword] if is_string(value): value = value.strip() if value == 'True': value = True elif value == 'False': value = False return value if len(self.bundled_messages) > 0: for bundled_message in self.bundled_messages: self.log_message( 'message included in bundled: {}'.format(bundled_message)) with open(bundled_message) as f: original_message, addendum = parse_bundled_message( f.read()) self.log_message('addendum: {}'.format(addendum)) encrypted = get_addendum_value(addendum, constants.CRYPTED_KEYWORD) private_signed = get_addendum_value( addendum, constants.PRIVATE_SIGNED_KEYWORD) clear_signed = get_addendum_value( addendum, constants.CLEAR_SIGNED_KEYWORD) dkim_signed = get_addendum_value( addendum, constants.DKIM_SIGNED_KEYWORD) if encrypted or private_signed or clear_signed or dkim_signed: sender = get_addendum_value( addendum, mime_constants.FROM_KEYWORD) recipient = get_addendum_value( addendum, mime_constants.TO_KEYWORD) verification_code = get_addendum_value( addendum, constants.VERIFICATION_KEYWORD) crypto_message = CryptoMessage( email_message=EmailMessage(original_message)) crypto_message.set_smtp_sender(sender) crypto_message.set_smtp_recipient(recipient) crypto_message.set_crypted(encrypted) crypto_message.set_crypted_with( addendum[constants.CRYPTED_WITH_KEYWORD]) crypto_message.set_metadata_crypted(True) crypto_message.set_metadata_crypted_with( self.crypted_with) crypto_message.set_private_signed(private_signed) if private_signed: if encrypted: crypto_message.add_private_signer({ constants.SIGNER: sender, constants.SIGNER_VERIFIED: True }) crypto_message.add_private_signer({ constants.SIGNER: get_metadata_address(sender), constants.SIGNER_VERIFIED: True }) crypto_message.set_clear_signed(clear_signed) if clear_signed: if encrypted: crypto_message.add_clear_signer({ constants.SIGNER: sender, constants.SIGNER_VERIFIED: True }) else: crypto_message.add_clear_signer({ constants.SIGNER: get_metadata_address(sender), constants.SIGNER_VERIFIED: True }) crypto_message.set_dkim_signed(dkim_signed) if dkim_signed: crypto_message.set_dkim_sig_verified(True) history.add_outbound_record(crypto_message, verification_code) self.log_message( 'added outbound history record from {}'.format( sender)) if self.DEBUGGING: self.log_message( 'logged headers in goodcrypto.message.utils.log' ) utils.log_message_headers(crypto_message, tag='bundled headers') if os.path.exists(bundled_message): os.remove(bundled_message) else: self.log_message( 'tried to delete message after bundling it, but message no longer exists on disk' ) else: self.log_message('no bundled messages')
def process_message(self): ''' If the message is encrypted, try to decrypt it. If it's not encrypted, add a warning about the dangers of unencrypted messages. See unittests for usage as the test set up is too complex for a doctest. ''' try: filtered = decrypted = False from_user = self.crypto_message.smtp_sender() to_user = self.crypto_message.smtp_recipient() if options.verify_dkim_sig(): self.crypto_message, dkim_sig_verified = decrypt_utils.verify_dkim_sig( self.crypto_message) if options.dkim_delivery_policy() == DKIM_DROP_POLICY: self.log_message('verified dkim signature ok: {}'.format( dkim_sig_verified)) elif dkim_sig_verified: self.log_message('verified dkim signature') else: self.log_message( 'unable to verify dkim signature, but dkim policy is to just warn' ) self.log_message( "checking if message from {} to {} needs decryption".format( from_user, to_user)) if Decrypt.DEBUGGING: self.log_message( 'logged original message headers in goodcrypto.message.utils.log' ) utils.log_message_headers(self.crypto_message, tag='original message headers') header_keys = HeaderKeys() if options.auto_exchange_keys(): header_contains_key_info = header_keys.manage_keys_in_header( self.crypto_message) else: header_contains_key_info = header_keys.keys_in_header( self.crypto_message) if self.crypto_message.is_dropped(): decrypted = False self.log_message( "message dropped because of bad key in header") self.log_message( 'logged dropped message headers in goodcrypto.message.utils.log' ) utils.log_message_headers(self.crypto_message, tag='dropped message headers') else: message_char_set, __ = get_charset( self.crypto_message.get_email_message()) self.log_message( 'message char set: {}'.format(message_char_set)) if self.crypto_message.get_email_message().is_probably_pgp(): decrypted, decrypted_with = self.decrypt_message() if not decrypted and self.crypto_message.is_metadata_crypted( ): tags.add_metadata_tag(self.crypto_message) self.log_message( "message only encrypted with metadata key") else: decrypt_utils.verify_clear_signed(from_user, self.crypto_message) if self.crypto_message.is_metadata_crypted(): tags.add_metadata_tag(self.crypto_message) self.log_message( "message only encrypted with metadata key") else: tags.add_unencrypted_warning(self.crypto_message) filtered = True self.log_message( "message doesn't appear to be encrypted at all") # create a private key for the recipient if there isn't one already add_private_key(to_user) self.need_to_send_metadata_key = ( # if the metadata wasn't encrypted not self.crypto_message.is_metadata_crypted() and # but the sender's key was in the header so we know the sender uses GoodCrypto private server header_contains_key_info and # and we don't have the sender's metadata key len( contacts.get_encryption_names( get_metadata_address(email=from_user))) < 1) self.log_message('need to send metadata key: {}'.format( self.need_to_send_metadata_key)) self.log_message('message content decrypted: {}'.format(decrypted)) # finally save a record so the user can verify the message was received securely if (decrypted or self.crypto_message.is_metadata_crypted() or self.crypto_message.is_private_signed() or self.crypto_message.is_clear_signed()): decrypt_utils.add_history_and_verification(self.crypto_message) if decrypted or self.crypto_message.is_metadata_crypted(): self.crypto_message.set_crypted(True) if not decrypted: self.log_message('metadata tag: {}'.format( self.crypto_message.get_tag())) analyzer = OpenPGPAnalyzer() if analyzer.is_encrypted( self.crypto_message.get_email_message().get_content()): tags.add_extra_layer_warning(self.crypto_message) else: self.crypto_message.set_crypted(False) decrypted_tags_filtered = tags.add_decrypted_tags_to_message( self.crypto_message) if decrypted_tags_filtered: filtered = True if filtered and not self.crypto_message.is_filtered(): self.crypto_message.set_filtered(True) self.log_message( "finished adding tags to decrypted message; filtered: {}". format(self.crypto_message.is_filtered())) self.log_message("message originally encrypted with: {}".format( self.crypto_message.get_crypted_with())) self.log_message("metadata originally encrypted with: {}".format( self.crypto_message.get_metadata_crypted_with())) decrypt_utils.re_mime_encode(self.crypto_message) if self.DEBUGGING: self.log_message( 'logged final decrypted headers in goodcrypto.message.utils.log' ) utils.log_message_headers(self.crypto_message, tag='final decrypted headers') self.log_message( ' final status: filtered: {} decrypted: {}'.format( self.crypto_message.is_filtered(), self.crypto_message.is_crypted())) except MessageException as message_exception: self.log_message(message_exception) raise MessageException(value=message_exception.value) except: record_exception() self.log_message('EXCEPTION - see syr.exception.log for details') return self.crypto_message
def decrypt_metadata(self): ''' Decrypt the wrapper message that protects metadata. ''' dkim_sig_verified = False from_user = self.crypto_message.smtp_sender() to_user = self.crypto_message.smtp_recipient() self.log_message("decrypting metadata protected message from {} to {}".format(from_user, to_user)) if self.DEBUGGING: self.log_message('DEBUG: logged original metadata headers in goodcrypto.message.utils.log') utils.log_message_headers(self.crypto_message, tag='original metadata headers') if options.verify_dkim_sig(): # verify dkim sig before any changes to message happen self.crypto_message, dkim_sig_verified = decrypt_utils.verify_dkim_sig(self.crypto_message) if options.dkim_delivery_policy() == DKIM_DROP_POLICY: self.log_message('verified metadata dkim signature ok: {}'.format(dkim_sig_verified)) elif dkim_sig_verified: self.log_message('verified metadata dkim signature') else: self.log_message('unable to verify metadata dkim signature, but dkim policy is to just warn') # the metadata wrapper always includes its own pub key in the header # and it must be imported if we don't already have it auto_exchange_keys = options.auto_exchange_keys() options.set_auto_exchange_keys(True) header_keys = HeaderKeys() # import the key if it's new, and # verify the key matches the sender's email address and our database header_keys.manage_keys_in_header(self.crypto_message) new_metadata_key = header_keys.new_key_imported_from_header() options.set_auto_exchange_keys(auto_exchange_keys) # before we change anything, send our metadata key to the recipient # this could result in a sending the key twice, but until we keep a # long term record of who we've sent the key, this is better than not sending it if new_metadata_key: metadata_key_sent = send_metadata_key(from_user, to_user) self.log_message('sent metadata key: {}'.format(metadata_key_sent)) decrypt = Decrypt(self.crypto_message) decrypted, decrypted_with = decrypt.decrypt_message(filter_msg=False) inner_crypto_message = decrypt.get_crypto_message() if inner_crypto_message.is_dropped(): self.log_message('public metadata wrapper dropped') wrapped_crypto_message = inner_crypto_message else: if decrypted: wrapped_crypto_message = self.get_bundled_message(inner_crypto_message) wrapped_crypto_message.set_metadata_crypted(True) wrapped_crypto_message.set_metadata_crypted_with(decrypted_with) self.log_message('created decrypted wrapped message') if self.DEBUGGING: self.log_message('DEBUG: logged decrypted wrapped headers in goodcrypto.message.utils.log') utils.log_message_headers(wrapped_crypto_message, tag='decrypted wrapped headers') else: # if it's not encypted, then drop the message inner_crypto_message.drop(True) wrapped_crypto_message = inner_crypto_message self.log_message('public metadata wrapper message not encrypted so dropping message') # use the new inner crypto message self.crypto_message = wrapped_crypto_message self.log_message("metadata decrypted with: {}".format( self.crypto_message.get_metadata_crypted_with())) return dkim_sig_verified
def get_tags(crypto_message, filtered=False): ''' Get tags to add to message. Test extreme cases. >>> tags, filtered = get_tags(None) >>> tags is None True >>> filtered False ''' def get_short_tags(tags, possible_tags): ''' Get the short tags for a message. ''' # only keep the basic tags for tag in possible_tags: tag_ready = False if (MESSAGE_VERIFICATION_PREFIX in tag or MESSAGE_VERIFY_PREFIX in tag): tag_ready = True elif (RECEIVED_CONTENT_PRIVATELY in tag or RECEIVED_FULL_MESSAGE_PRIVATELY in tag): # keep the tag simple m = re.match('^(.*?), but.*', tag) if m: tag = m.group(1) tag_ready = True if tag_ready and tag not in tags: tags.append(tag) return tags crypt_tags = None add_long_tags = options.add_long_tags() try: # if we have something to say, it's still been filtered if filtered and not crypto_message.is_filtered(): crypto_message.set_filtered(True) log_message("filtered: {}".format(crypto_message.is_filtered())) if DEBUGGING: log_message('DEBUG: logged tagged and filtered headers in goodcrypto.message.utils.log') log_message_headers(crypto_message, tag='tagged and filtered headers') tags = crypto_message.get_error_tags() if add_long_tags: long_tags = crypto_message.get_tags() for tag in long_tags: if tag not in tags: tags.append(tag) else: possible_tags = crypto_message.get_tags() if len(possible_tags) > 0: tags = get_short_tags(tags, possible_tags) log_message('tags: {}'.format(tags)) total_tags = len(tags) if total_tags > 0: # look for the verification tag verify_tag = None reordered_tags = [] for count in range(total_tags): tag = tags[count] if (MESSAGE_VERIFICATION_PREFIX in tag or MESSAGE_VERIFY_PREFIX in tag): verify_tag = tag else: reordered_tags.append(tag) # add the verify tag last in the list if verify_tag is not None: reordered_tags.append(verify_tag) tags = reordered_tags if len(tags) > 2: new_tag = '{}:\n'.format(TAG_PREFIX) else: new_tag = '{} -'.format(TAG_PREFIX) for count in range(total_tags): tag = tags[count] if len(tags) > 2: new_tag += ' {}) '.format(count + 1) else: new_tag += ' ' new_tag += tag if (not new_tag.endswith('.') and MESSAGE_VERIFICATION_PREFIX not in tag and MESSAGE_VERIFY_PREFIX not in tag): new_tag += '.' if len(tags) > 2: new_tag += '\n' else: new_tag = '' if len(new_tag) > 0: crypt_tags = new_tag filtered = crypto_message.is_filtered() except Exception: record_exception() log_message('EXCEPTION - see syr.exception.log for details') log_message('crypt tags: {}'.format(crypt_tags)) return crypt_tags, filtered
def process_message(self): ''' If the message is encrypted, try to decrypt it. If it's not encrypted, add a warning about the dangers of unencrypted messages. See unittests for usage as the test set up is too complex for a doctest. ''' try: filtered = decrypted = False from_user = self.crypto_message.smtp_sender() to_user = self.crypto_message.smtp_recipient() if options.verify_dkim_sig(): self.crypto_message, dkim_sig_verified = decrypt_utils.verify_dkim_sig(self.crypto_message) if options.dkim_delivery_policy() == DKIM_DROP_POLICY: self.log_message('verified dkim signature ok: {}'.format(dkim_sig_verified)) elif dkim_sig_verified: self.log_message('verified dkim signature') else: self.log_message('unable to verify dkim signature, but dkim policy is to just warn') self.log_message("checking if message from {} to {} needs decryption".format(from_user, to_user)) if Decrypt.DEBUGGING: self.log_message('logged original message headers in goodcrypto.message.utils.log') utils.log_message_headers(self.crypto_message, tag='original message headers') header_keys = HeaderKeys() if options.auto_exchange_keys(): header_contains_key_info = header_keys.manage_keys_in_header(self.crypto_message) else: header_contains_key_info = header_keys.keys_in_header(self.crypto_message) if self.crypto_message.is_dropped(): decrypted = False self.log_message("message dropped because of bad key in header") self.log_message('logged dropped message headers in goodcrypto.message.utils.log') utils.log_message_headers(self.crypto_message, tag='dropped message headers') else: message_char_set, __ = get_charset(self.crypto_message.get_email_message()) self.log_message('message char set: {}'.format(message_char_set)) if self.crypto_message.get_email_message().is_probably_pgp(): decrypted, decrypted_with = self.decrypt_message() if not decrypted and self.crypto_message.is_metadata_crypted(): tags.add_metadata_tag(self.crypto_message) self.log_message("message only encrypted with metadata key") else: decrypt_utils.verify_clear_signed(from_user, self.crypto_message) if self.crypto_message.is_metadata_crypted(): tags.add_metadata_tag(self.crypto_message) self.log_message("message only encrypted with metadata key") else: tags.add_unencrypted_warning(self.crypto_message) filtered = True self.log_message("message doesn't appear to be encrypted at all") # create a private key for the recipient if there isn't one already add_private_key(to_user) self.need_to_send_metadata_key = ( # if the metadata wasn't encrypted not self.crypto_message.is_metadata_crypted() and # but the sender's key was in the header so we know the sender uses GoodCrypto private server header_contains_key_info and # and we don't have the sender's metadata key len(contacts.get_encryption_names(get_metadata_address(email=from_user))) < 1) self.log_message('need to send metadata key: {}'.format(self.need_to_send_metadata_key)) self.log_message('message content decrypted: {}'.format(decrypted)) # finally save a record so the user can verify the message was received securely if (decrypted or self.crypto_message.is_metadata_crypted() or self.crypto_message.is_private_signed() or self.crypto_message.is_clear_signed() ): decrypt_utils.add_history_and_verification(self.crypto_message) if decrypted or self.crypto_message.is_metadata_crypted(): self.crypto_message.set_crypted(True) if not decrypted: self.log_message('metadata tag: {}'.format(self.crypto_message.get_tag())) analyzer = OpenPGPAnalyzer() if analyzer.is_encrypted(self.crypto_message.get_email_message().get_content()): tags.add_extra_layer_warning(self.crypto_message) else: self.crypto_message.set_crypted(False) decrypted_tags_filtered = tags.add_decrypted_tags_to_message(self.crypto_message) if decrypted_tags_filtered: filtered = True if filtered and not self.crypto_message.is_filtered(): self.crypto_message.set_filtered(True) self.log_message("finished adding tags to decrypted message; filtered: {}".format(self.crypto_message.is_filtered())) self.log_message("message originally encrypted with: {}".format(self.crypto_message.get_crypted_with())) self.log_message("metadata originally encrypted with: {}".format(self.crypto_message.get_metadata_crypted_with())) decrypt_utils.re_mime_encode(self.crypto_message) if self.DEBUGGING: self.log_message('logged final decrypted headers in goodcrypto.message.utils.log') utils.log_message_headers(self.crypto_message, tag='final decrypted headers') self.log_message(' final status: filtered: {} decrypted: {}'.format( self.crypto_message.is_filtered(), self.crypto_message.is_crypted())) except MessageException as message_exception: self.log_message(message_exception) raise MessageException(value=message_exception.value) except: record_exception() self.log_message('EXCEPTION - see syr.exception.log for details') return self.crypto_message