We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Use upstream version of python-gnupg
See https://github.com/freedomofpress/securedrop/issues/250#issuecomment-86237456 for context. We have worked with the upstream maintainer to fix the issues that were preventing us from using the upstream version with SecureDrop. Now that they are fixed, we should start using the upstream and discontinue our use of the outdated `gnupg-securedrop` [fork](https://pypi.python.org/pypi/gnupg-securedrop/1.2.5-9-g6f9d63a-dirty).


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `securedrop/crypto_util.py`
Content:
```
1 # -*- coding: utf-8 -*-
2 import os
3 import subprocess
4 from base64 import b32encode
5 
6 from Crypto.Random import random
7 import gnupg
8 import scrypt
9 
10 import config
11 import store
12 
13 # to fix gpg error #78 on production
14 os.environ['USERNAME'] = 'www-data'
15 
16 GPG_KEY_TYPE = "RSA"
17 if os.environ.get('SECUREDROP_ENV') == 'test':
18     # Optimize crypto to speed up tests (at the expense of security - DO NOT
19     # use these settings in production)
20     GPG_KEY_LENGTH = 1024
21     SCRYPT_PARAMS = dict(N=2**1, r=1, p=1)
22 else:
23     GPG_KEY_LENGTH = 4096
24     SCRYPT_PARAMS = config.SCRYPT_PARAMS
25 
26 SCRYPT_ID_PEPPER = config.SCRYPT_ID_PEPPER
27 SCRYPT_GPG_PEPPER = config.SCRYPT_GPG_PEPPER
28 
29 DEFAULT_WORDS_IN_RANDOM_ID = 8
30 
31 
32 # Make sure these pass before the app can run
33 # TODO: Add more tests
34 def do_runtime_tests():
35     assert(config.SCRYPT_ID_PEPPER != config.SCRYPT_GPG_PEPPER)
36     # crash if we don't have srm:
37     try:
38         subprocess.check_call(['srm'], stdout=subprocess.PIPE)
39     except subprocess.CalledProcessError:
40         pass
41 
42 do_runtime_tests()
43 
44 # HACK: use_agent=True is used to avoid logging noise.
45 #
46 # --use-agent is a dummy option in gpg2, which is the only version of
47 # gpg used by SecureDrop. If use_agent=False, gpg2 prints a warning
48 # message every time it runs because the option is deprecated and has
49 # no effect. This message cannot be silenced even if you change the
50 # --debug-level (controlled via the verbose= keyword argument to the
51 # gnupg.GPG constructor), and creates a lot of logging noise.
52 #
53 # The best solution here would be to avoid passing either --use-agent
54 # or --no-use-agent to gpg2, and I have filed an issue upstream to
55 # address this: https://github.com/isislovecruft/python-gnupg/issues/96
56 gpg = gnupg.GPG(binary='gpg2', homedir=config.GPG_KEY_DIR, use_agent=True)
57 
58 words = file(config.WORD_LIST).read().split('\n')
59 nouns = file(config.NOUNS).read().split('\n')
60 adjectives = file(config.ADJECTIVES).read().split('\n')
61 
62 
63 class CryptoException(Exception):
64     pass
65 
66 
67 def clean(s, also=''):
68     """
69     >>> clean("Hello, world!")
70     Traceback (most recent call last):
71       ...
72     CryptoException: invalid input
73     >>> clean("Helloworld")
74     'Helloworld'
75     """
76     # safe characters for every possible word in the wordlist includes capital
77     # letters because codename hashes are base32-encoded with capital letters
78     ok = ' !#%$&)(+*-1032547698;:=?@acbedgfihkjmlonqpsrutwvyxzABCDEFGHIJKLMNOPQRSTUVWXYZ'
79     for c in s:
80         if c not in ok and c not in also:
81             raise CryptoException("invalid input: %s" % s)
82     # scrypt.hash requires input of type str. Since the wordlist is all ASCII
83     # characters, this conversion is not problematic
84     return str(s)
85 
86 
87 def genrandomid(words_in_random_id=DEFAULT_WORDS_IN_RANDOM_ID):
88     return ' '.join(random.choice(words) for x in range(words_in_random_id))
89 
90 
91 def display_id():
92     return ' '.join([random.choice(adjectives), random.choice(nouns)])
93 
94 
95 def hash_codename(codename, salt=SCRYPT_ID_PEPPER):
96     """
97     >>> hash_codename('Hello, world!')
98     'EQZGCJBRGISGOTC2NZVWG6LILJBHEV3CINNEWSCLLFTUWZLFHBTS6WLCHFHTOLRSGQXUQLRQHFMXKOKKOQ4WQ6SXGZXDAS3Z'
99     """
100     return b32encode(scrypt.hash(clean(codename), salt, **SCRYPT_PARAMS))
101 
102 
103 def genkeypair(name, secret):
104     """
105     >>> if not gpg.list_keys(hash_codename('randomid')):
106     ...     genkeypair(hash_codename('randomid'), 'randomid').type
107     ... else:
108     ...     u'P'
109     u'P'
110     """
111     name = clean(name)
112     secret = hash_codename(secret, salt=SCRYPT_GPG_PEPPER)
113     return gpg.gen_key(gpg.gen_key_input(
114         key_type=GPG_KEY_TYPE, key_length=GPG_KEY_LENGTH,
115         passphrase=secret,
116         name_email=name
117     ))
118 
119 
120 def delete_reply_keypair(source_id):
121     key = getkey(source_id)
122     # If this source was never flagged for review, they won't have a reply keypair
123     if not key:
124         return
125     # The private key needs to be deleted before the public key can be deleted
126     # http://pythonhosted.org/python-gnupg/#deleting-keys
127     gpg.delete_keys(key, True)  # private key
128     gpg.delete_keys(key)  # public key
129     # TODO: srm?
130 
131 
132 def getkey(name):
133     for key in gpg.list_keys():
134         for uid in key['uids']:
135             if name in uid:
136                 return key['fingerprint']
137     return None
138 
139 
140 def get_key_by_fingerprint(fingerprint):
141     matches = filter(lambda k: k['fingerprint'] == fingerprint, gpg.list_keys())
142     return matches[0] if matches else None
143 
144 
145 def encrypt(plaintext, fingerprints, output=None):
146     # Verify the output path
147     if output:
148         store.verify(output)
149 
150     # Remove any spaces from provided fingerprints
151     # GPG outputs fingerprints with spaces for readability, but requires the
152     # spaces to be removed when using fingerprints to specify recipients.
153     if not isinstance(fingerprints, (list, tuple)):
154         fingerprints = [fingerprints, ]
155     fingerprints = [fpr.replace(' ', '') for fpr in fingerprints]
156 
157     if isinstance(plaintext, unicode):
158         plaintext = plaintext.encode('utf8')
159 
160     encrypt_fn = gpg.encrypt if isinstance(plaintext, str) else gpg.encrypt_file
161     out = encrypt_fn(plaintext,
162                      *fingerprints,
163                      output=output,
164                      always_trust=True,
165                      armor=False)
166     if out.ok:
167         return out.data
168     else:
169         raise CryptoException(out.stderr)
170 
171 
172 def decrypt(secret, plain_text):
173     """
174     >>> key = genkeypair('randomid', 'randomid')
175     >>> decrypt('randomid', 'randomid',
176     ...   encrypt('randomid', 'Goodbye, cruel world!')
177     ... )
178     'Goodbye, cruel world!'
179     """
180     hashed_codename = hash_codename(secret, salt=SCRYPT_GPG_PEPPER)
181     return gpg.decrypt(plain_text, passphrase=hashed_codename).data
182 
183 
184 if __name__ == "__main__":
185     import doctest
186     doctest.testmod()
187
```

Path: `securedrop/secure_tempfile.py`
Content:
```
1 import base64
2 import os
3 from tempfile import _TemporaryFileWrapper
4 
5 from Crypto.Cipher import AES
6 from Crypto.Random import random
7 from Crypto.Util import Counter
8 
9 
10 class SecureTemporaryFile(_TemporaryFileWrapper):
11     """Temporary file that is ephemerally encrypted on the fly.
12 
13     Since only encrypted data is ever written to disk, using this
14     classes minimizes the chances of plaintext recovery through
15     forensic disk analysis.
16 
17     Adapted from Globaleaks' GLSecureTemporaryFile: https://github.com/globaleaks/GlobaLeaks/blob/master/backend/globaleaks/security.py#L35
18 
19     WARNING: you can't use this like a normal file object. It supports
20     being written to exactly once, then read from exactly once.
21     """
22 
23     AES_key_size = 256
24     AES_block_size = 128
25 
26     def __init__(self, store_dir):
27         self.last_action = 'init'
28         self.create_key()
29 
30         self.tmp_file_id = base64.urlsafe_b64encode(os.urandom(32)).strip('=')
31         self.filepath = os.path.join(store_dir, "{}.aes".format(self.tmp_file_id))
32         self.file = open(self.filepath, 'w+b')
33 
34         _TemporaryFileWrapper.__init__(self, self.file, self.filepath, delete=True)
35 
36     def create_key(self):
37         """
38         Randomly generate an AES key to encrypt the file
39         """
40         self.key = os.urandom(self.AES_key_size / 8)
41         self.iv = random.getrandbits(self.AES_block_size)
42         self.initialize_cipher()
43 
44     def initialize_cipher(self):
45         self.ctr_e = Counter.new(self.AES_block_size, initial_value=self.iv)
46         self.ctr_d = Counter.new(self.AES_block_size, initial_value=self.iv)
47         self.encryptor = AES.new(self.key, AES.MODE_CTR, counter=self.ctr_e)
48         self.decryptor = AES.new(self.key, AES.MODE_CTR, counter=self.ctr_d)
49 
50     def write(self, data):
51         """
52         We track the internal status and don't allow writing after reading.
53         It might be possible to be smarter about this.
54         """
55         assert self.last_action != 'read', "You cannot write after read!"
56         self.last_action = 'write'
57 
58         try:
59             if isinstance(data, unicode):
60                 data = data.encode('utf-8')
61             self.file.write(self.encryptor.encrypt(data))
62         except Exception as err:
63             raise err
64 
65     def read(self, count=None):
66         """
67         The first time 'read' is called after a write, automatically seek(0).
68         """
69         if self.last_action == 'write':
70             self.seek(0, 0)
71             self.last_action = 'read'
72 
73         if count is None:
74             return self.decryptor.decrypt(self.file.read())
75         else:
76             return self.decryptor.decrypt(self.file.read(count))
77 
78     def close(self):
79         return _TemporaryFileWrapper.close(self)
80
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/securedrop/crypto_util.py b/securedrop/crypto_util.py
--- a/securedrop/crypto_util.py
+++ b/securedrop/crypto_util.py
@@ -154,15 +154,11 @@
         fingerprints = [fingerprints, ]
     fingerprints = [fpr.replace(' ', '') for fpr in fingerprints]
 
-    if isinstance(plaintext, unicode):
-        plaintext = plaintext.encode('utf8')
-
-    encrypt_fn = gpg.encrypt if isinstance(plaintext, str) else gpg.encrypt_file
-    out = encrypt_fn(plaintext,
-                     *fingerprints,
-                     output=output,
-                     always_trust=True,
-                     armor=False)
+    out = gpg.encrypt(plaintext,
+                      *fingerprints,
+                      output=output,
+                      always_trust=True,
+                      armor=False)
     if out.ok:
         return out.data
     else:
diff --git a/securedrop/secure_tempfile.py b/securedrop/secure_tempfile.py
--- a/securedrop/secure_tempfile.py
+++ b/securedrop/secure_tempfile.py
@@ -6,6 +6,7 @@
 from Crypto.Random import random
 from Crypto.Util import Counter
 
+from gnupg._util import _STREAMLIKE_TYPES
 
 class SecureTemporaryFile(_TemporaryFileWrapper):
     """Temporary file that is ephemerally encrypted on the fly.
@@ -77,3 +78,8 @@
 
     def close(self):
         return _TemporaryFileWrapper.close(self)
+
+# python-gnupg will not recognize our SecureTemporaryFile as a stream-like type
+# and will attempt to call encode on it, thinking it's a string-like type. To
+# avoid this we add it the list of stream-like types.
+_STREAMLIKE_TYPES.append(_TemporaryFileWrapper)

{"golden_diff": "diff --git a/securedrop/crypto_util.py b/securedrop/crypto_util.py\n--- a/securedrop/crypto_util.py\n+++ b/securedrop/crypto_util.py\n@@ -154,15 +154,11 @@\n         fingerprints = [fingerprints, ]\n     fingerprints = [fpr.replace(' ', '') for fpr in fingerprints]\n \n-    if isinstance(plaintext, unicode):\n-        plaintext = plaintext.encode('utf8')\n-\n-    encrypt_fn = gpg.encrypt if isinstance(plaintext, str) else gpg.encrypt_file\n-    out = encrypt_fn(plaintext,\n-                     *fingerprints,\n-                     output=output,\n-                     always_trust=True,\n-                     armor=False)\n+    out = gpg.encrypt(plaintext,\n+                      *fingerprints,\n+                      output=output,\n+                      always_trust=True,\n+                      armor=False)\n     if out.ok:\n         return out.data\n     else:\ndiff --git a/securedrop/secure_tempfile.py b/securedrop/secure_tempfile.py\n--- a/securedrop/secure_tempfile.py\n+++ b/securedrop/secure_tempfile.py\n@@ -6,6 +6,7 @@\n from Crypto.Random import random\n from Crypto.Util import Counter\n \n+from gnupg._util import _STREAMLIKE_TYPES\n \n class SecureTemporaryFile(_TemporaryFileWrapper):\n     \"\"\"Temporary file that is ephemerally encrypted on the fly.\n@@ -77,3 +78,8 @@\n \n     def close(self):\n         return _TemporaryFileWrapper.close(self)\n+\n+# python-gnupg will not recognize our SecureTemporaryFile as a stream-like type\n+# and will attempt to call encode on it, thinking it's a string-like type. To\n+# avoid this we add it the list of stream-like types.\n+_STREAMLIKE_TYPES.append(_TemporaryFileWrapper)\n", "issue": "Use upstream version of python-gnupg\nSee https://github.com/freedomofpress/securedrop/issues/250#issuecomment-86237456 for context. We have worked with the upstream maintainer to fix the issues that were preventing us from using the upstream version with SecureDrop. Now that they are fixed, we should start using the upstream and discontinue our use of the outdated `gnupg-securedrop` [fork](https://pypi.python.org/pypi/gnupg-securedrop/1.2.5-9-g6f9d63a-dirty).\n\n", "before_files": [{"content": "# -*- coding: utf-8 -*-\nimport os\nimport subprocess\nfrom base64 import b32encode\n\nfrom Crypto.Random import random\nimport gnupg\nimport scrypt\n\nimport config\nimport store\n\n# to fix gpg error #78 on production\nos.environ['USERNAME'] = 'www-data'\n\nGPG_KEY_TYPE = \"RSA\"\nif os.environ.get('SECUREDROP_ENV') == 'test':\n    # Optimize crypto to speed up tests (at the expense of security - DO NOT\n    # use these settings in production)\n    GPG_KEY_LENGTH = 1024\n    SCRYPT_PARAMS = dict(N=2**1, r=1, p=1)\nelse:\n    GPG_KEY_LENGTH = 4096\n    SCRYPT_PARAMS = config.SCRYPT_PARAMS\n\nSCRYPT_ID_PEPPER = config.SCRYPT_ID_PEPPER\nSCRYPT_GPG_PEPPER = config.SCRYPT_GPG_PEPPER\n\nDEFAULT_WORDS_IN_RANDOM_ID = 8\n\n\n# Make sure these pass before the app can run\n# TODO: Add more tests\ndef do_runtime_tests():\n    assert(config.SCRYPT_ID_PEPPER != config.SCRYPT_GPG_PEPPER)\n    # crash if we don't have srm:\n    try:\n        subprocess.check_call(['srm'], stdout=subprocess.PIPE)\n    except subprocess.CalledProcessError:\n        pass\n\ndo_runtime_tests()\n\n# HACK: use_agent=True is used to avoid logging noise.\n#\n# --use-agent is a dummy option in gpg2, which is the only version of\n# gpg used by SecureDrop. If use_agent=False, gpg2 prints a warning\n# message every time it runs because the option is deprecated and has\n# no effect. This message cannot be silenced even if you change the\n# --debug-level (controlled via the verbose= keyword argument to the\n# gnupg.GPG constructor), and creates a lot of logging noise.\n#\n# The best solution here would be to avoid passing either --use-agent\n# or --no-use-agent to gpg2, and I have filed an issue upstream to\n# address this: https://github.com/isislovecruft/python-gnupg/issues/96\ngpg = gnupg.GPG(binary='gpg2', homedir=config.GPG_KEY_DIR, use_agent=True)\n\nwords = file(config.WORD_LIST).read().split('\\n')\nnouns = file(config.NOUNS).read().split('\\n')\nadjectives = file(config.ADJECTIVES).read().split('\\n')\n\n\nclass CryptoException(Exception):\n    pass\n\n\ndef clean(s, also=''):\n    \"\"\"\n    >>> clean(\"Hello, world!\")\n    Traceback (most recent call last):\n      ...\n    CryptoException: invalid input\n    >>> clean(\"Helloworld\")\n    'Helloworld'\n    \"\"\"\n    # safe characters for every possible word in the wordlist includes capital\n    # letters because codename hashes are base32-encoded with capital letters\n    ok = ' !#%$&)(+*-1032547698;:=?@acbedgfihkjmlonqpsrutwvyxzABCDEFGHIJKLMNOPQRSTUVWXYZ'\n    for c in s:\n        if c not in ok and c not in also:\n            raise CryptoException(\"invalid input: %s\" % s)\n    # scrypt.hash requires input of type str. Since the wordlist is all ASCII\n    # characters, this conversion is not problematic\n    return str(s)\n\n\ndef genrandomid(words_in_random_id=DEFAULT_WORDS_IN_RANDOM_ID):\n    return ' '.join(random.choice(words) for x in range(words_in_random_id))\n\n\ndef display_id():\n    return ' '.join([random.choice(adjectives), random.choice(nouns)])\n\n\ndef hash_codename(codename, salt=SCRYPT_ID_PEPPER):\n    \"\"\"\n    >>> hash_codename('Hello, world!')\n    'EQZGCJBRGISGOTC2NZVWG6LILJBHEV3CINNEWSCLLFTUWZLFHBTS6WLCHFHTOLRSGQXUQLRQHFMXKOKKOQ4WQ6SXGZXDAS3Z'\n    \"\"\"\n    return b32encode(scrypt.hash(clean(codename), salt, **SCRYPT_PARAMS))\n\n\ndef genkeypair(name, secret):\n    \"\"\"\n    >>> if not gpg.list_keys(hash_codename('randomid')):\n    ...     genkeypair(hash_codename('randomid'), 'randomid').type\n    ... else:\n    ...     u'P'\n    u'P'\n    \"\"\"\n    name = clean(name)\n    secret = hash_codename(secret, salt=SCRYPT_GPG_PEPPER)\n    return gpg.gen_key(gpg.gen_key_input(\n        key_type=GPG_KEY_TYPE, key_length=GPG_KEY_LENGTH,\n        passphrase=secret,\n        name_email=name\n    ))\n\n\ndef delete_reply_keypair(source_id):\n    key = getkey(source_id)\n    # If this source was never flagged for review, they won't have a reply keypair\n    if not key:\n        return\n    # The private key needs to be deleted before the public key can be deleted\n    # http://pythonhosted.org/python-gnupg/#deleting-keys\n    gpg.delete_keys(key, True)  # private key\n    gpg.delete_keys(key)  # public key\n    # TODO: srm?\n\n\ndef getkey(name):\n    for key in gpg.list_keys():\n        for uid in key['uids']:\n            if name in uid:\n                return key['fingerprint']\n    return None\n\n\ndef get_key_by_fingerprint(fingerprint):\n    matches = filter(lambda k: k['fingerprint'] == fingerprint, gpg.list_keys())\n    return matches[0] if matches else None\n\n\ndef encrypt(plaintext, fingerprints, output=None):\n    # Verify the output path\n    if output:\n        store.verify(output)\n\n    # Remove any spaces from provided fingerprints\n    # GPG outputs fingerprints with spaces for readability, but requires the\n    # spaces to be removed when using fingerprints to specify recipients.\n    if not isinstance(fingerprints, (list, tuple)):\n        fingerprints = [fingerprints, ]\n    fingerprints = [fpr.replace(' ', '') for fpr in fingerprints]\n\n    if isinstance(plaintext, unicode):\n        plaintext = plaintext.encode('utf8')\n\n    encrypt_fn = gpg.encrypt if isinstance(plaintext, str) else gpg.encrypt_file\n    out = encrypt_fn(plaintext,\n                     *fingerprints,\n                     output=output,\n                     always_trust=True,\n                     armor=False)\n    if out.ok:\n        return out.data\n    else:\n        raise CryptoException(out.stderr)\n\n\ndef decrypt(secret, plain_text):\n    \"\"\"\n    >>> key = genkeypair('randomid', 'randomid')\n    >>> decrypt('randomid', 'randomid',\n    ...   encrypt('randomid', 'Goodbye, cruel world!')\n    ... )\n    'Goodbye, cruel world!'\n    \"\"\"\n    hashed_codename = hash_codename(secret, salt=SCRYPT_GPG_PEPPER)\n    return gpg.decrypt(plain_text, passphrase=hashed_codename).data\n\n\nif __name__ == \"__main__\":\n    import doctest\n    doctest.testmod()\n", "path": "securedrop/crypto_util.py"}, {"content": "import base64\nimport os\nfrom tempfile import _TemporaryFileWrapper\n\nfrom Crypto.Cipher import AES\nfrom Crypto.Random import random\nfrom Crypto.Util import Counter\n\n\nclass SecureTemporaryFile(_TemporaryFileWrapper):\n    \"\"\"Temporary file that is ephemerally encrypted on the fly.\n\n    Since only encrypted data is ever written to disk, using this\n    classes minimizes the chances of plaintext recovery through\n    forensic disk analysis.\n\n    Adapted from Globaleaks' GLSecureTemporaryFile: https://github.com/globaleaks/GlobaLeaks/blob/master/backend/globaleaks/security.py#L35\n\n    WARNING: you can't use this like a normal file object. It supports\n    being written to exactly once, then read from exactly once.\n    \"\"\"\n\n    AES_key_size = 256\n    AES_block_size = 128\n\n    def __init__(self, store_dir):\n        self.last_action = 'init'\n        self.create_key()\n\n        self.tmp_file_id = base64.urlsafe_b64encode(os.urandom(32)).strip('=')\n        self.filepath = os.path.join(store_dir, \"{}.aes\".format(self.tmp_file_id))\n        self.file = open(self.filepath, 'w+b')\n\n        _TemporaryFileWrapper.__init__(self, self.file, self.filepath, delete=True)\n\n    def create_key(self):\n        \"\"\"\n        Randomly generate an AES key to encrypt the file\n        \"\"\"\n        self.key = os.urandom(self.AES_key_size / 8)\n        self.iv = random.getrandbits(self.AES_block_size)\n        self.initialize_cipher()\n\n    def initialize_cipher(self):\n        self.ctr_e = Counter.new(self.AES_block_size, initial_value=self.iv)\n        self.ctr_d = Counter.new(self.AES_block_size, initial_value=self.iv)\n        self.encryptor = AES.new(self.key, AES.MODE_CTR, counter=self.ctr_e)\n        self.decryptor = AES.new(self.key, AES.MODE_CTR, counter=self.ctr_d)\n\n    def write(self, data):\n        \"\"\"\n        We track the internal status and don't allow writing after reading.\n        It might be possible to be smarter about this.\n        \"\"\"\n        assert self.last_action != 'read', \"You cannot write after read!\"\n        self.last_action = 'write'\n\n        try:\n            if isinstance(data, unicode):\n                data = data.encode('utf-8')\n            self.file.write(self.encryptor.encrypt(data))\n        except Exception as err:\n            raise err\n\n    def read(self, count=None):\n        \"\"\"\n        The first time 'read' is called after a write, automatically seek(0).\n        \"\"\"\n        if self.last_action == 'write':\n            self.seek(0, 0)\n            self.last_action = 'read'\n\n        if count is None:\n            return self.decryptor.decrypt(self.file.read())\n        else:\n            return self.decryptor.decrypt(self.file.read(count))\n\n    def close(self):\n        return _TemporaryFileWrapper.close(self)\n", "path": "securedrop/secure_tempfile.py"}], "after_files": [{"content": "# -*- coding: utf-8 -*-\nimport os\nimport subprocess\nfrom base64 import b32encode\n\nfrom Crypto.Random import random\nimport gnupg\nimport scrypt\n\nimport config\nimport store\n\n# to fix gpg error #78 on production\nos.environ['USERNAME'] = 'www-data'\n\nGPG_KEY_TYPE = \"RSA\"\nif os.environ.get('SECUREDROP_ENV') == 'test':\n    # Optimize crypto to speed up tests (at the expense of security - DO NOT\n    # use these settings in production)\n    GPG_KEY_LENGTH = 1024\n    SCRYPT_PARAMS = dict(N=2**1, r=1, p=1)\nelse:\n    GPG_KEY_LENGTH = 4096\n    SCRYPT_PARAMS = config.SCRYPT_PARAMS\n\nSCRYPT_ID_PEPPER = config.SCRYPT_ID_PEPPER\nSCRYPT_GPG_PEPPER = config.SCRYPT_GPG_PEPPER\n\nDEFAULT_WORDS_IN_RANDOM_ID = 8\n\n\n# Make sure these pass before the app can run\n# TODO: Add more tests\ndef do_runtime_tests():\n    assert(config.SCRYPT_ID_PEPPER != config.SCRYPT_GPG_PEPPER)\n    # crash if we don't have srm:\n    try:\n        subprocess.check_call(['srm'], stdout=subprocess.PIPE)\n    except subprocess.CalledProcessError:\n        pass\n\ndo_runtime_tests()\n\n# HACK: use_agent=True is used to avoid logging noise.\n#\n# --use-agent is a dummy option in gpg2, which is the only version of\n# gpg used by SecureDrop. If use_agent=False, gpg2 prints a warning\n# message every time it runs because the option is deprecated and has\n# no effect. This message cannot be silenced even if you change the\n# --debug-level (controlled via the verbose= keyword argument to the\n# gnupg.GPG constructor), and creates a lot of logging noise.\n#\n# The best solution here would be to avoid passing either --use-agent\n# or --no-use-agent to gpg2, and I have filed an issue upstream to\n# address this: https://github.com/isislovecruft/python-gnupg/issues/96\ngpg = gnupg.GPG(binary='gpg2', homedir=config.GPG_KEY_DIR, use_agent=True)\n\nwords = file(config.WORD_LIST).read().split('\\n')\nnouns = file(config.NOUNS).read().split('\\n')\nadjectives = file(config.ADJECTIVES).read().split('\\n')\n\n\nclass CryptoException(Exception):\n    pass\n\n\ndef clean(s, also=''):\n    \"\"\"\n    >>> clean(\"Hello, world!\")\n    Traceback (most recent call last):\n      ...\n    CryptoException: invalid input\n    >>> clean(\"Helloworld\")\n    'Helloworld'\n    \"\"\"\n    # safe characters for every possible word in the wordlist includes capital\n    # letters because codename hashes are base32-encoded with capital letters\n    ok = ' !#%$&)(+*-1032547698;:=?@acbedgfihkjmlonqpsrutwvyxzABCDEFGHIJKLMNOPQRSTUVWXYZ'\n    for c in s:\n        if c not in ok and c not in also:\n            raise CryptoException(\"invalid input: %s\" % s)\n    # scrypt.hash requires input of type str. Since the wordlist is all ASCII\n    # characters, this conversion is not problematic\n    return str(s)\n\n\ndef genrandomid(words_in_random_id=DEFAULT_WORDS_IN_RANDOM_ID):\n    return ' '.join(random.choice(words) for x in range(words_in_random_id))\n\n\ndef display_id():\n    return ' '.join([random.choice(adjectives), random.choice(nouns)])\n\n\ndef hash_codename(codename, salt=SCRYPT_ID_PEPPER):\n    \"\"\"\n    >>> hash_codename('Hello, world!')\n    'EQZGCJBRGISGOTC2NZVWG6LILJBHEV3CINNEWSCLLFTUWZLFHBTS6WLCHFHTOLRSGQXUQLRQHFMXKOKKOQ4WQ6SXGZXDAS3Z'\n    \"\"\"\n    return b32encode(scrypt.hash(clean(codename), salt, **SCRYPT_PARAMS))\n\n\ndef genkeypair(name, secret):\n    \"\"\"\n    >>> if not gpg.list_keys(hash_codename('randomid')):\n    ...     genkeypair(hash_codename('randomid'), 'randomid').type\n    ... else:\n    ...     u'P'\n    u'P'\n    \"\"\"\n    name = clean(name)\n    secret = hash_codename(secret, salt=SCRYPT_GPG_PEPPER)\n    return gpg.gen_key(gpg.gen_key_input(\n        key_type=GPG_KEY_TYPE, key_length=GPG_KEY_LENGTH,\n        passphrase=secret,\n        name_email=name\n    ))\n\n\ndef delete_reply_keypair(source_id):\n    key = getkey(source_id)\n    # If this source was never flagged for review, they won't have a reply keypair\n    if not key:\n        return\n    # The private key needs to be deleted before the public key can be deleted\n    # http://pythonhosted.org/python-gnupg/#deleting-keys\n    gpg.delete_keys(key, True)  # private key\n    gpg.delete_keys(key)  # public key\n    # TODO: srm?\n\n\ndef getkey(name):\n    for key in gpg.list_keys():\n        for uid in key['uids']:\n            if name in uid:\n                return key['fingerprint']\n    return None\n\n\ndef get_key_by_fingerprint(fingerprint):\n    matches = filter(lambda k: k['fingerprint'] == fingerprint, gpg.list_keys())\n    return matches[0] if matches else None\n\n\ndef encrypt(plaintext, fingerprints, output=None):\n    # Verify the output path\n    if output:\n        store.verify(output)\n\n    # Remove any spaces from provided fingerprints\n    # GPG outputs fingerprints with spaces for readability, but requires the\n    # spaces to be removed when using fingerprints to specify recipients.\n    if not isinstance(fingerprints, (list, tuple)):\n        fingerprints = [fingerprints, ]\n    fingerprints = [fpr.replace(' ', '') for fpr in fingerprints]\n\n    out = gpg.encrypt(plaintext,\n                      *fingerprints,\n                      output=output,\n                      always_trust=True,\n                      armor=False)\n    if out.ok:\n        return out.data\n    else:\n        raise CryptoException(out.stderr)\n\n\ndef decrypt(secret, plain_text):\n    \"\"\"\n    >>> key = genkeypair('randomid', 'randomid')\n    >>> decrypt('randomid', 'randomid',\n    ...   encrypt('randomid', 'Goodbye, cruel world!')\n    ... )\n    'Goodbye, cruel world!'\n    \"\"\"\n    hashed_codename = hash_codename(secret, salt=SCRYPT_GPG_PEPPER)\n    return gpg.decrypt(plain_text, passphrase=hashed_codename).data\n\n\nif __name__ == \"__main__\":\n    import doctest\n    doctest.testmod()\n", "path": "securedrop/crypto_util.py"}, {"content": "import base64\nimport os\nfrom tempfile import _TemporaryFileWrapper\n\nfrom Crypto.Cipher import AES\nfrom Crypto.Random import random\nfrom Crypto.Util import Counter\n\nfrom gnupg._util import _STREAMLIKE_TYPES\n\nclass SecureTemporaryFile(_TemporaryFileWrapper):\n    \"\"\"Temporary file that is ephemerally encrypted on the fly.\n\n    Since only encrypted data is ever written to disk, using this\n    classes minimizes the chances of plaintext recovery through\n    forensic disk analysis.\n\n    Adapted from Globaleaks' GLSecureTemporaryFile: https://github.com/globaleaks/GlobaLeaks/blob/master/backend/globaleaks/security.py#L35\n\n    WARNING: you can't use this like a normal file object. It supports\n    being written to exactly once, then read from exactly once.\n    \"\"\"\n\n    AES_key_size = 256\n    AES_block_size = 128\n\n    def __init__(self, store_dir):\n        self.last_action = 'init'\n        self.create_key()\n\n        self.tmp_file_id = base64.urlsafe_b64encode(os.urandom(32)).strip('=')\n        self.filepath = os.path.join(store_dir, \"{}.aes\".format(self.tmp_file_id))\n        self.file = open(self.filepath, 'w+b')\n\n        _TemporaryFileWrapper.__init__(self, self.file, self.filepath, delete=True)\n\n    def create_key(self):\n        \"\"\"\n        Randomly generate an AES key to encrypt the file\n        \"\"\"\n        self.key = os.urandom(self.AES_key_size / 8)\n        self.iv = random.getrandbits(self.AES_block_size)\n        self.initialize_cipher()\n\n    def initialize_cipher(self):\n        self.ctr_e = Counter.new(self.AES_block_size, initial_value=self.iv)\n        self.ctr_d = Counter.new(self.AES_block_size, initial_value=self.iv)\n        self.encryptor = AES.new(self.key, AES.MODE_CTR, counter=self.ctr_e)\n        self.decryptor = AES.new(self.key, AES.MODE_CTR, counter=self.ctr_d)\n\n    def write(self, data):\n        \"\"\"\n        We track the internal status and don't allow writing after reading.\n        It might be possible to be smarter about this.\n        \"\"\"\n        assert self.last_action != 'read', \"You cannot write after read!\"\n        self.last_action = 'write'\n\n        try:\n            if isinstance(data, unicode):\n                data = data.encode('utf-8')\n            self.file.write(self.encryptor.encrypt(data))\n        except Exception as err:\n            raise err\n\n    def read(self, count=None):\n        \"\"\"\n        The first time 'read' is called after a write, automatically seek(0).\n        \"\"\"\n        if self.last_action == 'write':\n            self.seek(0, 0)\n            self.last_action = 'read'\n\n        if count is None:\n            return self.decryptor.decrypt(self.file.read())\n        else:\n            return self.decryptor.decrypt(self.file.read(count))\n\n    def close(self):\n        return _TemporaryFileWrapper.close(self)\n\n# python-gnupg will not recognize our SecureTemporaryFile as a stream-like type\n# and will attempt to call encode on it, thinking it's a string-like type. To\n# avoid this we add it the list of stream-like types.\n_STREAMLIKE_TYPES.append(_TemporaryFileWrapper)\n", "path": "securedrop/secure_tempfile.py"}]}