Skip to content

Commit

Permalink
Merge pull request #184 from freedomofpress/gpg
Browse files Browse the repository at this point in the history
Gpg helper / wrapper
  • Loading branch information
redshiftzero authored Nov 21, 2018
2 parents 043e985 + b5cd5a4 commit 1e96664
Show file tree
Hide file tree
Showing 10 changed files with 320 additions and 95 deletions.
9 changes: 7 additions & 2 deletions run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,16 @@ SDC_HOME=${SDC_HOME:-$(mktemp -d)}

export SDC_HOME

chmod 0700 $SDC_HOME
GPG_HOME="$SDC_HOME/gpg"
mkdir "$GPG_HOME"
chmod 0700 "$SDC_HOME" "$GPG_HOME"

echo "Running app with home directory: $SDC_HOME"
echo ""

gpg --homedir "$GPG_HOME" --allow-secret-key-import --import tests/files/securedrop.gpg.asc

# create the database for local testing
./createdb.py $SDC_HOME
./createdb.py "$SDC_HOME"

exec python -m securedrop_client --sdc-home "$SDC_HOME" --no-proxy $@
119 changes: 65 additions & 54 deletions securedrop_client/crypto.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,61 +22,72 @@
import subprocess
import tempfile

from securedrop_client.utils import safe_mkdir

logger = logging.getLogger(__name__)


def decrypt_submission_or_reply(filepath, target_filename, home_dir,
is_qubes=True, is_doc=False):
out = tempfile.NamedTemporaryFile(suffix=".message")
err = tempfile.NamedTemporaryFile(suffix=".message-error", delete=False)
if is_qubes:
gpg_binary = "qubes-gpg-client"
else:
gpg_binary = "gpg"
cmd = [gpg_binary, "--decrypt", filepath]
res = subprocess.call(cmd, stdout=out, stderr=err)

os.unlink(filepath) # original file

if res != 0:
# The out tempfile will be automatically deleted after closing.
out.close()
# The err tempfile was created with delete=False, so needs to
# be explicitly cleaned up. We will do that after we've read the file.
err.close()

with open(err.name) as e:
msg = e.read()
logger.error("GPG error: {}".format(msg))

os.unlink(err.name)
dest = ""
else:
# Cleanup err file
err.close()
os.unlink(err.name)

if is_doc:
# Docs are gzipped, so gunzip the file
with gzip.open(out.name, 'rb') as infile:
unzipped_decrypted_data = infile.read()

# Need to split twice as filename is e.g.
# 1-impractical_thing-doc.gz.gpg
fn_no_ext, _ = os.path.splitext(
os.path.splitext(os.path.basename(filepath))[0])
dest = os.path.join(home_dir, "data", fn_no_ext)

with open(dest, 'wb') as outfile:
outfile.write(unzipped_decrypted_data)
else:
fn_no_ext, _ = os.path.splitext(target_filename)
dest = os.path.join(home_dir, "data", fn_no_ext)
shutil.copy(out.name, dest)

# Now close to automatically delete the out tempfile.
out.close()
logger.info("Downloaded and decrypted: {}".format(dest))

return res, dest
class CryptoError(Exception):

pass


class GpgHelper:

def __init__(self, sdc_home: str, is_qubes: bool) -> None:
'''
:param sdc_home: Home directory for the SecureDrop client
:param is_qubes: Whether the client is running in Qubes or not
'''
safe_mkdir(os.path.join(sdc_home), "gpg")
self.sdc_home = sdc_home
self.is_qubes = is_qubes

def decrypt_submission_or_reply(self, filepath, target_filename, is_doc=False) -> None:
err = tempfile.NamedTemporaryFile(suffix=".message-error", delete=False)
with tempfile.NamedTemporaryFile(suffix=".message") as out:
if self.is_qubes: # pragma: no cover
cmd = ["qubes-gpg-client"]
else:
cmd = ["gpg", "--homedir", os.path.join(self.sdc_home, "gpg")]

cmd.extend(["--decrypt", filepath])
res = subprocess.call(cmd, stdout=out, stderr=err)

os.unlink(filepath) # original file

if res != 0:
# The err tempfile was created with delete=False, so needs to
# be explicitly cleaned up. We will do that after we've read the file.
err.close()

with open(err.name) as e:
msg = "GPG Error: {}".format(e.read())

logger.error(msg)
os.unlink(err.name)

raise CryptoError(msg)
else:
# Cleanup err file
err.close()
os.unlink(err.name)

if is_doc:
# Need to split twice as filename is e.g.
# 1-impractical_thing-doc.gz.gpg
fn_no_ext, _ = os.path.splitext(
os.path.splitext(os.path.basename(filepath))[0])
dest = os.path.join(self.sdc_home, "data", fn_no_ext)

# Docs are gzipped, so gunzip the file
with gzip.open(out.name, 'rb') as infile, \
open(dest, 'wb') as outfile:
shutil.copyfileobj(infile, outfile)
else:
fn_no_ext, _ = os.path.splitext(target_filename)
dest = os.path.join(self.sdc_home, "data", fn_no_ext)
shutil.copy(out.name, dest)
logger.info("Downloaded and decrypted: {}".format(dest))

return dest
14 changes: 7 additions & 7 deletions securedrop_client/logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@
import arrow
import uuid
from sqlalchemy import event
from securedrop_client import crypto
from securedrop_client import storage
from securedrop_client import models
from securedrop_client.utils import check_dir_permissions
from securedrop_client.crypto import GpgHelper, CryptoError
from securedrop_client.data import Data
from securedrop_client.message_sync import MessageSync, ReplySync
from PyQt5.QtCore import QObject, QThread, pyqtSignal, QTimer, QProcess
Expand Down Expand Up @@ -114,6 +114,7 @@ def __init__(self, hostname, gui, session,
self.data_dir = os.path.join(self.home, 'data') # File data.
self.timer = None # call timeout timer
self.proxy = proxy
self.gpg = GpgHelper(home, proxy)

def setup(self):
"""
Expand Down Expand Up @@ -551,12 +552,11 @@ def on_file_downloaded(self, result, current_object):
filepath_in_datadir = os.path.join(self.data_dir, server_filename)
shutil.move(filename, filepath_in_datadir)

# Attempt to decrypt the file.
res, filepath = crypto.decrypt_submission_or_reply(
filepath_in_datadir, server_filename, self.home,
self.proxy, is_doc=True)

if res != 0: # Then the file did not decrypt properly.
try:
# Attempt to decrypt the file.
self.gpg.decrypt_submission_or_reply(
filepath_in_datadir, server_filename, is_doc=True)
except CryptoError:
self.set_status("Failed to download and decrypt file, "
"please try again.")
# TODO: We should save the downloaded content, and just
Expand Down
16 changes: 5 additions & 11 deletions securedrop_client/message_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

from PyQt5.QtCore import QObject
from securedrop_client import storage
from securedrop_client import crypto
from securedrop_client.crypto import GpgHelper
from securedrop_client.models import make_engine

from sqlalchemy.orm import sessionmaker
Expand All @@ -44,19 +44,13 @@ def __init__(self, api, home, is_qubes):
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)

res, dest = crypto.decrypt_submission_or_reply(filepath,
msg.filename,
self.home,
self.is_qubes, False)

if res == 0:
update_fn(msg.uuid, self.session)
logger.info("Stored message or reply at {}".format(
msg.filename))
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))


class MessageSync(APISyncObject):
Expand Down
106 changes: 106 additions & 0 deletions tests/files/securedrop.gpg.asc
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
-----BEGIN PGP PRIVATE KEY BLOCK-----
Version: GnuPG v2.0.19 (GNU/Linux)

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
d9X9on517X6qMDw=
=E6hg
-----END PGP PRIVATE KEY BLOCK-----
52 changes: 52 additions & 0 deletions tests/files/securedrop.gpg.pub.asc
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v2.0.19 (GNU/Linux)

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
Vu4MPiQNn1h4dk1RonfV/aJ+de1+qjA8
=XVz8
-----END PGP PUBLIC KEY BLOCK-----
1 change: 1 addition & 0 deletions tests/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ def test_signal_interception():
mock.patch('securedrop_client.models.make_engine'), \
mock.patch('securedrop_client.app.init'), \
mock.patch('securedrop_client.logic.Client.setup'), \
mock.patch('securedrop_client.logic.GpgHelper'), \
mock.patch('securedrop_client.app.configure_logging'), \
mock.patch('securedrop_client.app.configure_signal_handlers') \
as mock_signal_handlers:
Expand Down
Loading

0 comments on commit 1e96664

Please sign in to comment.