Skip to content

Commit 9ecdf69

Browse files
committed
Update signing key fields to include a version prefix
Assisted By: Claude Opus 4.6
1 parent 0840954 commit 9ecdf69

File tree

10 files changed

+66
-35
lines changed

10 files changed

+66
-35
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Signing fingerprints now use a versioned prefix format (e.g. `v4:<hex>`, `keyid:<hex>`) for
2+
`package_signing_fingerprint` on repositories and `signing_keys` on packages. The repository
3+
serializer validates the format on input.

pulp_rpm/app/migrations/0070_add_rpm_signing_keys.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,26 @@
55

66

77
class Migration(migrations.Migration):
8-
98
dependencies = [
10-
('rpm', '0069_DATA_fix_signing_fingerprint'),
9+
("rpm", "0069_DATA_fix_signing_fingerprint"),
1110
]
1211

1312
operations = [
1413
migrations.AddField(
15-
model_name='package',
16-
name='signing_keys',
17-
field=django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=None, null=True, size=None),
14+
model_name="package",
15+
name="signing_keys",
16+
field=django.contrib.postgres.fields.ArrayField(
17+
base_field=models.TextField(), default=None, null=True, size=None
18+
),
19+
),
20+
migrations.AlterField(
21+
model_name="rpmpackagesigningresult",
22+
name="package_signing_fingerprint",
23+
field=models.TextField(),
24+
),
25+
migrations.AlterField(
26+
model_name="rpmrepository",
27+
name="package_signing_fingerprint",
28+
field=models.TextField(null=True),
1829
),
1930
]

pulp_rpm/app/models/content.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def sign(
3737
Args:
3838
filename: The absolute path to the package to be signed.
3939
env_vars: (optional) Dict of env_vars to be passed to the signing script.
40-
pubkey_fingerprint: The V4 fingerprint that correlates with the private key to use.
40+
pubkey_fingerprint: The raw fingerprint that correlates with the private key to use.
4141
"""
4242
if not pubkey_fingerprint:
4343
raise ValueError("A pubkey_fingerprint must be provided.")
@@ -87,16 +87,16 @@ class RpmPackageSigningResult(BaseModel):
8787
original_package_sha256 (String):
8888
The sha256 digest of the original package artifact.
8989
package_signing_fingerprint (String):
90-
The fingerprint used to sign the package. This value is copied from the repo's
91-
package_signing_fingerprint field.
90+
The prefixed fingerprint used to sign the package (e.g. 'v4:<hex>').
91+
This value is copied from the repo's package_signing_fingerprint field.
9292
9393
Relations:
9494
result_package (ForeignKey):
9595
The resulting package that was signed by @package_signing_fingerprint.
9696
"""
9797

9898
original_package_sha256 = models.TextField(max_length=64)
99-
package_signing_fingerprint = models.TextField(max_length=40)
99+
package_signing_fingerprint = models.TextField()
100100
result_package = models.ForeignKey(Content, on_delete=models.CASCADE)
101101

102102
class Meta:

pulp_rpm/app/models/repository.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,8 @@ class RpmRepository(Repository, AutoAddObjPermsMixin):
208208
package_signing_service (RpmPackageSigningService):
209209
Signing service to be used on package signing operations related to this repository.
210210
package_signing_fingerprint (String):
211-
The V4 fingerprint (160 bits) to be used by @package_signing_service.
211+
The fingerprint to be used by @package_signing_service.
212+
Format: 'v<N>:<hex-fingerprint>' or 'keyid:<16-hex>'.
212213
repo_config (JSON): repo configuration that will be served by distribution
213214
compression_type(pulp_rpm.app.constants.COMPRESSION_TYPES):
214215
Compression type to use for metadata files.
@@ -238,7 +239,7 @@ class RpmRepository(Repository, AutoAddObjPermsMixin):
238239
package_signing_service = models.ForeignKey(
239240
RpmPackageSigningService, on_delete=models.SET_NULL, null=True
240241
)
241-
package_signing_fingerprint = models.TextField(null=True, max_length=40)
242+
package_signing_fingerprint = models.TextField(null=True)
242243
last_sync_details = models.JSONField(default=dict)
243244
retain_package_versions = models.PositiveIntegerField(default=0)
244245

pulp_rpm/app/serializers/package.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from pulpcore.plugin.util import get_domain_pk
2323

2424
from pulp_rpm.app.constants import CR_HEADER_FLAGS
25+
from pulpcore.plugin.serializers import PgpKeyFingerprintField
2526
from pulp_rpm.app.models import Package
2627
from pulp_rpm.app.shared_utils import format_nvra, read_crpackage_from_artifact
2728

@@ -250,7 +251,7 @@ class PackageSerializer(SingleArtifactContentUploadSerializer, ContentChecksumSe
250251
)
251252

252253
signing_keys = serializers.ListField(
253-
child=serializers.CharField(),
254+
child=PgpKeyFingerprintField(),
254255
help_text=_("List of signing key fingerprints used to sign this package. "),
255256
allow_null=True,
256257
required=False,

pulp_rpm/app/serializers/repository.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
RpmRepository,
4242
UlnRemote,
4343
)
44+
from pulpcore.plugin.serializers import PgpKeyFingerprintField
4445
from pulp_rpm.app.schema import COPY_CONFIG_SCHEMA
4546
from urllib.parse import urlparse
4647
from textwrap import dedent
@@ -87,12 +88,12 @@ class RpmRepositorySerializer(RepositorySerializer):
8788
required=False,
8889
allow_null=True,
8990
)
90-
package_signing_fingerprint = serializers.CharField(
91+
package_signing_fingerprint = PgpKeyFingerprintField(
9192
help_text=_(
92-
"The pubkey V4 fingerprint (160 bits) to be passed to the package signing service."
93-
"The signing service will use that on signing operations related to this repository."
93+
"The pubkey fingerprint to be passed to the package signing service. "
94+
"Format: 'v<N>:<hex-fingerprint>' or 'keyid:<16-hex-char>'. "
95+
"Example: 'v4:ABCDEF1234567890ABCDEF1234567890ABCDEF12'."
9496
),
95-
max_length=40,
9697
required=False,
9798
allow_null=True,
9899
default=None,

pulp_rpm/app/tasks/signing.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
from pulp_rpm.app.models.content import RpmPackageSigningResult, RpmPackageSigningService
2323
from pulp_rpm.app.models.package import Package
2424
from pulp_rpm.app.models.repository import RpmRepository
25-
2625
log = logging.getLogger(__name__)
2726

2827

@@ -54,14 +53,16 @@ def _verify_package_fingerprint(path, signing_fingerprint):
5453
f"{completed_process.stderr}."
5554
)
5655

56+
raw_fingerprint = signing_fingerprint.split(":", 1)[1]
57+
5758
# check for `key ID` followed by a string of hex digits
5859
key_ids = re.findall(r"key ID ([0-9A-Fa-f]+)", completed_process.stdout, re.IGNORECASE)
5960
# check for `key fingerprint:` followed by a string of hex digits
6061
fingerprints = re.findall(
6162
r"key fingerprint:\s*([0-9A-Fa-f]+)", completed_process.stdout, re.IGNORECASE
6263
)
6364
for candidate in key_ids + fingerprints:
64-
if signing_fingerprint.lower().endswith(candidate.lower()):
65+
if raw_fingerprint.upper().endswith(candidate.upper()):
6566
return True
6667

6768
return False
@@ -78,7 +79,8 @@ def _update_signing_keys(package_file, keys):
7879

7980
def _sign_file(package_file, signing_service, signing_fingerprint):
8081
"""Sign a package and return the local path of the signed file."""
81-
result = signing_service.sign(package_file.name, pubkey_fingerprint=signing_fingerprint)
82+
raw_fingerprint = signing_fingerprint.split(":", 1)[1]
83+
result = signing_service.sign(package_file.name, pubkey_fingerprint=raw_fingerprint)
8284
signed_package_path = Path(result["rpm_package"])
8385
if not signed_package_path.exists():
8486
raise Exception(f"Signing script did not create the signed package: {result}")

pulp_rpm/tests/functional/api/test_package_signing.py

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,12 @@
99
from pulpcore.exceptions.validation import InvalidSignatureError
1010

1111
from pulp_rpm.app.shared_utils import RpmTool
12-
from pulp_rpm.tests.functional.constants import RPM_PACKAGE_FILENAME, RPM_UNSIGNED_URL
12+
from pulp_rpm.tests.functional.constants import (
13+
RPM_PACKAGE_FILENAME,
14+
RPM_PACKAGE_FILENAME2,
15+
RPM_UNSIGNED_URL,
16+
RPM_UNSIGNED_URL2,
17+
)
1318
from pulp_rpm.tests.functional.utils import get_package_repo_path
1419

1520

@@ -94,17 +99,18 @@ def test_sign_package_on_upload(
9499
# Upload Package to Repository
95100
# The same file is uploaded, but signed with different keys each time
96101
for fingerprint in fingerprint_set:
102+
prefixed_fingerprint = f"v4:{fingerprint}"
97103
repository = rpm_repository_factory(
98104
package_signing_service=rpm_package_signing_service.pulp_href,
99-
package_signing_fingerprint=fingerprint,
105+
package_signing_fingerprint=prefixed_fingerprint,
100106
)
101107
upload_response = rpm_package_api.create(
102108
file=str(file_to_upload.absolute()),
103109
repository=repository.pulp_href,
104110
)
105111
package_href = monitor_task(upload_response.task).created_resources[2]
106112
package = rpm_package_api.read(package_href)
107-
assert package.signing_keys == [fingerprint]
113+
assert package.signing_keys == [prefixed_fingerprint]
108114

109115
# Verify that the final served package is signed
110116
publication = rpm_publication_factory(repository=repository.pulp_href)
@@ -121,7 +127,7 @@ def test_sign_package_on_upload(
121127
repository = rpm_repository_api.read(repository.pulp_href)
122128
assert (
123129
rpm_package_api.list(
124-
repository_version=repository.latest_version_href, signing_key=fingerprint
130+
repository_version=repository.latest_version_href, signing_key=prefixed_fingerprint
125131
).count
126132
== 1
127133
)
@@ -216,17 +222,18 @@ def test_sign_chunked_package_on_upload(
216222
rpm_tool.import_pubkey_string(gpg_a.pubkey)
217223
rpm_tool.import_pubkey_string(gpg_b.pubkey)
218224

219-
file_to_upload = tmp_path / RPM_PACKAGE_FILENAME
220-
file_to_upload.write_bytes(requests.get(RPM_UNSIGNED_URL).content)
225+
file_to_upload = tmp_path / RPM_PACKAGE_FILENAME2
226+
file_to_upload.write_bytes(requests.get(RPM_UNSIGNED_URL2).content)
221227
with pytest.raises(InvalidSignatureError, match="The package is not signed: .*"):
222228
rpm_tool.verify_signature(file_to_upload)
223229

224230
# Upload Package to Repository
225231
# The same file is uploaded, but signed with different keys each time
226232
for fingerprint in fingerprint_set:
233+
prefixed_fingerprint = f"v4:{fingerprint}"
227234
repository = rpm_repository_factory(
228235
package_signing_service=rpm_package_signing_service.pulp_href,
229-
package_signing_fingerprint=fingerprint,
236+
package_signing_fingerprint=prefixed_fingerprint,
230237
)
231238
file_chunks_data = pulpcore_chunked_file_factory(file_to_upload)
232239
size = file_chunks_data["size"]
@@ -240,7 +247,7 @@ def test_sign_chunked_package_on_upload(
240247
)
241248
package_href = monitor_task(upload_response.task).created_resources[2]
242249
package = rpm_package_api.read(package_href)
243-
assert package.signing_keys == [fingerprint]
250+
assert package.signing_keys == [prefixed_fingerprint]
244251

245252
# Verify that the final served package is signed
246253
publication = rpm_publication_factory(repository=repository.pulp_href)
@@ -271,6 +278,7 @@ def test_signed_repo_modify(
271278
"""Ensure packages added via modify are signed before distribution."""
272279

273280
gpg, fingerprint, _ = signing_gpg_metadata
281+
prefixed_fingerprint = f"v4:{fingerprint}"
274282

275283
rpm_tool = RpmTool(tmp_path)
276284
rpm_tool.import_pubkey_string(gpg.export_keys(fingerprint))
@@ -283,7 +291,7 @@ def test_signed_repo_modify(
283291

284292
repository = rpm_repository_factory(
285293
package_signing_service=rpm_package_signing_service.pulp_href,
286-
package_signing_fingerprint=fingerprint,
294+
package_signing_fingerprint=prefixed_fingerprint,
287295
)
288296

289297
created_package = rpm_package_factory(url=RPM_UNSIGNED_URL)
@@ -299,7 +307,7 @@ def test_signed_repo_modify(
299307
repository_version=repository.latest_version_href
300308
).results[0]
301309
assert signed_package.pulp_href != created_package.pulp_href
302-
assert signed_package.signing_keys == [fingerprint]
310+
assert signed_package.signing_keys == [prefixed_fingerprint]
303311
assert signed_package.time_file != created_package.time_file
304312
assert sorted(task_result.created_resources) == sorted(
305313
[repository.latest_version_href, signed_package.pulp_href, signed_package.artifact]
@@ -342,14 +350,15 @@ def test_already_signed_package(
342350
"""Don't sign a package if it's already signed with our key."""
343351

344352
_, fingerprint, _ = signing_gpg_metadata
353+
prefixed_fingerprint = f"v4:{fingerprint}"
345354

346355
repo_one = rpm_repository_factory(
347356
package_signing_service=rpm_package_signing_service.pulp_href,
348-
package_signing_fingerprint=fingerprint,
357+
package_signing_fingerprint=prefixed_fingerprint,
349358
)
350359
repo_two = rpm_repository_factory(
351360
package_signing_service=rpm_package_signing_service.pulp_href,
352-
package_signing_fingerprint=fingerprint,
361+
package_signing_fingerprint=prefixed_fingerprint,
353362
)
354363

355364
created_package = rpm_package_factory(url=RPM_UNSIGNED_URL)
@@ -366,7 +375,7 @@ def test_already_signed_package(
366375
repository_version=repo_one.latest_version_href
367376
).results
368377
signed_package_href = repo_one_packages[0].pulp_href
369-
assert repo_one_packages[0].signing_keys == [fingerprint]
378+
assert repo_one_packages[0].signing_keys == [prefixed_fingerprint]
370379
assert len(repo_one_packages) == 1
371380
assert sorted(task_result.created_resources) == sorted(
372381
[signed_package_href, repo_one_packages[0].artifact, repo_one.latest_version_href]
@@ -402,7 +411,7 @@ def test_signed_repo_rejects_on_demand_content(
402411
_, fingerprint, _ = signing_gpg_metadata
403412
destination_repo = rpm_repository_factory(
404413
package_signing_service=rpm_package_signing_service.pulp_href,
405-
package_signing_fingerprint=fingerprint,
414+
package_signing_fingerprint=f"v4:{fingerprint}",
406415
)
407416

408417
packages = rpm_package_api.list(repository_version=source_repo.latest_version_href).results

pulp_rpm/tests/functional/constants.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,9 @@
365365
RPM_UNSIGNED_URL = urljoin(RPM_UNSIGNED_FIXTURE_URL, RPM_PACKAGE_FILENAME)
366366
"""The path to a single unsigned RPM package."""
367367

368+
RPM_UNSIGNED_URL2 = urljoin(RPM_UNSIGNED_FIXTURE_URL, RPM_PACKAGE_FILENAME2)
369+
"""The path to a second single unsigned RPM package."""
370+
368371
RPM_UPDATED_UPDATEINFO_FIXTURE_URL = urljoin(PULP_FIXTURES_BASE_URL, "rpm-updated-updateinfo/")
369372
"""The URL to a repository containing UpdateRecords (Advisory) with the same IDs
370373
as the ones in the standard repositories, but with different metadata.

pulp_rpm/tests/unit/test_signing.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def fake_run(*args, **kwargs):
1818

1919
monkeypatch.setattr(subprocess, "run", fake_run)
2020
package = SimpleNamespace(name=str(tmp_path / "example.rpm"))
21-
assert _verify_package_fingerprint(package, "c6e7f081cf80e13146676e88829b606631645531")
21+
assert _verify_package_fingerprint(package, "v4:C6E7F081CF80E13146676E88829B606631645531")
2222

2323

2424
def test_verify_package_fingerprint_accepts_long_key_id(monkeypatch, tmp_path):
@@ -35,4 +35,4 @@ def fake_run(*args, **kwargs):
3535

3636
monkeypatch.setattr(subprocess, "run", fake_run)
3737
package = SimpleNamespace(name=str(tmp_path / "example.rpm"))
38-
assert _verify_package_fingerprint(package, "999f7cbf38ab71f4")
38+
assert _verify_package_fingerprint(package, "keyid:999F7CBF38AB71F4")

0 commit comments

Comments
 (0)