Exemplo n.º 1
0
def update_source_key(gpg: GpgHelper, local_source: Source, remote_source: SDKSource) -> None:
    """
    Updates a source's GPG key.
    """
    if not remote_source.key.get("fingerprint"):
        logger.error("New source data lacks key fingerprint")
        return

    if not remote_source.key.get("public"):
        logger.error("New source data lacks public key")
        return

    if (
        local_source.fingerprint == remote_source.key['fingerprint'] and
        local_source.public_key == remote_source.key['public']
    ):
        logger.debug("Source key data is unchanged")
        return

    try:
        # import_key updates the source's key and fingerprint, and commits
        gpg.import_key(
            remote_source.uuid,
            remote_source.key['public'],
            remote_source.key['fingerprint']
        )
    except CryptoError:
        logger.error('Failed to update key information for source %s', remote_source.uuid)
Exemplo n.º 2
0
def test_import_key(homedir, config, source):
    '''
    Check the happy path that we can import a single PGP key.
    Using the `config` fixture to ensure the config is written to disk.
    '''
    helper = GpgHelper(homedir, is_qubes=False)
    helper.import_key(source['uuid'], source['public_key'])
Exemplo n.º 3
0
def test_import_nonexistent_key(homedir, config, session_maker):
    """
    Check failure handling when a source has no key.
    """
    source = factory.Source(public_key=None)
    helper = GpgHelper(homedir, session_maker, is_qubes=False)
    with pytest.raises(CryptoError):
        helper.import_key(source)
Exemplo n.º 4
0
def test_import_key(homedir, config, session_maker):
    """
    Check the happy path that we can import a single PGP key.
    Using the `config` fixture to ensure the config is written to disk.
    """
    source = factory.Source()
    helper = GpgHelper(homedir, session_maker, is_qubes=False)
    helper.import_key(source)
Exemplo n.º 5
0
    def __init__(self,
                 hostname: str,
                 gui,
                 session_maker: sessionmaker,
                 home: str,
                 proxy: bool = True,
                 qubes: bool = True) -> None:
        """
        The hostname, gui and session objects are used to coordinate with the
        various other layers of the application: the location of the SecureDrop
        proxy, the user interface and SqlAlchemy local storage respectively.
        """
        check_dir_permissions(home)
        super().__init__()

        # Controller is unauthenticated by default
        self.__is_authenticated = False

        # used for finding DB in sync thread
        self.home = home

        # boolean flag for whether or not the client is operating behind a proxy
        self.proxy = proxy

        # boolean flag for whether the client is running within Qubes
        # (regardless of proxy state, to support local dev in an AppVM)
        self.qubes = qubes

        # Location of the SecureDrop server.
        self.hostname = hostname

        # Reference to the UI window.
        self.gui = gui

        # Reference to the API for secure drop proxy.
        self.api = None  # type: sdclientapi.API

        # Reference to the SqlAlchemy `sessionmaker` and `session`
        self.session_maker = session_maker
        self.session = session_maker()

        # Queue that handles running API job
        self.api_job_queue = ApiJobQueue(self.api, self.session_maker)
        self.api_job_queue.paused.connect(self.on_queue_paused)

        # Contains active threads calling the API.
        self.api_threads = {}  # type: Dict[str, Dict]

        self.gpg = GpgHelper(home, self.session_maker, proxy)

        self.export = Export()

        self.sync_flag = os.path.join(home, 'sync_flag')

        # File data.
        self.data_dir = os.path.join(self.home, 'data')
    def __init__(self, api, home, is_qubes):
        super().__init__()

        engine = make_engine(home)
        Session = sessionmaker(bind=engine)
        self.session = Session()  # Reference to the SqlAlchemy session.
        self.api = api
        self.home = home
        self.is_qubes = is_qubes
        self.gpg = GpgHelper(home, is_qubes)
Exemplo n.º 7
0
    def __init__(self, api: API, home: str, is_qubes: bool) -> None:
        super().__init__()

        engine = make_engine(home)
        current_session = sessionmaker(bind=engine)
        self.session = current_session()  # type: Session
        self.api = api
        self.home = home
        self.is_qubes = is_qubes
        self.gpg = GpgHelper(home, is_qubes)
def test_import_key_multiple_fingerprints(homedir, source, config, mocker):
    '''
    Check that an error is raised if multiple fingerpints are found on key import.
    Using the `config` fixture to ensure the config is written to disk.
    '''
    helper = GpgHelper(homedir, is_qubes=False)
    mock_import = mocker.patch.object(helper, '_import', returnvalue={'a', 'b'})

    with pytest.raises(RuntimeError, match='Expected exactly one fingerprint\\.'):
        helper.import_key(source['uuid'], source['public_key'])

    # ensure the mock was used
    assert mock_import.called
Exemplo n.º 9
0
def test_import_key_failure_in_encrypt_to_source(homedir, config, mocker, session_maker, session):
    """
    Confirm handling of key import failure.
    """
    helper = GpgHelper(homedir, session_maker, is_qubes=False)

    source = factory.Source(public_key="iwillbreakyou")
    session.add(source)

    plaintext = "bueller?"

    with pytest.raises(CryptoError, match=r"Could not import key before encrypting reply:"):
        helper.encrypt_to_source(source.uuid, plaintext)
Exemplo n.º 10
0
    def __init__(self,
                 hostname,
                 gui,
                 session,
                 home: str,
                 proxy: bool = True) -> None:
        """
        The hostname, gui and session objects are used to coordinate with the
        various other layers of the application: the location of the SecureDrop
        proxy, the user interface and SqlAlchemy local storage respectively.
        """
        check_dir_permissions(home)
        super().__init__()

        # Controller is unauthenticated by default
        self.__is_authenticated = False

        # used for finding DB in sync thread
        self.home = home

        # boolean flag for whether or not the client is operating behind a proxy
        self.proxy = proxy

        # Location of the SecureDrop server.
        self.hostname = hostname

        # Reference to the UI window.
        self.gui = gui

        # Reference to the API for secure drop proxy.
        self.api = None  # type: sdclientapi.API
        # Contains active threads calling the API.
        self.api_threads = {}  # type: Dict[str, Dict]

        # Reference to the SqlAlchemy session.
        self.session = session

        # thread responsible for fetching messages
        self.message_thread = None
        self.message_sync = MessageSync(self.api, self.home, self.proxy)

        # thread responsible for fetching replies
        self.reply_thread = None
        self.reply_sync = ReplySync(self.api, self.home, self.proxy)

        self.sync_flag = os.path.join(home, 'sync_flag')

        # File data.
        self.data_dir = os.path.join(self.home, 'data')

        self.gpg = GpgHelper(home, proxy)
Exemplo n.º 11
0
def test_import_key_gpg_call_fail(homedir, config, mocker, session_maker):
    '''
    Check that a `CryptoError` is raised if calling `gpg` fails.
    Using the `config` fixture to ensure the config is written to disk.
    '''
    helper = GpgHelper(homedir, session_maker, is_qubes=False)
    err = subprocess.CalledProcessError(cmd=['foo'], returncode=1)
    mock_call = mocker.patch('securedrop_client.crypto.subprocess.check_call',
                             side_effect=err)

    with pytest.raises(CryptoError, match='Could not import key\\.'):
        helper._import(PUB_KEY)

    # ensure the mock was used
    assert mock_call.called
Exemplo n.º 12
0
def test_encrypt_fail_if_journo_fingerprint_missing(homedir, source, config, mocker, session_maker):
    """
    Check that a `CryptoError` is raised before making a call to `gpg` if source fingerprint is
    missing.
    """
    helper = GpgHelper(homedir, session_maker, is_qubes=False)
    helper.journalist_key_fingerprint = None
    check_call_fn = mocker.patch("securedrop_client.crypto.subprocess.check_call")

    with pytest.raises(
        CryptoError, match=r"Could not encrypt reply due to missing fingerprint for journalist"
    ):
        helper.encrypt_to_source(source["uuid"], "mock")

    check_call_fn.assert_not_called()
Exemplo n.º 13
0
def test_import_key_gpg_call_fail(homedir, config, mocker, session_maker):
    """
    Check that a `CryptoError` is raised if calling `gpg` fails.
    Using the `config` fixture to ensure the config is written to disk.
    """
    helper = GpgHelper(homedir, session_maker, is_qubes=False)
    source = factory.Source()
    err = subprocess.CalledProcessError(cmd=["foo"], returncode=1)
    mock_call = mocker.patch("securedrop_client.crypto.subprocess.check_call", side_effect=err)

    with pytest.raises(CryptoError, match=f"Could not import key."):
        helper.import_key(source)

    # ensure the mock was used
    assert mock_call.called
Exemplo n.º 14
0
def test_send_reply_failure_timeout_error(
    homedir, mocker, session, session_maker, reply_status_codes, exception
):
    """
    Check that if the SendReplyJob api call fails because of a RequestTimeoutError or
    ServerConnectionError that a SendReplyJobTimeoutError is raised.
    """
    source = factory.Source()
    session.add(source)
    draft_reply = factory.DraftReply(uuid="mock_reply_uuid")
    session.add(draft_reply)
    session.commit()
    api_client = mocker.MagicMock()
    mocker.patch.object(api_client, "reply_source", side_effect=exception)
    gpg = GpgHelper(homedir, session_maker, is_qubes=False)
    encrypt_fn = mocker.patch.object(gpg, "encrypt_to_source")
    job = SendReplyJob(source.uuid, "mock_reply_uuid", "mock_message", gpg)

    with pytest.raises(SendReplyJobTimeoutError):
        job.call_api(api_client, session)

    encrypt_fn.assert_called_once_with(source.uuid, "mock_message")
    replies = session.query(db.Reply).filter_by(uuid="mock_reply_uuid").all()
    assert len(replies) == 0

    # Ensure that the draft reply is still in the db
    drafts = session.query(db.DraftReply).filter_by(uuid="mock_reply_uuid").all()
    assert len(drafts) == 1
Exemplo n.º 15
0
def test_MetadataSyncJob_success_with_missing_key(mocker, homedir, session,
                                                  session_maker):
    """
    Check that we can gracefully handle missing source keys.
    """
    gpg = GpgHelper(homedir, session_maker, is_qubes=False)
    job = MetadataSyncJob(homedir, gpg)

    mock_source = factory.RemoteSource(key={
        'type': 'PGP',
        'public': '',
        'fingerprint': '',
    })

    mock_key_import = mocker.patch.object(job.gpg, 'import_key')
    mock_get_remote_data = mocker.patch(
        'securedrop_client.api_jobs.sync.get_remote_data',
        return_value=([mock_source], [], []))

    api_client = mocker.MagicMock()
    api_client.default_request_timeout = mocker.MagicMock()

    job.call_api(api_client, session)

    assert mock_key_import.call_count == 0
    assert mock_get_remote_data.call_count == 1
Exemplo n.º 16
0
def test_MetadataSyncJob_success_with_key_import_fail(mocker, homedir, session,
                                                      session_maker):
    """
    Check that we can gracefully handle a key import failure.
    """
    gpg = GpgHelper(homedir, session_maker, is_qubes=False)
    job = MetadataSyncJob(homedir, gpg)

    mock_source = factory.RemoteSource(key={
        'type': 'PGP',
        'public': PUB_KEY,
        'fingerprint': '123456ABC',
    })

    mock_key_import = mocker.patch.object(job.gpg,
                                          'import_key',
                                          side_effect=CryptoError)
    mock_get_remote_data = mocker.patch(
        'securedrop_client.api_jobs.sync.get_remote_data',
        return_value=([mock_source], [], []))

    api_client = mocker.MagicMock()
    api_client.default_request_timeout = mocker.MagicMock()

    job.call_api(api_client, session)

    assert mock_key_import.call_args[0][0] == mock_source.uuid
    assert mock_key_import.call_args[0][1] == mock_source.key['public']
    assert mock_key_import.call_args[0][2] == mock_source.key['fingerprint']
    assert mock_get_remote_data.call_count == 1
Exemplo n.º 17
0
def test_send_reply_failure_when_repr_is_none(homedir, mocker, session,
                                              session_maker):
    '''
    Check that the SendReplyJob api call results in a SendReplyJobError and nothing else, e.g.
    no TypeError, when an api call results in an exception that returns None for __repr__
    (regression test).
    '''
    class MockException(Exception):
        def __repr__(self):
            return None

    source = factory.Source(uuid='mock_reply_uuid')
    session.add(source)
    session.commit()
    api_client = mocker.MagicMock()
    mocker.patch.object(api_client,
                        'reply_source',
                        side_effect=MockException('mock'))
    gpg = GpgHelper(homedir, session_maker, is_qubes=False)
    encrypt_fn = mocker.patch.object(gpg, 'encrypt_to_source')
    job = SendReplyJob(source.uuid, 'mock_reply_uuid', 'mock_message', gpg)

    with pytest.raises(
            SendReplyJobError,
            match=
            r'Failed to send reply for source mock_reply_uuid due to Exception: mock'
    ):
        job.call_api(api_client, session)

    encrypt_fn.assert_called_once_with(source.uuid, 'mock_message')
    replies = session.query(db.Reply).filter_by(uuid='mock_reply_uuid').all()
    assert len(replies) == 0
Exemplo n.º 18
0
def test_ReplyDownloadJob_no_download_or_decrypt(mocker, homedir, session,
                                                 session_maker):
    """
    Test that an already-downloaded reply successfully decrypts.
    """
    reply_is_decrypted_false = factory.Reply(source=factory.Source(),
                                             is_downloaded=True,
                                             is_decrypted=False,
                                             content=None)
    reply_is_decrypted_none = factory.Reply(source=factory.Source(),
                                            is_downloaded=True,
                                            is_decrypted=None,
                                            content=None)
    session.add(reply_is_decrypted_false)
    session.add(reply_is_decrypted_none)
    session.commit()
    gpg = GpgHelper(homedir, session_maker, is_qubes=False)
    job_1 = ReplyDownloadJob(reply_is_decrypted_false.uuid, homedir, gpg)
    job_2 = ReplyDownloadJob(reply_is_decrypted_none.uuid, homedir, gpg)
    mocker.patch.object(job_1.gpg, 'decrypt_submission_or_reply')
    mocker.patch.object(job_2.gpg, 'decrypt_submission_or_reply')
    api_client = mocker.MagicMock()
    api_client.default_request_timeout = mocker.MagicMock()
    path = os.path.join(homedir, 'data')
    api_client.download_submission = mocker.MagicMock(return_value=('', path))

    job_1.call_api(api_client, session)
    job_2.call_api(api_client, session)

    assert reply_is_decrypted_false.content is not None
    assert reply_is_decrypted_false.is_downloaded is True
    assert reply_is_decrypted_false.is_decrypted is True
    assert reply_is_decrypted_none.content is not None
    assert reply_is_decrypted_none.is_downloaded is True
    assert reply_is_decrypted_none.is_decrypted is True
Exemplo n.º 19
0
def test_send_reply_failure_unknown_error(
    homedir, mocker, session, session_maker, reply_status_codes
):
    """
    Check that if the SendReplyJob api call fails when sending a message that SendReplyJobError
    is raised and the reply is not added to the local database.
    """
    source = factory.Source()
    session.add(source)
    draft_reply = factory.DraftReply(uuid="mock_reply_uuid")
    session.add(draft_reply)
    session.commit()
    api_client = mocker.MagicMock()
    mocker.patch.object(api_client, "reply_source", side_effect=Exception)
    gpg = GpgHelper(homedir, session_maker, is_qubes=False)
    encrypt_fn = mocker.patch.object(gpg, "encrypt_to_source")
    job = SendReplyJob(source.uuid, "mock_reply_uuid", "mock_message", gpg)

    with pytest.raises(Exception):
        job.call_api(api_client, session)

    encrypt_fn.assert_called_once_with(source.uuid, "mock_message")
    replies = session.query(db.Reply).filter_by(uuid="mock_reply_uuid").all()
    assert len(replies) == 0

    # Ensure that the draft reply is still in the db
    drafts = session.query(db.DraftReply).filter_by(uuid="mock_reply_uuid").all()
    assert len(drafts) == 1
Exemplo n.º 20
0
def test_MessageDownloadJob_with_crypto_error(mocker, homedir, session,
                                              session_maker):
    """
    Test when a message successfully downloads, but does not successfully decrypt. Use the `homedir`
    fixture to get a GPG keyring.
    """
    message = factory.Message(source=factory.Source(),
                              is_downloaded=False,
                              is_decrypted=None,
                              content=None)
    session.add(message)
    session.commit()
    gpg = GpgHelper(homedir, session_maker, is_qubes=False)
    job = MessageDownloadJob(message.uuid, homedir, gpg)
    mocker.patch.object(job.gpg,
                        'decrypt_submission_or_reply',
                        side_effect=CryptoError)
    api_client = mocker.MagicMock()
    api_client.default_request_timeout = mocker.MagicMock()
    path = os.path.join(homedir, 'data')
    api_client.download_submission = mocker.MagicMock(return_value=('', path))

    with pytest.raises(DownloadDecryptionException):
        job.call_api(api_client, session)

    assert message.content is None
    assert message.is_downloaded is True
    assert message.is_decrypted is False
Exemplo n.º 21
0
def test_FileDownloadJob_bad_sha256_etag(mocker, homedir, session,
                                         session_maker):
    source = factory.Source()
    file_ = factory.File(source=source, is_downloaded=None, is_decrypted=None)
    session.add(source)
    session.add(file_)
    session.commit()

    gpg = GpgHelper(homedir, session_maker, is_qubes=False)

    def fake_download(sdk_obj: SdkSubmission, timeout: int) -> Tuple[str, str]:
        '''
        :return: (etag, path_to_dl)
        '''
        full_path = os.path.join(homedir, 'data', 'mock')
        with open(full_path, 'wb') as f:
            f.write(b'')

        return ('sha256:not-a-sha-sum', full_path)

    api_client = mocker.MagicMock()
    api_client.default_request_timeout = mocker.MagicMock()
    api_client.download_submission = fake_download

    job = FileDownloadJob(
        file_.uuid,
        os.path.join(homedir, 'data'),
        gpg,
    )

    with pytest.raises(DownloadChecksumMismatchException):
        job.call_api(api_client, session)
Exemplo n.º 22
0
def test_MessageDownloadJob_happiest_path(mocker, homedir, session,
                                          session_maker):
    """
    Test when a message successfully downloads and decrypts. Use the `homedir` fixture to get a GPG
    keyring.
    """
    message = factory.Message(source=factory.Source(),
                              is_downloaded=False,
                              is_decrypted=None,
                              content=None)
    session.add(message)
    session.commit()
    gpg = GpgHelper(homedir, session_maker, is_qubes=False)
    job = MessageDownloadJob(message.uuid, homedir, gpg)
    mocker.patch.object(job.gpg, 'decrypt_submission_or_reply')
    api_client = mocker.MagicMock()
    api_client.default_request_timeout = mocker.MagicMock()
    data_dir = os.path.join(homedir, 'data')
    api_client.download_submission = mocker.MagicMock(return_value=('',
                                                                    data_dir))

    job.call_api(api_client, session)

    assert message.content is not None
    assert message.is_downloaded is True
    assert message.is_decrypted is True
Exemplo n.º 23
0
def test_MessageDownloadJob_with_base_error(mocker, homedir, session,
                                            session_maker):
    """
    Test when a message does not successfully download.
    """
    message = factory.Message(source=factory.Source(),
                              is_downloaded=False,
                              is_decrypted=None,
                              content=None)
    session.add(message)
    session.commit()
    gpg = GpgHelper(homedir, session_maker, is_qubes=False)
    job = MessageDownloadJob(message.uuid, homedir, gpg)
    api_client = mocker.MagicMock()
    api_client.default_request_timeout = mocker.MagicMock()
    mocker.patch.object(api_client,
                        'download_submission',
                        side_effect=BaseError)
    decrypt_fn = mocker.patch.object(job.gpg, 'decrypt_submission_or_reply')

    with pytest.raises(BaseError):
        job.call_api(api_client, session)

    assert message.content is None
    assert message.is_downloaded is False
    assert message.is_decrypted is None
    decrypt_fn.assert_not_called()
Exemplo n.º 24
0
def test_FileDownloadJob_happy_path_unknown_etag(mocker, homedir, session,
                                                 session_maker):
    source = factory.Source()
    file_ = factory.File(source=source, is_downloaded=None, is_decrypted=None)
    session.add(source)
    session.add(file_)
    session.commit()

    gpg = GpgHelper(homedir, session_maker, is_qubes=False)

    def fake_download(sdk_obj: SdkSubmission, timeout: int) -> Tuple[str, str]:
        """
        :return: (etag, path_to_dl)
        """
        full_path = os.path.join(homedir, "data", "mock")
        with open(full_path, "wb") as f:
            f.write(b"")
        return ("UNKNOWN:abc123", full_path)

    api_client = mocker.MagicMock()
    api_client.default_request_timeout = mocker.MagicMock()
    api_client.download_submission = fake_download

    job = FileDownloadJob(file_.uuid, os.path.join(homedir, "data"), gpg)

    mock_decrypt = patch_decrypt(mocker, homedir, gpg, file_.filename)
    mock_logger = mocker.patch("securedrop_client.api_jobs.downloads.logger")

    job.call_api(api_client, session)

    log_msg = mock_logger.debug.call_args_list[0][0][0]
    assert log_msg.startswith("Unknown hash algorithm")

    # ensure mocks aren't stale
    assert mock_decrypt.called
Exemplo n.º 25
0
def test_MessageDownloadJob_no_download_or_decrypt(mocker, homedir, session,
                                                   session_maker):
    """
    Test that an already-downloaded message successfully decrypts. Use the `homedir` fixture to get
    a GPG keyring.
    """
    message_is_decrypted_false = factory.Message(source=factory.Source(),
                                                 is_downloaded=True,
                                                 is_decrypted=False,
                                                 content=None)
    message_is_decrypted_none = factory.Message(source=factory.Source(),
                                                is_downloaded=True,
                                                is_decrypted=None,
                                                content=None)
    session.add(message_is_decrypted_false)
    session.add(message_is_decrypted_none)
    session.commit()
    gpg = GpgHelper(homedir, session_maker, is_qubes=False)
    job_1 = MessageDownloadJob(message_is_decrypted_false.uuid, homedir, gpg)
    job_2 = MessageDownloadJob(message_is_decrypted_none.uuid, homedir, gpg)
    mocker.patch.object(job_1.gpg, 'decrypt_submission_or_reply')
    mocker.patch.object(job_2.gpg, 'decrypt_submission_or_reply')
    api_client = mocker.MagicMock()
    path = os.path.join(homedir, 'data')
    api_client.download_submission = mocker.MagicMock(return_value=('', path))

    job_1.call_api(api_client, session)
    job_2.call_api(api_client, session)

    assert message_is_decrypted_false.content is not None
    assert message_is_decrypted_false.is_downloaded is True
    assert message_is_decrypted_false.is_decrypted is True
    assert message_is_decrypted_none.content is not None
    assert message_is_decrypted_none.is_downloaded is True
    assert message_is_decrypted_none.is_decrypted is True
class APISyncObject(QObject):
    def __init__(self, api, home, is_qubes):
        super().__init__()

        engine = make_engine(home)
        Session = sessionmaker(bind=engine)
        self.session = Session()  # Reference to the SqlAlchemy session.
        self.api = api
        self.home = home
        self.is_qubes = is_qubes
        self.gpg = GpgHelper(home, is_qubes)

    def fetch_the_thing(self, item, msg, download_fn, update_fn):
        shasum, filepath = download_fn(item)
        self.gpg.decrypt_submission_or_reply(filepath, msg.filename, False)
        update_fn(msg.uuid, self.session)
        logger.info("Stored message or reply at {}".format(msg.filename))
Exemplo n.º 27
0
def test_message_logic(homedir, config, mocker):
    """
    Ensure that messages are handled.
    Using the `config` fixture to ensure the config is written to disk.
    """
    gpg = GpgHelper(homedir, is_qubes=False)

    test_msg = 'tests/files/test-msg.gpg'
    expected_output_filename = 'test-msg'

    mock_gpg = mocker.patch('subprocess.call', return_value=0)
    mocker.patch('os.unlink')

    dest = gpg.decrypt_submission_or_reply(test_msg, expected_output_filename, is_doc=False)

    assert mock_gpg.call_count == 1
    assert dest == os.path.join(homedir, 'data', expected_output_filename)
Exemplo n.º 28
0
def test_gunzip_logic(homedir, config, mocker, session_maker):
    """
    Ensure that gzipped documents/files are handled
    Using the `config` fixture to ensure the config is written to disk.
    """
    gpg = GpgHelper(homedir, session_maker, is_qubes=False)

    gpg._import(PUB_KEY)
    gpg._import(JOURNO_KEY)

    test_gzip = "tests/files/test-doc.gz.gpg"
    expected_output_filepath = "tests/files/test-doc.txt"

    # mock_gpg = mocker.patch('subprocess.call', return_value=0)
    mock_unlink = mocker.patch("os.unlink")
    original_filename = gpg.decrypt_submission_or_reply(
        test_gzip, expected_output_filepath, is_doc=True
    )

    assert original_filename == "test-doc.txt"

    # We should remove two files in the success scenario: err, filepath
    assert mock_unlink.call_count == 2
    mock_unlink.stop()
    os.remove(expected_output_filepath)
Exemplo n.º 29
0
def test_gzip_header_without_filename(homedir, config, mocker, session_maker):
    """
    Test processing of a gzipped file without a filename in the header.
    """
    gpg = GpgHelper(homedir, session_maker, is_qubes=False)

    gpg._import(PUB_KEY)
    gpg._import(JOURNO_KEY)

    mocker.patch("os.unlink")
    mocker.patch("gzip.open")
    mocker.patch("shutil.copy")
    mocker.patch("shutil.copyfileobj")

    # pretend the gzipped file header lacked the original filename
    mock_read_gzip_header_filename = mocker.patch(
        "securedrop_client.crypto.read_gzip_header_filename"
    )
    mock_read_gzip_header_filename.return_value = ""

    test_gzip = "tests/files/test-doc.gz.gpg"
    output_filename = "test-doc"
    expected_output_filename = "tests/files/test-doc"

    original_filename = gpg.decrypt_submission_or_reply(test_gzip, output_filename, is_doc=True)
    assert original_filename == output_filename
    os.remove(expected_output_filename)
Exemplo n.º 30
0
def test_encrypt(homedir, source, config, mocker, session_maker):
    '''
    Check that calling `encrypt` encrypts the message.
    Using the `config` fixture to ensure the config is written to disk.
    '''
    helper = GpgHelper(homedir, session_maker, is_qubes=False)

    # first we have to ensure the pubkeys are available
    helper._import(PUB_KEY)
    helper._import(JOURNO_KEY)

    plaintext = 'bueller?'
    cyphertext = helper.encrypt_to_source(source['uuid'], plaintext)

    # check that we go *any* output just for sanity
    assert cyphertext

    cyphertext_file = os.path.join(homedir, 'cyphertext.out')
    decrypted_file = os.path.join(homedir, 'decrypted.out')
    gpg_home = os.path.join(homedir, 'gpg')

    with open(cyphertext_file, 'w') as f:
        f.write(cyphertext)

    subprocess.check_call([
        'gpg', '--homedir', gpg_home, '--output', decrypted_file, '--decrypt',
        cyphertext_file
    ])

    with open(decrypted_file) as f:
        decrypted = f.read()

    assert decrypted == plaintext