def is_local_message(sender, recipient): ''' Determine if the message is from the localhost. >>> sender = 'test@localhost' >>> recipient = '*****@*****.**' >>> is_local_message(sender, recipient) True >>> sender = '*****@*****.**' >>> recipient = 'test@localhost' >>> is_local_message(sender, recipient) True >>> sender = None >>> recipient = '*****@*****.**' >>> is_local_message(sender, recipient) False >>> sender = None >>> recipient = 'test@localhost' >>> is_local_message(sender, recipient) True ''' def is_local_host_domain(user): is_text = isinstance(user, str) return user is not None and is_text and user.endswith('@localhost') return (is_local_host_domain(sender) or is_local_host_domain(recipient) or (email_in_domain(sender) and email_in_domain(recipient)))
def reject_message(self, error_message, message=None): ''' Reject a message that had an unexpected error and return the to address. >>> # This message will fail if testing on dev system >>> to_address = get_admin_email() >>> filter = Filter('root', 'root', 'bad message') >>> filter.reject_message('Unknown message') == to_address True ''' try: if message is None: message = self.out_message if email_in_domain(self.sender): to_address = self.sender subject = i18n('Undelivered Mail: Unable to send message') elif email_in_domain(self.recipient): to_address = self.recipient subject = i18n('Error: Unable to receive message') else: to_address = get_admin_email() subject = i18n('Message rejected.') notice = '{}'.format(error_message) if message is not None: notice += '\n\n===================\n{}'.format(message) notify_user(to_address, subject, notice) except: raise return to_address
def bounce_message(original_message, user, subject, error_message): ''' Bounce a message that a local user. Test extreme case >>> bounce_message(None, None, None, None) False ''' notified_user = False try: log_message(error_message) if user is None: log_message('unable to bounce message without a user email address') elif email_in_domain(user): message = '{}\n\n===================\n{}'.format( error_message, original_message) notified_user = notify_user(user, subject, message) log_message('sent note to {} about error.'.format(user)) else: log_message('unable to send note to {} about error.'.format(user)) except: record_exception() log_message('EXCEPTION - see syr.exception.log for details') return notified_user
def drop_message(original_message, recipient, subject, error_message): ''' Drop a message that we shouldn't process from a remote user. Test extreme case >>> drop_message(None, None, None, None) False ''' notified_user = False try: log_message(error_message) if recipient is None: log_message('unable to notify recipient about dropped message') elif email_in_domain(recipient): message = '{}\n\n===================\n{}'.format( error_message, original_message) notified_user = notify_user(recipient, subject, message) log_message('sent note to {} about error.'.format(recipient)) else: log_message('unable to send note to {} about error.'.format(recipient)) except: record_exception() log_message('EXCEPTION - see syr.exception.log for details') return notified_user
def get_passcode(email, encryption_name): ''' Get the passcode for the encryption program for the contact. The email can be an RFC address or just the email address. >>> len(get_passcode('*****@*****.**', 'GPG')) > 0 True >>> get_passcode('*****@*****.**', 'TestBC') is None True ''' passcode = None try: if email_in_domain(email): address = get_email(email) user_key = get(address, encryption_name) if user_key: passcode = user_key.passcode if passcode and len(passcode) > 0: log_message("private {} key configured for {}".format(encryption_name, email)) else: log_message('{} does not have a {} private key configured'.format(email, encryption_name)) else: log_message('{} does not have a matching contact'.format(email)) else: log_message('{} not part of managed domain so no private user key'.format(email)) except Exception: record_exception() log_message('EXCEPTION - see syr.exception.log for details') return passcode
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 post_save_contacts_crypto(sender, **kwargs): ''' Process the contact's encryption record after it's saved. After crypto record saved: * if record for managed domain: * if no user_key record: * create user_key record * create gpg key * get gpg fingerprint * update crypto record with fingerprint * if user_key record, but no fingerprint in crypto record: * get gpg fingerprint * update crypto record with fingerprint * if no fingerprint in crypto record * get gpg fingerprint * update crypto record with fingerprint ''' if TESTS_RUNNING: log_message('tests running so no post save processing') else: created = kwargs['created'] contacts_encryption = kwargs['instance'] email = contacts_encryption.contact.email encryption_name = contacts_encryption.encryption_software.name fingerprint = contacts_encryption.fingerprint log_message("starting post save for {} contact's {} crypto".format(email, encryption_name)) if email_in_domain(email): if created: from goodcrypto.mail.message.utils import sync_private_key_via_queue log_message('starting to add private {} user key for {}'.format(encryption_name, email)) if contacts_encryption.source is None: contacts_encryption.source = AUTO_GENERATED sync_private_key_via_queue(contacts_encryption) elif fingerprint is None: from goodcrypto.mail.message.utils import sync_fingerprint_via_queue log_message('setting private {} fingerprint for {}'.format(encryption_name, email)) sync_fingerprint_via_queue(contacts_encryption) else: log_message("{} already has {} fingerprint: {}".format(email, encryption_name, fingerprint)) elif fingerprint is None: from goodcrypto.mail.message.utils import sync_fingerprint_via_queue log_message('setting {} fingerprint for {}'.format(encryption_name, email)) sync_fingerprint_via_queue(contacts_encryption) else: log_message('{} already has {} crypto software defined'.format(email, encryption_name)) log_message("finished post save for {} contact's {} crypto".format(email, encryption_name))
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 make_key(line, domain, crypto_name): ''' Make a private key if its in the domain.''' user_name, email = parse_address(line) if email is None: print('{} is not a valid email address'.format(line)) elif email_in_domain(email): if user_name is None or len(user_name.strip()) <= 0: full_address = email else: full_address = '{} <{}>'.format(user_name, email) contacts.add(full_address, crypto_name, source=AUTO_GENERATED) else: print('{} not in the domain: {}'.format(line, domain))
def configure(self): ''' Add a crypto key pair. >>> # In honor of David Fifield, developer and co-inventor of Flash Proxy. >>> from time import sleep >>> email = '*****@*****.**' >>> crypto_name = 'GPG' >>> contact = contacts.add(email, crypto_name) >>> contact != None True >>> contacts_crypto = contacts.get_contacts_crypto(email, crypto_name) >>> sync_db_key_class = SyncPrivateKey(contacts_crypto) >>> sync_db_key_class.configure() True >>> contacts.delete(email) True >>> sync_db_key_class.key_plugin.delete(email) True ''' try: ok, self.crypto_name, self.email, self.key_plugin = prep_sync( self.contacts_encryption) self.result_ok = ok and email_in_domain(self.email) if self.result_ok and utils.ok_to_modify_key( self.crypto_name, self.key_plugin): log_message('starting SyncPrivateKey.configure for {}'.format( self.email)) # if there's a matching private key if self._need_new_crypto_key(): self.result_ok = self._add_crypto_key() else: self.result_ok = self._validate_passcode() if self.result_ok: self._save_fingerprint() else: log_message('{} not ok to configure private key'.format( self.email)) self.result_ok = False except Exception as exception: record_exception() log_message('EXCEPTION - see syr.exception.log for details') self.result_ok = False log_message('finished SyncPrivateKey.configure for {} ok: {}'.format( self.email, self.result_ok)) return self.result_ok
def possibly_needs_encryption(self): ''' Determine if the message might need to be encrypted or not. It could need encrytion if the sender has the same domain as the domain defined in goodcrypto mail server's options. If we're not using an SMTP proxy, then it never needs encryption if the message is going to and from a user with the domain defined in goodcrypto mail server's options. ''' maybe_needs_encryption = email_in_domain(self.sender) self.log_message('possibly needs encryption: {}'.format(maybe_needs_encryption)) return maybe_needs_encryption
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 possibly_needs_encryption(self): ''' Determine if the message might need to be encrypted or not. It could need encrytion if the sender has the same domain as the domain defined in goodcrypto mail server's options. If we're not using an SMTP proxy, then it never needs encryption if the message is going to and from a user with the domain defined in goodcrypto mail server's options. ''' maybe_needs_encryption = email_in_domain(self.sender) self.log_message( 'possibly needs encryption: {}'.format(maybe_needs_encryption)) return maybe_needs_encryption
def _is_ready_for_search(self): ''' Verify that we're ready to search for this key. Test extreme case. >>> srk_class = SearchKeyserver(None, None, None, None) >>> srk_class._is_ready_for_search() False ''' ready = False try: ready = (self.email is not None and self.encryption_name is not None and self.keyserver is not None and self.user_initiated_search is not None and not email_in_domain(self.email)) if ready: self.key_plugin = KeyFactory.get_crypto( self.encryption_name, crypto_software.get_key_classname(self.encryption_name)) ready = self.key_plugin is not None if ready: # make sure we don't already have crypto defined for this user contacts_crypto = contacts.get_contacts_crypto( self.email, self.encryption_name) if contacts_crypto is None or contacts_crypto.fingerprint is None: fingerprint, expiration = self.key_plugin.get_fingerprint( self.email) if fingerprint is not None: ready = False self.log_message( '{} public key exists for {}: {}'.format( self.encryption_name, self.email, fingerprint)) else: ready = False self.log_message('crypto for {} already defined'.format( self.email)) except Exception as exception: record_exception() self.log_message('EXCEPTION - see syr.exception.log for details') ready = False return ready
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 configure(self): ''' Add a crypto key pair. >>> # In honor of David Fifield, developer and co-inventor of Flash Proxy. >>> from time import sleep >>> email = '*****@*****.**' >>> crypto_name = 'GPG' >>> contact = contacts.add(email, crypto_name) >>> contact != None True >>> contacts_crypto = contacts.get_contacts_crypto(email, crypto_name) >>> sync_db_key_class = SyncPrivateKey(contacts_crypto) >>> sync_db_key_class.configure() True >>> contacts.delete(email) True >>> sync_db_key_class.key_plugin.delete(email) True ''' try: ok, self.crypto_name, self.email, self.key_plugin = prep_sync(self.contacts_encryption) self.result_ok = ok and email_in_domain(self.email) if self.result_ok and utils.ok_to_modify_key(self.crypto_name, self.key_plugin): log_message('starting SyncPrivateKey.configure for {}'.format(self.email)) # if there's a matching private key if self._need_new_crypto_key(): self.result_ok = self._add_crypto_key() else: self.result_ok = self._validate_passcode() if self.result_ok: self._save_fingerprint() else: log_message('{} not ok to configure private key'.format(self.email)) self.result_ok = False except Exception as exception: record_exception() log_message('EXCEPTION - see syr.exception.log for details') self.result_ok = False log_message('finished SyncPrivateKey.configure for {} ok: {}'.format(self.email, self.result_ok)) return self.result_ok
def _is_ready_for_search(self): ''' Verify that we're ready to search for this key. Test extreme case. >>> srk_class = SearchKeyserver(None, None, None, None) >>> srk_class._is_ready_for_search() False ''' ready = False try: ready = (self.email is not None and self.encryption_name is not None and self.keyserver is not None and self.user_initiated_search is not None and not email_in_domain(self.email)) if ready: self.key_plugin = KeyFactory.get_crypto( self.encryption_name, crypto_software.get_key_classname(self.encryption_name)) ready = self.key_plugin is not None if ready: # make sure we don't already have crypto defined for this user contacts_crypto = contacts.get_contacts_crypto(self.email, self.encryption_name) if contacts_crypto is None or contacts_crypto.fingerprint is None: fingerprint, expiration = self.key_plugin.get_fingerprint(self.email) if fingerprint is not None: ready = False self.log_message('{} public key exists for {}: {}'.format( self.encryption_name, self.email, fingerprint)) else: ready = False self.log_message('crypto for {} already defined'.format(self.email)) except Exception as exception: record_exception() self.log_message('EXCEPTION - see syr.exception.log for details') ready = False return ready
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_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_passcode(email, encryption_name): ''' Get the passcode for the encryption program for the contact. The email can be an RFC address or just the email address. >>> len(get_passcode('*****@*****.**', 'GPG')) > 0 True >>> get_passcode('*****@*****.**', 'TestBC') is None True ''' passcode = None try: if email_in_domain(email): address = get_email(email) user_key = get(address, encryption_name) if user_key: passcode = user_key.passcode if passcode and len(passcode) > 0: log_message("private {} key configured for {}".format( encryption_name, email)) else: log_message( '{} does not have a {} private key configured'.format( email, encryption_name)) else: log_message( '{} does not have a matching contact'.format(email)) else: log_message( '{} not part of managed domain so no private user key'.format( email)) except Exception: record_exception() log_message('EXCEPTION - see syr.exception.log for details') return passcode
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 is_key_ok(email, encryption_name): ''' Throws a CryptoException if the email address does not have a crypto key, or the key has expired, or the key's fingerprint does not match the fingerprint in the database. >>> from goodcrypto.oce.test_constants import EDWARD_LOCAL_USER_ADDR >>> ok, __, active = is_key_ok(EDWARD_LOCAL_USER_ADDR, KeyFactory.DEFAULT_ENCRYPTION_NAME) >>> ok True >>> active True # In honor of Georg Koppen, works on Tor Browser, Torbutton, and our build automation. >>> email = 'Georg <*****@*****.**>' >>> try: ... is_key_ok(email, KeyFactory.DEFAULT_ENCRYPTION_NAME) ... fail() ... except CryptoException as crypto_exception: ... crypto_exception.__str__() == 'There is no key for Georg <*****@*****.**>.' True ''' # we use NO_FINGERPRINT_IN_DB a few times because we don't want to get too technical key_ok = verified = active = False encryption_software = crypto_software.get(encryption_name) if encryption_software is None: # this should never happen, but better be prepared log_message('no database entry for {}'.format(email)) raise CryptoException( i18n( NO_FINGERPRINT_IN_DB.format(encryption=encryption_name, email=email))) else: key_crypto = KeyFactory.get_crypto(encryption_name, encryption_software.classname) if key_crypto is None: # this should never happen, but better to be prepared log_message('no plugin for {} with classname: {}'.format( encryption_name, encryption_software.classname)) raise CryptoException( i18n( NO_FINGERPRINT_IN_DB.format(encryption=encryption_name, email=email))) else: # see if the crypto key exists crypto_fingerprint, expiration = key_crypto.get_fingerprint(email) if crypto_fingerprint is None: message = i18n( 'There is no key for {email}.'.format(email=email)) log_message(message) raise CryptoException(message) # if the key has expired, then raise an error if expiration is not None and key_crypto.fingerprint_expired( expiration): message = i18n('The key for {email} expired on {date}.'.format( email=email, date=expiration)) log_message(message) raise CryptoException(message) database_fingerprint, verified, active = get_fingerprint( email, encryption_name) # if there isn't a fingerprint, then try to save the crypto fingerprint if database_fingerprint is None or len( database_fingerprint.strip()) <= 0: contacts_encryption = get_contacts_crypto( email, encryption_name=encryption_name) if contacts_encryption is not None and contacts_encryption.fingerprint is None: database_fingerprint = crypto_fingerprint contacts_encryption.fingerprint = database_fingerprint if email_in_domain( email) and contacts.crypto.source is None: contacts.crypto.source = constants.AUTO_GENERATED contacts_encryption.save() log_message('updated {} fingerprint for {}'.format( encryption_name, email)) if database_fingerprint is None or len( database_fingerprint.strip()) <= 0: error_message = i18n( NO_FINGERPRINT_IN_DB.format(encryption=encryption_name, email=email)) log_message(error_message) raise CryptoException(error_message) else: # finally verify the fingerprints agree if (strip_fingerprint(database_fingerprint).lower() == strip_fingerprint(crypto_fingerprint).lower()): key_ok = True else: message = i18n( 'The fingerprint for {email} does not match the saved fingerprint.' .format(email=email)) log_message('email address: {}'.format(email)) log_message(' database fingerprint: {}'.format( database_fingerprint.lower())) log_message(' crypto fingerprint: {}'.format( crypto_fingerprint.lower())) log_message(message) raise CryptoException(message) log_message( '{} fingerprints agree and key has not expired for {}'.format( encryption_name, email)) return key_ok, verified, active
def add(email, encryption_program, fingerprint=None, passcode=None, source=None): ''' Add a contact and related settings. >>> # In honor of Thomas Drake, a whistleblower about Trailblazer, a NSA mass surveillance project. >>> email = '*****@*****.**' >>> encryption_software = crypto_software.get(KeyFactory.DEFAULT_ENCRYPTION_NAME) >>> contact = add(email, KeyFactory.DEFAULT_ENCRYPTION_NAME) >>> contact.email '*****@*****.**' >>> contact.user_name 'Thomas' >>> address = contact.email >>> address = '*****@*****.**' >>> contacts_crypto = ContactsCrypto.objects.get( ... contact=contact, encryption_software=encryption_software) >>> contacts_crypto is not None True >>> x = contact.delete() >>> contact = add(None, encryption_software) >>> contact is None True >>> contact = add(email, None) >>> contact.email = '*****@*****.**' >>> contact.user_name = 'Thomas' >>> get_contacts_crypto(email) [] >>> x = contact.delete() >>> contact = add(None, None) >>> contact is None True >>> contact = add('*****@*****.**', None) >>> contact.email = '*****@*****.**' >>> contact.user_name = 'test.com domain key (system use only)' >>> x = contact.delete() ''' try: new_contact = True user_name, email_address = parse_address(email) if email_address is None: contact = None else: try: contact = Contact.objects.get(email=email_address) new_contact = False # update the user name if it's been given and it differs from the name in the DB if user_name is not None and contact.user_name != user_name: contact.user_name = user_name contact.save() log_message('updated {} user name to {}'.format(email_address, user_name)) except Contact.DoesNotExist: log_message('creating a contact for {}'.format(email_address)) try: if user_name is None or len(user_name.strip()) <= 0: from goodcrypto.mail.message.metadata import is_metadata_address user_name = email_address i = user_name.find('@') # handle domain keys specially if is_metadata_address(email_address): if i > 0: email_domain = user_name[i+1:] user_name = '{} domain key (system use only)'.format(email_domain) else: if i > 0: user_name = user_name[:i] user_name = user_name.replace('.', ' ').replace('-', ' ').replace('_', ' ') user_name = capwords(user_name) except: pass contact = Contact.objects.create(email=email_address, user_name=user_name) except Exception: record_exception() log_message('EXCEPTION - see syr.exception.log for details') contact = None if encryption_program is None: log_message("no encryption software defined so not creating contact's crytpo record for {}".format(email)) else: # add a corresponding record for the contact's crypto program encryption_software = crypto_software.get(encryption_program) if contact is None or encryption_software is None: log_message('no contact and/or encryption software defined') else: try: contacts_crypto = ContactsCrypto.objects.get( contact=contact, encryption_software=encryption_software) if (fingerprint is not None and strip_fingerprint(contacts_crypto.fingerprint) != strip_fingerprint(fingerprint)): contacts_crypto.fingerprint = format_fingerprint(fingerprint) if email_in_domain(email): if contacts.crypto.source is None: contacts.crypto.source = constants.AUTO_GENERATED if contacts_crypto.source == constants.AUTO_GENERATED: contacts_crypto.verified = True contacts_crypto.save() except ContactsCrypto.DoesNotExist: # if the contact existed without any contact crypto, but was set # to never encrypt and now we have a key, then change the # outbound encrypt policy to the default if (not new_contact and contact.outbound_encrypt_policy == constants.NEVER_ENCRYPT_OUTBOUND): contact.outbound_encrypt_policy = constants.DEFAULT_OUTBOUND_ENCRYPT_POLICY contact.save() contacts_crypto = add_contacts_crypto(contact, encryption_software, fingerprint=fingerprint, source=source) log_message("created {} crypto record for {} with {} fingerprint: {}".format( encryption_software, email, fingerprint, contacts_crypto is not None)) except: record_exception() log_message('EXCEPTION - see syr.exception.log for details') except Exception: contact = None record_exception() log_message('EXCEPTION - see syr.exception.log for details') return contact
def _import_key_add_contact(keyblock, user_name, possible_fingerprint, passcode, id_fingerprint_pairs, plugin): ''' Import keys and create associated contact records. ''' def update_contact(contact, crypto_name, fingerprint): fingerprint_ok = True try: if (user_name is not None and (contact.user_name is None or len(contact.user_name.strip()) <= 0)): contact.user_name = user_name.strip() contact.save() log_message('updated user name') else: log_message('user name: {}'.format(user_name)) log_message('contact user name: {}'.format(contact.user_name)) if possible_fingerprint is not None and len(possible_fingerprint.strip()) > 0: if strip_fingerprint(possible_fingerprint).lower() == strip_fingerprint(fingerprint).lower(): contacts_crypto = contacts.get_contacts_crypto(user_id, plugin.get_name()) contacts_crypto.verified = True contacts_crypto.save() log_message('verified fingerprint') else: fingerprint_ok = False log_message('possible fingerprint: {}'.format(strip_fingerprint(possible_fingerprint).lower())) log_message('imported fingerprint: {}'.format(strip_fingerprint(fingerprint).lower())) except: record_exception() log_message('EXCEPTION - see syr.exception.log for details') return fingerprint_ok result_ok = True fingerprint_ok = True status = i18n('Imported key:') if plugin is None: result_ok = False fingerprint_ok = False else: result_ok = plugin.import_public(keyblock, id_fingerprint_pairs) log_message('imported key: {}'.format(result_ok)) if result_ok: crypto_name = plugin.get_name() local_users = [] for (user_id, fingerprint) in id_fingerprint_pairs: if email_in_domain(user_id): local_users.append(user_id) if user_name is not None: full_email = '{} <{}>'.format(user_name, user_id) else: full_email = user_id contact = contacts.add(full_email, crypto_name, passcode=passcode, source=MANUALLY_IMPORTED) if contact is None: log_message('unable to add contact for {}'.format(user_id)) else: if not update_contact(contact, plugin.get_name(), fingerprint): fingerprint_ok = False status += ' {}'.format(user_id) if len(local_users) > 0: for local_user in local_users: if not plugin.is_passcode_valid(local_user, passcode, key_exists=True): result_ok = False status = ('The passcode is not correct for the imported key for {email}').format(email=user_id) log_message(status) break for (user_id, __) in id_fingerprint_pairs: if not result_ok: contacts.delete(user_id) log_message('deleted {}'.format(user_id)) else: result_ok = False status = KEYBLOCK_INVALID log_message(status) return result_ok, status, fingerprint_ok
def import_key_now(encryption_name, keyblock, user_name, possible_fingerprint, passcode): ''' Import if key is ok and doesn't exist. ''' fingerprint_ok = True id_fingerprint_pairs = [] if encryption_name is None or keyblock is None: result_ok = False fingerprint_ok = False status = MISSING_DATA_STATUS log_message('crypto: {} / keyblock is None: {}'.format( encryption_name, keyblock is None)) else: encryption_software = crypto_software.get(encryption_name) plugin = KeyFactory.get_crypto(encryption_software.name, encryption_software.classname) if plugin is None: result_ok = False status = ('GoodCrypto does not currently support {encryption}').format( encryption=encryption_software.name) log_message('no plugin for {} with classname: {}'.format( encryption_software.name, encryption_software.classname)) else: id_fingerprint_pairs = plugin.get_id_fingerprint_pairs(keyblock) if id_fingerprint_pairs is None or len(id_fingerprint_pairs) <= 0: result_ok = False status = KEYBLOCK_INVALID else: result_ok = True for (user_id, fingerprint) in id_fingerprint_pairs: if email_in_domain(user_id): if passcode is None or len(passcode.strip()) <= 0: result_ok = False status = ('You must include the passcode when importing a key for {email}').format(email=user_id) break if result_ok: # make sure we don't already have crypto defined for this user contacts_crypto = contacts.get_contacts_crypto(user_id, encryption_name) if contacts_crypto is None or contacts_crypto.fingerprint is None: fingerprint, expiration = plugin.get_fingerprint(user_id) if fingerprint is not None: log_message('{} key exists for {}: {}'.format( encryption_name, user_id, fingerprint)) result_ok = False else: result_ok = False if not result_ok: status = ('A {encryption_name} key already exists for {email}. If you have a new key, then delete the Contact and then try importing the key again.').format( encryption_name=encryption_name, email=user_id) break # import the key if this is a new contact if result_ok: log_message('importing keys for {}'.format(id_fingerprint_pairs)) result_ok, status, fingerprint_ok = _import_key_add_contact( keyblock, user_name, possible_fingerprint, passcode, id_fingerprint_pairs, plugin) else: log_message('unable to import keys for {}'.format(id_fingerprint_pairs)) log_message("Imported public {} key ok: {}".format(encryption_name, result_ok)) log_message(" Status: {}".format(status)) return result_ok, status, fingerprint_ok, id_fingerprint_pairs
def is_key_ok(email, encryption_name): ''' Throws a CryptoException if the email address does not have a crypto key, or the key has expired, or the key's fingerprint does not match the fingerprint in the database. >>> from goodcrypto.oce.test_constants import EDWARD_LOCAL_USER_ADDR >>> ok, __, active = is_key_ok(EDWARD_LOCAL_USER_ADDR, KeyFactory.DEFAULT_ENCRYPTION_NAME) >>> ok True >>> active True # In honor of Georg Koppen, works on Tor Browser, Torbutton, and our build automation. >>> email = 'Georg <*****@*****.**>' >>> try: ... is_key_ok(email, KeyFactory.DEFAULT_ENCRYPTION_NAME) ... fail() ... except CryptoException as crypto_exception: ... crypto_exception.__str__() == 'There is no key for Georg <*****@*****.**>.' True ''' # we use NO_FINGERPRINT_IN_DB a few times because we don't want to get too technical key_ok = verified = active = False encryption_software = crypto_software.get(encryption_name) if encryption_software is None: # this should never happen, but better be prepared log_message('no database entry for {}'.format(email)) raise CryptoException(i18n(NO_FINGERPRINT_IN_DB.format(encryption=encryption_name, email=email))) else: key_crypto = KeyFactory.get_crypto(encryption_name, encryption_software.classname) if key_crypto is None: # this should never happen, but better to be prepared log_message('no plugin for {} with classname: {}'.format( encryption_name, encryption_software.classname)) raise CryptoException(i18n(NO_FINGERPRINT_IN_DB.format(encryption=encryption_name, email=email))) else: # see if the crypto key exists crypto_fingerprint, expiration = key_crypto.get_fingerprint(email) if crypto_fingerprint is None: message = i18n('There is no key for {email}.'.format(email=email)) log_message(message) raise CryptoException(message) # if the key has expired, then raise an error if expiration is not None and key_crypto.fingerprint_expired(expiration): message = i18n('The key for {email} expired on {date}.'.format(email=email, date=expiration)) log_message(message) raise CryptoException(message) database_fingerprint, verified, active = get_fingerprint(email, encryption_name) # if there isn't a fingerprint, then try to save the crypto fingerprint if database_fingerprint is None or len(database_fingerprint.strip()) <= 0: contacts_encryption = get_contacts_crypto(email, encryption_name=encryption_name) if contacts_encryption is not None and contacts_encryption.fingerprint is None: database_fingerprint = crypto_fingerprint contacts_encryption.fingerprint = database_fingerprint if email_in_domain(email) and contacts.crypto.source is None: contacts.crypto.source = constants.AUTO_GENERATED contacts_encryption.save() log_message('updated {} fingerprint for {}'.format(encryption_name, email)) if database_fingerprint is None or len(database_fingerprint.strip()) <= 0: error_message = i18n(NO_FINGERPRINT_IN_DB.format(encryption=encryption_name, email=email)) log_message(error_message) raise CryptoException(error_message) else: # finally verify the fingerprints agree if (strip_fingerprint(database_fingerprint).lower() == strip_fingerprint(crypto_fingerprint).lower()): key_ok = True else: message = i18n('The fingerprint for {email} does not match the saved fingerprint.'.format( email=email)) log_message('email address: {}'.format(email)) log_message(' database fingerprint: {}'.format(database_fingerprint.lower())) log_message(' crypto fingerprint: {}'.format(crypto_fingerprint.lower())) log_message(message) raise CryptoException(message) log_message('{} fingerprints agree and key has not expired for {}'.format( encryption_name, email)) return key_ok, verified, active
def import_key_now(encryption_name, keyblock, user_name, possible_fingerprint, passcode): ''' Import if key is ok and doesn't exist. ''' fingerprint_ok = True id_fingerprint_pairs = [] if encryption_name is None or keyblock is None: result_ok = False fingerprint_ok = False status = MISSING_DATA_STATUS log_message('crypto: {} / keyblock is None: {}'.format( encryption_name, keyblock is None)) else: encryption_software = crypto_software.get(encryption_name) plugin = KeyFactory.get_crypto(encryption_software.name, encryption_software.classname) if plugin is None: result_ok = False status = ( 'GoodCrypto does not currently support {encryption}').format( encryption=encryption_software.name) log_message('no plugin for {} with classname: {}'.format( encryption_software.name, encryption_software.classname)) else: id_fingerprint_pairs = plugin.get_id_fingerprint_pairs(keyblock) if id_fingerprint_pairs is None or len(id_fingerprint_pairs) <= 0: result_ok = False status = KEYBLOCK_INVALID else: result_ok = True for (user_id, fingerprint) in id_fingerprint_pairs: if email_in_domain(user_id): if passcode is None or len(passcode.strip()) <= 0: result_ok = False status = ( 'You must include the passcode when importing a key for {email}' ).format(email=user_id) break if result_ok: # make sure we don't already have crypto defined for this user contacts_crypto = contacts.get_contacts_crypto( user_id, encryption_name) if contacts_crypto is None or contacts_crypto.fingerprint is None: fingerprint, expiration = plugin.get_fingerprint( user_id) if fingerprint is not None: log_message('{} key exists for {}: {}'.format( encryption_name, user_id, fingerprint)) result_ok = False else: result_ok = False if not result_ok: status = ( 'A {encryption_name} key already exists for {email}. If you have a new key, then delete the Contact and then try importing the key again.' ).format(encryption_name=encryption_name, email=user_id) break # import the key if this is a new contact if result_ok: log_message( 'importing keys for {}'.format(id_fingerprint_pairs)) result_ok, status, fingerprint_ok = _import_key_add_contact( keyblock, user_name, possible_fingerprint, passcode, id_fingerprint_pairs, plugin) else: log_message('unable to import keys for {}'.format( id_fingerprint_pairs)) log_message("Imported public {} key ok: {}".format(encryption_name, result_ok)) log_message(" Status: {}".format(status)) return result_ok, status, fingerprint_ok, id_fingerprint_pairs
def post_save_contacts_crypto(sender, **kwargs): ''' Process the contact's encryption record after it's saved. After crypto record saved: * if record for managed domain: * if no user_key record: * create user_key record * create gpg key * get gpg fingerprint * update crypto record with fingerprint * if user_key record, but no fingerprint in crypto record: * get gpg fingerprint * update crypto record with fingerprint * if no fingerprint in crypto record * get gpg fingerprint * update crypto record with fingerprint ''' if TESTS_RUNNING: log_message('tests running so no post save processing') else: created = kwargs['created'] contacts_encryption = kwargs['instance'] email = contacts_encryption.contact.email encryption_name = contacts_encryption.encryption_software.name fingerprint = contacts_encryption.fingerprint log_message("starting post save for {} contact's {} crypto".format( email, encryption_name)) if email_in_domain(email): if created: from goodcrypto.mail.message.utils import sync_private_key_via_queue log_message( 'starting to add private {} user key for {}'.format( encryption_name, email)) if contacts_encryption.source is None: contacts_encryption.source = AUTO_GENERATED sync_private_key_via_queue(contacts_encryption) elif fingerprint is None: from goodcrypto.mail.message.utils import sync_fingerprint_via_queue log_message('setting private {} fingerprint for {}'.format( encryption_name, email)) sync_fingerprint_via_queue(contacts_encryption) else: log_message("{} already has {} fingerprint: {}".format( email, encryption_name, fingerprint)) elif fingerprint is None: from goodcrypto.mail.message.utils import sync_fingerprint_via_queue log_message('setting {} fingerprint for {}'.format( encryption_name, email)) sync_fingerprint_via_queue(contacts_encryption) else: log_message('{} already has {} crypto software defined'.format( email, encryption_name)) log_message("finished post save for {} contact's {} crypto".format( email, encryption_name))
def add(email, encryption_program, fingerprint=None, passcode=None, source=None): ''' Add a contact and related settings. >>> # In honor of Thomas Drake, a whistleblower about Trailblazer, a NSA mass surveillance project. >>> email = '*****@*****.**' >>> encryption_software = crypto_software.get(KeyFactory.DEFAULT_ENCRYPTION_NAME) >>> contact = add(email, KeyFactory.DEFAULT_ENCRYPTION_NAME) >>> contact.email '*****@*****.**' >>> contact.user_name 'Thomas' >>> address = contact.email >>> address = '*****@*****.**' >>> contacts_crypto = ContactsCrypto.objects.get( ... contact=contact, encryption_software=encryption_software) >>> contacts_crypto is not None True >>> x = contact.delete() >>> contact = add(None, encryption_software) >>> contact is None True >>> contact = add(email, None) >>> contact.email = '*****@*****.**' >>> contact.user_name = 'Thomas' >>> get_contacts_crypto(email) [] >>> x = contact.delete() >>> contact = add(None, None) >>> contact is None True >>> contact = add('*****@*****.**', None) >>> contact.email = '*****@*****.**' >>> contact.user_name = 'test.com domain key (system use only)' >>> x = contact.delete() ''' try: new_contact = True user_name, email_address = parse_address(email) if email_address is None: contact = None else: try: contact = Contact.objects.get(email=email_address) new_contact = False # update the user name if it's been given and it differs from the name in the DB if user_name is not None and contact.user_name != user_name: contact.user_name = user_name contact.save() log_message('updated {} user name to {}'.format( email_address, user_name)) except Contact.DoesNotExist: log_message('creating a contact for {}'.format(email_address)) try: if user_name is None or len(user_name.strip()) <= 0: from goodcrypto.mail.message.metadata import is_metadata_address user_name = email_address i = user_name.find('@') # handle domain keys specially if is_metadata_address(email_address): if i > 0: email_domain = user_name[i + 1:] user_name = '{} domain key (system use only)'.format( email_domain) else: if i > 0: user_name = user_name[:i] user_name = user_name.replace('.', ' ').replace( '-', ' ').replace('_', ' ') user_name = capwords(user_name) except: pass contact = Contact.objects.create(email=email_address, user_name=user_name) except Exception: record_exception() log_message('EXCEPTION - see syr.exception.log for details') contact = None if encryption_program is None: log_message( "no encryption software defined so not creating contact's crytpo record for {}" .format(email)) else: # add a corresponding record for the contact's crypto program encryption_software = crypto_software.get(encryption_program) if contact is None or encryption_software is None: log_message('no contact and/or encryption software defined') else: try: contacts_crypto = ContactsCrypto.objects.get( contact=contact, encryption_software=encryption_software) if (fingerprint is not None and strip_fingerprint(contacts_crypto.fingerprint) != strip_fingerprint(fingerprint)): contacts_crypto.fingerprint = format_fingerprint( fingerprint) if email_in_domain(email): if contacts.crypto.source is None: contacts.crypto.source = constants.AUTO_GENERATED if contacts_crypto.source == constants.AUTO_GENERATED: contacts_crypto.verified = True contacts_crypto.save() except ContactsCrypto.DoesNotExist: # if the contact existed without any contact crypto, but was set # to never encrypt and now we have a key, then change the # outbound encrypt policy to the default if (not new_contact and contact.outbound_encrypt_policy == constants.NEVER_ENCRYPT_OUTBOUND): contact.outbound_encrypt_policy = constants.DEFAULT_OUTBOUND_ENCRYPT_POLICY contact.save() contacts_crypto = add_contacts_crypto( contact, encryption_software, fingerprint=fingerprint, source=source) log_message( "created {} crypto record for {} with {} fingerprint: {}" .format(encryption_software, email, fingerprint, contacts_crypto is not None)) except: record_exception() log_message( 'EXCEPTION - see syr.exception.log for details') except Exception: contact = None record_exception() log_message('EXCEPTION - see syr.exception.log for details') return contact
def _import_key_add_contact(keyblock, user_name, possible_fingerprint, passcode, id_fingerprint_pairs, plugin): ''' Import keys and create associated contact records. ''' def update_contact(contact, crypto_name, fingerprint): fingerprint_ok = True try: if (user_name is not None and (contact.user_name is None or len(contact.user_name.strip()) <= 0)): contact.user_name = user_name.strip() contact.save() log_message('updated user name') else: log_message('user name: {}'.format(user_name)) log_message('contact user name: {}'.format(contact.user_name)) if possible_fingerprint is not None and len( possible_fingerprint.strip()) > 0: if strip_fingerprint(possible_fingerprint).lower( ) == strip_fingerprint(fingerprint).lower(): contacts_crypto = contacts.get_contacts_crypto( user_id, plugin.get_name()) contacts_crypto.verified = True contacts_crypto.save() log_message('verified fingerprint') else: fingerprint_ok = False log_message('possible fingerprint: {}'.format( strip_fingerprint(possible_fingerprint).lower())) log_message('imported fingerprint: {}'.format( strip_fingerprint(fingerprint).lower())) except: record_exception() log_message('EXCEPTION - see syr.exception.log for details') return fingerprint_ok result_ok = True fingerprint_ok = True status = i18n('Imported key:') if plugin is None: result_ok = False fingerprint_ok = False else: result_ok = plugin.import_public(keyblock, id_fingerprint_pairs) log_message('imported key: {}'.format(result_ok)) if result_ok: crypto_name = plugin.get_name() local_users = [] for (user_id, fingerprint) in id_fingerprint_pairs: if email_in_domain(user_id): local_users.append(user_id) if user_name is not None: full_email = '{} <{}>'.format(user_name, user_id) else: full_email = user_id contact = contacts.add(full_email, crypto_name, passcode=passcode, source=MANUALLY_IMPORTED) if contact is None: log_message('unable to add contact for {}'.format(user_id)) else: if not update_contact(contact, plugin.get_name(), fingerprint): fingerprint_ok = False status += ' {}'.format(user_id) if len(local_users) > 0: for local_user in local_users: if not plugin.is_passcode_valid( local_user, passcode, key_exists=True): result_ok = False status = ( 'The passcode is not correct for the imported key for {email}' ).format(email=user_id) log_message(status) break for (user_id, __) in id_fingerprint_pairs: if not result_ok: contacts.delete(user_id) log_message('deleted {}'.format(user_id)) else: result_ok = False status = KEYBLOCK_INVALID log_message(status) return result_ok, status, fingerprint_ok