From: Michael Tremer Date: Fri, 24 Nov 2017 17:45:24 +0000 (+0100) Subject: libpakfire: Read signatures from archives and verify them X-Git-Tag: 0.9.28~1285^2~1284 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=618ca5006d1a863023281272ccc9054d271241d6;p=pakfire.git libpakfire: Read signatures from archives and verify them Signed-off-by: Michael Tremer --- diff --git a/src/_pakfire/_pakfiremodule.c b/src/_pakfire/_pakfiremodule.c index 617cd0b16..0af859b88 100644 --- a/src/_pakfire/_pakfiremodule.c +++ b/src/_pakfire/_pakfiremodule.c @@ -120,6 +120,10 @@ PyMODINIT_FUNC PyInit__pakfire(void) { if (!module) return NULL; + PyExc_BadSignatureError = PyErr_NewException("_pakfire.BadSignatureError", NULL, NULL); + Py_INCREF(PyExc_BadSignatureError); + PyModule_AddObject(module, "BadSignatureError", PyExc_BadSignatureError); + PyExc_DependencyError = PyErr_NewException("_pakfire.DependencyError", NULL, NULL); Py_INCREF(PyExc_DependencyError); PyModule_AddObject(module, "DependencyError", PyExc_DependencyError); diff --git a/src/_pakfire/archive.c b/src/_pakfire/archive.c index 5a9a64855..439c97f6f 100644 --- a/src/_pakfire/archive.c +++ b/src/_pakfire/archive.c @@ -24,10 +24,12 @@ #include #include "archive.h" +#include "errors.h" static PyObject* Archive_new(PyTypeObject* type, PyObject* args, PyObject* kwds) { ArchiveObject* self = (ArchiveObject *)type->tp_alloc(type, 0); if (self) { + self->pakfire = NULL; self->archive = NULL; } @@ -38,16 +40,22 @@ static void Archive_dealloc(ArchiveObject* self) { if (self->archive) pakfire_archive_free(self->archive); + Py_DECREF(self->pakfire); + Py_TYPE(self)->tp_free((PyObject *)self); } static int Archive_init(ArchiveObject* self, PyObject* args, PyObject* kwds) { + PakfireObject* pakfire = NULL; const char* filename = NULL; - if (!PyArg_ParseTuple(args, "s", &filename)) + if (!PyArg_ParseTuple(args, "O!s", &PakfireType, &pakfire, &filename)) return -1; - self->archive = pakfire_archive_open(filename); + self->pakfire = pakfire; + Py_INCREF(self->pakfire); + + self->archive = pakfire_archive_open(self->pakfire->pakfire, filename); return 0; } @@ -92,13 +100,71 @@ static PyObject* Archive_read(ArchiveObject* self, PyObject* args, PyObject* kwd return bytes; } +static PyObject* Archive_verify(ArchiveObject* self) { + pakfire_archive_verify_status_t status = pakfire_archive_verify(self->archive); + + // Return True if everything is fine + if (status == PAKFIRE_ARCHIVE_VERIFY_OK || status == PAKFIRE_ARCHIVE_VERIFY_KEY_EXPIRED) + Py_RETURN_TRUE; + + // Raise an exception if not okay + PyErr_SetString(PyExc_BadSignatureError, pakfire_archive_verify_strerror(status)); + + return NULL; +} + +static PyObject* Archive_get_signatures(ArchiveObject* self) { + PyObject* list = PyList_New(0); + + char** head = pakfire_archive_get_signatures(self->archive); + + char** signatures = head; + while (*signatures) { + char* signature = *signatures++; + + PyObject* object = PyUnicode_FromString(signature); + PyList_Append(list, object); + + Py_DECREF(object); + pakfire_free(signature); + } + + pakfire_free(head); + + return list; +} + static struct PyMethodDef Archive_methods[] = { - {"read", (PyCFunction)Archive_read, METH_VARARGS|METH_KEYWORDS, NULL}, + { + "read", + (PyCFunction)Archive_read, + METH_VARARGS|METH_KEYWORDS, + NULL + }, + { + "verify", + (PyCFunction)Archive_verify, + METH_NOARGS, + NULL + }, { NULL }, }; static struct PyGetSetDef Archive_getsetters[] = { - {"format", (getter)Archive_get_format, NULL, NULL, NULL}, + { + "format", + (getter)Archive_get_format, + NULL, + NULL, + NULL + }, + { + "signatures", + (getter)Archive_get_signatures, + NULL, + NULL, + NULL + }, { NULL }, }; diff --git a/src/_pakfire/archive.h b/src/_pakfire/archive.h index a750842d2..0630627c2 100644 --- a/src/_pakfire/archive.h +++ b/src/_pakfire/archive.h @@ -25,8 +25,11 @@ #include +#include "pakfire.h" + typedef struct { PyObject_HEAD + PakfireObject* pakfire; PakfireArchive archive; } ArchiveObject; diff --git a/src/_pakfire/errors.h b/src/_pakfire/errors.h index 4b26f7efc..35033c0c0 100644 --- a/src/_pakfire/errors.h +++ b/src/_pakfire/errors.h @@ -24,6 +24,7 @@ #include // Exceptions +PyObject* PyExc_BadSignatureError; PyObject* PyExc_DependencyError; #endif /* PYTHON_PAKFIRE_ERRORS_H */ diff --git a/src/libpakfire/archive.c b/src/libpakfire/archive.c index 7a60035c5..a82da1076 100644 --- a/src/libpakfire/archive.c +++ b/src/libpakfire/archive.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -33,6 +34,9 @@ #include #include #include +#include +#include +#include #include static void configure_archive(struct archive* a) { @@ -146,10 +150,38 @@ static void pakfire_archive_checksum_free(archive_checksum_t* c) { pakfire_free(c); } -PakfireArchive pakfire_archive_create() { +static archive_signature_t* pakfire_archive_signature_create(const char* sigdata) { + archive_signature_t* s = pakfire_calloc(1, sizeof(*s)); + if (s) { + s->sigdata = pakfire_strdup(sigdata); + } + + return s; +} + +static void pakfire_archive_signature_free(archive_signature_t* s) { + pakfire_free(s->sigdata); + pakfire_free(s); +} + +size_t pakfire_archive_count_signatures(PakfireArchive archive) { + size_t i = 0; + + archive_signature_t** signatures = archive->signatures; + while (*signatures++) { + i++; + } + + return i; +} + +PakfireArchive pakfire_archive_create(Pakfire pakfire) { PakfireArchive archive = pakfire_calloc(1, sizeof(*archive)); if (archive) { + archive->pakfire = pakfire_ref(pakfire); archive->format = -1; + + archive->signatures = NULL; } return archive; @@ -164,6 +196,12 @@ void pakfire_archive_free(PakfireArchive archive) { while (*checksums) pakfire_archive_checksum_free(*checksums++); + // Free signatures + archive_signature_t** signatures = archive->signatures; + while (*signatures) + pakfire_archive_signature_free(*signatures++); + + pakfire_unref(archive->pakfire); pakfire_free(archive); } @@ -284,6 +322,47 @@ static int pakfire_archive_parse_entry_checksums(PakfireArchive archive, return 0; } +static int pakfire_archive_parse_entry_signature(PakfireArchive archive, + struct archive* a, struct archive_entry* e) { + char* data; + size_t data_size; + + int r = archive_read(a, (void**)&data, &data_size); + if (r) + return 1; + + // Terminate string. + data[data_size] = '\0'; + + archive_signature_t* signature = pakfire_archive_signature_create(data); + if (!signature) + return 1; + + if (archive->signatures) { + // Count signatures + size_t num_signatures = pakfire_archive_count_signatures(archive) + 1; + + // Resize the array + archive->signatures = pakfire_realloc(archive->signatures, sizeof(*archive->signatures) * num_signatures); + } else { + archive->signatures = pakfire_calloc(2, sizeof(*archive->signatures)); + } + + // Look for last element + archive_signature_t** signatures = archive->signatures; + while (*signatures) { + *signatures++; + } + + // Append signature + *signatures++ = signature; + + // Terminate list + *signatures = NULL; + + return 0; +} + static int pakfire_archive_read_metadata(PakfireArchive archive, struct archive* a) { int ret; @@ -324,6 +403,12 @@ static int pakfire_archive_read_metadata(PakfireArchive archive, struct archive* ret = pakfire_archive_parse_entry_checksums(archive, a, entry); if (ret) return PAKFIRE_E_PKG_INVALID; + + // Parse signatures + } else if (strncmp(PAKFIRE_ARCHIVE_FN_SIGNATURES, entry_name, strlen(PAKFIRE_ARCHIVE_FN_SIGNATURES)) == 0) { + ret = pakfire_archive_parse_entry_signature(archive, a, entry); + if (ret) + return PAKFIRE_E_PKG_INVALID; } } } @@ -430,8 +515,8 @@ out: } -PakfireArchive pakfire_archive_open(const char* path) { - PakfireArchive archive = pakfire_archive_create(); +PakfireArchive pakfire_archive_open(Pakfire pakfire, const char* path) { + PakfireArchive archive = pakfire_archive_create(pakfire); archive->path = pakfire_strdup(path); // Open the archive file for reading. @@ -555,3 +640,151 @@ unsigned int pakfire_archive_get_format(PakfireArchive archive) { PakfireFile pakfire_archive_get_filelist(PakfireArchive archive) { return archive->filelist; } + +char** pakfire_archive_get_signatures(PakfireArchive archive) { + size_t size = pakfire_archive_count_signatures(archive); + + char** head = pakfire_calloc(size, sizeof(*head)); + if (!head) + return NULL; + + char** list = head; + archive_signature_t** signatures = archive->signatures; + while (*signatures) { + archive_signature_t* signature = *signatures++; + + *list++ = pakfire_strdup(signature->sigdata); + } + + // Terminate list + *list = NULL; + + return head; +} + +static pakfire_archive_verify_status_t pakfire_archive_verify_checksums(PakfireArchive archive) { + pakfire_archive_verify_status_t status = PAKFIRE_ARCHIVE_VERIFY_INVALID; + + char* data = NULL; + size_t size = 0; + gpgme_error_t error; + + // Load the checksums file + int r = pakfire_archive_read(archive, PAKFIRE_ARCHIVE_FN_CHECKSUMS, + (void *)&data, &size, 0); + if (r) + return status; + + // Convert into gpgme data object + gpgme_data_t signed_text; + error = gpgme_data_new_from_mem(&signed_text, data, size, 0); + if (error != GPG_ERR_NO_ERROR) + return -1; + + // Get GPG context + gpgme_ctx_t gpgctx = pakfire_get_gpgctx(archive->pakfire); + + // Try for each signature + archive_signature_t** signatures = archive->signatures; + while (*signatures) { + archive_signature_t* signature = *signatures++; + + gpgme_data_t sigdata; + error = gpgme_data_new_from_mem(&sigdata, signature->sigdata, strlen(signature->sigdata), 0); + if (error != GPG_ERR_NO_ERROR) + continue; + + // Perform verification + error = gpgme_op_verify(gpgctx, sigdata, signed_text, NULL); + if (error != GPG_ERR_NO_ERROR) + goto CLEANUP; + + // Run the operation + gpgme_verify_result_t result = gpgme_op_verify_result(gpgctx); + + // Check if any signatures have been returned + if (!result || !result->signatures) + goto CLEANUP; + + // Walk through all signatures + for (gpgme_signature_t sig = result->signatures; sig; sig = sig->next) { + switch (gpg_err_code(sig->status)) { + // All good + case GPG_ERR_NO_ERROR: + status = PAKFIRE_ARCHIVE_VERIFY_OK; + break; + + // Key has expired (still good) + case GPG_ERR_KEY_EXPIRED: + status = PAKFIRE_ARCHIVE_VERIFY_KEY_EXPIRED; + break; + + // Signature has expired (bad) + case GPG_ERR_SIG_EXPIRED: + status = PAKFIRE_ARCHIVE_VERIFY_SIG_EXPIRED; + break; + + // We don't have the key + case GPG_ERR_NO_PUBKEY: + status = PAKFIRE_ARCHIVE_VERIFY_KEY_UNKNOWN; + break; + + // Bad signature (or any other errors) + case GPG_ERR_BAD_SIGNATURE: + default: + status = PAKFIRE_ARCHIVE_VERIFY_INVALID; + break; + } + } + +CLEANUP: + gpgme_data_release(sigdata); + } + + gpgme_data_release(signed_text); + gpgme_release(gpgctx); + + return status; +} + +static pakfire_archive_verify_status_t pakfire_archive_verify_file(PakfireArchive archive, const archive_checksum_t* checksum) { + return PAKFIRE_ARCHIVE_VERIFY_OK; // TODO +} + +pakfire_archive_verify_status_t pakfire_archive_verify(PakfireArchive archive) { + // TODO Verify that checksums file is signed with a valid key + pakfire_archive_verify_status_t r = pakfire_archive_verify_checksums(archive); + if (r) + return r; + + // TODO Verify checksum of each file + archive_checksum_t** checksum = archive->checksums; + while (*checksum) { + r = pakfire_archive_verify_file(archive, *checksum); + if (r) + return r; + } + + return 0; +} + +const char* pakfire_archive_verify_strerror(pakfire_archive_verify_status_t status) { + switch (status) { + case PAKFIRE_ARCHIVE_VERIFY_OK: + return _("Verify OK"); + + case PAKFIRE_ARCHIVE_VERIFY_INVALID: + return _("Invalid signature"); + + case PAKFIRE_ARCHIVE_VERIFY_SIG_EXPIRED: + return _("Signature expired"); + + case PAKFIRE_ARCHIVE_VERIFY_KEY_EXPIRED: + return _("Key expired"); + + case PAKFIRE_ARCHIVE_VERIFY_KEY_UNKNOWN: + return _("Key unknown"); + } + + return NULL; +} diff --git a/src/libpakfire/include/pakfire/archive.h b/src/libpakfire/include/pakfire/archive.h index 2bbea8f65..d102dbd9d 100644 --- a/src/libpakfire/include/pakfire/archive.h +++ b/src/libpakfire/include/pakfire/archive.h @@ -25,10 +25,18 @@ #include -PakfireArchive pakfire_archive_create(); +typedef enum pakfire_archive_verify_status { + PAKFIRE_ARCHIVE_VERIFY_OK = 0, + PAKFIRE_ARCHIVE_VERIFY_INVALID, + PAKFIRE_ARCHIVE_VERIFY_SIG_EXPIRED, + PAKFIRE_ARCHIVE_VERIFY_KEY_EXPIRED, + PAKFIRE_ARCHIVE_VERIFY_KEY_UNKNOWN, +} pakfire_archive_verify_status_t; + +PakfireArchive pakfire_archive_create(Pakfire pakfire); void pakfire_archive_free(PakfireArchive archive); -PakfireArchive pakfire_archive_open(const char* path); +PakfireArchive pakfire_archive_open(Pakfire pakfire, const char* path); int pakfire_archive_read(PakfireArchive archive, const char* filename, void** data, size_t* data_size, int flags); @@ -40,6 +48,12 @@ unsigned int pakfire_archive_get_format(PakfireArchive archive); PakfireFile pakfire_archive_get_filelist(PakfireArchive archive); +size_t pakfire_archive_count_signatures(PakfireArchive archive); +char** pakfire_archive_get_signatures(PakfireArchive archive); + +pakfire_archive_verify_status_t pakfire_archive_verify(PakfireArchive archive); +const char* pakfire_archive_verify_strerror(pakfire_archive_verify_status_t status); + enum pakfire_archive_flags { PAKFIRE_ARCHIVE_USE_PAYLOAD = 1 << 0, }; @@ -49,6 +63,7 @@ enum pakfire_archive_flags { #define PAKFIRE_ARCHIVE_FN_FORMAT "pakfire-format" #define PAKFIRE_ARCHIVE_FN_METADATA "info" #define PAKFIRE_ARCHIVE_FN_PAYLOAD "data.img" +#define PAKFIRE_ARCHIVE_FN_SIGNATURES "signatures" #ifdef PAKFIRE_PRIVATE @@ -66,7 +81,12 @@ typedef struct archive_checksum { archive_checksum_algo_t algo; } archive_checksum_t; +typedef struct archive_signature { + char* sigdata; +} archive_signature_t; + struct _PakfireArchive { + Pakfire pakfire; char* path; // metadata @@ -74,6 +94,7 @@ struct _PakfireArchive { PakfireFile filelist; archive_checksum_t** checksums; + archive_signature_t** signatures; }; struct payload_archive_data { diff --git a/src/libpakfire/include/pakfire/key.h b/src/libpakfire/include/pakfire/key.h index 8bb8de170..36111c319 100644 --- a/src/libpakfire/include/pakfire/key.h +++ b/src/libpakfire/include/pakfire/key.h @@ -59,6 +59,8 @@ char* pakfire_key_dump(PakfireKey key); #ifdef PAKFIRE_PRIVATE +gpgme_ctx_t pakfire_get_gpgctx(Pakfire pakfire); + struct _PakfireKey { Pakfire pakfire; gpgme_key_t gpgkey; diff --git a/src/libpakfire/key.c b/src/libpakfire/key.c index 5fdbd4c71..938a6438f 100644 --- a/src/libpakfire/key.c +++ b/src/libpakfire/key.c @@ -31,7 +31,7 @@ #define DEFAULT_KEY_SIZE "rsa4096" -static gpgme_ctx_t pakfire_get_gpgctx(Pakfire pakfire) { +gpgme_ctx_t pakfire_get_gpgctx(Pakfire pakfire) { static int gpg_initialized = 0; gpgme_error_t error; const char* error_string; diff --git a/src/libpakfire/libpakfire.sym b/src/libpakfire/libpakfire.sym index ddcfa19d1..460b5f656 100644 --- a/src/libpakfire/libpakfire.sym +++ b/src/libpakfire/libpakfire.sym @@ -29,14 +29,18 @@ global: pakfire_unref; # archive + pakfire_archive_count_signatures; pakfire_archive_create; pakfire_archive_extract; pakfire_archive_free; pakfire_archive_get_filelist; pakfire_archive_get_format; pakfire_archive_get_path; + pakfire_archive_get_signatures; pakfire_archive_open; pakfire_archive_read; + pakfire_archive_verify; + pakfire_archive_verify_strerror; # file pakfire_file_append;