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