Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES/2261.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add a signature fingerprint to the ManifestSignature model for improved forwards compatibility with OpenPGP v6
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Generated by Django 5.2.11 on 2026-04-15 02:37

import base64
import logging

from django.db import migrations, models

from pysequoia.packet import PacketPile, Tag


logger = logging.getLogger(__name__)


def keyid_from_fingerprint(fingerprint):
if len(fingerprint) == 40:
return fingerprint[-16:]
elif len(fingerprint) == 64:
return fingerprint[:16]
else:
raise ValueError(f"Unexpected fingerprint length: {len(fingerprint)}")


def populate_fingerprint(apps, schema_editor):
ManifestSignature = apps.get_model("container", "ManifestSignature")

updated = []
for sig in ManifestSignature.objects.filter(fingerprint__isnull=True).iterator():
try:
signature_raw = base64.b64decode(sig.data)
pile = PacketPile.from_bytes(signature_raw)
except Exception as exc:
logger.warning("Could not parse signature %s, skipping fingerprint extraction", sig.pk)
logger.warning(str(exc))
continue

fingerprint = None
for packet in pile:
if packet.tag == Tag.Signature:
if packet.issuer_fingerprint is not None:
fingerprint = packet.issuer_fingerprint.upper()
break
elif packet.issuer_key_id is not None:
# No fingerprint available, only key_id — nothing new to store
break

if fingerprint:
assert sig.key_id == keyid_from_fingerprint(fingerprint), (
f"Signature {sig.pk}: key_id {sig.key_id} does not match "
f"fingerprint {fingerprint}"
)
sig.fingerprint = fingerprint
updated.append(sig)

if updated:
ManifestSignature.objects.bulk_update(updated, ["fingerprint"], batch_size=1000)


class Migration(migrations.Migration):

dependencies = [
('container', '0047_containernamespace_pulp_labels'),
]

operations = [
migrations.AddField(
model_name='manifestsignature',
name='fingerprint',
field=models.TextField(db_index=True, null=True),
),
migrations.RunPython(
populate_fingerprint,
reverse_code=migrations.RunPython.noop,
elidable=True,
),
]
4 changes: 3 additions & 1 deletion pulp_container/app/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,8 @@ class ManifestSignature(Content):
digest (models.TextField): A signature sha256 digest prepended with its algorithm `sha256:`.
type (models.TextField): A signature type as specified in signature metadata. Currently
it's only "atomic container signature".
key_id (models.TextField): A key id identified by gpg (last 8 bytes of the fingerprint).
key_id (models.TextField): A PGP key id (last 8 bytes of the fingerprint).
fingerprint (models.TextField): A PGP key fingerprint
timestamp (models.PositiveIntegerField): A signature timestamp identified by gpg.
creator (models.TextField): A signature creator.
data (models.TextField): A signature, base64 encoded.
Expand All @@ -416,6 +417,7 @@ class ManifestSignature(Content):
digest = models.TextField()
type = models.TextField(choices=SIGNATURE_CHOICES)
key_id = models.TextField(db_index=True)
fingerprint = models.TextField(null=True, db_index=True)
timestamp = models.PositiveIntegerField()
creator = models.TextField(blank=True)
data = models.TextField()
Expand Down
1 change: 1 addition & 0 deletions pulp_container/app/registry_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -1557,6 +1557,7 @@ def put(self, request, path, pk):
digest=f"sha256:{sig_digest}",
type=SIGNATURE_TYPE.ATOMIC_SHORT,
key_id=signature_json["signing_key_id"],
fingerprint=signature_json["signing_key_fingerprint"],
timestamp=signature_json["signature_timestamp"],
creator=signature_json["optional"].get("creator"),
data=signature_dict["content"],
Expand Down
2 changes: 2 additions & 0 deletions pulp_container/app/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ class ManifestSignatureSerializer(NoArtifactContentSerializer):
digest = serializers.CharField(help_text="sha256 digest of the signature blob")
type = serializers.CharField(help_text="Container signature type, e.g. 'atomic'")
key_id = serializers.CharField(help_text="Signing key ID")
fingerprint = serializers.CharField(help_text="Signing key fingerprint", allow_null=True)
timestamp = serializers.IntegerField(help_text="Timestamp of a signature")
creator = serializers.CharField(help_text="Signature creator")
signed_manifest = DetailRelatedField(
Expand All @@ -201,6 +202,7 @@ class Meta:
"digest",
"type",
"key_id",
"fingerprint",
"timestamp",
"creator",
"signed_manifest",
Expand Down
1 change: 1 addition & 0 deletions pulp_container/app/tasks/sign.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ async def create_signature(manifest, reference, signing_service):
digest=f"sha256:{sig_digest}",
type=SIGNATURE_TYPE.ATOMIC_SHORT,
key_id=sig_json["signing_key_id"],
fingerprint=sig_json["signing_key_fingerprint"],
timestamp=sig_json["signature_timestamp"],
creator=sig_json["optional"].get("creator"),
data=encoded_sig,
Expand Down
1 change: 1 addition & 0 deletions pulp_container/app/tasks/sync_stages.py
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,7 @@ def _create_signature_declarative_content(
digest=f"sha256:{sig_digest}",
type=SIGNATURE_TYPE.ATOMIC_SHORT,
key_id=signature_json["signing_key_id"],
fingerprint=signature_json["signing_key_fingerprint"],
timestamp=signature_json["signature_timestamp"],
creator=signature_json["optional"].get("creator"),
data=signature_b64 or base64.b64encode(signature_raw).decode(),
Expand Down
Loading