summaryrefslogtreecommitdiffstats
path: root/signature.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 19:12:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 19:12:14 +0000
commit4b8a0f3f3dcf60dac2ce308ea08d413a535af29f (patch)
tree0f09c0ad2a4d0f535d89040a63dc3a866a6606e6 /signature.c
parentInitial commit. (diff)
downloadreprepro-4b8a0f3f3dcf60dac2ce308ea08d413a535af29f.tar.xz
reprepro-4b8a0f3f3dcf60dac2ce308ea08d413a535af29f.zip
Adding upstream version 5.4.4.upstream/5.4.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'signature.c')
-rw-r--r--signature.c570
1 files changed, 570 insertions, 0 deletions
diff --git a/signature.c b/signature.c
new file mode 100644
index 0000000..93ff207
--- /dev/null
+++ b/signature.c
@@ -0,0 +1,570 @@
+/* This file is part of "reprepro"
+ * Copyright (C) 2003,2004,2005,2006,2007,2009,2010 Bernhard R. Link
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+#include <config.h>
+
+#include <errno.h>
+#include <assert.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <time.h>
+#include <string.h>
+#include <fcntl.h>
+
+#include "signature_p.h"
+#include "mprintf.h"
+#include "strlist.h"
+#include "dirs.h"
+#include "names.h"
+#include "chunks.h"
+#include "readtextfile.h"
+
+#ifdef HAVE_LIBGPGME
+gpgme_ctx_t context = NULL;
+
+retvalue gpgerror(gpg_error_t err) {
+ if (err != 0) {
+ fprintf(stderr, "gpgme gave error %s:%d: %s\n",
+ gpg_strsource(err), gpg_err_code(err),
+ gpg_strerror(err));
+ if (gpg_err_code(err) == GPG_ERR_ENOMEM)
+ return RET_ERROR_OOM;
+ else
+ return RET_ERROR_GPGME;
+ } else
+ return RET_OK;
+}
+
+/* Quick&dirty passphrase asking */
+static gpg_error_t signature_getpassphrase(UNUSED(void *hook), const char *uid_hint, UNUSED(const char *info), int prev_was_bad, int fd) {
+ char *msg;
+ const char *p;
+ int e = 0;
+
+ msg = mprintf("%s needs a passphrase\nPlease enter passphrase%s:",
+ (uid_hint!=NULL)?uid_hint:"key",
+ (prev_was_bad!=0)?" again":"");
+ if (msg == NULL)
+ return gpg_err_make(GPG_ERR_SOURCE_USER_1, GPG_ERR_ENOMEM);
+ p = getpass(msg);
+ if (write(fd, p, strlen(p)) < 0) {
+ e = errno;
+ }
+ if (write(fd, "\n", 1) < 0 && e == 0) {
+ e = errno;
+ }
+ if (e != 0) {
+ fprintf(stderr, "Error %d writing to fd %i: %s\n",
+ e, fd, strerror(e));
+ free(msg);
+ return RET_ERRNO(e);
+ }
+ free(msg);
+ return GPG_ERR_NO_ERROR;
+}
+#endif /* HAVE_LIBGPGME */
+
+retvalue signature_init(bool allowpassphrase){
+#ifdef HAVE_LIBGPGME
+ gpg_error_t err;
+
+ if (context != NULL)
+ return RET_NOTHING;
+ gpgme_check_version(NULL);
+ err = gpgme_engine_check_version(GPGME_PROTOCOL_OpenPGP);
+ if (err != 0)
+ return gpgerror(err);
+ err = gpgme_new(&context);
+ if (err != 0)
+ return gpgerror(err);
+ err = gpgme_set_protocol(context, GPGME_PROTOCOL_OpenPGP);
+ if (err != 0)
+ return gpgerror(err);
+ if (allowpassphrase)
+ gpgme_set_passphrase_cb(context, signature_getpassphrase,
+ NULL);
+ gpgme_set_armor(context, 1);
+#endif /* HAVE_LIBGPGME */
+ return RET_OK;
+}
+
+void signatures_done(void) {
+#ifdef HAVE_LIBGPGME
+ if (context != NULL) {
+ gpgme_release(context);
+ context = NULL;
+ }
+#endif /* HAVE_LIBGPGME */
+}
+
+#ifdef HAVE_LIBGPGME
+/* retrieve a list of fingerprints of keys having signed (valid) or
+ * which are mentioned in the signature (all). set broken if all signatures
+ * was broken (hints to a broken file, as opposed to expired or whatever
+ * else may make a signature invalid)). */
+static retvalue checksigs(const char *filename, struct signatures **signatures_p, bool *broken) {
+ gpgme_verify_result_t result;
+ gpgme_signature_t s;
+ bool had_valid = false, had_broken = false;
+ size_t count;
+ struct signatures *signatures;
+ struct signature *sig;
+
+ result = gpgme_op_verify_result(context);
+ if (result == NULL) {
+ fprintf(stderr,
+"Internal error communicating with libgpgme: no result record!\n\n");
+ return RET_ERROR_GPGME;
+ }
+ if (signatures_p != NULL) {
+ count = 0;
+ for (s = result->signatures ; s != NULL ; s = s->next) {
+ count++;
+ }
+ signatures = calloc(1, sizeof(struct signatures) +
+ count * sizeof(struct signature));
+ if (FAILEDTOALLOC(signatures))
+ return RET_ERROR_OOM;
+ signatures->count = count;
+ signatures->validcount = 0;
+ sig = signatures->signatures;
+ } else {
+ signatures = NULL;
+ sig = NULL;
+ }
+ for (s = result->signatures ; s != NULL ; s = s->next) {
+ enum signature_state state = sist_error;
+
+ if (signatures_p != NULL) {
+ sig->keyid = strdup(s->fpr);
+ if (FAILEDTOALLOC(sig->keyid)) {
+ signatures_free(signatures);
+ return RET_ERROR_OOM;
+ }
+ }
+ switch (gpg_err_code(s->status)) {
+ case GPG_ERR_NO_ERROR:
+ had_valid = true;
+ state = sist_valid;
+ if (signatures)
+ signatures->validcount++;
+ break;
+ case GPG_ERR_KEY_EXPIRED:
+ had_valid = true;
+ if (verbose > 0)
+ fprintf(stderr,
+"Ignoring signature with '%s' on '%s', as the key has expired.\n",
+ s->fpr, filename);
+ state = sist_mostly;
+ if (sig != NULL)
+ sig->expired_key = true;
+ break;
+ case GPG_ERR_CERT_REVOKED:
+ had_valid = true;
+ if (verbose > 0)
+ fprintf(stderr,
+"Ignoring signature with '%s' on '%s', as the key is revoked.\n",
+ s->fpr, filename);
+ state = sist_mostly;
+ if (sig != NULL)
+ sig->revoced_key = true;
+ break;
+ case GPG_ERR_SIG_EXPIRED:
+ had_valid = true;
+ if (verbose > 0) {
+ time_t timestamp = s->timestamp,
+ exp_timestamp = s->exp_timestamp;
+ fprintf(stderr,
+"Ignoring signature with '%s' on '%s', as the signature has expired.\n"
+" signature created %s, expired %s\n",
+ s->fpr, filename,
+ ctime(&timestamp),
+ ctime(&exp_timestamp));
+ }
+ state = sist_mostly;
+ if (sig != NULL)
+ sig->expired_signature = true;
+ break;
+ case GPG_ERR_BAD_SIGNATURE:
+ had_broken = true;
+ if (verbose > 0) {
+ fprintf(stderr,
+"WARNING: '%s' has a invalid signature with '%s'\n", filename, s->fpr);
+ }
+ state = sist_bad;
+ break;
+ case GPG_ERR_NO_PUBKEY:
+ if (verbose > 0) {
+ fprintf(stderr,
+"Could not check validity of signature with '%s' in '%s' as public key missing!\n",
+ s->fpr, filename);
+ }
+ state = sist_missing;
+ break;
+ case GPG_ERR_GENERAL:
+ fprintf(stderr,
+"gpgme returned an general error verifing signature with '%s' in '%s'!\n"
+"Try running gpg --verify '%s' manually for hints what is happening.\n"
+"If this does not print any errors, retry the command causing this message.\n",
+ s->fpr, filename,
+ filename);
+ signatures_free(signatures);
+ return RET_ERROR_GPGME;
+ /* there sadly no more is a way to make sure we have
+ * all possible ones handled */
+ default:
+ break;
+ }
+ if (state == sist_error) {
+ fprintf(stderr,
+"Error checking signature (gpgme returned unexpected value %d)!\n"
+"Please file a bug report, so reprepro can handle this in the future.\n",
+ gpg_err_code(s->status));
+ signatures_free(signatures);
+ return RET_ERROR_GPGME;
+ }
+ if (sig != NULL) {
+ sig->state = state;
+ sig++;
+ }
+ }
+ if (broken != NULL && had_broken && ! had_valid)
+ *broken = true;
+ if (signatures_p != NULL)
+ *signatures_p = signatures;
+ return RET_OK;
+}
+
+static retvalue check_primary_keys(struct signatures *signatures) {
+ /* Get the primary keys belonging to each signing key.
+ This might also invalidate a signature previously believed
+ valid if the primary key is expired */
+ int i;
+
+ for (i = 0 ; i < signatures->count ; i++) {
+ gpg_error_t err;
+ gpgme_key_t gpgme_key = NULL;
+ gpgme_subkey_t subkey;
+ struct signature *sig = &signatures->signatures[i];
+
+ if (sig->state == sist_error || sig->state == sist_missing) {
+ sig->primary_keyid = strdup(sig->keyid);
+ if (FAILEDTOALLOC(sig->primary_keyid))
+ return RET_ERROR_OOM;
+ continue;
+ }
+
+ err = gpgme_get_key(context, sig->keyid, &gpgme_key, 0);
+ if (err != 0) {
+ fprintf(stderr,
+"gpgme error %s:%d retrieving key '%s': %s\n",
+ gpg_strsource(err),
+ (int)gpg_err_code(err),
+ sig->keyid, gpg_strerror(err));
+ if (gpg_err_code(err) == GPG_ERR_ENOMEM)
+ return RET_ERROR_OOM;
+ else
+ return RET_ERROR_GPGME;
+ }
+ assert (gpgme_key != NULL);
+ /* the first "sub"key is the primary key */
+ subkey = gpgme_key->subkeys;
+ if (subkey->revoked) {
+ sig->revoced_key = true;
+ if (sig->state == sist_valid) {
+ sig->state = sist_mostly;
+ signatures->validcount--;
+ }
+ }
+ if (subkey->expired) {
+ sig->expired_key = true;
+ if (sig->state == sist_valid) {
+ sig->state = sist_mostly;
+ signatures->validcount--;
+ }
+ }
+ sig->primary_keyid = strdup(subkey->keyid);
+ gpgme_key_unref(gpgme_key);
+ if (FAILEDTOALLOC(sig->primary_keyid))
+ return RET_ERROR_OOM;
+ }
+ return RET_OK;
+}
+#endif /* HAVE_LIBGPGME */
+
+void signatures_free(struct signatures *signatures) {
+ int i;
+
+ if (signatures == NULL)
+ return;
+
+ for (i = 0 ; i < signatures->count ; i++) {
+ free(signatures->signatures[i].keyid);
+ free(signatures->signatures[i].primary_keyid);
+ }
+ free(signatures);
+}
+
+#ifdef HAVE_LIBGPGME
+static retvalue extract_signed_data(const char *buffer, size_t bufferlen, const char *filenametoshow, char **chunkread, /*@null@*/ /*@out@*/struct signatures **signatures_p, bool *brokensignature) {
+ char *chunk;
+ gpg_error_t err;
+ gpgme_data_t dh, dh_gpg;
+ size_t plain_len;
+ char *plain_data;
+ retvalue r;
+ struct signatures *signatures = NULL;
+ bool foundbroken = false;
+
+ r = signature_init(false);
+ if (RET_WAS_ERROR(r))
+ return r;
+
+ err = gpgme_data_new_from_mem(&dh_gpg, buffer, bufferlen, 0);
+ if (err != 0)
+ return gpgerror(err);
+
+ err = gpgme_data_new(&dh);
+ if (err != 0) {
+ gpgme_data_release(dh_gpg);
+ return gpgerror(err);
+ }
+ err = gpgme_op_verify(context, dh_gpg, NULL, dh);
+ if (gpg_err_code(err) == GPG_ERR_NO_DATA) {
+ if (verbose > 5)
+ fprintf(stderr,
+"Data seems not to be signed trying to use directly....\n");
+ gpgme_data_release(dh);
+ gpgme_data_release(dh_gpg);
+ return RET_NOTHING;
+ } else {
+ if (err != 0) {
+ gpgme_data_release(dh_gpg);
+ gpgme_data_release(dh);
+ return gpgerror(err);
+ }
+ if (signatures_p != NULL || brokensignature != NULL) {
+ r = checksigs(filenametoshow,
+ (signatures_p!=NULL)?&signatures:NULL,
+ (brokensignature!=NULL)?&foundbroken:NULL);
+ if (RET_WAS_ERROR(r)) {
+ gpgme_data_release(dh_gpg);
+ gpgme_data_release(dh);
+ return r;
+ }
+ }
+ gpgme_data_release(dh_gpg);
+ plain_data = gpgme_data_release_and_get_mem(dh, &plain_len);
+ if (plain_data == NULL) {
+ fprintf(stderr,
+"(not yet fatal) ERROR: libgpgme failed to extract the plain data out of\n"
+"'%s'.\n"
+"While it did so in a way indicating running out of memory, experience says\n"
+"this also happens when gpg returns a error code it does not understand.\n"
+"To check this please try running gpg --verify '%s' manually.\n"
+"Continuing extracting it ignoring all signatures...",
+ filenametoshow, filenametoshow);
+ signatures_free(signatures);
+ return RET_NOTHING;
+ }
+ if (signatures != NULL) {
+ r = check_primary_keys(signatures);
+ if (RET_WAS_ERROR(r)) {
+ signatures_free(signatures);
+ return r;
+ }
+ }
+ }
+
+ if (FAILEDTOALLOC(plain_data))
+ r = RET_ERROR_OOM;
+ else {
+ size_t len;
+ const char *afterchanges;
+
+ chunk = malloc(plain_len + 1);
+ len = chunk_extract(chunk, plain_data, plain_len, false,
+ &afterchanges);
+ if (len == 0) {
+ fprintf(stderr,
+"Could only find spaces within '%s'!\n",
+ filenametoshow);
+ free(chunk);
+ r = RET_ERROR;
+ } else if (afterchanges != plain_data + plain_len) {
+ if (*afterchanges == '\0')
+ fprintf(stderr,
+"Unexpected \\0 character within '%s'!\n",
+ filenametoshow);
+ else
+ fprintf(stderr,
+"Unexpected data after ending empty line in '%s'!\n",
+ filenametoshow);
+ free(chunk);
+ r = RET_ERROR;
+ } else
+ *chunkread = chunk;
+ }
+#ifdef HAVE_GPGPME_FREE
+ gpgme_free(plain_data);
+#else
+ free(plain_data);
+#endif
+ if (RET_IS_OK(r)) {
+ if (signatures_p != NULL)
+ *signatures_p = signatures;
+ if (brokensignature != NULL)
+ *brokensignature = foundbroken;
+ } else {
+ signatures_free(signatures);
+ }
+ return r;
+}
+#endif /* HAVE_LIBGPGME */
+
+/* Read a single chunk from a file, that may be signed. */
+retvalue signature_readsignedchunk(const char *filename, const char *filenametoshow, char **chunkread, /*@null@*/ /*@out@*/struct signatures **signatures_p, bool *brokensignature) {
+ char *chunk;
+ const char *startofchanges, *afterchunk;
+ const char *endmarker;
+ size_t chunklen, len;
+ retvalue r;
+
+ r = readtextfile(filename, filenametoshow, &chunk, &chunklen);
+ if (!RET_IS_OK(r))
+ return r;
+
+ if (chunklen == 0) {
+ fprintf(stderr, "Unexpected empty file '%s'!\n",
+ filenametoshow);
+ free(chunk);
+ return RET_ERROR;
+ }
+
+ startofchanges = chunk_getstart(chunk, chunklen, false);
+
+ /* fast-track unsigned chunks: */
+ if (startofchanges[0] != '-') {
+ const char *afterchanges;
+
+ len = chunk_extract(chunk, chunk, chunklen, false,
+ &afterchanges);
+
+ if (len == 0) {
+ fprintf(stderr,
+"Could only find spaces within '%s'!\n",
+ filenametoshow);
+ free(chunk);
+ return RET_ERROR;
+ }
+ if (*afterchanges != '\0') {
+ fprintf(stderr,
+"Error parsing '%s': Seems not to be signed but has spurious empty line.\n",
+ filenametoshow);
+ free(chunk);
+ return RET_ERROR;
+ }
+ if (verbose > 5 && strncmp(chunk, "Format:", 7) != 0
+ && strncmp(chunk, "Source:", 7) != 0)
+ fprintf(stderr,
+"Data seems not to be signed trying to use directly...\n");
+ assert (chunk[len] == '\0');
+ *chunkread = realloc(chunk, len + 1);
+ if (FAILEDTOALLOC(*chunkread))
+ *chunkread = chunk;
+ if (signatures_p != NULL)
+ *signatures_p = NULL;
+ if (brokensignature != NULL)
+ *brokensignature = false;
+ return RET_OK;
+ }
+
+#ifdef HAVE_LIBGPGME
+ r = extract_signed_data(chunk, chunklen, filenametoshow, chunkread,
+ signatures_p, brokensignature);
+ if (r != RET_NOTHING) {
+ free(chunk);
+ return r;
+ }
+#endif
+ /* We have no libgpgme, it failed, or could not find signature data,
+ * trying to extract it manually, ignoring signatures: */
+
+ if (strncmp(startofchanges, "-----BEGIN", 10) != 0) {
+ fprintf(stderr,
+"Strange content of '%s': First non-space character is '-',\n"
+"but it does not begin with '-----BEGIN'.\n", filenametoshow);
+ free(chunk);
+ return RET_ERROR;
+#ifndef HAVE_LIBGPGME
+ } else {
+ fprintf(stderr,
+"Cannot check signatures from '%s' as compiled without support for libgpgme!\n"
+"Extracting the content manually without looking at the signature...\n", filenametoshow);
+#endif
+ }
+ startofchanges = chunk_over(startofchanges);
+
+ len = chunk_extract(chunk, startofchanges, chunklen - (startofchanges - chunk),
+ false, &afterchunk);
+
+ if (len == 0) {
+ fprintf(stderr, "Could not find any data within '%s'!\n",
+ filenametoshow);
+ free(chunk);
+ return RET_ERROR;
+ }
+
+ endmarker = strstr(chunk, "\n-----");
+ if (endmarker != NULL) {
+ endmarker++;
+ assert ((size_t)(endmarker-chunk) < len);
+ len = endmarker-chunk;
+ chunk[len] = '\0';
+ } else if (*afterchunk == '\0') {
+ fprintf(stderr,
+"ERROR: Could not find end marker of signed data within '%s'.\n"
+"Cannot determine what is data and what is not!\n",
+filenametoshow);
+ free(chunk);
+ return RET_ERROR;
+ } else if (strncmp(afterchunk, "-----", 5) != 0) {
+ fprintf(stderr, "ERROR: Spurious empty line within '%s'.\n"
+"Cannot determine what is data and what is not!\n",
+ filenametoshow);
+ free(chunk);
+ return RET_ERROR;
+ }
+
+ assert (chunk[len] == '\0');
+ if (signatures_p != NULL) {
+ /* pointer to structure with count 0 to make clear
+ * it is not unsigned */
+ *signatures_p = calloc(1, sizeof(struct signatures));
+ if (FAILEDTOALLOC(*signatures_p)) {
+ free(chunk);
+ return RET_ERROR_OOM;
+ }
+ }
+ *chunkread = realloc(chunk, len + 1);
+ if (FAILEDTOALLOC(*chunkread))
+ *chunkread = chunk;
+ if (brokensignature != NULL)
+ *brokensignature = false;
+ return RET_OK;
+}
+