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
16 changes: 16 additions & 0 deletions pulp_deb/tests/functional/api/test_crud_remotes.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,29 @@
from uuid import uuid4

import pytest
import requests

from pulpcore.client.pulp_deb.exceptions import ApiException
from pulpcore.pytest_plugin import KEY_V6_ED25519_PUBLIC, KEY_V6_MLDSA65_ED25519_PUBLIC

from pulp_deb.tests.functional.constants import DOWNLOAD_POLICIES
from pulp_deb.tests.functional.utils import gen_deb_remote_verbose


@pytest.mark.parametrize("key_url", [KEY_V6_ED25519_PUBLIC, KEY_V6_MLDSA65_ED25519_PUBLIC])
def test_create_remote_with_openpgp_gpgkey(
apt_remote_api, deb_remote_custom_data_factory, deb_get_fixture_server_url, key_url
):
"""Verify that classical and PQC OpenPGP keys can be stored on an AptRemote."""
key = requests.get(key_url)
key.raise_for_status()
remote = deb_remote_custom_data_factory(
gen_deb_remote_verbose(deb_get_fixture_server_url()) | {"gpgkey": key.text}
)

assert remote.gpgkey == key.text.rstrip()


@pytest.fixture
def deb_init_verbose_remote(deb_get_fixture_server_url, deb_remote_custom_data_factory):
"""A fixture that initializes are deb remote with verbose data."""
Expand Down
80 changes: 80 additions & 0 deletions pulp_deb/tests/functional/api/test_publish.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,23 @@
from random import choice

import pytest
import requests
from debian import deb822
from django.conf import settings

from pulpcore.client.pulp_deb.exceptions import ApiException
from pulpcore.pytest_plugin import (
KEY_V6_ED25519_PRIVATE,
KEY_V6_ED25519_PUBLIC,
KEY_V6_MLDSA65_ED25519_PRIVATE,
KEY_V6_MLDSA65_ED25519_PUBLIC,
)

from pulp_deb.tests.functional.conftest import (
create_signing_service,
import_signing_key,
remove_signing_service,
)
from pulp_deb.tests.functional.constants import (
DEB_FIXTURE_ALT_SINGLE_DIST,
DEB_FIXTURE_ARCH,
Expand All @@ -23,6 +35,7 @@
DEB_PACKAGE_INDEX_NAME,
DEB_PACKAGE_NAME,
DEB_PACKAGE_RELEASE_COMPONENT_NAME,
DEB_PQC_SIGNING_SCRIPT_STRING,
DEB_PUBLICATION_ARGS_ALL,
DEB_PUBLICATION_ARGS_NESTED_ALPHABETICALLY,
DEB_PUBLICATION_ARGS_NESTED_BY_BOTH,
Expand Down Expand Up @@ -270,7 +283,10 @@ def test_publish_layout(
apt_distribution_api,
create_publication_and_verify_repo_version,
deb_distribution_factory,
deb_delete_publication,
deb_delete_repository,
download_content_unit,
pulpcore_bindings,
publication_args,
):
"""Test whether a the layout parameter is generating expected package URLs
Expand Down Expand Up @@ -382,6 +398,70 @@ def test_publish_signing_services(
deb_delete_repository(repo)


@pytest.mark.parametrize(
"private_key_url, public_key_url",
[
(KEY_V6_ED25519_PRIVATE, KEY_V6_ED25519_PUBLIC),
(KEY_V6_MLDSA65_ED25519_PRIVATE, KEY_V6_MLDSA65_ED25519_PUBLIC),
],
ids=["v6-ed25519", "v6-mldsa65-ed25519"],
)
def test_release_signing_service(
tmp_path,
create_publication_and_verify_repo_version,
deb_delete_publication,
deb_delete_repository,
deb_distribution_factory,
download_content_unit,
pulpcore_bindings,
private_key_url,
public_key_url,
):
"""Verify API-created signing services publish detached and inline signatures."""
backend = "sq"
home = tmp_path / backend
home.mkdir()
_, fingerprint, _ = import_signing_key(private_key_url, home, backend=backend)
script = tmp_path / "sign.sh"
script.write_text(
DEB_PQC_SIGNING_SCRIPT_STRING.replace("SQ_HOME", str(home)).replace(
"PQC_SIGNER", fingerprint
)
)
public_key = requests.get(public_key_url)
public_key.raise_for_status()
public_key = public_key.text
script.chmod(0o755)
service_name = create_signing_service(
home,
fingerprint,
script,
service_class="deb:AptReleaseSigningService",
backend=backend,
)
try:
service = pulpcore_bindings.SigningServicesApi.list(name=service_name).results[0]
publication, repo, _, _ = create_publication_and_verify_repo_version(
{"distributions": DEB_FIXTURE_SINGLE_DIST},
{"signing_service": service.pulp_href},
)
distribution = deb_distribution_factory(publication)
inrelease = download_content_unit(
distribution.to_dict()["base_path"], "dists/ragnarok/InRelease"
)
assert inrelease.startswith(b"-----BEGIN PGP SIGNED MESSAGE-----")
release_gpg = download_content_unit(
distribution.to_dict()["base_path"], "dists/ragnarok/Release.gpg"
)
from pysequoia import Sig

Sig.from_bytes(release_gpg)
deb_delete_publication(publication)
deb_delete_repository(repo)
finally:
remove_signing_service(service_name, service_class="deb:AptReleaseSigningService")


@pytest.mark.parallel
def test_publish_repository_version_verbatim(
create_publication_and_verify_repo_version,
Expand Down
76 changes: 55 additions & 21 deletions pulp_deb/tests/functional/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -539,50 +539,84 @@ def _deb_copy_content_domain(
return _deb_copy_content_domain


def import_signing_key(key_url, gpg_home):
"""Import a PGP key into a GPG home directory and trust it.
def import_signing_key(key_url, home, *, backend="gpg"):
"""Import a PGP key into a keyring and return metadata.

Returns ``(gpg, fingerprint, keyid)``.
Returns `(gpg_instance_or_none, fingerprint, keyid)`. The first element
is a `gnupg.GPG` instance when `backend` is `"gpg"`, or `None` when
`backend` is `"sq"`.
"""
try:
import gnupg
except ImportError:
pytest.skip("python-gnupg not installed")

gpg = gnupg.GPG(gnupghome=gpg_home)

response = requests.get(key_url)
response.raise_for_status()
result = gpg.import_keys(response.content)
assert result.count >= 1, f"Failed to import key from {key_url}"

key_info = gpg.list_keys()[0]
fingerprint = key_info["fingerprint"]
keyid = key_info["keyid"]
gpg.trust_keys(fingerprint, "TRUST_ULTIMATE")
if backend == "sq":
from pysequoia import Cert

return gpg, fingerprint, keyid
def openpgp_key_id(fingerprint):
"""Return the OpenPGP key ID for a hexadecimal fingerprint.

OpenPGP v4 key IDs use the low-order 64 bits, while v6 key IDs use the
high-order 64 bits. The fingerprint length distinguishes these versions.
"""
return (fingerprint[:16] if len(fingerprint) == 64 else fingerprint[-16:]).upper()

completed = subprocess.run(
("sq", "--home", str(home), "key", "import"),
input=response.content,
capture_output=True,
)
assert completed.returncode == 0, completed.stderr.decode()

cert = Cert.from_bytes(response.content)
fingerprint = cert.fingerprint.upper()
keyid = openpgp_key_id(fingerprint)

return None, fingerprint, keyid
else:
try:
import gnupg
except ImportError:
pytest.skip("python-gnupg not installed")

gpg = gnupg.GPG(gnupghome=home)

result = gpg.import_keys(response.content)
assert result.count >= 1, f"Failed to import key from {key_url}"

key_info = gpg.list_keys()[0]
fingerprint = key_info["fingerprint"]
keyid = key_info["keyid"]
gpg.trust_keys(fingerprint, "TRUST_ULTIMATE")

return gpg, fingerprint, keyid


def create_signing_service(
gpg_home, fingerprint, script_path, *, service_class="core:AsciiArmoredDetachedSigningService"
gpg_home,
fingerprint,
script_path,
*,
service_class="core:AsciiArmoredDetachedSigningService",
backend="gpg",
):
"""Register a signing service via pulpcore-manager.

Returns the service name.
"""
service_name = str(uuid.uuid4())
cmd = (
cmd = [
"pulpcore-manager",
"add-signing-service",
service_name,
str(script_path),
fingerprint,
"--class",
service_class,
"--gnupghome",
"--backend",
backend,
"--home",
str(gpg_home),
)
]
completed = subprocess.run(cmd, capture_output=True, text=True)
assert completed.returncode == 0, completed.stderr

Expand Down
11 changes: 11 additions & 0 deletions pulp_deb/tests/functional/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,17 @@ def _clean_dict(d):
}
"""

DEB_PQC_SIGNING_SCRIPT_STRING = r"""#!/bin/sh

sq --home "SQ_HOME" sign --signer "PQC_SIGNER" \
--signature-file="$PULP_TEMP_WORKING_DIR/Release.gpg" "$1"
sq --home "SQ_HOME" sign --signer "PQC_SIGNER" --cleartext \
--output="$PULP_TEMP_WORKING_DIR/InRelease" "$1"
python3 -c 'import json, os; print(json.dumps({"signatures": {
"detached": os.environ["PULP_TEMP_WORKING_DIR"] + "/Release.gpg",
"inline": os.environ["PULP_TEMP_WORKING_DIR"] + "/InRelease"}}))'
"""

DEB_PACKAGE_SIGNING_SCRIPT_STRING = r"""#!/usr/bin/env bash
export GNUPGHOME="HOMEDIRHERE"
GPG_NAME="${PULP_SIGNING_KEY_FINGERPRINT}"
Expand Down
39 changes: 0 additions & 39 deletions pulp_deb/tests/unit/test_phase_out_reminders.py

This file was deleted.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ requires-python = ">=3.11"
dependencies = [
# All things django and asyncio are deliberately left to pulpcore
# Example transitive requirements: asgiref, asyncio, aiohttp
"pulpcore>=3.113.0,<3.130",
"pulpcore>=3.117.2,<3.130",
"python-debian>=0.1.44,<0.2.0",
"jsonschema>=4.6,<5.0",
]
Expand Down
Loading