Subject: Move key handling to tang itself Origin: v7-8-g7119454 Upstream-Author: Sergio Correia 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 -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# - -TMP='{"protected":{"cty":"jwk-set+json"}}' - -trap 'exit' ERR - -shopt -s nullglob - -HASHES=`jose alg -k hash` - -if [ $# -ne 2 ] || [ ! -d "$1" ]; then - echo "Usage: $0 " >&2 - exit 1 -fi - -[ ! -d "$2" ] && mkdir -p -m 0700 "$2" - -src=`realpath "$1"` -dst=`realpath "$2"` - -payl=() -sign=() - -for jwk in $src/*.jwk; do - if jose jwk use -i "$jwk" -r -u sign -u verify; then - sign+=("-s" "$TMP" "-k" "$jwk") - payl+=("-i" "$jwk") - elif jose jwk use -i "$jwk" -r -u deriveKey; then - payl+=("-i" "$jwk") - else - echo "Skipping invalid key: $jwk" >&2 - fi -done - -if [ ${#sign[@]} -gt 0 ]; then - jose jwk pub -s "${payl[@]}" \ - | jose jws sig -I- "${sign[@]}" -o "$dst/.default.jws" - mv -f "$dst/.default.jws" "$dst/default.jws" - new=default.jws -fi - -shopt -s dotglob - -for jwk in $src/*.jwk; do - for hsh in $HASHES; do - thp=`jose jwk thp -i "$jwk" -a $hsh` - - if jose jwk use -i "$jwk" -r -u deriveKey; then - ln -sf "$jwk" "$dst/.$thp.jwk" - mv -f "$dst/.$thp.jwk" "$dst/$thp.jwk" - new="$new\n$thp.jwk" - elif jose jwk use -i "$jwk" -r -u sign; then - keys=("${sign[@]}" -s "$TMP" -k "$jwk") - jose jwk pub -s "${payl[@]}" \ - | jose jws sig -I- "${keys[@]}" -o "$dst/.$thp.jws" - mv -f "$dst/.$thp.jws" "$dst/$thp.jws" - new="$new\n$thp.jws" - fi - done -done - -for f in "$dst"/*; do - b=`basename "$f"` - echo -e "$new" | grep -q "^$b\$" || rm -f "$f" -done --- a/src/tangd.c +++ b/src/tangd.c @@ -28,6 +28,7 @@ #include #include +#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 \n", argv[0]); + fprintf(stderr, "Usage: %s \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@