Browse Source

Cherry pick commits to move the key handling to the tang process. Closes: #975343

Also adjust autopkgtest.
Christoph Biedl 3 years ago
parent
commit
a7d19acff0

+ 528 - 0
debian/patches/cherry-pick/1606480249.v7-6-g6090505.add-functions-for-key-manipulation.patch

@@ -0,0 +1,528 @@
+Subject: Add functions for key manipulation
+Origin: v7-6-g6090505 <https://github.com/latchset/tang/commit/v7-6-g6090505>
+Upstream-Author: Sergio Correia <scorreia@redhat.com>
+Date: Fri Nov 27 09:30:49 2020 -0300
+
+    We currently rely on the tangd-update script to read the keys and
+    generate signed advertisements as well as JWKs for key derivation.
+
+    Whenever there is a change in the directory containing the actual
+    keys, we run tangd-update through a systemd file watching mechanism,
+    so that we can have a cache directory with updated advertisements +
+    JWKs.
+
+    As reported in #23 and #24, this mechanism can be unreliable in
+    certain situations, and having up-to-date information on the keys that
+    are available is critical to tang, so the idea here is to remove this
+    dependency on external scripts (e.g. tangd-update) and move this
+    computation to tang itself.
+
+    In this commit we add the related functions for key manipulation so
+    that in a next step we can start using it in tang.
+
+--- /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);
++}
+--- /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 */);

+ 473 - 0
debian/patches/cherry-pick/1606480958.v7-7-gc71df1d.add-tests-for-key-manipulation-functions.patch

@@ -0,0 +1,473 @@
+Subject: Add tests for key manipulation functions
+Origin: v7-7-gc71df1d <https://github.com/latchset/tang/commit/v7-7-gc71df1d>
+Upstream-Author: Sergio Correia <scorreia@redhat.com>
+Date: Fri Nov 27 09:42:38 2020 -0300
+
+    In this commit we add tests for the key manipulation functions added in
+    src/keys.{c|h}.
+
+--- /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"}
+--- /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"}
+--- /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"}
+--- /dev/null
++++ b/tests/keys/another-bad-file
+@@ -0,0 +1 @@
++foobar
+--- /dev/null
++++ b/tests/keys/invalid.jwk
+@@ -0,0 +1 @@
++foo
+--- /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"}
+--- 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:
+--- /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;
++}
+--- /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);
++}
++
+--- /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 */);

+ 381 - 0
debian/patches/cherry-pick/1606525324.v7-8-g7119454.move-key-handling-to-tang-itself.patch

@@ -0,0 +1,381 @@
+Subject: Move key handling to tang itself
+Origin: v7-8-g7119454 <https://github.com/latchset/tang/commit/v7-8-g7119454>
+Upstream-Author: Sergio Correia <scorreia@redhat.com>
+Date: Fri Nov 27 22:02:04 2020 -0300
+
+    Use the key manipulation functions added in src/keys.{c|h} in tangd.
+
+    This effectively removes the need for a cache directory -- usually
+    /var/cache/tang --, which contained pre-computed files with signed
+    advertisements and JWK with keys for deriving new keys.
+
+    This computation was done by the tangd-update script, which has also
+    been removed in this commit.
+
+    We relied on systemd to run this script whenever the JWK dir -- usually
+    /var/db/tang, which is where the actual keys are located -- changed, to
+    keep the cache directory updated, but this is sometimes unreliable,
+    causing issues like the ones reported in #23 and #24.
+
+    As of now, tang performs these computations itself and does not depend
+    on external scripts to make sure it has reliable information regarding
+    its keys.
+
+    Additionally, tang also creates a new pair of keys if none exist.
+
+--- a/meson.build
++++ b/meson.build
+@@ -16,14 +16,12 @@
+ 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(
+--- 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 @@
+ 
+ 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:
+--- 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
+--- 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 @@
+         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 @@
+             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 @@
+ {
+     __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 @@
+     /*
+      * 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 @@
+     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;
+     }
+ 
+--- a/tests/adv
++++ b/tests/adv
+@@ -36,15 +36,13 @@
+ 
+ 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 @@
+                -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)"
+--- a/tests/rec
++++ b/tests/rec
+@@ -28,11 +28,9 @@
+ 
+ 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 @@
+ 
+ # 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
+ 
+--- 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:
+--- 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@
+--- a/units/tangd-update.path.in
++++ /dev/null
+@@ -1,4 +0,0 @@
+-[Path]
+-PathChanged=@jwkdir@
+-MakeDirectory=true
+-DirectoryMode=0700
+--- 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@
+--- a/units/tangd@.service.in
++++ b/units/tangd@.service.in
+@@ -1,10 +1,8 @@
+ [Unit]
+ Description=Tang Server
+-Requires=tangd-keygen.service
+-After=tangd-keygen.service
+ 
+ [Service]
+ StandardInput=socket
+ StandardOutput=socket
+ StandardError=journal
+-ExecStart=@libexecdir@/tangd @cachedir@
++ExecStart=@libexecdir@/tangd @jwkdir@

+ 2 - 2
debian/patches/for-upstream/2018-08-11.use-asciidoctor-to-build-manpages.patch

@@ -5,7 +5,7 @@ Bug: https://github.com/latchset/tang/issues/32
 
 --- a/meson.build
 +++ b/meson.build
-@@ -47,7 +47,7 @@
+@@ -45,7 +45,7 @@
  )
  
  jose = dependency('jose', version: '>=8')
@@ -14,7 +14,7 @@ Bug: https://github.com/latchset/tang/issues/32
  compiler = meson.get_compiler('c')
  if not compiler.has_header('http_parser.h')
    error('http-parser devel files not found.')
-@@ -70,16 +70,16 @@
+@@ -68,16 +68,16 @@
  install_data(units, install_dir: systemunitdir)
  install_data(licenses, install_dir: licensedir)
  

+ 1 - 3
debian/patches/for-upstream/2018-08-12.add-systemd-documentation-key.patch

@@ -5,11 +5,9 @@ Bug: https://github.com/latchset/tang/issues/49
 
 --- a/units/tangd@.service.in
 +++ b/units/tangd@.service.in
-@@ -1,7 +1,6 @@
+@@ -1,5 +1,6 @@
  [Unit]
  Description=Tang Server
--Requires=tangd-keygen.service
--After=tangd-keygen.service
 +Documentation=man:tangd(8)
  
  [Service]

+ 3 - 0
debian/patches/series

@@ -3,6 +3,9 @@
 cherry-pick/1576775046.v7-2-gfed9020.move-build-system-to-meson.patch
 cherry-pick/1580371641.v7-4-g7778512.fixed-ordering-cycle-found-causing-spurious-issues-during-boot-no.patch
 cherry-pick/1594371716.v7-5-g2ef4acf.just-correct-some-spelling.patch
+cherry-pick/1606480249.v7-6-g6090505.add-functions-for-key-manipulation.patch
+cherry-pick/1606480958.v7-7-gc71df1d.add-tests-for-key-manipulation-functions.patch
+cherry-pick/1606525324.v7-8-g7119454.move-key-handling-to-tang-itself.patch
 
 # patches for upstream
 for-upstream/2018-08-11.use-asciidoctor-to-build-manpages.patch

+ 5 - 1
debian/tests/control

@@ -1,7 +1,11 @@
 Tests: run-testsuite
 Depends:
-    @,
+    tang (>= 7-3~),
     curl,
     dpkg-dev,
     moreutils,
     socat,
+    systemd,
+Restrictions:
+    allow-stderr,
+Architecture: linux-any

+ 10 - 7
debian/tests/run-testsuite

@@ -1,22 +1,25 @@
 #!/bin/sh
 
-set -e
+set -eu
+
+export PATH="$PATH:/usr/libexec:/usr/lib/$(dpkg-architecture --query=DEB_HOST_GNU_TYPE 2>/dev/null)"
 
 cd tests
 
-export PATH="$PATH:/usr/libexec:/usr/lib/$(dpkg-architecture --query=DEB_HOST_GNU_TYPE 2>/dev/null)"
+EXIT=0
 
 for f in $(find . -type f -executable | sort) ; do
     echo "I: Preparing test $f"
-    # replace socket activation with socat
+    # replace socket activation with socat, systemd-socket-activate
+    # results in spurious errors I've already spent hours debugging.
     if [ "$f" = './adv' ] ; then
-        sed -i -e 's;\$SD_ACTIVATE -l "127.0.0.1:\$PORT" -a \$VALGRIND tangd \$TMP/cache;socat TCP-LISTEN:$PORT,bind=127.0.0.1,fork "EXEC:tangd $TMP/cache";' $f
+        sed -i -e 's;\$SD_ACTIVATE -l "127.0.0.1:\$PORT" -a \$VALGRIND tangd \$TMP/db;socat TCP-LISTEN:$PORT,bind=127.0.0.1,fork "EXEC:tangd $TMP/db";' $f
     elif [ "$f" = './rec' ] ; then
-        sed -i -e 's;\$SD_ACTIVATE -l 127.0.0.1:\$port -a \$VALGRIND tangd \$TMP/cache;socat TCP-LISTEN:$port,bind=127.0.0.1,fork "EXEC:tangd $TMP/cache";' $f
+        sed -i -e 's;\$SD_ACTIVATE -l 127.0.0.1:\$port -a \$VALGRIND tangd \$TMP/db;socat TCP-LISTEN:$port,bind=127.0.0.1,fork "EXEC:tangd $TMP/db";' $f
     fi
     echo "I: Running test $f"
     # test are very noisy, keep them silent as long as they pass
-    chronic "$f" || :
+    chronic "$f" || EXIT=1
 done
 
-exit 0
+exit $EXIT