Merge ~lgp171188/launchpad:update-publisher-multi-sign-archive-when-multiple-keys-available into launchpad:master
- Git
- lp:~lgp171188/launchpad
- update-publisher-multi-sign-archive-when-multiple-keys-available
- Merge into master
Proposed by
Guruprasad
Status: | Merged |
---|---|
Approved by: | Guruprasad |
Approved revision: | d340954deb191a7e6ee34bcad2159f3aa5c54e34 |
Merge reported by: | Otto Co-Pilot |
Merged at revision: | not available |
Proposed branch: | ~lgp171188/launchpad:update-publisher-multi-sign-archive-when-multiple-keys-available |
Merge into: | launchpad:master |
Diff against target: |
681 lines (+213/-74) 10 files modified
lib/lp/archivepublisher/archivegpgsigningkey.py (+26/-12) lib/lp/archivepublisher/signing.py (+6/-4) lib/lp/archivepublisher/tests/test_archivegpgsigningkey.py (+61/-4) lib/lp/archivepublisher/tests/test_signing.py (+30/-30) lib/lp/services/signing/interfaces/signingkey.py (+20/-10) lib/lp/services/signing/interfaces/signingserviceclient.py (+6/-5) lib/lp/services/signing/model/signingkey.py (+33/-3) lib/lp/services/signing/proxy.py (+21/-2) lib/lp/services/signing/tests/test_proxy.py (+3/-1) lib/lp/services/signing/tests/test_signingkey.py (+7/-3) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Simone Pelosi | Approve | ||
Review via email: mp+464692@code.launchpad.net |
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.
Description of the change
To post a comment you must log in.
Revision history for this message
Guruprasad (lgp171188) wrote : | # |
> Great work! Looks good to me! I just left some comments related to the
> docstrings
Thanks for the review, Simone! I have addressed them and pushed a new revision. Will merge this MP after the lpci checks pass.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/lib/lp/archivepublisher/archivegpgsigningkey.py b/lib/lp/archivepublisher/archivegpgsigningkey.py |
2 | index f24322d..34267a4 100644 |
3 | --- a/lib/lp/archivepublisher/archivegpgsigningkey.py |
4 | +++ b/lib/lp/archivepublisher/archivegpgsigningkey.py |
5 | @@ -72,16 +72,29 @@ class SignableArchive: |
6 | ) |
7 | |
8 | @cachedproperty |
9 | - def _signing_key(self): |
10 | - """This archive's signing key on the signing service, if any.""" |
11 | + def _signing_keys(self): |
12 | + """This archive's signing keys on the signing service, if any.""" |
13 | if not getFeatureFlag(PUBLISHER_GPG_USES_SIGNING_SERVICE): |
14 | - return None |
15 | - elif self.archive.signing_key_fingerprint is not None: |
16 | - return getUtility(ISigningKeySet).get( |
17 | - SigningKeyType.OPENPGP, self.archive.signing_key_fingerprint |
18 | + return [] |
19 | + |
20 | + signing_keys = [] |
21 | + signing_keys.extend( |
22 | + getUtility(IArchiveSigningKeySet).getOpenPGPSigningKeysForArchive( |
23 | + self.archive, |
24 | ) |
25 | - else: |
26 | - return None |
27 | + ) |
28 | + if self.archive.signing_key_fingerprint is not None: |
29 | + if not any( |
30 | + key.fingerprint == self.archive.signing_key_fingerprint |
31 | + for key in signing_keys |
32 | + ): |
33 | + signing_key = getUtility(ISigningKeySet).get( |
34 | + SigningKeyType.OPENPGP, |
35 | + self.archive.signing_key_fingerprint, |
36 | + ) |
37 | + if signing_key: |
38 | + signing_keys.append(signing_key) |
39 | + return signing_keys |
40 | |
41 | @cachedproperty |
42 | def _secret_key(self): |
43 | @@ -121,12 +134,13 @@ class SignableArchive: |
44 | raise ValueError("Invalid signature mode for GPG: %s" % mode) |
45 | signed = False |
46 | |
47 | - if self._signing_key is not None or self._secret_key is not None: |
48 | + if self._signing_keys or self._secret_key is not None: |
49 | with open(input_path, "rb") as input_file: |
50 | input_content = input_file.read() |
51 | - if self._signing_key is not None: |
52 | + if self._signing_keys: |
53 | try: |
54 | - signature = self._signing_key.sign( |
55 | + signature = getUtility(ISigningKeySet).sign( |
56 | + self._signing_keys, |
57 | input_content, |
58 | os.path.basename(input_path), |
59 | mode=mode, |
60 | @@ -138,7 +152,7 @@ class SignableArchive: |
61 | "Failed to sign archive using signing " |
62 | "service; falling back to local key" |
63 | ) |
64 | - get_property_cache(self)._signing_key = None |
65 | + get_property_cache(self)._signing_keys = None |
66 | if not signed and self._secret_key is not None: |
67 | signature = getUtility(IGPGHandler).signContent( |
68 | input_content, |
69 | diff --git a/lib/lp/archivepublisher/signing.py b/lib/lp/archivepublisher/signing.py |
70 | index 9938a95..899ddf1 100644 |
71 | --- a/lib/lp/archivepublisher/signing.py |
72 | +++ b/lib/lp/archivepublisher/signing.py |
73 | @@ -32,7 +32,10 @@ from lp.registry.interfaces.distroseries import IDistroSeriesSet |
74 | from lp.services.features import getFeatureFlag |
75 | from lp.services.osutils import remove_if_exists |
76 | from lp.services.signing.enums import SigningKeyType |
77 | -from lp.services.signing.interfaces.signingkey import IArchiveSigningKeySet |
78 | +from lp.services.signing.interfaces.signingkey import ( |
79 | + IArchiveSigningKeySet, |
80 | + ISigningKeySet, |
81 | +) |
82 | from lp.soyuz.interfaces.queue import CustomUploadError |
83 | |
84 | PUBLISHER_USES_SIGNING_SERVICE = "archivepublisher.signing_service.enabled" |
85 | @@ -398,10 +401,9 @@ class SigningUpload(CustomUpload): |
86 | |
87 | with open(filename, "rb") as fd: |
88 | content = fd.read() |
89 | - |
90 | try: |
91 | - signed_content = signing_key.sign( |
92 | - content, message_name=os.path.basename(filename) |
93 | + signed_content = getUtility(ISigningKeySet).sign( |
94 | + [signing_key], content, message_name=os.path.basename(filename) |
95 | ) |
96 | except Exception as e: |
97 | if self.logger: |
98 | diff --git a/lib/lp/archivepublisher/tests/test_archivegpgsigningkey.py b/lib/lp/archivepublisher/tests/test_archivegpgsigningkey.py |
99 | index e51077a..ff2ce27 100644 |
100 | --- a/lib/lp/archivepublisher/tests/test_archivegpgsigningkey.py |
101 | +++ b/lib/lp/archivepublisher/tests/test_archivegpgsigningkey.py |
102 | @@ -38,7 +38,10 @@ from lp.services.gpg.tests.test_gpghandler import FakeGenerateKey |
103 | from lp.services.log.logger import BufferLogger |
104 | from lp.services.osutils import write_file |
105 | from lp.services.signing.enums import SigningKeyType, SigningMode |
106 | -from lp.services.signing.interfaces.signingkey import ISigningKeySet |
107 | +from lp.services.signing.interfaces.signingkey import ( |
108 | + IArchiveSigningKeySet, |
109 | + ISigningKeySet, |
110 | +) |
111 | from lp.services.signing.tests.helpers import SigningServiceClientFixture |
112 | from lp.services.twistedsupport.testing import TReqFixture |
113 | from lp.services.twistedsupport.treq import check_status |
114 | @@ -146,14 +149,14 @@ class TestSignableArchiveWithSigningKey(TestCaseWithFactory): |
115 | [ |
116 | mock.call( |
117 | SigningKeyType.OPENPGP, |
118 | - self.archive.signing_key_fingerprint, |
119 | + [self.archive.signing_key_fingerprint], |
120 | "Release", |
121 | b"Release contents", |
122 | SigningMode.DETACHED, |
123 | ), |
124 | mock.call( |
125 | SigningKeyType.OPENPGP, |
126 | - self.archive.signing_key_fingerprint, |
127 | + [self.archive.signing_key_fingerprint], |
128 | "Release", |
129 | b"Release contents", |
130 | SigningMode.CLEAR, |
131 | @@ -169,6 +172,60 @@ class TestSignableArchiveWithSigningKey(TestCaseWithFactory): |
132 | FileContains("signed with key_type=OPENPGP mode=CLEAR"), |
133 | ) |
134 | |
135 | + def test_signRepository_uses_all_OpenPGPG_keys_of_an_archive(self): |
136 | + self.useFixture( |
137 | + FeatureFixture({PUBLISHER_GPG_USES_SIGNING_SERVICE: "on"}) |
138 | + ) |
139 | + current_key = self.factory.makeSigningKey( |
140 | + key_type=SigningKeyType.OPENPGP, |
141 | + fingerprint=self.archive.signing_key_fingerprint, |
142 | + ) |
143 | + new_key = self.factory.makeSigningKey( |
144 | + key_type=SigningKeyType.OPENPGP, |
145 | + # The fingerprint has to be 30 characters long to pass validation. |
146 | + fingerprint="ABCDEFGHIJKLMNOPQRSTUVWXYZ1234", |
147 | + ) |
148 | + self.archive.signing_key_fingerprint = current_key.fingerprint |
149 | + getUtility(IArchiveSigningKeySet).create( |
150 | + self.archive, None, current_key |
151 | + ) |
152 | + getUtility(IArchiveSigningKeySet).create(self.archive, None, new_key) |
153 | + signing_service_client = self.useFixture( |
154 | + SigningServiceClientFixture(self.factory) |
155 | + ) |
156 | + suite_dir = os.path.join( |
157 | + self.archive_root, |
158 | + "dists", |
159 | + self.suite, |
160 | + ) |
161 | + release_path = os.path.join(suite_dir, "Release") |
162 | + write_file(release_path, b"Release contents") |
163 | + logger = BufferLogger() |
164 | + signer = ISignableArchive(self.archive) |
165 | + self.assertTrue(signer.can_sign) |
166 | + self.assertContentEqual( |
167 | + ["Release.gpg", "InRelease"], |
168 | + signer.signRepository(self.suite, log=logger), |
169 | + ) |
170 | + signing_service_client.sign.assert_has_calls( |
171 | + [ |
172 | + mock.call( |
173 | + SigningKeyType.OPENPGP, |
174 | + [current_key.fingerprint, new_key.fingerprint], |
175 | + "Release", |
176 | + b"Release contents", |
177 | + SigningMode.DETACHED, |
178 | + ), |
179 | + mock.call( |
180 | + SigningKeyType.OPENPGP, |
181 | + [current_key.fingerprint, new_key.fingerprint], |
182 | + "Release", |
183 | + b"Release contents", |
184 | + SigningMode.CLEAR, |
185 | + ), |
186 | + ] |
187 | + ) |
188 | + |
189 | def test_signRepository_falls_back_from_signing_service(self): |
190 | # If the signing service fails to sign a file, we fall back to |
191 | # making local signatures if possible. |
192 | @@ -202,7 +259,7 @@ class TestSignableArchiveWithSigningKey(TestCaseWithFactory): |
193 | ) |
194 | signing_service_client.sign.assert_called_once_with( |
195 | SigningKeyType.OPENPGP, |
196 | - self.archive.signing_key_fingerprint, |
197 | + [self.archive.signing_key_fingerprint], |
198 | "Release", |
199 | b"Release contents", |
200 | SigningMode.DETACHED, |
201 | diff --git a/lib/lp/archivepublisher/tests/test_signing.py b/lib/lp/archivepublisher/tests/test_signing.py |
202 | index c352f3d..91e40cc 100644 |
203 | --- a/lib/lp/archivepublisher/tests/test_signing.py |
204 | +++ b/lib/lp/archivepublisher/tests/test_signing.py |
205 | @@ -1955,49 +1955,49 @@ class TestSigningUploadWithSigningService(TestSigningHelpers): |
206 | [ |
207 | call( |
208 | SigningKeyType.UEFI, |
209 | - keys[SigningKeyType.UEFI].fingerprint, |
210 | + [keys[SigningKeyType.UEFI].fingerprint], |
211 | "empty.efi", |
212 | b"a", |
213 | SigningMode.ATTACHED, |
214 | ), |
215 | call( |
216 | SigningKeyType.KMOD, |
217 | - keys[SigningKeyType.KMOD].fingerprint, |
218 | + [keys[SigningKeyType.KMOD].fingerprint], |
219 | "empty.ko", |
220 | b"b", |
221 | SigningMode.DETACHED, |
222 | ), |
223 | call( |
224 | SigningKeyType.OPAL, |
225 | - keys[SigningKeyType.OPAL].fingerprint, |
226 | + [keys[SigningKeyType.OPAL].fingerprint], |
227 | "empty.opal", |
228 | b"c", |
229 | SigningMode.DETACHED, |
230 | ), |
231 | call( |
232 | SigningKeyType.SIPL, |
233 | - keys[SigningKeyType.SIPL].fingerprint, |
234 | + [keys[SigningKeyType.SIPL].fingerprint], |
235 | "empty.sipl", |
236 | b"d", |
237 | SigningMode.DETACHED, |
238 | ), |
239 | call( |
240 | SigningKeyType.FIT, |
241 | - keys[SigningKeyType.FIT].fingerprint, |
242 | + [keys[SigningKeyType.FIT].fingerprint], |
243 | "empty.fit", |
244 | b"e", |
245 | SigningMode.ATTACHED, |
246 | ), |
247 | call( |
248 | SigningKeyType.CV2_KERNEL, |
249 | - keys[SigningKeyType.CV2_KERNEL].fingerprint, |
250 | + [keys[SigningKeyType.CV2_KERNEL].fingerprint], |
251 | "empty.cv2-kernel", |
252 | b"f", |
253 | SigningMode.DETACHED, |
254 | ), |
255 | call( |
256 | SigningKeyType.ANDROID_KERNEL, |
257 | - keys[SigningKeyType.ANDROID_KERNEL].fingerprint, |
258 | + [keys[SigningKeyType.ANDROID_KERNEL].fingerprint], |
259 | "empty.android-kernel", |
260 | b"g", |
261 | SigningMode.DETACHED, |
262 | @@ -2051,49 +2051,49 @@ class TestSigningUploadWithSigningService(TestSigningHelpers): |
263 | [ |
264 | call( |
265 | SigningKeyType.UEFI, |
266 | - keys[SigningKeyType.UEFI].fingerprint, |
267 | + [keys[SigningKeyType.UEFI].fingerprint], |
268 | "empty.efi", |
269 | b"a", |
270 | SigningMode.ATTACHED, |
271 | ), |
272 | call( |
273 | SigningKeyType.KMOD, |
274 | - keys[SigningKeyType.KMOD].fingerprint, |
275 | + [keys[SigningKeyType.KMOD].fingerprint], |
276 | "empty.ko", |
277 | b"b", |
278 | SigningMode.DETACHED, |
279 | ), |
280 | call( |
281 | SigningKeyType.OPAL, |
282 | - keys[SigningKeyType.OPAL].fingerprint, |
283 | + [keys[SigningKeyType.OPAL].fingerprint], |
284 | "empty.opal", |
285 | b"c", |
286 | SigningMode.DETACHED, |
287 | ), |
288 | call( |
289 | SigningKeyType.SIPL, |
290 | - keys[SigningKeyType.SIPL].fingerprint, |
291 | + [keys[SigningKeyType.SIPL].fingerprint], |
292 | "empty.sipl", |
293 | b"d", |
294 | SigningMode.DETACHED, |
295 | ), |
296 | call( |
297 | SigningKeyType.FIT, |
298 | - keys[SigningKeyType.FIT].fingerprint, |
299 | + [keys[SigningKeyType.FIT].fingerprint], |
300 | "empty.fit", |
301 | b"e", |
302 | SigningMode.ATTACHED, |
303 | ), |
304 | call( |
305 | SigningKeyType.CV2_KERNEL, |
306 | - keys[SigningKeyType.CV2_KERNEL].fingerprint, |
307 | + [keys[SigningKeyType.CV2_KERNEL].fingerprint], |
308 | "empty.cv2-kernel", |
309 | b"f", |
310 | SigningMode.DETACHED, |
311 | ), |
312 | call( |
313 | SigningKeyType.ANDROID_KERNEL, |
314 | - keys[SigningKeyType.ANDROID_KERNEL].fingerprint, |
315 | + [keys[SigningKeyType.ANDROID_KERNEL].fingerprint], |
316 | "empty.android-kernel", |
317 | b"g", |
318 | SigningMode.DETACHED, |
319 | @@ -2158,49 +2158,49 @@ class TestSigningUploadWithSigningService(TestSigningHelpers): |
320 | [ |
321 | call( |
322 | SigningKeyType.UEFI, |
323 | - keys[SigningKeyType.UEFI].fingerprint, |
324 | + [keys[SigningKeyType.UEFI].fingerprint], |
325 | "empty.efi", |
326 | b"a", |
327 | SigningMode.ATTACHED, |
328 | ), |
329 | call( |
330 | SigningKeyType.KMOD, |
331 | - keys[SigningKeyType.KMOD].fingerprint, |
332 | + [keys[SigningKeyType.KMOD].fingerprint], |
333 | "empty.ko", |
334 | b"b", |
335 | SigningMode.DETACHED, |
336 | ), |
337 | call( |
338 | SigningKeyType.OPAL, |
339 | - keys[SigningKeyType.OPAL].fingerprint, |
340 | + [keys[SigningKeyType.OPAL].fingerprint], |
341 | "empty.opal", |
342 | b"c", |
343 | SigningMode.DETACHED, |
344 | ), |
345 | call( |
346 | SigningKeyType.SIPL, |
347 | - keys[SigningKeyType.SIPL].fingerprint, |
348 | + [keys[SigningKeyType.SIPL].fingerprint], |
349 | "empty.sipl", |
350 | b"d", |
351 | SigningMode.DETACHED, |
352 | ), |
353 | call( |
354 | SigningKeyType.FIT, |
355 | - keys[SigningKeyType.FIT].fingerprint, |
356 | + [keys[SigningKeyType.FIT].fingerprint], |
357 | "empty.fit", |
358 | b"e", |
359 | SigningMode.ATTACHED, |
360 | ), |
361 | call( |
362 | SigningKeyType.CV2_KERNEL, |
363 | - keys[SigningKeyType.CV2_KERNEL].fingerprint, |
364 | + [keys[SigningKeyType.CV2_KERNEL].fingerprint], |
365 | "empty.cv2-kernel", |
366 | b"f", |
367 | SigningMode.DETACHED, |
368 | ), |
369 | call( |
370 | SigningKeyType.ANDROID_KERNEL, |
371 | - keys[SigningKeyType.ANDROID_KERNEL].fingerprint, |
372 | + [keys[SigningKeyType.ANDROID_KERNEL].fingerprint], |
373 | "empty.android-kernel", |
374 | b"g", |
375 | SigningMode.DETACHED, |
376 | @@ -2323,14 +2323,14 @@ class TestSigningUploadWithSigningService(TestSigningHelpers): |
377 | [ |
378 | call( |
379 | SigningKeyType.KMOD, |
380 | - keys[SigningKeyType.KMOD].fingerprint, |
381 | + [keys[SigningKeyType.KMOD].fingerprint], |
382 | "empty.ko", |
383 | b"some data for 1.0/empty.ko", |
384 | SigningMode.DETACHED, |
385 | ), |
386 | call( |
387 | SigningKeyType.OPAL, |
388 | - keys[SigningKeyType.OPAL].fingerprint, |
389 | + [keys[SigningKeyType.OPAL].fingerprint], |
390 | "empty.opal", |
391 | b"some data for 1.0/empty.opal", |
392 | SigningMode.DETACHED, |
393 | @@ -2410,49 +2410,49 @@ class TestSigningUploadWithSigningService(TestSigningHelpers): |
394 | [ |
395 | call( |
396 | SigningKeyType.UEFI, |
397 | - fingerprints[SigningKeyType.UEFI], |
398 | + [fingerprints[SigningKeyType.UEFI]], |
399 | "empty.efi", |
400 | b"data - 1.0/empty.efi", |
401 | SigningMode.ATTACHED, |
402 | ), |
403 | call( |
404 | SigningKeyType.KMOD, |
405 | - fingerprints[SigningKeyType.KMOD], |
406 | + [fingerprints[SigningKeyType.KMOD]], |
407 | "empty.ko", |
408 | b"data - 1.0/empty.ko", |
409 | SigningMode.DETACHED, |
410 | ), |
411 | call( |
412 | SigningKeyType.OPAL, |
413 | - fingerprints[SigningKeyType.OPAL], |
414 | + [fingerprints[SigningKeyType.OPAL]], |
415 | "empty.opal", |
416 | b"data - 1.0/empty.opal", |
417 | SigningMode.DETACHED, |
418 | ), |
419 | call( |
420 | SigningKeyType.SIPL, |
421 | - fingerprints[SigningKeyType.SIPL], |
422 | + [fingerprints[SigningKeyType.SIPL]], |
423 | "empty.sipl", |
424 | b"data - 1.0/empty.sipl", |
425 | SigningMode.DETACHED, |
426 | ), |
427 | call( |
428 | SigningKeyType.FIT, |
429 | - fingerprints[SigningKeyType.FIT], |
430 | + [fingerprints[SigningKeyType.FIT]], |
431 | "empty.fit", |
432 | b"data - 1.0/empty.fit", |
433 | SigningMode.ATTACHED, |
434 | ), |
435 | call( |
436 | SigningKeyType.CV2_KERNEL, |
437 | - fingerprints[SigningKeyType.CV2_KERNEL], |
438 | + [fingerprints[SigningKeyType.CV2_KERNEL]], |
439 | "empty.cv2-kernel", |
440 | b"data - 1.0/empty.cv2-kernel", |
441 | SigningMode.DETACHED, |
442 | ), |
443 | call( |
444 | SigningKeyType.ANDROID_KERNEL, |
445 | - fingerprints[SigningKeyType.ANDROID_KERNEL], |
446 | + [fingerprints[SigningKeyType.ANDROID_KERNEL]], |
447 | "empty.android-kernel", |
448 | b"data - 1.0/empty.android-kernel", |
449 | SigningMode.DETACHED, |
450 | diff --git a/lib/lp/services/signing/interfaces/signingkey.py b/lib/lp/services/signing/interfaces/signingkey.py |
451 | index a99e018..b28d703 100644 |
452 | --- a/lib/lp/services/signing/interfaces/signingkey.py |
453 | +++ b/lib/lp/services/signing/interfaces/signingkey.py |
454 | @@ -44,16 +44,6 @@ class ISigningKey(Interface): |
455 | title=_("When this key was created"), required=True, readonly=True |
456 | ) |
457 | |
458 | - def sign(message, message_name, mode=None): |
459 | - """Sign the given message using this key |
460 | - |
461 | - :param message: The message to be signed. |
462 | - :param message_name: A name for the message being signed. |
463 | - :param mode: A `SigningMode` specifying how the message is to be |
464 | - signed. Defaults to `SigningMode.ATTACHED` for UEFI and FIT |
465 | - keys, and `SigningMode.DETACHED` for other key types. |
466 | - """ |
467 | - |
468 | def addAuthorization(client_name): |
469 | """Authorize another client to use this key. |
470 | |
471 | @@ -101,6 +91,19 @@ class ISigningKeySet(Interface): |
472 | key at lp-signing |
473 | """ |
474 | |
475 | + def sign(signing_keys, message, message_name, mode=None): |
476 | + """Sign the given message using the given keys |
477 | + |
478 | + :param signing_keys: A list of one or more signing keys to sign |
479 | + the given message with. If more than one signing key is provided, |
480 | + all signing keys must be of the same type. |
481 | + :param message: The message to be signed. |
482 | + :param message_name: A name for the message being signed. |
483 | + :param mode: A `SigningMode` specifying how the message is to be |
484 | + signed. Defaults to `SigningMode.ATTACHED` for UEFI and FIT |
485 | + keys, and `SigningMode.DETACHED` for other key types. |
486 | + """ |
487 | + |
488 | |
489 | class IArchiveSigningKey(Interface): |
490 | """Which signing key should be used by a specific archive""" |
491 | @@ -162,6 +165,13 @@ class IArchiveSigningKeySet(Interface): |
492 | :return: The most suitable key, or None. |
493 | """ |
494 | |
495 | + def getOpenPGPSigningKeysForArchive(archive): |
496 | + """Find and return the OpenPGP signing keys for the given archive. |
497 | + |
498 | + :param archive: The archive to get the OpenPGP signing keys for. |
499 | + :return: A list of matching signing keys or an empty list. |
500 | + """ |
501 | + |
502 | def getByArchiveAndFingerprint(archive, fingerprint): |
503 | """Get ArchiveSigningKey by archive and fingerprint. |
504 | |
505 | diff --git a/lib/lp/services/signing/interfaces/signingserviceclient.py b/lib/lp/services/signing/interfaces/signingserviceclient.py |
506 | index 9077d9c..493164c 100644 |
507 | --- a/lib/lp/services/signing/interfaces/signingserviceclient.py |
508 | +++ b/lib/lp/services/signing/interfaces/signingserviceclient.py |
509 | @@ -33,13 +33,14 @@ class ISigningServiceClient(Interface): |
510 | :return: A dict with 'fingerprint' (str) and 'public-key' (bytes) |
511 | """ |
512 | |
513 | - def sign(key_type, fingerprint, message_name, message, mode): |
514 | - """Sign the given message using the specified key_type and a |
515 | - pre-generated fingerprint (see `generate` method). |
516 | + def sign(key_type, fingerprints, message_name, message, mode): |
517 | + """Sign the given message using the specified key_type and the |
518 | + given pre-generated fingerprints (see`generate` method). |
519 | |
520 | :param key_type: One of the key types from SigningKeyType enum |
521 | - :param fingerprint: The fingerprint of the signing key, generated by |
522 | - the `generate` method |
523 | + :param fingerprints: A list of the fingerprints of one or more |
524 | + signing keys, generated by the `generate` |
525 | + method. |
526 | :param message_name: A description of the message being signed |
527 | :param message: The message to be signed |
528 | :param mode: SigningMode.ATTACHED or SigningMode.DETACHED |
529 | diff --git a/lib/lp/services/signing/model/signingkey.py b/lib/lp/services/signing/model/signingkey.py |
530 | index 4bb96c3..b51a081 100644 |
531 | --- a/lib/lp/services/signing/model/signingkey.py |
532 | +++ b/lib/lp/services/signing/model/signingkey.py |
533 | @@ -133,15 +133,24 @@ class SigningKey(StormBase): |
534 | store.add(db_key) |
535 | return db_key |
536 | |
537 | - def sign(self, message, message_name, mode=None): |
538 | + @classmethod |
539 | + def sign(cls, signing_keys, message, message_name, mode=None): |
540 | + fingerprints = [key.fingerprint for key in signing_keys] |
541 | + key_type = signing_keys[0].key_type |
542 | + if len(signing_keys) > 1 and not all( |
543 | + key.key_type == key_type for key in signing_keys[1:] |
544 | + ): |
545 | + raise ValueError( |
546 | + "Cannot sign as all the keys are not of the same type." |
547 | + ) |
548 | if mode is None: |
549 | - if self.key_type in (SigningKeyType.UEFI, SigningKeyType.FIT): |
550 | + if key_type in (SigningKeyType.UEFI, SigningKeyType.FIT): |
551 | mode = SigningMode.ATTACHED |
552 | else: |
553 | mode = SigningMode.DETACHED |
554 | signing_service = getUtility(ISigningServiceClient) |
555 | signed = signing_service.sign( |
556 | - self.key_type, self.fingerprint, message_name, message, mode |
557 | + key_type, fingerprints, message_name, message, mode |
558 | ) |
559 | return signed["signed-message"] |
560 | |
561 | @@ -248,6 +257,27 @@ class ArchiveSigningKeySet: |
562 | ) |
563 | |
564 | @classmethod |
565 | + def getOpenPGPSigningKeysForArchive(cls, archive): |
566 | + join = ( |
567 | + ArchiveSigningKey, |
568 | + Join( |
569 | + SigningKey, |
570 | + SigningKey.id == ArchiveSigningKey.signing_key_id, |
571 | + ), |
572 | + ) |
573 | + |
574 | + results = list( |
575 | + IStore(ArchiveSigningKey) |
576 | + .using(*join) |
577 | + .find( |
578 | + SigningKey, |
579 | + ArchiveSigningKey.archive == archive, |
580 | + ArchiveSigningKey.key_type == SigningKeyType.OPENPGP, |
581 | + ) |
582 | + ) |
583 | + return results |
584 | + |
585 | + @classmethod |
586 | def getByArchiveAndFingerprint(cls, archive, fingerprint): |
587 | join = ( |
588 | ArchiveSigningKey, |
589 | diff --git a/lib/lp/services/signing/proxy.py b/lib/lp/services/signing/proxy.py |
590 | index 70fefa1..43d01be 100644 |
591 | --- a/lib/lp/services/signing/proxy.py |
592 | +++ b/lib/lp/services/signing/proxy.py |
593 | @@ -178,7 +178,7 @@ class SigningServiceClient: |
594 | "public-key": base64.b64decode(ret["public-key"].encode("UTF-8")), |
595 | } |
596 | |
597 | - def sign(self, key_type, fingerprint, message_name, message, mode): |
598 | + def sign(self, key_type, fingerprints, message_name, message, mode): |
599 | valid_modes = {SigningMode.ATTACHED, SigningMode.DETACHED} |
600 | if key_type == SigningKeyType.OPENPGP: |
601 | valid_modes.add(SigningMode.CLEAR) |
602 | @@ -186,7 +186,20 @@ class SigningServiceClient: |
603 | raise ValueError("%s is not a valid mode" % mode) |
604 | if key_type not in SigningKeyType.items: |
605 | raise ValueError("%s is not a valid key type" % key_type) |
606 | + if not fingerprints: |
607 | + raise ValueError("Not even one fingerprint was provided") |
608 | + if len(fingerprints) > 1 and key_type != SigningKeyType.OPENPGP: |
609 | + raise ValueError( |
610 | + "Multi-signing is not supported for non-OpenPGP keys" |
611 | + ) |
612 | |
613 | + # The signing service accepts either a single fingerprint |
614 | + # string (for all key types) or a list of two or more |
615 | + # fingerprints (only for OpenPGP keys) for the 'fingerprint' |
616 | + # property. |
617 | + fingerprint = ( |
618 | + fingerprints[0] if len(fingerprints) == 1 else fingerprints |
619 | + ) |
620 | payload = { |
621 | "key-type": key_type.name, |
622 | "fingerprint": fingerprint, |
623 | @@ -196,8 +209,14 @@ class SigningServiceClient: |
624 | } |
625 | |
626 | ret = self._requestJson("/sign", "POST", encrypt=True, json=payload) |
627 | + if isinstance(ret["public-key"], str): |
628 | + public_key = base64.b64decode(ret["public-key"].encode("UTF-8")) |
629 | + else: # is a list of public key strings |
630 | + public_key = [ |
631 | + base64.b64decode(x).encode("UTF-8") for x in ret["public-key"] |
632 | + ] |
633 | return { |
634 | - "public-key": base64.b64decode(ret["public-key"].encode("UTF-8")), |
635 | + "public-key": public_key, |
636 | "signed-message": base64.b64decode( |
637 | ret["signed-message"].encode("UTF-8") |
638 | ), |
639 | diff --git a/lib/lp/services/signing/tests/test_proxy.py b/lib/lp/services/signing/tests/test_proxy.py |
640 | index 78ba505..c639f7e 100644 |
641 | --- a/lib/lp/services/signing/tests/test_proxy.py |
642 | +++ b/lib/lp/services/signing/tests/test_proxy.py |
643 | @@ -543,7 +543,9 @@ class SigningServiceProxyTest(TestCaseWithFactory, TestWithFixtures): |
644 | message = b"this is the message content" |
645 | |
646 | signing = getUtility(ISigningServiceClient) |
647 | - data = signing.sign(key_type, fingerprint, message_name, message, mode) |
648 | + data = signing.sign( |
649 | + key_type, [fingerprint], message_name, message, mode |
650 | + ) |
651 | |
652 | self.assertEqual(3, len(responses.calls)) |
653 | # expected order of HTTP calls |
654 | diff --git a/lib/lp/services/signing/tests/test_signingkey.py b/lib/lp/services/signing/tests/test_signingkey.py |
655 | index 327de1e..686456d 100644 |
656 | --- a/lib/lp/services/signing/tests/test_signingkey.py |
657 | +++ b/lib/lp/services/signing/tests/test_signingkey.py |
658 | @@ -165,7 +165,8 @@ class TestSigningKey(TestCaseWithFactory, TestWithFixtures): |
659 | bytes(self.signing_service.generated_public_key), |
660 | description="This is my key!", |
661 | ) |
662 | - signed = s.sign(b"secure message", "message_name") |
663 | + signing_key_set = getUtility(ISigningKeySet) |
664 | + signed = signing_key_set.sign([s], b"secure message", "message_name") |
665 | |
666 | # Checks if the returned value is actually the returning value from |
667 | # HTTP POST /sign call to lp-signing service |
668 | @@ -204,8 +205,11 @@ class TestSigningKey(TestCaseWithFactory, TestWithFixtures): |
669 | bytes(self.signing_service.generated_public_key), |
670 | description="This is my key!", |
671 | ) |
672 | - s.sign(b"secure message", "message_name") |
673 | - s.sign(b"another message", "another_name", mode=SigningMode.CLEAR) |
674 | + signing_key_set = getUtility(ISigningKeySet) |
675 | + signing_key_set.sign([s], b"secure message", "message_name") |
676 | + signing_key_set.sign( |
677 | + [s], b"another message", "another_name", mode=SigningMode.CLEAR |
678 | + ) |
679 | |
680 | self.assertEqual(5, len(responses.calls)) |
681 | self.assertThat( |
Great work! Looks good to me! I just left some comments related to the docstrings