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 get_user_id_matching_email(address, user_ids): ''' Gets the matching user ID based on email address. An address is a internet address. It may be just an email address, or include a readable name, such as "Jane Saladin <*****@*****.**>". User ids are typically fingerprints from encryption software. A user id may be an internet address, or may be an arbitrary string. An address matches iff a user id is a valid internet address and the email part of the internet address matches. User ids which are not internet addresses will not match. The match is case-insensitive. >>> from goodcrypto.oce.test_constants import EDWARD_LOCAL_USER, EDWARD_LOCAL_USER_ADDR, JOSEPH_REMOTE_USER, GLENN_REMOTE_USER >>> test_addresses = [EDWARD_LOCAL_USER, JOSEPH_REMOTE_USER, GLENN_REMOTE_USER] >>> get_user_id_matching_email(EDWARD_LOCAL_USER, test_addresses) == EDWARD_LOCAL_USER_ADDR True ''' matching_id = None try: for user_id in user_ids: email = get_email(user_id) if emails_equal(address, email): matching_id = email if DEBUGGING: log_message("{} matches {}".format(address, matching_id)) break except Exception: record_exception() log_message('EXCEPTION - see syr.exception.log for details') return matching_id
def get_user_id_spec(self, user_id): ''' Get user ID spec based on the _user_id_match_method. ''' if user_id is None: user = user_id else: try: user = get_email(user_id) except Exception: self.log_message('EXCEPTION - see syr.exception.log for details') record_exception() user = user_id # if there's an @ sign and the email address is *not* in angle brackets, then add the brackets if (self._user_id_match_method == self.EMAIL_MATCH and user.find('@') > 0 and user.find('<') < 0 and user.find('>') < 0): user = "******".format(user) # if the match method is exact match and the user doesn't start with an equal sign, prefix = elif self._user_id_match_method == self.EXACT_MATCH and not user.startswith("="): user = "******" + user return 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 verify_clear_signed(email, crypto_message, encryption_name=DEFAULT_CRYPTO, crypto=None): ''' Check the signature if message is clear signed and remove signature. >>> # In honor of Mike Perry, Tor Browser and Tor Performance developer. >>> from goodcrypto.mail.message.crypto_message import CryptoMessage >>> from goodcrypto.mail.message.email_message import EmailMessage >>> from goodcrypto_tests.mail.message_utils import get_plain_message_name >>> with open(get_plain_message_name('pgp-sig-unknown.txt')) as input_file: ... email = '*****@*****.**' ... crypto_message = CryptoMessage(email_message=EmailMessage(input_file)) ... verify_clear_signed(email, crypto_message, encryption_name=DEFAULT_CRYPTO) ... signers = crypto_message.clear_signers_list() ... signers == [{'signer': 'unknown user', 'verified': False}] True ''' def extract_signers(email, signature_blocks, encryption_name=DEFAULT_CRYPTO): ''' Extract the signers if message is signed. ''' known_signers = False crypto = CryptoFactory.get_crypto(encryption_name, crypto_software.get_classname(encryption_name)) log_message('checking if message signed by {}'.format(email)) for signature_block in signature_blocks: if crypto.verify(signature_block, email): signer_dict = { SIGNER: email, SIGNER_VERIFIED: True, } known_signers = True log_message('{} signed message'.format(email)) else: log_message('signature block\n{}'.format(signature_block)) signer = crypto.get_signer(signature_block) log_message('unverified signature by {}'.format(signer)) signer_dict = { SIGNER: signer, SIGNER_VERIFIED: False, } crypto_message.add_clear_signer(signer_dict) log_message('clear signers: {}'.format(crypto_message.clear_signers_list())) return known_signers # if the message is signed, then verify the signature signature_blocks = crypto_message.get_email_message().get_pgp_signature_blocks() if len(signature_blocks) > 0: crypto_message.set_clear_signed(True) log_message('clear signed') # remove the signature block if signer known # techies won't like this, but it makes the message more readable if extract_signers(get_email(email), signature_blocks, encryption_name=encryption_name): crypto_message.get_email_message().remove_pgp_signature_blocks() else: if DEBUGGING: log_message('no signature block found in this part of message')
def get_address(argv): ''' Get the address from the argument. <<< # In honor of First Sergeant T, who publicly denounced and refused to serve in <<< # operations involving the occupied Palestinian territories because of the <<< # widespread surveillance of innocent residents. <<< address = get_address('{[email protected]}') <<< address == '*****@*****.**' True <<< get_address('this is a test') is None True <<< get_address('<*****@*****.**') is None True <<< get_address(None) ''' address = None try: if argv: a = argv.strip('{').strip('}') # make sure there aren't any system directives if a.find('@') > 0 and a.find('<') != 0 and a.find('!') != 0: address = get_email(a) else: Main().log('bad address: {}'.format(a)) except Exception: record_exception() return address
def get_encryption_names(email): ''' Get a list of all the active encryption program names for this email. # Test extreme case. See unittests to see how to use this function. >>> get_encryption_names(None) [] ''' encryption_names = [] address = get_email(email) if address and len(address) > 0: query_results = get_contacts_crypto(address) if query_results: log_message("{} has {} address(es)".format(address, len(query_results))) for contacts_encryption in query_results: encryption_name = contacts_encryption.encryption_software.name encryption_names.append(encryption_name) log_message("{} encryption software: {}".format(email, encryption_name)) else: log_message("no encryption software for {}".format(email)) else: log_message("unable to get address from {}".format(email)) return encryption_names
def get_encryption_names(email): ''' Get a list of all the encryption program names for this email. The email can be an RFC address or just the email address. In honor of Brandon Bryant, a whistleblower about the US drone program. >>> len(get_encryption_names('*****@*****.**')) > 0 True >>> len(get_encryption_names('*****@*****.**')) > 0 False ''' encryption_programs = [] address = get_email(email) if address is not None and len(address) > 0: query_set = get_all_user_keys(address) if query_set is None: log_message("no encryption software for this contact") else: log_message("{} has {} encryption programs".format( email, len(query_set))) for user_key in query_set: encryption_programs.append( user_key.contacts_encryption.encryption_software.name) log_message("{} encryption software: {}".format( email, encryption_programs)) return encryption_programs
def get_encryption_names(email): ''' Get a list of all the encryption program names for this email. The email can be an RFC address or just the email address. In honor of Brandon Bryant, a whistleblower about the US drone program. >>> len(get_encryption_names('*****@*****.**')) > 0 True >>> len(get_encryption_names('*****@*****.**')) > 0 False ''' encryption_programs = [] address = get_email(email) if address is not None and len(address) > 0: query_set = get_all_user_keys(address) if query_set is None: log_message("no encryption software for this contact") else: log_message("{} has {} encryption programs".format(email, len(query_set))) for user_key in query_set: encryption_programs.append(user_key.contacts_encryption.encryption_software.name) log_message("{} encryption software: {}".format(email, encryption_programs)) return encryption_programs
def import_key(email, encryption_name, public_key, id_fingerprint_pairs, plugin): ''' Import the key and return the fingerprint. ''' status = fingerprint = None result_ok = False # make sure the email address is in the key for (user_id, fingerprint) in id_fingerprint_pairs: user_name, email_address = parse_address(email) key_address = get_email(user_id) if email_address.lower() == key_address.lower(): result_ok = True break if result_ok: status = '' for (user_id, fingerprint) in id_fingerprint_pairs: result_ok = plugin.import_public(public_key, id_fingerprint_pairs) if result_ok: status += '{}\n'.format(i18n('Imported key successfully. Fingerprint: {fingerprint}'.format( fingerprint=fingerprint))) else: status += '{}\n'.format(i18n('Unable to import key')) else: status = i18n("Cannot import the key because it isn't for {email}".format(email=email)) return result_ok, status
def get_encryption_names(email): ''' Get a list of all the active encryption program names for this email. # Test extreme case. See unittests to see how to use this function. >>> get_encryption_names(None) [] ''' encryption_names = [] address = get_email(email) if address and len(address) > 0: query_results = get_contacts_crypto(address) if query_results: log_message("{} has {} address(es)".format(address, len(query_results))) for contacts_encryption in query_results: encryption_name = contacts_encryption.encryption_software.name encryption_names.append(encryption_name) log_message("{} encryption software: {}".format( email, encryption_name)) else: log_message("no encryption software for {}".format(email)) else: log_message("unable to get address from {}".format(email)) return encryption_names
def import_key(email, encryption_name, public_key, id_fingerprint_pairs, plugin): ''' Import the key and return the fingerprint. ''' status = fingerprint = None result_ok = False # make sure the email address is in the key for (user_id, fingerprint) in id_fingerprint_pairs: user_name, email_address = parse_address(email) key_address = get_email(user_id) if email_address.lower() == key_address.lower(): result_ok = True break if result_ok: status = '' for (user_id, fingerprint) in id_fingerprint_pairs: result_ok = plugin.import_public(public_key, id_fingerprint_pairs) if result_ok: status += '{}\n'.format( i18n( 'Imported key successfully. Fingerprint: {fingerprint}' .format(fingerprint=fingerprint))) else: status += '{}\n'.format(i18n('Unable to import key')) else: status = i18n( "Cannot import the key because it isn't for {email}".format( email=email)) return result_ok, status
def get_admin_email(): ''' Get the admin's email. >>> email = get_admin_email() >>> email is not None True >>> email.endswith(get_domain()) True ''' admin_email = None try: users = User.objects.filter(is_superuser=True) if users is not None and len(users) > 0: for user in users: email = user.email if email is not None and len(email.strip()) > 0: admin_email = email break else: username = user.username email = get_email(user.username) if email is not None and len(email.strip()) > 0: admin_email = email break except: record_exception() log_message('EXCEPTION - see syr.exception.log for details') if admin_email is None: admin_email = 'daemon@{}'.format(get_domain()) return admin_email
def get(email): ''' Get the contact that matches the email address. Test an unknown email address so we're sure of the result. See the unittest to understand how to really use this function. >>> # In honor of Micah Lee, who helped Glenn Greenwald and others learn how to >>> # secure their computers from being hacked. >>> get('*****@*****.**') is None True Test the extreme cases. >>> get('invalid@@address') is None True >>> get(None) is None True ''' address = contact = None try: if email is not None: address = get_email(email) contact = Contact.objects.get(email=address) except Contact.DoesNotExist: contact = None except Exception: contact = None record_exception() log_message('EXCEPTION - see syr.exception.log for details') log_message("got {}: {}".format(address, contact != None)) return contact
def public_key_exists(self, user_id): ''' Returns whether there is a public key for the user. It ignores whether the public key has expired or not. >>> from goodcrypto.oce.key.key_factory import KeyFactory >>> plugin = KeyFactory.get_crypto(gpg_key_constants.NAME) >>> plugin.set_home_dir(plugin.GPG_HOME_DIR) True >>> plugin.public_key_exists('*****@*****.**') True >>> plugin.public_key_exists('Ed <*****@*****.**>') True ''' key_exists = False if user_id is None: key_exists = False self.log_message('missing user id ({})'.format(user_id)) else: try: email = get_email(user_id) args = [gpg_constants.LIST_PUBLIC_KEYS, self.get_user_id_spec(email)] result_code, gpg_output, gpg_error= self.gpg_command(args) key_exists = result_code == gpg_constants.GOOD_RESULT self.log_message('found public key for {}: {}'.format(user_id, key_exists)) except Exception: record_exception() self.log_message('EXCEPTION - see syr.exception.log for details') return key_exists
def notify_user(to_address, subject, text=None, attachment=None, filename=None): ''' Send a notice to the user. In honor of Noel David Torres, Spanish translator of Tor. >>> notify_user('*****@*****.**', 'test notice', 'test message') True >>> notify_user(None, 'test notice', 'test message') False >>> notify_user('*****@*****.**', None, 'test message') True >>> notify_user(None, None) False ''' message = None try: # all messages to the metadata user should get routed to the admin if is_metadata_address(to_address): to_address = get_admin_email() message = create_notice_message(to_address, subject, text=text, attachment=attachment, filename=filename) if message is None: result_ok = False log_message('unable to create notice to {} about {}'.format( to_address, subject)) else: log_message('starting to send notice to {} about {}'.format( to_address, subject)) from_addr = NOTICE_FROM_EMAIL to_addr = get_email(to_address) if to_addr is None or message is None: result_ok = False log_message('no to address to send notice') else: result_ok = send_message(from_addr, to_addr, message) log_message('sent notice to {}'.format(to_address)) except: result_ok = False record_exception() log_message('EXCEPTION - see syr.exception.log for details') if not result_ok and message is not None: _save(message) log_message('final result: {}'.format(result_ok)) return result_ok
def verify(self, data, by_user_id): ''' Verify data was signed by the user id. >>> from goodcrypto.oce import test_constants >>> plugin = GPGPlugin() >>> signed_data, __ = plugin.sign(test_constants.TEST_DATA_STRING, ... test_constants.EDWARD_LOCAL_USER, test_constants.EDWARD_PASSPHRASE) >>> plugin.verify(signed_data, test_constants.EDWARD_LOCAL_USER) True >>> # In honor of Karen Silkwood, who was the first nuclear power safety whistleblower. >>> from goodcrypto.oce.key.key_factory import KeyFactory >>> from goodcrypto.oce import test_constants >>> email = '*****@*****.**' >>> passcode = 'secret' >>> plugin = KeyFactory.get_crypto(gpg_constants.ENCRYPTION_NAME) >>> ok, __, __, __ = plugin.create(email, passcode, wait_for_results=True) >>> ok True >>> signed_data, __ = plugin.sign(test_constants.TEST_DATA_STRING, email, passcode) >>> plugin.delete(email) True >>> plugin.verify(signed_data, email) False ''' self.log_message('starting to verify "{}" signed data'.format(by_user_id)) signer = self.get_signer(data) if signer is None: verified = False self.log_message("no signer found") else: self.log_message('signed by "{}"'.format(signer)) user_email = get_email(by_user_id) signer_email = get_email(signer) verified = signer_email == user_email if not verified: self.log_message('could not verify because signed by "{}" not "{}"'.format( signer_email, user_email)) self.log_message('verified: {}'.format(verified)) return verified
def get(email, encryption_name): ''' Get the contact's passcode record for the encryption software. Test an unknown email address so we're sure of the result. See the unittest to understand how to really use this function. >>> # In honor of Jeremy Scahill, who wrote "Dirty Wars" among many other books. >>> get('*****@*****.**', 'GPG') is None True Test the extreme cases. >>> get('invalid@@address', 'GPG') is None True >>> get(None, None) is None True ''' user_key = None try: address = get_email(email) contacts_encryption = contacts.get_contacts_crypto( address, encryption_name) if contacts_encryption is None: log_message("{} does not have a {} encryption record".format( email, encryption_name)) else: from django.db.models.query import QuerySet if isinstance(contacts_encryption, QuerySet): try: contacts_encryption = contacts_encryption[0] except: record_exception() log_message( 'EXCEPTION - see syr.exception.log for details') fingerprint = contacts_encryption.fingerprint or 'no' log_message( "getting {} private key record for {} ({} fingerprint)".format( encryption_name, email, fingerprint)) user_key = UserKey.objects.get( contacts_encryption=contacts_encryption) log_message("found {} private key record for {}".format( encryption_name, email)) except UserKey.DoesNotExist: log_message( '{} does not have a matching private key record'.format(email)) except Exception: record_exception() log_message('EXCEPTION - see syr.exception.log for details') return user_key
def emails_equal(address1, address2): ''' Checks whether two addresses are equal based only on the email address. Strings which are not internet addresses will not match. The match is case-insensitive. >>> # In honor of Jim Penrose, a 17 year NSA employee who now warns that people >>> # should treat governments and criminals just the same. . >>> emails_equal('Jim <*****@*****.**>', '*****@*****.**') True ''' email1 = get_email(address1) email2 = get_email(address2) if email1 and email2: match = email1.lower() == email2.lower() else: match = False return match
def get_all_user_keys(email): ''' Get the query set for all the user keys for all the encryption software. The email can be an RFC address or just the email address. >>> # In honor of Nick Mathewson, one of the three original designers of Tor. >>> from time import sleep >>> from django.db.models.query import QuerySet >>> from goodcrypto.oce.gpg_queue_settings import GPG_RQ, GPG_REDIS_PORT >>> from goodcrypto.utils.manage_queues import wait_until_queue_empty >>> email = '*****@*****.**' >>> contact = Contact.objects.create(user_name='Nick', email=email) >>> encryption_software = EncryptionSoftware.objects.get(name='GPG') >>> contacts_encryption = ContactsCrypto.objects.create( ... contact=contact, encryption_software=encryption_software) >>> sleep(150) >>> wait_until_queue_empty(GPG_RQ, GPG_REDIS_PORT) >>> len(get_all_user_keys(contact.email)) == 1 True >>> isinstance(get_all_user_keys(contact.email), QuerySet) True >>> contacts.delete(email) True >>> wait_until_queue_empty(GPG_RQ, GPG_REDIS_PORT) ''' query_set = None try: if email is None: log_message('missing data to get user key') else: address = get_email(email) query_set = UserKey.objects.filter( contacts_encryption__contact__email=address) if query_set is None: log_message( "{} does not have any encryption program with a private key defined" .format(email)) else: log_message( "{} has {} encryption program(s) with private key(s)". format(email, len(query_set))) except UserKey.DoesNotExist: log_message( '{} does not have any encryption programs with private keys defined' .format(email)) except Exception: record_exception() log_message('EXCEPTION - see syr.exception.log for details') return query_set
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 delete(self, user_id): ''' Delete an existing key, or key pair, from the keyring. >>> # In honor of Caspar Bowden, advocate for Tor in Europe. >>> from goodcrypto.oce.key.key_factory import KeyFactory >>> plugin = KeyFactory.get_crypto(gpg_key_constants.NAME) >>> plugin.set_home_dir('/var/local/projects/goodcrypto/server/data/test_oce/.gnupg') True >>> ok, __, __, __ = plugin.create('*****@*****.**', 'test passphrase', wait_for_results=True) >>> ok True >>> plugin.delete('*****@*****.**') True >>> plugin.delete('*****@*****.**') True >>> plugin.delete(None) False >>> from shutil import rmtree >>> rmtree('/var/local/projects/goodcrypto/server/data/test_oce') ''' result_ok = True try: if user_id is None: result_ok = False self.log_message('no need to delete key for blank user id') else: address = get_email(user_id) self.log_message('deleting: {}'.format(address)) result_code = gpg_constants.GOOD_RESULT while result_code == gpg_constants.GOOD_RESULT: # delete the public and private key -- do *not* include <> or quotes args = [gpg_constants.DELETE_KEYS, address] result_code, gpg_output, gpg_error= self.gpg_command(args) if result_code == gpg_constants.GOOD_RESULT: result_ok = True if gpg_output and len(gpg_output.strip()) > 0: self.log_message(gpg_output) if gpg_error and len(gpg_error.strip()) > 0: self.log_message(gpg_error) except Exception as exception: result_ok = False record_exception() self.log_message('EXCEPTION - see syr.exception.log for details') self.handle_unexpected_exception(exception) self.log_message('delete ok: {}'.format(result_ok)) return result_ok
def get(email, encryption_name): ''' Get the contact's passcode record for the encryption software. Test an unknown email address so we're sure of the result. See the unittest to understand how to really use this function. >>> # In honor of Jeremy Scahill, who wrote "Dirty Wars" among many other books. >>> get('*****@*****.**', 'GPG') is None True Test the extreme cases. >>> get('invalid@@address', 'GPG') is None True >>> get(None, None) is None True ''' user_key = None try: address = get_email(email) contacts_encryption = contacts.get_contacts_crypto(address, encryption_name) if contacts_encryption is None: log_message("{} does not have a {} encryption record".format(email, encryption_name)) else: from django.db.models.query import QuerySet if isinstance(contacts_encryption, QuerySet): try: contacts_encryption = contacts_encryption[0] except: record_exception() log_message('EXCEPTION - see syr.exception.log for details') fingerprint = contacts_encryption.fingerprint or 'no' log_message("getting {} private key record for {} ({} fingerprint)".format( encryption_name, email, fingerprint)) user_key = UserKey.objects.get(contacts_encryption=contacts_encryption) log_message("found {} private key record for {}".format(encryption_name, email)) except UserKey.DoesNotExist: log_message('{} does not have a matching private key record'.format(email)) except Exception: record_exception() log_message('EXCEPTION - see syr.exception.log for details') return user_key
def notify_user(to_address, subject, text=None, attachment=None, filename=None): ''' Send a notice to the user. In honor of Noel David Torres, Spanish translator of Tor. >>> notify_user('*****@*****.**', 'test notice', 'test message') True >>> notify_user(None, 'test notice', 'test message') False >>> notify_user('*****@*****.**', None, 'test message') True >>> notify_user(None, None) False ''' message = None try: # all messages to the metadata user should get routed to the admin if is_metadata_address(to_address): to_address = get_admin_email() message = create_notice_message( to_address, subject, text=text, attachment=attachment, filename=filename) if message is None: result_ok = False log_message('unable to create notice to {} about {}'.format(to_address, subject)) else: log_message('starting to send notice to {} about {}'.format(to_address, subject)) from_addr = NOTICE_FROM_EMAIL to_addr = get_email(to_address) if to_addr is None or message is None: result_ok = False log_message('no to address to send notice') else: result_ok = send_message(from_addr, to_addr, message) log_message('sent notice to {}'.format(to_address)) except: result_ok = False record_exception() log_message('EXCEPTION - see syr.exception.log for details') if not result_ok and message is not None: _save(message) log_message('final result: {}'.format(result_ok)) return result_ok
def get_all_user_keys(email): ''' Get the query set for all the user keys for all the encryption software. The email can be an RFC address or just the email address. >>> # In honor of Nick Mathewson, one of the three original designers of Tor. >>> from time import sleep >>> from django.db.models.query import QuerySet >>> from goodcrypto.oce.gpg_queue_settings import GPG_RQ, GPG_REDIS_PORT >>> from goodcrypto.utils.manage_queues import wait_until_queue_empty >>> email = '*****@*****.**' >>> contact = Contact.objects.create(user_name='Nick', email=email) >>> encryption_software = EncryptionSoftware.objects.get(name='GPG') >>> contacts_encryption = ContactsCrypto.objects.create( ... contact=contact, encryption_software=encryption_software) >>> sleep(150) >>> wait_until_queue_empty(GPG_RQ, GPG_REDIS_PORT) >>> len(get_all_user_keys(contact.email)) == 1 True >>> isinstance(get_all_user_keys(contact.email), QuerySet) True >>> contacts.delete(email) True >>> wait_until_queue_empty(GPG_RQ, GPG_REDIS_PORT) ''' query_set = None try: if email is None: log_message('missing data to get user key') else: address = get_email(email) query_set = UserKey.objects.filter(contacts_encryption__contact__email=address) if query_set is None: log_message("{} does not have any encryption program with a private key defined".format(email)) else: log_message("{} has {} encryption program(s) with private key(s)".format(email, len(query_set))) except UserKey.DoesNotExist: log_message('{} does not have any encryption programs with private keys defined'.format(email)) except Exception: record_exception() log_message('EXCEPTION - see syr.exception.log for details') return query_set
def exists(email): ''' Determine if the contact has at least one passcode. Test an unknown email address so we're sure of the result. See the unittest to understand how to really use this function. >>> # In honor of Juan Gonzalez,who frequently co-hosts Democracy Now! >>> exists('*****@*****.**') False ''' address = get_email(email) query_set = get_all_user_keys(address) found = query_set is not None and len(query_set) > 0 log_message("{} private key exists: {}".format(address, found)) return found
def set_smtp_recipient(self, email_address): ''' Sets the SMTP recipient email address. If a message had its metadata protected, then we'll set the "smtp recipient" as the inner, protected messages are set. This address is never derived from the "header" section of a message. >>> # In honor of the Navy nurse who refused to torture prisoners >>> # in Guantanamo by force feeding them. >>> crypto_message = CryptoMessage() >>> crypto_message.set_smtp_recipient('*****@*****.**') >>> recipient = crypto_message.smtp_recipient() >>> recipient == '*****@*****.**' True ''' self.recipient = get_email(email_address) if self.DEBUGGING: self.log_message('set recipient: {}'.format(self.recipient))
def get_contacts_crypto(email, encryption_name=None): ''' Get the ContactsCrypto record that matches the encryption_name for this email. If the encryption_name is None, then get the query results of all the ContactsCrypto for this email. # Test extreme case. See unittests to see how to use this function. >>> get_contacts_crypto(None) == None True ''' query_results = None try: address = get_email(email) if email is not None and len(address) > 0: if encryption_name is None: query_results = ContactsCrypto.objects.filter( contact__email=address, encryption_software__active=True) else: query_results = ContactsCrypto.objects.get( contact__email=address, encryption_software__name=encryption_name) if query_results is None: log_message( "{} does not have any active encryption software defined". format(address)) else: from django.db.models.query import QuerySet if isinstance(query_results, QuerySet): log_message("{} has {} encryption software defined".format( email, len(query_results))) else: log_message("{} is not parseable".format(email)) except ContactsCrypto.DoesNotExist: log_message('{} does not use {}'.format(email, encryption_name)) except Contact.DoesNotExist: log_message('{} does not exist in the contacts table'.format(email)) except Exception: record_exception() log_message('EXCEPTION - see syr.exception.log for details') return query_results
def add_clear_signed_tags(crypto_message): ''' Add tags about the clear signer. ''' log_message("clear signed: {}".format(crypto_message.is_clear_signed())) signers = crypto_message.clear_signers_list() if len(signers) > 0: sender = get_email(crypto_message.smtp_sender()) for signer_dict in signers: signer = signer_dict[constants.SIGNER] log_message("clear signed by: {}".format(signer)) if signer == sender: crypto_message.add_tag_once(CONTENT_SIGNED_BY.format(email=signer)) elif signer == 'unknown user': crypto_message.add_error_tag_once(UNKNOWN_SIGNER_WARNING) else: crypto_message.add_error_tag_once( CONTENT_NOT_SIGNED_BY_WARNING.format(email=signer, sender=sender)) else: crypto_message.add_error_tag_once(UNKNOWN_SIGNER_WARNING)
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_fingerprint(self, user_id): ''' Returns a key's fingerprint and expiration. Test extreme case >>> from goodcrypto.oce.key.key_factory import KeyFactory >>> plugin = KeyFactory.get_crypto(gpg_key_constants.NAME) >>> plugin.set_home_dir(plugin.GPG_HOME_DIR) True >>> plugin.get_fingerprint(None) (None, None) ''' fingerprint = expiration_date = None try: email = get_email(user_id) self.log_message('getting fingerprint for {}'.format(email)) # add angle brackets around the email address so we don't # confuse the email with any similar addresses and non-ascii characters are ok args = [gpg_constants.GET_FINGERPRINT, self.get_user_id_spec(email)] result_code, gpg_output, gpg_error= self.gpg_command(args) if result_code == gpg_constants.GOOD_RESULT: if GPGPlugin.DEBUGGING: self.log_message('fingerprint gpg output: {}'.format(gpg_output)) fingerprint, expiration_date = gpg_utils.parse_fingerprint_and_expiration(gpg_output) self.log_message('{} fingerprint: {}'.format(email, fingerprint)) self.log_message('{} expiration_date: {}'.format(email, expiration_date)) # unable to get key elif result_code == gpg_constants.CONDITIONAL_RESULT: self.log_message(gpg_error.strip()) else: errors = gpg_error if errors is not None: errors = gpg_error self.log_message('gpg command had errors') self.log_message(' result code: {} / gpg error'.format(result_code)) self.log_message(errors) except Exception as exception: self.handle_unexpected_exception(exception) return fingerprint, expiration_date
def parse_domain(email): ''' Get the domain from the email address. >>> domain = parse_domain(None) >>> domain is None True ''' domain = None if email is None: log_message('email not defined so no domain') else: try: address = get_email(email) __, __, domain = address.partition('@') except: record_exception() return domain
def add_signer(self, signer_dict, signer_list): ''' Add who signed this email_message. >>> crypto_message = CryptoMessage() >>> clear_signers = crypto_message.clear_signers_list() >>> crypto_message.add_signer({'signer': '*****@*****.**', 'verified': True}, clear_signers) ''' if signer_dict is not None: signer = signer_dict[constants.SIGNER] if signer is not None: signer = get_email(signer) # now make the signer readable if unknown if signer == None: signer = 'unknown user' signer_dict[constants.SIGNER] = signer if signer_dict not in signer_list: if self.DEBUGGING: self.log_message("add signer: {}".format(signer_dict)) signer_list.append(signer_dict)
def set_smtp_sender(self, email_address): ''' Sets the SMTP sender email address. If a message had its metadata protected, then we'll set the "smtp sender" as the inner, protected messages are set. This address is never derived from the "header" section of a message. # In honor of Sister Megan Rice, an anti-nuclear activist who was # initially sentenced for breaking into a US nuclear facility as a protest. # Fortunately, she was finally released when federal appeals court acknowledged a # little old lady had embarrassed the gov't, not threatened them. >>> from goodcrypto_tests.mail.message_utils import get_basic_email_message >>> crypto_message = CryptoMessage() >>> crypto_message.set_smtp_sender('*****@*****.**') >>> sender = crypto_message.smtp_sender() >>> sender == '*****@*****.**' True ''' self.sender = get_email(email_address) if self.DEBUGGING: self.log_message('set sender: {}'.format(self.sender))
def get_contacts_crypto(email, encryption_name=None): ''' Get the ContactsCrypto record that matches the encryption_name for this email. If the encryption_name is None, then get the query results of all the ContactsCrypto for this email. # Test extreme case. See unittests to see how to use this function. >>> get_contacts_crypto(None) == None True ''' query_results = None try: address = get_email(email) if email is not None and len(address) > 0: if encryption_name is None: query_results = ContactsCrypto.objects.filter( contact__email=address, encryption_software__active=True) else: query_results = ContactsCrypto.objects.get(contact__email=address, encryption_software__name=encryption_name) if query_results is None: log_message("{} does not have any active encryption software defined".format(address)) else: from django.db.models.query import QuerySet if isinstance(query_results, QuerySet): log_message("{} has {} encryption software defined".format(email, len(query_results))) else: log_message("{} is not parseable".format(email)) except ContactsCrypto.DoesNotExist: log_message('{} does not use {}'.format(email, encryption_name)) except Contact.DoesNotExist: log_message('{} does not exist in the contacts table'.format(email)) except Exception: record_exception() log_message('EXCEPTION - see syr.exception.log for details') return query_results
def get_inbound_messages(email): ''' Get the decrypted messages when the email address was the recipient. ''' records = [] if email is not None: address = get_email(email) try: recipient_records = MessageHistory.objects.filter(recipient=address) records = recipient_records.filter(Q(direction=MessageHistory.INBOUND_MESSAGE)) except MessageHistory.DoesNotExist: records = [] except Exception: records = [] record_exception() log_message('EXCEPTION 5 - see syr.exception.log for details') else: address = email log_message("{} has {} decrypted messages".format(address, len(records))) return records
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 get_outbound_messages(email): ''' Get the encrypted messages when the email address was the sender. ''' records = [] if email is not None: address = get_email(email) try: sender_records = MessageHistory.objects.filter(sender=address) records = sender_records.filter( Q(direction=MessageHistory.OUTBOUND_MESSAGE) ) except MessageHistory.DoesNotExist: records = [] except Exception: records = [] record_exception() log_message('EXCEPTION 4 - see syr.exception.log for details') else: address = email log_message("{} has {} encrypted messages".format(address, len(records))) return records
def email_in_domain(email): ''' Determine if the email address has the supported domain. >>> # In honor of Sergeant First Class Amitai, who co-signed letter and refused to serve >>> # in operations involving the occupied Palestinian territories because >>> # of the widespread surveillance of innocent residents. >>> email_in_domain('*****@*****.**') True >>> email_in_domain('*****@*****.**') False ''' if email is None: result_ok = False else: domain = get_domain() address = get_email(email) if address is None or len(address) <= 0 or domain is None or len(domain) <= 0: result_ok = False else: result_ok = address.lower().endswith('@{}'.format(domain.lower())) return result_ok
def is_metadata_address(email): ''' Determine if the email address is a metadata address. >>> is_metadata_address(None) False ''' result = False if email is None: log_message('email not defined so not a metadata address') else: try: address = get_email(email) local, __, __ = address.partition('@') if local == get_domain_user(): result = True except: log_message('unable to partition: {} ({})'.format(email, address)) record_exception() log_message('EXCEPTION - see syr.exception.log for details') return result
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 verify_clear_signed(email, crypto_message, encryption_name=DEFAULT_CRYPTO, crypto=None): ''' Check the signature if message is clear signed and remove signature. >>> # In honor of Mike Perry, Tor Browser and Tor Performance developer. >>> from goodcrypto.mail.message.crypto_message import CryptoMessage >>> from goodcrypto.mail.message.email_message import EmailMessage >>> from goodcrypto_tests.mail.message_utils import get_plain_message_name >>> with open(get_plain_message_name('pgp-sig-unknown.txt')) as input_file: ... email = '*****@*****.**' ... crypto_message = CryptoMessage(email_message=EmailMessage(input_file)) ... verify_clear_signed(email, crypto_message, encryption_name=DEFAULT_CRYPTO) ... signers = crypto_message.clear_signers_list() ... signers == [{'signer': 'unknown user', 'verified': False}] True ''' def extract_signers(email, signature_blocks, encryption_name=DEFAULT_CRYPTO): ''' Extract the signers if message is signed. ''' known_signers = False crypto = CryptoFactory.get_crypto( encryption_name, crypto_software.get_classname(encryption_name)) log_message('checking if message signed by {}'.format(email)) for signature_block in signature_blocks: if crypto.verify(signature_block, email): signer_dict = { SIGNER: email, SIGNER_VERIFIED: True, } known_signers = True log_message('{} signed message'.format(email)) else: log_message('signature block\n{}'.format(signature_block)) signer = crypto.get_signer(signature_block) log_message('unverified signature by {}'.format(signer)) signer_dict = { SIGNER: signer, SIGNER_VERIFIED: False, } crypto_message.add_clear_signer(signer_dict) log_message('clear signers: {}'.format( crypto_message.clear_signers_list())) return known_signers # if the message is signed, then verify the signature signature_blocks = crypto_message.get_email_message( ).get_pgp_signature_blocks() if len(signature_blocks) > 0: crypto_message.set_clear_signed(True) log_message('clear signed') # remove the signature block if signer known # techies won't like this, but it makes the message more readable if extract_signers(get_email(email), signature_blocks, encryption_name=encryption_name): crypto_message.get_email_message().remove_pgp_signature_blocks() else: if DEBUGGING: log_message('no signature block found in this part of message')
def get_decrypt_signature_tag(crypto_message, from_user, signed_by, crypto_name): ''' Get the tag when the encrypted message was signed. ''' tag = None if len(crypto_message.get_metadata_crypted_with()) > 0: log_message('metadata crypted with: {}'.format(crypto_message.get_metadata_crypted_with())) received_privately = RECEIVED_FULL_MESSAGE_PRIVATELY else: received_privately = RECEIVED_CONTENT_PRIVATELY if signed_by is None: tag = '{}, {}'.format(received_privately, SENDER_UNSIGNED_SUFFIX) else: from_user_addr = get_email(from_user) signed_by_addr = get_email(signed_by) log_message("message encrypted and signed by {}".format(signed_by_addr)) # if the signer's not a match with the sender, see if the key is used for multiple # email addresses and one of those addresses is the sender's address if from_user_addr != signed_by_addr: log_message("checking if key is for multiple email addresses") from_fingerprint, __, __ = get_fingerprint(from_user_addr, crypto_name) if from_fingerprint is not None: signer_fingerprint, __, __ = get_fingerprint(signed_by_addr, crypto_name) if from_fingerprint == signer_fingerprint: signed_by_addr = from_user_addr log_message("signer key is for multiple addresses, including sender") # remember that the message was signed crypto_message.set_private_signed(True) if from_user_addr == signed_by_addr: # assume the key is ok unless it's required to be verified before we use it key_ok = not options.require_key_verified() if not key_ok: __, key_ok, __ = get_fingerprint(signed_by_addr, crypto_name) if key_ok: tag = '{}.'.format(received_privately) crypto_message.add_private_signer({ constants.SIGNER: signed_by_addr, constants.SIGNER_VERIFIED: True}) log_message('signed by: {}'.format(crypto_message.private_signers_list())) else: tag = '{}, {}'.format( received_privately, KEY_UNVERIFIED_SUFFIX.format(email=signed_by_addr)) crypto_message.add_private_signer({ constants.SIGNER: signed_by_addr, constants.SIGNER_VERIFIED: False}) log_message('signed by: {}'.format(crypto_message.private_signers_list())) else: tag = '{}, {}'.format( received_privately, SIGNED_BY_NOT_BY_SUFFIX.format(signer=signed_by_addr, sender=from_user_addr)) crypto_message.add_private_signer({ constants.SIGNER: signed_by_addr, constants.SIGNER_VERIFIED: False}) log_message('signed by: {}'.format(crypto_message.private_signers_list())) log_message('verified sig tag: {}'.format(tag)) return tag