Exemple #1
0
def reply(journalist, source, num_replies):
    """Generates and submits *num_replies* replies to *source*
    from *journalist*. Returns reply objects as a list.

    :param db.Journalist journalist: The journalist to write the
                                     reply from.

    :param db.Source source: The source to send the reply to.

    :param int num_replies: Number of random-data replies to make.

    :returns: A list of the :class:`db.Reply`s submitted.
    """
    assert num_replies >= 1
    replies = []
    for _ in range(num_replies):
        source.interaction_count += 1
        fname = "{}-{}-reply.gpg".format(source.interaction_count,
                                         source.journalist_filename)
        crypto_util.encrypt(
            str(os.urandom(1)),
            [crypto_util.getkey(source.filesystem_id), config.JOURNALIST_KEY],
            store.path(source.filesystem_id, fname))
        reply = db.Reply(journalist, source, fname)
        replies.append(reply)
        db.db_session.add(reply)

    db.db_session.commit()
    return replies
def reply(journalist, source, num_replies):
    """Generates and submits *num_replies* replies to *source*
    from *journalist*. Returns reply objects as a list.

    :param db.Journalist journalist: The journalist to write the
                                     reply from.

    :param db.Source source: The source to send the reply to.

    :param int num_replies: Number of random-data replies to make.

    :returns: A list of the :class:`db.Reply`s submitted.
    """
    assert num_replies >= 1
    replies = []
    for _ in range(num_replies):
        source.interaction_count += 1
        fname = "{}-{}-reply.gpg".format(source.interaction_count,
                                         source.journalist_filename)
        crypto_util.encrypt(str(os.urandom(1)),
                            [
                                crypto_util.getkey(source.filesystem_id),
                                config.JOURNALIST_KEY
                            ],
                            store.path(source.filesystem_id, fname))
        reply = db.Reply(journalist, source, fname)
        replies.append(reply)
        db.db_session.add(reply)

    db.db_session.commit()
    return replies
Exemple #3
0
def save_file_submission(filesystem_id, count, journalist_filename, filename,
                         stream):
    sanitized_filename = secure_filename(filename)

    # We store file submissions in a .gz file for two reasons:
    #
    # 1. Downloading large files over Tor is very slow. If we can
    # compress the file, we can speed up future downloads.
    #
    # 2. We want to record the original filename because it might be
    # useful, either for context about the content of the submission
    # or for figuring out which application should be used to open
    # it. However, we'd like to encrypt that info and have the
    # decrypted file automatically have the name of the original
    # file. Given various usability constraints in GPG and Tails, this
    # is the most user-friendly way we have found to do this.

    encrypted_file_name = "{0}-{1}-doc.gz.gpg".format(
        count,
        journalist_filename)
    encrypted_file_path = path(filesystem_id, encrypted_file_name)
    with SecureTemporaryFile("/tmp") as stf:
        with gzip.GzipFile(filename=sanitized_filename,
                           mode='wb', fileobj=stf) as gzf:
            # Buffer the stream into the gzip file to avoid excessive
            # memory consumption
            while True:
                buf = stream.read(1024 * 8)
                if not buf:
                    break
                gzf.write(buf)

        crypto_util.encrypt(stf, config.JOURNALIST_KEY, encrypted_file_path)

    return encrypted_file_name
Exemple #4
0
def save_file_submission(filesystem_id, count, journalist_filename, filename,
                         stream):
    sanitized_filename = secure_filename(filename)

    # We store file submissions in a .gz file for two reasons:
    #
    # 1. Downloading large files over Tor is very slow. If we can
    # compress the file, we can speed up future downloads.
    #
    # 2. We want to record the original filename because it might be
    # useful, either for context about the content of the submission
    # or for figuring out which application should be used to open
    # it. However, we'd like to encrypt that info and have the
    # decrypted file automatically have the name of the original
    # file. Given various usability constraints in GPG and Tails, this
    # is the most user-friendly way we have found to do this.

    encrypted_file_name = "{0}-{1}-doc.gz.gpg".format(
        count,
        journalist_filename)
    encrypted_file_path = path(filesystem_id, encrypted_file_name)
    with SecureTemporaryFile("/tmp") as stf:
        with gzip.GzipFile(filename=sanitized_filename,
                           mode='wb', fileobj=stf) as gzf:
            # Buffer the stream into the gzip file to avoid excessive
            # memory consumption
            while True:
                buf = stream.read(1024 * 8)
                if not buf:
                    break
                gzf.write(buf)

        crypto_util.encrypt(stf, config.JOURNALIST_KEY, encrypted_file_path)

    return encrypted_file_name
 def test_encrypt_failure(self):
     source, _ = utils.db_helper.init_source()
     with self.assertRaisesRegexp(crypto_util.CryptoException,
                                  'no terminal at all requested'):
         crypto_util.encrypt(
             str(os.urandom(1)),
             [],
             store.path(source.filesystem_id, 'other.gpg'))
Exemple #6
0
def reply():
    sid, msg_candidate = request.form["sid"], request.form["msg"]
    try:
        msg = msg_candidate.decode()
    except (UnicodeDecodeError, UnicodeEncodeError):
        flash("You have entered text that we could not parse. Please try again.", "notification")
        return render_template("col.html", sid=sid, codename=db.display_id(sid, db.sqlalchemy_handle()))
    crypto_util.encrypt(crypto_util.getkey(sid), msg, output=store.path(sid, "reply-%s.gpg" % uuid.uuid4()))
    return render_template("reply.html", sid=sid, codename=db.display_id(sid, db.sqlalchemy_handle()))
Exemple #7
0
def save_file_submission(sid, filename, stream):
    sanitized_filename = secure_filename(filename)

    s = StringIO()
    with zipfile.ZipFile(s, 'w') as zf:
        zf.writestr(sanitized_filename, stream.read())
    s.reset()

    file_loc = path(sid, "%s_doc.zip.gpg" % uuid.uuid4())
    crypto_util.encrypt(config.JOURNALIST_KEY, s, file_loc)
Exemple #8
0
def reply():
    g.source.interaction_count += 1
    filename = "{0}-{1}-reply.gpg".format(g.source.interaction_count, g.source.journalist_filename)
    crypto_util.encrypt(
        request.form["msg"], [crypto_util.getkey(g.sid), config.JOURNALIST_KEY], output=store.path(g.sid, filename)
    )
    reply = Reply(g.user, g.source, filename)
    db_session.add(reply)
    db_session.commit()

    flash("Thanks! Your reply has been stored.", "notification")
    return redirect(url_for("col", sid=g.sid))
Exemple #9
0
def reply():
    g.source.interaction_count += 1
    filename = "{0}-{1}-reply.gpg".format(g.source.interaction_count,
                                          g.source.journalist_filename)
    crypto_util.encrypt(request.form['msg'],
                        [crypto_util.getkey(g.sid), config.JOURNALIST_KEY],
                        output=store.path(g.sid, filename))
    reply = Reply(g.user, g.source, filename)
    db_session.add(reply)
    db_session.commit()

    flash("Thanks! Your reply has been stored.", "notification")
    return redirect(url_for('col', sid=g.sid))
Exemple #10
0
def save_file_submission(sid, filename, stream, content_type, strip_metadata):
    sanitized_filename = secure_filename(filename)
    clean_file = sanitize_metadata(stream, content_type, strip_metadata)

    s = StringIO()
    with zipfile.ZipFile(s, 'w') as zf:
        zf.writestr(sanitized_filename, clean_file.read() if clean_file else stream.read())
    s.reset()

    filename = "%s_doc.zip.gpg" % uuid.uuid4()
    file_loc = path(sid, filename)
    crypto_util.encrypt(config.JOURNALIST_KEY, s, file_loc)
    return filename
Exemple #11
0
    def reply():
        """Attempt to send a Reply from a Journalist to a Source. Empty
        messages are rejected, and an informative error message is flashed
        on the client. In the case of unexpected errors involving database
        transactions (potentially caused by racing request threads that
        modify the same the database object) logging is done in such a way
        so as not to write potentially sensitive information to disk, and a
        generic error message is flashed on the client.

        Returns:
           flask.Response: The user is redirected to the same Source
               collection view, regardless if the Reply is created
               successfully.
        """
        form = ReplyForm()
        if not form.validate_on_submit():
            for error in form.message.errors:
                flash(error, "error")
            return redirect(url_for('col.col', filesystem_id=g.filesystem_id))

        g.source.interaction_count += 1
        filename = "{0}-{1}-reply.gpg".format(g.source.interaction_count,
                                              g.source.journalist_filename)
        crypto_util.encrypt(form.message.data,
                            [crypto_util.getkey(g.filesystem_id),
                             config.JOURNALIST_KEY],
                            output=store.path(g.filesystem_id, filename))
        reply = Reply(g.user, g.source, filename)

        try:
            db_session.add(reply)
            db_session.commit()
        except Exception as exc:
            flash(gettext(
                "An unexpected error occurred! Please "
                "inform your administrator."), "error")
            # We take a cautious approach to logging here because we're dealing
            # with responses to sources. It's possible the exception message
            # could contain information we don't want to write to disk.
            current_app.logger.error(
                "Reply from '{}' (ID {}) failed: {}!".format(g.user.username,
                                                             g.user.id,
                                                             exc.__class__))
        else:
            flash(gettext("Thanks. Your reply has been stored."),
                  "notification")
        finally:
            return redirect(url_for('col.col', filesystem_id=g.filesystem_id))
Exemple #12
0
    def reply():
        """Attempt to send a Reply from a Journalist to a Source. Empty
        messages are rejected, and an informative error message is flashed
        on the client. In the case of unexpected errors involving database
        transactions (potentially caused by racing request threads that
        modify the same the database object) logging is done in such a way
        so as not to write potentially sensitive information to disk, and a
        generic error message is flashed on the client.

        Returns:
           flask.Response: The user is redirected to the same Source
               collection view, regardless if the Reply is created
               successfully.
        """
        form = ReplyForm()
        if not form.validate_on_submit():
            for error in form.message.errors:
                flash(error, "error")
            return redirect(url_for('col.col', filesystem_id=g.filesystem_id))

        g.source.interaction_count += 1
        filename = "{0}-{1}-reply.gpg".format(g.source.interaction_count,
                                              g.source.journalist_filename)
        crypto_util.encrypt(
            form.message.data,
            [crypto_util.getkey(g.filesystem_id), config.JOURNALIST_KEY],
            output=store.path(g.filesystem_id, filename))
        reply = Reply(g.user, g.source, filename)

        try:
            db_session.add(reply)
            db_session.commit()
        except Exception as exc:
            flash(
                gettext("An unexpected error occurred! Please "
                        "inform your administrator."), "error")
            # We take a cautious approach to logging here because we're dealing
            # with responses to sources. It's possible the exception message
            # could contain information we don't want to write to disk.
            current_app.logger.error(
                "Reply from '{}' (ID {}) failed: {}!".format(
                    g.user.username, g.user.id, exc.__class__))
        else:
            flash(gettext("Thanks. Your reply has been stored."),
                  "notification")
        finally:
            return redirect(url_for('col.col', filesystem_id=g.filesystem_id))
Exemple #13
0
def reply():
    sid, msg_candidate = request.form['sid'], request.form['msg']
    try:
        msg = msg_candidate.decode()
    except (UnicodeDecodeError, UnicodeEncodeError):
        flash(
            "You have entered text that we could not parse. Please try again.",
            "notification")
        return render_template('col.html',
                               sid=sid,
                               codename=db.display_id(sid,
                                                      db.sqlalchemy_handle()))
    crypto_util.encrypt(crypto_util.getkey(sid),
                        msg,
                        output=store.path(sid, 'reply-%s.gpg' % uuid.uuid4()))
    return render_template('reply.html',
                           sid=sid,
                           codename=db.display_id(sid, db.sqlalchemy_handle()))
    def test_encrypt_success(self):
        source, _ = utils.db_helper.init_source()
        message = str(os.urandom(1))
        ciphertext = crypto_util.encrypt(
            message,
            [crypto_util.getkey(source.filesystem_id), config.JOURNALIST_KEY],
            store.path(source.filesystem_id, 'somefile.gpg'))

        self.assertIsInstance(ciphertext, str)
        self.assertNotEqual(ciphertext, message)
        self.assertGreater(len(ciphertext), 0)
Exemple #15
0
    def test_encrypt_fingerprints_not_a_list_or_tuple(self):
        """If passed a single fingerprint as a string, encrypt should
        correctly place that string in a list, and encryption/
        decryption should work as intended."""
        source, codename = utils.db_helper.init_source()
        message = str(os.urandom(1))
        ciphertext = crypto_util.encrypt(
            message, crypto_util.getkey(source.filesystem_id),
            store.path(source.filesystem_id, 'somefile.gpg'))
        plaintext = crypto_util.decrypt(codename, ciphertext)

        self.assertEqual(message, plaintext)
Exemple #16
0
def submit():
    msg = request.form["msg"]
    fh = request.files["fh"]

    if msg:
        msg_loc = store.path(g.sid, "%s_msg.gpg" % uuid.uuid4())
        crypto_util.encrypt(config.JOURNALIST_KEY, msg, msg_loc)
        flash("Thanks! We received your message.", "notification")
    if fh:
        file_loc = store.path(g.sid, "%s_doc.zip.gpg" % uuid.uuid4())

        s = StringIO()
        zip_file = zipfile.ZipFile(s, "w")
        zip_file.writestr(fh.filename, fh.read())
        zip_file.close()
        s.reset()

        crypto_util.encrypt(config.JOURNALIST_KEY, s, file_loc)
        flash("Thanks! We received your document '%s'." % fh.filename or "[unnamed]", "notification")

    return redirect(url_for("lookup"))
    def test_encrypt_without_output(self):
        """We simply do not specify the option output keyword argument
        to crypto_util.encrypt() here in order to confirm encryption
        works when it defaults to `None`.
        """
        source, codename = utils.db_helper.init_source()
        message = str(os.urandom(1))
        ciphertext = crypto_util.encrypt(
            message,
            [crypto_util.getkey(source.filesystem_id), config.JOURNALIST_KEY])
        plaintext = crypto_util.decrypt(codename, ciphertext)

        self.assertEqual(message, plaintext)
Exemple #18
0
    def test_basic_encrypt_then_decrypt_multiple_recipients(self):
        source, codename = utils.db_helper.init_source()
        message = str(os.urandom(1))
        ciphertext = crypto_util.encrypt(
            message,
            [crypto_util.getkey(source.filesystem_id), config.JOURNALIST_KEY],
            store.path(source.filesystem_id, 'somefile.gpg'))
        plaintext = crypto_util.decrypt(codename, ciphertext)

        self.assertEqual(message, plaintext)

        # Since there's no way to specify which key to use for
        # decryption to python-gnupg, we delete the `source`'s key and
        # ensure we can decrypt with the `config.JOURNALIST_KEY`.
        crypto_util.delete_reply_keypair(source.filesystem_id)
        plaintext_ = crypto_util.gpg.decrypt(ciphertext).data

        self.assertEqual(message, plaintext_)
    def test_encrypt_binary_stream(self):
        """Generally, we pass unicode strings (the type form data is
        returned as) as plaintext to crypto_util.encrypt(). These have
        to be converted to "binary stream" types (such as `file`) before
        we can actually call gnupg.GPG.encrypt() on them. This is done
        in crypto_util.encrypt() with an `if` branch that uses
        `gnupg._util._is_stream(plaintext)` as the predicate, and calls
        `gnupg._util._make_binary_stream(plaintext)` if necessary. This
        test ensures our encrypt function works even if we provide
        inputs such that this `if` branch is skipped (i.e., the object
        passed for `plaintext` is one such that
        `gnupg._util._is_stream(plaintext)` returns `True`).
        """
        source, codename = utils.db_helper.init_source()
        with open(os.path.realpath(__file__)) as fh:
            ciphertext = crypto_util.encrypt(
                fh,
                [crypto_util.getkey(source.filesystem_id),
                 config.JOURNALIST_KEY],
                store.path(source.filesystem_id, 'somefile.gpg'))
        plaintext = crypto_util.decrypt(codename, ciphertext)

        with open(os.path.realpath(__file__)) as fh:
            self.assertEqual(fh.read(), plaintext)
Exemple #20
0
def save_message_submission(filesystem_id, count, journalist_filename,
                            message):
    filename = "{0}-{1}-msg.gpg".format(count, journalist_filename)
    msg_loc = path(filesystem_id, filename)
    crypto_util.encrypt(message, config.JOURNALIST_KEY, msg_loc)
    return filename
Exemple #21
0
def save_message_submission(sid, message):
    filename = "%s_msg.gpg" % uuid.uuid4()
    msg_loc = path(sid, filename)
    crypto_util.encrypt(config.JOURNALIST_KEY, message, msg_loc)
    return filename
Exemple #22
0
def save_message_submission(filesystem_id, count, journalist_filename,
                            message):
    filename = "{0}-{1}-msg.gpg".format(count, journalist_filename)
    msg_loc = path(filesystem_id, filename)
    crypto_util.encrypt(message, config.JOURNALIST_KEY, msg_loc)
    return filename
Exemple #23
0
def reply():
    msg = request.form['msg']
    crypto_util.encrypt(crypto_util.getkey(g.sid), msg, output=
                        store.path(g.sid, 'reply-%s.gpg' % uuid.uuid4()))
    return render_template('reply.html', sid=g.sid,
            codename=g.source.journalist_designation)
Exemple #24
0
def reply():
    sid, msg = request.form['sid'], request.form['msg']
    crypto_util.encrypt(crypto_util.getkey(sid), request.form['msg'], output=
                        store.path(sid, 'reply-%s.gpg' % uuid.uuid4()))
    return render_template('reply.html', sid=sid, codename=db.display_id(sid, db.sqlalchemy_handle()))
Exemple #25
0
def save_message_submission(sid, message):
    msg_loc = path(sid, '%s_msg.gpg' % uuid.uuid4())
    crypto_util.encrypt(config.JOURNALIST_KEY, message, msg_loc)