def prep_message_header(self, message, to_domain): ''' Prepare the header of a Message. ''' if message is None: self.log_message('no message defined in prep_message_header') elif to_domain is None: self.log_message('domain is not defined in prep_message_header') else: message_date = datetime.utcnow().replace(tzinfo=utc) from_user = get_metadata_address(domain=get_domain()) to_user = get_metadata_address(domain=to_domain) message.__setitem__(mime_constants.FROM_KEYWORD, from_user) message.__setitem__(mime_constants.TO_KEYWORD, to_user) message.__setitem__(constants.ORIGINAL_FROM, from_user) message.__setitem__(constants.ORIGINAL_TO, to_user) message.__setitem__(mime_constants.DATE_KEYWORD, message_date.__str__()) message.__setitem__(mime_constants.MESSAGE_ID_KEYWORD, utils.get_message_id()) self.log_message("message's content type: {}".format( message.get_content_type())) self.log_message("message's boundary: {}".format( message.get_boundary())) if self.DEBUGGING: self.log_message("message's key/value pair") for key in message.keys(): self.log_message('{}: {}'.format(key, message.get(key))) return message
def create_encrypted_message(self, inner_message, to_domain): ''' Create an encrypted Message. ''' message = None if to_domain is None: self.log_message('domain is not defined') elif inner_message is None: self.log_message('no inner message defined') else: from_user = get_email(get_metadata_address(domain=get_domain())) to_user = get_email(get_metadata_address(domain=to_domain)) crypto_message = create_protected_message( from_user, to_user, inner_message.as_string(), utils.get_message_id()) if crypto_message.is_crypted(): # add the DKIM signature to the inner message if user opted for it crypto_message = add_dkim_sig_optionally(crypto_message) message = crypto_message.get_email_message().get_message() self.crypted_with = crypto_message.get_metadata_crypted_with() self.log_message('crypted with: {}'.format(self.crypted_with)) for part in message.walk(): self.log_message('Content type of part: {}'.format( part.get_content_type())) if self.DEBUGGING: self.log_message(part.get_payload()) else: report_bad_bundled_encrypted_message(to_domain, self.bundled_messages) return message
def start_crypto_message(): from goodcrypto.mail.message.crypto_message import CryptoMessage # start a new crypto message from_metadata_user = metadata.get_metadata_address(email=from_user) to_metadata_user = metadata.get_metadata_address(email=to_user) crypto_message = CryptoMessage() crypto_message.get_email_message().add_header( mime_constants.FROM_KEYWORD, from_metadata_user) crypto_message.get_email_message().add_header( mime_constants.TO_KEYWORD, to_metadata_user) crypto_message.get_email_message().add_header(mime_constants.MESSAGE_ID_KEYWORD, message_id) # include the timestamp because some MTAs/spam filters object if it's not set crypto_message.get_email_message().add_header( mime_constants.DATE_KEYWORD, datetime.utcnow().isoformat(str(' '))) return crypto_message
def start_crypto_message(): from goodcrypto.mail.message.crypto_message import CryptoMessage # start a new crypto message from_metadata_user = metadata.get_metadata_address(email=from_user) to_metadata_user = metadata.get_metadata_address(email=to_user) crypto_message = CryptoMessage() crypto_message.get_email_message().add_header( mime_constants.FROM_KEYWORD, from_metadata_user) crypto_message.get_email_message().add_header( mime_constants.TO_KEYWORD, to_metadata_user) crypto_message.get_email_message().add_header( mime_constants.MESSAGE_ID_KEYWORD, message_id) # include the timestamp because some MTAs/spam filters object if it's not set crypto_message.get_email_message().add_header( mime_constants.DATE_KEYWORD, datetime.utcnow().isoformat(str(' '))) return crypto_message
def send_bundled_message(self, message, to_domain): ''' Send a Message to the domain. ''' try: if message is None: result_ok = False self.log_message('nothing to send to {}'.format(to_domain)) else: sender = get_email(get_metadata_address(domain=get_domain())) recipient = get_email(get_metadata_address(domain=to_domain)) self.log_message( 'starting to send message from {} to {}'.format( sender, recipient)) result_ok = send_message(sender, recipient, message.as_string()) self.log_message('finished sending message') except Exception as exception: result_ok = False self.log_message('error while sending message') self.log_message('EXCEPTION - see syr.exception.log for details') record_exception() return result_ok
def _start_check_for_encryption(self, from_user, to_user): ''' Start checking if the user uses encryption. ''' # don't look for keys if the to_user's domain is also using goodcrypto if contacts.get(get_metadata_address(email=to_user)) is None: # start by adding a record so we don't search for a key again contact = contacts.add(to_user, None) # search for the keys; if one's found, send email to the from # user so they can verify the fingerprint self._log_message('queuing search for a key for {}'.format(to_user)) queue_keyserver_search(to_user, from_user) else: self._log_message('{} is also using goodcrypto so not searching for a key'.format(to_user))
def add_history_and_remove(self, to_domain): ''' Add history records for the messages sent and then remove the associated file. ''' def get_addendum_value(addendum, keyword): value = addendum[keyword] if is_string(value): value = value.strip() if value == 'True': value = True elif value == 'False': value = False return value if len(self.bundled_messages) > 0: for bundled_message in self.bundled_messages: self.log_message( 'message included in bundled: {}'.format(bundled_message)) with open(bundled_message) as f: original_message, addendum = parse_bundled_message( f.read()) self.log_message('addendum: {}'.format(addendum)) encrypted = get_addendum_value(addendum, constants.CRYPTED_KEYWORD) private_signed = get_addendum_value( addendum, constants.PRIVATE_SIGNED_KEYWORD) clear_signed = get_addendum_value( addendum, constants.CLEAR_SIGNED_KEYWORD) dkim_signed = get_addendum_value( addendum, constants.DKIM_SIGNED_KEYWORD) if encrypted or private_signed or clear_signed or dkim_signed: sender = get_addendum_value( addendum, mime_constants.FROM_KEYWORD) recipient = get_addendum_value( addendum, mime_constants.TO_KEYWORD) verification_code = get_addendum_value( addendum, constants.VERIFICATION_KEYWORD) crypto_message = CryptoMessage( email_message=EmailMessage(original_message)) crypto_message.set_smtp_sender(sender) crypto_message.set_smtp_recipient(recipient) crypto_message.set_crypted(encrypted) crypto_message.set_crypted_with( addendum[constants.CRYPTED_WITH_KEYWORD]) crypto_message.set_metadata_crypted(True) crypto_message.set_metadata_crypted_with( self.crypted_with) crypto_message.set_private_signed(private_signed) if private_signed: if encrypted: crypto_message.add_private_signer({ constants.SIGNER: sender, constants.SIGNER_VERIFIED: True }) crypto_message.add_private_signer({ constants.SIGNER: get_metadata_address(sender), constants.SIGNER_VERIFIED: True }) crypto_message.set_clear_signed(clear_signed) if clear_signed: if encrypted: crypto_message.add_clear_signer({ constants.SIGNER: sender, constants.SIGNER_VERIFIED: True }) else: crypto_message.add_clear_signer({ constants.SIGNER: get_metadata_address(sender), constants.SIGNER_VERIFIED: True }) crypto_message.set_dkim_signed(dkim_signed) if dkim_signed: crypto_message.set_dkim_sig_verified(True) history.add_outbound_record(crypto_message, verification_code) self.log_message( 'added outbound history record from {}'.format( sender)) if self.DEBUGGING: self.log_message( 'logged headers in goodcrypto.message.utils.log' ) utils.log_message_headers(crypto_message, tag='bundled headers') if os.path.exists(bundled_message): os.remove(bundled_message) else: self.log_message( 'tried to delete message after bundling it, but message no longer exists on disk' ) else: self.log_message('no bundled messages')
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
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