diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1591087 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/tang-7.tar.bz2 diff --git a/0001-Move-key-generation-to-tang.patch b/0001-Move-key-generation-to-tang.patch new file mode 100644 index 0000000..74fa4cc --- /dev/null +++ b/0001-Move-key-generation-to-tang.patch @@ -0,0 +1,1743 @@ +From a0aa280cfe7a756bc9f965129c847dc9bfd2e84d Mon Sep 17 00:00:00 2001 +From: Sergio Correia +Date: Sun, 29 Sep 2019 10:05:05 -0300 +Subject: [PATCH] Move key generation to tang + +Until now, tang has relied the following two scripts to handle keys: +- tangd-keygen, which generates the keys in a specific JWK dir +- tangd-update, which reads the directory with the keys and updates a + cache directory with files that will be used by tang during its + operation + +We also rely on systemd to watch the JWK dir and automatically run +tangd-update when it is modified, so that the cache directory would be +kept in a consistent state with regard to the available keys. + +However, this has shown to be unreliable and to cause issues in some +situations. For instance, in #23 we see that sometimes the keys are not +ready when tang starts operating, and in #24 we see that we have issues +if somehow the cache directory is removed while tang is running. + +To improve reliability, the handling of keys is now being moved to tang +itself. In this commit we implement routines to perform the operations +that the aforementioned scripts did; tang can now create keys and also +read them directly to perform its required operations. + +Changes after this commit: +1) there is no cache directory anymore; tang will read the keys directly + from JWK dir instead of reading files from the cache directory +2) as a consequence of 1), we now pass JWK dir as the argument to tang +3) tangd-update is gone, since there is no need for a cache directory +4) when reading JWK dir, tang will create keys if there are none -- this + was already the case on startup before this commit, but now we do not + rely on systemd units to do this any longer +5) many of the previously used systemd units are not required anymore + +Resolves #23 +Resolves #24 +--- + Makefile.am | 11 +- + src/keys.c | 1043 +++++++++++++++++++++++++++++++++ + src/keys.h | 84 +++ + src/tangd-update | 83 --- + src/tangd.c | 60 +- + src/util.c | 141 +++++ + src/util.h | 33 ++ + tests/adv | 4 +- + tests/rec | 4 +- + units/tangd-keygen.service.in | 8 - + units/tangd-update.path.in | 4 - + units/tangd-update.service.in | 6 - + units/tangd.socket.in | 5 - + units/tangd@.service.in | 2 +- + 14 files changed, 1342 insertions(+), 146 deletions(-) + create mode 100644 src/keys.c + create mode 100644 src/keys.h + delete mode 100755 src/tangd-update + create mode 100644 src/util.c + create mode 100644 src/util.h + delete mode 100644 units/tangd-keygen.service.in + delete mode 100644 units/tangd-update.path.in + delete mode 100644 units/tangd-update.service.in + +diff --git a/Makefile.am b/Makefile.am +index af30d2f..f855dcd 100644 +--- a/Makefile.am ++++ b/Makefile.am +@@ -7,17 +7,13 @@ man8_MANS= + AM_CFLAGS = @TANG_CFLAGS@ @jose_CFLAGS@ + LDADD = @jose_LIBS@ @http_parser_LIBS@ + +-cachedir = $(localstatedir)/cache/$(PACKAGE_NAME) + jwkdir = $(localstatedir)/db/$(PACKAGE_NAME) + + nodist_systemdsystemunit_DATA = \ + units/tangd@.service \ +- units/tangd.socket \ +- units/tangd-update.path \ +- units/tangd-update.service \ +- units/tangd-keygen.service ++ units/tangd.socket + +-dist_libexec_SCRIPTS = src/tangd-update src/tangd-keygen ++dist_libexec_SCRIPTS = src/tangd-keygen + dist_bin_SCRIPTS = src/tang-show-keys + libexec_PROGRAMS = src/tangd + +@@ -39,14 +35,13 @@ man1_MANS += doc/tang-show-keys.1 + man8_MANS += doc/tang.8 + endif + +-src_tangd_SOURCES = src/http.c src/http.h src/tangd.c ++src_tangd_SOURCES = src/util.c src/util.h src/keys.c src/keys.h src/http.c src/http.h src/tangd.c + + %: %.in + $(AM_V_GEN)mkdir -p "`dirname "$@"`" + $(AM_V_GEN)$(SED) \ + -e 's,@libexecdir\@,$(libexecdir),g' \ + -e 's,@jwkdir\@,$(jwkdir),g' \ +- -e 's,@cachedir\@,$(cachedir),g' \ + $(srcdir)/$@.in > $@ + + AM_TESTS_ENVIRONMENT = SD_ACTIVATE="@SD_ACTIVATE@" PATH=$(srcdir)/src:$(builddir)/src:$(PATH) +diff --git a/src/keys.c b/src/keys.c +new file mode 100644 +index 0000000..77f5d3c +--- /dev/null ++++ b/src/keys.c +@@ -0,0 +1,1043 @@ ++/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */ ++/* ++ * Copyright (c) 2019 Red Hat, Inc. ++ * Author: Sergio Correia ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * 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, see . ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "util.h" ++#include "keys.h" ++ ++#ifndef PATH_MAX ++#define PATH_MAX 4096 ++#endif ++ ++#define TANG_MAXBUFLEN (1024 * 1024) ++ ++#define DEFAULT_HASH_ALG "S1" ++ ++/* TODO: check if jose has a way to export the hash algorithms it supports. */ ++const char *hash_alg[] = {"S1", "S224", "S256", "S384", "S512", NULL}; ++ ++size_t ++hash_alg_size(void) ++{ ++ size_t count = 0; ++ for (size_t i = 0; hash_alg[i]; i++) { ++ count++; ++ } ++ return count; ++} ++ ++int ++is_hash(const char *alg) ++{ ++ for (size_t a = 0, size = hash_alg_size(); a < size; a++) { ++ if (strcmp(alg, hash_alg[a]) == 0) { ++ return 1; ++ } ++ } ++ return 0; ++} ++ ++/* ++ * Generates a JWK and returns a json_t*, which must be released with ++ * json_decref(). ++ */ ++static json_t* ++jwk_generate(const char* alg) ++{ ++ json_t *jalg = json_pack("{s:s}", "alg", alg); ++ if (!jalg) { ++ fprintf(stderr, "Error packing JSON with alg %s\n", alg); ++ return NULL; ++ } ++ ++ if (!jose_jwk_gen(NULL, jalg)) { ++ fprintf(stderr, "Error generating JWK with alg %s\n", alg); ++ json_decref(jalg); ++ return NULL; ++ } ++ ++ return jalg; ++} ++ ++/* ++ * Returns a thumbprint from a JWK and a given algorithm, which must be ++ * released with free(). ++ */ ++char* ++jwk_thumbprint(const json_t* jwk, const char* alg) ++{ ++ size_t elen = 0; ++ size_t dlen = 0; ++ ++ const char* hash = alg; ++ if (!is_hash(alg)) { ++ hash = DEFAULT_HASH_ALG; ++ } ++ ++ dlen = jose_jwk_thp_buf(NULL, NULL, hash, NULL, 0); ++ if (dlen == SIZE_MAX) { ++ fprintf(stderr, "Error determining hash size for %s\n", hash); ++ return NULL; ++ } ++ ++ elen = jose_b64_enc_buf(NULL, dlen, NULL, 0); ++ if (elen == SIZE_MAX) { ++ fprintf(stderr, "Error determining encoded size for %s\n", hash); ++ return NULL; ++ } ++ ++ uint8_t dec[dlen]; ++ char enc[elen]; ++ ++ if (!jose_jwk_thp_buf(NULL, jwk, hash, dec, sizeof(dec))) { ++ fprintf(stderr, "Error making thumbprint\n"); ++ return NULL; ++ } ++ ++ if (jose_b64_enc_buf(dec, dlen, enc, sizeof(enc)) != elen) { ++ fprintf(stderr, "Error encoding data Base64\n"); ++ return NULL; ++ } ++ ++ char *thp = malloc(elen + 1); ++ if (!thp) { ++ fprintf(stderr, "Error allocating string for thumbprint\n"); ++ return NULL; ++ } ++ ++ if (!strncpy(thp, enc, elen)) { ++ fprintf(stderr, "Error copying thumbprint to string\n"); ++ free(thp); ++ return NULL; ++ } ++ ++ thp[elen] = '\0'; ++ return thp; ++} ++ ++char* ++jwk_thumbprint_from_file(const char *file, const char *alg) ++{ ++ json_auto_t *jwk = json_load_file(file, 0, NULL); ++ if (!jwk) { ++ return 0; ++ } ++ return jwk_thumbprint(jwk, alg); ++} ++ ++/* ++ * Releases the allocated memory by struct tang_jwk*. ++ */ ++void ++free_tang_jwk(struct tang_jwk* tjwk) ++{ ++ if (!tjwk) { ++ return; ++ } ++ ++ if (tjwk->m_json) { ++ json_decref(tjwk->m_json); ++ } ++ free(tjwk->m_from_file); ++ free(tjwk->m_thp); ++ free(tjwk->m_alg); ++ free(tjwk->m_str); ++ free(tjwk); ++} ++ ++void ++cleanup_tang_jwk(struct tang_jwk **jwk) ++{ ++ if (!jwk || !*jwk) { ++ return; ++ } ++ free_tang_jwk(*jwk); ++} ++ ++struct tang_jwk* ++new_tang_jwk_from_args(json_t *jwk, const char* thp, const char* alg) ++{ ++ if (!jwk) { ++ fprintf(stderr, "Invalid JWK (%p) \n", jwk); ++ return NULL; ++ } ++ ++ struct tang_jwk *tjwk = calloc(1, sizeof(*tjwk)); ++ if (!tjwk) { ++ fprintf(stderr, "Error allocating new struct tang_jwk.\n"); ++ return NULL; ++ } ++ ++ tjwk->m_json = json_incref(jwk); ++ tjwk->m_from_file = NULL; ++ ++ if (alg) { ++ tjwk->m_alg = strdup(alg); ++ if (!tjwk->m_alg) { ++ fprintf(stderr, "Unable to copy algorithm (%s) to tang_jwk.\n", alg); ++ free_tang_jwk(tjwk); ++ return NULL; ++ } ++ } ++ ++ if (thp) { ++ tjwk->m_thp = strdup(thp); ++ if (!tjwk->m_thp) { ++ fprintf(stderr, "Unable to copy thumbprint (%s).\n", thp); ++ free_tang_jwk(tjwk); ++ return NULL; ++ } ++ } ++ ++ tjwk->m_str = json_dumps(tjwk->m_json, JSON_SORT_KEYS | JSON_COMPACT); ++ if (!tjwk->m_str) { ++ fprintf(stderr, "Unable to get string version from JWK.\n"); ++ free_tang_jwk(tjwk); ++ return NULL; ++ } ++ ++ return tjwk; ++} ++ ++struct tang_jwk* ++tang_jwk_dup(const struct tang_jwk *jwk) ++{ ++ if (!jwk) { ++ return NULL; ++ } ++ ++ struct tang_jwk *new_jwk = new_tang_jwk_from_args(jwk->m_json, jwk->m_thp, jwk->m_alg); ++ if (!new_jwk) { ++ return NULL; ++ } ++ ++ if (jwk->m_from_file) { ++ new_jwk->m_from_file = strdup(jwk->m_from_file); ++ if (!new_jwk->m_from_file) { ++ free_tang_jwk(new_jwk); ++ return NULL; ++ } ++ } ++ ++ return new_jwk; ++} ++ ++struct tang_jwk* ++new_tang_jwk(const char* file, const char* alg) ++{ ++ if (!file || !alg) { ++ fprintf(stderr, "Invalid file (%s) or algorithm (%s).\n", file, alg); ++ return NULL; ++ } ++ ++ json_auto_t *jwk = json_load_file(file, 0, NULL); ++ if (!jwk) { ++ fprintf(stderr, "Unable to parse JSON from %s.\n", file); ++ return NULL; ++ } ++ ++ struct tang_jwk *tjwk = calloc(1, sizeof(*tjwk)); ++ if (!tjwk) { ++ fprintf(stderr, "Error allocating new struct tang_jwk.\n"); ++ return NULL; ++ } ++ ++ tjwk->m_json = json_incref(jwk); ++ tjwk->m_from_file = strdup(file); ++ if (!tjwk->m_from_file) { ++ fprintf(stderr, "Unable to copy file name (%s) to m_from_file.\n", file); ++ free_tang_jwk(tjwk); ++ return NULL; ++ } ++ tjwk->m_alg = strdup(alg); ++ if (!tjwk->m_alg) { ++ fprintf(stderr, "Unable to copy algorithm (%s) to tang_jwk.\n", alg); ++ free_tang_jwk(tjwk); ++ return NULL; ++ } ++ ++ tjwk->m_thp = jwk_thumbprint(tjwk->m_json, tjwk->m_alg); ++ if (!tjwk->m_thp) { ++ fprintf(stderr, "Unable to get thumbprint using alg (%s).\n", alg); ++ free_tang_jwk(tjwk); ++ return NULL; ++ } ++ ++ tjwk->m_str = json_dumps(tjwk->m_json, JSON_SORT_KEYS | JSON_COMPACT); ++ if (!tjwk->m_str) { ++ fprintf(stderr, "Unable to get string version from JWK.\n"); ++ free_tang_jwk(tjwk); ++ return NULL; ++ } ++ ++ return tjwk; ++} ++ ++/* ++ * Builds a new struct tang_jwk*, which should be destructed by calling ++ * free_tang_jwk(). ++ */ ++struct tang_jwk* ++generate_new_tang_jwk(const char* alg) ++{ ++ if (!alg) { ++ fprintf(stderr, "Invalid algorithm.\n"); ++ return NULL; ++ } ++ ++ struct tang_jwk *tjwk = calloc(1, sizeof(*tjwk)); ++ if (!tjwk) { ++ fprintf(stderr, "Error allocating new struct tang_jwk.\n"); ++ return NULL; ++ } ++ ++ tjwk->m_alg = strdup(alg); ++ if (!tjwk->m_alg) { ++ fprintf(stderr, "Unable to copy algorithm (%s) to tang_jwk.\n", alg); ++ free_tang_jwk(tjwk); ++ return NULL; ++ } ++ ++ tjwk->m_json = jwk_generate(alg); ++ if (!tjwk->m_json) { ++ fprintf(stderr, "Unable to generate new JWK using alg (%s).\n", alg); ++ free_tang_jwk(tjwk); ++ return NULL; ++ } ++ tjwk->m_thp = jwk_thumbprint(tjwk->m_json, tjwk->m_alg); ++ if (!tjwk->m_thp) { ++ fprintf(stderr, "Unable to get thumbprint using alg (%s).\n", alg); ++ free_tang_jwk(tjwk); ++ return NULL; ++ } ++ ++ tjwk->m_str = json_dumps(tjwk->m_json, JSON_SORT_KEYS | JSON_COMPACT); ++ if (!tjwk->m_str) { ++ fprintf(stderr, "Unable to get string version from JWK.\n"); ++ free_tang_jwk(tjwk); ++ return NULL; ++ } ++ return tjwk; ++} ++ ++static int ++file_valid_for(const char *file, const char *use) ++{ ++ json_auto_t *jwk = json_load_file(file, 0, NULL); ++ if (!jwk) { ++ return 0; ++ } ++ ++ return jose_jwk_prm(NULL, jwk, false, use); ++} ++ ++static int ++jwk_valid_for(const json_t *jwk, const char *use) ++{ ++ return jose_jwk_prm(NULL, jwk, false, use); ++} ++ ++ ++int valid_for_signing_and_verifying(const char *file) ++{ ++ json_auto_t *jwk = json_load_file(file, 0, NULL); ++ if (!jwk) { ++ return 0; ++ } ++ ++ const char *use[] = {"sign", "verify", NULL}; ++ int ret = 1; ++ for (int i = 0; use[i] != NULL; i++) { ++ if (!jwk_valid_for(jwk, use[i])) { ++ ret = 0; ++ break; ++ } ++ } ++ ++ return ret; ++} ++ ++int valid_for_signing(const char *file) ++{ ++ return file_valid_for(file, "sign"); ++} ++ ++int valid_for_deriving_keys(const char *file) ++{ ++ return file_valid_for(file, "deriveKey"); ++} ++ ++struct tang_jwk_list* ++new_tang_jwk_list(void) ++{ ++ struct tang_jwk_list *tjl = malloc(sizeof(*tjl)); ++ if (!tjl) { ++ return NULL; ++ } ++ tjl->m_jwk = NULL; ++ tjl->m_size = 0; ++ return tjl; ++} ++ ++void ++free_tang_jwk_list(struct tang_jwk_list *tjl) ++{ ++ if (!tjl) { ++ return; ++ } ++ ++ for (size_t i = 0, size = tjl->m_size; i < size; i++) { ++ free_tang_jwk(tjl->m_jwk[i]); ++ } ++ free(tjl->m_jwk); ++ free(tjl); ++} ++ ++int ++tang_jwk_list_add(struct tang_jwk_list *tjl, struct tang_jwk *jwk_to_add) ++{ ++ if (!tjl || !jwk_to_add) { ++ return 0; ++ } ++ ++ struct tang_jwk *jwk = tang_jwk_dup(jwk_to_add); ++ if (!jwk) { ++ return 0; ++ } ++ ++ struct tang_jwk **new_jwk = realloc(tjl->m_jwk, sizeof(struct tang_jwk*) * (tjl->m_size + 1)); ++ if (!new_jwk) { ++ fprintf(stderr, "Error reallocating memory for the new JWK.\n"); ++ free_tang_jwk(jwk); ++ return 0; ++ } ++ ++ tjl->m_jwk = new_jwk; ++ tjl->m_jwk[tjl->m_size++] = jwk; ++ return 1; ++} ++ ++static int ++tang_jwk_thp_bsearch_cmp_func(const void *a, const void *b) ++{ ++ const char *key = (const char*)a; ++ const struct tang_jwk *jwk = *(const struct tang_jwk**)b; ++ return strcmp(key, jwk->m_thp); ++} ++ ++struct tang_jwk* ++tang_jwk_list_find_thp(const struct tang_jwk_list *tjl, const char *thp) ++{ ++ if (!tjl || !thp) { ++ return NULL; ++ } ++ ++ if (tjl->m_size == 0) { ++ return NULL; ++ } ++ ++ struct tang_jwk **item = bsearch(thp, tjl->m_jwk, tjl->m_size, sizeof(struct tang_jwk*), tang_jwk_thp_bsearch_cmp_func); ++ if (!item) { ++ return NULL; ++ } ++ return *item; ++} ++ ++void ++free_tang_keys_info(struct tang_keys_info *tki) ++{ ++ if (!tki) { ++ return; ++ } ++ ++ free(tki->m_jwkdir); ++ free_file_list(tki->m_payload_keys); ++ free_file_list(tki->m_sign_keys); ++ free_tang_jwk_list(tki->m_derive); ++ free_tang_jwk_list(tki->m_adv); ++ free_tang_jwk(tki->m_default_adv); ++ free(tki); ++} ++ ++struct tang_keys_info* ++new_tang_keys_info(const char* jwkdir) ++{ ++ if (!jwkdir) { ++ fprintf(stderr, "Invalid JWK dir.\n"); ++ return NULL; ++ } ++ ++ struct tang_keys_info *tki = calloc(1, sizeof(struct tang_keys_info)); ++ if (!tki) { ++ fprintf(stderr, "Error allocating tang_keys_info struct.\n"); ++ return NULL; ++ } ++ ++ tki->m_jwkdir = strdup(jwkdir); ++ if (!tki->m_jwkdir) { ++ fprintf(stderr, "Error copying JWK dir to tang_keys_info struct.\n"); ++ free_tang_keys_info(tki); ++ return NULL; ++ } ++ ++ tki->m_payload_keys = new_file_list(); ++ if (!tki->m_payload_keys) { ++ fprintf(stderr, "Error allocating payload keys.\n"); ++ free_tang_keys_info(tki); ++ return NULL; ++ } ++ ++ tki->m_sign_keys = new_file_list(); ++ if (!tki->m_sign_keys) { ++ fprintf(stderr, "Error allocating signing keys.\n"); ++ free_tang_keys_info(tki); ++ return NULL; ++ } ++ ++ tki->m_derive = new_tang_jwk_list(); ++ if (!tki->m_derive) { ++ fprintf(stderr, "Error allocating list of deriving keys.\n"); ++ free_tang_keys_info(tki); ++ return NULL; ++ } ++ ++ tki->m_adv = new_tang_jwk_list(); ++ if (!tki->m_adv) { ++ fprintf(stderr, "Error allocating list adv.\n"); ++ free_tang_keys_info(tki); ++ return NULL; ++ } ++ ++ tki->m_default_adv = NULL; ++ ++ return tki; ++} ++ ++int ++check_keys(const char* jwkdir) ++{ ++ if (!jwkdir) { ++ return 0; ++ } ++ ++ /* We ignore hidden files in here because we only care about ++ * advertised keys. */ ++ struct file_list *fl __attribute__ ((__cleanup__(cleanup_file_list))) = list_files(jwkdir, ".jwk", 1 /* ignore hidden */); ++ if (!fl) { ++ return 0; ++ } ++ ++ if (fl->m_size > 0) { ++ /* There are already keys in the JWKdir, so let's leave it as is. */ ++ return 1; ++ } ++ ++ /* At this point, there are no keys, so let's create them. */ ++ const char *alg[] = {"ES512", "ECMR", NULL}; ++ char path[PATH_MAX]; ++ for (int i = 0; alg[i] != NULL; i++) { ++ struct tang_jwk *jwk __attribute__((cleanup(cleanup_tang_jwk))) = generate_new_tang_jwk(alg[i]); ++ if (!jwk) { ++ fprintf(stderr, "Error generating JWK using %s\n", alg[i]); ++ return 0; ++ } ++ ++ snprintf(path, PATH_MAX, "%s/%s.jwk", jwkdir, jwk->m_thp); ++ path[sizeof(path) - 1] = '\0'; ++ ++ FILE *fp = fopen(path, "w+"); ++ if (!fp) { ++ fprintf(stderr, "Error creating JWK file to %s\n", path); ++ return 0; ++ } ++ fprintf(fp, "%s", jwk->m_str); ++ fclose(fp); ++ } ++ return 1; ++} ++ ++static int ++tang_jwk_thp_cmp_func(const void *a, const void *b) ++{ ++ const struct tang_jwk *ta = *(const struct tang_jwk**)a; ++ const struct tang_jwk *tb = *(const struct tang_jwk**)b; ++ return strcmp(ta->m_thp, tb->m_thp); ++} ++ ++void ++cleanup_tang_keys_info(struct tang_keys_info **tki) ++{ ++ if (!tki || !*tki) { ++ return; ++ } ++ free_tang_keys_info(*tki); ++} ++ ++static void ++cleanup_buffer(char **buffer) ++{ ++ if (!buffer || !*buffer) { ++ return; ++ } ++ free(*buffer); ++} ++ ++static void ++cleanup_jose_io_t(jose_io_t ***iosp) ++{ ++ jose_io_t **ios = *iosp; ++ for (size_t i = 0; ios && ios[i]; i++) { ++ jose_io_auto(&ios[i]); ++ } ++} ++ ++static json_t* ++build_json_array(const char **files, size_t total_files) ++{ ++ if (!files || total_files == 0) { ++ return NULL; ++ } ++ ++ json_t *arr = json_array(); ++ if (!arr) { ++ fprintf(stderr, "Unable to create json array\n"); ++ return NULL; ++ } ++ ++ for (size_t i = 0; i < total_files; i++) { ++ json_t *jwk = json_load_file(files[i], 0, NULL); ++ if (!jwk) { ++ fprintf(stderr, "Unable to load JSON from %s; skipping\n", files[i]); ++ continue; ++ } ++ ++ if (json_array_append_new(arr, jwk) != 0) { ++ fprintf(stderr, "Unable to append JSON %s to array; skipping\n", files[i]); ++ continue; ++ } ++ } ++ return arr; ++} ++ ++static json_t* ++remove_private_keys(const struct file_list *fl) ++{ ++ if (!fl) { ++ fprintf(stderr, "Invalid file list for cleaning private keys.\n"); ++ return NULL; ++ } ++ ++ json_auto_t *array = build_json_array((const char**)fl->m_files, fl->m_size); ++ if (!array || json_array_size(array) == 0) { ++ fprintf(stderr, "Empty array %p.\n", array); ++ return NULL; ++ } ++ ++ for (size_t i = 0, size = json_array_size(array); i < size; i++) { ++ if (!jose_jwk_pub(NULL, json_array_get(array, i))) { ++ fprintf(stderr, "Error removing private keys.\n"); ++ return NULL; ++ } ++ } ++ ++ return json_pack("{s:O}", "keys", array); ++} ++ ++static json_t* ++prepare_template_sigs(size_t total) ++{ ++ json_t *arr = json_array(); ++ if (!arr) { ++ fprintf(stderr, "Unable to create JSON sigs array\n"); ++ return NULL; ++ } ++ ++ for (size_t i = 0; i < total; i++) { ++ json_t *cty = json_pack("{s:{s:s}}", "protected", "cty", "jwk-set+json"); ++ if (!cty) { ++ fprintf(stderr, "Unable to create item %zu/%zu; skipping\n", i, total); ++ continue; ++ } ++ ++ if (json_array_append_new(arr, cty) != 0) { ++ fprintf(stderr, "Unable to append item %zu/%zu to array; skipping\n", i, total); ++ continue; ++ } ++ } ++ return arr; ++} ++ ++static jose_io_t* ++tang_prep_io(jose_io_t *io, uint8_t *buffer, size_t *buflen) ++{ ++ if (!io || !buffer || !buflen) { ++ fprintf(stderr, "Either io (%p) the buffer (%p) or the buffer len (%p) are NULL\n", io, buffer, buflen); ++ } ++ ++ jose_io_t **ios __attribute__((cleanup(cleanup_jose_io_t))) = NULL; ++ size_t i = 0; ++ ++ ios = alloca(sizeof(*ios) * 3); ++ memset(ios, 0, sizeof(*ios) * 3); ++ ++ if (io) { ++ ios[i++] = io; ++ } ++ ++ ios[i] = jose_io_buffer(NULL, buffer, buflen); ++ if (!ios[i]) { ++ return NULL; ++ } ++ ++ for (i = 0; ios[i]; i++) { ++ jose_io_auto_t *b64 = NULL; ++ ++ b64 = jose_b64_enc_io(ios[i]); ++ if (!b64) { ++ return NULL; ++ } ++ ++ jose_io_decref(ios[i]); ++ ios[i] = jose_io_incref(b64); ++ } ++ ++ return jose_io_multiplex(NULL, ios, true); ++} ++ ++static int ++prepare_deriving_jwk(struct tang_keys_info *tki, const struct file_list *fl) ++{ ++ const size_t hash_alg_count = hash_alg_size(); ++ for (size_t a = 0; a < hash_alg_count; a++) { ++ for (size_t i = 0; i < fl->m_size; i++) { ++ struct tang_jwk *jwk __attribute__((cleanup(cleanup_tang_jwk))) = new_tang_jwk(fl->m_files[i], hash_alg[a]); ++ if (!jwk) { ++ fprintf(stderr, "Unable to create tang_jwk from %s with alg %s; skipping.\n", fl->m_files[i], hash_alg[a]); ++ continue; ++ } ++ if (!tang_jwk_list_add(tki->m_derive, jwk)) { ++ fprintf(stderr, "Unable to add JWK from %s with alg %s to list of deriving keys; skipping.\n", fl->m_files[i], hash_alg[a]); ++ continue; ++ } ++ } ++ } ++ ++ if (tki->m_derive->m_size > 1) { ++ qsort(tki->m_derive->m_jwk, tki->m_derive->m_size, sizeof(struct tang_jwk*), tang_jwk_thp_cmp_func); ++ } ++ return 1; ++} ++ ++static int ++prepare_adv_jwk(struct tang_keys_info *tki, const struct file_list *fl) ++{ ++ const size_t hash_alg_count = hash_alg_size(); ++ json_auto_t *keys = build_json_array((const char**)tki->m_sign_keys->m_files, tki->m_sign_keys->m_size); ++ json_auto_t *pub = remove_private_keys(tki->m_payload_keys); ++ ++ /* ++ * Adding dummy element in the first position. We will be be replacing ++ * it with the actual ones and then creating the JWS data. ++ */ ++ json_auto_t *dummy = json_object(); ++ if (json_array_insert(keys, 0, dummy) != 0) { ++ fprintf(stderr, "Error preparing adv JWKs.\n"); ++ return 0; ++ } ++ ++ json_auto_t *sigs = prepare_template_sigs(json_array_size(keys)); ++ ++ for (size_t i = 0; i < fl->m_size; i++) { ++ json_auto_t *jwk = json_load_file(fl->m_files[i], 0, NULL); ++ if (!jwk) { ++ fprintf(stderr, "Unable to load JWK from %s; skipping.\n", fl->m_files[i]); ++ continue; ++ } ++ if (json_array_set(keys, 0, jwk) != 0) { ++ fprintf(stderr, "Unable to add JWK from file (%s) to array; skipping.\n", fl->m_files[i]); ++ continue; ++ } ++ ++ char *jws_data __attribute__((cleanup(cleanup_buffer))) = process_adv(keys, sigs, pub); ++ if (!jws_data) { ++ fprintf(stderr, "Unable to obtain JWS from %s; skipping.\n", fl->m_files[i]); ++ continue; ++ } ++ json_auto_t *jws_json = json_loads(jws_data, 0, NULL); ++ if (!jws_json) { ++ fprintf(stderr, "Unable to convert string to JSON; skipping.\n"); ++ continue; ++ } ++ for (size_t a = 0; a < hash_alg_count; a++) { ++ char *thp __attribute__((cleanup(cleanup_buffer))) = jwk_thumbprint(jwk, hash_alg[a]); ++ if (!thp) { ++ fprintf(stderr, "Unable to obtain thumbprint from file (%s) and alg (%s); skipping.\n", fl->m_files[i], hash_alg[a]); ++ continue; ++ } ++ struct tang_jwk *jws __attribute__((cleanup(cleanup_tang_jwk)))= new_tang_jwk_from_args(jws_json, thp, hash_alg[a]); ++ if (!jws) { ++ fprintf(stderr, "Error creating tang_jwk with JWS data from file (%s) and alg (%s); skipping.\n", fl->m_files[i], hash_alg[a]); ++ continue; ++ } ++ if (!tang_jwk_list_add(tki->m_adv, jws)) { ++ fprintf(stderr, "Error adding JWS data from file (%s) and alg (%s) to adv list; skipping.\n", fl->m_files[i], hash_alg[a]); ++ continue; ++ } ++ } ++ } ++ ++ if (tki->m_adv->m_size > 1) { ++ qsort(tki->m_adv->m_jwk, tki->m_adv->m_size, sizeof(struct tang_jwk*), tang_jwk_thp_cmp_func); ++ } ++ return 1; ++} ++ ++static int ++prepare_deriving_and_adv(struct tang_keys_info *tki) ++{ ++ if (!tki) { ++ return 0; ++ } ++ ++ struct file_list *fl __attribute__ ((__cleanup__(cleanup_file_list))) = list_files(tki->m_jwkdir, ".jwk", 0 /* ignore hidden */); ++ if (!fl || fl->m_size == 0) { ++ fprintf(stderr, "No JWK keys available.\n"); ++ return 0; ++ } ++ ++ struct file_list *derive_key __attribute__((__cleanup__(cleanup_file_list))) = new_file_list(); ++ struct file_list *jws __attribute__((__cleanup__(cleanup_file_list))) = new_file_list(); ++ ++ char filepath[PATH_MAX] = {}; ++ for (size_t i = 0, size = fl->m_size; i < size; i++) { ++ snprintf(filepath, sizeof(filepath), "%s/%s", tki->m_jwkdir, fl->m_files[i]); ++ filepath[sizeof(filepath) - 1] = '\0'; ++ if (valid_for_deriving_keys(filepath)) { ++ if (!file_list_add(derive_key, filepath)) { ++ fprintf(stderr, "Error adding %s to file list of keys valid for deriving keys; skipping.\n", filepath); ++ } ++ } else if (valid_for_signing(filepath)) { ++ if (!file_list_add(jws, filepath)) { ++ fprintf(stderr, "Error adding %s to file list of keys valid for signing; skipping.\n", filepath); ++ } ++ } ++ } ++ ++ if (derive_key->m_size == 0 || jws->m_size == 0) { ++ fprintf(stderr, "Either the number of keys able to derive keys (%zu) or to sign keys (%zu) is zero.\n", derive_key->m_size, jws->m_size); ++ return 0; ++ } ++ ++ if (!prepare_deriving_jwk(tki, derive_key)) { ++ fprintf(stderr, "Error preparing deriving keys JWK.\n"); ++ return 0; ++ } ++ ++ if (!prepare_adv_jwk(tki, jws)) { ++ fprintf(stderr, "Error preparing advertising JWK.\n"); ++ return 0; ++ } ++ ++ return 1; ++} ++ ++struct tang_jwk* ++find_adv(const struct tang_keys_info *tki, const char* thp) ++{ ++ if (!tki) { ++ fprintf(stderr, "Invalid tang_keys_info (%p).\n", tki); ++ return NULL; ++ } ++ return tang_jwk_list_find_thp(tki->m_adv, thp); ++} ++ ++struct tang_jwk* ++find_deriving_key(const struct tang_keys_info *tki, const char* thp) ++{ ++ if (!tki) { ++ fprintf(stderr, "Invalid tang_keys_info (%p).\n", tki); ++ return NULL; ++ } ++ return tang_jwk_list_find_thp(tki->m_derive, thp); ++} ++ ++struct tang_keys_info* ++read_keys(const char *jwkdir) ++{ ++ struct tang_keys_info *tki = new_tang_keys_info(jwkdir); ++ if (!tki) { ++ fprintf(stderr, "Unable to create tang_keys_info\n"); ++ return NULL; ++ } ++ ++ struct file_list *fl __attribute__ ((__cleanup__(cleanup_file_list))) = list_files(jwkdir, ".jwk", 1 /* ignore hidden */); ++ if (!fl || fl->m_size == 0) { ++ fprintf(stderr, "No JWK keys available.\n"); ++ free_tang_keys_info(tki); ++ return NULL; ++ } ++ ++ char filepath[PATH_MAX] = {}; ++ for (size_t i = 0, size = fl->m_size; i < size; i++) { ++ snprintf(filepath, sizeof(filepath), "%s/%s", jwkdir, fl->m_files[i]); ++ filepath[sizeof(filepath) - 1] = '\0'; ++ if (valid_for_signing_and_verifying(filepath)) { ++ if (!file_list_add(tki->m_sign_keys, filepath)) { ++ fprintf(stderr, "Error adding %s to the list of signing keys\n", filepath); ++ free_tang_keys_info(tki); ++ return NULL; ++ } ++ if (!file_list_add(tki->m_payload_keys, filepath)) { ++ fprintf(stderr, "Error adding %s to the list of payload keys\n", filepath); ++ free_tang_keys_info(tki); ++ return NULL; ++ } ++ } else if (valid_for_deriving_keys(filepath)) { ++ if (!file_list_add(tki->m_payload_keys, filepath)) { ++ fprintf(stderr, "Error adding %s to the list of payload keys\n", filepath); ++ free_tang_keys_info(tki); ++ return NULL; ++ } ++ } ++ } ++ ++ if (!prepare_deriving_and_adv(tki)) { ++ fprintf(stderr, "Unable to prepare deriving and advcertising JWKs.\n"); ++ free_tang_keys_info(tki); ++ return NULL; ++ } ++ ++ if (!prepare_default_adv(tki)) { ++ fprintf(stderr, "Unable to prepare the default adv.\n"); ++ free_tang_keys_info(tki); ++ return NULL; ++ } ++ ++ return tki; ++} ++ ++int ++process_payload(const json_t *jwk, jose_io_t *io) ++{ ++ char *payload __attribute__((__cleanup__(cleanup_buffer))) = json_dumps(jwk, JSON_SORT_KEYS | JSON_COMPACT); ++ if (!payload) { ++ fprintf(stderr, "Error converting JSON to char*.\n"); ++ return 0; ++ } ++ ++ for (size_t i = 0, size = strlen(payload); i < size; i++) { ++ uint8_t b = payload[i]; ++ if (!io->feed(io, &b, sizeof(b))) { ++ fprintf(stderr, "Error calling io-feed with b = [%c].\n", b); ++ return 0; ++ } ++ } ++ ++ if (!io->done(io)) { ++ fprintf(stderr, "Error calling io-done.\n"); ++ return 0; ++ } ++ return 1; ++} ++ ++char* ++process_adv(const json_t *keys, json_t* sigs, const json_t *pub) ++{ ++ /* For the IO data, we need to have an own buffer. */ ++ uint8_t buffer[TANG_MAXBUFLEN] = {}; ++ size_t buflen = sizeof(buffer); ++ json_auto_t *io_data = json_object(); ++ ++ jose_io_auto_t *io = jose_jws_sig_io(NULL, io_data, sigs, keys); ++ if (!io) { ++ fprintf(stderr, "jose_jws_sig_io() failed.\n"); ++ return NULL; ++ } ++ ++ io = tang_prep_io(io, buffer, &buflen); ++ if (!io) { ++ fprintf(stderr, "tang_prep_io() failed.\n"); ++ return NULL; ++ } ++ ++ if (!process_payload(pub, io)) { ++ fprintf(stderr, "Error processing payload.\n"); ++ return NULL; ++ } ++ ++ const char *preamble = "{\"payload\":\""; ++ const char *separator = "\","; ++ const char *postamble = "}"; ++ char *data __attribute__ ((__cleanup__(cleanup_buffer))) = json_dumps(io_data, JSON_EMBED | JSON_COMPACT | JSON_SORT_KEYS); ++ if (!data) { ++ fprintf(stderr, "Error obtaining signing data.\n"); ++ return NULL; ++ } ++ ++ size_t adv_len = strlen(preamble) + buflen + strlen(separator) + strlen(data) + strlen(postamble) + 1; ++ char *adv_data = malloc(adv_len); ++ snprintf(adv_data, adv_len, "%s%s%s%s%s", preamble, buffer, separator, data, postamble); ++ adv_data[adv_len - 1] = '\0'; ++ return adv_data; ++} ++ ++int ++prepare_default_adv(struct tang_keys_info *tki) ++{ ++ if (!tki) { ++ fprintf(stderr, "Invalid tang_keys_info\n"); ++ return 0; ++ } ++ ++ if (!tki->m_sign_keys || tki->m_sign_keys->m_size == 0) { ++ fprintf(stderr, "No valid signing keys.\n"); ++ return 0; ++ } ++ ++ if (!tki->m_payload_keys || tki->m_payload_keys->m_size <= tki->m_sign_keys->m_size) { ++ fprintf(stderr, "Invalid payload keys.\n"); ++ return 0; ++ } ++ ++ json_auto_t *keys = build_json_array((const char**)tki->m_sign_keys->m_files, tki->m_sign_keys->m_size); ++ json_auto_t *sigs = prepare_template_sigs(tki->m_sign_keys->m_size); ++ json_auto_t *pub = remove_private_keys(tki->m_payload_keys); ++ ++ char *adv_str __attribute__((cleanup(cleanup_buffer))) = process_adv(keys, sigs, pub); ++ if (!adv_str) { ++ return 0; ++ } ++ json_auto_t *json = json_loads(adv_str, 0, NULL); ++ if (!json) { ++ return 0; ++ } ++ ++ struct tang_jwk *jwk = new_tang_jwk_from_args(json, NULL, NULL); ++ if (!jwk) { ++ return 0; ++ } ++ tki->m_default_adv = jwk; ++ return 1; ++} +diff --git a/src/keys.h b/src/keys.h +new file mode 100644 +index 0000000..150b881 +--- /dev/null ++++ b/src/keys.h +@@ -0,0 +1,84 @@ ++/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */ ++/* ++ * Copyright (c) 2019 Red Hat, Inc. ++ * Author: Sergio Correia ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * 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, see . ++ */ ++ ++#pragma once ++ ++#include ++#include ++ ++struct tang_jwk { ++ json_t* m_json; ++ char* m_from_file; ++ char* m_str; ++ char* m_thp; ++ char* m_alg; ++}; ++ ++struct tang_jwk_list { ++ struct tang_jwk **m_jwk; ++ size_t m_size; ++}; ++ ++struct tang_keys_info { ++ char *m_jwkdir; ++ struct file_list *m_payload_keys; ++ struct file_list *m_sign_keys; ++ ++ struct tang_jwk_list *m_derive; ++ struct tang_jwk_list *m_adv; ++ struct tang_jwk *m_default_adv; ++}; ++ ++/* struct tang_jwk. */ ++struct tang_jwk *new_tang_jwk(const char* /* file */, const char* /* alg */); ++struct tang_jwk* new_tang_jwk_from_args(json_t*, const char* /* thp */, const char* /* alg */); ++struct tang_jwk* tang_jwk_dup(const struct tang_jwk*); ++struct tang_jwk *generate_new_tang_jwk(const char* /* alg */); ++void free_tang_jwk(struct tang_jwk*); ++void cleanup_tang_jwk(struct tang_jwk **jwk); ++ ++/* struct tang_jwk_list. */ ++struct tang_jwk_list* new_tang_jwk_list(void); ++void free_tang_jwk_list(struct tang_jwk_list*); ++int tang_jwk_list_add(struct tang_jwk_list*, struct tang_jwk*); ++struct tang_jwk* tang_jwk_list_find_thp(const struct tang_jwk_list*, const char*); ++ ++char *jwk_thumbprint(const json_t* /* jwk */, const char* /* alg */); ++char *jwk_thumbprint_from_file(const char* /* file */, const char* /* alg */); ++int valid_for_signing(const char* /* file */); ++int valid_for_signing_and_verifying(const char* /* file */); ++int valid_for_deriving_keys(const char* /* file */); ++ ++struct tang_keys_info* new_tang_keys_info(const char*); ++void free_tang_keys_info(struct tang_keys_info*); ++void cleanup_tang_keys_info(struct tang_keys_info**); ++ ++ ++struct tang_keys_info* read_keys(const char*); ++ ++int process_payload(const json_t*, jose_io_t*); ++char* process_adv(const json_t*, json_t*, const json_t*); ++int prepare_default_adv(struct tang_keys_info*); ++ ++size_t hash_alg_size(void); ++int check_keys(const char*); ++struct tang_jwk* find_adv(const struct tang_keys_info*, const char*); ++struct tang_jwk* find_deriving_key(const struct tang_keys_info*, const char*); ++int is_hash(const char*); ++ +diff --git a/src/tangd-update b/src/tangd-update +deleted file mode 100755 +index 652dbef..0000000 +--- a/src/tangd-update ++++ /dev/null +@@ -1,83 +0,0 @@ +-#!/bin/bash +-# vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: +-# +-# Copyright (c) 2016 Red Hat, Inc. +-# Author: Nathaniel McCallum +-# +-# This program is free software: you can redistribute it and/or modify +-# it under the terms of the GNU General Public License as published by +-# the Free Software Foundation, either version 3 of the License, or +-# (at your option) any later version. +-# +-# 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, see . +-# +- +-TMP='{"protected":{"cty":"jwk-set+json"}}' +- +-trap 'exit' ERR +- +-shopt -s nullglob +- +-HASHES=`jose alg -k hash` +- +-if [ $# -ne 2 ] || [ ! -d "$1" ]; then +- echo "Usage: $0 " >&2 +- exit 1 +-fi +- +-[ ! -d "$2" ] && mkdir -p -m 0700 "$2" +- +-src=`realpath "$1"` +-dst=`realpath "$2"` +- +-payl=() +-sign=() +- +-for jwk in $src/*.jwk; do +- if jose jwk use -i "$jwk" -r -u sign -u verify; then +- sign+=("-s" "$TMP" "-k" "$jwk") +- payl+=("-i" "$jwk") +- elif jose jwk use -i "$jwk" -r -u deriveKey; then +- payl+=("-i" "$jwk") +- else +- echo "Skipping invalid key: $jwk" >&2 +- fi +-done +- +-if [ ${#sign[@]} -gt 0 ]; then +- jose jwk pub -s "${payl[@]}" \ +- | jose jws sig -I- "${sign[@]}" -o "$dst/.default.jws" +- mv -f "$dst/.default.jws" "$dst/default.jws" +- new=default.jws +-fi +- +-shopt -s dotglob +- +-for jwk in $src/*.jwk; do +- for hsh in $HASHES; do +- thp=`jose jwk thp -i "$jwk" -a $hsh` +- +- if jose jwk use -i "$jwk" -r -u deriveKey; then +- ln -sf "$jwk" "$dst/.$thp.jwk" +- mv -f "$dst/.$thp.jwk" "$dst/$thp.jwk" +- new="$new\n$thp.jwk" +- elif jose jwk use -i "$jwk" -r -u sign; then +- keys=("${sign[@]}" -s "$TMP" -k "$jwk") +- jose jwk pub -s "${payl[@]}" \ +- | jose jws sig -I- "${keys[@]}" -o "$dst/.$thp.jws" +- mv -f "$dst/.$thp.jws" "$dst/$thp.jws" +- new="$new\n$thp.jws" +- fi +- done +-done +- +-for f in "$dst"/*; do +- b=`basename "$f"` +- echo -e "$new" | grep -q "^$b\$" || rm -f "$f" +-done +diff --git a/src/tangd.c b/src/tangd.c +index dc45a90..b569f38 100644 +--- a/src/tangd.c ++++ b/src/tangd.c +@@ -28,6 +28,7 @@ + #include + + #include ++#include "keys.h" + + static void + str_cleanup(char **str) +@@ -50,9 +51,8 @@ adv(enum http_method method, const char *path, const char *body, + __attribute__((cleanup(FILE_cleanup))) FILE *file = NULL; + __attribute__((cleanup(str_cleanup))) char *adv = NULL; + __attribute__((cleanup(str_cleanup))) char *thp = NULL; +- char filename[PATH_MAX] = {}; +- const char *cachedir = misc; +- struct stat st = {}; ++ __attribute__((cleanup(cleanup_tang_keys_info))) struct tang_keys_info *tki = NULL; ++ const char *jwkdir = misc; + + if (matches[1].rm_so < matches[1].rm_eo) { + size_t size = matches[1].rm_eo - matches[1].rm_so; +@@ -61,23 +61,25 @@ adv(enum http_method method, const char *path, const char *body, + return http_reply(HTTP_STATUS_INTERNAL_SERVER_ERROR, NULL); + } + +- if (snprintf(filename, sizeof(filename), +- "%s/%s.jws", cachedir, thp ? thp : "default") < 0) +- return http_reply(HTTP_STATUS_INTERNAL_SERVER_ERROR, NULL); +- +- file = fopen(filename, "r"); +- if (!file) +- return http_reply(HTTP_STATUS_NOT_FOUND, NULL); +- +- if (fstat(fileno(file), &st) != 0) ++ if (!check_keys(jwkdir)) { + return http_reply(HTTP_STATUS_INTERNAL_SERVER_ERROR, NULL); ++ } + +- adv = calloc(st.st_size + 1, 1); +- if (!adv) ++ tki = read_keys(jwkdir); ++ if (!tki) { + return http_reply(HTTP_STATUS_INTERNAL_SERVER_ERROR, NULL); ++ } + +- if (fread(adv, st.st_size, 1, file) != 1) +- return http_reply(HTTP_STATUS_INTERNAL_SERVER_ERROR, NULL); ++ if (thp) { ++ const struct tang_jwk *jwk = find_adv(tki, thp); ++ if (!jwk) { ++ return http_reply(HTTP_STATUS_NOT_FOUND, NULL); ++ } ++ adv = strdup(jwk->m_str); ++ } else { ++ /* Default adv. */ ++ adv = strdup(tki->m_default_adv->m_str); ++ } + + return http_reply(HTTP_STATUS_OK, + "Content-Type: application/jose+json\r\n" +@@ -91,10 +93,11 @@ rec(enum http_method method, const char *path, const char *body, + { + __attribute__((cleanup(str_cleanup))) char *enc = NULL; + __attribute__((cleanup(str_cleanup))) char *thp = NULL; ++ __attribute__((cleanup(cleanup_tang_keys_info))) struct tang_keys_info *tki = NULL; ++ const struct tang_jwk *jwk = NULL; ++ + size_t size = matches[1].rm_eo - matches[1].rm_so; +- char filename[PATH_MAX] = {}; +- const char *cachedir = misc; +- json_auto_t *jwk = NULL; ++ const char *jwkdir = misc; + json_auto_t *req = NULL; + json_auto_t *rep = NULL; + const char *alg = NULL; +@@ -129,17 +132,24 @@ rec(enum http_method method, const char *path, const char *body, + if (!thp) + return http_reply(HTTP_STATUS_INTERNAL_SERVER_ERROR, NULL); + +- if (snprintf(filename, sizeof(filename), "%s/%s.jwk", cachedir, thp) < 0) ++ if (!check_keys(jwkdir)) { ++ return http_reply(HTTP_STATUS_INTERNAL_SERVER_ERROR, NULL); ++ } ++ ++ tki = read_keys(jwkdir); ++ if (!tki) { + return http_reply(HTTP_STATUS_INTERNAL_SERVER_ERROR, NULL); ++ } + +- jwk = json_load_file(filename, 0, NULL); +- if (!jwk) ++ jwk = find_deriving_key(tki, thp); ++ if (!jwk) { + return http_reply(HTTP_STATUS_NOT_FOUND, NULL); ++ } + +- if (!jose_jwk_prm(NULL, jwk, true, "deriveKey")) ++ if (!jose_jwk_prm(NULL, jwk->m_json, true, "deriveKey")) + return http_reply(HTTP_STATUS_FORBIDDEN, NULL); + +- if (json_unpack(jwk, "{s:s,s?s}", "d", &d, "alg", &alg) < 0) ++ if (json_unpack(jwk->m_json, "{s:s,s?s}", "d", &d, "alg", &alg) < 0) + return http_reply(HTTP_STATUS_FORBIDDEN, NULL); + + if (alg && strcmp(alg, "ECMR") != 0) +@@ -148,7 +158,7 @@ rec(enum http_method method, const char *path, const char *body, + /* + * Perform the exchange and return + */ +- rep = jose_jwk_exc(NULL, jwk, req); ++ rep = jose_jwk_exc(NULL, jwk->m_json, req); + if (!rep) + return http_reply(HTTP_STATUS_BAD_REQUEST, NULL); + +diff --git a/src/util.c b/src/util.c +new file mode 100644 +index 0000000..b8e3756 +--- /dev/null ++++ b/src/util.c +@@ -0,0 +1,141 @@ ++/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */ ++/* ++ * Copyright (c) 2019 Red Hat, Inc. ++ * Author: Sergio Correia ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * 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, see . ++ */ ++ ++ ++#include ++#include ++#include ++#include ++ ++#include "util.h" ++ ++struct file_list* ++new_file_list(void) ++{ ++ struct file_list *fl = malloc(sizeof(*fl)); ++ if (!fl) { ++ return NULL; ++ } ++ fl->m_files = NULL; ++ fl->m_size = 0; ++ ++ return fl; ++} ++ ++void ++free_file_list(struct file_list *fl) ++{ ++ if (!fl) { ++ return; ++ } ++ ++ for (size_t i = 0, size = fl->m_size; i < size; i++) { ++ free(fl->m_files[i]); ++ } ++ free(fl->m_files); ++ fl->m_size = 0; ++ free(fl); ++} ++ ++void ++cleanup_file_list(struct file_list **fl) ++{ ++ if (!fl || !*fl) { ++ return; ++ } ++ free_file_list(*fl); ++} ++ ++int ++file_list_add(struct file_list *fl, const char *filepath) ++{ ++ if (!fl || !filepath) { ++ return 0; ++ } ++ ++ char **new_files = realloc(fl->m_files, sizeof(char *) * (fl->m_size + 1)); ++ if (!new_files) { ++ fprintf(stderr, "Error reallocating memory for the new file\n"); ++ return 0; ++ } ++ ++ fl->m_files = new_files; ++ fl->m_files[fl->m_size++] = strdup(filepath); ++ return 1; ++} ++ ++int ++match_file(const char *file, const char *pattern) ++{ ++ if (!file) { ++ return 0; ++ } ++ ++ if (!pattern) { ++ return 1; ++ } ++ ++ return strstr(file, pattern) != NULL; ++} ++ ++int ++list_files_cmp_func(const void *a, const void *b) ++{ ++ const char *sa = *(const char**)a; ++ const char *sb = *(const char**)b; ++ return strcmp(sa, sb); ++} ++ ++struct file_list* ++list_files(const char *path, const char *pattern, int ignore_hidden) ++{ ++ struct file_list *fl = new_file_list(); ++ struct dirent *d; ++ DIR *dir = opendir(path); ++ ++ if (dir == NULL) { ++ return fl; ++ } ++ ++ while ((d = readdir(dir)) != NULL) { ++ if (strcmp(d->d_name, ".") == 0 || strcmp(d->d_name, "..") == 0) { ++ continue; ++ } ++ ++ if (ignore_hidden && d->d_name[0] == '.') { ++ continue; ++ } ++ ++ if (match_file(d->d_name, pattern)) { ++ if (!file_list_add(fl, d->d_name)) { ++ fprintf(stderr, "Unable to add file %s to file list.\n", d->d_name); ++ continue; ++ } ++ } ++ } ++ ++ if (fl->m_size > 1) { ++ qsort(fl->m_files, fl->m_size, sizeof(char*), list_files_cmp_func); ++ } ++ ++ closedir(dir); ++ return fl; ++} ++ ++ +diff --git a/src/util.h b/src/util.h +new file mode 100644 +index 0000000..c3af014 +--- /dev/null ++++ b/src/util.h +@@ -0,0 +1,33 @@ ++/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */ ++/* ++ * Copyright (c) 2019 Red Hat, Inc. ++ * Author: Sergio Correia ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * 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, see . ++ */ ++ ++#pragma once ++ ++struct file_list { ++ char **m_files; ++ size_t m_size; ++}; ++ ++struct file_list* new_file_list(void); ++void free_file_list(struct file_list*); ++void cleanup_file_list(struct file_list**); ++int file_list_add(struct file_list* /* fl */, const char* /* filepath */); ++int match_file(const char* /* file */, const char* /* pattern */); ++int list_files_cmp_func(const void*, const void*); ++struct file_list* list_files(const char* /* path */, const char* /* match */, int /* ignore_hidden */); +diff --git a/tests/adv b/tests/adv +index 357d097..986e632 100755 +--- a/tests/adv ++++ b/tests/adv +@@ -36,15 +36,13 @@ trap 'exit' ERR + + export TMP=`mktemp -d` + mkdir -p $TMP/db +-mkdir -p $TMP/cache + + tangd-keygen $TMP/db sig exc + jose jwk gen -i '{"alg": "ES512"}' -o $TMP/db/.sig.jwk + jose jwk gen -i '{"alg": "ES512"}' -o $TMP/db/.oth.jwk +-tangd-update $TMP/db $TMP/cache + + export PORT=`shuf -i 1024-65536 -n 1` +-$SD_ACTIVATE -l "127.0.0.1:$PORT" -a $VALGRIND tangd $TMP/cache & ++$SD_ACTIVATE -l "127.0.0.1:$PORT" -a $VALGRIND tangd $TMP/db & + export PID=$! + sleep 0.5 + +diff --git a/tests/rec b/tests/rec +index d1dfe97..3316565 100755 +--- a/tests/rec ++++ b/tests/rec +@@ -28,11 +28,9 @@ trap 'exit' ERR + + export TMP=`mktemp -d` + mkdir -p $TMP/db +-mkdir -p $TMP/cache + + # Generate the server keys + tangd-keygen $TMP/db sig exc +-tangd-update $TMP/db $TMP/cache + + # Generate the client keys + exc_kid=`jose jwk thp -i $TMP/db/exc.jwk` +@@ -42,7 +40,7 @@ jose jwk pub -i $TMP/exc.jwk -o $TMP/exc.pub.jwk + + # Start the server + port=`shuf -i 1024-65536 -n 1` +-$SD_ACTIVATE -l 127.0.0.1:$port -a $VALGRIND tangd $TMP/cache & ++$SD_ACTIVATE -l 127.0.0.1:$port -a $VALGRIND tangd $TMP/db & + export PID=$! + sleep 0.5 + +diff --git a/units/tangd-keygen.service.in b/units/tangd-keygen.service.in +deleted file mode 100644 +index 2f80cd8..0000000 +--- a/units/tangd-keygen.service.in ++++ /dev/null +@@ -1,8 +0,0 @@ +-[Unit] +-Description=Tang Server key generation script +-ConditionDirectoryNotEmpty=|!@jwkdir@ +-Requires=tangd-update.path +- +-[Service] +-Type=oneshot +-ExecStart=@libexecdir@/tangd-keygen @jwkdir@ +diff --git a/units/tangd-update.path.in b/units/tangd-update.path.in +deleted file mode 100644 +index ee9005d..0000000 +--- a/units/tangd-update.path.in ++++ /dev/null +@@ -1,4 +0,0 @@ +-[Path] +-PathChanged=@jwkdir@ +-MakeDirectory=true +-DirectoryMode=0700 +diff --git a/units/tangd-update.service.in b/units/tangd-update.service.in +deleted file mode 100644 +index 11a4cb2..0000000 +--- a/units/tangd-update.service.in ++++ /dev/null +@@ -1,6 +0,0 @@ +-[Unit] +-Description=Tang Server key update script +- +-[Service] +-Type=oneshot +-ExecStart=@libexecdir@/tangd-update @jwkdir@ @cachedir@ +diff --git a/units/tangd.socket.in b/units/tangd.socket.in +index 22474ea..0a3e239 100644 +--- a/units/tangd.socket.in ++++ b/units/tangd.socket.in +@@ -1,10 +1,5 @@ + [Unit] + Description=Tang Server socket +-Requires=tangd-keygen.service +-Requires=tangd-update.service +-Requires=tangd-update.path +-After=tangd-keygen.service +-After=tangd-update.service + + [Socket] + ListenStream=80 +diff --git a/units/tangd@.service.in b/units/tangd@.service.in +index c4b9597..f1db261 100644 +--- a/units/tangd@.service.in ++++ b/units/tangd@.service.in +@@ -5,4 +5,4 @@ Description=Tang Server + StandardInput=socket + StandardOutput=socket + StandardError=journal +-ExecStart=@libexecdir@/tangd @cachedir@ ++ExecStart=@libexecdir@/tangd @jwkdir@ +-- +2.18.1 + diff --git a/0002-Exit-with-success-unless-the-issue-was-with-with-tan.patch b/0002-Exit-with-success-unless-the-issue-was-with-with-tan.patch new file mode 100644 index 0000000..f8c27a3 --- /dev/null +++ b/0002-Exit-with-success-unless-the-issue-was-with-with-tan.patch @@ -0,0 +1,38 @@ +From ea43ca02cf52d0455c6949683692a95e38ccdf70 Mon Sep 17 00:00:00 2001 +From: Sergio Correia +Date: Fri, 4 Dec 2020 09:05:19 -0300 +Subject: [PATCH 2/2] Exit with success unless the issue was with with tangd + itself + +When an HTTP parser error happens, tangd is currently exiting with an +error status, which may cause trouble in some scenarios [1]. + +However, we don't exit with an error in situations where we try requests +that do not exist, for instance. It makes sense to only exit with an +error when the error was with tangd itself, e.g.: when we are unable to +read the directory with the keys, not when the actual HTTP operation +does not succeed for some reason. + +Upstream: https://github.com/latchset/tang/pull/55 + +[1] https://bugzilla.redhat.com/show_bug.cgi?id=1828558 +--- + src/tangd.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/tangd.c b/src/tangd.c +index b569f38..d40201f 100644 +--- a/src/tangd.c ++++ b/src/tangd.c +@@ -225,7 +225,7 @@ main(int argc, char *argv[]) + if (parser.http_errno != 0) { + fprintf(stderr, "HTTP Parsing Error: %s\n", + http_errno_description(parser.http_errno)); +- return EXIT_FAILURE; ++ return EXIT_SUCCESS; + } + + memmove(req, &req[r], rcvd - r); +-- +2.27.0 + diff --git a/EMPTY b/EMPTY deleted file mode 100644 index 0519ecb..0000000 --- a/EMPTY +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/sources b/sources new file mode 100644 index 0000000..b79c37c --- /dev/null +++ b/sources @@ -0,0 +1 @@ +SHA512 (tang-7.tar.bz2) = dd4187fa6962523ac2a745ef05a27cdf7e5264c595942e63d406d67a1c595e87bd7e74091cb2e16362f369480cdf8e3b4bfe1fee13b9cb73d5308f42c52f138a diff --git a/tang.spec b/tang.spec new file mode 100644 index 0000000..db2af33 --- /dev/null +++ b/tang.spec @@ -0,0 +1,152 @@ +Name: tang +Version: 7 +Release: 6%{?dist} +Summary: Network Presence Binding Daemon + +License: GPLv3+ +URL: https://github.com/latchset/%{name} +Source0: https://github.com/latchset/%{name}/releases/download/v%{version}/%{name}-%{version}.tar.bz2 +Patch1: 0001-Move-key-generation-to-tang.patch +Patch2: 0002-Exit-with-success-unless-the-issue-was-with-with-tan.patch + +BuildRequires: gcc +BuildRequires: autoconf +BuildRequires: automake +BuildRequires: jose >= 8 +BuildRequires: libjose-devel >= 8 +BuildRequires: libjose-zlib-devel >= 8 +BuildRequires: libjose-openssl-devel >= 8 + +BuildRequires: http-parser-devel >= 2.7.1-3 +BuildRequires: systemd-devel +BuildRequires: pkgconfig + +BuildRequires: systemd +BuildRequires: curl + +BuildRequires: asciidoc +BuildRequires: coreutils +BuildRequires: grep +BuildRequires: sed +BuildRequires: git-core + +%{?systemd_requires} +Requires: coreutils +Requires: jose >= 8 +Requires: grep +Requires: sed + +Requires(pre): shadow-utils + +%description +Tang is a small daemon for binding data to the presence of a third party. + +%prep +%autosetup -S git + +%build +autoreconf -i +%configure +make %{?_smp_mflags} V=1 + +%install +rm -rf $RPM_BUILD_ROOT +%make_install +echo "User=%{name}" >> $RPM_BUILD_ROOT/%{_unitdir}/%{name}d@.service +%{__mkdir_p} $RPM_BUILD_ROOT/%{_localstatedir}/db/%{name} + +%check +if ! make %{?_smp_mflags} check; then + cat test-suite.log + false +fi + +%pre +getent group %{name} >/dev/null || groupadd -r %{name} +getent passwd %{name} >/dev/null || \ + useradd -r -g %{name} -d %{_localstatedir}/cache/%{name} -s /sbin/nologin \ + -c "Tang Network Presence Daemon user" %{name} +exit 0 + +%post +%systemd_post %{name}d.socket + +%preun +%systemd_preun %{name}d.socket + +%postun +%systemd_postun_with_restart %{name}d.socket + +%files +%license COPYING +%attr(0700, %{name}, %{name}) %{_localstatedir}/db/%{name} +%{_unitdir}/%{name}d@.service +%{_unitdir}/%{name}d.socket +%{_libexecdir}/%{name}d-keygen +%{_libexecdir}/%{name}d +%{_mandir}/man8/tang.8* +%{_bindir}/%{name}-show-keys +%{_mandir}/man1/tang-show-keys.1* + +%changelog +* Wed Jan 13 2021 Sergio Correia - 7-6 +- Exit with success unless the issue was with with tangd itself + Resolves: rhbz#1828558 + +* Sun Dec 01 2019 Sergio Correia - 7-5 +- Permissions of /var/db/tang set to 0700 +- Home dir of user tang is /var/cache/tang + +* Fri Nov 29 2019 Sergio Correia - 7-4 +- Fix permissions of /var/db/tang + +* Tue Oct 15 2019 Sergio Correia - 7-3 +- Rebuild to ensure correct dist tag + +* Sun Sep 29 2019 Sergio Correia - 7-2 +- Move key generation to tang +- Resolves rhbz#1745177, rhbz#1679186 + +* Fri Aug 10 2018 Nathaniel McCallum - 7-1 +- New upstream release +- Retire tang-nagios package (now separate upstream) + +* Sat Jul 14 2018 Fedora Release Engineering - 6-5 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_29_Mass_Rebuild + +* Fri Feb 09 2018 Fedora Release Engineering - 6-4 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_28_Mass_Rebuild + +* Thu Aug 03 2017 Fedora Release Engineering - 6-3 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Binutils_Mass_Rebuild + +* Thu Jul 27 2017 Fedora Release Engineering - 6-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Mass_Rebuild + +* Wed Jun 14 2017 Nathaniel McCallum - 6-1 +- New upstream release + +* Wed Jun 14 2017 Nathaniel McCallum - 5-2 +- Fix incorrect dependencies + +* Wed Jun 14 2017 Nathaniel McCallum - 5-1 +- New upstream release + +* Sat Feb 11 2017 Fedora Release Engineering - 4-3 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_26_Mass_Rebuild + +* Mon Nov 14 2016 Nathaniel McCallum - 4-2 +- Fix a race condition in one of the tests + +* Thu Nov 10 2016 Nathaniel McCallum - 4-1 +- New upstream release +- Add nagios subpackage + +* Wed Oct 26 2016 Nathaniel McCallum - 3-1 +- New upstream release + +* Wed Oct 19 2016 Nathaniel McCallum - 2-1 +- New upstream release + +* Tue Aug 23 2016 Nathaniel McCallum - 1-1 +- First release