Exemple #1
0
    def _prep_from_user(self, private_user_ids, encryption_name):

        result_ok = private_user_ids is not None and len(private_user_ids) > 0
        if result_ok or options.create_private_keys():

            if result_ok:
                from_user_id = utils.get_user_id_matching_email(
                  self.crypto_message.smtp_sender(), private_user_ids)
            else:
                from_user_id = None

            if from_user_id is None:
                # add a key if there isn't one for the sender and we're creating keys
                if options.create_private_keys():
                    add_private_key(
                       self.crypto_message.smtp_sender(), encryption_software=encryption_name)
                else:
                    self._log_message("not creating a new {} key for {} because auto-create disabled".format(
                        encryption_name, from_user_id))
            else:
                self._log_message('found matching user id: {}'.format(from_user_id))

        self._log_message('_prep_from_user: {}'.format(result_ok))

        return result_ok
Exemple #2
0
    def _get_encrypt_error_message(self, users_dict, encryption_name):
        to_user = users_dict[encrypt_utils.TO_KEYWORD]
        from_user = users_dict[encrypt_utils.FROM_KEYWORD]
        passcode = users_dict[encrypt_utils.PASSCODE_KEYWORD]

        if options.clear_sign_email():
            if from_user is None or passcode is None:
                error_message = '{}  '.format(
                    i18n("Message not sent to {email} because currently there isn't a private {encryption} key for you and your mail administrator requires all encrypted messages also be clear signed.".format(
                        email=to_user, encryption=encryption_name)))
                if options.create_private_keys():
                    error_message += i18n("GoodCrypto is creating a private key now. You will receive email when your keys are ready so you can resend your message.")
                else:
                    error_message += '\n\n{}'.format(i18n(
                      'Ask your mail administrator to create a private key for you and then try resending the message.'))
            else:
                error_message = '{}\n{}'.format(
                    i18n(self.UNABLE_TO_ENCRYPT.format(
                        from_email=from_user, to_email=to_user, encryption=encryption_name)),
                    self.POSSIBLE_ENCRYPT_SOLUTION)
        else:
            error_message = '{}\n{}'.format(
                i18n(self.UNABLE_TO_ENCRYPT.format(
                    from_email=from_user, to_email=to_user, encryption=encryption_name)),
                self.POSSIBLE_ENCRYPT_SOLUTION)

        return error_message
    def save(self):
        '''
            Save the fingerprint in the database.

            Test extreme case.
            >>> set_fingerprint_class = SetFingerprint(None)
            >>> set_fingerprint_class.save()
            False
        '''

        self.result_ok, self.crypto_name, self.email, self.key_plugin = prep_sync(self.contacts_encryption)
        if self.result_ok:
            try:
                # the contact's crypto record must exist or we'll get into an infinite loop
                # because whenever we add a contact's encryption for an email in our supported domain,
                # then this class is activated so we can complete the configuration
                if self.contacts_encryption is None:
                    self.result_ok = False
                    log_message('no contact encryption record for {}'.format(self.email))
                else:
                    fingerprint, expiration_date = self.key_plugin.get_fingerprint(self.email)
                    if fingerprint is None:
                        self.result_ok = False
                        log_message('no {} fingerprint found for {}'.format(self.crypto_name, self.email))
                    else:
                        self.result_ok = True
                        try:
                            fingerprint = format_fingerprint(fingerprint)
                            if (self.contacts_encryption.fingerprint is not None and
                                self.contacts_encryption.fingerprint != fingerprint):
                                log_message('replaced old fingerprint {} with {} for {}'.format(
                                    self.contacts_encryption.fingerprint, fingerprint, self.email))

                            # if we created the key, then be sure it's verified
                            if utils.email_in_domain(self.email):
                                if self.contacts_encryption.source is None and create_private_keys():
                                    self.contacts_encryption.source = AUTO_GENERATED
                                if self.contacts_encryption.source == AUTO_GENERATED:
                                    self.contacts_encryption.verified = True

                            self.contacts_encryption.fingerprint = fingerprint
                            self.contacts_encryption.save()
                            log_message('set {} fingerprint for {} in database: {}'.format(
                                self.crypto_name, self.email, fingerprint))
                        except IntegrityError as ie:
                            self.result_ok = False
                            if 'insert or update on table "mail_contactscrypto" violates foreign key constraint' in str(ie):
                                log_message('{} crypto key no longer exists for {}'.format(self.crypto_name, self.email))
                            else:
                                raise
            except Exception as exception:
                record_exception()
                log_message('EXCEPTION - see syr.exception.log for details')
                self.result_ok = False

        return self.result_ok
    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
    def get_public_key_header(self, from_user):
        '''
            Get the public key header lines.

            >>> from goodcrypto_tests.mail.message_utils import get_plain_message_name
            >>> from goodcrypto.oce.test_constants import EDWARD_LOCAL_USER
            >>> auto_exchange = options.auto_exchange_keys()
            >>> options.set_auto_exchange_keys(True)
            >>> filename = get_plain_message_name('basic.txt')
            >>> with open(filename) as input_file:
            ...     crypto_message = CryptoMessage(email_message=EmailMessage(input_file))
            ...     key_block = crypto_message.get_public_key_header(EDWARD_LOCAL_USER)
            ...     key_block is not None
            ...     len(key_block) > 0
            True
            True
            >>> options.set_auto_exchange_keys(auto_exchange)
        '''

        header_lines = []
        if options.auto_exchange_keys():
            encryption_software_list = contacts.get_encryption_names(from_user)

            # if no crypto and we're creating keys, then do so now
            if (len(encryption_software_list) <= 0
                    and email_in_domain(from_user)
                    and options.create_private_keys()):

                add_private_key(from_user)
                self.log_message(
                    "started to create a new key for {}".format(from_user))
                encryption_software_list = contacts.get_encryption_names(
                    from_user)

            if len(encryption_software_list) > 0:
                self.log_message(
                    "getting header with public keys for {}: {}".format(
                        from_user, encryption_software_list))

                for encryption_software in encryption_software_list:
                    key_block = self.create_public_key_block(
                        encryption_software, from_user)
                    if len(key_block) > 0:
                        header_lines += key_block
        else:
            self.log_message("Warning: auto-exchange of keys is not active")

        return header_lines
    def get_public_key_header(self, from_user):
        '''
            Get the public key header lines.

            >>> from goodcrypto_tests.mail.message_utils import get_plain_message_name
            >>> from goodcrypto.oce.test_constants import EDWARD_LOCAL_USER
            >>> auto_exchange = options.auto_exchange_keys()
            >>> options.set_auto_exchange_keys(True)
            >>> filename = get_plain_message_name('basic.txt')
            >>> with open(filename) as input_file:
            ...     crypto_message = CryptoMessage(email_message=EmailMessage(input_file))
            ...     key_block = crypto_message.get_public_key_header(EDWARD_LOCAL_USER)
            ...     key_block is not None
            ...     len(key_block) > 0
            True
            True
            >>> options.set_auto_exchange_keys(auto_exchange)
        '''

        header_lines = []
        if options.auto_exchange_keys():
            encryption_software_list = contacts.get_encryption_names(from_user)

            # if no crypto and we're creating keys, then do so now
            if (len(encryption_software_list) <= 0 and
                email_in_domain(from_user) and
                options.create_private_keys()):

                add_private_key(from_user)
                self.log_message("started to create a new key for {}".format(from_user))
                encryption_software_list = contacts.get_encryption_names(from_user)

            if len(encryption_software_list) > 0:
                self.log_message("getting header with public keys for {}: {}".format(
                   from_user, encryption_software_list))

                for encryption_software in encryption_software_list:
                    key_block = self.create_public_key_block(encryption_software, from_user)
                    if len(key_block) > 0:
                        header_lines += key_block
        else:
            self.log_message("Warning: auto-exchange of keys is not active")

        return header_lines
    def add_public_key_to_header(self, from_user):
        '''
            Add public key and accepted crypto to header if automatically exchanging keys.
        '''

        if options.auto_exchange_keys():
            header_lines = self.get_public_key_header(from_user)
            if header_lines and len(header_lines) > 0:
                for line in header_lines:
                    # we can't just use split() because some lines have no value
                    index = line.find(CryptoMessage.SEPARATOR)
                    if index > 0:
                        header_name = line[0:index]

                        value_index = index + len(CryptoMessage.SEPARATOR)
                        if len(line) > value_index:
                            value = line[value_index:]
                        else:
                            value = ''
                    else:
                        header_name = line
                        value = ''

                    self.email_message.add_header(header_name, value)

                self.add_accepted_crypto_software(from_user)
                self.add_fingerprint(from_user)
                self.log_message(
                    "added key for {} to header".format(from_user))
            else:
                encryption_name = CryptoFactory.DEFAULT_ENCRYPTION_NAME
                if options.create_private_keys():
                    add_private_key(from_user,
                                    encryption_software=encryption_name)
                    self.log_message("creating a new {} key for {}".format(
                        encryption_name, from_user))
                else:
                    self.log_message(
                        "not creating a new {} key for {} because auto-create disabled"
                        .format(encryption_name, from_user_id))
        else:
            self.log_message(
                "not adding key for {} to header because auto-exchange disabled"
                .format(from_user))
    def is_create_private_keys_active(self):
        '''
            Gets whether creating private keys on the fly is active.

            >>> crypto_message = CryptoMessage()
            >>> current_setting = options.create_private_keys()
            >>> options.set_create_private_keys(True)
            >>> crypto_message.is_create_private_keys_active()
            True
            >>> options.set_create_private_keys(False)
            >>> crypto_message.is_create_private_keys_active()
            False
            >>> options.set_create_private_keys(current_setting)
        '''

        active = options.create_private_keys()
        if self.DEBUGGING: self.log_message("Create private keys: {}".format(active))

        return active
    def is_create_private_keys_active(self):
        '''
            Gets whether creating private keys on the fly is active.

            >>> crypto_message = CryptoMessage()
            >>> current_setting = options.create_private_keys()
            >>> options.set_create_private_keys(True)
            >>> crypto_message.is_create_private_keys_active()
            True
            >>> options.set_create_private_keys(False)
            >>> crypto_message.is_create_private_keys_active()
            False
            >>> options.set_create_private_keys(current_setting)
        '''

        active = options.create_private_keys()
        if self.DEBUGGING:
            self.log_message("Create private keys: {}".format(active))

        return active
def add_private_key(email, encryption_software=None):
    '''
        Add a private key if it doesn't exist.

        Creating a key takes minutes so a separate process handles it so no return code.

        >>> add_private_key(None)
    '''

    try:
        # only add private keys for members of the domain
        if email_in_domain(email):
            if options.create_private_keys():
                if encryption_software is None or len(encryption_software) <= 0:
                    encryption_software = CryptoFactory.DEFAULT_ENCRYPTION_NAME

                user_key = user_keys.get(email, encryption_software)
                if user_key is None:
                    contacts_crypto = contacts.get_contacts_crypto(email, encryption_name=encryption_software)
                    if contacts_crypto is None:
                        # a private user key will automatically be created
                        # when the contact's crypto record is created after the contact is created
                        contacts.add(email, encryption_software, source=AUTO_GENERATED)
                        log_message('add {} key for {}'.format(encryption_software, email))
                    else:
                        log_message('adding private {} user key for {}'.format(encryption_software, email))
                        sync_private_key_via_queue(contacts_crypto)

                elif user_key.contacts_encryption.fingerprint is None:
                    log_message('setting private {} key fingerprint for {}'.format(encryption_software, email))
                    sync_fingerprint_via_queue(user_key.contacts_encryption)
                else:
                    log_message('{} already has crypto software defined'.format(email))
            else:
                log_message('creating private key disabled so no key created for {}'.format(email))
        else:
            log_message('{} not a member of {}'.format(email, get_domain()))

    except Exception:
        record_exception()
        log_message('EXCEPTION - see syr.exception.log for details')
Exemple #11
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
Exemple #12
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
    def add_public_key_to_header(self, from_user):
        '''
            Add public key and accepted crypto to header if automatically exchanging keys.
        '''

        if options.auto_exchange_keys():
            header_lines = self.get_public_key_header(from_user)
            if header_lines and len(header_lines) > 0:
                for line in header_lines:
                    # we can't just use split() because some lines have no value
                    index = line.find(CryptoMessage.SEPARATOR)
                    if index > 0:
                        header_name = line[0:index]

                        value_index = index + len(CryptoMessage.SEPARATOR)
                        if len(line) > value_index:
                            value = line[value_index:]
                        else:
                            value = ''
                    else:
                        header_name = line
                        value = ''

                    self.email_message.add_header(header_name, value)

                self.add_accepted_crypto_software(from_user)
                self.add_fingerprint(from_user)
                self.log_message("added key for {} to header".format(from_user))
            else:
                encryption_name = CryptoFactory.DEFAULT_ENCRYPTION_NAME
                if options.create_private_keys():
                    add_private_key(from_user, encryption_software=encryption_name)
                    self.log_message("creating a new {} key for {}".format(encryption_name, from_user))
                else:
                    self.log_message("not creating a new {} key for {} because auto-create disabled".format(
                        encryption_name, from_user_id))
        else:
            self.log_message("not adding key for {} to header because auto-exchange disabled".format(from_user))
Exemple #14
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)
Exemple #15
0
    def _encrypt_message(self, encryption_name):
        '''
            Encrypt the message if both To and From have keys.

            Otherwise, if we just have a key for the From, then add it to the header.
        '''
        result_ok = False
        try:
            self._log_message("encrypting message with {}".format(encryption_name))
            crypto = CryptoFactory.get_crypto(encryption_name, get_classname(encryption_name))
            if crypto is None:
                self._log_message("{} is not ready to use".format(encryption_name))
            elif (self.crypto_message is None or
                  self.crypto_message.smtp_sender() is None or
                  self.crypto_message.smtp_recipient() is None):
                self._log_message("missing key data to encrypt message")
            else:
                # get all the user ids that have encryption keys
                user_ids = crypto.get_user_ids()
                if self.DEBUGGING: self._log_message('user ids: {}'.format(user_ids))
                private_user_ids = crypto.get_private_user_ids()
                if self.DEBUGGING: self._log_message('private user ids: {}'.format(private_user_ids))

                self._prep_from_user(private_user_ids, encryption_name)
                users_dict, result_ok = self._get_crypto_details(encryption_name, user_ids)
                if result_ok or options.create_private_keys():

                    # regardless if we can encrypt this message,
                    # add From's public key if we have it and we're exchanging keys
                    if options.auto_exchange_keys() and users_dict[encrypt_utils.FROM_KEYWORD] is not None:
                        self.crypto_message.add_public_key_to_header(users_dict[encrypt_utils.FROM_KEYWORD])
                else:
                    self._log_message("user_ids is not None and len(user_ids) > 0: {}".format(
                        user_ids is not None and len(user_ids) > 0))

                if result_ok:
                    self._log_message("from user ID: {}".format(users_dict[encrypt_utils.FROM_KEYWORD]))
                    self._log_message("to user ID: {}".format(users_dict[encrypt_utils.TO_KEYWORD]))
                    self._log_message("subject: {}".format(
                      self.crypto_message.get_email_message().get_header(mime_constants.SUBJECT_KEYWORD)))

                    result_ok = self._encrypt_message_with_keys(crypto, users_dict)
                    self._log_message('encrypted message: {}'.format(result_ok))
                    if result_ok:
                        self._log_message('encrypted with: {}'.format(encryption_name))
                    else:
                        error_message = self._get_encrypt_error_message(users_dict, encryption_name)
                        raise MessageException(value=error_message)

                else:
                    # the message hasn't been encrypted, but we have
                    # successfully processed it
                    self.crypto_message.set_filtered(True)

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

        except Exception:
            result_ok = False
            record_exception()
            self._log_message('EXCEPTION - see syr.exception.log for details')

        return result_ok
    def save(self):
        '''
            Save the fingerprint in the database.

            Test extreme case.
            >>> set_fingerprint_class = SetFingerprint(None)
            >>> set_fingerprint_class.save()
            False
        '''

        self.result_ok, self.crypto_name, self.email, self.key_plugin = prep_sync(
            self.contacts_encryption)
        if self.result_ok:
            try:
                # the contact's crypto record must exist or we'll get into an infinite loop
                # because whenever we add a contact's encryption for an email in our supported domain,
                # then this class is activated so we can complete the configuration
                if self.contacts_encryption is None:
                    self.result_ok = False
                    log_message('no contact encryption record for {}'.format(
                        self.email))
                else:
                    fingerprint, expiration_date = self.key_plugin.get_fingerprint(
                        self.email)
                    if fingerprint is None:
                        self.result_ok = False
                        log_message('no {} fingerprint found for {}'.format(
                            self.crypto_name, self.email))
                    else:
                        self.result_ok = True
                        try:
                            fingerprint = format_fingerprint(fingerprint)
                            if (self.contacts_encryption.fingerprint
                                    is not None
                                    and self.contacts_encryption.fingerprint !=
                                    fingerprint):
                                log_message(
                                    'replaced old fingerprint {} with {} for {}'
                                    .format(
                                        self.contacts_encryption.fingerprint,
                                        fingerprint, self.email))

                            # if we created the key, then be sure it's verified
                            if utils.email_in_domain(self.email):
                                if self.contacts_encryption.source is None and create_private_keys(
                                ):
                                    self.contacts_encryption.source = AUTO_GENERATED
                                if self.contacts_encryption.source == AUTO_GENERATED:
                                    self.contacts_encryption.verified = True

                            self.contacts_encryption.fingerprint = fingerprint
                            self.contacts_encryption.save()
                            log_message(
                                'set {} fingerprint for {} in database: {}'.
                                format(self.crypto_name, self.email,
                                       fingerprint))
                        except IntegrityError as ie:
                            self.result_ok = False
                            if 'insert or update on table "mail_contactscrypto" violates foreign key constraint' in str(
                                    ie):
                                log_message(
                                    '{} crypto key no longer exists for {}'.
                                    format(self.crypto_name, self.email))
                            else:
                                raise
            except Exception as exception:
                record_exception()
                log_message('EXCEPTION - see syr.exception.log for details')
                self.result_ok = False

        return self.result_ok