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
Example #2
0
    def _clear_sign_crypto_message(self, from_user):
        ''' Clear sign the message. '''

        """ !!!!!!
        clear_sign_policy = options.clear_sign_policy()
        if clear_sign_policy == constants.CLEAR_SIGN_WITH_DOMAIN_KEY:
            encryption_names = contacts.get_encryption_names(utils.get_domain_email())
        elif clear_sign_policy == constants.CLEAR_SIGN_WITH_SENDER_KEY:
            encryption_names = contacts.get_encryption_names(from_user)
        else:
            encryption_names = contacts.get_encryption_names(from_user)
            if encryption_names is None or len(encryption_names) <= 0:
                encryption_names = contacts.get_encryption_names(from_user)
        """
        encryption_names = contacts.get_encryption_names(from_user)
        if len(encryption_names) <= 0:
            signed = False
            self._log_message('unable to clear sign message because no encryption software defined for: {}'.format(from_user))
        else:
            signed = self._clear_sign_message_with_all(encryption_names)
            # the message isn't really encrypted, just signed
            if signed:
                self.crypto_message.set_crypted(False)
                self.crypto_message.set_clear_signed(True)
                self.crypto_message.add_clear_signer(
                   {constants.SIGNER: from_user, constants.SIGNER_VERIFIED: True})
                self._log_message('message clear signed, but not encrypted')
            if self.DEBUGGING:
                self._log_message('message after clear signature:\n{}'.format(
                  self.crypto_message.get_email_message().to_string()))

        return signed
    def add_fingerprint(self, from_user):
        '''
            Add the fingerprint for each type of crypto used to the email message header.
        '''

        try:
            encryption_software_list = contacts.get_encryption_names(from_user)
            if len(encryption_software_list) <= 0:
                self.log_message(
                    "Not adding fingerprint for {} because no crypto software".
                    format(from_user))
            else:
                for encryption_name in encryption_software_list:
                    fingerprint, __, active = contacts.get_fingerprint(
                        from_user, encryption_name)
                    if active and fingerprint is not None and len(
                            fingerprint.strip()) > 0:
                        self.email_message.add_header(
                            constants.PUBLIC_FINGERPRINT_HEADER.format(
                                encryption_name.upper()),
                            format_fingerprint(fingerprint))
                        self.log_message(
                            'added {} fingerprint'.format(encryption_name))
        except:
            record_exception()
            self.log_message('EXCEPTION - see syr.exception.log for details')
    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
Example #5
0
def prep_metadata_key_message(from_user, to_user):
    '''
        Prepare a Message that contains notice about a new metadata key.
    '''
    def get_extra_headers():

        from goodcrypto.mail.message.utils import make_public_key_block

        extra_headers = None

        key_block = make_public_key_block(local_metadata_address)
        if len(key_block) > 0:
            extra_headers = []
            for line in key_block:
                name, __, value = line.partition(': ')
                extra_headers.append((name, value))
            extra_headers.append((constants.ACCEPTED_CRYPTO_SOFTWARE_HEADER, ','.join(encryption_software)))

        return extra_headers

    try:
        from goodcrypto.mail.message.utils import add_private_key

        message = local_metadata_address = remote_metadata_address = None
        if from_user is None or to_user is None:
            log_message('missing user data so unable to prepare metadata key message')

        else:
            # we want to send a message from the original recipient's "no metadata" address
            # to the original sender's "no metadata" address
            remote_metadata_address = get_metadata_address(email=from_user)
            local_metadata_address = get_metadata_address(email=to_user)
            encryption_software = contacts.get_encryption_names(local_metadata_address)

            extra_headers = get_extra_headers()
            if extra_headers is None:
                log_message('"no metadata" key is not ready yet')
                for encryption_name in encryption_software:
                    add_private_key(local_metadata_address, encryption_name)
                    log_message('adding a "no metadata" {} key'.format(encryption_name))
            else:
                log_message('preparing a "no metadata" key message')
                # the message should only contain the key in the header
                message = prep_mime_message(
                           local_metadata_address, remote_metadata_address, 'Message',
                           text='', extra_headers=extra_headers)
    except:
        message = None
        record_exception()
        log_message('EXCEPTION - see syr.exception.log for details')

    return message, local_metadata_address, remote_metadata_address
    def add_accepted_crypto_software(self, from_user):
        '''
            Add accepted encryption software to email message header.
        '''

        #  check whether we've already added them
        existing_crypto_software = self.get_accepted_crypto_software()
        if len(existing_crypto_software) > 0:
            self.log_message("attempted to add accepted encryption software to email_message that already has them")
        else:
            encryption_software_list = contacts.get_encryption_names(from_user)
            if len(encryption_software_list) <= 0:
                self.log_message("No encryption software for {}".format(from_user))
            else:
                self.email_message.add_header(
                  constants.ACCEPTED_CRYPTO_SOFTWARE_HEADER, ','.join(encryption_software_list))
    def add_fingerprint(self, from_user):
        '''
            Add the fingerprint for each type of crypto used to the email message header.
        '''

        try:
            encryption_software_list = contacts.get_encryption_names(from_user)
            if len(encryption_software_list) <= 0:
                self.log_message("Not adding fingerprint for {} because no crypto software".format(from_user))
            else:
                for encryption_name in encryption_software_list:
                    fingerprint, __, active = contacts.get_fingerprint(from_user, encryption_name)
                    if active and fingerprint is not None and len(fingerprint.strip()) > 0:
                        self.email_message.add_header(constants.PUBLIC_FINGERPRINT_HEADER.format(
                            encryption_name.upper()), format_fingerprint(fingerprint))
                        self.log_message('added {} fingerprint'.format(encryption_name))
        except:
            record_exception()
            self.log_message('EXCEPTION - see syr.exception.log for details')
    def add_accepted_crypto_software(self, from_user):
        '''
            Add accepted encryption software to email message header.
        '''

        #  check whether we've already added them
        existing_crypto_software = self.get_accepted_crypto_software()
        if len(existing_crypto_software) > 0:
            self.log_message(
                "attempted to add accepted encryption software to email_message that already has them"
            )
        else:
            encryption_software_list = contacts.get_encryption_names(from_user)
            if len(encryption_software_list) <= 0:
                self.log_message(
                    "No encryption software for {}".format(from_user))
            else:
                self.email_message.add_header(
                    constants.ACCEPTED_CRYPTO_SOFTWARE_HEADER,
                    ','.join(encryption_software_list))
Example #9
0
def get_encryption_software(email):
    '''
        Gets the list of active encryption software for a contact.

        If the contact has no encryption software, returns a list
        consisting of just the default encryption software.

        >>> from goodcrypto.oce.test_constants import JOSEPH_REMOTE_USER
        >>> encryption_software = get_encryption_software(JOSEPH_REMOTE_USER)
        >>> encryption_software == ['GPG']
        True
        >>> get_encryption_software(None)
        []
    '''

    encryption_software_list = []

    try:
        #  start with the encryption software for this email
        address = get_email(email)

        from goodcrypto.mail.contacts import get_encryption_names
        encryption_names = get_encryption_names(address)
        if encryption_names is None:
            log_message("no encryption software names for {}".format(address))
            #  make sure we have at least the default encryption
            default_encryption_software = CryptoFactory.get_default_encryption_name()
            log_message("  defaulting to {}".format(default_encryption_software))
            encryption_names.append(default_encryption_software)

        #  only include active encryption software
        active_encryption_software = get_active_encryption_software()
        if active_encryption_software:
            for encryption_software in encryption_names:
                if encryption_software in active_encryption_software:
                    encryption_software_list.append(encryption_software)
    except:
        encryption_software_list = []
        record_exception()

    return encryption_software_list
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 is_ready_to_protect_metadata(from_user, to_user):
    '''
        Determine if encrypt_metadata is True and we have a
        metadata key for both the sender's and recipient's servers.
    '''
    if from_user is None or to_user is None:
        ready = False
    else:
        ready = options.encrypt_metadata()
        log_message("options set to encrypt metadata: {}".format(ready))
        if ready:
            # first see if we know the metadata address for the recipient's server
            to_metadata_user = get_metadata_address(email=to_user)
            encryption_names = contacts.get_encryption_names(to_metadata_user)
            ready = len(encryption_names) > 0
            log_message("{} uses {} encryption programs".format(to_metadata_user, encryption_names))
            for encryption_name in encryption_names:
                ready, __, __ = get_metadata_user_details(
                    to_user, encryption_name)

                # we only need 1 valid metadata address
                if ready:
                    log_message("recipient's server ready to protect metadata")
                    break

        if ready:
            # then see if we know the metadata address for the sender's server
            from_metadata_user = get_metadata_address(email=from_user)
            encryption_names = contacts.get_encryption_names(from_metadata_user)
            ready = len(encryption_names) > 0
            log_message("{} uses {} encryption programs".format(from_metadata_user, encryption_names))
            for encryption_name in encryption_names:
                ready, __, fingerprint = get_from_metadata_user_details(
                    from_user, encryption_name)

                # we only need 1 valid metadata address
                if ready:
                    log_message("sender's server ready to protect metadata")
                    break
                elif fingerprint is None:
                    contacts_crypto = contacts.get_contacts_crypto(from_user, encryption_name=encryption_name)
                    if contacts_crypto is None:
                        # we'll automatically add a private key after creating the contact's crypto
                        contacts.add(from_user, encryption_name, source=AUTO_GENERATED)
                        contacts_crypto = contacts.get_contacts_crypto(
                            from_user, encryption_name=encryption_name)
                    else:
                        user_key = user_keys.get(from_user, encryption_name)
                        if user_key is None:
                            from goodcrypto.mail.message.utils import sync_private_key_via_queue

                            sync_private_key_via_queue(contacts_crypto)
                            log_message('started to add private {} key for {}'.format(
                                encryption_name, from_user))
                        else:
                            from goodcrypto.mail.message.utils import sync_fingerprint_via_queue

                            sync_fingerprint_via_queue(contacts_crypto)
                            log_message('started to update {} fingerprint for {}'.format(
                                encryption_name, from_user))

    log_message('ready to protect metadata: {}'.format(ready))

    return ready
Example #12
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 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
Example #14
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