From ac9edf991142c1597c8d86431ee9bd50c21bce41 Mon Sep 17 00:00:00 2001 From: Thorsten Kukuk Date: Wed, 5 Feb 2025 11:16:21 +0100 Subject: [PATCH] systemd-pull: support .asc and .sha256.* signature --- man/importctl.xml | 18 ++--- src/import/pull-common.c | 144 ++++++++++++++++++++++++++++++++------- src/import/pull-common.h | 14 +++- 3 files changed, 144 insertions(+), 32 deletions(-) diff --git a/man/importctl.xml b/man/importctl.xml index 8a7aecf5182..56a4c9055c5 100644 --- a/man/importctl.xml +++ b/man/importctl.xml @@ -114,16 +114,18 @@ URL, with its suffix removed. The image is verified before it is made available, unless is - specified. Verification is done either via an inline signed file with the name of the image and the - suffix .sha256 or via separate SHA256SUMS and - SHA256SUMS.gpg files. The signature files need to be made available on the same - web server, under the same URL as the .tar file. With + specified. Verification is done either via a file with the name of the image and the + suffix .sha256 and a detached .sha256.asc or + .sha256.gpg signature or via separate SHA256SUMS and + SHA256SUMS.asc or SHA256SUMS.gpg files. The signature + files need to be made available on the same web server, under the same URL as the + .tar file. With , only the SHA256 checksum for the file is verified, based on the .sha256 suffixed file or the SHA256SUMS file. With - , the sha checksum file is first verified with the inline - signature in the .sha256 file or the detached GPG signature file - SHA256SUMS.gpg. The public key for this verification step needs to be available - in /usr/lib/systemd/import-pubring.gpg or + , the sha checksum file is first verified with the detached GPG + signature of .sha256 or SHA256SUMS. The public key for + this verification step needs to be available in + /usr/lib/systemd/import-pubring.gpg or /etc/systemd/import-pubring.gpg. If is specified the image will be downloaded and stored in diff --git a/src/import/pull-common.c b/src/import/pull-common.c index b566e52b567..76b3cf81ac0 100644 --- a/src/import/pull-common.c +++ b/src/import/pull-common.c @@ -226,14 +226,25 @@ static bool is_checksum_file(const char *fn) { return streq(fn, "SHA256SUMS") || endswith(fn, ".sha256"); } -static bool is_signature_file(const char *fn) { - /* Returns true if the specified filename refers to a signature file we grok (reminder: - * suse-style .sha256 files are inline signed) */ +static SignatureStyle signature_style_from_filename(const char *fn) { + /* Returns true if the specified filename refers to a signature file we grok */ if (!fn) - return false; + return _SIGNATURE_STYLE_INVALID; + + if (streq(fn, "SHA256SUMS.gpg")) + return SIGNATURE_GPG_PER_DIRECTORY; + + if (streq(fn, "SHA256SUMS.asc")) + return SIGNATURE_ASC_PER_DIRECTORY; + + if (endswith(fn, ".sha256.gpg")) + return SIGNATURE_GPG_PER_FILE; - return streq(fn, "SHA256SUMS.gpg") || endswith(fn, ".sha256"); + if (endswith(fn, ".sha256.asc")) + return SIGNATURE_ASC_PER_FILE; + + return _SIGNATURE_STYLE_INVALID; } int pull_make_verification_jobs( @@ -272,7 +283,7 @@ int pull_make_verification_jobs( /* Acquire the checksum file if verification or signature verification is requested and the main file * to acquire isn't a checksum or signature file anyway */ - if (verify != IMPORT_VERIFY_NO && !is_checksum_file(fn) && !is_signature_file(fn)) { + if (verify != IMPORT_VERIFY_NO && !is_checksum_file(fn) && signature_style_from_filename(fn) < 0) { _cleanup_free_ char *checksum_url = NULL; const char *suffixed = NULL; @@ -296,11 +307,17 @@ int pull_make_verification_jobs( checksum_job->on_not_found = pull_job_restart_with_sha256sum; /* if this fails, look for ubuntu-style checksum */ } - if (verify == IMPORT_VERIFY_SIGNATURE && !is_signature_file(fn)) { + if (verify == IMPORT_VERIFY_SIGNATURE && signature_style_from_filename(fn) < 0) { _cleanup_free_ char *signature_url = NULL; + const char *suffixed = NULL; + + if (fn) + suffixed = strjoina(fn, ".sha256.asc"); /* Start with the suse-style checksum (if there's a base filename) */ + else + suffixed = "SHA256SUMS.gpg"; - /* Queue job for the SHA256SUMS.gpg file for the image. */ - r = import_url_change_last_component(url, "SHA256SUMS.gpg", &signature_url); + /* Queue job for the signature file for the image. */ + r = import_url_change_last_component(url, suffixed, &signature_url); if (r < 0) return r; @@ -310,6 +327,7 @@ int pull_make_verification_jobs( signature_job->on_finished = on_finished; signature_job->uncompressed_max = signature_job->compressed_max = 1ULL * 1024ULL * 1024ULL; + signature_job->on_not_found = pull_job_restart_with_signature; } *ret_checksum_job = TAKE_PTR(checksum_job); @@ -348,7 +366,7 @@ static int verify_one(PullJob *checksum_job, PullJob *job) { return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Cannot verify checksum, could not determine server-side file name."); - if (is_checksum_file(fn) || is_signature_file(fn)) /* We cannot verify checksum files or signature files with a checksum file */ + if (is_checksum_file(fn) || signature_style_from_filename(fn) >= 0) /* We cannot verify checksum files or signature files with a checksum file */ return log_error_errno(SYNTHETIC_ERRNO(ELOOP), "Cannot verify checksum/signature files via themselves."); @@ -536,7 +554,7 @@ int pull_verify(ImportVerify verify, if (r < 0) return log_error_errno(r, "Failed to extract filename from URL '%s': %m", main_job->url); - if (is_signature_file(fn)) + if (signature_style_from_filename(fn) >= 0) return log_error_errno(SYNTHETIC_ERRNO(ELOOP), "Main download is a signature file, can't verify it."); @@ -572,17 +590,14 @@ int pull_verify(ImportVerify verify, if (r < 0) return log_error_errno(r, "Failed to determine verification style from URL '%s': %m", verify_job->url); - if (style == VERIFICATION_PER_DIRECTORY) { - assert(signature_job); - assert(signature_job->state == PULL_JOB_DONE); + assert(signature_job); + assert(signature_job->state == PULL_JOB_DONE); - if (!signature_job->payload || signature_job->payload_size <= 0) - return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), - "Signature is empty, cannot verify."); + if (!signature_job->payload || signature_job->payload_size <= 0) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), + "Signature is empty, cannot verify."); - return verify_gpg(verify_job->payload, verify_job->payload_size, signature_job->payload, signature_job->payload_size); - } else - return verify_gpg(verify_job->payload, verify_job->payload_size, NULL, 0); + return verify_gpg(verify_job->payload, verify_job->payload_size, signature_job->payload, signature_job->payload_size); } int verification_style_from_url(const char *url, VerificationStyle *ret) { @@ -623,12 +638,14 @@ int pull_job_restart_with_sha256sum(PullJob *j, char **ret) { if (r < 0) return log_error_errno(r, "Failed to determine verification style of URL '%s': %m", j->url); - if (style == VERIFICATION_PER_DIRECTORY) /* Nothing to do anymore */ + if (style == VERIFICATION_PER_DIRECTORY) { /* Nothing to do anymore */ + *ret = NULL; return 0; + } assert(style == VERIFICATION_PER_FILE); /* This must have been .sha256 style URL before */ - log_debug("Got 404 for %s, now trying to get SHA256SUMS instead.", j->url); + log_debug("Got 404 for '%s', now trying to get SHA256SUMS instead.", j->url); r = import_url_change_last_component(j->url, "SHA256SUMS", ret); if (r < 0) @@ -637,6 +654,87 @@ int pull_job_restart_with_sha256sum(PullJob *j, char **ret) { return 1; } +int signature_style_from_url(const char *url, SignatureStyle *ret, char **ret_filename) { + _cleanup_free_ char *last = NULL; + SignatureStyle style; + int r; + + assert(url); + assert(ret); + assert(ret_filename); + + /* Determines which kind of signature style is appropriate for this url */ + + r = import_url_last_component(url, &last); + if (r < 0) + return r; + + style = signature_style_from_filename(last); + if (style < 0) + return style; + + *ret_filename = TAKE_PTR(last); + *ret = style; + return 0; +} + +int pull_job_restart_with_signature(PullJob *j, char **ret) { + _cleanup_free_ char *last = NULL; + SignatureStyle style; + int r; + + assert(j); + + /* Generic implementation of a PullJobNotFound handler, that restarts the job requesting a different + * signature file. After the initial file, additional *.sha256.gpg, SHA256SUMS.gpg and SHA256SUMS.asc + * are tried in this order. */ + + r = signature_style_from_url(j->url, &style, &last); + if (r < 0) + return log_error_errno(r, "Failed to determine signature style of URL '%s': %m", j->url); + + switch (style) { + + case SIGNATURE_ASC_PER_DIRECTORY: /* Nothing to do anymore */ + *ret = NULL; + return 0; + + case SIGNATURE_ASC_PER_FILE: { /* Try .sha256.gpg next */ + char *ext; + + log_debug("Got 404 for '%s', now trying to get .sha256.gpg instead.", j->url); + + ext = endswith(last, ".asc"); + assert(ext); + strcpy(ext, ".gpg"); + + r = import_url_change_last_component(j->url, last, ret); + if (r < 0) + return log_error_errno(r, "Failed to replace .sha256.asc suffix: %m"); + break; + } + + case SIGNATURE_GPG_PER_FILE: /* Try SHA256SUMS.gpg next */ + log_debug("Got 404 for '%s', now trying to get SHA256SUMS.gpg instead.", j->url); + r = import_url_change_last_component(j->url, "SHA256SUMS.gpg", ret); + if (r < 0) + return log_error_errno(r, "Failed to replace SHA256SUMS suffix: %m"); + break; + + case SIGNATURE_GPG_PER_DIRECTORY: + log_debug("Got 404 for '%s', now trying to get SHA256SUMS.asc instead.", j->url); + r = import_url_change_last_component(j->url, "SHA256SUMS.asc", ret); + if (r < 0) + return log_error_errno(r, "Failed to replace SHA256SUMS.gpg suffix: %m"); + break; + + default: + assert_not_reached(); + } + + return 1; +} + bool pull_validate_local(const char *name, ImportFlags flags) { if (FLAGS_SET(flags, IMPORT_DIRECT)) @@ -659,5 +757,5 @@ int pull_url_needs_checksum(const char *url) { if (r < 0) return r; - return !is_checksum_file(fn) && !is_signature_file(fn); + return !is_checksum_file(fn) && signature_style_from_filename(fn) < 0; } diff --git a/src/import/pull-common.h b/src/import/pull-common.h index 82e25267e3f..5a1bac19a1e 100644 --- a/src/import/pull-common.h +++ b/src/import/pull-common.h @@ -17,7 +17,7 @@ int pull_make_verification_jobs(PullJob **ret_checksum_job, PullJob **ret_signat int pull_verify(ImportVerify verify, const char *checksum, PullJob *main_job, PullJob *checksum_job, PullJob *signature_job, PullJob *settings_job, PullJob *roothash_job, PullJob *roothash_signature_job, PullJob *verity_job); typedef enum VerificationStyle { - VERIFICATION_PER_FILE, /* SuSE-style ".sha256" files with inline gpg signature */ + VERIFICATION_PER_FILE, /* SUSE-style ".sha256" files with detached gpg signature */ VERIFICATION_PER_DIRECTORY, /* Ubuntu-style SHA256SUM files with detached SHA256SUM.gpg signatures */ _VERIFICATION_STYLE_MAX, _VERIFICATION_STYLE_INVALID = -EINVAL, @@ -25,7 +25,19 @@ typedef enum VerificationStyle { int verification_style_from_url(const char *url, VerificationStyle *style); +typedef enum SignatureStyle { + SIGNATURE_GPG_PER_FILE, /* ".sha256" files with detached .gpg signature */ + SIGNATURE_ASC_PER_FILE, /* SUSE-style ".sha256" files with detached .asc signature */ + SIGNATURE_GPG_PER_DIRECTORY, /* Ubuntu-style SHA256SUM files with detached SHA256SUM.gpg signatures */ + SIGNATURE_ASC_PER_DIRECTORY, /* SUSE-style SHA256SUM files with detached SHA256SUM.asc signatures */ + _SIGNATURE_STYLE_MAX, + _SIGNATURE_STYLE_INVALID = -EINVAL, +} SignatureStyle; + +int signature_style_from_url(const char *url, SignatureStyle *style, char **ret_filename); + int pull_job_restart_with_sha256sum(PullJob *job, char **ret); +int pull_job_restart_with_signature(PullJob *job, char **ret); bool pull_validate_local(const char *name, ImportFlags flags); -- 2.47.3