← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~lgp171188/launchpad:update-publisher-multi-sign-archive-when-multiple-keys-available into launchpad:master

 

Guruprasad has proposed merging ~lgp171188/launchpad:update-publisher-multi-sign-archive-when-multiple-keys-available into launchpad:master.

Commit message:
Sign the archive with all its OpenPGP signing keys
    
If an archive has more than one OpenPGP signing key, sign the archive's
metadata files with all the available keys. For this, make signing an
operation on the signing key set and require passing a list of keys
to all signing operations.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~lgp171188/launchpad/+git/launchpad/+merge/464692
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~lgp171188/launchpad:update-publisher-multi-sign-archive-when-multiple-keys-available into launchpad:master.
diff --git a/lib/lp/archivepublisher/archivegpgsigningkey.py b/lib/lp/archivepublisher/archivegpgsigningkey.py
index f24322d..b244b3d 100644
--- a/lib/lp/archivepublisher/archivegpgsigningkey.py
+++ b/lib/lp/archivepublisher/archivegpgsigningkey.py
@@ -72,16 +72,29 @@ class SignableArchive:
         )
 
     @cachedproperty
-    def _signing_key(self):
+    def _signing_keys(self):
         """This archive's signing key on the signing service, if any."""
         if not getFeatureFlag(PUBLISHER_GPG_USES_SIGNING_SERVICE):
-            return None
-        elif self.archive.signing_key_fingerprint is not None:
-            return getUtility(ISigningKeySet).get(
-                SigningKeyType.OPENPGP, self.archive.signing_key_fingerprint
+            return []
+
+        signing_keys = []
+        signing_keys.extend(
+            getUtility(IArchiveSigningKeySet).getOpenPGPSigningKeysForArchive(
+                self.archive,
             )
-        else:
-            return None
+        )
+        if self.archive.signing_key_fingerprint is not None:
+            if not any(
+                key.fingerprint == self.archive.signing_key_fingerprint
+                for key in signing_keys
+            ):
+                signing_key = getUtility(ISigningKeySet).get(
+                    SigningKeyType.OPENPGP,
+                    self.archive.signing_key_fingerprint,
+                )
+                if signing_key:
+                    signing_keys.append(signing_key)
+        return signing_keys
 
     @cachedproperty
     def _secret_key(self):
@@ -121,12 +134,13 @@ class SignableArchive:
                 raise ValueError("Invalid signature mode for GPG: %s" % mode)
             signed = False
 
-            if self._signing_key is not None or self._secret_key is not None:
+            if self._signing_keys or self._secret_key is not None:
                 with open(input_path, "rb") as input_file:
                     input_content = input_file.read()
-                if self._signing_key is not None:
+                if self._signing_keys:
                     try:
-                        signature = self._signing_key.sign(
+                        signature = getUtility(ISigningKeySet).sign(
+                            self._signing_keys,
                             input_content,
                             os.path.basename(input_path),
                             mode=mode,
@@ -138,7 +152,7 @@ class SignableArchive:
                                 "Failed to sign archive using signing "
                                 "service; falling back to local key"
                             )
-                        get_property_cache(self)._signing_key = None
+                        get_property_cache(self)._signing_keys = None
                 if not signed and self._secret_key is not None:
                     signature = getUtility(IGPGHandler).signContent(
                         input_content,
diff --git a/lib/lp/archivepublisher/signing.py b/lib/lp/archivepublisher/signing.py
index 9938a95..899ddf1 100644
--- a/lib/lp/archivepublisher/signing.py
+++ b/lib/lp/archivepublisher/signing.py
@@ -32,7 +32,10 @@ from lp.registry.interfaces.distroseries import IDistroSeriesSet
 from lp.services.features import getFeatureFlag
 from lp.services.osutils import remove_if_exists
 from lp.services.signing.enums import SigningKeyType
-from lp.services.signing.interfaces.signingkey import IArchiveSigningKeySet
+from lp.services.signing.interfaces.signingkey import (
+    IArchiveSigningKeySet,
+    ISigningKeySet,
+)
 from lp.soyuz.interfaces.queue import CustomUploadError
 
 PUBLISHER_USES_SIGNING_SERVICE = "archivepublisher.signing_service.enabled"
@@ -398,10 +401,9 @@ class SigningUpload(CustomUpload):
 
         with open(filename, "rb") as fd:
             content = fd.read()
-
         try:
-            signed_content = signing_key.sign(
-                content, message_name=os.path.basename(filename)
+            signed_content = getUtility(ISigningKeySet).sign(
+                [signing_key], content, message_name=os.path.basename(filename)
             )
         except Exception as e:
             if self.logger:
diff --git a/lib/lp/archivepublisher/tests/test_archivegpgsigningkey.py b/lib/lp/archivepublisher/tests/test_archivegpgsigningkey.py
index e51077a..ff2ce27 100644
--- a/lib/lp/archivepublisher/tests/test_archivegpgsigningkey.py
+++ b/lib/lp/archivepublisher/tests/test_archivegpgsigningkey.py
@@ -38,7 +38,10 @@ from lp.services.gpg.tests.test_gpghandler import FakeGenerateKey
 from lp.services.log.logger import BufferLogger
 from lp.services.osutils import write_file
 from lp.services.signing.enums import SigningKeyType, SigningMode
-from lp.services.signing.interfaces.signingkey import ISigningKeySet
+from lp.services.signing.interfaces.signingkey import (
+    IArchiveSigningKeySet,
+    ISigningKeySet,
+)
 from lp.services.signing.tests.helpers import SigningServiceClientFixture
 from lp.services.twistedsupport.testing import TReqFixture
 from lp.services.twistedsupport.treq import check_status
@@ -146,14 +149,14 @@ class TestSignableArchiveWithSigningKey(TestCaseWithFactory):
             [
                 mock.call(
                     SigningKeyType.OPENPGP,
-                    self.archive.signing_key_fingerprint,
+                    [self.archive.signing_key_fingerprint],
                     "Release",
                     b"Release contents",
                     SigningMode.DETACHED,
                 ),
                 mock.call(
                     SigningKeyType.OPENPGP,
-                    self.archive.signing_key_fingerprint,
+                    [self.archive.signing_key_fingerprint],
                     "Release",
                     b"Release contents",
                     SigningMode.CLEAR,
@@ -169,6 +172,60 @@ class TestSignableArchiveWithSigningKey(TestCaseWithFactory):
             FileContains("signed with key_type=OPENPGP mode=CLEAR"),
         )
 
+    def test_signRepository_uses_all_OpenPGPG_keys_of_an_archive(self):
+        self.useFixture(
+            FeatureFixture({PUBLISHER_GPG_USES_SIGNING_SERVICE: "on"})
+        )
+        current_key = self.factory.makeSigningKey(
+            key_type=SigningKeyType.OPENPGP,
+            fingerprint=self.archive.signing_key_fingerprint,
+        )
+        new_key = self.factory.makeSigningKey(
+            key_type=SigningKeyType.OPENPGP,
+            # The fingerprint has to be 30 characters long to pass validation.
+            fingerprint="ABCDEFGHIJKLMNOPQRSTUVWXYZ1234",
+        )
+        self.archive.signing_key_fingerprint = current_key.fingerprint
+        getUtility(IArchiveSigningKeySet).create(
+            self.archive, None, current_key
+        )
+        getUtility(IArchiveSigningKeySet).create(self.archive, None, new_key)
+        signing_service_client = self.useFixture(
+            SigningServiceClientFixture(self.factory)
+        )
+        suite_dir = os.path.join(
+            self.archive_root,
+            "dists",
+            self.suite,
+        )
+        release_path = os.path.join(suite_dir, "Release")
+        write_file(release_path, b"Release contents")
+        logger = BufferLogger()
+        signer = ISignableArchive(self.archive)
+        self.assertTrue(signer.can_sign)
+        self.assertContentEqual(
+            ["Release.gpg", "InRelease"],
+            signer.signRepository(self.suite, log=logger),
+        )
+        signing_service_client.sign.assert_has_calls(
+            [
+                mock.call(
+                    SigningKeyType.OPENPGP,
+                    [current_key.fingerprint, new_key.fingerprint],
+                    "Release",
+                    b"Release contents",
+                    SigningMode.DETACHED,
+                ),
+                mock.call(
+                    SigningKeyType.OPENPGP,
+                    [current_key.fingerprint, new_key.fingerprint],
+                    "Release",
+                    b"Release contents",
+                    SigningMode.CLEAR,
+                ),
+            ]
+        )
+
     def test_signRepository_falls_back_from_signing_service(self):
         # If the signing service fails to sign a file, we fall back to
         # making local signatures if possible.
@@ -202,7 +259,7 @@ class TestSignableArchiveWithSigningKey(TestCaseWithFactory):
         )
         signing_service_client.sign.assert_called_once_with(
             SigningKeyType.OPENPGP,
-            self.archive.signing_key_fingerprint,
+            [self.archive.signing_key_fingerprint],
             "Release",
             b"Release contents",
             SigningMode.DETACHED,
diff --git a/lib/lp/archivepublisher/tests/test_signing.py b/lib/lp/archivepublisher/tests/test_signing.py
index c352f3d..91e40cc 100644
--- a/lib/lp/archivepublisher/tests/test_signing.py
+++ b/lib/lp/archivepublisher/tests/test_signing.py
@@ -1955,49 +1955,49 @@ class TestSigningUploadWithSigningService(TestSigningHelpers):
             [
                 call(
                     SigningKeyType.UEFI,
-                    keys[SigningKeyType.UEFI].fingerprint,
+                    [keys[SigningKeyType.UEFI].fingerprint],
                     "empty.efi",
                     b"a",
                     SigningMode.ATTACHED,
                 ),
                 call(
                     SigningKeyType.KMOD,
-                    keys[SigningKeyType.KMOD].fingerprint,
+                    [keys[SigningKeyType.KMOD].fingerprint],
                     "empty.ko",
                     b"b",
                     SigningMode.DETACHED,
                 ),
                 call(
                     SigningKeyType.OPAL,
-                    keys[SigningKeyType.OPAL].fingerprint,
+                    [keys[SigningKeyType.OPAL].fingerprint],
                     "empty.opal",
                     b"c",
                     SigningMode.DETACHED,
                 ),
                 call(
                     SigningKeyType.SIPL,
-                    keys[SigningKeyType.SIPL].fingerprint,
+                    [keys[SigningKeyType.SIPL].fingerprint],
                     "empty.sipl",
                     b"d",
                     SigningMode.DETACHED,
                 ),
                 call(
                     SigningKeyType.FIT,
-                    keys[SigningKeyType.FIT].fingerprint,
+                    [keys[SigningKeyType.FIT].fingerprint],
                     "empty.fit",
                     b"e",
                     SigningMode.ATTACHED,
                 ),
                 call(
                     SigningKeyType.CV2_KERNEL,
-                    keys[SigningKeyType.CV2_KERNEL].fingerprint,
+                    [keys[SigningKeyType.CV2_KERNEL].fingerprint],
                     "empty.cv2-kernel",
                     b"f",
                     SigningMode.DETACHED,
                 ),
                 call(
                     SigningKeyType.ANDROID_KERNEL,
-                    keys[SigningKeyType.ANDROID_KERNEL].fingerprint,
+                    [keys[SigningKeyType.ANDROID_KERNEL].fingerprint],
                     "empty.android-kernel",
                     b"g",
                     SigningMode.DETACHED,
@@ -2051,49 +2051,49 @@ class TestSigningUploadWithSigningService(TestSigningHelpers):
             [
                 call(
                     SigningKeyType.UEFI,
-                    keys[SigningKeyType.UEFI].fingerprint,
+                    [keys[SigningKeyType.UEFI].fingerprint],
                     "empty.efi",
                     b"a",
                     SigningMode.ATTACHED,
                 ),
                 call(
                     SigningKeyType.KMOD,
-                    keys[SigningKeyType.KMOD].fingerprint,
+                    [keys[SigningKeyType.KMOD].fingerprint],
                     "empty.ko",
                     b"b",
                     SigningMode.DETACHED,
                 ),
                 call(
                     SigningKeyType.OPAL,
-                    keys[SigningKeyType.OPAL].fingerprint,
+                    [keys[SigningKeyType.OPAL].fingerprint],
                     "empty.opal",
                     b"c",
                     SigningMode.DETACHED,
                 ),
                 call(
                     SigningKeyType.SIPL,
-                    keys[SigningKeyType.SIPL].fingerprint,
+                    [keys[SigningKeyType.SIPL].fingerprint],
                     "empty.sipl",
                     b"d",
                     SigningMode.DETACHED,
                 ),
                 call(
                     SigningKeyType.FIT,
-                    keys[SigningKeyType.FIT].fingerprint,
+                    [keys[SigningKeyType.FIT].fingerprint],
                     "empty.fit",
                     b"e",
                     SigningMode.ATTACHED,
                 ),
                 call(
                     SigningKeyType.CV2_KERNEL,
-                    keys[SigningKeyType.CV2_KERNEL].fingerprint,
+                    [keys[SigningKeyType.CV2_KERNEL].fingerprint],
                     "empty.cv2-kernel",
                     b"f",
                     SigningMode.DETACHED,
                 ),
                 call(
                     SigningKeyType.ANDROID_KERNEL,
-                    keys[SigningKeyType.ANDROID_KERNEL].fingerprint,
+                    [keys[SigningKeyType.ANDROID_KERNEL].fingerprint],
                     "empty.android-kernel",
                     b"g",
                     SigningMode.DETACHED,
@@ -2158,49 +2158,49 @@ class TestSigningUploadWithSigningService(TestSigningHelpers):
             [
                 call(
                     SigningKeyType.UEFI,
-                    keys[SigningKeyType.UEFI].fingerprint,
+                    [keys[SigningKeyType.UEFI].fingerprint],
                     "empty.efi",
                     b"a",
                     SigningMode.ATTACHED,
                 ),
                 call(
                     SigningKeyType.KMOD,
-                    keys[SigningKeyType.KMOD].fingerprint,
+                    [keys[SigningKeyType.KMOD].fingerprint],
                     "empty.ko",
                     b"b",
                     SigningMode.DETACHED,
                 ),
                 call(
                     SigningKeyType.OPAL,
-                    keys[SigningKeyType.OPAL].fingerprint,
+                    [keys[SigningKeyType.OPAL].fingerprint],
                     "empty.opal",
                     b"c",
                     SigningMode.DETACHED,
                 ),
                 call(
                     SigningKeyType.SIPL,
-                    keys[SigningKeyType.SIPL].fingerprint,
+                    [keys[SigningKeyType.SIPL].fingerprint],
                     "empty.sipl",
                     b"d",
                     SigningMode.DETACHED,
                 ),
                 call(
                     SigningKeyType.FIT,
-                    keys[SigningKeyType.FIT].fingerprint,
+                    [keys[SigningKeyType.FIT].fingerprint],
                     "empty.fit",
                     b"e",
                     SigningMode.ATTACHED,
                 ),
                 call(
                     SigningKeyType.CV2_KERNEL,
-                    keys[SigningKeyType.CV2_KERNEL].fingerprint,
+                    [keys[SigningKeyType.CV2_KERNEL].fingerprint],
                     "empty.cv2-kernel",
                     b"f",
                     SigningMode.DETACHED,
                 ),
                 call(
                     SigningKeyType.ANDROID_KERNEL,
-                    keys[SigningKeyType.ANDROID_KERNEL].fingerprint,
+                    [keys[SigningKeyType.ANDROID_KERNEL].fingerprint],
                     "empty.android-kernel",
                     b"g",
                     SigningMode.DETACHED,
@@ -2323,14 +2323,14 @@ class TestSigningUploadWithSigningService(TestSigningHelpers):
             [
                 call(
                     SigningKeyType.KMOD,
-                    keys[SigningKeyType.KMOD].fingerprint,
+                    [keys[SigningKeyType.KMOD].fingerprint],
                     "empty.ko",
                     b"some data for 1.0/empty.ko",
                     SigningMode.DETACHED,
                 ),
                 call(
                     SigningKeyType.OPAL,
-                    keys[SigningKeyType.OPAL].fingerprint,
+                    [keys[SigningKeyType.OPAL].fingerprint],
                     "empty.opal",
                     b"some data for 1.0/empty.opal",
                     SigningMode.DETACHED,
@@ -2410,49 +2410,49 @@ class TestSigningUploadWithSigningService(TestSigningHelpers):
             [
                 call(
                     SigningKeyType.UEFI,
-                    fingerprints[SigningKeyType.UEFI],
+                    [fingerprints[SigningKeyType.UEFI]],
                     "empty.efi",
                     b"data - 1.0/empty.efi",
                     SigningMode.ATTACHED,
                 ),
                 call(
                     SigningKeyType.KMOD,
-                    fingerprints[SigningKeyType.KMOD],
+                    [fingerprints[SigningKeyType.KMOD]],
                     "empty.ko",
                     b"data - 1.0/empty.ko",
                     SigningMode.DETACHED,
                 ),
                 call(
                     SigningKeyType.OPAL,
-                    fingerprints[SigningKeyType.OPAL],
+                    [fingerprints[SigningKeyType.OPAL]],
                     "empty.opal",
                     b"data - 1.0/empty.opal",
                     SigningMode.DETACHED,
                 ),
                 call(
                     SigningKeyType.SIPL,
-                    fingerprints[SigningKeyType.SIPL],
+                    [fingerprints[SigningKeyType.SIPL]],
                     "empty.sipl",
                     b"data - 1.0/empty.sipl",
                     SigningMode.DETACHED,
                 ),
                 call(
                     SigningKeyType.FIT,
-                    fingerprints[SigningKeyType.FIT],
+                    [fingerprints[SigningKeyType.FIT]],
                     "empty.fit",
                     b"data - 1.0/empty.fit",
                     SigningMode.ATTACHED,
                 ),
                 call(
                     SigningKeyType.CV2_KERNEL,
-                    fingerprints[SigningKeyType.CV2_KERNEL],
+                    [fingerprints[SigningKeyType.CV2_KERNEL]],
                     "empty.cv2-kernel",
                     b"data - 1.0/empty.cv2-kernel",
                     SigningMode.DETACHED,
                 ),
                 call(
                     SigningKeyType.ANDROID_KERNEL,
-                    fingerprints[SigningKeyType.ANDROID_KERNEL],
+                    [fingerprints[SigningKeyType.ANDROID_KERNEL]],
                     "empty.android-kernel",
                     b"data - 1.0/empty.android-kernel",
                     SigningMode.DETACHED,
diff --git a/lib/lp/services/signing/interfaces/signingkey.py b/lib/lp/services/signing/interfaces/signingkey.py
index a99e018..6215571 100644
--- a/lib/lp/services/signing/interfaces/signingkey.py
+++ b/lib/lp/services/signing/interfaces/signingkey.py
@@ -44,16 +44,6 @@ class ISigningKey(Interface):
         title=_("When this key was created"), required=True, readonly=True
     )
 
-    def sign(message, message_name, mode=None):
-        """Sign the given message using this key
-
-        :param message: The message to be signed.
-        :param message_name: A name for the message being signed.
-        :param mode: A `SigningMode` specifying how the message is to be
-            signed.  Defaults to `SigningMode.ATTACHED` for UEFI and FIT
-            keys, and `SigningMode.DETACHED` for other key types.
-        """
-
     def addAuthorization(client_name):
         """Authorize another client to use this key.
 
@@ -101,6 +91,19 @@ class ISigningKeySet(Interface):
                  key at lp-signing
         """
 
+    def sign(signing_keys, message, message_name, mode=None):
+        """Sign the given message using this key
+
+        :param signing_keys: A list of one or more signing keys to sign
+            the given message with. If more than one signing key is provided,
+            all signing keys must be of the same type.
+        :param message: The message to be signed.
+        :param message_name: A name for the message being signed.
+        :param mode: A `SigningMode` specifying how the message is to be
+            signed.  Defaults to `SigningMode.ATTACHED` for UEFI and FIT
+            keys, and `SigningMode.DETACHED` for other key types.
+        """
+
 
 class IArchiveSigningKey(Interface):
     """Which signing key should be used by a specific archive"""
@@ -162,6 +165,13 @@ class IArchiveSigningKeySet(Interface):
         :return: The most suitable key, or None.
         """
 
+    def getOpenPGPSigningKeysForArchive(archive):
+        """Find and return the OpenPGP signing keys for the given archive.
+
+        :param archive: The archive to get the OpenPGP signing keys for.
+        :return: A list of matching signing keys or an empty list.
+        """
+
     def getByArchiveAndFingerprint(archive, fingerprint):
         """Get ArchiveSigningKey by archive and fingerprint.
 
diff --git a/lib/lp/services/signing/interfaces/signingserviceclient.py b/lib/lp/services/signing/interfaces/signingserviceclient.py
index 9077d9c..e67d03b 100644
--- a/lib/lp/services/signing/interfaces/signingserviceclient.py
+++ b/lib/lp/services/signing/interfaces/signingserviceclient.py
@@ -33,13 +33,14 @@ class ISigningServiceClient(Interface):
         :return: A dict with 'fingerprint' (str) and 'public-key' (bytes)
         """
 
-    def sign(key_type, fingerprint, message_name, message, mode):
+    def sign(key_type, fingerprints, message_name, message, mode):
         """Sign the given message using the specified key_type and a
         pre-generated fingerprint (see `generate` method).
 
         :param key_type: One of the key types from SigningKeyType enum
-        :param fingerprint: The fingerprint of the signing key, generated by
-                            the `generate` method
+        :param fingerprints: A list of the fingerprints of one or more
+                             signing keys, generated by the `generate`
+                             method.
         :param message_name: A description of the message being signed
         :param message: The message to be signed
         :param mode: SigningMode.ATTACHED or SigningMode.DETACHED
diff --git a/lib/lp/services/signing/model/signingkey.py b/lib/lp/services/signing/model/signingkey.py
index 4bb96c3..b51a081 100644
--- a/lib/lp/services/signing/model/signingkey.py
+++ b/lib/lp/services/signing/model/signingkey.py
@@ -133,15 +133,24 @@ class SigningKey(StormBase):
             store.add(db_key)
         return db_key
 
-    def sign(self, message, message_name, mode=None):
+    @classmethod
+    def sign(cls, signing_keys, message, message_name, mode=None):
+        fingerprints = [key.fingerprint for key in signing_keys]
+        key_type = signing_keys[0].key_type
+        if len(signing_keys) > 1 and not all(
+            key.key_type == key_type for key in signing_keys[1:]
+        ):
+            raise ValueError(
+                "Cannot sign as all the keys are not of the same type."
+            )
         if mode is None:
-            if self.key_type in (SigningKeyType.UEFI, SigningKeyType.FIT):
+            if key_type in (SigningKeyType.UEFI, SigningKeyType.FIT):
                 mode = SigningMode.ATTACHED
             else:
                 mode = SigningMode.DETACHED
         signing_service = getUtility(ISigningServiceClient)
         signed = signing_service.sign(
-            self.key_type, self.fingerprint, message_name, message, mode
+            key_type, fingerprints, message_name, message, mode
         )
         return signed["signed-message"]
 
@@ -248,6 +257,27 @@ class ArchiveSigningKeySet:
         )
 
     @classmethod
+    def getOpenPGPSigningKeysForArchive(cls, archive):
+        join = (
+            ArchiveSigningKey,
+            Join(
+                SigningKey,
+                SigningKey.id == ArchiveSigningKey.signing_key_id,
+            ),
+        )
+
+        results = list(
+            IStore(ArchiveSigningKey)
+            .using(*join)
+            .find(
+                SigningKey,
+                ArchiveSigningKey.archive == archive,
+                ArchiveSigningKey.key_type == SigningKeyType.OPENPGP,
+            )
+        )
+        return results
+
+    @classmethod
     def getByArchiveAndFingerprint(cls, archive, fingerprint):
         join = (
             ArchiveSigningKey,
diff --git a/lib/lp/services/signing/proxy.py b/lib/lp/services/signing/proxy.py
index 70fefa1..43d01be 100644
--- a/lib/lp/services/signing/proxy.py
+++ b/lib/lp/services/signing/proxy.py
@@ -178,7 +178,7 @@ class SigningServiceClient:
             "public-key": base64.b64decode(ret["public-key"].encode("UTF-8")),
         }
 
-    def sign(self, key_type, fingerprint, message_name, message, mode):
+    def sign(self, key_type, fingerprints, message_name, message, mode):
         valid_modes = {SigningMode.ATTACHED, SigningMode.DETACHED}
         if key_type == SigningKeyType.OPENPGP:
             valid_modes.add(SigningMode.CLEAR)
@@ -186,7 +186,20 @@ class SigningServiceClient:
             raise ValueError("%s is not a valid mode" % mode)
         if key_type not in SigningKeyType.items:
             raise ValueError("%s is not a valid key type" % key_type)
+        if not fingerprints:
+            raise ValueError("Not even one fingerprint was provided")
+        if len(fingerprints) > 1 and key_type != SigningKeyType.OPENPGP:
+            raise ValueError(
+                "Multi-signing is not supported for non-OpenPGP keys"
+            )
 
+        # The signing service accepts either a single fingerprint
+        # string (for all key types) or a list of two or more
+        # fingerprints (only for OpenPGP keys) for the 'fingerprint'
+        # property.
+        fingerprint = (
+            fingerprints[0] if len(fingerprints) == 1 else fingerprints
+        )
         payload = {
             "key-type": key_type.name,
             "fingerprint": fingerprint,
@@ -196,8 +209,14 @@ class SigningServiceClient:
         }
 
         ret = self._requestJson("/sign", "POST", encrypt=True, json=payload)
+        if isinstance(ret["public-key"], str):
+            public_key = base64.b64decode(ret["public-key"].encode("UTF-8"))
+        else:  # is a list of public key strings
+            public_key = [
+                base64.b64decode(x).encode("UTF-8") for x in ret["public-key"]
+            ]
         return {
-            "public-key": base64.b64decode(ret["public-key"].encode("UTF-8")),
+            "public-key": public_key,
             "signed-message": base64.b64decode(
                 ret["signed-message"].encode("UTF-8")
             ),
diff --git a/lib/lp/services/signing/tests/test_proxy.py b/lib/lp/services/signing/tests/test_proxy.py
index 78ba505..c639f7e 100644
--- a/lib/lp/services/signing/tests/test_proxy.py
+++ b/lib/lp/services/signing/tests/test_proxy.py
@@ -543,7 +543,9 @@ class SigningServiceProxyTest(TestCaseWithFactory, TestWithFixtures):
         message = b"this is the message content"
 
         signing = getUtility(ISigningServiceClient)
-        data = signing.sign(key_type, fingerprint, message_name, message, mode)
+        data = signing.sign(
+            key_type, [fingerprint], message_name, message, mode
+        )
 
         self.assertEqual(3, len(responses.calls))
         # expected order of HTTP calls
diff --git a/lib/lp/services/signing/tests/test_signingkey.py b/lib/lp/services/signing/tests/test_signingkey.py
index 327de1e..686456d 100644
--- a/lib/lp/services/signing/tests/test_signingkey.py
+++ b/lib/lp/services/signing/tests/test_signingkey.py
@@ -165,7 +165,8 @@ class TestSigningKey(TestCaseWithFactory, TestWithFixtures):
             bytes(self.signing_service.generated_public_key),
             description="This is my key!",
         )
-        signed = s.sign(b"secure message", "message_name")
+        signing_key_set = getUtility(ISigningKeySet)
+        signed = signing_key_set.sign([s], b"secure message", "message_name")
 
         # Checks if the returned value is actually the returning value from
         # HTTP POST /sign call to lp-signing service
@@ -204,8 +205,11 @@ class TestSigningKey(TestCaseWithFactory, TestWithFixtures):
             bytes(self.signing_service.generated_public_key),
             description="This is my key!",
         )
-        s.sign(b"secure message", "message_name")
-        s.sign(b"another message", "another_name", mode=SigningMode.CLEAR)
+        signing_key_set = getUtility(ISigningKeySet)
+        signing_key_set.sign([s], b"secure message", "message_name")
+        signing_key_set.sign(
+            [s], b"another message", "another_name", mode=SigningMode.CLEAR
+        )
 
         self.assertEqual(5, len(responses.calls))
         self.assertThat(