Example #1
0
def get_hashcode(message):
    '''
        Gets a hashcode for a message.

        >>> from goodcrypto_tests.mail import message_utils
        >>> filename = message_utils.get_plain_message_name('basic.txt')
        >>> with open(filename) as input_file:
        ...     get_hashcode(input_file) is not None
        True
    '''

    hash_code = None
    try:
        from goodcrypto.mail.message.email_message import EmailMessage

        email_message = EmailMessage(message)
        message_string = email_message.to_string()
        if message_string == None:
            if isinstance(get_last_error(), Exception):
                raise MessageException("Invalid message")
            else:
                raise MessageException("Invalid message: {}".format(get_last_error()))
        else:
            charset, __ = get_charset(email_message)
            hash_code = sha224(bytearray(message_string, charset)).hexdigest().upper()
    except Exception as hash_exception:
        set_last_error(hash_exception)
        record_exception()

    return hash_code
def otp_filename(mailfrom, mailto):
    ''' Returns One Time Pad filename for email_addr.

        If there is no otp for email_addr, starts process to create one. '''

    # just use the email address part, the string with '@'
    __, mailfrom = email.utils.parseaddr(mailfrom)
    __, mailto = email.utils.parseaddr(mailto)

    users_dir = 'something unknown'
    user_dir = os.path.join(users_dir, mailfrom)
    if not os.path.exists(user_dir):
        raise MessageException('No directory for user: {0}'.format(mailfrom))

    peer_dir = os.path.join(users_dir, mailto)
    if not os.path.exists(peer_dir):
        raise MessageException('No directory for user, peer: {0}, {1}'.format(
            mailfrom, mailto))

    filename = os.path.join(users_dir, 'otp')
    if not os.path.exists(filename):

        thread = Thread(target=create_otp, args=(filename))
        thread.start()
        raise MessageException(
            'Creating otp file for user, peer: {0}, {1}'.format(
                mailfrom, mailto))

    return filename
Example #3
0
    def decrypt_message(self):
        ''' Decrypt a message for the original recipient. '''

        try:
            self.log_message('decrypting message')
            if self.DEBUGGING and self.crypto_message is not None:
                self.log_message(
                    'original message:\n{}'.format(self.crypto_message.get_email_message().to_string()))
            decrypt = Decrypt(self.crypto_message)
            self.crypto_message = decrypt.process_message()
            self.log_message('decrypted message: {}'.format(self.crypto_message.is_crypted()))

            # send our metadata key to the recipient if needed
            if decrypt.needs_metadata_key():
                self.log_message('need to send metadata key')
                metadata_key_sent = send_metadata_key(
                  self.crypto_message.smtp_sender(), self.crypto_message.smtp_recipient())
                self.log_message('sent metadata key: {}'.format(metadata_key_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')
            try:
                self.crypto_message.add_error_tag_once(SERIOUS_ERROR_PREFIX)
            except Exception:
                record_exception()
                self.log_message('EXCEPTION - see syr.exception.log for details')
            raise MessageException()
Example #4
0
    def process_message(self):
        '''
            If the message is an encrypted message to a single individual,
            then decrypt the message and send it to the recipient. If the
            message is an encrypted message that protects metadata and contains
            one or more messages inside, then decrypt the metadata, decrypt the
            inner message(s) and deliver each message to the intended recipient.
            If the message is not encrypted, then just add a warning about
            receiving unencrypted messages.

            See unittests for usage as the test set up is too complex for a doctest.
        '''

        try:
            filtered = decrypted = dkim_sig_verified = False

            from_user = self.crypto_message.smtp_sender()
            to_user = self.crypto_message.smtp_recipient()

            # see if there's a metadata wrapper
            if is_metadata_address(from_user) and is_metadata_address(to_user):
                dkim_sig_verified = self.decrypt_metadata()

            # if only one of to/from is a metadata address, the message is bad
            elif is_metadata_address(from_user) or is_metadata_address(to_user):
                self.log_message("bad envelope: from user: {} to_user: {}".format(from_user, to_user))
                self.crypto_message.drop()

            if not self.crypto_message.is_dropped():
                # now see if there's a bundle of padded messages
                if (is_metadata_address(self.crypto_message.smtp_sender()) and
                    is_metadata_address(self.crypto_message.smtp_recipient())):

                    self.unbundle_wrapped_messages()

                else:
                    if not dkim_sig_verified and options.verify_dkim_sig():
                        self.crypto_message, dkim_sig_verified = decrypt_utils.verify_dkim_sig(
                            self.crypto_message)
                        self.log_message('dkim signature must be verified: {}'.format(dkim_sig_verified))

                    self.decrypt_message()

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

        return self.crypto_message
    def _create_good_message_from_bad(self, source):
        '''
            Create a good message from a source that contains a corrupted message.

            >>> from goodcrypto_tests.mail.message_utils import get_plain_message_name
            >>> with open(get_plain_message_name('bad-basic.txt')) as input_file:
            ...    email_message = EmailMessage()
            ...    email_message._create_good_message_from_bad(input_file)
        '''

        try:
            # start with a fresh message
            self._message = Message()

            if isinstance(source, IOBase):
                source.seek(os.SEEK_SET)
                message_string = source.read()
            else:
                message_string = source

            body_text = self._create_new_header(message_string)
            if body_text:
                self._create_new_body_text(body_text)

        except Exception as message_exception:
            self.log_message(message_exception)
            record_exception()
            raise MessageException(message_exception)
def encrypt_mime_message(crypto_message, crypto, users_dict):
    '''
        Encrypt a MIME message by encrypting the entire original message and creating a new
        plain text message with the payload the encrypted original message. This reduces the
        metadata someone can collect, but it does require the receiving end decrypt the
        message and create a new readable message from the encrypted original message.
    '''
    def copy_item_from_original(msg, keyword):
        value = crypto_message.get_email_message().get_header(keyword)
        if value is not None:
            msg.__setitem__(keyword, value)

    log_message('encrypting a mime message')
    message = crypto_message.get_email_message().get_message()
    log_message('content type: {}'.format(message.get_content_type()))

    #  Encrypt the whole message and add it to the body text
    #  This removes important meta data. The recieving end must
    #  decrypt the message, and then create a new message with the original structure.
    email_message = crypto_message.get_email_message()
    charset, __ = get_charset(email_message)
    log_message('about to encrypt {} mime message'.format(charset))
    ciphertext, error_message = encrypt_byte_array(
        bytearray(email_message.to_string(), charset), crypto, users_dict)

    if ciphertext is not None and len(ciphertext) > 0:
        from_user = users_dict[FROM_KEYWORD]
        convert_encrypted_mime_message(crypto_message, ciphertext, from_user,
                                       users_dict[TO_KEYWORD])
        set_sigs(crypto_message, from_user, users_dict[PASSCODE_KEYWORD])

    elif error_message is not None:
        raise MessageException(value=error_message)
def sign_mime_message(crypto_message, crypto, from_user_id, passcode):
    '''
        Sign a MIME message by signing the entire original message and creating a new
        plain text message with the payload the signed original message. This reduces the
        metadata someone can collect, but it does require the receiving end decrypt the
        message and create a new readable message from the signed original message.
    '''
    def copy_item_from_original(msg, keyword):
        value = crypto_message.get_email_message().get_header(keyword)
        if value is not None:
            msg.__setitem__(keyword, value)

    log_message('signing a mime message')
    message = crypto_message.get_email_message().get_message()
    log_message('content type: {}'.format(message.get_content_type()))

    #  Sign the whole message and add it to the body text
    #  This removes important meta data. The recieving end must
    #  decrypt the message, and then create a new message with the original structure.
    log_message('about to sign mime message')
    email_message = crypto_message.get_email_message()
    charset, __ = get_charset(email_message)
    ciphertext, error_message = crypto.sign(
        bytearray(email_message.to_string(), charset), from_user_id, passcode)

    if ciphertext is not None and len(ciphertext) > 0:
        convert_encrypted_mime_message(crypto_message, ciphertext,
                                       from_user_id,
                                       crypto_message.smtp_recipient())

    elif error_message is not None:
        raise MessageException(value=error_message)
Example #8
0
    def _encrypt_message_with_all(self, encryption_names):
        '''
            Encrypt the message with each encryption program.
        '''
        encrypted = fatal_error = False
        error_message = ''
        encrypted_with = []
        encrypted_classnames = []
        to_user = self.crypto_message.smtp_recipient()
        original_payload = self.crypto_message.get_email_message().get_message().get_payload()

        self._log_message("encrypting using {} with {}'s key".format(encryption_names, to_user))
        for encryption_name in encryption_names:
            encryption_classname = get_key_classname(encryption_name)
            if encryption_classname not in encrypted_classnames:
                try:
                    if options.require_key_verified():
                        __, key_ok, __ = contacts.get_fingerprint(to_user, encryption_name)
                        self._log_message("{} {} key verified: {}".format(to_user, encryption_name, key_ok))
                    else:
                        key_ok = True

                    if key_ok:
                        if self._encrypt_message(encryption_name):
                            encrypted_classnames.append(encryption_classname)
                            encrypted_with.append(encryption_name)
                    else:
                        error_message += i18n('You need to verify the {encryption} key for {email} before you can use it.'.format(
                            encryption=encryption_name, email=to_user))
                        self._log_message(error_message)
                except MessageException as message_exception:
                    fatal_error = True
                    error_message += message_exception.value
                    self._log_exception(error_message)
                    break

        # if the user has encryption software defined, then the message
        # must be encrypted or bounced to the sender
        if len(encrypted_classnames) > 0:
            encrypted = True
        else:
            MSG_NOT_SET = i18n('Message not sent to {email} because there was a problem encrypting.'.format(
                email=to_user))
            fatal_error = True
            if error_message is None or len(error_message) <= 0:
                error_message = '{} {}\n{}'.format(MSG_NOT_SET,
                    i18n("It's possible the recipient's key is missing."),
                    self.POSSIBLE_ENCRYPT_SOLUTION)
            else:
                error_message = '{} {}'.format(MSG_NOT_SET, error_message)

            # restore the payload
            self.crypto_message.get_email_message().get_message().set_payload(original_payload)

        if fatal_error:
            self._log_message('raising message exception in _encrypt_message_with_all')
            self._log_message(error_message)
            raise MessageException(value=error_message)

        return encrypted_with
Example #9
0
    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
Example #10
0
    def _get_recipient_encryption_software(self):
        '''
            Get the software to decrypt a message for the recipient (internal use only).

            If the user doesn't have a key, then configure one and return None.
        '''

        encryption_software = None

        if self.crypto_message is None:
            self.log_message("missing crypto_message".format(
                self.crypto_message))
        else:
            try:
                from_user = self.crypto_message.smtp_sender()
                to_user = self.crypto_message.smtp_recipient()
                encryption_software = contacts.get_encryption_names(to_user)
                if len(encryption_software) > 0:
                    self.log_message(
                        "encryption software: {}".format(encryption_software))
                elif email_in_domain(
                        to_user) and options.create_private_keys():
                    add_private_key(to_user,
                                    encryption_software=encryption_software)
                    self.log_message(
                        "started to create a new {} key for {}".format(
                            encryption_software, to_user))
                else:
                    self.log_message(
                        "no encryption software for {}".format(to_user))
                    self.crypto_message.add_error_tag_once(
                        i18n(
                            'Message appears encrypted, but {email} does not use any known encryption'
                            .format(email=to_user)))
                    report_unable_to_decrypt(
                        to_user,
                        self.crypto_message.get_email_message().to_string())
            except CryptoException as crypto_exception:
                raise CryptoException(crypto_exception.value)
            except Exception as IOError:
                utils.log_crypto_exception(MessageException(format_exc()))
                record_exception()
                self.log_message(
                    'EXCEPTION - see syr.exception.log for details')
                try:
                    self.crypto_message.add_error_tag_once(
                        SERIOUS_ERROR_PREFIX)
                except Exception:
                    record_exception()
                    self.log_message(
                        'EXCEPTION - see syr.exception.log for details')

        return encryption_software
Example #11
0
    def _process_no_metadata_message(self, to_user):
        '''
            Handle a message without metadata protection.
        '''
        if self.crypto_message.is_crypted():
            if self.DEBUGGING:
                self._log_message('full encrypted message:\n{}'.format(
                   self.crypto_message.get_email_message().to_string()))

        elif contacts.never_encrypt_outbound(to_user):
            self._log_message('{} not using encryption'.format(to_user))
            self.crypto_message.add_error_tag_once(USE_ENCRYPTION_WARNING)

        elif options.require_outbound_encryption():
            self._log_message('message not sent because global encryption required')
            raise MessageException(value=self.MUST_ENCRYPT_ALL_MAIL.format(to_email=to_user))

        elif contacts.always_encrypt_outbound(to_user):
            self._log_message('message not sent because encryption required for {}'.format(to_user))
            raise MessageException(value=self.MUST_ENCRYPT_MAIL_TO_USER.format(to_email=to_user))

        else:
            self.crypto_message.add_error_tag_once(USE_ENCRYPTION_WARNING)
Example #12
0
    def _process_plain_message(self, from_user, to_user, never_encrypt, encryption_names):
        '''
            Handle the initial processing of a message that does not need encryption.
        '''

        self._log_message('{} uses {} known encryption'.format(to_user, len(encryption_names)))

        if not self.ready_to_protect_metadata:
            if never_encrypt:
                self._log_message('{} set not to use any encryption'.format(to_user))

            # fail if encryption is required globally or for this individual
            elif options.require_outbound_encryption():
                self._log_message('message not sent because global encryption required')
                raise MessageException(value=self.MUST_ENCRYPT_ALL_MAIL.format(to_email=to_user))

            elif contacts.always_encrypt_outbound(to_user):
                self._log_message('message not sent because encryption required for {}'.format(to_user))
                raise MessageException(value=self.MUST_ENCRYPT_MAIL_TO_USER.format(to_email=to_user))

        # see if message must be clear signed
        if options.clear_sign_email():
            self._clear_sign_crypto_message(from_user)

        # add the public key so the receiver can use crypto with us in the future
        if options.auto_exchange_keys():
            self.crypto_message.add_public_key_to_header(from_user)
            self._log_message('added {} public key to header'.format(from_user))

        # if we're not exchanging keys, but we are creating them,
        # then start the process in background
        elif options.create_private_keys():
            add_private_key(from_user)
            self._log_message('created private key for {} if needed'.format(from_user))

        self.crypto_message.set_filtered(True)
    def _manage_key_header(self, from_user, crypto_message, encryption_name,
                           key_block):
        '''
            Manage a key in the header for the encryption software (internal use only).
        '''

        tag = None

        try:
            if key_block == None or len(key_block.strip()) <= 0:
                self.log_message(
                    "no {} public key in header".format(encryption_name))
            else:
                if self.DEBUGGING:
                    self.log_message("{} key from message:\n{}".format(
                        encryption_name, key_block))

                key_crypto = KeyFactory.get_crypto(
                    encryption_name,
                    crypto_software.get_key_classname(encryption_name))
                if key_crypto is None:
                    id_fingerprint_pairs = None
                    self.log_message(
                        'no key crypto for {}'.format(encryption_name))
                else:
                    id_fingerprint_pairs = key_crypto.get_id_fingerprint_pairs(
                        key_block)

                if id_fingerprint_pairs is None or len(
                        id_fingerprint_pairs) <= 0:
                    tag = None
                    self.log_message('no user keys in key block')
                else:
                    tag = self._manage_public_key(from_user, crypto_message,
                                                  key_crypto, key_block,
                                                  id_fingerprint_pairs)

                if tag is not None and len(tag.strip()) > 0:
                    crypto_message.add_tag_once(tag)

        except MessageException as message_exception:
            raise MessageException(value=message_exception.value)

        except:
            record_exception()
            self.log_message('EXCEPTION - see syr.exception.log for details')

        return tag
def sign_text_message(crypto_message, crypto, from_user_id, passcode):
    '''
        Sign a plain text message.
    '''
    def sign_text_part(content, charset, crypto, from_user_id, passcode):
        if DEBUGGING: log_message('type of content: {}'.format(type(content)))

        ciphertext, error_message = crypto.sign(bytearray(content, charset),
                                                from_user_id, passcode)

        #  if we signed successfully, save the results
        if ciphertext != None and len(ciphertext) > 0:
            crypto_message.get_email_message().get_message().set_payload(
                ciphertext)
            result_ok = True
        else:
            result_ok = False

        return result_ok, error_message

    log_message('signing a text message')

    error_message = None
    email_message = crypto_message.get_email_message()
    if is_multipart_message(email_message):
        for part in email_message.walk():
            charset, __ = get_charset(part)
            result_ok, error_message = sign_text_part(part.get_payload(),
                                                      charset, crypto,
                                                      from_user_id, passcode)
            if not result_ok:
                break
    else:
        final_content = email_message.get_content()
        if DEBUGGING: log_message('  content:\n{!s}'.format(final_content))

        charset, __ = get_charset(final_content)
        result_ok, error_message = sign_text_part(final_content, charset,
                                                  crypto, from_user_id,
                                                  passcode)

    #  if we signed successfully, save the results
    if result_ok:
        crypto_message.set_filtered(True)
        crypto_message.set_crypted(True)

    elif error_message is not None:
        raise MessageException(value=error_message)
Example #15
0
    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')
Example #16
0
def packetize(crypto_message, encrypted_with, verification_code):
    ''' Packetize for later delivery. '''

    try:
        message_name = None
        domain = parse_domain(crypto_message.smtp_recipient())
        dirname = os.path.join(get_packet_directory(), '.{}'.format(domain))
        if not os.path.exists(dirname):
            os.mkdir(dirname, SafeDirPermissions)
            log_message('created packet queue for {}'.format(domain))
        crypto_message.set_processed(True)

        encrypted_names = ''
        if crypto_message.is_crypted():
            for encrypted_name in encrypted_with:
                if len(encrypted_names) > 0:
                    encrypted_names += ', '
                encrypted_names += encrypted_name
            log_message('queued message encrypted with: {}'.format(encrypted_names))
        message_name = get_unique_filename(dirname, constants.MESSAGE_PREFIX, constants.MESSAGE_SUFFIX)
        with open(message_name, 'wt') as f:
            f.write(crypto_message.get_email_message().to_string())
            f.write('{}'.format(constants.START_ADDENDUM))
            f.write('{}: {}\n'.format(mime_constants.FROM_KEYWORD, crypto_message.smtp_sender()))
            f.write('{}: {}\n'.format(mime_constants.TO_KEYWORD, crypto_message.smtp_recipient()))
            f.write('{}: {}\n'.format(constants.CRYPTED_KEYWORD, crypto_message.is_crypted()))
            f.write('{}: {}\n'.format(constants.CRYPTED_WITH_KEYWORD, encrypted_names))
            f.write('{}: {}\n'.format(constants.PRIVATE_SIGNED_KEYWORD, crypto_message.is_private_signed()))
            f.write('{}: {}\n'.format(constants.CLEAR_SIGNED_KEYWORD, crypto_message.is_clear_signed()))
            f.write('{}: {}\n'.format(constants.DKIM_SIGNED_KEYWORD, crypto_message.is_dkim_signed()))
            f.write('{}: {}\n'.format(constants.VERIFICATION_KEYWORD, verification_code))
            f.write('{}'.format(constants.END_ADDENDUM))
        log_message('packetized message filename: {}'.format(os.path.basename(message_name)))
    except:
        message_name = None
        crypto_message.set_processed(False)
        error_message = i18n('Unable to packetize message due to an unexpected error.')
        log_message(error_message)
        log_message('EXCEPTION - see syr.exception.log for details')
        record_exception()
        raise MessageException(value=error_message)

    return message_name
    def is_message_valid(self):
        '''
             Returns true if the message is parsable, else false.

             If a MIME message is not parsable, you should still be able to process it.
             As we find different errors in messages, we should make sure this
             method catches them.

            >>> from goodcrypto_tests.mail.message_utils import get_basic_email_message
            >>> good_message = get_basic_email_message()
            >>> validator = Validator(good_message)
            >>> validator.is_message_valid()
            True
        '''

        is_valid = False
        self.why = None

        if self.email_message is None:
            raise MessageException('Message is None')
        else:
            try:
                self.email_message.write_to(StringIO())
                if Validator.DEBUGGING:
                    self.log_message("message after check:\n{}".format(
                        self.email_message.to_string()))

                self._check_content(self.email_message)
                is_valid = True

            except Exception:
                is_valid = False

                #  we explicitly want to catch everything here, even NPE
                record_exception()
                self.log_message(
                    'EXCEPTION - see syr.exception.log for details')

        self.log_message('message is valid: {}'.format(is_valid))

        return is_valid
Example #18
0
    def _get_crypto_details(self, encryption_name, user_ids):
        '''
            Get the details needed to encrypt a message.
        '''

        from_user_id = to_user_id = passcode = None
        ready_to_encrypt = user_ids is not None and len(user_ids) > 0

        if ready_to_encrypt:
            to_user_id, ready_to_encrypt = self._get_to_crypto_details(encryption_name, user_ids)

        if ready_to_encrypt or options.auto_exchange_keys():
            from_user_id, passcode, error_message = self._get_from_crypto_details(encryption_name, user_ids)
            if ready_to_encrypt and passcode is None and options.clear_sign_email():
                if error_message is not None:
                    error_message += i18n(' and clear signing is required.')
                    if options.create_private_keys():
                        error_message += '\n\n{}'.format(i18n(
                          "You should wait 5-10 minutes and try again. If that doesn't help, then contact your mail administrator."))
                    else:
                        error_message += '\n\n{}'.format(i18n(
                          'Ask your mail administrator to create a private key for you.'))

                self._log_exception(error_message)
                raise MessageException(error_message)

        if self.crypto_message is None or self.crypto_message.get_email_message() is None:
            charset = 'UTF-8'
        else:
            charset, __ = get_charset(self.crypto_message.get_email_message().get_message())
            self._log_message('char set in crypto message: {}'.format(charset))

        users_dict = {encrypt_utils.TO_KEYWORD: to_user_id,
                      encrypt_utils.FROM_KEYWORD: from_user_id,
                      encrypt_utils.PASSCODE_KEYWORD: passcode,
                      encrypt_utils.CHARSET_KEYWORD: charset}

        self._log_message('got crypto details and ready to encrypt: {}'.format(ready_to_encrypt))

        return users_dict, ready_to_encrypt
Example #19
0
    def _process_encrypt_message(self, encryption_names):
        '''
            Handle the initial processing of a message that needs encryption.
        '''
        inner_encrypted_with = []
        try:
            if self.DEBUGGING:
                self._log_message('message before encryption:\n{}'.format(
                  self.crypto_message.get_email_message().to_string()))
            self._log_message('trying to encrypt using {}'.format(encryption_names))
            inner_encrypted_with = self._encrypt_message_with_all(encryption_names)
            if self.DEBUGGING:
                self._log_message('message after encryption:\n{}'.format(
                  self.crypto_message.get_email_message().to_string()))
        except MessageException as message_exception:
            raise MessageException(value=message_exception.value)
        except Exception as exception:
            self._log_error(str(exception))
        except IOError as io_error:
            self._log_error(io_error.value)

        return inner_encrypted_with
Example #20
0
    def _get_to_crypto_details(self, encryption_name, user_ids):
        '''
            Get the recipient details needed to encrypt a message.
        '''

        from_user = self.crypto_message.smtp_sender()
        to_user = self.crypto_message.smtp_recipient()

        to_user_id = utils.get_user_id_matching_email(to_user, user_ids)
        if to_user_id is None:
            ready_to_encrypt = False
            self._log_message("No {} key for {}".format(encryption_name, to_user))
        else:
            try:
                contacts.is_key_ok(to_user_id, encryption_name)
                ready_to_encrypt = True
            except CryptoException as exception:
                to_user_id = None
                ready_to_encrypt = False
                self._log_exception(exception.value)
                self._log_message('raising message exception in _get_to_crypto_details')
                raise MessageException(value=exception.value)

        return to_user_id, ready_to_encrypt
    def _check_content(self, part):
        '''
             Make sure we can read the content.

            >>> from goodcrypto_tests.mail.message_utils import get_basic_email_message
            >>> good_message = get_basic_email_message()
            >>> validator = Validator(good_message)
            >>> validator._check_content(validator.email_message)
        '''

        content_type = part.get_header('Content-Type')
        self.log_message("MIME part content type: {}".format(content_type))
        content = part.get_content()
        if isinstance(content, MIMEMultipart):
            count = 0
            parts = content
            for sub_part in parts:
                self._check_content(sub_part)
                count += 1
            self.log_message('parts in message: {}'.format(count))
            if count != parts.getCount():
                self.why = "Unable to read all content. Reported: '{}', read: {}".format(
                    parts.getCount(), count)
                raise MessageException(self.why)
def encrypt_text_message(crypto_message, crypto, users_dict):
    '''
        Encrypt a plain text message.
    '''
    def encrypt_text_part(content, charset, crypto, users_dict):
        if DEBUGGING:
            log_message('type of content: {}'.format(type(content)))
            log_message('charset: {}'.format(charset))

        result_ok = True
        try:
            data = bytearray(content, charset)
        except UnicodeEncodeError as uee:
            error_message = str(uee)
            result_ok = False

        if result_ok:
            ciphertext, error_message = encrypt_byte_array(
                data, crypto, users_dict)

            #  if we encrypted successfully, save the results
            if ciphertext is not None and len(ciphertext) > 0:
                crypto_message.get_email_message().get_message().set_payload(
                    ciphertext)
                from_user = users_dict[FROM_KEYWORD]
                log_message('from user: {}'.format(from_user))
                log_message('passcode: {}'.format(
                    users_dict[PASSCODE_KEYWORD]))
                set_sigs(crypto_message, from_user,
                         users_dict[PASSCODE_KEYWORD])
                result_ok = True
            else:
                result_ok = False

        return result_ok, error_message

    log_message('encrypting a text message')

    error_message = None

    email_message = crypto_message.get_email_message()
    if is_multipart_message(email_message):
        for part in email_message.walk():
            charset, __ = get_charset(part)
            log_message(
                'char set while encrypting text part: {}'.format(charset))
            result_ok, error_message = encrypt_text_part(
                part.get_payload(), charset, crypto, users_dict)
            if not result_ok:
                break
    else:
        final_content = email_message.get_content()
        if DEBUGGING: log_message('  content:\n{!s}'.format(final_content))

        charset, __ = get_charset(final_content)
        log_message(
            'char set while encrypting text message: {}'.format(charset))
        result_ok, error_message = encrypt_text_part(final_content, charset,
                                                     crypto, users_dict)

    #  if we encrypted successfully, save the results
    if result_ok:
        crypto_message.set_filtered(True)
        crypto_message.set_crypted(True)

    elif error_message is not None:
        raise MessageException(value=error_message)
    def encrypt_message(crypto_message, data):

        encryption_ready = False
        encrypted_with = []

        # use the metadata address' encryption
        to_metadata_address = metadata.get_metadata_address(email=to_user)
        encryption_names = contacts.get_encryption_names(to_metadata_address)
        log_message('{} encryption software for: {}'.format(
            encryption_names, to_metadata_address))

        if len(encryption_names) < 1:
            error_message = i18n(
                'Unable to protect metadata because there are no encryption programs for {}.'
                .format(to_metadata_address))
            log_message(error_message)
            raise MessageException(value=error_message)
        else:
            # encrypt with each common encryption program
            for encryption_name in encryption_names:
                ready, to_metadata_address, __ = metadata.get_metadata_user_details(
                    to_user, encryption_name)
                log_message('to metadata ready {} '.format(ready))

                if ready:
                    ready, from_metadata_address, passcode = metadata.get_from_metadata_user_details(
                        from_user, encryption_name)
                    log_message('metadata keys ready {}'.format(ready))

                if ready:
                    log_message(
                        'protecting metadata with {}'.format(encryption_names))

                    # if we're ready with any key, then the encryption is ready
                    encryption_ready = True

                    from_user_id = get_email(from_metadata_address)
                    to_user_id = get_email(to_metadata_address)
                    crypto_message.set_smtp_sender(from_user_id)
                    crypto_message.set_smtp_recipient(to_user_id)

                    # use the default charset to prevent metadata leakage
                    charset, __ = get_charset(constants.DEFAULT_CHAR_SET)
                    users_dict = {
                        TO_KEYWORD: to_user_id,
                        FROM_KEYWORD: from_user_id,
                        PASSCODE_KEYWORD: passcode,
                        CHARSET_KEYWORD: charset
                    }

                    crypto = CryptoFactory.get_crypto(
                        encryption_name, get_classname(encryption_name))
                    ciphertext, error_message = encrypt_byte_array(
                        data, crypto, users_dict)
                    if ciphertext is not None and len(ciphertext) > 0:
                        crypto_message.get_email_message().get_message(
                        ).set_payload(ciphertext)

                        crypto_message.add_public_key_to_header(
                            users_dict[FROM_KEYWORD])
                        set_sigs(crypto_message, from_user_id, passcode)
                        crypto_message.set_filtered(True)
                        crypto_message.set_crypted(True)

                        # use the encrypted data for the next level of encryption
                        data = ciphertext

                        encrypted_with.append(encryption_name)
                    else:
                        log_message(
                            'unable to encrypt the metadata with {}'.format(
                                encryption_name))
                        raise MessageException(value=error_message)
                else:
                    log_message('unable to protect metadata with {}'.format(
                        encryption_name))

            return encryption_ready, encrypted_with
def create_protected_message(from_user, to_user, data, message_id):
    '''
        Create a new message that protects the metadata.
    '''
    def start_crypto_message():
        from goodcrypto.mail.message.crypto_message import CryptoMessage

        # start a new crypto message
        from_metadata_user = metadata.get_metadata_address(email=from_user)
        to_metadata_user = metadata.get_metadata_address(email=to_user)

        crypto_message = CryptoMessage()
        crypto_message.get_email_message().add_header(
            mime_constants.FROM_KEYWORD, from_metadata_user)
        crypto_message.get_email_message().add_header(
            mime_constants.TO_KEYWORD, to_metadata_user)
        crypto_message.get_email_message().add_header(
            mime_constants.MESSAGE_ID_KEYWORD, message_id)
        # include the timestamp because some MTAs/spam filters object if it's not set
        crypto_message.get_email_message().add_header(
            mime_constants.DATE_KEYWORD,
            datetime.utcnow().isoformat(str(' ')))

        return crypto_message

    def encrypt_message(crypto_message, data):

        encryption_ready = False
        encrypted_with = []

        # use the metadata address' encryption
        to_metadata_address = metadata.get_metadata_address(email=to_user)
        encryption_names = contacts.get_encryption_names(to_metadata_address)
        log_message('{} encryption software for: {}'.format(
            encryption_names, to_metadata_address))

        if len(encryption_names) < 1:
            error_message = i18n(
                'Unable to protect metadata because there are no encryption programs for {}.'
                .format(to_metadata_address))
            log_message(error_message)
            raise MessageException(value=error_message)
        else:
            # encrypt with each common encryption program
            for encryption_name in encryption_names:
                ready, to_metadata_address, __ = metadata.get_metadata_user_details(
                    to_user, encryption_name)
                log_message('to metadata ready {} '.format(ready))

                if ready:
                    ready, from_metadata_address, passcode = metadata.get_from_metadata_user_details(
                        from_user, encryption_name)
                    log_message('metadata keys ready {}'.format(ready))

                if ready:
                    log_message(
                        'protecting metadata with {}'.format(encryption_names))

                    # if we're ready with any key, then the encryption is ready
                    encryption_ready = True

                    from_user_id = get_email(from_metadata_address)
                    to_user_id = get_email(to_metadata_address)
                    crypto_message.set_smtp_sender(from_user_id)
                    crypto_message.set_smtp_recipient(to_user_id)

                    # use the default charset to prevent metadata leakage
                    charset, __ = get_charset(constants.DEFAULT_CHAR_SET)
                    users_dict = {
                        TO_KEYWORD: to_user_id,
                        FROM_KEYWORD: from_user_id,
                        PASSCODE_KEYWORD: passcode,
                        CHARSET_KEYWORD: charset
                    }

                    crypto = CryptoFactory.get_crypto(
                        encryption_name, get_classname(encryption_name))
                    ciphertext, error_message = encrypt_byte_array(
                        data, crypto, users_dict)
                    if ciphertext is not None and len(ciphertext) > 0:
                        crypto_message.get_email_message().get_message(
                        ).set_payload(ciphertext)

                        crypto_message.add_public_key_to_header(
                            users_dict[FROM_KEYWORD])
                        set_sigs(crypto_message, from_user_id, passcode)
                        crypto_message.set_filtered(True)
                        crypto_message.set_crypted(True)

                        # use the encrypted data for the next level of encryption
                        data = ciphertext

                        encrypted_with.append(encryption_name)
                    else:
                        log_message(
                            'unable to encrypt the metadata with {}'.format(
                                encryption_name))
                        raise MessageException(value=error_message)
                else:
                    log_message('unable to protect metadata with {}'.format(
                        encryption_name))

            return encryption_ready, encrypted_with

    try:
        log_message('creating a new message that protects the metadata')
        crypto_message = start_crypto_message()

        if data is None:
            log_message('no data to encrypt')
        else:
            ready, encrypted_with = encrypt_message(crypto_message, data)

            if crypto_message.is_crypted():
                crypto_message.set_metadata_crypted(True)
                crypto_message.set_metadata_crypted_with(encrypted_with)
                log_message(
                    'metadata encrypted with: {}'.format(encrypted_with))
                if DEBUGGING:
                    log_message('metadata message:\n{}'.format(
                        crypto_message.get_email_message().to_string()))
            elif not ready:
                error_message = i18n(
                    'Unable to protect metadata because a key is missing.')
                log_message(error_message)
                raise MessageException(value=error_message)
            else:
                error_message = i18n(
                    'Unable to protect metadata even though keys for both servers exist.'
                )
                log_message(error_message)
                raise MessageException(value=error_message)
    except MessageException as message_exception:
        log_message('raising MessageException')
        raise MessageException(value=message_exception.value)
    except:
        error_message = i18n(
            'Unable to protect metadata due to an unexpected error.')
        log_message(error_message)
        log_message('EXCEPTION - see syr.exception.log for details')
        record_exception()
        raise MessageException(value=error_message)

    return crypto_message
Example #25
0
    def process_message(self):
        '''
            Process a message and encrypt if possible,
            bounce if unable to encrypt and encryption required, or
            add a warning about the danger of sending unencrypted messages.
            Also, add clear sig and DKIM sig if user opted for either or both.
        '''

        filtered = encrypted = False
        inner_encrypted_with = []
        # a place for the original message in case metadata is protected
        original_crypto_message = None
        try:
            if self.crypto_message is None:
                self._log_message('no crypto message defined')
                self.crypto_message = CryptoMessage()
                from_user = to_user = None
            else:
                from_user = self.crypto_message.smtp_sender()
                to_user = self.crypto_message.smtp_recipient()

            self._log_message("trying to encrypt message from {} to {}".format(from_user, to_user))

            self.verification_code = gen_verification_code()
            self.ready_to_protect_metadata = is_ready_to_protect_metadata(from_user, to_user)

            contact = contacts.get(to_user)
            if contact is None:
                never_encrypt = True
                encryption_names = []

                if options.use_keyservers():
                    # if there's no contact, then start another job
                    # that can determine whether we can find a key
                    self._start_check_for_encryption(from_user, to_user)
            else:
                never_encrypt = contact.outbound_encrypt_policy == NEVER_ENCRYPT_OUTBOUND
                encryption_names = contacts.get_encryption_names(to_user)

            if len(encryption_names) <= 0 or never_encrypt:
                self._process_plain_message(from_user, to_user, never_encrypt, encryption_names)
            else:
                inner_encrypted_with = self._process_encrypt_message(encryption_names)

            if self.ready_to_protect_metadata:
                original_crypto_message = copy.copy(self.crypto_message)
                self._protect_metadata(from_user, to_user, inner_encrypted_with)

            else:
                self._process_no_metadata_message(to_user)

            if self.crypto_message.is_processed():
                self._log_message('message processed and awaiting bundling')
            else:
                self._finish_processing_message(inner_encrypted_with, original_crypto_message)

        except MessageException as message_exception:
            raise MessageException(value=message_exception.value)

        except Exception as IOError:
            record_exception()
            self._log_message('EXCEPTION - see syr.exception.log for details')

        if self.crypto_message is not None:
            self._log_message('  final status: filtered: {} encrypted: {}'.format(
                self.crypto_message.is_filtered(), self.crypto_message.is_crypted()))

        return self.crypto_message
    def keys_in_header(self, crypto_message):
        '''
            Return true if there are public keys in the message's header.
        '''
        header_contains_key_info = False
        try:
            good_key = True
            from_user = crypto_message.smtp_sender()

            accepted_crypto_packages = crypto_message.get_accepted_crypto_software(
            )
            if accepted_crypto_packages is not None and len(
                    accepted_crypto_packages) > 0:
                self.log_message(
                    "checking for {} keys".format(accepted_crypto_packages))
                for encryption_name in accepted_crypto_packages:
                    # see if there's a the key block for this encryption program
                    header_name = get_public_key_header_name(encryption_name)
                    key_block = get_multientry_header(
                        crypto_message.get_email_message().get_message(),
                        header_name)
                    # see if there's a plain key block
                    if ((key_block is None or len(key_block) <= 0)
                            and len(accepted_crypto_packages) == 1):
                        self.log_message(
                            "no {} public key in header so trying generic header"
                            .format(encryption_name))
                        key_block = get_multientry_header(
                            crypto_message.get_email_message().get_message(),
                            PUBLIC_KEY_HEADER)

                    if key_block is not None and len(key_block) > 0:
                        header_contains_key_info = True

                        user_ids = []
                        key_crypto = KeyFactory.get_crypto(
                            encryption_name,
                            crypto_software.get_key_classname(encryption_name))
                        if key_crypto is None:
                            id_fingerprint_pairs = None
                            self.log_message(
                                'no key crypto for {}'.format(encryption_name))
                        else:
                            id_fingerprint_pairs = key_crypto.get_id_fingerprint_pairs(
                                key_block)
                            for (user_id, __) in id_fingerprint_pairs:
                                user_ids.append(user_id)
                            self.log_message(
                                "key block includes key for {}".format(
                                    user_ids))

                        # don't consider a key valid if it's not from the sender
                        if from_user not in user_ids:
                            tag = notices.report_bad_header_key(
                                self.recipient_to_notify, from_user, user_ids,
                                encryption_name, crypto_message)
                            self.log_message('tag: {}'.format(tag))

                            if crypto_message.get_email_message(
                            ).is_probably_pgp():
                                crypto_message.drop(dropped=True)
                                self.log_message(
                                    'serious error in header so original message sent as attchment'
                                )
                                raise MessageException(value=i18n(tag))

                self.update_accepted_crypto(from_user,
                                            accepted_crypto_packages)

        except MessageException as message_exception:
            raise MessageException(value=message_exception.value)

        except:
            record_exception()
            self.log_message('EXCEPTION - see syr.exception.log for details')
            if crypto_message is not None:
                crypto_message.add_error_tag_once(self.UNEXPECTED_ERROR)

        return header_contains_key_info
Example #27
0
    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 _manage_public_key(self, from_user, crypto_message, key_crypto,
                           key_block, id_fingerprint_pairs):
        '''
            Manage a public key for the encryption software (internal use only).
        '''

        tag = None
        drop = False

        encryption_name = key_crypto.get_name()
        user_ids = []
        for (user_id, __) in id_fingerprint_pairs:
            user_ids.append(user_id)
        self.log_message("key block includes key for {}".format(user_ids))

        if from_user in user_ids:
            #  see if we already have a key for this ID or if it has expired
            saved_fingerprint, verified, __ = contacts.get_fingerprint(
                from_user, encryption_name)
            crypto_fingerprint, expiration = key_crypto.get_fingerprint(
                from_user)

            self.log_message("{} {} saved fingerprint {}".format(
                from_user, encryption_name, saved_fingerprint))
            self.log_message("{} {} crypto fingerprint {} expires {}".format(
                from_user, encryption_name, crypto_fingerprint, expiration))
            self.log_message("{} {} id fingerprint pairs {}".format(
                from_user, encryption_name, id_fingerprint_pairs))

            if crypto_fingerprint is None:
                if saved_fingerprint is None:
                    self.log_message("importing new key")
                    tag = self._import_new_key(from_user, encryption_name,
                                               key_block, id_fingerprint_pairs)
                else:
                    self.log_message("checking if key matches")
                    drop = crypto_message.get_email_message().is_probably_pgp()
                    key_matches, key_error = self._key_matches(
                        encryption_name, saved_fingerprint,
                        id_fingerprint_pairs)
                    self.log_message("key matches: {} / key error: {}".format(
                        key_matches, key_error))
                    if key_error:
                        tag = notices.report_error_verifying_key(
                            self.recipient_to_notify, from_user,
                            encryption_name, crypto_message)
                    else:
                        tag = notices.report_missing_key(
                            self.recipient_to_notify, from_user, key_matches,
                            id_fingerprint_pairs, crypto_message)
            else:
                if saved_fingerprint is None:
                    # remember the fingerprint for the future
                    saved_fingerprint = crypto_fingerprint

                if (strip_fingerprint(crypto_fingerprint).lower() ==
                        strip_fingerprint(saved_fingerprint).lower()):
                    key_matches, key_error = self._key_matches(
                        encryption_name, crypto_fingerprint,
                        id_fingerprint_pairs)
                    if key_error:
                        tag = notices.report_error_verifying_key(
                            self.recipient_to_notify, from_user,
                            encryption_name, crypto_message)
                    elif key_matches:
                        if key_crypto.fingerprint_expired(expiration):
                            drop = crypto_message.get_email_message(
                            ).is_probably_pgp()
                            tag = notices.report_expired_key(
                                self.recipient_to_notify, from_user,
                                encryption_name, expiration, crypto_message)
                        else:
                            self.log_message('  same fingerprint: {}'.format(
                                saved_fingerprint))
                            # warn user if unable to save the fingerprint, but proceed
                            if not self.update_fingerprint(
                                    from_user, encryption_name,
                                    crypto_fingerprint):
                                tag = notices.report_db_error(
                                    self.recipient_to_notify, from_user,
                                    encryption_name, crypto_message)
                    else:
                        if self.DEBUGGING:
                            self.log_message('{} key block\n{}'.format(
                                from_user,
                                key_crypto.export_public(from_user)))
                        drop = crypto_message.get_email_message(
                        ).is_probably_pgp()
                        tag = notices.report_replacement_key(
                            self.recipient_to_notify, from_user,
                            encryption_name, id_fingerprint_pairs,
                            crypto_message)
                else:
                    drop = crypto_message.get_email_message().is_probably_pgp()
                    tag = notices.report_mismatched_keys(
                        self.recipient_to_notify, from_user, encryption_name,
                        crypto_message)
        else:
            if len(user_ids) > 0:
                drop = crypto_message.get_email_message().is_probably_pgp()
                tag = notices.report_bad_header_key(self.recipient_to_notify,
                                                    from_user, user_ids,
                                                    encryption_name,
                                                    crypto_message)
            else:
                self.log_message(
                    'no importable keys found\n{}'.format(key_block))

        self.log_message('tag: {}'.format(tag))

        if drop:
            crypto_message.drop(dropped=True)
            self.log_message(
                'serious error in header so original message sent as attchment'
            )
            raise MessageException(value=i18n(tag))

        return tag
    def manage_keys_in_header(self, crypto_message):
        '''
            Manage all the public keys in the message's header.
        '''
        header_contains_key_info = False
        try:
            from_user = crypto_message.smtp_sender()
            self.recipient_to_notify = crypto_message.smtp_recipient()
            # all notices about a metadata address goes to the admin
            if is_metadata_address(self.recipient_to_notify):
                self.recipient_to_notify = get_admin_email()

            name, address = parse_address(from_user)
            if address is None or crypto_message is None or crypto_message.get_email_message(
            ) is None:
                self.log_message('missing data so cannot import key')
                self.log_message('   from user: {}'.format(from_user))
                self.log_message('   address: {}'.format(address))
                self.log_message(
                    '   crypto message: {}'.format(crypto_message))
                if crypto_message is not None:
                    self.log_message('   email message: {}'.format(
                        crypto_message.get_email_message()))
            else:
                accepted_crypto_packages = crypto_message.get_accepted_crypto_software(
                )
                if accepted_crypto_packages is None or len(
                        accepted_crypto_packages) <= 0:
                    self.log_message(
                        "checking for default key for {} <{}>".format(
                            name, address))
                    tag = self._manage_key_header(
                        address, crypto_message,
                        KeyFactory.get_default_encryption_name(),
                        PUBLIC_KEY_HEADER)
                else:
                    self.log_message("checking for {} keys".format(
                        accepted_crypto_packages))
                    for encryption_name in accepted_crypto_packages:
                        # see if there's a the key block for this encryption program
                        header_name = get_public_key_header_name(
                            encryption_name)
                        key_block = get_multientry_header(
                            crypto_message.get_email_message().get_message(),
                            header_name)
                        # see if there's a plain key block
                        if ((key_block is None or len(key_block) <= 0)
                                and len(accepted_crypto_packages) == 1):
                            self.log_message(
                                "no {} public key in header so trying generic header"
                                .format(encryption_name))
                            key_block = get_multientry_header(
                                crypto_message.get_email_message().get_message(
                                ), PUBLIC_KEY_HEADER)

                        tag = self._manage_key_header(address, crypto_message,
                                                      encryption_name,
                                                      key_block)
                    header_contains_key_info = True
                    self.update_accepted_crypto(from_user,
                                                accepted_crypto_packages)

        except MessageException as message_exception:
            self.log_message(message_exception.value)
            raise MessageException(value=message_exception.value)

        except:
            record_exception()
            self.log_message('EXCEPTION - see syr.exception.log for details')
            if crypto_message is not None:
                crypto_message.add_error_tag_once(self.UNEXPECTED_ERROR)

        self.log_message(
            'header_contains_key_info: {}'.format(header_contains_key_info))

        return header_contains_key_info
    def __init__(self, message_or_file=None):
        '''
             Creates an EmailMessage from a Message or a file.
             Non-mime messages are converted to MIME "text/plain".

             >>> email_message = EmailMessage()
             >>> type(email_message)
             <class 'goodcrypto.mail.message.email_message.EmailMessage'>
        '''

        self.bad_header_lines = []
        self.parser = Parser()

        self._last_charset = constants.DEFAULT_CHAR_SET
        self._log = self._message = None

        if message_or_file is None:
            self._message = Message()

        elif isinstance(message_or_file, Message):
            self._message = message_or_file

        elif isinstance(message_or_file, EmailMessage):
            self._message = message_or_file.get_message()

        else:
            try:
                if isinstance(message_or_file, IOBase)  or isinstance(message_or_file, StringIO):
                    self.log_message('about to parse a message from a file')
                    try:
                        self._message = self.parser.parse(message_or_file)
                        self.log_message('parsed message from file')
                    except TypeError:
                        message_or_file.seek(0, os.SEEK_SET)
                        self.parser = BytesParser()
                        self._message = self.parser.parse(message_or_file)
                        self.log_message('parsed message from file as bytes')
                else:
                    try:
                        self.log_message('about to parse a message from a string')
                        self._message = self.parser.parsestr(message_or_file)
                        self.log_message('parsed message from string')
                    except TypeError:
                        self.parser = BytesParser()
                        self._message = self.parser.parsebytes(message_or_file)
                        self.log_message('parsed message from bytes')

                if not self.validate_message():
                    self._create_good_message_from_bad(message_or_file)
            except Exception:
                try:
                    self.log_message('EXCEPTION - see syr.exception.log for details')
                    record_exception()

                    self._create_good_message_from_bad(message_or_file)

                    # if we still don't have a good message, then blow up
                    if not self.validate_message():
                        self.log_message('unable to create a valid message')
                        raise MessageException()
                except Exception:
                    record_exception()

        if self.DEBUGGING:
            try:
                self.log_message(self.to_string())
            except:
                pass