def test_encrypt_source_file(self, setup_journalist_key_and_gpg_folder, tmp_path): # Given an encryption manager journalist_key_fingerprint, gpg_key_dir = setup_journalist_key_and_gpg_folder encryption_mgr = EncryptionManager( gpg_key_dir=gpg_key_dir, journalist_key_fingerprint=journalist_key_fingerprint) # And a file to be submitted by a source - we use this python file file_to_encrypt_path = Path(__file__) with file_to_encrypt_path.open() as file_to_encrypt: # When the source tries to encrypt the file # It succeeds encrypted_file_path = tmp_path / "file.gpg" encryption_mgr.encrypt_source_file( file_in=file_to_encrypt, encrypted_file_path_out=encrypted_file_path, ) # And the output file contains the encrypted data encrypted_file = encrypted_file_path.read_bytes() assert encrypted_file # And the journalist is able to decrypt the file with import_journalist_private_key(encryption_mgr): decrypted_file = encryption_mgr._gpg.decrypt(encrypted_file).data assert decrypted_file.decode() == file_to_encrypt_path.read_text() # And the source or anyone else is NOT able to decrypt the file # For GPG 2.1+, a non-null passphrase _must_ be passed to decrypt() assert not encryption_mgr._gpg.decrypt(encrypted_file, passphrase="test 123").ok
def test_encrypt_source_message(self, setup_journalist_key_and_gpg_folder, tmp_path): # Given an encryption manager journalist_key_fingerprint, gpg_key_dir = setup_journalist_key_and_gpg_folder encryption_mgr = EncryptionManager( gpg_key_dir=gpg_key_dir, journalist_key_fingerprint=journalist_key_fingerprint) # And a message to be submitted by a source message = "s3cr3t message" # When the source tries to encrypt the message # It succeeds encrypted_message_path = tmp_path / "message.gpg" encryption_mgr.encrypt_source_message( message_in=message, encrypted_message_path_out=encrypted_message_path) # And the output file contains the encrypted data encrypted_message = encrypted_message_path.read_bytes() assert encrypted_message # And the journalist is able to decrypt the message with import_journalist_private_key(encryption_mgr): decrypted_message = encryption_mgr._gpg.decrypt( encrypted_message).data assert decrypted_message.decode() == message # And the source or anyone else is NOT able to decrypt the message # For GPG 2.1+, a non-null passphrase _must_ be passed to decrypt() assert not encryption_mgr._gpg.decrypt(encrypted_message, passphrase="test 123").ok
async def save_notes(message): msg = message["text"].replace('/save', '', 1).strip() if len(msg) > 0: msg = EncryptionManager(message["from"]["id"]).encrypt_data(msg) db.save_notes(message["from"]["id"], msg, datetime.now().strftime("%y-%m-%d %H:%M:%S")) await message.reply("Saved 👍") else: await message.reply("SEND SOMETHING TO SAVE! 😕")
def test_get_source_public_key_wrong_id( self, setup_journalist_key_and_gpg_folder): # Given an encryption manager journalist_key_fingerprint, gpg_key_dir = setup_journalist_key_and_gpg_folder encryption_mgr = EncryptionManager( gpg_key_dir=gpg_key_dir, journalist_key_fingerprint=journalist_key_fingerprint) # When using the encryption manager to fetch a key for an invalid filesystem id # It fails with pytest.raises(GpgKeyNotFoundError): encryption_mgr.get_source_public_key("1234test")
def test_delete_source_key_pair_on_journalist_key( self, setup_journalist_key_and_gpg_folder): # Given an encryption manager journalist_key_fingerprint, gpg_key_dir = setup_journalist_key_and_gpg_folder encryption_mgr = EncryptionManager( gpg_key_dir=gpg_key_dir, journalist_key_fingerprint=journalist_key_fingerprint) # When trying to delete the journalist key via the encryption manager # It fails with pytest.raises(GpgKeyNotFoundError): encryption_mgr.delete_source_key_pair(journalist_key_fingerprint)
async def get_saved_notes(message): data = db.get_notes(message["from"]["id"]) if len(data) > 0: msg = '''Your saved notes 📖 are:''' enc = EncryptionManager(message["from"]["id"]) for i in data: msg += "\n\n" + enc.decrypt_data(i[0]) await message.reply(msg) del enc else: await message.reply("YOU HAVE NOT SAVED ANYTHING YET! 😕")
def test_get_journalist_public_key(self, setup_journalist_key_and_gpg_folder): # Given an encryption manager journalist_key_fingerprint, gpg_key_dir = setup_journalist_key_and_gpg_folder encryption_mgr = EncryptionManager( gpg_key_dir=gpg_key_dir, journalist_key_fingerprint=journalist_key_fingerprint) # When using the encryption manager to fetch the journalist public key # It succeeds journalist_pub_key = encryption_mgr.get_journalist_public_key() assert journalist_pub_key assert journalist_pub_key.startswith( "-----BEGIN PGP PUBLIC KEY BLOCK----")
def test_encrypt_fails(self, setup_journalist_key_and_gpg_folder, tmp_path): # Given an encryption manager journalist_key_fingerprint, gpg_key_dir = setup_journalist_key_and_gpg_folder encryption_mgr = EncryptionManager( gpg_key_dir=gpg_key_dir, journalist_key_fingerprint=journalist_key_fingerprint) # When trying to encrypt some data without providing any recipient # It fails and the right exception is raised with pytest.raises(GpgEncryptError) as exc: encryption_mgr._encrypt( using_keys_with_fingerprints=[], plaintext_in="test", ciphertext_path_out=tmp_path / "encrypt_fails", ) assert "no terminal at all requested" in str(exc)
def test_generate_source_key_pair(self, setup_journalist_key_and_gpg_folder, source_app, app_storage): # Given a source user with source_app.app_context(): source_user = create_source_user( db_session=db.session, source_passphrase=PassphraseGenerator.get_default(). generate_passphrase(), source_app_storage=app_storage, ) # And an encryption manager journalist_key_fingerprint, gpg_key_dir = setup_journalist_key_and_gpg_folder encryption_mgr = EncryptionManager( gpg_key_dir=gpg_key_dir, journalist_key_fingerprint=journalist_key_fingerprint) # When using the encryption manager to generate a key pair for this source user # It succeeds encryption_mgr.generate_source_key_pair(source_user) # And the newly-created key's fingerprint was added to Redis fingerprint_in_redis = encryption_mgr._redis.hget( encryption_mgr.REDIS_FINGERPRINT_HASH, source_user.filesystem_id) assert fingerprint_in_redis source_key_fingerprint = encryption_mgr.get_source_key_fingerprint( source_user.filesystem_id) assert fingerprint_in_redis == source_key_fingerprint # And the user's newly-generated public key can be retrieved assert encryption_mgr.get_source_public_key(source_user.filesystem_id) # And the key has a hardcoded creation date to avoid leaking information about when sources # first created their account source_key_details = encryption_mgr._get_source_key_details( source_user.filesystem_id) assert source_key_details creation_date = _parse_gpg_date_string(source_key_details["date"]) assert creation_date.date( ) == EncryptionManager.DEFAULT_KEY_CREATION_DATE # And the user's key does not expire assert source_key_details["expires"] == ""
def test_submit_and_retrieve_happy_path(self, sd_servers_v2_with_clean_state, tor_browser_web_driver, firefox_web_driver): # Given a source user accessing the app from their browser source_app_nav = SourceAppNagivator( source_app_base_url=sd_servers_v2_with_clean_state. source_app_base_url, web_driver=tor_browser_web_driver, ) # And they created an account source_app_nav.source_visits_source_homepage() source_app_nav.source_clicks_submit_documents_on_homepage() source_app_nav.source_continues_to_submit_page() # And the source user submitted a message submitted_message = "Confidential message with some international characters: éèö" source_app_nav.source_submits_a_message(message=submitted_message) source_app_nav.source_logs_out() # When a journalist logs in journ_app_nav = JournalistAppNavigator( journalist_app_base_url=sd_servers_v2_with_clean_state. journalist_app_base_url, web_driver=firefox_web_driver, ) journ_app_nav.journalist_logs_in( username=sd_servers_v2_with_clean_state.journalist_username, password=sd_servers_v2_with_clean_state.journalist_password, otp_secret=sd_servers_v2_with_clean_state.journalist_otp_secret, ) journ_app_nav.journalist_checks_messages() # And they try to download the message # Then it succeeds and the journalist sees correct message servers_sd_config = sd_servers_v2_with_clean_state.config_in_use retrieved_message = journ_app_nav.journalist_downloads_first_message( encryption_mgr_to_use_for_decryption=EncryptionManager( gpg_key_dir=Path(servers_sd_config.GPG_KEY_DIR), journalist_key_fingerprint=servers_sd_config.JOURNALIST_KEY, )) assert retrieved_message == submitted_message
def test_submit_and_retrieve_happy_path(self, locale, sd_servers_v2_with_clean_state, tor_browser_web_driver, firefox_web_driver): # Given a source user accessing the app from their browser locale_with_commas = locale.replace("_", "-") source_app_nav = SourceAppNagivator( source_app_base_url=sd_servers_v2_with_clean_state. source_app_base_url, web_driver=tor_browser_web_driver, accept_languages=locale_with_commas, ) # And they created an account source_app_nav.source_visits_source_homepage() source_app_nav.source_clicks_submit_documents_on_homepage() source_app_nav.source_continues_to_submit_page() source_codename = source_app_nav.source_retrieves_codename_from_hint() # And the source user submitted a file submitted_content = "Confidential file with some international characters: éèö" source_app_nav.source_submits_a_file(file_content=submitted_content) source_app_nav.source_logs_out() # And a journalist logs in journ_app_nav = JournalistAppNavigator( journalist_app_base_url=sd_servers_v2_with_clean_state. journalist_app_base_url, web_driver=firefox_web_driver, ) journ_app_nav.journalist_logs_in( username=sd_servers_v2_with_clean_state.journalist_username, password=sd_servers_v2_with_clean_state.journalist_password, otp_secret=sd_servers_v2_with_clean_state.journalist_otp_secret, ) journ_app_nav.journalist_checks_messages() # When they star and unstar the submission, then it succeeds self._journalist_stars_and_unstars_single_message(journ_app_nav) # And when they try to download the file # Then it succeeds and the journalist sees the correct content apps_sd_config = sd_servers_v2_with_clean_state.config_in_use retrieved_message = journ_app_nav.journalist_downloads_first_message( encryption_mgr_to_use_for_decryption=EncryptionManager( gpg_key_dir=Path(apps_sd_config.GPG_KEY_DIR), journalist_key_fingerprint=apps_sd_config.JOURNALIST_KEY, )) assert retrieved_message == submitted_content # And when they reply to the source, it succeeds journ_app_nav.journalist_sends_reply_to_source() # And when the source user comes back source_app_nav.source_visits_source_homepage() source_app_nav.source_chooses_to_login() source_app_nav.source_proceeds_to_login(codename=source_codename) save_screenshot_and_html(source_app_nav.driver, locale, "source-checks_for_reply") # When they delete the journalist's reply, it succeeds self._source_deletes_journalist_reply(source_app_nav) save_screenshot_and_html(source_app_nav.driver, locale, "source-deletes_reply")
from flask import Flask, jsonify, request from encryption import EncryptionManager from studies import StudyMatcher from validation import ValidationManager from tests import get_test_graph from queries import * import uuid TEST_GRAPH_SIZE = 20000 app = Flask(__name__) # Setup encryption = EncryptionManager() study_matcher = StudyMatcher() validation = ValidationManager() key_session_map = {} graph = get_test_graph(TEST_GRAPH_SIZE) @app.route('/studies/check', methods=['GET']) def check_for_studies(): global encryption, study_matcher, validation decrypted_request = encryption.decrypt(request.args) user_token = decrypted_request['token'] if validation.is_valid_token(user_token): user_pub_key = decrypted_request['key'] matched_studies = study_matcher.match_request_to_studies( decrypted_request) payload = encryption.encrypt(matched_studies, user_pub_key) return jsonify(payload)