launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #24599
[Merge] ~pappacena/launchpad:lp-signing-inject into launchpad:master
Thiago F. Pappacena has proposed merging ~pappacena/launchpad:lp-signing-inject into launchpad:master.
Commit message:
Adding signing service /inject API integrations.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~pappacena/launchpad/+git/launchpad/+merge/382457
This MP adds the methods to call lp-signing service, but it's not actually using this just yet. I'm splitting the MP to make it easier to review.
Another MP should come soon to use the `ArchiveSigningKeySet.inject` call to actually import the existing signing keys to the new structure.
--
Your team Launchpad code reviewers is requested to review the proposed merge of ~pappacena/launchpad:lp-signing-inject into launchpad:master.
diff --git a/lib/lp/services/signing/interfaces/signingkey.py b/lib/lp/services/signing/interfaces/signingkey.py
index 594a053..74d92c9 100644
--- a/lib/lp/services/signing/interfaces/signingkey.py
+++ b/lib/lp/services/signing/interfaces/signingkey.py
@@ -66,8 +66,22 @@ class ISigningKeySet(Interface):
:param key_type: One of the SigningKeyType enum's value
:param description: (optional) The description associated with this
key
- :returns: The SigningKey object associated with the newly created
- key at lp-signing"""
+ :return: The SigningKey object associated with the newly created
+ key at lp-signing
+ """
+
+ def inject(key_type, private_key, public_key, description, created_at):
+ """Inject an existing key pair on lp-signing and stores it in LP's
+ database.
+
+ :param key_type: One of the SigningKeyType enum's value.
+ :param private_key: The private key to be injected into lp-signing
+ :param public_key: The public key to be injected into lp-signing
+ :param description: The description of the key being injected
+ :param created_at: The datetime when the key was originally created.
+ :return: The SigningKey object associated with the newly created
+ key at lp-signing
+ """
class IArchiveSigningKey(Interface):
diff --git a/lib/lp/services/signing/interfaces/signingserviceclient.py b/lib/lp/services/signing/interfaces/signingserviceclient.py
index 0d2a242..32ef1c6 100644
--- a/lib/lp/services/signing/interfaces/signingserviceclient.py
+++ b/lib/lp/services/signing/interfaces/signingserviceclient.py
@@ -45,3 +45,13 @@ class ISigningServiceClient(Interface):
:param mode: SigningMode.ATTACHED or SigningMode.DETACHED
:return: A dict with 'public-key' and 'signed-message'
"""
+
+ def inject(key_type, private_key, public_key, description, created_at):
+ """Injects an existing key on lp-signing service.
+
+ :param key_type: One of `SigningKeyType` items.
+ :param private_key: The private key content, (bytes or nacl object).
+ :param public_key: The public key content (bytes or nacl object).
+ :param description: The description of this key.
+ :param created_at: datetime of when the key was created.
+ """
diff --git a/lib/lp/services/signing/model/signingkey.py b/lib/lp/services/signing/model/signingkey.py
index e77c200..9638466 100644
--- a/lib/lp/services/signing/model/signingkey.py
+++ b/lib/lp/services/signing/model/signingkey.py
@@ -100,6 +100,20 @@ class SigningKey(StormBase):
store.add(signing_key)
return signing_key
+ @classmethod
+ def inject(cls, key_type, private_key, public_key, description,
+ created_at):
+ signing_service = getUtility(ISigningServiceClient)
+ generated_key = signing_service.inject(
+ key_type, private_key, public_key, description, created_at)
+ signing_key = SigningKey(
+ key_type=key_type, fingerprint=generated_key['fingerprint'],
+ public_key=bytes(public_key),
+ description=description, date_created=created_at)
+ store = IMasterStore(SigningKey)
+ store.add(signing_key)
+ return signing_key
+
def sign(self, message, message_name):
if self.key_type in (SigningKeyType.UEFI, SigningKeyType.FIT):
mode = SigningMode.ATTACHED
diff --git a/lib/lp/services/signing/proxy.py b/lib/lp/services/signing/proxy.py
index d8542e3..7dc248a 100644
--- a/lib/lp/services/signing/proxy.py
+++ b/lib/lp/services/signing/proxy.py
@@ -180,3 +180,24 @@ class SigningServiceClient:
return {
'public-key': base64.b64decode(data['public-key']),
'signed-message': base64.b64decode(data['signed-message'])}
+
+ def inject(self, key_type, private_key, public_key, description,
+ created_at):
+ payload = json.dumps({
+ "key-type": key_type.name,
+ "private-key": base64.b64encode(
+ bytes(private_key)).decode("UTF-8"),
+ "public-key": base64.b64encode(
+ bytes(public_key)).decode("UTF-8"),
+ "created-at": created_at.isoformat(),
+ "description": description,
+ }).encode("UTF-8")
+
+ nonce = self.getNonce()
+ response_nonce = self._makeResponseNonce()
+
+ data = self._requestJson(
+ "/inject", "POST",
+ headers=self._getAuthHeaders(nonce, response_nonce),
+ data=self._encryptPayload(nonce, payload))
+ return {"fingerprint": data["fingerprint"]}
diff --git a/lib/lp/services/signing/tests/test_proxy.py b/lib/lp/services/signing/tests/test_proxy.py
index a972ea3..9180267 100644
--- a/lib/lp/services/signing/tests/test_proxy.py
+++ b/lib/lp/services/signing/tests/test_proxy.py
@@ -4,6 +4,7 @@
__metaclass__ = type
import base64
+from datetime import datetime
import json
from fixtures import MockPatch
@@ -131,8 +132,16 @@ class SigningServiceResponseFactory:
body=self._encryptPayload({
'fingerprint': self.generated_fingerprint,
'public-key': self.b64_generated_public_key.decode('utf8')
- }, nonce=response_nonce),
+ }, nonce=response_nonce),
status=201)
+
+ responses.add(
+ responses.POST, self.getUrl("/inject"),
+ body=self._encryptPayload({
+ 'fingerprint': self.generated_fingerprint,
+ }, nonce=response_nonce),
+ status=200)
+
call_counts = {'/sign': 0}
def sign_callback(request):
@@ -316,3 +325,45 @@ class SigningServiceProxyTest(TestCaseWithFactory, TestWithFixtures):
self.assertEqual(
bytes(self.response_factory.generated_public_key),
data['public-key'])
+
+ @responses.activate
+ def test_inject_key(self):
+ """Makes sure that the SigningService.inject method calls the
+ correct endpoints, and actually injects key contents.
+ """
+ self.response_factory.addResponses(self)
+ private_key = PrivateKey.generate()
+ public_key = private_key.public_key
+
+ # Generate the key, and checks if we got back the correct dict.
+ signing = getUtility(ISigningServiceClient)
+ response_data = signing.inject(
+ SigningKeyType.UEFI, private_key, public_key,
+ "This is a test key injected.", datetime.now())
+
+ self.assertEqual(response_data, {
+ 'fingerprint': self.response_factory.generated_fingerprint})
+
+ self.assertEqual(3, len(responses.calls))
+
+ # expected order of HTTP calls
+ http_nonce, http_service_key, http_inject = responses.calls
+
+ self.assertEqual("POST", http_nonce.request.method)
+ self.assertEqual(
+ self.response_factory.getUrl("/nonce"), http_nonce.request.url)
+
+ self.assertEqual("GET", http_service_key.request.method)
+ self.assertEqual(
+ self.response_factory.getUrl("/service-key"),
+ http_service_key.request.url)
+
+ self.assertEqual("POST", http_inject.request.method)
+ self.assertEqual(
+ self.response_factory.getUrl("/inject"),
+ http_inject.request.url)
+ self.assertThat(http_inject.request.headers, ContainsDict({
+ "Content-Type": Equals("application/x-boxed-json"),
+ "X-Client-Public-Key": Equals(config.signing.client_public_key),
+ "X-Nonce": Equals(self.response_factory.b64_nonce)}))
+ self.assertIsNotNone(http_inject.request.body)
diff --git a/lib/lp/services/signing/tests/test_signingkey.py b/lib/lp/services/signing/tests/test_signingkey.py
index 2e0a5df..c4e7533 100644
--- a/lib/lp/services/signing/tests/test_signingkey.py
+++ b/lib/lp/services/signing/tests/test_signingkey.py
@@ -4,8 +4,11 @@
__metaclass__ = type
import base64
+from datetime import datetime
from fixtures.testcase import TestWithFixtures
+from nacl.public import PrivateKey
+from pytz import utc
import responses
from storm.store import Store
from testtools.matchers import MatchesStructure
@@ -61,6 +64,33 @@ class TestSigningKey(TestCaseWithFactory, TestWithFixtures):
self.assertEqual("this is my key", db_key.description)
@responses.activate
+ def test_inject_signing_key_saves_correctly(self):
+ self.signing_service.addResponses(self)
+
+ priv_key = PrivateKey.generate()
+ pub_key = priv_key.public_key
+ created_at = datetime(2020, 4, 16, 16, 35).replace(tzinfo=utc)
+
+ key = SigningKey.inject(
+ SigningKeyType.KMOD, bytes(priv_key), bytes(pub_key),
+ u"This is a test key", created_at)
+ self.assertIsInstance(key, SigningKey)
+
+ store = IMasterStore(SigningKey)
+ store.invalidate()
+
+ rs = store.find(SigningKey)
+ self.assertEqual(1, rs.count())
+ db_key = rs.one()
+
+ self.assertEqual(SigningKeyType.KMOD, db_key.key_type)
+ self.assertEqual(
+ self.signing_service.generated_fingerprint, db_key.fingerprint)
+ self.assertEqual(bytes(pub_key), db_key.public_key)
+ self.assertEqual(u"This is a test key", db_key.description)
+ self.assertEqual(created_at, db_key.date_created)
+
+ @responses.activate
def test_sign_some_data(self):
self.signing_service.addResponses(self)