Repository: freedomofpress/securedrop

Issue:
GPG PINENTRY_LAUNCHED error when sources are deleted
## Description

Deleting a source raises this error.

```pytb
Exception in thread Thread-39:
Traceback (most recent call last):
  File "/usr/lib/python2.7/threading.py", line 801, in __bootstrap_inner
    self.run()
  File "/usr/lib/python2.7/threading.py", line 754, in run
    self.__target(*self.__args, **self.__kwargs)
  File "/usr/local/lib/python2.7/dist-packages/pretty_bad_protocol/_meta.py", line 670, in _read_response
    result._handle_status(keyword, value)
  File "/usr/local/lib/python2.7/dist-packages/pretty_bad_protocol/_parsers.py", line 1024, in _handle_status
    raise ValueError("Unknown status message: %r" % key)
ValueError: Unknown status message: u'PINENTRY_LAUNCHED'
```

## Steps to Reproduce

- Start container
- Start client
- Delete source

## Expected Behavior

No errors.

## Actual Behavior

There are errors but the HTTP code is still 200, and the key is still deleted on the server side.


Discussion:
I can see the same errors.
Background: I made this change 62bd71a97d10a6d4aac4c20bbb1b71fac12ddb28 such that secret keys would be properly deleted on Xenial (due to [upstream issues](https://dev.gnupg.org/T3465) with `--pinentry-mode loopback` and `--delete-secret-keys`)

Resolving patch:
diff --git a/securedrop/crypto_util.py b/securedrop/crypto_util.py
--- a/securedrop/crypto_util.py
+++ b/securedrop/crypto_util.py
@@ -37,6 +37,23 @@
                        'BCDEFGHIJKLMNOPQRSTUVWXYZ')
 
 
+def monkey_patch_delete_handle_status(self, key, value):
+        """Parse a status code from the attached GnuPG process.
+        :raises: :exc:`~exceptions.ValueError` if the status message is unknown.
+        """
+        if key in ("DELETE_PROBLEM", "KEY_CONSIDERED"):
+            self.status = self.problem_reason.get(value, "Unknown error: %r"
+                                                  % value)
+        elif key in ("PINENTRY_LAUNCHED"):
+            self.status = key.replace("_", " ").lower()
+        else:
+            raise ValueError("Unknown status message: %r" % key)
+
+
+# Monkey patching to resolve https://github.com/freedomofpress/securedrop/issues/4294
+gnupg._parsers.DeleteResult._handle_status = monkey_patch_delete_handle_status
+
+
 class CryptoException(Exception):
     pass
 


Test patch:
diff --git a/securedrop/tests/test_crypto_util.py b/securedrop/tests/test_crypto_util.py
--- a/securedrop/tests/test_crypto_util.py
+++ b/securedrop/tests/test_crypto_util.py
@@ -268,6 +268,24 @@ def test_delete_reply_keypair(source_app, test_source):
     assert source_app.crypto_util.getkey(fid) is None
 
 
+def test_delete_reply_keypair_pinentry_status_is_handled(source_app, test_source,
+                                                         mocker, capsys):
+    """
+    Regression test for https://github.com/freedomofpress/securedrop/issues/4294
+    """
+    fid = test_source['filesystem_id']
+
+    # Patch private python-gnupg method to reproduce the issue in #4294
+    mocker.patch('pretty_bad_protocol._util._separate_keyword',
+                 return_value=('PINENTRY_LAUNCHED', 'does not matter'))
+
+    source_app.crypto_util.delete_reply_keypair(fid)
+
+    captured = capsys.readouterr()
+    assert "ValueError: Unknown status message: 'PINENTRY_LAUNCHED'" not in captured.err
+    assert source_app.crypto_util.getkey(fid) is None
+
+
 def test_delete_reply_keypair_no_key(source_app):
     """No exceptions should be raised when provided a filesystem id that
     does not exist.