]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
systemd-pull: support .asc and .sha256.* signature
authorThorsten Kukuk <kukuk@suse.com>
Wed, 5 Feb 2025 10:16:21 +0000 (11:16 +0100)
committerLennart Poettering <lennart@poettering.net>
Thu, 6 Feb 2025 15:49:36 +0000 (16:49 +0100)
man/importctl.xml
src/import/pull-common.c
src/import/pull-common.h

index 8a7aecf5182e14d62dd49f9d83fdf022edbaeedd..56a4c9055c588f2f50260fe355c14ae8662c4105 100644 (file)
         URL, with its suffix removed.</para>
 
         <para>The image is verified before it is made available, unless <option>--verify=no</option> is
-        specified.  Verification is done either via an inline signed file with the name of the image and the
-        suffix <filename>.sha256</filename> or via separate <filename>SHA256SUMS</filename> and
-        <filename>SHA256SUMS.gpg</filename> files.  The signature files need to be made available on the same
-        web server, under the same URL as the <filename>.tar</filename> file.  With
+        specified.  Verification is done either via a file with the name of the image and the
+        suffix <filename>.sha256</filename> and a detached <filename>.sha256.asc</filename> or
+        <filename>.sha256.gpg</filename> signature or via separate <filename>SHA256SUMS</filename> and
+        <filename>SHA256SUMS.asc</filename> or <filename>SHA256SUMS.gpg</filename> files.  The signature
+        files need to be made available on the same web server, under the same URL as the
+        <filename>.tar</filename> file. With
         <option>--verify=checksum</option>, only the SHA256 checksum for the file is verified, based on the
         <filename>.sha256</filename> suffixed file or the <filename>SHA256SUMS</filename> file.  With
-        <option>--verify=signature</option>, the sha checksum file is first verified with the inline
-        signature in the <filename>.sha256</filename> file or the detached GPG signature file
-        <filename>SHA256SUMS.gpg</filename>.  The public key for this verification step needs to be available
-        in <filename>/usr/lib/systemd/import-pubring.gpg</filename> or
+        <option>--verify=signature</option>, the sha checksum file is first verified with the detached GPG
+        signature of <filename>.sha256</filename> or <filename>SHA256SUMS</filename>.  The public key for
+        this verification step needs to be available in
+        <filename>/usr/lib/systemd/import-pubring.gpg</filename> or
         <filename>/etc/systemd/import-pubring.gpg</filename>.</para>
 
         <para>If <option>-keep-download=yes</option> is specified the image will be downloaded and stored in
index b566e52b567e0e117850ddff4eaf1ae2695b288c..76b3cf81ac08d25a2703976233397f82cbcc8b5d 100644 (file)
@@ -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;
 }
index 82e25267e3f374001a5c981700840e0e9be8aea7..5a1bac19a1e180eeb7762f418d457b1d8fb6cff2 100644 (file)
@@ -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);