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