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