← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~lgp171188/launchpad:gnupg2-xenial into launchpad:master

 

Guruprasad has proposed merging ~lgp171188/launchpad:gnupg2-xenial into launchpad:master.

Commit message:
Use `/usr/bin/gpg2` everywhere instead of `/usr/bin/gpg`

This enables GnuPG 2 support in Launchpad.


Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~lgp171188/launchpad/+git/launchpad/+merge/429507
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~lgp171188/launchpad:gnupg2-xenial into launchpad:master.
diff --git a/lib/lp/services/gpg/doc/gpg-encryption.rst b/lib/lp/services/gpg/doc/gpg-encryption.rst
index 4d6af02..a6bd329 100644
--- a/lib/lp/services/gpg/doc/gpg-encryption.rst
+++ b/lib/lp/services/gpg/doc/gpg-encryption.rst
@@ -117,3 +117,4 @@ What about a message encrypted for an unknown key.
     >>> plain is None
     True
 
+    >>> gpghandler.resetLocalState()
diff --git a/lib/lp/services/gpg/doc/gpg-signatures.rst b/lib/lp/services/gpg/doc/gpg-signatures.rst
index 9c3d285..99e61c0 100644
--- a/lib/lp/services/gpg/doc/gpg-signatures.rst
+++ b/lib/lp/services/gpg/doc/gpg-signatures.rst
@@ -231,5 +231,5 @@ itself.
     89
     [<gpgme.Signature object at ...>]
     7
-
+    >>> gpghandler.resetLocalState()
     >>> keyserver.tearDown()
diff --git a/lib/lp/services/gpg/doc/gpghandler.rst b/lib/lp/services/gpg/doc/gpghandler.rst
index 5d1b5d3..32285af 100644
--- a/lib/lp/services/gpg/doc/gpghandler.rst
+++ b/lib/lp/services/gpg/doc/gpghandler.rst
@@ -120,17 +120,8 @@ preamble.
     ...
     lp.services.gpg.interfaces.GPGKeyNotFoundError: ...
 
-Apparently GPGME is able to import an incomplete public key:
-
-    >>> key = gpghandler.importPublicKey(pubkey[:-300])
-    >>> assert key is not None
-    >>> verifyObject(IPymeKey, key)
-    True
-    >>> print(key.fingerprint)
-    A419AE861E88BC9E04B9C26FBA2B9389DFD20543
-
-But we get an error if the damage is big:
-(what probably happened in bug #2547)
+We also get an error if we try to import an incomplete public key
+(which probably happened in bug #2547):
 
     >>> key = gpghandler.importPublicKey(pubkey[:-500])
     Traceback (most recent call last):
diff --git a/lib/lp/services/gpg/handler.py b/lib/lp/services/gpg/handler.py
index 5cce7ef..c4472ff 100644
--- a/lib/lp/services/gpg/handler.py
+++ b/lib/lp/services/gpg/handler.py
@@ -105,11 +105,19 @@ class GPGHandler:
         with open(confpath, "w") as conf:
             # Avoid wasting time verifying the local keyring's consistency.
             conf.write("no-auto-check-trustdb\n")
+            # Use the loopback mode to allow using password callbacks.
+            conf.write("pinentry-mode loopback\n")
+            # Assume "yes" on most questions; this is needed to allow
+            # deletion of secret keys via GPGME.
+            conf.write("yes\n")
             # Prefer a SHA-2 hash where possible, otherwise GPG will fall
             # back to a hash it can use.
             conf.write(
                 "personal-digest-preferences SHA512 SHA384 SHA256 SHA224\n"
             )
+        agentconfpath = os.path.join(self.home, "gpg-agent.conf")
+        with open(agentconfpath, "w") as agentconf:
+            agentconf.write("allow-loopback-pinentry\n")
         # create a local atexit handler to remove the configuration directory
         # on normal termination.
 
@@ -126,11 +134,23 @@ class GPGHandler:
 
     def resetLocalState(self):
         """See IGPGHandler."""
-        # remove the public keyring, private keyring and the trust DB
-        for filename in ["pubring.gpg", "secring.gpg", "trustdb.gpg"]:
+        # Remove the public keyring, private keyring and the trust DB.
+        for filename in (
+            "pubring.gpg",
+            "pubring.kbx",
+            "secring.gpg",
+            "private-keys-v1.d",
+            "trustdb.gpg",
+        ):
             filename = os.path.join(self.home, filename)
             if os.path.exists(filename):
-                os.remove(filename)
+                if os.path.isdir(filename):
+                    shutil.rmtree(filename)
+                else:
+                    os.remove(filename)
+        # Kill any running gpg-agent for GnuPG 2
+        if shutil.which("gpgconf"):
+            subprocess.check_call(["gpgconf", "--kill", "gpg-agent"])
 
     def getVerifiedSignatureResilient(self, content, signature=None):
         """See IGPGHandler."""
@@ -301,6 +321,11 @@ class GPGHandler:
             del os.environ["GPG_AGENT_INFO"]
 
         context = get_gpgme_context()
+
+        def passphrase_cb(uid_hint, passphrase_info, prev_was_bad, fd):
+            os.write(fd, b"\n")
+
+        context.passphrase_cb = passphrase_cb
         newkey = BytesIO(content)
         with gpgme_timeline("import", "new secret key"):
             import_result = context.import_(newkey)
@@ -696,10 +721,13 @@ class PymeKey:
                     get_gpg_path(),
                     "--export-secret-keys",
                     "-a",
+                    # This only works for keys with empty passphrase.
+                    "--passphrase",
+                    "",
                     self.fingerprint,
                 ],
                 stdout=subprocess.PIPE,
-                stderr=subprocess.STDOUT,
+                stderr=subprocess.DEVNULL,
             )
             return p.stdout.read()
 
diff --git a/lib/lp/services/gpg/interfaces.py b/lib/lp/services/gpg/interfaces.py
index 90bf7e7..0668ea1 100644
--- a/lib/lp/services/gpg/interfaces.py
+++ b/lib/lp/services/gpg/interfaces.py
@@ -25,7 +25,6 @@ __all__ = [
 ]
 
 import http.client
-import os.path
 import re
 
 from lazr.enum import DBEnumeratedType, DBItem
@@ -57,15 +56,8 @@ def valid_keyid(keyid):
 
 
 def get_gpg_path():
-    """Return the path to the GPG executable we prefer.
-
-    We stick to GnuPG 1 until we've worked out how to get things working
-    with GnuPG 2.
-    """
-    if os.path.exists("/usr/bin/gpg1"):
-        return "/usr/bin/gpg1"
-    else:
-        return "/usr/bin/gpg"
+    """Return the path to the GPG executable we prefer."""
+    return "/usr/bin/gpg2"
 
 
 def get_gpgme_context():
@@ -443,7 +435,7 @@ class IPymeKey(Interface):
 
         Both public and secret keys are supported, although secret keys are
         exported by calling `gpg` process while public ones use the native
-        gpgme API.
+        gpgme API. Only secret keys with empty passphrases may be exported.
 
         :return: a string containing the exported key.
         """
diff --git a/lib/lp/soyuz/tests/fakepackager.py b/lib/lp/soyuz/tests/fakepackager.py
index 162aa15..ebafbb8 100644
--- a/lib/lp/soyuz/tests/fakepackager.py
+++ b/lib/lp/soyuz/tests/fakepackager.py
@@ -22,7 +22,7 @@ from zope.component import getUtility
 from lp.archiveuploader.nascentupload import NascentUpload
 from lp.archiveuploader.uploadpolicy import findPolicyByName
 from lp.registry.interfaces.distribution import IDistributionSet
-from lp.services.gpg.interfaces import IGPGHandler
+from lp.services.gpg.interfaces import IGPGHandler, get_gpg_path
 from lp.services.log.logger import BufferLogger
 from lp.soyuz.enums import PackageUploadStatus
 from lp.testing.gpgkeys import import_secret_test_key
@@ -394,6 +394,7 @@ class FakePackager:
                 self.gpg_key_fingerprint is not None
             ), "Cannot build signed packages because the key is not set."
             debuild_options.append("-k%s" % self.gpg_key_fingerprint)
+            debuild_options.append("-p%s" % get_gpg_path())
 
         if include_orig:
             debuild_options.append("-sa")
diff --git a/lib/lp/testing/gpgkeys/data/test@xxxxxxxxxxxxxxxxx b/lib/lp/testing/gpgkeys/data/test@xxxxxxxxxxxxxxxxx
index 4eacac1..ea3d992 100644
--- a/lib/lp/testing/gpgkeys/data/test@xxxxxxxxxxxxxxxxx
+++ b/lib/lp/testing/gpgkeys/data/test@xxxxxxxxxxxxxxxxx
@@ -1,5 +1,4 @@
 -----BEGIN PGP PUBLIC KEY BLOCK-----
-Version: GnuPG v1.4.9 (GNU/Linux)
 
 mQGiBEJdmOcRBADkNJPTBuCIefBdRAhvWyD9SSVHh8GHQWS7l9sRLEsirQkKz1yB
 pjmskVK57sa9G8lamu15aj6nGOPf52oNIFjYcgrsbca65xOgP6KXHDDzv7rP27d/
@@ -12,18 +11,11 @@ pJQUChXxh3jDxMhNlNTxp9EKt8kI/mZGpzg3Q0oWQt7elSTCftVa97PYgLtPyh2b
 mA5VAQ11NCzfp3ym2JQRI6RMl6twSHZS/Dy/dDCPTK1gCAQKw7QrU2FtcGxlIFBl
 cnNvbiA8c2FtcGxlLnBlcnNvbkBjYW5vbmljYWwuY29tPoheBBMRAgAeBQJC0tXh
 AhsDBgsJCAcDAgMVAgMDFgIBAh4BAheAAAoJELork4nf0gVDKFMAoK2KaBBi+Y4V
-yReUQUo6ZK+qG+5LAJ9aO35LHBHYJkg52BSLsXNV5Bb/V4hGBBMRAgAGBQJDFWvr
-AAoJEI5+twhsZKjF3cUAoJu4yUtm6TXN1wzjl/RbhwaUL6lWAJ9wc9t92MpffbL1
-KfNM6LghI6ebZbQ2U2FtcGxlIFBlcnNvbiAocmV2b2tlZCkgPHNhbXBsZS5yZXZv
-a2VkQGNhbm9uaWNhbC5jb20+iEkEMBECAAkFAkLTAQ0CHSAACgkQuiuTid/SBUOR
-0QCgtLd3xoYpIHm3ansN4W1YrP1GvgMAnAvrmXORjansHdqoVbzab4r+OkhtiF4E
-ExECAB4FAkLS+rcCGwMGCwkIBwMCAxUCAwMWAgECHgECF4AACgkQuiuTid/SBUNW
-IgCcCQgN2IL3nDSjQbZPiCRZPfxAgMcAnj2j7Pcv/lYPRh2f+6AjEv+8z3AVtCJT
-YW1wbGUgUGVyc29uIDx0ZXN0QGNhbm9uaWNhbC5jb20+iF4EExECAB4FAkJdmOcC
-GwMGCwkIBwMCAxUCAwMWAgECHgECF4AACgkQuiuTid/SBUNH9wCdHos9BQYiJB2h
-XBU9erVtzEYFqpwAn1jD5TWgoa1ps1CxZf68SN9eohMBiEYEExECAAYFAkMVa+0A
-CgkQjn63CGxkqMWbagCgihA/L3W6ZQyJJlpIn7KeR+oFDxsAnjEgpY0vFH0tcKHX
-qK0GdDe1MfRviF4EExECAB4FAkJdmOcCGwMGCwkIBwMCAxUCAwMWAgECHgECF4AA
+yReUQUo6ZK+qG+5LAJ9aO35LHBHYJkg52BSLsXNV5Bb/V7Q2U2FtcGxlIFBlcnNv
+biAocmV2b2tlZCkgPHNhbXBsZS5yZXZva2VkQGNhbm9uaWNhbC5jb20+iEkEMBEC
+AAkFAkLTAQ0CHSAACgkQuiuTid/SBUOR0QCgtLd3xoYpIHm3ansN4W1YrP1GvgMA
+nAvrmXORjansHdqoVbzab4r+OkhttCJTYW1wbGUgUGVyc29uIDx0ZXN0QGNhbm9u
+aWNhbC5jb20+iF4EExECAB4FAkJdmOcCGwMGCwkIBwMCAxUCAwMWAgECHgECF4AA
 CgkQuiuTid/SBUNH9wCdG+YqQqcC3g6VYH3fs1j+92euVWoAn2lwpWkyWlhDRmdt
 HEaIupSOl/ZXuQENBEJdmOkQBADME8ONMc4DG5UcMKgv5zp5aXh8wjxQNi5Fs3Y0
 emPpz8eBBQcaHVGOe8hlgwvNlYMx9UmK4QyFZz03KCKFC1yboDjLUljOTjL2VKVE
@@ -44,18 +36,6 @@ aLtFyrwxrc72M/ywGTwy6ld0gnk0ehierMQoiaw5Tz7DvZHyc3UNsCoefIu+42/4
 r5jiwubQ7zebiJEEGBECAAkCGwIFAkle+W4AUkcgBBkRAgAGBQJC7YoZAAoJENsl
 l1YCul72Kk4AnjK1epKVH9PULlNW+MBs2wk5WskDAKCtBcwsqSX1ioY3qfYDxp9H
 r54A/wkQuiuTid/SBUOdtACfQOBN/qOqfd1RZUS2ClgskQ6Lj1gAn1PfyUE0IPyO
-vTCjK5DHonYE3RGYuQGiBEcE3OwRBAC6hpmpLoOt5I93ag2+TJB6kCS6M5bnxPOc
-78INa1907P/abfLOLLORlLy8fWWP3bsvooUxcmOVEGcqeGWPmDIiti4yBaOYyh73
-yhvRcwPSA5QItayLvmaxisEkeEzimCgpfO8Mn6nwV0sfNqGB+pfKhumj4HvChNtj
-0NjgaP85rwCgyFGhn+oyC6u8scemM7RbkM1B6JMD/iYh7rKna7br8/PIO6odeiCg
-6As/0FAsdIO2n9IT4My+HG7vTaHpFzjaRFCVv9AhtcEIZKbnfF84fckcVL0nDuEe
-jji03lnC5eGP8SO9V+xNNkGEKd4FKwSwHv2M0NyRTB3xzhsloNWH/u5ICFDbyF0Z
-JZAxkcBaCOayXiCsGjroBAC4Bxd+BZPxBNF1SMiwONS5i+hOPuTaOEaznOTATGw+
-aHwvSdYXq3U4n9wWZDNNLPlo5N0UAXOdDHAhc9dAeWqadExXTS8/r/ixK27Bheev
-x6ek5u1g+p72hFjGxRBtBIVOjKqXUVtVS1BYc1WBAOqk1h+wE4hm75y0g2BW4K5N
-64iRBBgRAgAJBQJHBNzsAhsCAFIJELork4nf0gVDRyAEGRECAAYFAkcE3OwACgkQ
-JGAU/m192yUzAQCgvh7ZenOdUeLO9ww+lZv62Hvy2Z4An3tUDMkgC5n+vfE7XbbF
-oC0u7EFkwAwAmgNfoEH3fcrpV0ZQxyDaTdlb4bL3AKCc4BDXu7gsrQ9F3gPEZ9GJ
-/GM63Q==
-=k5fr
+vTCjK5DHonYE3RGY
+=wc0B
 -----END PGP PUBLIC KEY BLOCK-----

Follow ups