Merge ~lgp171188/launchpad:signing-service-proxy-typo-fix into launchpad:master

Proposed by Guruprasad
Status: Merged
Approved by: Guruprasad
Approved revision: acad50b2079273df54ddec08552179a20e094862
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~lgp171188/launchpad:signing-service-proxy-typo-fix
Merge into: launchpad:master
Diff against target: 287 lines (+206/-7)
2 files modified
lib/lp/services/signing/proxy.py (+7/-2)
lib/lp/services/signing/tests/test_proxy.py (+199/-5)
Reviewer Review Type Date Requested Status
Ines Almeida Approve
Review via email: mp+464957@code.launchpad.net

Commit message

Fix a typo in SigningServiceClient.sign

Also add tests for the SigningServiceClient to verify
the recent changes to this method.

To post a comment you must log in.
Revision history for this message
Ines Almeida (ines-almeida) wrote :

LGTM!
No blocking comments, just a few suggestions.

review: Approve
Revision history for this message
Guruprasad (lgp171188) :
Revision history for this message
Guruprasad (lgp171188) :

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/lib/lp/services/signing/proxy.py b/lib/lp/services/signing/proxy.py
2index 43d01be..45b254c 100644
3--- a/lib/lp/services/signing/proxy.py
4+++ b/lib/lp/services/signing/proxy.py
5@@ -186,8 +186,13 @@ class SigningServiceClient:
6 raise ValueError("%s is not a valid mode" % mode)
7 if key_type not in SigningKeyType.items:
8 raise ValueError("%s is not a valid key type" % key_type)
9+ if not isinstance(fingerprints, list):
10+ raise ValueError(
11+ "Expected a list of fingerprints but got "
12+ f"{type(fingerprints)} instead"
13+ )
14 if not fingerprints:
15- raise ValueError("Not even one fingerprint was provided")
16+ raise ValueError("At least one fingerprint must be provided")
17 if len(fingerprints) > 1 and key_type != SigningKeyType.OPENPGP:
18 raise ValueError(
19 "Multi-signing is not supported for non-OpenPGP keys"
20@@ -213,7 +218,7 @@ class SigningServiceClient:
21 public_key = base64.b64decode(ret["public-key"].encode("UTF-8"))
22 else: # is a list of public key strings
23 public_key = [
24- base64.b64decode(x).encode("UTF-8") for x in ret["public-key"]
25+ base64.b64decode(x.encode("UTF-8")) for x in ret["public-key"]
26 ]
27 return {
28 "public-key": public_key,
29diff --git a/lib/lp/services/signing/tests/test_proxy.py b/lib/lp/services/signing/tests/test_proxy.py
30index c639f7e..f5d6069 100644
31--- a/lib/lp/services/signing/tests/test_proxy.py
32+++ b/lib/lp/services/signing/tests/test_proxy.py
33@@ -19,6 +19,7 @@ from testtools.matchers import (
34 MatchesListwise,
35 MatchesStructure,
36 )
37+from testtools.testcase import ExpectedException
38 from zope.component import getUtility
39 from zope.security.proxy import removeSecurityProxy
40
41@@ -69,6 +70,10 @@ class SigningServiceResponseFactory:
42 self.b64_generated_public_key = base64.b64encode(
43 bytes(self.generated_public_key)
44 ).decode("UTF-8")
45+ self.generated_public_key_2 = PrivateKey.generate().public_key
46+ self.b64_generated_public_key_2 = base64.b64encode(
47+ bytes(self.generated_public_key_2)
48+ ).decode("UTF-8")
49 self.generated_fingerprint = "338D218488DFD597D8FCB9C328C3E9D9ADA16CEE"
50 self.fingerprint_generator = fingerprint_generator
51
52@@ -194,11 +199,24 @@ class SigningServiceResponseFactory:
53 call_counts["/sign"] += 1
54 signed_msg = self.signed_msg_template % call_counts["/sign"]
55 signed = base64.b64encode(signed_msg)
56- data = {
57+ payload = self._decryptPayload(request.body)
58+
59+ if isinstance(payload["fingerprint"], list):
60+ public_key = [
61+ self.b64_generated_public_key,
62+ self.b64_generated_public_key_2,
63+ ]
64+ else:
65+ public_key = self.b64_generated_public_key
66+ response_payload = {
67 "signed-message": signed.decode("utf8"),
68- "public-key": self.b64_generated_public_key,
69+ "public-key": public_key,
70 }
71- return 201, {}, self._encryptPayload(data, self.response_nonce)
72+ return (
73+ 201,
74+ {},
75+ self._encryptPayload(response_payload, self.response_nonce),
76+ )
77
78 responses.add_callback(
79 responses.POST, self.getUrl("/sign"), callback=sign_callback
80@@ -508,7 +526,7 @@ class SigningServiceProxyTest(TestCaseWithFactory, TestWithFixtures):
81 ValueError,
82 signing.sign,
83 SigningKeyType.UEFI,
84- "fingerprint",
85+ ["fingerprint"],
86 "message_name",
87 "message",
88 "NO-MODE",
89@@ -522,7 +540,7 @@ class SigningServiceProxyTest(TestCaseWithFactory, TestWithFixtures):
90 ValueError,
91 signing.sign,
92 "shrug",
93- "fingerprint",
94+ ["fingerprint"],
95 "message_name",
96 "message",
97 SigningMode.ATTACHED,
98@@ -530,6 +548,70 @@ class SigningServiceProxyTest(TestCaseWithFactory, TestWithFixtures):
99 self.assertEqual(0, len(responses.calls))
100
101 @responses.activate
102+ def test_sign_fingerprints_string_instead_of_list(self):
103+ signing = getUtility(ISigningServiceClient)
104+ with ExpectedException(
105+ ValueError, "Expected a list of fingerprints but got"
106+ ):
107+ signing.sign(
108+ SigningKeyType.UEFI,
109+ "fingerprint",
110+ "message_name",
111+ "message",
112+ SigningMode.ATTACHED,
113+ )
114+ self.assertEqual(0, len(responses.calls))
115+
116+ @responses.activate
117+ def test_sign_empty_list_of_fingerprints(self):
118+ signing = getUtility(ISigningServiceClient)
119+ with ExpectedException(
120+ ValueError, "At least one fingerprint must be provided"
121+ ):
122+ signing.sign(
123+ SigningKeyType.OPENPGP,
124+ [],
125+ "message_name",
126+ "message",
127+ SigningMode.ATTACHED,
128+ )
129+ self.assertRaises(
130+ ValueError,
131+ signing.sign,
132+ SigningKeyType.OPENPGP,
133+ [],
134+ "message_name",
135+ "message",
136+ SigningMode.CLEAR,
137+ )
138+ self.assertEqual(0, len(responses.calls))
139+
140+ @responses.activate
141+ def test_sign_multiple_fingerprints_non_openpgp(self):
142+ signing = getUtility(ISigningServiceClient)
143+ other_key_types = (
144+ SigningKeyType.ANDROID_KERNEL,
145+ SigningKeyType.CV2_KERNEL,
146+ SigningKeyType.FIT,
147+ SigningKeyType.KMOD,
148+ SigningKeyType.OPAL,
149+ SigningKeyType.SIPL,
150+ SigningKeyType.UEFI,
151+ )
152+ for key_type in other_key_types:
153+ with ExpectedException(
154+ ValueError,
155+ "Multi-signing is not supported for non-OpenPGP keys",
156+ ):
157+ signing.sign(
158+ key_type,
159+ ["fingerprint1", "fingerprint2"],
160+ "message_name",
161+ "message",
162+ SigningMode.ATTACHED,
163+ )
164+
165+ @responses.activate
166 def test_sign(self):
167 """Runs through SignService.sign() flow"""
168 # Replace GET /service-key response by our mock.
169@@ -634,6 +716,118 @@ class SigningServiceProxyTest(TestCaseWithFactory, TestWithFixtures):
170 )
171
172 @responses.activate
173+ def test_multi_sign_openpgp(self):
174+ """
175+ Test the process of multi-signing with OpenPGP keys.
176+ """
177+ response_factory = self.response_factory
178+ response_factory.addResponses(self)
179+ fingerprints = [
180+ self.factory.getUniqueHexString(40).upper() for _ in range(2)
181+ ]
182+
183+ key_type = SigningKeyType.OPENPGP
184+ mode = SigningMode.DETACHED
185+ message_name = "my test msg"
186+ message = b"this is the message content"
187+
188+ signing = getUtility(ISigningServiceClient)
189+ data = signing.sign(
190+ key_type, fingerprints, message_name, message, mode
191+ )
192+ self.assertEqual(3, len(responses.calls))
193+ # expected order of HTTP calls
194+ http_nonce, http_service_key, http_sign = responses.calls
195+
196+ self.assertEqual("POST", http_nonce.request.method)
197+ self.assertEqual(
198+ self.response_factory.getUrl("/nonce"), http_nonce.request.url
199+ )
200+
201+ self.assertEqual("GET", http_service_key.request.method)
202+ self.assertEqual(
203+ self.response_factory.getUrl("/service-key"),
204+ http_service_key.request.url,
205+ )
206+
207+ self.assertEqual("POST", http_sign.request.method)
208+ self.assertEqual(
209+ self.response_factory.getUrl("/sign"), http_sign.request.url
210+ )
211+ self.assertThat(
212+ http_sign.request.headers,
213+ ContainsDict(
214+ {
215+ "Content-Type": Equals("application/x-boxed-json"),
216+ "X-Client-Public-Key": Equals(
217+ config.signing.client_public_key
218+ ),
219+ "X-Nonce": Equals(self.response_factory.b64_nonce),
220+ "X-Response-Nonce": Equals(
221+ self.response_factory.b64_response_nonce
222+ ),
223+ }
224+ ),
225+ )
226+ self.assertThat(
227+ http_sign.request.body,
228+ AfterPreprocessing(
229+ self.response_factory._decryptPayload,
230+ MatchesDict(
231+ {
232+ "key-type": Equals("OPENPGP"),
233+ "fingerprint": Equals(fingerprints),
234+ "message-name": Equals(message_name),
235+ "message": Equals(
236+ base64.b64encode(message).decode("UTF-8")
237+ ),
238+ "mode": Equals("DETACHED"),
239+ }
240+ ),
241+ ),
242+ )
243+
244+ self.assertTimeline(
245+ [
246+ ("POST", "/nonce", {}),
247+ ("GET", "/service-key", {}),
248+ (
249+ "POST",
250+ "/sign",
251+ {
252+ "headers": {
253+ "Content-Type": "application/x-boxed-json",
254+ "X-Client-Public-Key": (
255+ config.signing.client_public_key
256+ ),
257+ "X-Nonce": self.response_factory.b64_nonce,
258+ "X-Response-Nonce": (
259+ self.response_factory.b64_response_nonce
260+ ),
261+ },
262+ },
263+ ),
264+ ]
265+ )
266+
267+ # It should have returned the correct JSON content, with signed
268+ # message from the API and the public-key.
269+ self.assertEqual(2, len(data))
270+ public_key = removeSecurityProxy(data["public-key"])
271+ self.assertTrue(isinstance(public_key, list))
272+ self.assertEqual(2, len(public_key))
273+ self.assertEqual(
274+ self.response_factory.getAPISignedContent(), data["signed-message"]
275+ )
276+ self.assertEqual(
277+ [
278+ bytes(self.response_factory.generated_public_key),
279+ bytes(self.response_factory.generated_public_key_2),
280+ ],
281+ public_key,
282+ )
283+
284+ @responses.activate
285 def test_inject_key(self):
286 """Makes sure that the SigningService.inject method calls the
287 correct endpoints, and actually injects key contents.

Subscribers

People subscribed via source and target branches

to status/vote changes: