Skip to content

Commit f37231e

Browse files
dralleygerrod3
authored andcommitted
Add a fingerprint to ManifestSignature
closes #2261
1 parent 9df3158 commit f37231e

11 files changed

Lines changed: 104 additions & 5 deletions

File tree

CHANGES/2261.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add a signature fingerprint to the ManifestSignature model for improved forwards compatibility with OpenPGP v6
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# Generated by Django 5.2.11 on 2026-04-15 02:37
2+
3+
import base64
4+
import logging
5+
6+
from django.db import migrations, models
7+
8+
from pysequoia.packet import PacketPile, Tag
9+
10+
11+
logger = logging.getLogger(__name__)
12+
13+
14+
def keyid_from_fingerprint(fingerprint):
15+
if len(fingerprint) == 40:
16+
return fingerprint[-16:]
17+
elif len(fingerprint) == 64:
18+
return fingerprint[:16]
19+
else:
20+
raise ValueError(f"Unexpected fingerprint length: {len(fingerprint)}")
21+
22+
23+
def populate_fingerprint(apps, schema_editor):
24+
ManifestSignature = apps.get_model("container", "ManifestSignature")
25+
26+
batch = []
27+
for sig in ManifestSignature.objects.filter(fingerprint__isnull=True).iterator():
28+
try:
29+
signature_raw = base64.b64decode(sig.data)
30+
pile = PacketPile.from_bytes(signature_raw)
31+
except Exception as exc:
32+
logger.warning("Could not parse signature %s, skipping fingerprint extraction", sig.pk)
33+
logger.warning(str(exc))
34+
continue
35+
36+
fingerprint = None
37+
for packet in pile:
38+
if packet.tag == Tag.Signature:
39+
if packet.issuer_fingerprint is not None:
40+
fingerprint = packet.issuer_fingerprint.upper()
41+
break
42+
elif packet.issuer_key_id is not None:
43+
# No fingerprint available, only key_id — nothing new to store
44+
break
45+
46+
if fingerprint:
47+
assert sig.key_id == keyid_from_fingerprint(fingerprint), (
48+
f"Signature {sig.pk}: key_id {sig.key_id} does not match "
49+
f"fingerprint {fingerprint}"
50+
)
51+
sig.fingerprint = fingerprint
52+
batch.append(sig)
53+
54+
if len(batch) > 500:
55+
ManifestSignature.objects.bulk_update(batch, ["fingerprint"])
56+
batch.clear()
57+
58+
if batch:
59+
ManifestSignature.objects.bulk_update(batch, ["fingerprint"])
60+
61+
class Migration(migrations.Migration):
62+
63+
dependencies = [
64+
('container', '0048_containerremote_includes_excludes'),
65+
]
66+
67+
operations = [
68+
migrations.AddField(
69+
model_name='manifestsignature',
70+
name='fingerprint',
71+
field=models.TextField(db_index=True, null=True),
72+
),
73+
migrations.RunPython(
74+
populate_fingerprint,
75+
reverse_code=migrations.RunPython.noop,
76+
elidable=True,
77+
),
78+
]

pulp_container/app/models.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,8 @@ class ManifestSignature(Content):
399399
digest (models.TextField): A signature sha256 digest prepended with its algorithm `sha256:`.
400400
type (models.TextField): A signature type as specified in signature metadata. Currently
401401
it's only "atomic container signature".
402-
key_id (models.TextField): A key id identified by gpg (last 8 bytes of the fingerprint).
402+
key_id (models.TextField): A PGP key id (last 8 bytes of the fingerprint).
403+
fingerprint (models.TextField): A PGP key fingerprint
403404
timestamp (models.PositiveIntegerField): A signature timestamp identified by gpg.
404405
creator (models.TextField): A signature creator.
405406
data (models.TextField): A signature, base64 encoded.
@@ -417,6 +418,7 @@ class ManifestSignature(Content):
417418
digest = models.TextField()
418419
type = models.TextField(choices=SIGNATURE_CHOICES)
419420
key_id = models.TextField(db_index=True)
421+
fingerprint = models.TextField(null=True, db_index=True)
420422
timestamp = models.PositiveIntegerField()
421423
creator = models.TextField(blank=True)
422424
data = models.TextField()

pulp_container/app/registry_api.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1745,6 +1745,7 @@ def put(self, request, path, pk):
17451745
digest=f"sha256:{sig_digest}",
17461746
type=SIGNATURE_TYPE.ATOMIC_SHORT,
17471747
key_id=signature_json["signing_key_id"],
1748+
fingerprint=signature_json["signing_key_fingerprint"],
17481749
timestamp=signature_json["signature_timestamp"],
17491750
creator=signature_json["optional"].get("creator"),
17501751
data=signature_dict["content"],

pulp_container/app/serializers.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ class ManifestSignatureSerializer(NoArtifactContentSerializer):
186186
digest = serializers.CharField(help_text="sha256 digest of the signature blob")
187187
type = serializers.CharField(help_text="Container signature type, e.g. 'atomic'")
188188
key_id = serializers.CharField(help_text="Signing key ID")
189+
fingerprint = serializers.CharField(help_text="Signing key fingerprint", allow_null=True)
189190
timestamp = serializers.IntegerField(help_text="Timestamp of a signature")
190191
creator = serializers.CharField(help_text="Signature creator")
191192
signed_manifest = DetailRelatedField(
@@ -201,6 +202,7 @@ class Meta:
201202
"digest",
202203
"type",
203204
"key_id",
205+
"fingerprint",
204206
"timestamp",
205207
"creator",
206208
"signed_manifest",

pulp_container/app/tasks/sign.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ async def create_signature(manifest, reference, signing_service):
128128
digest=f"sha256:{sig_digest}",
129129
type=SIGNATURE_TYPE.ATOMIC_SHORT,
130130
key_id=sig_json["signing_key_id"],
131+
fingerprint=sig_json["signing_key_fingerprint"],
131132
timestamp=sig_json["signature_timestamp"],
132133
creator=sig_json["optional"].get("creator"),
133134
data=encoded_sig,

pulp_container/app/tasks/sync_stages.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,7 @@ def _create_signature_declarative_content(
484484
digest=f"sha256:{sig_digest}",
485485
type=SIGNATURE_TYPE.ATOMIC_SHORT,
486486
key_id=signature_json["signing_key_id"],
487+
fingerprint=signature_json["signing_key_fingerprint"],
487488
timestamp=signature_json["signature_timestamp"],
488489
creator=signature_json["optional"].get("creator"),
489490
data=signature_b64 or base64.b64encode(signature_raw).decode(),

pulp_container/app/utils.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,10 +125,13 @@ def extract_data_from_signature(signature_raw, man_digest):
125125
elif packet.tag == Tag.Signature:
126126
if packet.issuer_key_id is not None:
127127
signing_key_id = packet.issuer_key_id.upper()
128-
elif packet.issuer_fingerprint is not None:
128+
129+
if packet.issuer_fingerprint is not None:
129130
signing_key_fingerprint = packet.issuer_fingerprint.upper()
130-
signing_key_id = keyid_from_fingerprint(signing_key_fingerprint)
131-
else:
131+
if signing_key_id is None:
132+
signing_key_id = keyid_from_fingerprint(signing_key_fingerprint)
133+
134+
if signing_key_id is None and signing_key_fingerprint is None:
132135
raise ValueError(
133136
"Signature for manifest {} has no fingerprint or key_id".format(man_digest)
134137
)

pulp_container/tests/functional/api/test_push_signatures.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ def test_assert_signed_image(
6363
assert manifest.digest in signature.name
6464
assert signature.signed_manifest == manifest.pulp_href
6565
assert signature.key_id == keyid
66+
assert signature.fingerprint == fingerprint
6667

6768
path = f"/extensions/v2/{full_path(distribution)}/signatures/{manifest.digest}"
6869
response, _ = local_registry.get_response("GET", path)
@@ -77,6 +78,7 @@ def test_assert_signed_image(
7778
decrypted = gpg.decrypt(raw_s)
7879

7980
assert decrypted.key_id == keyid
81+
assert decrypted.fingerprint == fingerprint
8082
assert decrypted.status == "signature valid"
8183

8284
json_s = json.loads(decrypted.data)

pulp_container/tests/functional/api/test_sign_manifests.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def test_sign_manifest(
3232
monitor_task,
3333
):
3434
"""Test whether a user can sign a manifest by leveraging a signing service."""
35-
_, _, keyid = signing_gpg_metadata
35+
_, fingerprint, keyid = signing_gpg_metadata
3636
sign_data = {"manifest_signing_service": container_signing_service.pulp_href}
3737

3838
response = container_push_repository_api.sign(distribution.repository, sign_data)
@@ -49,6 +49,7 @@ def test_sign_manifest(
4949

5050
signature = signatures.results[0]
5151
assert signature.key_id == keyid
52+
assert signature.fingerprint == fingerprint
5253
assert signature.type == SIGNATURE_TYPE.ATOMIC_SHORT
5354

5455
manifest = container_manifest_api.read(tag.tagged_manifest)

0 commit comments

Comments
 (0)