1457 строки
45 KiB
Diff
1457 строки
45 KiB
Diff
From 7961ab6175829c6a71796f34cbe0b8d247245ca9 Mon Sep 17 00:00:00 2001
|
|
From: rpm-build <rpm-build>
|
|
Date: Tue, 1 Dec 2020 15:30:19 -0300
|
|
Subject: [PATCH 2/2] Move key handling to tang itself
|
|
|
|
Upstream commits:
|
|
- https://github.com/latchset/tang/commit/6090505
|
|
- https://github.com/latchset/tang/commit/c71df1d
|
|
- https://github.com/latchset/tang/commit/7119454
|
|
---
|
|
meson.build | 2 -
|
|
src/keys.c | 455 ++++++++++++++++++++
|
|
src/keys.h | 45 ++
|
|
src/meson.build | 3 +-
|
|
src/tangd-update | 83 ----
|
|
src/tangd.c | 52 +--
|
|
tests/adv | 6 +-
|
|
tests/keys/-bWkGaJi0Zdvxaj4DCp28umLcRA.jwk | 1 +
|
|
tests/keys/.r4E2wG1u_YyKUo0N0rIK7jJF5Xg.jwk | 1 +
|
|
tests/keys/.uZ0s8YTXcGcuWduWWBSiR2OjOVg.jwk | 1 +
|
|
tests/keys/another-bad-file | 1 +
|
|
tests/keys/empty.jwk | 0
|
|
tests/keys/invalid.jwk | 1 +
|
|
tests/keys/qgmqJSo6AEEuVQY7zVlklqdTMqY.jwk | 1 +
|
|
tests/meson.build | 38 +-
|
|
tests/rec | 4 +-
|
|
tests/test-keys.c.in | 257 +++++++++++
|
|
tests/test-util.c | 75 ++++
|
|
tests/test-util.h | 46 ++
|
|
units/meson.build | 21 -
|
|
units/tangd-keygen.service.in | 8 -
|
|
units/tangd-update.path.in | 4 -
|
|
units/tangd-update.service.in | 6 -
|
|
units/tangd.socket | 5 -
|
|
units/tangd@.service.in | 2 +-
|
|
25 files changed, 939 insertions(+), 179 deletions(-)
|
|
create mode 100644 src/keys.c
|
|
create mode 100644 src/keys.h
|
|
delete mode 100755 src/tangd-update
|
|
create mode 100644 tests/keys/-bWkGaJi0Zdvxaj4DCp28umLcRA.jwk
|
|
create mode 100644 tests/keys/.r4E2wG1u_YyKUo0N0rIK7jJF5Xg.jwk
|
|
create mode 100644 tests/keys/.uZ0s8YTXcGcuWduWWBSiR2OjOVg.jwk
|
|
create mode 100644 tests/keys/another-bad-file
|
|
create mode 100644 tests/keys/empty.jwk
|
|
create mode 100644 tests/keys/invalid.jwk
|
|
create mode 100644 tests/keys/qgmqJSo6AEEuVQY7zVlklqdTMqY.jwk
|
|
create mode 100644 tests/test-keys.c.in
|
|
create mode 100644 tests/test-util.c
|
|
create mode 100644 tests/test-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/meson.build b/meson.build
|
|
index d91f485..0343fa7 100644
|
|
--- a/meson.build
|
|
+++ b/meson.build
|
|
@@ -16,14 +16,12 @@ sysconfdir = join_paths(get_option('prefix'), get_option('sysconfdir'))
|
|
bindir = join_paths(get_option('prefix'), get_option('bindir'))
|
|
systemunitdir = join_paths(get_option('prefix'), 'lib/systemd/system')
|
|
licensedir = join_paths(get_option('prefix'), 'share', 'licenses', meson.project_name())
|
|
-cachedir = join_paths(get_option('localstatedir'), 'cache', meson.project_name())
|
|
jwkdir = join_paths(get_option('localstatedir'), 'db', meson.project_name())
|
|
|
|
data = configuration_data()
|
|
data.set('libexecdir', libexecdir)
|
|
data.set('sysconfdir', sysconfdir)
|
|
data.set('systemunitdir', systemunitdir)
|
|
-data.set('cachedir', cachedir)
|
|
data.set('jwkdir', jwkdir)
|
|
|
|
add_project_arguments(
|
|
diff --git a/src/keys.c b/src/keys.c
|
|
new file mode 100644
|
|
index 0000000..e79be8d
|
|
--- /dev/null
|
|
+++ b/src/keys.c
|
|
@@ -0,0 +1,455 @@
|
|
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
|
|
+/*
|
|
+ * Copyright (c) 2020 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 <stdlib.h>
|
|
+#include <string.h>
|
|
+#include <dirent.h>
|
|
+#include <stdio.h>
|
|
+
|
|
+#include <jose/b64.h>
|
|
+#include <jose/jwk.h>
|
|
+#include <jose/jws.h>
|
|
+
|
|
+#include "keys.h"
|
|
+
|
|
+#ifndef PATH_MAX
|
|
+#define PATH_MAX 4096
|
|
+#endif
|
|
+
|
|
+static const char**
|
|
+supported_hashes(void)
|
|
+{
|
|
+ /* TODO: check if jose has a way to export the hash algorithms it
|
|
+ * supports. */
|
|
+ static const char* hashes[] = {"S1", "S224", "S256", "S384", "S512", NULL};
|
|
+ return hashes;
|
|
+}
|
|
+
|
|
+static int
|
|
+is_hash(const char* alg)
|
|
+{
|
|
+ if (!alg) {
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ const char** algs = supported_hashes();
|
|
+ for (size_t a = 0; algs[a]; a++) {
|
|
+ if (strcmp(alg, algs[a]) == 0) {
|
|
+ return 1;
|
|
+ }
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static json_t*
|
|
+jwk_generate(const char* alg)
|
|
+{
|
|
+ json_auto_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);
|
|
+ return NULL;
|
|
+ }
|
|
+
|
|
+ return json_incref(jalg);
|
|
+}
|
|
+
|
|
+static char*
|
|
+jwk_thumbprint(const json_t* jwk, const char* alg)
|
|
+{
|
|
+ size_t elen = 0;
|
|
+ size_t dlen = 0;
|
|
+
|
|
+ if (!jwk) {
|
|
+ fprintf(stderr, "Invalid JWK\n");
|
|
+ return NULL;
|
|
+ }
|
|
+
|
|
+ if (!alg || !is_hash(alg)) {
|
|
+ fprintf(stderr, "Invalid hash algorithm (%s)\n", alg);
|
|
+ return NULL;
|
|
+ }
|
|
+
|
|
+ dlen = jose_jwk_thp_buf(NULL, NULL, alg, NULL, 0);
|
|
+ if (dlen == SIZE_MAX) {
|
|
+ fprintf(stderr, "Error determining hash size for %s\n", alg);
|
|
+ return NULL;
|
|
+ }
|
|
+
|
|
+ elen = jose_b64_enc_buf(NULL, dlen, NULL, 0);
|
|
+ if (elen == SIZE_MAX) {
|
|
+ fprintf(stderr, "Error determining encoded size for %s\n", alg);
|
|
+ return NULL;
|
|
+ }
|
|
+
|
|
+ uint8_t dec[dlen];
|
|
+ char enc[elen];
|
|
+
|
|
+ if (!jose_jwk_thp_buf(NULL, jwk, alg, 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;
|
|
+ }
|
|
+
|
|
+ return strndup(enc, elen);
|
|
+}
|
|
+
|
|
+void
|
|
+free_tang_keys_info(struct tang_keys_info* tki)
|
|
+{
|
|
+ if (!tki) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ json_t* to_free[] = {tki->m_keys, tki->m_rotated_keys,
|
|
+ tki->m_payload, tki->m_sign
|
|
+ };
|
|
+ size_t len = sizeof(to_free) / sizeof(to_free[0]);
|
|
+
|
|
+ for (size_t i = 0; i < len; i++) {
|
|
+ if (to_free[i] == NULL) {
|
|
+ continue;
|
|
+ }
|
|
+ json_decref(to_free[i]);
|
|
+ }
|
|
+ free(tki);
|
|
+}
|
|
+
|
|
+void
|
|
+cleanup_tang_keys_info(struct tang_keys_info** tki)
|
|
+{
|
|
+ if (!tki || !*tki) {
|
|
+ return;
|
|
+ }
|
|
+ free_tang_keys_info(*tki);
|
|
+ *tki = NULL;
|
|
+}
|
|
+
|
|
+static struct tang_keys_info*
|
|
+new_tang_keys_info(void)
|
|
+{
|
|
+ struct tang_keys_info* tki = calloc(1, sizeof(*tki));
|
|
+ if (!tki) {
|
|
+ return NULL;
|
|
+ }
|
|
+
|
|
+ tki->m_keys = json_array();
|
|
+ tki->m_rotated_keys = json_array();
|
|
+ tki->m_payload = json_array();
|
|
+ tki->m_sign = json_array();
|
|
+
|
|
+ if (!tki->m_keys || !tki->m_rotated_keys ||
|
|
+ !tki->m_payload || !tki->m_sign) {
|
|
+ free_tang_keys_info(tki);
|
|
+ return NULL;
|
|
+ }
|
|
+ tki->m_keys_count = 0;
|
|
+ return tki;
|
|
+}
|
|
+
|
|
+static int
|
|
+jwk_valid_for(const json_t* jwk, const char* use)
|
|
+{
|
|
+ if (!jwk || !use) {
|
|
+ return 0;
|
|
+ }
|
|
+ return jose_jwk_prm(NULL, jwk, false, use);
|
|
+}
|
|
+
|
|
+static int
|
|
+jwk_valid_for_signing_and_verifying(const json_t* jwk)
|
|
+{
|
|
+ const char* uses[] = {"sign", "verify", NULL};
|
|
+ int ret = 1;
|
|
+ for (int i = 0; uses[i]; i++) {
|
|
+ if (!jwk_valid_for(jwk, uses[i])) {
|
|
+ ret = 0;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int
|
|
+jwk_valid_for_signing(const json_t* jwk)
|
|
+{
|
|
+ return jwk_valid_for(jwk, "sign");
|
|
+}
|
|
+
|
|
+static int
|
|
+jwk_valid_for_deriving_keys(const json_t* jwk)
|
|
+{
|
|
+ return jwk_valid_for(jwk, "deriveKey");
|
|
+}
|
|
+
|
|
+static void
|
|
+cleanup_str(char** str)
|
|
+{
|
|
+ if (!str || !*str) {
|
|
+ return;
|
|
+ }
|
|
+ free(*str);
|
|
+ *str = NULL;
|
|
+}
|
|
+
|
|
+static json_t*
|
|
+jwk_sign(const json_t* to_sign, const json_t* sig_keys)
|
|
+{
|
|
+ if (!sig_keys || !json_is_array(sig_keys) || !json_is_array(to_sign)) {
|
|
+ return NULL;
|
|
+ }
|
|
+
|
|
+ json_auto_t* to_sign_copy = json_deep_copy(to_sign);
|
|
+ if (!jose_jwk_pub(NULL, to_sign_copy)) {
|
|
+ fprintf(stderr, "Error removing private material from data to sign\n");
|
|
+ }
|
|
+
|
|
+ json_auto_t* payload = json_pack("{s:O}", "keys", to_sign_copy);
|
|
+ json_auto_t* sig_template = json_pack("{s:{s:s}}",
|
|
+ "protected", "cty", "jwk-set+json");
|
|
+
|
|
+ /* Use the template with the signing keys. */
|
|
+ json_auto_t* sig_template_arr = json_array();
|
|
+ size_t arr_size = json_array_size(sig_keys);
|
|
+ for (size_t i = 0; i < arr_size; i++) {
|
|
+ if (json_array_append(sig_template_arr, sig_template) == -1) {
|
|
+ fprintf(stderr, "Unable to append sig template to array\n");
|
|
+ return NULL;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ __attribute__ ((__cleanup__(cleanup_str))) char* data_to_sign = json_dumps(payload, 0);
|
|
+ json_auto_t* jws = json_pack("{s:o}", "payload",
|
|
+ jose_b64_enc(data_to_sign, strlen(data_to_sign)));
|
|
+
|
|
+ if (!jose_jws_sig(NULL, jws, sig_template_arr, sig_keys)) {
|
|
+ fprintf(stderr, "Error trying to jose_jws_sign\n");
|
|
+ return NULL;
|
|
+ }
|
|
+ return json_incref(jws);
|
|
+}
|
|
+
|
|
+static json_t*
|
|
+find_by_thp(struct tang_keys_info* tki, const char* target)
|
|
+{
|
|
+ if (!tki) {
|
|
+ return NULL;
|
|
+ }
|
|
+
|
|
+ json_auto_t* keys = json_deep_copy(tki->m_keys);
|
|
+ json_array_extend(keys, tki->m_rotated_keys);
|
|
+
|
|
+ size_t idx;
|
|
+ json_t* jwk;
|
|
+ const char** hashes = supported_hashes();
|
|
+ json_array_foreach(keys, idx, jwk) {
|
|
+ for (int i = 0; hashes[i]; i++) {
|
|
+ __attribute__ ((__cleanup__(cleanup_str))) char* thumbprint = jwk_thumbprint(jwk, hashes[i]);
|
|
+ if (strcmp(thumbprint, target) != 0) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ if (jwk_valid_for_deriving_keys(jwk)) {
|
|
+ return json_incref(jwk);
|
|
+ } else if (jwk_valid_for_signing(jwk)) {
|
|
+ json_auto_t* sign = json_deep_copy(tki->m_sign);
|
|
+ if (json_array_append(sign, jwk) == -1) {
|
|
+ return NULL;
|
|
+ }
|
|
+ json_auto_t* jws = jwk_sign(tki->m_payload, sign);
|
|
+ if (!jws) {
|
|
+ return NULL;
|
|
+ }
|
|
+ return json_incref(jws);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+static int
|
|
+prepare_payload_and_sign(struct tang_keys_info* tki)
|
|
+{
|
|
+ if (!tki) {
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ size_t idx;
|
|
+ json_t* jwk;
|
|
+ json_array_foreach(tki->m_keys, idx, jwk) {
|
|
+ if (jwk_valid_for_signing_and_verifying(jwk)) {
|
|
+ if (json_array_append(tki->m_sign, jwk) == -1) {
|
|
+ continue;
|
|
+ }
|
|
+ if (json_array_append(tki->m_payload, jwk) == -1) {
|
|
+ continue;
|
|
+ }
|
|
+ } else if (jwk_valid_for_deriving_keys(jwk)) {
|
|
+ if (json_array_append(tki->m_payload, jwk) == -1) {
|
|
+ continue;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ if (json_array_size(tki->m_sign) == 0 || json_array_size(tki->m_payload) == 0) {
|
|
+ return 0;
|
|
+ }
|
|
+ return 1;
|
|
+}
|
|
+
|
|
+static int
|
|
+create_new_keys(const char* jwkdir)
|
|
+{
|
|
+ const char** hashes = supported_hashes();
|
|
+ const char* alg[] = {"ES512", "ECMR", NULL};
|
|
+ char path[PATH_MAX];
|
|
+ for (int i = 0; alg[i] != NULL; i++) {
|
|
+ json_auto_t* jwk = jwk_generate(alg[i]);
|
|
+ if (!jwk) {
|
|
+ return 0;
|
|
+ }
|
|
+ __attribute__ ((__cleanup__(cleanup_str))) char* thp = jwk_thumbprint(jwk, hashes[0]);
|
|
+ if (!thp) {
|
|
+ return 0;
|
|
+ }
|
|
+ if (snprintf(path, PATH_MAX, "%s/%s.jwk", jwkdir, thp) < 0) {
|
|
+ fprintf(stderr, "Unable to prepare variable with file full path (%s)\n", thp);
|
|
+ return 0;
|
|
+ }
|
|
+ path[sizeof(path) - 1] = '\0';
|
|
+ if (json_dump_file(jwk, path, 0) == -1) {
|
|
+ fprintf(stderr, "Error saving JWK to file (%s)\n", path);
|
|
+ return 0;
|
|
+ }
|
|
+ }
|
|
+ return 1;
|
|
+}
|
|
+
|
|
+static struct tang_keys_info*
|
|
+load_keys(const char* jwkdir)
|
|
+{
|
|
+ struct tang_keys_info* tki = new_tang_keys_info();
|
|
+ if (!tki) {
|
|
+ return NULL;
|
|
+ }
|
|
+
|
|
+ struct dirent* d;
|
|
+ DIR* dir = opendir(jwkdir);
|
|
+ if (!dir) {
|
|
+ free_tang_keys_info(tki);
|
|
+ return NULL;
|
|
+ }
|
|
+
|
|
+ char filepath[PATH_MAX];
|
|
+ const char* pattern = ".jwk";
|
|
+ while ((d = readdir(dir)) != NULL) {
|
|
+ if (strcmp(d->d_name, ".") == 0 || strcmp(d->d_name, "..") == 0) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ char* dot = strrchr(d->d_name, '.');
|
|
+ if (!dot) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ if (strcmp(dot, pattern) == 0) {
|
|
+ /* Found a file with .jwk extension. */
|
|
+ if (snprintf(filepath, PATH_MAX, "%s/%s", jwkdir, d->d_name) < 0) {
|
|
+ fprintf(stderr, "Unable to prepare variable with file full path (%s); skipping\n", d->d_name);
|
|
+ continue;
|
|
+ }
|
|
+ filepath[sizeof(filepath) - 1] = '\0';
|
|
+ json_auto_t* json = json_load_file(filepath, 0, NULL);
|
|
+ if (!json) {
|
|
+ fprintf(stderr, "Invalid JSON file (%s); skipping\n", filepath);
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ json_t* arr = tki->m_keys;
|
|
+ if (d->d_name[0] == '.') {
|
|
+ arr = tki->m_rotated_keys;
|
|
+ }
|
|
+ if (json_array_append(arr, json) == -1) {
|
|
+ fprintf(stderr, "Unable to append JSON (%s) to array; skipping\n", d->d_name);
|
|
+ continue;
|
|
+ }
|
|
+ tki->m_keys_count++;
|
|
+ }
|
|
+ }
|
|
+ closedir(dir);
|
|
+ return tki;
|
|
+}
|
|
+
|
|
+struct tang_keys_info*
|
|
+read_keys(const char* jwkdir)
|
|
+{
|
|
+ struct tang_keys_info* tki = load_keys(jwkdir);
|
|
+ if (!tki) {
|
|
+ return NULL;
|
|
+ }
|
|
+
|
|
+ if (tki->m_keys_count == 0) {
|
|
+ /* Let's attempt to create a new pair of keys. */
|
|
+ free_tang_keys_info(tki);
|
|
+ if (!create_new_keys(jwkdir)) {
|
|
+ return NULL;
|
|
+ }
|
|
+ tki = load_keys(jwkdir);
|
|
+ }
|
|
+
|
|
+ if (!prepare_payload_and_sign(tki)) {
|
|
+ free_tang_keys_info(tki);
|
|
+ return NULL;
|
|
+ }
|
|
+ return tki;
|
|
+}
|
|
+
|
|
+json_t*
|
|
+find_jws(struct tang_keys_info* tki, const char* thp)
|
|
+{
|
|
+ if (!tki) {
|
|
+ return NULL;
|
|
+ }
|
|
+
|
|
+ if (thp == NULL) {
|
|
+ /* Default advertisement. */
|
|
+ json_auto_t* jws = jwk_sign(tki->m_payload, tki->m_sign);
|
|
+ if (!jws) {
|
|
+ return NULL;
|
|
+ }
|
|
+ return json_incref(jws);
|
|
+ }
|
|
+ return find_by_thp(tki, thp);
|
|
+}
|
|
+
|
|
+json_t*
|
|
+find_jwk(struct tang_keys_info* tki, const char* thp)
|
|
+{
|
|
+ if (!tki || !thp) {
|
|
+ return NULL;
|
|
+ }
|
|
+ return find_by_thp(tki, thp);
|
|
+}
|
|
diff --git a/src/keys.h b/src/keys.h
|
|
new file mode 100644
|
|
index 0000000..5bae23c
|
|
--- /dev/null
|
|
+++ b/src/keys.h
|
|
@@ -0,0 +1,45 @@
|
|
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
|
|
+/*
|
|
+ * Copyright (c) 2020 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 <stddef.h>
|
|
+
|
|
+struct tang_keys_info {
|
|
+ /* Arrays. */
|
|
+ json_t* m_keys; /* Regular keys. */
|
|
+ json_t* m_rotated_keys; /* Rotated keys. */
|
|
+
|
|
+ json_t* m_payload; /* Payload made of regular keys capable of
|
|
+ * either signing+verifying or deriving new
|
|
+ * keys. */
|
|
+
|
|
+ json_t* m_sign; /* Set of signing keys made from regular
|
|
+ keys. */
|
|
+
|
|
+ size_t m_keys_count; /* Number of keys (regular + rotated). */
|
|
+
|
|
+};
|
|
+
|
|
+void cleanup_tang_keys_info(struct tang_keys_info**);
|
|
+void free_tang_keys_info(struct tang_keys_info*);
|
|
+struct tang_keys_info* read_keys(const char* /* jwkdir */);
|
|
+json_t* find_jws(struct tang_keys_info* /* tki */, const char* /* thp */);
|
|
+json_t* find_jwk(struct tang_keys_info* /* tki */, const char* /* thp */);
|
|
diff --git a/src/meson.build b/src/meson.build
|
|
index 1eb8baf..fd14fcb 100644
|
|
--- a/src/meson.build
|
|
+++ b/src/meson.build
|
|
@@ -1,6 +1,6 @@
|
|
tangd = executable('tangd',
|
|
- 'http.h',
|
|
'http.c',
|
|
+ 'keys.c',
|
|
'tangd.c',
|
|
dependencies: [jose, http_parser],
|
|
install: true,
|
|
@@ -9,6 +9,5 @@ tangd = executable('tangd',
|
|
|
|
bins += join_paths(meson.current_source_dir(), 'tang-show-keys')
|
|
libexecbins += join_paths(meson.current_source_dir(), 'tangd-keygen')
|
|
-libexecbins += join_paths(meson.current_source_dir(), 'tangd-update')
|
|
|
|
# vim:set ts=2 sw=2 et:
|
|
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..cd1488e 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)
|
|
@@ -36,23 +37,20 @@ str_cleanup(char **str)
|
|
free(*str);
|
|
}
|
|
|
|
-static void
|
|
-FILE_cleanup(FILE **file)
|
|
-{
|
|
- if (file && *file)
|
|
- fclose(*file);
|
|
-}
|
|
-
|
|
static int
|
|
adv(enum http_method method, const char *path, const char *body,
|
|
regmatch_t matches[], void *misc)
|
|
{
|
|
- __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;
|
|
+ json_auto_t *jws = NULL;
|
|
+ const char *jwkdir = misc;
|
|
+
|
|
+ tki = read_keys(jwkdir);
|
|
+ if (!tki || tki->m_keys_count == 0) {
|
|
+ return http_reply(HTTP_STATUS_INTERNAL_SERVER_ERROR, NULL);
|
|
+ }
|
|
|
|
if (matches[1].rm_so < matches[1].rm_eo) {
|
|
size_t size = matches[1].rm_eo - matches[1].rm_so;
|
|
@@ -61,24 +59,15 @@ 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)
|
|
+ jws = find_jws(tki, thp);
|
|
+ if (!jws) {
|
|
return http_reply(HTTP_STATUS_NOT_FOUND, NULL);
|
|
+ }
|
|
|
|
- if (fstat(fileno(file), &st) != 0)
|
|
- return http_reply(HTTP_STATUS_INTERNAL_SERVER_ERROR, NULL);
|
|
-
|
|
- adv = calloc(st.st_size + 1, 1);
|
|
+ adv = json_dumps(jws, 0);
|
|
if (!adv)
|
|
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);
|
|
-
|
|
return http_reply(HTTP_STATUS_OK,
|
|
"Content-Type: application/jose+json\r\n"
|
|
"Content-Length: %zu\r\n"
|
|
@@ -91,9 +80,9 @@ 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;
|
|
size_t size = matches[1].rm_eo - matches[1].rm_so;
|
|
- char filename[PATH_MAX] = {};
|
|
- const char *cachedir = misc;
|
|
+ const char *jwkdir = misc;
|
|
json_auto_t *jwk = NULL;
|
|
json_auto_t *req = NULL;
|
|
json_auto_t *rep = NULL;
|
|
@@ -124,15 +113,16 @@ rec(enum http_method method, const char *path, const char *body,
|
|
/*
|
|
* Parse and validate the server-side JWK
|
|
*/
|
|
+ tki = read_keys(jwkdir);
|
|
+ if (!tki || tki->m_keys_count == 0) {
|
|
+ return http_reply(HTTP_STATUS_INTERNAL_SERVER_ERROR, NULL);
|
|
+ }
|
|
|
|
thp = strndup(&path[matches[1].rm_so], size);
|
|
if (!thp)
|
|
return http_reply(HTTP_STATUS_INTERNAL_SERVER_ERROR, NULL);
|
|
|
|
- if (snprintf(filename, sizeof(filename), "%s/%s.jwk", cachedir, thp) < 0)
|
|
- return http_reply(HTTP_STATUS_INTERNAL_SERVER_ERROR, NULL);
|
|
-
|
|
- jwk = json_load_file(filename, 0, NULL);
|
|
+ jwk = find_jwk(tki, thp);
|
|
if (!jwk)
|
|
return http_reply(HTTP_STATUS_NOT_FOUND, NULL);
|
|
|
|
@@ -188,7 +178,7 @@ main(int argc, char *argv[])
|
|
http_parser_init(&parser, HTTP_REQUEST);
|
|
|
|
if (argc != 2) {
|
|
- fprintf(stderr, "Usage: %s <cachedir>\n", argv[0]);
|
|
+ fprintf(stderr, "Usage: %s <jwkdir>\n", argv[0]);
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
diff --git a/tests/adv b/tests/adv
|
|
index 357d097..06d1330 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
|
|
|
|
@@ -84,4 +82,4 @@ fetch /adv/`jose jwk thp -i $TMP/db/.sig.jwk` \
|
|
-g 0 -Og protected -SyOg cty -Sq "jwk-set+json" -EUUUUU \
|
|
-g 1 -Og protected -SyOg cty -Sq "jwk-set+json" -EUUUUU
|
|
|
|
-test $(tang-show-keys $PORT) == $(jose jwk thp -i $TMP/db/sig.jwk)
|
|
+test "$(tang-show-keys $PORT)" == "$(jose jwk thp -i $TMP/db/sig.jwk)"
|
|
diff --git a/tests/keys/-bWkGaJi0Zdvxaj4DCp28umLcRA.jwk b/tests/keys/-bWkGaJi0Zdvxaj4DCp28umLcRA.jwk
|
|
new file mode 100644
|
|
index 0000000..d51ccbd
|
|
--- /dev/null
|
|
+++ b/tests/keys/-bWkGaJi0Zdvxaj4DCp28umLcRA.jwk
|
|
@@ -0,0 +1 @@
|
|
+{"alg":"ECMR","crv":"P-521","d":"AQ3u1g0L__GIGSJRX1LtjSArwJxxQz0kWXIi-X4PqwoheoeY57cw36pmWmyVsn43jDEZ6SAsiNeIw9sHDkFZe1VV","key_ops":["deriveKey"],"kty":"EC","x":"AcfowrKEKteg_jKX1CiR2RQfbUGJ73KXlcl8AgIDAgN7R6yNKWpKhZNBmV2tAxxMCQcIksqQl17UXwemvH2j2fem","y":"ACrb-y4ZhLIGX-41QYgJhniiZ85qkjILbkVUcC8gBYxOAnKWIpMGLsjrT3AYhM6jk6puwnNYbEM28s2caAEogUcA"}
|
|
diff --git a/tests/keys/.r4E2wG1u_YyKUo0N0rIK7jJF5Xg.jwk b/tests/keys/.r4E2wG1u_YyKUo0N0rIK7jJF5Xg.jwk
|
|
new file mode 100644
|
|
index 0000000..5602a0c
|
|
--- /dev/null
|
|
+++ b/tests/keys/.r4E2wG1u_YyKUo0N0rIK7jJF5Xg.jwk
|
|
@@ -0,0 +1 @@
|
|
+{"alg":"ES512","crv":"P-521","d":"Af5SfYh_4LmBDF9dCGDQRDA52yzqnzDeo-GZU05hpnLr6bsIBTFpc8rdcCm95mhZ59ngC-6WNtmAF_nLCDcHKg0A","key_ops":["sign","verify"],"kty":"EC","x":"AUxS3DXdoONUB-6-nyzdd3V42iD7obGTJ1m40t3V6jzXfABWp_gtTidwiKyDJQXxhEzMSToo-V0RGq6Qz2XTOgPe","y":"AFrkJfkLGJz_2v-k3-wdydckVcBXql2NR66HaF0U9NlcfGLezQau7XihArm4GE3-sHoQLsRa-HvYET9zyd9Syh5y"}
|
|
diff --git a/tests/keys/.uZ0s8YTXcGcuWduWWBSiR2OjOVg.jwk b/tests/keys/.uZ0s8YTXcGcuWduWWBSiR2OjOVg.jwk
|
|
new file mode 100644
|
|
index 0000000..5088e1c
|
|
--- /dev/null
|
|
+++ b/tests/keys/.uZ0s8YTXcGcuWduWWBSiR2OjOVg.jwk
|
|
@@ -0,0 +1 @@
|
|
+{"alg":"ECMR","crv":"P-521","d":"Acuk66sHvSS5TN-p0XhwHhVKyifYr-ecur-7zfgQZMzSq9SeBJjuX6Ttav-7AbnVvXU_S55BgtGL5iymXGuMguCp","key_ops":["deriveKey"],"kty":"EC","x":"AEz9EwBOIrLeV4in6M1oWVnOWVR7ubkFB0R0-AwyIL7u5-7G3u2tvPIJRQY-l1Wttn7Ar4DhflcMhnb3rk5hT5yh","y":"AZt35wqOhNsnEi-GLAgyCaiW_c6h6Zyo4xwjuvXzmQMDwh9MtdaUigFuBOTlfRj1uri_YBqdpI09nYrqqgx97Ca6"}
|
|
diff --git a/tests/keys/another-bad-file b/tests/keys/another-bad-file
|
|
new file mode 100644
|
|
index 0000000..323fae0
|
|
--- /dev/null
|
|
+++ b/tests/keys/another-bad-file
|
|
@@ -0,0 +1 @@
|
|
+foobar
|
|
diff --git a/tests/keys/empty.jwk b/tests/keys/empty.jwk
|
|
new file mode 100644
|
|
index 0000000..e69de29
|
|
diff --git a/tests/keys/invalid.jwk b/tests/keys/invalid.jwk
|
|
new file mode 100644
|
|
index 0000000..257cc56
|
|
--- /dev/null
|
|
+++ b/tests/keys/invalid.jwk
|
|
@@ -0,0 +1 @@
|
|
+foo
|
|
diff --git a/tests/keys/qgmqJSo6AEEuVQY7zVlklqdTMqY.jwk b/tests/keys/qgmqJSo6AEEuVQY7zVlklqdTMqY.jwk
|
|
new file mode 100644
|
|
index 0000000..107a545
|
|
--- /dev/null
|
|
+++ b/tests/keys/qgmqJSo6AEEuVQY7zVlklqdTMqY.jwk
|
|
@@ -0,0 +1 @@
|
|
+{"alg":"ES512","crv":"P-521","d":"AUQpuWtoSqURl0OuWQ-I9i4X1F3sDak0Hbf9Ixj7uwjA20A0ABJdCHbai1Ai0t3yoxWKPYi6t2XjjeRzHIKyhXbf","key_ops":["sign","verify"],"kty":"EC","x":"ACbYnvh1EtLePkM5jCtuxLpMroOUNRfv-wQdXgT5AQ5bhSLv6wkrBzh1rwymo-fmCNWzrcTflzqWf-wXAd00lokM","y":"AEaDByxXfbee4TlPXgPRg5S4MVOqgObjX6_JJkySTudSfOygcx7dujsf32dOEI25d8bNKUgdjQt8lc5XtHeWXH3a"}
|
|
diff --git a/tests/meson.build b/tests/meson.build
|
|
index b03531d..9fe656e 100644
|
|
--- a/tests/meson.build
|
|
+++ b/tests/meson.build
|
|
@@ -1,22 +1,42 @@
|
|
+incdir = include_directories(
|
|
+ join_paths('..', 'src')
|
|
+)
|
|
+
|
|
+test_data = configuration_data()
|
|
+test_data.set('testjwkdir', join_paths(meson.source_root(), 'tests','keys'))
|
|
+
|
|
+test_keys_c = configure_file(
|
|
+ input: 'test-keys.c.in',
|
|
+ output: 'test-keys.c',
|
|
+ configuration: test_data
|
|
+)
|
|
+
|
|
+test_keys = executable('test-keys',
|
|
+ test_keys_c,
|
|
+ 'test-util.c',
|
|
+ dependencies: [jose],
|
|
+ include_directories: incdir
|
|
+)
|
|
+
|
|
sd_activate = find_program(
|
|
'systemd-socket-activate',
|
|
'systemd-activate',
|
|
required: false
|
|
)
|
|
|
|
+env = environment()
|
|
+env.prepend('PATH',
|
|
+ join_paths(meson.source_root(), 'src'),
|
|
+ join_paths(meson.build_root(), 'src'),
|
|
+ separator: ':'
|
|
+)
|
|
+
|
|
if sd_activate.found()
|
|
- env = environment()
|
|
- env.prepend('PATH',
|
|
- join_paths(meson.source_root(), 'src'),
|
|
- join_paths(meson.build_root(), 'src'),
|
|
- separator: ':'
|
|
- )
|
|
env.set('SD_ACTIVATE', sd_activate.path() + ' --inetd')
|
|
|
|
- test('adv', find_program('adv'), env: env)
|
|
+ test('adv', find_program('adv'), env: env, timeout: 60)
|
|
test('rec', find_program('rec'), env: env)
|
|
-else
|
|
- warning('Will not run the tests due to missing dependencies!')
|
|
endif
|
|
+test('test-keys', test_keys, env: env, timeout: 60)
|
|
|
|
# vim:set ts=2 sw=2 et:
|
|
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/tests/test-keys.c.in b/tests/test-keys.c.in
|
|
new file mode 100644
|
|
index 0000000..2071b9f
|
|
--- /dev/null
|
|
+++ b/tests/test-keys.c.in
|
|
@@ -0,0 +1,257 @@
|
|
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
|
|
+/*
|
|
+ * Copyright (c) 2020 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 "keys.c"
|
|
+#include "test-util.h"
|
|
+
|
|
+const char* jwkdir = "@testjwkdir@";
|
|
+
|
|
+struct thp_result {
|
|
+ const char* thp;
|
|
+ int valid;
|
|
+};
|
|
+
|
|
+struct test_result_int {
|
|
+ const char* data;
|
|
+ int expected;
|
|
+};
|
|
+
|
|
+static void
|
|
+test_create_new_keys(void)
|
|
+{
|
|
+ __attribute__((cleanup(cleanup_str))) char* newdir = create_tempdir();
|
|
+ ASSERT(newdir);
|
|
+ __attribute__((cleanup(cleanup_tang_keys_info))) struct tang_keys_info* tki = read_keys(newdir);
|
|
+ ASSERT(tki);
|
|
+ ASSERT(tki->m_keys_count == 2);
|
|
+ remove_tempdir(newdir);
|
|
+}
|
|
+
|
|
+
|
|
+static void
|
|
+test_is_hash(void)
|
|
+{
|
|
+ const struct test_result_int test_data[] = {
|
|
+ {NULL, 0},
|
|
+ {"", 0},
|
|
+ {"ES512", 0},
|
|
+ {"ECMR", 0},
|
|
+ {"foobar", 0},
|
|
+ {"{", 0},
|
|
+ {"[}", 0},
|
|
+ {"[]", 0},
|
|
+ {"S1", 1},
|
|
+ {"S224", 1},
|
|
+ {"S256", 1},
|
|
+ {"S384", 1},
|
|
+ {"S512", 1},
|
|
+ {"S42", 0}
|
|
+ };
|
|
+ for (int i = 0, len = ARRAY_COUNT(test_data); i < len; i++) {
|
|
+ int ret = is_hash(test_data[i].data);
|
|
+ ASSERT_WITH_MSG(ret == test_data[i].expected, "i = %d, alg = %s", i, test_data[i].data);
|
|
+ };
|
|
+
|
|
+}
|
|
+
|
|
+static void
|
|
+test_jwk_generate(void)
|
|
+{
|
|
+ const struct test_result_int test_data[] = {
|
|
+ {NULL, 0},
|
|
+ {"", 0},
|
|
+ {"ES512", 1},
|
|
+ {"ECMR", 1},
|
|
+ {"foobar", 0},
|
|
+ {"{", 0},
|
|
+ {"[}", 0},
|
|
+ {"[]", 0}
|
|
+ };
|
|
+
|
|
+ for (int i = 0, len = ARRAY_COUNT(test_data); i < len; i++) {
|
|
+ json_auto_t* jwk = jwk_generate(test_data[i].data);
|
|
+ ASSERT_WITH_MSG(!!jwk == test_data[i].expected, "i = %d, alg = %s", i, test_data[i].data);
|
|
+ };
|
|
+}
|
|
+
|
|
+static void
|
|
+test_find_jws(void)
|
|
+{
|
|
+ const struct thp_result test_data[] = {
|
|
+ {"00BUQM4A7NYxbOrBR9QDfkzGVGj3k57Fs4jCbJxcLYAgRFHu5B7jtbL97x1T7stQ", 1},
|
|
+ {"dd5qbN1lQ6UWdZszbfx2oIcH34ShklzFL1SUQg", 1},
|
|
+ {"dOZkUtZ_gLDUP53GIlyAxHMNuyrk8vdY-XXND32GccqNbT_MKpqGC-13-GNEye48", 1},
|
|
+ {"DZrlBQvfvlwPQlvH_IieBdc_KpesEramLygVL_rFr7g", 1},
|
|
+ {"FL_Zt5fFadUL4syeMMpUnss8aKdCrPGFy3102JGR3EE", 1},
|
|
+ {"qgmqJSo6AEEuVQY7zVlklqdTMqY", 1},
|
|
+ {"r4E2wG1u_YyKUo0N0rIK7jJF5Xg", 1},
|
|
+ {"ugJ4Ula-YABQIiJ-0g3B_jpFpF2nl3W-DNpfLdXArhTusV0QCcd1vtgDeGHEPzpm7jEsyC7VYYSSOkZicK22mw", 1},
|
|
+ {"up0Z4fRhpd4O5QwBaMCXDTlrvxCmZacU0MD8kw", 1},
|
|
+ {"vllHS-M0aQFCo2yUCcAahMU4TAtXACyeuRf-zbmmTPBg7V0Pb-RRFGo5C6MnpzdirK8B3ORLOsN8RyXClvtjxA", 1},
|
|
+ {NULL, 1},
|
|
+ {"a", 0},
|
|
+ {"foo", 0},
|
|
+ {"bar", 0},
|
|
+ {"XXXXXXXXXXXXXXXXXX", 0}
|
|
+ };
|
|
+
|
|
+ __attribute__((cleanup(cleanup_tang_keys_info))) struct tang_keys_info* tki = read_keys(jwkdir);
|
|
+ for (int i = 0, len = ARRAY_COUNT(test_data); i < len; i++) {
|
|
+ json_auto_t* jws = find_jws(tki, test_data[i].thp);
|
|
+ ASSERT_WITH_MSG(!!jws == test_data[i].valid, "i = %d, thp = %s", i, test_data[i].thp);
|
|
+ }
|
|
+
|
|
+ /* Passing NULL to find_jws should return the default advertisement */
|
|
+ json_auto_t* adv = find_jws(tki, NULL);
|
|
+ ASSERT(adv);
|
|
+
|
|
+
|
|
+ /*
|
|
+ * The default set of signing keys are the signing keys that are not
|
|
+ * rotated. The payload is made of deriving keys that are also not
|
|
+ * rotated. The default advertisement should be signed by this set of
|
|
+ * default signing keys.
|
|
+ */
|
|
+ ASSERT(jose_jws_ver(NULL, adv, NULL, tki->m_sign, 1));
|
|
+
|
|
+ /* find_jws should be able to respond to thumbprints of keys using any
|
|
+ * of jose supported hash algorithms. */
|
|
+ const char** hashes = supported_hashes();
|
|
+ size_t idx;
|
|
+ json_t* jwk;
|
|
+
|
|
+ /* Let's put together all the keys, including rotated ones. */
|
|
+ json_auto_t* keys = json_deep_copy(tki->m_keys);
|
|
+ ASSERT(keys);
|
|
+ ASSERT(json_array_extend(keys, tki->m_rotated_keys) == 0);
|
|
+ ASSERT(json_array_size(keys) == (size_t)tki->m_keys_count);
|
|
+
|
|
+ for (int i = 0; hashes[i]; i++) {
|
|
+ json_array_foreach(keys, idx, jwk) {
|
|
+ if (!jwk_valid_for_signing(jwk)) {
|
|
+ continue;
|
|
+ }
|
|
+ __attribute__((cleanup(cleanup_str))) char* thp = jwk_thumbprint(jwk, hashes[i]);
|
|
+ ASSERT_WITH_MSG(thp, "i = %d, hash = %s, key idx = %d", i, hashes[i], idx);
|
|
+ json_auto_t* jws = find_jws(tki, thp);
|
|
+ ASSERT_WITH_MSG(jws, "i = %d, hash = %s, key idx = %d, thp = %s", i, hashes[i], idx, thp);
|
|
+
|
|
+ /* Signing keys should sign the payload, in addition to the
|
|
+ * default set of signing keys. */
|
|
+ json_auto_t* sign = json_deep_copy(tki->m_sign);
|
|
+ ASSERT_WITH_MSG(sign, "i = %d, hash = %s, key idx = %d, thp = %s", i, hashes[i], idx, thp);
|
|
+ ASSERT_WITH_MSG(json_array_append(sign, jwk) == 0, "i = %d, hash = %s, key idx = %d, thp = %s", i, hashes[i], idx, thp);
|
|
+ ASSERT_WITH_MSG(jose_jws_ver(NULL, jws, NULL, sign, 1), "i = %d, hash = %s, key idx = %d, thp = %s", i, hashes[i], idx, thp);
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
+static void
|
|
+test_find_jwk(void)
|
|
+{
|
|
+ const struct thp_result test_data[] = {
|
|
+ {"1HdF3XKRSsuZdkpXNurBPoL_pvxdvCOlHuhB4DP-4xWFqbZ51zo29kR4fSiT3BGy9UrHVJ26JMBLOA1vKq3lxA", 1},
|
|
+ {"9U8qgy_YjyY6Isuq6QuiKEiYZgNJShcGgJx5FJzCu6m3N6zFaIPy_HDkxkVqAZ9E", 1},
|
|
+ {"-bWkGaJi0Zdvxaj4DCp28umLcRA", 1},
|
|
+ {"Cy73glFjs6B6RU7wy6vWxAc-2bJy5VJOT9LyK80eKgZ8k27wXZ-3rjsuNU5tua_yHWtluyoSYtjoKXfI0E8ESw", 1},
|
|
+ {"kfjbqx_b3BsgPC87HwlOWL9daGMMHBzxcFLClw", 1},
|
|
+ {"L4xg2tZXTEVbsK39bzOZM1jGWn3HtOxF5gh6F9YVf5Q", 1},
|
|
+ {"LsVAV2ig5LlfstM8TRSf-c7IAkLpNYbIysNuRCVlxocRCGqAh6-f9PklM4nU4N-J", 1},
|
|
+ {"OkAcDxYHNlo7-tul8OubYuWXB8CPEhAkcacCmhTclMU", 1},
|
|
+ {"uZ0s8YTXcGcuWduWWBSiR2OjOVg", 1},
|
|
+ {"WEpfFyeoNKkE2-TosN_bP-gd9UgRvQCZpVasZQ", 1},
|
|
+ {NULL, 0},
|
|
+ {"a", 0},
|
|
+ {"foo", 0},
|
|
+ {"bar", 0},
|
|
+ {"XXXXXXXXXXXXXXXXXX", 0},
|
|
+ };
|
|
+
|
|
+ __attribute__((cleanup(cleanup_tang_keys_info))) struct tang_keys_info* tki = read_keys(jwkdir);
|
|
+
|
|
+ for (int i = 0, len = ARRAY_COUNT(test_data); i < len; i++) {
|
|
+ json_auto_t* tjwk = find_jwk(tki, test_data[i].thp);
|
|
+ ASSERT_WITH_MSG(!!tjwk == test_data[i].valid, "i = %d, thp = %s", i, test_data[i].thp);
|
|
+ }
|
|
+ /* Passing NULL to find_jwk should fail */
|
|
+ json_auto_t* bad_jwk = find_jwk(tki, NULL);
|
|
+ ASSERT(bad_jwk == NULL);
|
|
+
|
|
+ /* find_jwk should be able to respond to thumbprints of keys using any
|
|
+ * of jose supported hash algorithms. */
|
|
+ const char** hashes = supported_hashes();
|
|
+ size_t idx;
|
|
+ json_t* jwk;
|
|
+
|
|
+ /* Let's put together all the keys, including rotated ones. */
|
|
+ json_auto_t* keys = json_deep_copy(tki->m_keys);
|
|
+ ASSERT(keys);
|
|
+ ASSERT(json_array_extend(keys, tki->m_rotated_keys) == 0);
|
|
+ ASSERT(json_array_size(keys) == (size_t)tki->m_keys_count);
|
|
+
|
|
+ for (int i = 0; hashes[i]; i++) {
|
|
+ json_array_foreach(keys, idx, jwk) {
|
|
+ if (!jwk_valid_for_deriving_keys(jwk)) {
|
|
+ continue;
|
|
+ }
|
|
+ __attribute__((cleanup(cleanup_str))) char* thp = jwk_thumbprint(jwk, hashes[i]);
|
|
+ json_auto_t* tjwk = find_jwk(tki, thp);
|
|
+ ASSERT_WITH_MSG(tjwk, "i = %d, hash = %s, key idx = %d, thp = %s", i, hashes[i], idx, thp);
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
+static void
|
|
+test_read_keys(void)
|
|
+{
|
|
+ __attribute__((cleanup(cleanup_tang_keys_info))) struct tang_keys_info* tki = read_keys(jwkdir);
|
|
+ ASSERT(tki);
|
|
+
|
|
+ /*
|
|
+ * Keys in tests/keys:
|
|
+ * - .uZ0s8YTXcGcuWduWWBSiR2OjOVg.jwk
|
|
+ * - .r4E2wG1u_YyKUo0N0rIK7jJF5Xg.jwk
|
|
+ * - qgmqJSo6AEEuVQY7zVlklqdTMqY.jwk
|
|
+ * - -bWkGaJi0Zdvxaj4DCp28umLcRA.jwk
|
|
+ */
|
|
+ ASSERT(tki->m_keys_count == 4);
|
|
+ ASSERT(json_array_size(tki->m_keys) == 2);
|
|
+ ASSERT(json_array_size(tki->m_rotated_keys) == 2);
|
|
+
|
|
+ const char* invalid_jwkdir = "foobar";
|
|
+ __attribute__((cleanup(cleanup_tang_keys_info))) struct tang_keys_info* tki2 = read_keys(invalid_jwkdir);
|
|
+ ASSERT(tki2 == NULL);
|
|
+}
|
|
+
|
|
+static void
|
|
+run_tests(void)
|
|
+{
|
|
+ test_read_keys();
|
|
+ test_find_jwk();
|
|
+ test_find_jws();
|
|
+ test_jwk_generate();
|
|
+ test_is_hash();
|
|
+ test_create_new_keys();
|
|
+}
|
|
+
|
|
+int main(int argc, char** argv)
|
|
+{
|
|
+ run_tests();
|
|
+ return 0;
|
|
+}
|
|
diff --git a/tests/test-util.c b/tests/test-util.c
|
|
new file mode 100644
|
|
index 0000000..bc1299b
|
|
--- /dev/null
|
|
+++ b/tests/test-util.c
|
|
@@ -0,0 +1,75 @@
|
|
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
|
|
+/*
|
|
+ * Copyright (c) 2020 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/>.
|
|
+ */
|
|
+
|
|
+#define _XOPEN_SOURCE 500L
|
|
+
|
|
+#include <stdarg.h>
|
|
+#include <stdlib.h>
|
|
+#include <string.h>
|
|
+#include <unistd.h>
|
|
+#include <stdio.h>
|
|
+#include <ftw.h>
|
|
+
|
|
+#include "test-util.h"
|
|
+
|
|
+void
|
|
+assert_func(const char* filename,
|
|
+ int lineno,
|
|
+ const char* funcname,
|
|
+ const char* expr,
|
|
+ const char* fmt,
|
|
+ ...)
|
|
+{
|
|
+ char buffer[MAX_BUF_LEN] = {};
|
|
+ if (fmt) {
|
|
+ va_list ap;
|
|
+ va_start(ap, fmt);
|
|
+ vsnprintf(buffer, MAX_BUF_LEN, fmt, ap);
|
|
+ va_end(ap);
|
|
+ buffer[strcspn(buffer, "\r\n")] = '\0';
|
|
+ }
|
|
+ fprintf(stderr, "%s:%d: assertion '%s' failed in %s(). %s\n", filename,
|
|
+ lineno,
|
|
+ expr,
|
|
+ funcname,
|
|
+ buffer);
|
|
+ abort();
|
|
+}
|
|
+
|
|
+static int
|
|
+nftw_remove_callback(const char* path, const struct stat* stat,
|
|
+ int type, struct FTW* ftw)
|
|
+{
|
|
+ return remove(path);
|
|
+}
|
|
+
|
|
+char*
|
|
+create_tempdir(void)
|
|
+{
|
|
+ char template[] = "/tmp/tang.test.XXXXXX";
|
|
+ char *tmpdir = mkdtemp(template);
|
|
+ return strdup(tmpdir);
|
|
+}
|
|
+
|
|
+int
|
|
+remove_tempdir(const char* path)
|
|
+{
|
|
+ return nftw(path, nftw_remove_callback, FOPEN_MAX, FTW_DEPTH|FTW_MOUNT|FTW_PHYS);
|
|
+}
|
|
+
|
|
diff --git a/tests/test-util.h b/tests/test-util.h
|
|
new file mode 100644
|
|
index 0000000..eae9a71
|
|
--- /dev/null
|
|
+++ b/tests/test-util.h
|
|
@@ -0,0 +1,46 @@
|
|
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
|
|
+/*
|
|
+ * Copyright (c) 2020 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 <stdarg.h>
|
|
+
|
|
+#define ARRAY_COUNT(arr) (sizeof(arr)/sizeof(0[arr]))
|
|
+#define MAX_BUF_LEN 2048
|
|
+
|
|
+void
|
|
+assert_func(const char* /* filename */,
|
|
+ int /* line number */,
|
|
+ const char* /* function name */,
|
|
+ const char* /* expression */,
|
|
+ const char* /* format */,
|
|
+ ...);
|
|
+
|
|
+#define ASSERT_WITH_MSG(expr, fmt, ...) \
|
|
+ if (!(expr)) \
|
|
+ assert_func(__FILE__, __LINE__, __FUNCTION__, #expr, fmt, ##__VA_ARGS__)
|
|
+
|
|
+#define ASSERT(expr) \
|
|
+ ASSERT_WITH_MSG(expr, NULL)
|
|
+
|
|
+char*
|
|
+create_tempdir(void);
|
|
+
|
|
+int
|
|
+remove_tempdir(const char* /* path */);
|
|
diff --git a/units/meson.build b/units/meson.build
|
|
index ada6dd0..6e66af9 100644
|
|
--- a/units/meson.build
|
|
+++ b/units/meson.build
|
|
@@ -1,31 +1,10 @@
|
|
-tangd_keygen_service = configure_file(
|
|
- input: 'tangd-keygen.service.in',
|
|
- output: 'tangd-keygen.service',
|
|
- configuration: data
|
|
-)
|
|
-
|
|
tangd_service = configure_file(
|
|
input: 'tangd@.service.in',
|
|
output: 'tangd@.service',
|
|
configuration: data
|
|
)
|
|
|
|
-tangd_update_path = configure_file(
|
|
- input: 'tangd-update.path.in',
|
|
- output: 'tangd-update.path',
|
|
- configuration: data
|
|
-)
|
|
-
|
|
-tangd_update_service = configure_file(
|
|
- input: 'tangd-update.service.in',
|
|
- output: 'tangd-update.service',
|
|
- configuration: data
|
|
-)
|
|
-
|
|
units += join_paths(meson.current_source_dir(), 'tangd.socket')
|
|
-units += tangd_keygen_service
|
|
units += tangd_service
|
|
-units += tangd_update_path
|
|
-units += tangd_update_service
|
|
|
|
# vim:set ts=2 sw=2 et:
|
|
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 b/units/tangd.socket
|
|
index 22474ea..0a3e239 100644
|
|
--- a/units/tangd.socket
|
|
+++ b/units/tangd.socket
|
|
@@ -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.28.0
|
|
|