tang/SOURCES/0001-Move-key-generation-to...

1744 lines
50 KiB
Diff

From a0aa280cfe7a756bc9f965129c847dc9bfd2e84d Mon Sep 17 00:00:00 2001
From: Sergio Correia <scorreia@redhat.com>
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 <scorreia@redhat.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <jose/b64.h>
+#include <jose/jwk.h>
+#include <jose/jws.h>
+#include <jose/io.h>
+#include <jansson.h>
+#include <string.h>
+
+#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 <scorreia@redhat.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <jansson.h>
+#include <jose/io.h>
+
+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 <npmccallum@redhat.com>
-#
-# 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 <http://www.gnu.org/licenses/>.
-#
-
-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 <jwkdir> <cachedir>" >&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 <unistd.h>
#include <jose/jose.h>
+#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 <scorreia@redhat.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+
+#include <dirent.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#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 <scorreia@redhat.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#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