← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~apw/launchpad/signing-gpg-sign-checksum-files into lp:launchpad

 

Andy Whitcroft has proposed merging lp:~apw/launchpad/signing-gpg-sign-checksum-files into lp:launchpad.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)
Related bugs:
  Bug #1285919 in Launchpad itself: "uefi archive files don't have signed checksums"
  https://bugs.launchpad.net/launchpad/+bug/1285919
  Bug #1577736 in Launchpad itself: "Expand archive signing to kernel modules"
  https://bugs.launchpad.net/launchpad/+bug/1577736

For more details, see:
https://code.launchpad.net/~apw/launchpad/signing-gpg-sign-checksum-files/+merge/297149
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~apw/launchpad/signing-gpg-sign-checksum-files into lp:launchpad.
=== modified file 'lib/lp/archivepublisher/archivesigningkey.py'
--- lib/lp/archivepublisher/archivesigningkey.py	2016-03-23 17:55:39 +0000
+++ lib/lp/archivepublisher/archivesigningkey.py	2016-06-13 05:30:16 +0000
@@ -144,3 +144,20 @@
             os.path.join(suite_path, 'InRelease'), 'w')
         inline_release_file.write(inline_release)
         inline_release_file.close()
+
+    def signFile(self, path):
+        """See `IArchiveSigningKey`."""
+        assert self.archive.signing_key is not None, (
+            "No signing key available for %s" % self.archive.displayname)
+
+        secret_key_export = open(
+            self.getPathForSecretKey(self.archive.signing_key)).read()
+        gpghandler = getUtility(IGPGHandler)
+        secret_key = gpghandler.importSecretKey(secret_key_export)
+
+        file_content = open(path).read()
+        signature = gpghandler.signContent(
+            file_content, secret_key, mode=gpgme.SIG_MODE_DETACH)
+
+        with open(os.path.join(path + '.gpg'), 'w') as signature_file:
+            signature_file.write(signature)

=== modified file 'lib/lp/archivepublisher/customupload.py'
--- lib/lp/archivepublisher/customupload.py	2016-05-23 10:18:21 +0000
+++ lib/lp/archivepublisher/customupload.py	2016-06-13 05:30:16 +0000
@@ -123,11 +123,11 @@
         self.tmpdir = None
         self.logger = logger
 
-    def process(self, pubconf, tarfile_path, suite):
+    def process(self, archive, tarfile_path, suite):
         """Process the upload and install it into the archive."""
         self.tarfile_path = tarfile_path
         try:
-            self.setTargetDirectory(pubconf, tarfile_path, suite)
+            self.setTargetDirectory(archive, tarfile_path, suite)
             self.checkForConflicts()
             self.extract()
             self.installFiles()
@@ -147,7 +147,7 @@
         """Set instance variables based on decomposing the filename."""
         raise NotImplementedError
 
-    def setTargetDirectory(self, pubconf, tarfile_path, suite):
+    def setTargetDirectory(self, archive, tarfile_path, suite):
         """Set self.targetdir based on parameters.
 
         This should also set self.version and self.arch (if applicable) as a

=== modified file 'lib/lp/archivepublisher/ddtp_tarball.py'
--- lib/lp/archivepublisher/ddtp_tarball.py	2016-05-23 10:18:21 +0000
+++ lib/lp/archivepublisher/ddtp_tarball.py	2016-06-13 05:30:16 +0000
@@ -18,6 +18,7 @@
 
 import os
 
+from lp.archivepublisher.config import getPubConfig
 from lp.archivepublisher.customupload import CustomUpload
 
 
@@ -58,8 +59,9 @@
         _, self.component, self.version = self.parsePath(tarfile_path)
         self.arch = None
 
-    def setTargetDirectory(self, pubconf, tarfile_path, suite):
+    def setTargetDirectory(self, archive, tarfile_path, suite):
         self.setComponents(tarfile_path)
+        pubconf = getPubConfig(archive)
         self.targetdir = os.path.join(
             pubconf.archiveroot, 'dists', suite, self.component)
 

=== modified file 'lib/lp/archivepublisher/debian_installer.py'
--- lib/lp/archivepublisher/debian_installer.py	2016-05-23 10:18:21 +0000
+++ lib/lp/archivepublisher/debian_installer.py	2016-06-13 05:30:16 +0000
@@ -15,6 +15,7 @@
 import os
 import shutil
 
+from lp.archivepublisher.config import getPubConfig
 from lp.archivepublisher.customupload import CustomUpload
 
 
@@ -50,8 +51,9 @@
     def setComponents(self, tarfile_path):
         _, self.version, self.arch = self.parsePath(tarfile_path)
 
-    def setTargetDirectory(self, pubconf, tarfile_path, suite):
+    def setTargetDirectory(self, archive, tarfile_path, suite):
         self.setComponents(tarfile_path)
+        pubconf = getPubConfig(archive)
         self.targetdir = os.path.join(
             pubconf.archiveroot, 'dists', suite, 'main',
             'installer-%s' % self.arch)

=== modified file 'lib/lp/archivepublisher/dist_upgrader.py'
--- lib/lp/archivepublisher/dist_upgrader.py	2016-05-23 10:18:21 +0000
+++ lib/lp/archivepublisher/dist_upgrader.py	2016-06-13 05:30:16 +0000
@@ -11,6 +11,7 @@
 
 import os
 
+from lp.archivepublisher.config import getPubConfig
 from lp.archivepublisher.customupload import CustomUpload
 from lp.archivepublisher.debversion import (
     BadUpstreamError,
@@ -65,8 +66,9 @@
     def setComponents(self, tarfile_path):
         _, self.version, self.arch = self.parsePath(tarfile_path)
 
-    def setTargetDirectory(self, pubconf, tarfile_path, suite):
+    def setTargetDirectory(self, archive, tarfile_path, suite):
         self.setComponents(tarfile_path)
+        pubconf = getPubConfig(archive)
         self.targetdir = os.path.join(
             pubconf.archiveroot, 'dists', suite, 'main',
             'dist-upgrader-%s' % self.arch)

=== modified file 'lib/lp/archivepublisher/interfaces/archivesigningkey.py'
--- lib/lp/archivepublisher/interfaces/archivesigningkey.py	2015-10-21 09:37:08 +0000
+++ lib/lp/archivepublisher/interfaces/archivesigningkey.py	2016-06-13 05:30:16 +0000
@@ -85,3 +85,10 @@
         :raises AssertionError: if the context archive has no `signing_key`
             or there is no Release file in the given suite.
         """
+
+    def signFile(path):
+        """Sign the corresponding file.
+
+        :param path: path within dists to sign with the archive key.
+        :raises AssertionError: if the context archive has no `signing_key`.
+        """

=== modified file 'lib/lp/archivepublisher/publishing.py'
--- lib/lp/archivepublisher/publishing.py	2016-06-06 17:00:54 +0000
+++ lib/lp/archivepublisher/publishing.py	2016-06-13 05:30:16 +0000
@@ -3,6 +3,7 @@
 
 __all__ = [
     'cannot_modify_suite',
+    'DirectoryHash',
     'FORMAT_TO_SUBCOMPONENT',
     'GLOBAL_PUBLISHER_LOCK',
     'Publisher',
@@ -1477,16 +1478,17 @@
 class DirectoryHash:
     """Represents a directory hierarchy for hashing."""
 
-    def __init__(self, root, tmpdir, log):
+    def __init__(self, root, tmpdir, signer=None, logger=None):
         self.root = root
         self.tmpdir = tmpdir
-        self.log = log
+        self.logger = logger
+        self.signer = signer
         self.checksum_hash = []
 
         for usable in self._usable_archive_hashes:
-            checksum_file = os.path.join(self.root, usable.dh_name)
-            self.checksum_hash.append(
-                (RepositoryIndexFile(checksum_file, self.tmpdir), usable))
+            csum_path = os.path.join(self.root, usable.dh_name)
+            self.checksum_hash.append((csum_path,
+                RepositoryIndexFile(csum_path, self.tmpdir), usable))
 
     def __enter__(self):
         return self
@@ -1504,7 +1506,7 @@
         """Add a path to be checksummed."""
         hashes = [
             (checksum_file, archive_hash.hash_factory())
-            for (checksum_file, archive_hash) in self.checksum_hash]
+            for (_, checksum_file, archive_hash) in self.checksum_hash]
         with open(path, 'rb') as in_file:
             for chunk in iter(lambda: in_file.read(256 * 1024), ""):
                 for (checksum_file, hashobj) in hashes:
@@ -1521,5 +1523,7 @@
                 self.add(os.path.join(dirpath, filename))
 
     def close(self):
-        for (checksum_file, archive_hash) in self.checksum_hash:
+        for (checksum_path, checksum_file, archive_hash) in self.checksum_hash:
             checksum_file.close()
+            if self.signer:
+                self.signer.signFile(checksum_path)

=== modified file 'lib/lp/archivepublisher/rosetta_translations.py'
--- lib/lp/archivepublisher/rosetta_translations.py	2016-05-23 10:14:39 +0000
+++ lib/lp/archivepublisher/rosetta_translations.py	2016-06-13 05:30:16 +0000
@@ -139,7 +139,7 @@
         """Sets the package name parsed from the lfa filename."""
         self.package_name = self.parsePath(tarfile_name)[0]
 
-    def setTargetDirectory(self, pubconf, tarfile_path, distroseries):
+    def setTargetDirectory(self, archive, tarfile_path, distroseries):
         pass
 
     @classmethod

=== modified file 'lib/lp/archivepublisher/signing.py'
--- lib/lp/archivepublisher/signing.py	2016-06-10 13:21:12 +0000
+++ lib/lp/archivepublisher/signing.py	2016-06-13 05:30:16 +0000
@@ -26,7 +26,14 @@
 import tempfile
 import textwrap
 
+from lp.archivepublisher.config import getPubConfig
 from lp.archivepublisher.customupload import CustomUpload
+<<<<<<< TREE
+=======
+from lp.archivepublisher.interfaces.archivesigningkey import (
+    IArchiveSigningKey,
+    )
+>>>>>>> MERGE-SOURCE
 from lp.services.osutils import remove_if_exists
 from lp.soyuz.interfaces.queue import CustomUploadError
 
@@ -79,7 +86,9 @@
         self.package, self.version, self.arch = self.parsePath(
             tarfile_path)
 
-    def setTargetDirectory(self, pubconf, tarfile_path, suite):
+    def setTargetDirectory(self, archive, tarfile_path, suite):
+        self.archive = archive
+        pubconf = getPubConfig(archive)
         if pubconf.signingroot is None:
             if self.logger is not None:
                 self.logger.warning(
@@ -146,14 +155,6 @@
         except ValueError:
             return None
 
-    def getArchiveOwnerAndName(self):
-        # XXX apw 2016-05-18: pull out the PPA owner and name to seed key CN
-        archive_name = os.path.dirname(self.archiveroot)
-        owner_name = os.path.basename(os.path.dirname(archive_name))
-        archive_name = os.path.basename(archive_name)
-
-        return owner_name + ' ' + archive_name
-
     def callLog(self, description, cmdl):
         status = subprocess.call(cmdl)
         if status != 0:
@@ -200,7 +201,8 @@
         if not os.path.exists(directory):
             os.makedirs(directory)
 
-        common_name = '/CN=PPA ' + self.getArchiveOwnerAndName() + '/'
+        common_name = '/CN=PPA %s %s/' % (
+            self.archive.owner.name, self.archive.name)
 
         old_mask = os.umask(0o077)
         try:
@@ -236,8 +238,6 @@
         old_mask = os.umask(0o077)
         try:
             with tempfile.NamedTemporaryFile(suffix='.keygen') as tf:
-                common_name = self.getArchiveOwnerAndName()
-
                 genkey_text = textwrap.dedent("""\
                     [ req ]
                     default_bits = 4096
@@ -247,14 +247,14 @@
                     x509_extensions = myexts
 
                     [ req_distinguished_name ]
-                    CN = /CN=PPA """ + common_name + """ kmod/
+                    CN = /CN=PPA %s %s kmod/
 
                     [ myexts ]
                     basicConstraints=critical,CA:FALSE
                     keyUsage=digitalSignature
                     subjectKeyIdentifier=hash
                     authorityKeyIdentifier=keyid
-                    """)
+                    """ % (self.archive.owner.name, self.archive.name))
 
                 print(genkey_text, file=tf)
 
@@ -334,7 +334,12 @@
             self.convertToTarball()
 
         versiondir = os.path.join(self.tmpdir, self.version)
-        with DirectoryHash(versiondir, self.tmpdir, self.logger) as hasher:
+
+        signer = None
+        if self.archive.signing_key:
+            signer = IArchiveSigningKey(self.archive)
+        with DirectoryHash(versiondir, self.tmpdir, signer,
+                           self.logger) as hasher:
             hasher.add_dir(versiondir)
 
     def shouldInstall(self, filename):

=== modified file 'lib/lp/archivepublisher/tests/test_ddtp_tarball.py'
--- lib/lp/archivepublisher/tests/test_ddtp_tarball.py	2016-05-23 10:14:39 +0000
+++ lib/lp/archivepublisher/tests/test_ddtp_tarball.py	2016-06-13 05:30:16 +0000
@@ -9,23 +9,30 @@
 
 import os
 
+from zope.component import getUtility
+
+from lp.archivepublisher.config import getPubConfig
 from lp.archivepublisher.ddtp_tarball import DdtpTarballUpload
+from lp.archivepublisher.interfaces.publisherconfig import IPublisherConfigSet
 from lp.services.tarfile_helpers import LaunchpadWriteTarFile
-from lp.testing import TestCase
-
-
-class FakeConfig:
-    """A fake publisher configuration."""
-    def __init__(self, archiveroot):
-        self.archiveroot = archiveroot
-
-
-class TestDdtpTarball(TestCase):
+from lp.soyuz.enums import ArchivePurpose
+from lp.testing import TestCaseWithFactory
+from lp.testing.layers import ZopelessDatabaseLayer
+
+
+class TestDdtpTarball(TestCaseWithFactory):
+
+    layer = ZopelessDatabaseLayer
 
     def setUp(self):
         super(TestDdtpTarball, self).setUp()
         self.temp_dir = self.makeTemporaryDirectory()
-        self.pubconf = FakeConfig(self.temp_dir)
+        self.distro = self.factory.makeDistribution()
+        db_pubconf = getUtility(IPublisherConfigSet).getByDistribution(
+            self.distro)
+        db_pubconf.root_dir = unicode(self.temp_dir)
+        self.archive = self.factory.makeArchive(
+            distribution=self.distro, purpose=ArchivePurpose.PRIMARY)
         self.suite = "distroseries"
         # CustomUpload.installFiles requires a umask of 0o022.
         old_umask = os.umask(0o022)
@@ -35,21 +42,22 @@
         self.path = os.path.join(
             self.temp_dir, "translations_main_%s.tar.gz" % version)
         self.buffer = open(self.path, "wb")
-        self.archive = LaunchpadWriteTarFile(self.buffer)
+        self.tarfile = LaunchpadWriteTarFile(self.buffer)
 
     def process(self):
-        self.archive.close()
+        self.tarfile.close()
         self.buffer.close()
-        DdtpTarballUpload().process(self.pubconf, self.path, self.suite)
+        DdtpTarballUpload().process(self.archive, self.path, self.suite)
 
     def getTranslationsPath(self, filename):
+        pubconf = getPubConfig(self.archive)
         return os.path.join(
-            self.temp_dir, "dists", self.suite, "main", "i18n", filename)
+            pubconf.archiveroot, "dists", self.suite, "main", "i18n", filename)
 
     def test_basic(self):
         # Processing a simple correct tar file works.
         self.openArchive("20060728")
-        self.archive.add_file("i18n/Translation-de", "")
+        self.tarfile.add_file("i18n/Translation-de", "")
         self.process()
         self.assertTrue(os.path.exists(
             self.getTranslationsPath("Translation-de")))
@@ -57,8 +65,8 @@
     def test_ignores_empty_directories(self):
         # Empty directories in the tarball are not extracted.
         self.openArchive("20060728")
-        self.archive.add_file("i18n/Translation-de", "")
-        self.archive.add_directory("i18n/foo")
+        self.tarfile.add_file("i18n/Translation-de", "")
+        self.tarfile.add_directory("i18n/foo")
         self.process()
         self.assertTrue(os.path.exists(
             self.getTranslationsPath("Translation-de")))
@@ -68,15 +76,15 @@
         # If a DDTP tarball only contains a subset of published translation
         # files, these are updated and the rest are left untouched.
         self.openArchive("20060728")
-        self.archive.add_file("i18n/Translation-bn", "bn")
-        self.archive.add_file("i18n/Translation-ca", "ca")
+        self.tarfile.add_file("i18n/Translation-bn", "bn")
+        self.tarfile.add_file("i18n/Translation-ca", "ca")
         self.process()
         with open(self.getTranslationsPath("Translation-bn")) as bn_file:
             self.assertEqual("bn", bn_file.read())
         with open(self.getTranslationsPath("Translation-ca")) as ca_file:
             self.assertEqual("ca", ca_file.read())
         self.openArchive("20060817")
-        self.archive.add_file("i18n/Translation-bn", "new bn")
+        self.tarfile.add_file("i18n/Translation-bn", "new bn")
         self.process()
         with open(self.getTranslationsPath("Translation-bn")) as bn_file:
             self.assertEqual("new bn", bn_file.read())
@@ -90,7 +98,7 @@
         # into place, so making this work requires special care.  Test that
         # that care has been taken.
         self.openArchive("20060728")
-        self.archive.add_file("i18n/Translation-ca", "")
+        self.tarfile.add_file("i18n/Translation-ca", "")
         self.process()
         ca = self.getTranslationsPath("Translation-ca")
         bn = self.getTranslationsPath("Translation-bn")
@@ -99,7 +107,7 @@
         self.assertEqual(2, os.stat(bn).st_nlink)
         self.assertEqual(2, os.stat(ca).st_nlink)
         self.openArchive("20060817")
-        self.archive.add_file("i18n/Translation-bn", "break hard link")
+        self.tarfile.add_file("i18n/Translation-bn", "break hard link")
         self.process()
         with open(bn) as bn_file:
             self.assertEqual("break hard link", bn_file.read())

=== modified file 'lib/lp/archivepublisher/tests/test_debian_installer.py'
--- lib/lp/archivepublisher/tests/test_debian_installer.py	2016-05-23 10:14:39 +0000
+++ lib/lp/archivepublisher/tests/test_debian_installer.py	2016-06-13 05:30:16 +0000
@@ -9,27 +9,34 @@
 
 import os
 
+from zope.component import getUtility
+
+from lp.archivepublisher.config import getPubConfig
 from lp.archivepublisher.customupload import (
     CustomUploadAlreadyExists,
     CustomUploadBadUmask,
     )
 from lp.archivepublisher.debian_installer import DebianInstallerUpload
+from lp.archivepublisher.interfaces.publisherconfig import IPublisherConfigSet
 from lp.services.tarfile_helpers import LaunchpadWriteTarFile
-from lp.testing import TestCase
-
-
-class FakeConfig:
-    """A fake publisher configuration."""
-    def __init__(self, archiveroot):
-        self.archiveroot = archiveroot
-
-
-class TestDebianInstaller(TestCase):
+from lp.soyuz.enums import ArchivePurpose
+from lp.testing import TestCaseWithFactory
+from lp.testing.layers import ZopelessDatabaseLayer
+
+
+class TestDebianInstaller(TestCaseWithFactory):
+
+    layer = ZopelessDatabaseLayer
 
     def setUp(self):
         super(TestDebianInstaller, self).setUp()
         self.temp_dir = self.makeTemporaryDirectory()
-        self.pubconf = FakeConfig(self.temp_dir)
+        self.distro = self.factory.makeDistribution()
+        db_pubconf = getUtility(IPublisherConfigSet).getByDistribution(
+            self.distro)
+        db_pubconf.root_dir = unicode(self.temp_dir)
+        self.archive = self.factory.makeArchive(
+            distribution=self.distro, purpose=ArchivePurpose.PRIMARY)
         self.suite = "distroseries"
         # CustomUpload.installFiles requires a umask of 0o022.
         old_umask = os.umask(0o022)
@@ -42,24 +49,25 @@
             self.temp_dir,
             "debian-installer-images_%s_%s.tar.gz" % (self.version, self.arch))
         self.buffer = open(self.path, "wb")
-        self.archive = LaunchpadWriteTarFile(self.buffer)
+        self.tarfile = LaunchpadWriteTarFile(self.buffer)
 
     def addFile(self, path, contents):
-        self.archive.add_file(
+        self.tarfile.add_file(
             "installer-%s/%s/%s" % (self.arch, self.version, path), contents)
 
     def addSymlink(self, path, target):
-        self.archive.add_symlink(
+        self.tarfile.add_symlink(
             "installer-%s/%s/%s" % (self.arch, self.version, path), target)
 
     def process(self):
-        self.archive.close()
+        self.tarfile.close()
         self.buffer.close()
-        DebianInstallerUpload().process(self.pubconf, self.path, self.suite)
+        DebianInstallerUpload().process(self.archive, self.path, self.suite)
 
     def getInstallerPath(self, versioned_filename=None):
+        pubconf = getPubConfig(self.archive)
         installer_path = os.path.join(
-            self.temp_dir, "dists", self.suite, "main",
+            pubconf.archiveroot, "dists", self.suite, "main",
             "installer-%s" % self.arch)
         if versioned_filename is not None:
             installer_path = os.path.join(

=== modified file 'lib/lp/archivepublisher/tests/test_dist_upgrader.py'
--- lib/lp/archivepublisher/tests/test_dist_upgrader.py	2016-05-23 10:14:39 +0000
+++ lib/lp/archivepublisher/tests/test_dist_upgrader.py	2016-06-13 05:30:16 +0000
@@ -9,6 +9,9 @@
 
 import os
 
+from zope.component import getUtility
+
+from lp.archivepublisher.config import getPubConfig
 from lp.archivepublisher.customupload import (
     CustomUploadAlreadyExists,
     CustomUploadBadUmask,
@@ -17,8 +20,11 @@
     DistUpgraderBadVersion,
     DistUpgraderUpload,
     )
+from lp.archivepublisher.interfaces.publisherconfig import IPublisherConfigSet
 from lp.services.tarfile_helpers import LaunchpadWriteTarFile
-from lp.testing import TestCase
+from lp.soyuz.enums import ArchivePurpose
+from lp.testing import TestCaseWithFactory
+from lp.testing.layers import ZopelessDatabaseLayer
 
 
 class FakeConfig:
@@ -27,12 +33,19 @@
         self.archiveroot = archiveroot
 
 
-class TestDistUpgrader(TestCase):
+class TestDistUpgrader(TestCaseWithFactory):
+
+    layer = ZopelessDatabaseLayer
 
     def setUp(self):
         super(TestDistUpgrader, self).setUp()
         self.temp_dir = self.makeTemporaryDirectory()
-        self.pubconf = FakeConfig(self.temp_dir)
+        self.distro = self.factory.makeDistribution()
+        db_pubconf = getUtility(IPublisherConfigSet).getByDistribution(
+            self.distro)
+        db_pubconf.root_dir = unicode(self.temp_dir)
+        self.archive = self.factory.makeArchive(
+            distribution=self.distro, purpose=ArchivePurpose.PRIMARY)
         self.suite = "distroseries"
         # CustomUpload.installFiles requires a umask of 0o022.
         old_umask = os.umask(0o022)
@@ -42,41 +55,43 @@
         self.path = os.path.join(
             self.temp_dir, "dist-upgrader_%s_all.tar.gz" % version)
         self.buffer = open(self.path, "wb")
-        self.archive = LaunchpadWriteTarFile(self.buffer)
+        self.tarfile = LaunchpadWriteTarFile(self.buffer)
 
     def process(self):
-        self.archive.close()
+        self.tarfile.close()
         self.buffer.close()
-        DistUpgraderUpload().process(self.pubconf, self.path, self.suite)
+        DistUpgraderUpload().process(self.archive, self.path, self.suite)
 
     def getUpgraderPath(self):
+        pubconf = getPubConfig(self.archive)
         return os.path.join(
-            self.temp_dir, "dists", self.suite, "main", "dist-upgrader-all")
+            pubconf.archiveroot, "dists", self.suite, "main",
+            "dist-upgrader-all")
 
     def test_basic(self):
         # Processing a simple correct tar file works.
         self.openArchive("20060302.0120")
-        self.archive.add_file("20060302.0120/hello", "world")
+        self.tarfile.add_file("20060302.0120/hello", "world")
         self.process()
 
     def test_already_exists(self):
         # If the target directory already exists, processing fails.
         self.openArchive("20060302.0120")
-        self.archive.add_file("20060302.0120/hello", "world")
+        self.tarfile.add_file("20060302.0120/hello", "world")
         os.makedirs(os.path.join(self.getUpgraderPath(), "20060302.0120"))
         self.assertRaises(CustomUploadAlreadyExists, self.process)
 
     def test_bad_umask(self):
         # The umask must be 0o022 to avoid incorrect permissions.
         self.openArchive("20060302.0120")
-        self.archive.add_file("20060302.0120/file", "foo")
+        self.tarfile.add_file("20060302.0120/file", "foo")
         os.umask(0o002)  # cleanup already handled by setUp
         self.assertRaises(CustomUploadBadUmask, self.process)
 
     def test_current_symlink(self):
         # A "current" symlink is created to the last version.
         self.openArchive("20060302.0120")
-        self.archive.add_file("20060302.0120/hello", "world")
+        self.tarfile.add_file("20060302.0120/hello", "world")
         self.process()
         upgrader_path = self.getUpgraderPath()
         self.assertContentEqual(
@@ -91,7 +106,7 @@
     def test_bad_version(self):
         # Bad versions in the tarball are refused.
         self.openArchive("20070219.1234")
-        self.archive.add_file("foobar/foobar/dapper.tar.gz", "")
+        self.tarfile.add_file("foobar/foobar/dapper.tar.gz", "")
         self.assertRaises(DistUpgraderBadVersion, self.process)
 
     def test_getSeriesKey_extracts_architecture(self):

=== modified file 'lib/lp/archivepublisher/tests/test_publisher.py'
--- lib/lp/archivepublisher/tests/test_publisher.py	2016-06-06 17:13:42 +0000
+++ lib/lp/archivepublisher/tests/test_publisher.py	2016-06-13 05:30:16 +0000
@@ -94,10 +94,7 @@
 from lp.soyuz.interfaces.archive import IArchiveSet
 from lp.soyuz.interfaces.archivefile import IArchiveFileSet
 from lp.soyuz.tests.test_publishing import TestNativePublishingBase
-from lp.testing import (
-    TestCase,
-    TestCaseWithFactory,
-    )
+from lp.testing import TestCaseWithFactory
 from lp.testing.fakemethod import FakeMethod
 from lp.testing.gpgkeys import gpgkeysdir
 from lp.testing.keyserver import KeyServerTac
@@ -105,6 +102,7 @@
     LaunchpadZopelessLayer,
     ZopelessDatabaseLayer,
     )
+from lp.archivepublisher.interfaces.publisherconfig import IPublisherConfigSet
 
 
 RELEASE = PackagePublishingPocket.RELEASE
@@ -3167,8 +3165,8 @@
         self.assertEqual([], self.makePublisher(partner).subcomponents)
 
 
-class TestDirectoryHash(TestCase):
-    """Unit tests for DirectoryHash object."""
+class TestDirectoryHashHelpers(TestCaseWithFactory):
+    """Helper functions for DirectoryHash testing."""
 
     def createTestFile(self, path, content):
         with open(path, "w") as tfd:
@@ -3194,6 +3192,23 @@
                         file_list.append(line.strip().split(' '))
         return result
 
+    def fetchSigs(self, rootdir):
+        result = {}
+        for dh_file in self.all_hash_files:
+            checksum_sig = os.path.join(rootdir, dh_file) + '.gpg'
+            if os.path.exists(checksum_sig):
+                with open(checksum_sig, "r") as sfd:
+                    for line in sfd:
+                        sig_lines = result.setdefault(dh_file, [])
+                        sig_lines.append(line)
+        return result
+
+
+class TestDirectoryHash(TestDirectoryHashHelpers):
+    """Unit tests for DirectoryHash object."""
+
+    layer = ZopelessDatabaseLayer
+
     def test_checksum_files_created(self):
         tmpdir = unicode(self.makeTemporaryDirectory())
         rootdir = unicode(self.makeTemporaryDirectory())
@@ -3202,7 +3217,7 @@
             checksum_file = os.path.join(rootdir, dh_file)
             self.assertFalse(os.path.exists(checksum_file))
 
-        with DirectoryHash(rootdir, tmpdir, None) as dh:
+        with DirectoryHash(rootdir, tmpdir, None):
             pass
 
         for dh_file in self.all_hash_files:
@@ -3226,7 +3241,7 @@
         test3_file = os.path.join(rootdir, "subdir1", "test3")
         test3_hash = self.createTestFile(test3_file, "test3")
 
-        with DirectoryHash(rootdir, tmpdir, None) as dh:
+        with DirectoryHash(rootdir, tmpdir) as dh:
             dh.add(test1_file)
             dh.add(test2_file)
             dh.add(test3_file)
@@ -3254,14 +3269,71 @@
         test3_file = os.path.join(rootdir, "subdir1", "test3")
         test3_hash = self.createTestFile(test3_file, "test3 dir")
 
-        with DirectoryHash(rootdir, tmpdir, None) as dh:
-            dh.add_dir(rootdir)
-
-        expected = {
-            'SHA256SUMS': MatchesSetwise(
-                Equals([test1_hash, "*test1"]),
-                Equals([test2_hash, "*test2"]),
-                Equals([test3_hash, "*subdir1/test3"]),
-            ),
-        }
-        self.assertThat(self.fetchSums(rootdir), MatchesDict(expected))
+        with DirectoryHash(rootdir, tmpdir) as dh:
+            dh.add_dir(rootdir)
+
+        expected = {
+            'SHA256SUMS': MatchesSetwise(
+                Equals([test1_hash, "*test1"]),
+                Equals([test2_hash, "*test2"]),
+                Equals([test3_hash, "*subdir1/test3"]),
+            ),
+        }
+        self.assertThat(self.fetchSums(rootdir), MatchesDict(expected))
+
+
+class TestDirectoryHashSigning(TestDirectoryHashHelpers):
+    """Unit tests for DirectoryHash object, signing functionality."""
+
+    layer = ZopelessDatabaseLayer
+
+    def setUp(self):
+        super(TestDirectoryHashSigning, self).setUp()
+        self.temp_dir = self.makeTemporaryDirectory()
+        self.distro = self.factory.makeDistribution()
+        db_pubconf = getUtility(IPublisherConfigSet).getByDistribution(
+            self.distro)
+        db_pubconf.root_dir = unicode(self.temp_dir)
+        self.archive = self.factory.makeArchive(
+            distribution=self.distro, purpose=ArchivePurpose.PRIMARY)
+        self.suite = "distroseries"
+
+        # Setup a keyserver so we can install the archive key.
+        tac = KeyServerTac()
+        tac.setUp()
+
+        key_path = os.path.join(gpgkeysdir, 'ppa-sample@xxxxxxxxxxxxxxxxx')
+        IArchiveSigningKey(self.archive).setSigningKey(key_path)
+
+        tac.tearDown()
+
+    def test_basic_directory_add_signed(self):
+        tmpdir = unicode(self.makeTemporaryDirectory())
+        rootdir = unicode(self.makeTemporaryDirectory())
+        test1_file = os.path.join(rootdir, "test1")
+        test1_hash = self.createTestFile(test1_file, "test1 dir")
+
+        test2_file = os.path.join(rootdir, "test2")
+        test2_hash = self.createTestFile(test2_file, "test2 dir")
+
+        os.mkdir(os.path.join(rootdir, "subdir1"))
+
+        test3_file = os.path.join(rootdir, "subdir1", "test3")
+        test3_hash = self.createTestFile(test3_file, "test3 dir")
+
+        signer = IArchiveSigningKey(self.archive)
+        with DirectoryHash(rootdir, tmpdir, signer=signer) as dh:
+            dh.add_dir(rootdir)
+
+        expected = {
+            'SHA256SUMS': MatchesSetwise(
+                Equals([test1_hash, "*test1"]),
+                Equals([test2_hash, "*test2"]),
+                Equals([test3_hash, "*subdir1/test3"]),
+            ),
+        }
+        self.assertThat(self.fetchSums(rootdir), MatchesDict(expected))
+        sig_content = self.fetchSigs(rootdir)
+        for dh_file in sig_content:
+            self.assertEqual(
+                sig_content[dh_file][0], '-----BEGIN PGP SIGNATURE-----\n')

=== modified file 'lib/lp/archivepublisher/tests/test_signing.py'
--- lib/lp/archivepublisher/tests/test_signing.py	2016-06-07 14:11:33 +0000
+++ lib/lp/archivepublisher/tests/test_signing.py	2016-06-13 05:30:16 +0000
@@ -10,19 +10,29 @@
 import tarfile
 
 from fixtures import MonkeyPatch
+from zope.component import getUtility
 
+from lp.archivepublisher.config import getPubConfig
 from lp.archivepublisher.customupload import (
     CustomUploadAlreadyExists,
     CustomUploadBadUmask,
     )
+from lp.archivepublisher.interfaces.archivesigningkey import (
+    IArchiveSigningKey,
+    )
+from lp.archivepublisher.interfaces.publisherconfig import IPublisherConfigSet
 from lp.archivepublisher.signing import (
     SigningUpload,
     UefiUpload,
     )
 from lp.services.osutils import write_file
 from lp.services.tarfile_helpers import LaunchpadWriteTarFile
-from lp.testing import TestCase
+from lp.soyuz.enums import ArchivePurpose
+from lp.testing import TestCaseWithFactory
 from lp.testing.fakemethod import FakeMethod
+from lp.testing.gpgkeys import gpgkeysdir
+from lp.testing.keyserver import KeyServerTac
+from lp.testing.layers import ZopelessDatabaseLayer
 
 
 class FakeMethodCallLog(FakeMethod):
@@ -74,49 +84,46 @@
         return self.callers.get(caller, 0)
 
 
-class FakeConfigPrimary:
-    """A fake publisher configuration for the main archive."""
-    def __init__(self, distroroot, signingroot):
-        self.distroroot = distroroot
-        self.signingroot = signingroot
-        self.archiveroot = os.path.join(self.distroroot, 'ubuntu')
-        self.signingautokey = False
-
-
-class FakeConfigCopy:
-    """A fake publisher configuration for a copy archive."""
-    def __init__(self, distroroot):
-        self.distroroot = distroroot
-        self.signingroot = None
-        self.archiveroot = os.path.join(self.distroroot, 'ubuntu')
-        self.signingautokey = False
-
-
-class FakeConfigPPA:
-    """A fake publisher configuration for a PPA."""
-    def __init__(self, distroroot, signingroot, owner, ppa):
-        self.distroroot = distroroot
-        self.signingroot = signingroot
-        self.archiveroot = os.path.join(self.distroroot, owner, ppa, 'ubuntu')
-        self.signingautokey = True
-
-
-class TestSigningHelpers(TestCase):
+class TestSigningHelpers(TestCaseWithFactory):
+
+    layer = ZopelessDatabaseLayer
 
     def setUp(self):
         super(TestSigningHelpers, self).setUp()
         self.temp_dir = self.makeTemporaryDirectory()
-        self.signing_dir = self.makeTemporaryDirectory()
-        self.pubconf = FakeConfigPrimary(self.temp_dir, self.signing_dir)
+        self.distro = self.factory.makeDistribution()
+        db_pubconf = getUtility(IPublisherConfigSet).getByDistribution(
+            self.distro)
+        db_pubconf.root_dir = unicode(self.temp_dir)
+        self.archive = self.factory.makeArchive(
+            distribution=self.distro, purpose=ArchivePurpose.PRIMARY)
+        self.signing_dir = os.path.join(
+            self.temp_dir, self.distro.name + "-signing")
         self.suite = "distroseries"
         # CustomUpload.installFiles requires a umask of 0o022.
         old_umask = os.umask(0o022)
         self.addCleanup(os.umask, old_umask)
 
     def setUpPPA(self):
-        self.pubconf = FakeConfigPPA(self.temp_dir, self.signing_dir,
-            'ubuntu-archive', 'testing')
-        self.testcase_cn = '/CN=PPA ubuntu-archive testing/'
+        self.pushConfig(
+            "personalpackagearchive", root=self.temp_dir,
+            signing_keys_root=self.temp_dir)
+        owner = self.factory.makePerson(name="signing-owner")
+        self.archive = self.factory.makeArchive(
+            distribution=self.distro, owner=owner, name="testing",
+            purpose=ArchivePurpose.PPA)
+        self.signing_dir = os.path.join(
+            self.temp_dir, "signing", "signing-owner", "testing")
+        self.testcase_cn = '/CN=PPA signing-owner testing/'
+
+    def setUpArchiveKey(self):
+        tac = KeyServerTac()
+        tac.setUp()
+
+        key_path = os.path.join(gpgkeysdir, 'ppa-sample@xxxxxxxxxxxxxxxxx')
+        IArchiveSigningKey(self.archive).setSigningKey(key_path)
+
+        tac.tearDown()
 
     def setUpUefiKeys(self, create=True):
         self.key = os.path.join(self.signing_dir, "uefi.key")
@@ -136,11 +143,11 @@
         self.path = os.path.join(
             self.temp_dir, "%s_%s_%s.tar.gz" % (loader_type, version, arch))
         self.buffer = open(self.path, "wb")
-        self.archive = LaunchpadWriteTarFile(self.buffer)
+        self.tarfile = LaunchpadWriteTarFile(self.buffer)
 
     def getDistsPath(self):
-        return os.path.join(self.pubconf.archiveroot, "dists",
-            self.suite, "main")
+        pubconf = getPubConfig(self.archive)
+        return os.path.join(pubconf.archiveroot, "dists", self.suite, "main")
 
 
 class TestSigning(TestSigningHelpers):
@@ -150,19 +157,19 @@
             "%s-%s" % (loader_type, arch))
 
     def process_emulate(self):
-        self.archive.close()
+        self.tarfile.close()
         self.buffer.close()
         upload = SigningUpload()
         # Under no circumstances is it safe to execute actual commands.
         self.fake_call = FakeMethod(result=0)
         upload.callLog = FakeMethodCallLog(upload=upload)
         self.useFixture(MonkeyPatch("subprocess.call", self.fake_call))
-        upload.process(self.pubconf, self.path, self.suite)
+        upload.process(self.archive, self.path, self.suite)
 
         return upload
 
     def process(self):
-        self.archive.close()
+        self.tarfile.close()
         self.buffer.close()
         upload = SigningUpload()
         upload.signUefi = FakeMethod()
@@ -170,7 +177,7 @@
         # Under no circumstances is it safe to execute actual commands.
         fake_call = FakeMethod(result=0)
         self.useFixture(MonkeyPatch("subprocess.call", fake_call))
-        upload.process(self.pubconf, self.path, self.suite)
+        upload.process(self.archive, self.path, self.suite)
         self.assertEqual(0, fake_call.call_count)
 
         return upload
@@ -178,10 +185,11 @@
     def test_archive_copy(self):
         # If there is no key/cert configuration, processing succeeds but
         # nothing is signed.
-        self.pubconf = FakeConfigCopy(self.temp_dir)
+        self.archive = self.factory.makeArchive(
+            distribution=self.distro, purpose=ArchivePurpose.COPY)
         self.openArchive("test", "1.0", "amd64")
-        self.archive.add_file("1.0/empty.efi", "")
-        self.archive.add_file("1.0/empty.ko", "")
+        self.tarfile.add_file("1.0/empty.efi", "")
+        self.tarfile.add_file("1.0/empty.ko", "")
         upload = self.process_emulate()
         self.assertEqual(0, upload.callLog.caller_count('UEFI keygen'))
         self.assertEqual(0, upload.callLog.caller_count('Kmod keygen key'))
@@ -193,8 +201,8 @@
         # If the configured key/cert are missing, processing succeeds but
         # nothing is signed.
         self.openArchive("test", "1.0", "amd64")
-        self.archive.add_file("1.0/empty.efi", "")
-        self.archive.add_file("1.0/empty.ko", "")
+        self.tarfile.add_file("1.0/empty.efi", "")
+        self.tarfile.add_file("1.0/empty.ko", "")
         upload = self.process_emulate()
         self.assertEqual(0, upload.callLog.caller_count('UEFI keygen'))
         self.assertEqual(0, upload.callLog.caller_count('Kmod keygen key'))
@@ -208,8 +216,8 @@
         self.setUpUefiKeys()
         self.setUpKmodKeys()
         self.openArchive("test", "1.0", "amd64")
-        self.archive.add_file("1.0/empty.efi", "")
-        self.archive.add_file("1.0/empty.ko", "")
+        self.tarfile.add_file("1.0/empty.efi", "")
+        self.tarfile.add_file("1.0/empty.ko", "")
         upload = self.process_emulate()
         self.assertEqual(0, upload.callLog.caller_count('UEFI keygen'))
         self.assertEqual(0, upload.callLog.caller_count('Kmod keygen key'))
@@ -222,8 +230,8 @@
         # nothing is signed.
         self.setUpPPA()
         self.openArchive("test", "1.0", "amd64")
-        self.archive.add_file("1.0/empty.efi", "")
-        self.archive.add_file("1.0/empty.ko", "")
+        self.tarfile.add_file("1.0/empty.efi", "")
+        self.tarfile.add_file("1.0/empty.ko", "")
         upload = self.process_emulate()
         self.assertEqual(1, upload.callLog.caller_count('UEFI keygen'))
         self.assertEqual(1, upload.callLog.caller_count('Kmod keygen key'))
@@ -235,7 +243,11 @@
         # If the configured key/cert are missing, processing succeeds but
         # nothing is signed.
         self.openArchive("test", "1.0", "amd64")
+<<<<<<< TREE
         self.archive.add_file("1.0/control/options", "")
+=======
+        self.tarfile.add_file("1.0/raw-signing.options", "")
+>>>>>>> MERGE-SOURCE
         upload = self.process_emulate()
         self.assertContentEqual([], upload.signing_options.keys())
 
@@ -243,7 +255,11 @@
         # If the configured key/cert are missing, processing succeeds but
         # nothing is signed.
         self.openArchive("test", "1.0", "amd64")
+<<<<<<< TREE
         self.archive.add_file("1.0/control/options", "first\n")
+=======
+        self.tarfile.add_file("1.0/raw-signing.options", "first\n")
+>>>>>>> MERGE-SOURCE
         upload = self.process_emulate()
         self.assertContentEqual(['first'], upload.signing_options.keys())
 
@@ -251,7 +267,11 @@
         # If the configured key/cert are missing, processing succeeds but
         # nothing is signed.
         self.openArchive("test", "1.0", "amd64")
+<<<<<<< TREE
         self.archive.add_file("1.0/control/options", "first\nsecond\n")
+=======
+        self.tarfile.add_file("1.0/raw-signing.options", "first\nsecond\n")
+>>>>>>> MERGE-SOURCE
         upload = self.process_emulate()
         self.assertContentEqual(['first', 'second'],
             upload.signing_options.keys())
@@ -261,8 +281,8 @@
         self.setUpUefiKeys()
         self.setUpKmodKeys()
         self.openArchive("test", "1.0", "amd64")
-        self.archive.add_file("1.0/empty.efi", "")
-        self.archive.add_file("1.0/empty.ko", "")
+        self.tarfile.add_file("1.0/empty.efi", "")
+        self.tarfile.add_file("1.0/empty.ko", "")
         self.process_emulate()
         self.assertTrue(os.path.exists(os.path.join(
             self.getSignedPath("test", "amd64"), "1.0", "empty.efi")))
@@ -279,9 +299,15 @@
         self.setUpUefiKeys()
         self.setUpKmodKeys()
         self.openArchive("test", "1.0", "amd64")
+<<<<<<< TREE
         self.archive.add_file("1.0/control/options", "tarball")
         self.archive.add_file("1.0/empty.efi", "")
         self.archive.add_file("1.0/empty.ko", "")
+=======
+        self.tarfile.add_file("1.0/raw-signing.options", "tarball")
+        self.tarfile.add_file("1.0/empty.efi", "")
+        self.tarfile.add_file("1.0/empty.ko", "")
+>>>>>>> MERGE-SOURCE
         self.process_emulate()
         self.assertFalse(os.path.exists(os.path.join(
             self.getSignedPath("test", "amd64"), "1.0", "empty.efi")))
@@ -304,9 +330,15 @@
         self.setUpUefiKeys()
         self.setUpKmodKeys()
         self.openArchive("test", "1.0", "amd64")
+<<<<<<< TREE
         self.archive.add_file("1.0/control/options", "signed-only")
         self.archive.add_file("1.0/empty.efi", "")
         self.archive.add_file("1.0/empty.ko", "")
+=======
+        self.tarfile.add_file("1.0/raw-signing.options", "signed-only")
+        self.tarfile.add_file("1.0/empty.efi", "")
+        self.tarfile.add_file("1.0/empty.ko", "")
+>>>>>>> MERGE-SOURCE
         self.process_emulate()
         self.assertFalse(os.path.exists(os.path.join(
             self.getSignedPath("test", "amd64"), "1.0", "empty.efi")))
@@ -324,10 +356,14 @@
         self.setUpUefiKeys()
         self.setUpKmodKeys()
         self.openArchive("test", "1.0", "amd64")
+<<<<<<< TREE
         self.archive.add_file("1.0/control/options",
+=======
+        self.tarfile.add_file("1.0/raw-signing.options",
+>>>>>>> MERGE-SOURCE
             "tarball\nsigned-only")
-        self.archive.add_file("1.0/empty.efi", "")
-        self.archive.add_file("1.0/empty.ko", "")
+        self.tarfile.add_file("1.0/empty.efi", "")
+        self.tarfile.add_file("1.0/empty.ko", "")
         self.process_emulate()
         tarfilename = os.path.join(self.getSignedPath("test", "amd64"),
             "1.0", "signed.tar.gz")
@@ -344,7 +380,7 @@
         # Nothing is signed.
         self.setUpUefiKeys()
         self.openArchive("empty", "1.0", "amd64")
-        self.archive.add_file("1.0/hello", "world")
+        self.tarfile.add_file("1.0/hello", "world")
         upload = self.process()
         self.assertTrue(os.path.exists(os.path.join(
             self.getSignedPath("empty", "amd64"), "1.0", "hello")))
@@ -355,7 +391,7 @@
         # If the target directory already exists, processing fails.
         self.setUpUefiKeys()
         self.openArchive("test", "1.0", "amd64")
-        self.archive.add_file("1.0/empty.efi", "")
+        self.tarfile.add_file("1.0/empty.efi", "")
         os.makedirs(os.path.join(self.getSignedPath("test", "amd64"), "1.0"))
         self.assertRaises(CustomUploadAlreadyExists, self.process)
 
@@ -363,7 +399,7 @@
         # The umask must be 0o022 to avoid incorrect permissions.
         self.setUpUefiKeys()
         self.openArchive("test", "1.0", "amd64")
-        self.archive.add_file("1.0/dir/file.efi", "foo")
+        self.tarfile.add_file("1.0/dir/file.efi", "foo")
         os.umask(0o002)  # cleanup already handled by setUp
         self.assertRaises(CustomUploadBadUmask, self.process)
 
@@ -376,7 +412,7 @@
         upload = SigningUpload()
         upload.generateUefiKeys = FakeMethod()
         upload.setTargetDirectory(
-            self.pubconf, "test_1.0_amd64.tar.gz", "distroseries")
+            self.archive, "test_1.0_amd64.tar.gz", "distroseries")
         upload.signUefi('t.efi')
         self.assertEqual(1, fake_call.call_count)
         # Assert command form.
@@ -396,7 +432,7 @@
         upload = SigningUpload()
         upload.generateUefiKeys = FakeMethod()
         upload.setTargetDirectory(
-            self.pubconf, "test_1.0_amd64.tar.gz", "distroseries")
+            self.archive, "test_1.0_amd64.tar.gz", "distroseries")
         upload.signUefi('t.efi')
         self.assertEqual(0, fake_call.call_count)
         self.assertEqual(0, upload.generateUefiKeys.call_count)
@@ -410,7 +446,7 @@
         self.useFixture(MonkeyPatch("subprocess.call", fake_call))
         upload = SigningUpload()
         upload.setTargetDirectory(
-            self.pubconf, "test_1.0_amd64.tar.gz", "distroseries")
+            self.archive, "test_1.0_amd64.tar.gz", "distroseries")
         upload.generateUefiKeys()
         self.assertEqual(1, fake_call.call_count)
         # Assert the actual command matches.
@@ -431,7 +467,7 @@
         upload = SigningUpload()
         upload.generateKmodKeys = FakeMethod()
         upload.setTargetDirectory(
-            self.pubconf, "test_1.0_amd64.tar.gz", "distroseries")
+            self.archive, "test_1.0_amd64.tar.gz", "distroseries")
         upload.signKmod('t.ko')
         self.assertEqual(1, fake_call.call_count)
         # Assert command form.
@@ -452,7 +488,7 @@
         upload = SigningUpload()
         upload.generateKmodKeys = FakeMethod()
         upload.setTargetDirectory(
-            self.pubconf, "test_1.0_amd64.tar.gz", "distroseries")
+            self.archive, "test_1.0_amd64.tar.gz", "distroseries")
         upload.signUefi('t.ko')
         self.assertEqual(0, fake_call.call_count)
         self.assertEqual(0, upload.generateKmodKeys.call_count)
@@ -466,7 +502,7 @@
         self.useFixture(MonkeyPatch("subprocess.call", fake_call))
         upload = SigningUpload()
         upload.setTargetDirectory(
-            self.pubconf, "test_1.0_amd64.tar.gz", "distroseries")
+            self.archive, "test_1.0_amd64.tar.gz", "distroseries")
         upload.generateKmodKeys()
         self.assertEqual(2, fake_call.call_count)
         # Assert the actual command matches.
@@ -492,7 +528,7 @@
         # Each image in the tarball is signed.
         self.setUpUefiKeys()
         self.openArchive("test", "1.0", "amd64")
-        self.archive.add_file("1.0/empty.efi", "")
+        self.tarfile.add_file("1.0/empty.efi", "")
         upload = self.process()
         self.assertEqual(1, upload.signUefi.call_count)
 
@@ -500,7 +536,7 @@
         # Each image in the tarball is signed.
         self.setUpKmodKeys()
         self.openArchive("test", "1.0", "amd64")
-        self.archive.add_file("1.0/empty.ko", "")
+        self.tarfile.add_file("1.0/empty.ko", "")
         upload = self.process()
         self.assertEqual(1, upload.signKmod.call_count)
 
@@ -508,9 +544,9 @@
         # Each image in the tarball is signed.
         self.setUpKmodKeys()
         self.openArchive("test", "1.0", "amd64")
-        self.archive.add_file("1.0/empty.efi", "")
-        self.archive.add_file("1.0/empty.ko", "")
-        self.archive.add_file("1.0/empty2.ko", "")
+        self.tarfile.add_file("1.0/empty.efi", "")
+        self.tarfile.add_file("1.0/empty.ko", "")
+        self.tarfile.add_file("1.0/empty2.ko", "")
         upload = self.process()
         self.assertEqual(1, upload.signUefi.call_count)
         self.assertEqual(2, upload.signKmod.call_count)
@@ -519,7 +555,7 @@
         # Files in the tarball are installed correctly.
         self.setUpUefiKeys()
         self.openArchive("test", "1.0", "amd64")
-        self.archive.add_file("1.0/empty.efi", "")
+        self.tarfile.add_file("1.0/empty.efi", "")
         self.process()
         self.assertTrue(os.path.isdir(os.path.join(
             self.getDistsPath(), "signed")))
@@ -531,7 +567,7 @@
         os.makedirs(os.path.join(self.getDistsPath(), "uefi"))
         self.setUpUefiKeys()
         self.openArchive("test", "1.0", "amd64")
-        self.archive.add_file("1.0/empty.efi", "")
+        self.tarfile.add_file("1.0/empty.efi", "")
         self.process()
         self.assertTrue(os.path.isdir(os.path.join(
             self.getDistsPath(), "signed")))
@@ -543,7 +579,7 @@
         os.makedirs(os.path.join(self.getDistsPath(), "signing"))
         self.setUpUefiKeys()
         self.openArchive("test", "1.0", "amd64")
-        self.archive.add_file("1.0/empty.efi", "")
+        self.tarfile.add_file("1.0/empty.efi", "")
         self.process()
         self.assertTrue(os.path.isdir(os.path.join(
             self.getDistsPath(), "signed")))
@@ -560,7 +596,7 @@
         upload = SigningUpload()
         upload.callLog = FakeMethodCallLog(upload=upload)
         upload.setTargetDirectory(
-            self.pubconf, "test_1.0_amd64.tar.gz", "distroseries")
+            self.archive, "test_1.0_amd64.tar.gz", "distroseries")
         upload.signUefi(os.path.join(self.makeTemporaryDirectory(), 't.efi'))
         self.assertEqual(0, upload.callLog.caller_count('UEFI keygen'))
         self.assertFalse(os.path.exists(self.key))
@@ -577,7 +613,7 @@
         upload = SigningUpload()
         upload.callLog = FakeMethodCallLog(upload=upload)
         upload.setTargetDirectory(
-            self.pubconf, "test_1.0_amd64.tar.gz", "distroseries")
+            self.archive, "test_1.0_amd64.tar.gz", "distroseries")
         upload.signUefi(os.path.join(self.makeTemporaryDirectory(), 't.efi'))
         self.assertEqual(1, upload.callLog.caller_count('UEFI keygen'))
         self.assertTrue(os.path.exists(self.key))
@@ -595,7 +631,7 @@
         upload = SigningUpload()
         upload.callLog = FakeMethodCallLog(upload=upload)
         upload.setTargetDirectory(
-            self.pubconf, "test_1.0_amd64.tar.gz", "distroseries")
+            self.archive, "test_1.0_amd64.tar.gz", "distroseries")
         upload.signKmod(os.path.join(self.makeTemporaryDirectory(), 't.ko'))
         self.assertEqual(0, upload.callLog.caller_count('Kmod keygen key'))
         self.assertEqual(0, upload.callLog.caller_count('Kmod keygen cert'))
@@ -613,7 +649,7 @@
         upload = SigningUpload()
         upload.callLog = FakeMethodCallLog(upload=upload)
         upload.setTargetDirectory(
-            self.pubconf, "test_1.0_amd64.tar.gz", "distroseries")
+            self.archive, "test_1.0_amd64.tar.gz", "distroseries")
         upload.signKmod(os.path.join(self.makeTemporaryDirectory(), 't.ko'))
         self.assertEqual(1, upload.callLog.caller_count('Kmod keygen key'))
         self.assertEqual(1, upload.callLog.caller_count('Kmod keygen cert'))
@@ -628,12 +664,28 @@
         self.setUpUefiKeys()
         self.setUpKmodKeys()
         self.openArchive("test", "1.0", "amd64")
-        self.archive.add_file("1.0/empty.efi", "")
-        self.archive.add_file("1.0/empty.ko", "")
-        self.process_emulate()
-        sha256file = os.path.join(self.getSignedPath("test", "amd64"),
-             "1.0", "SHA256SUMS")
-        self.assertTrue(os.path.exists(sha256file))
+        self.tarfile.add_file("1.0/empty.efi", "")
+        self.tarfile.add_file("1.0/empty.ko", "")
+        self.process_emulate()
+        sha256file = os.path.join(self.getSignedPath("test", "amd64"),
+             "1.0", "SHA256SUMS")
+        self.assertTrue(os.path.exists(sha256file))
+
+    def test_checksumming_tree_signed(self):
+        # Specifying no options should leave us with an open tree,
+        # confirm it is checksummed.  Supply an archive signing key
+        # which should trigger signing of the checksum file.
+        self.setUpArchiveKey()
+        self.setUpUefiKeys()
+        self.setUpKmodKeys()
+        self.openArchive("test", "1.0", "amd64")
+        self.tarfile.add_file("1.0/empty.efi", "")
+        self.tarfile.add_file("1.0/empty.ko", "")
+        self.process_emulate()
+        sha256file = os.path.join(self.getSignedPath("test", "amd64"),
+             "1.0", "SHA256SUMS")
+        self.assertTrue(os.path.exists(sha256file))
+        self.assertTrue(os.path.exists(sha256file + '.gpg'))
 
 
 class TestUefi(TestSigningHelpers):
@@ -643,7 +695,7 @@
             "%s-%s" % (loader_type, arch))
 
     def process(self):
-        self.archive.close()
+        self.tarfile.close()
         self.buffer.close()
         upload = UefiUpload()
         upload.signUefi = FakeMethod()
@@ -651,7 +703,7 @@
         # Under no circumstances is it safe to execute actual commands.
         fake_call = FakeMethod(result=0)
         self.useFixture(MonkeyPatch("subprocess.call", fake_call))
-        upload.process(self.pubconf, self.path, self.suite)
+        upload.process(self.archive, self.path, self.suite)
         self.assertEqual(0, fake_call.call_count)
 
         return upload
@@ -660,7 +712,7 @@
         # Files in the tarball are installed correctly.
         self.setUpUefiKeys()
         self.openArchive("test", "1.0", "amd64")
-        self.archive.add_file("1.0/empty.efi", "")
+        self.tarfile.add_file("1.0/empty.efi", "")
         self.process()
         self.assertTrue(os.path.isdir(os.path.join(
             self.getDistsPath(), "uefi")))


Follow ups