Skip to content

Commit 5fceef2

Browse files
committed
Add a fingerprint to ManifestSignature
closes #2261
1 parent 0986ab0 commit 5fceef2

7 files changed

Lines changed: 84 additions & 1 deletion

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: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
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+
updated = []
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+
updated.append(sig)
53+
54+
if updated:
55+
ManifestSignature.objects.bulk_update(updated, ["fingerprint"], batch_size=1000)
56+
57+
58+
class Migration(migrations.Migration):
59+
60+
dependencies = [
61+
('container', '0047_containernamespace_pulp_labels'),
62+
]
63+
64+
operations = [
65+
migrations.AddField(
66+
model_name='manifestsignature',
67+
name='fingerprint',
68+
field=models.TextField(db_index=True, null=True),
69+
),
70+
migrations.RunPython(
71+
populate_fingerprint,
72+
reverse_code=migrations.RunPython.noop,
73+
elidable=True,
74+
),
75+
]

pulp_container/app/models.py

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

pulp_container/app/registry_api.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1557,6 +1557,7 @@ def put(self, request, path, pk):
15571557
digest=f"sha256:{sig_digest}",
15581558
type=SIGNATURE_TYPE.ATOMIC_SHORT,
15591559
key_id=signature_json["signing_key_id"],
1560+
fingerprint=signature_json["signing_key_fingerprint"],
15601561
timestamp=signature_json["signature_timestamp"],
15611562
creator=signature_json["optional"].get("creator"),
15621563
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
@@ -129,6 +129,7 @@ async def create_signature(manifest, reference, signing_service):
129129
digest=f"sha256:{sig_digest}",
130130
type=SIGNATURE_TYPE.ATOMIC_SHORT,
131131
key_id=sig_json["signing_key_id"],
132+
fingerprint=sig_json["signing_key_fingerprint"],
132133
timestamp=sig_json["signature_timestamp"],
133134
creator=sig_json["optional"].get("creator"),
134135
data=encoded_sig,

pulp_container/app/tasks/sync_stages.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,7 @@ def _create_signature_declarative_content(
403403
digest=f"sha256:{sig_digest}",
404404
type=SIGNATURE_TYPE.ATOMIC_SHORT,
405405
key_id=signature_json["signing_key_id"],
406+
fingerprint=signature_json["signing_key_fingerprint"],
406407
timestamp=signature_json["signature_timestamp"],
407408
creator=signature_json["optional"].get("creator"),
408409
data=signature_b64 or base64.b64encode(signature_raw).decode(),

0 commit comments

Comments
 (0)