Patch tested on stretch, seems to do the job. Can't imagine it
wouldn't work on testing, too. Unfortunately, by the time I realized
that "python-gpg" and "python-gnupg" are not the same thing, I was
done. Please do let me know if you'd prefer me to use python-gnupg
instead, and I'll be happy to. This is good practice :)

Change summary:

1. Update dropbox distribution public key
2. Replace usages of StringIO with BytesIO. Since we are dealing with
binary files and signature files, encoding/decoding should be avoided.
This class was also needed for gnupg.GPG.verify_data() as it expected
a bytes object. The only other usage was when downloading the dropbox
.tgz - I believe it was more appropriate to use BytesIO for this as
well for the aforementioned encoding concern.
3. Replacement of python-gpgme, with python-gnupg. Note; with the
inclusion of this patch, you should replace the debian package
dependency of "python-gpgme" with "python-gnupg"

Since I can't sign this email, I recommend auditing the public key
before implementing (see DROPBOX_PUBLIC_KEY in caja-dropbox.in) should
you choose to accept this.
--- a/caja-dropbox.in	2017-07-12 18:33:02.375215166 -0400
+++ b/caja-dropbox.in	2017-07-12 18:37:44.826998583 -0400
@@ -28,7 +28,7 @@
 import platform
 import shutil
 import socket
-import StringIO
+import io
 import subprocess
 import sys
 import tarfile
@@ -40,9 +40,9 @@
 import urllib2
 
 try:
-    import gpgme
+    import gnupg
 except ImportError:
-    gpgme = None
+    gnupg = None
 
 from contextlib import closing, contextmanager
 from posixpath import curdir, sep, pardir, join, abspath, commonprefix
@@ -67,22 +67,25 @@
 enc = locale.getpreferredencoding()
 
 # Available from https://linux.dropbox.com/fedora/rpm-public-key.asc
+# last fetched 2017-07-12
 DROPBOX_PUBLIC_KEY = """
 -----BEGIN PGP PUBLIC KEY BLOCK-----
-Version: SKS 1.1.0
+Version: GnuPG v1.4.9 (GNU/Linux)
 
-mQENBEt0ibEBCACv4hZRPqwtpU6z8+BB5YZU1a3yjEvg2W68+a6hEwxtCa2U++4dzQ+7EqaU
-q5ybQnwtbDdpFpsOi9x31J+PCpufPUfIG694/0rlEpmzl2GWzY8NqfdBFGGm/SPSSwvKbeNc
-FMRLu5neo7W9kwvfMbGjHmvUbzBUVpCVKD0OEEf1q/Ii0Qcekx9CMoLvWq7ZwNHEbNnij7ec
-nvwNlE2MxNsOSJj+hwZGK+tM19kuYGSKw4b5mR8IyThlgiSLIfpSBh1n2KX+TDdk9GR+57TY
-vlRu6nTPu98P05IlrrCP+KF0hYZYOaMvQs9Rmc09tc/eoQlN0kkaBWw9Rv/dvLVc0aUXABEB
-AAG0MURyb3Bib3ggQXV0b21hdGljIFNpZ25pbmcgS2V5IDxsaW51eEBkcm9wYm94LmNvbT6J
-ATYEEwECACAFAkt0ibECGwMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAAKCRD8kYszUESRLi/z
-B/wMscEa15rS+0mIpsORknD7kawKwyda+LHdtZc0hD/73QGFINR2P23UTol/R4nyAFEuYNsF
-0C4IAD6y4pL49eZ72IktPrr4H27Q9eXhNZfJhD7BvQMBx75L0F5gSQwuC7GdYNlwSlCD0AAh
-Qbi70VBwzeIgITBkMQcJIhLvllYo/AKD7Gv9huy4RLaIoSeofp+2Q0zUHNPl/7zymOqu+5Ox
-e1ltuJT/kd/8hU+N5WNxJTSaOK0sF1/wWFM6rWd6XQUP03VyNosAevX5tBo++iD1WY2/lFVU
-JkvAvge2WFk3c6tAwZT/tKxspFy4M/tNbDKeyvr685XKJw9ei6GcOGHD
+mQENBEt0ibEBCACv4hZRPqwtpU6z8+BB5YZU1a3yjEvg2W68+a6hEwxtCa2U++4d
+zQ+7EqaUq5ybQnwtbDdpFpsOi9x31J+PCpufPUfIG694/0rlEpmzl2GWzY8NqfdB
+FGGm/SPSSwvKbeNcFMRLu5neo7W9kwvfMbGjHmvUbzBUVpCVKD0OEEf1q/Ii0Qce
+kx9CMoLvWq7ZwNHEbNnij7ecnvwNlE2MxNsOSJj+hwZGK+tM19kuYGSKw4b5mR8I
+yThlgiSLIfpSBh1n2KX+TDdk9GR+57TYvlRu6nTPu98P05IlrrCP+KF0hYZYOaMv
+Qs9Rmc09tc/eoQlN0kkaBWw9Rv/dvLVc0aUXABEBAAG0MURyb3Bib3ggQXV0b21h
+dGljIFNpZ25pbmcgS2V5IDxsaW51eEBkcm9wYm94LmNvbT6JATYEEwECACAFAkt0
+ibECGwMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAAKCRD8kYszUESRLi/zB/wMscEa
+15rS+0mIpsORknD7kawKwyda+LHdtZc0hD/73QGFINR2P23UTol/R4nyAFEuYNsF
+0C4IAD6y4pL49eZ72IktPrr4H27Q9eXhNZfJhD7BvQMBx75L0F5gSQwuC7GdYNlw
+SlCD0AAhQbi70VBwzeIgITBkMQcJIhLvllYo/AKD7Gv9huy4RLaIoSeofp+2Q0zU
+HNPl/7zymOqu+5Oxe1ltuJT/kd/8hU+N5WNxJTSaOK0sF1/wWFM6rWd6XQUP03Vy
+NosAevX5tBo++iD1WY2/lFVUJkvAvge2WFk3c6tAwZT/tKxspFy4M/tNbDKeyvr6
+85XKJw9ei6GcOGHD
 =5rWG
 -----END PGP PUBLIC KEY BLOCK-----
 """
@@ -178,26 +181,14 @@
     return os.path.abspath(path.encode(sys.getfilesystemencoding())).decode(sys.getfilesystemencoding())
 
 @contextmanager
-def gpgme_context(keys):
-    gpg_conf_contents = ''
+def gpg_context():
     _gpghome = tempfile.mkdtemp(prefix='tmp.gpghome')
-
     try:
         os.environ['GNUPGHOME'] = _gpghome
-        fp = open(os.path.join(_gpghome, 'gpg.conf'), 'wb')
-        fp.write(gpg_conf_contents)
-        fp.close()
-        ctx = gpgme.Context()
-
-        loaded = []
-        for key_file in keys:
-            result = ctx.import_(key_file)
-            key = ctx.get_key(result.imports[0][0])
-            loaded.append(key)
-
-        ctx.signers = loaded
-
-        yield ctx
+        open(os.path.join(_gpghome, 'gpg.conf'), 'a').close()
+        gpg = gnupg.GPG(gnupghome=_gpghome)
+        gpg.import_keys(DROPBOX_PUBLIC_KEY)
+        yield gpg
     finally:
         del os.environ['GNUPGHOME']
         shutil.rmtree(_gpghome, ignore_errors=True)
@@ -205,10 +196,10 @@
 class SignatureVerifyError(Exception):
     pass
 
-def verify_signature(key_file, sig_file, plain_file):
-    with gpgme_context([key_file]) as ctx:
-        sigs = ctx.verify(sig_file, plain_file, None)
-        return sigs[0].status == None
+def verify_signature(sig_filename, data):
+    with gpg_context() as gpg:
+        verification = gpg.verify_data(sig_filename, data)
+        return verification.valid
 
 def download_file_chunk(url, buf):
     opener = urllib2.build_opener()
@@ -238,22 +229,33 @@
 
 class DownloadState(object):
     def __init__(self):
-        self.local_file = StringIO.StringIO()
+        self.local_file = io.BytesIO()
 
     def copy_data(self):
         return download_file_chunk(DOWNLOAD_LOCATION_FMT % plat(), self.local_file)
 
     def unpack(self):
-        # download signature
-        signature = StringIO.StringIO()
-        for _ in download_file_chunk(SIGNATURE_LOCATION_FMT % plat(), signature):
-            pass
-        signature.seek(0)
-        self.local_file.seek(0)
-
-        if gpgme:
-            if not verify_signature(StringIO.StringIO(DROPBOX_PUBLIC_KEY), signature, self.local_file):
-                raise SignatureVerifyError()
+        # download signature to disk in tempdir, because python-gnupg cannot accept both the target file and signature
+        # file residing in memory, one must be on-disk and referred to by filename. The signature file should always
+        # be small so the impact in terms of disk usage and I/O should be minimal
+        if gnupg:
+            try:
+                self.signaturedir = tempfile.mkdtemp(prefix='tmp.dropboxsignature')
+                self.signaturefilename = os.path.join(self.signaturedir, 'signature.asc')
+                self.signaturedata = io.BytesIO()
+                for _ in download_file_chunk(SIGNATURE_LOCATION_FMT % plat(), self.signaturedata):
+                    pass
+                self.signaturedata.seek(0)
+                self.signature = open(self.signaturefilename, 'ab')
+                self.signature.write(self.signaturedata.read())
+                self.signature.close()
+                self.local_file.seek(0)
+                if not verify_signature(self.signaturefilename, self.local_file.getvalue()):
+                    raise SignatureVerifyError()
+            finally:
+                self.signaturedata.close()
+                shutil.rmtree(self.signaturedir, ignore_errors=True)
+                pass
 
         self.local_file.seek(0)
         archive = tarfile.open(fileobj=self.local_file, mode='r:gz')
@@ -457,7 +459,7 @@
                 self.progress.set_property('width-request', 300)
 
                 self.label = gtk.Label()
-                GPG_WARNING_MSG = (u"\n\n" + GPG_WARNING) if not gpgme else u""
+                GPG_WARNING_MSG = (u"\n\n" + GPG_WARNING) if not gnupg else u""
                 self.label.set_markup('%s <span foreground="#000099" underline="single" weight="bold">%s</span>\n\n%s%s' % (INFO, LINK, WARNING, GPG_WARNING_MSG))
                 self.label.set_line_wrap(True)
                 self.label.set_property('width-request', 300)
@@ -546,7 +548,7 @@
             write(save)
             flush()
         console_print(u"%s %s\n" % (INFO, LINK))
-        GPG_WARNING_MSG = (u"\n%s" % GPG_WARNING) if not gpgme else u""
+        GPG_WARNING_MSG = (u"\n%s" % GPG_WARNING) if not gnupg else u""
 
         download = DownloadState()
 

Reply via email to