Skip to content

Commit

Permalink
Move key handling to tang itself
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
sergio-correia committed Nov 29, 2020
1 parent eeb73dc commit c0f080e
Show file tree
Hide file tree
Showing 13 changed files with 28 additions and 170 deletions.
3 changes: 1 addition & 2 deletions README.md
Expand Up @@ -83,8 +83,7 @@ packages:
Tang is also capable of running on devices without systemd even for example
OpenWrt (see: [this PR](https://github.com/openwrt/packages/pull/5447)).
Instead of using systemd for socket activation you can use another daemon for
spawning services like xinetd. Without systemd watching Tang's database
directory you will have to make sure that key cache is generated properly.
spawning services like xinetd.

An example of configuration file for Tang using xinetd can be found in the
`units/` directory.
Expand Down
2 changes: 0 additions & 2 deletions meson.build
Expand Up @@ -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(
Expand Down
3 changes: 1 addition & 2 deletions src/meson.build
@@ -1,6 +1,6 @@
tangd = executable('tangd',
'http.h',
'http.c',
'keys.c',
'tangd.c',
dependencies: [jose, http_parser],
install: true,
Expand All @@ -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:
83 changes: 0 additions & 83 deletions src/tangd-update

This file was deleted.

52 changes: 21 additions & 31 deletions src/tangd.c
Expand Up @@ -28,6 +28,7 @@
#include <unistd.h>

#include <jose/jose.h>
#include "keys.h"

static void
str_cleanup(char **str)
Expand All @@ -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;
Expand All @@ -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"
Expand All @@ -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;
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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;
}

Expand Down
6 changes: 2 additions & 4 deletions tests/adv
Expand Up @@ -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

Expand Down Expand Up @@ -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)"
4 changes: 1 addition & 3 deletions tests/rec
Expand Up @@ -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`
Expand All @@ -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

Expand Down
21 changes: 0 additions & 21 deletions 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:
8 changes: 0 additions & 8 deletions units/tangd-keygen.service.in

This file was deleted.

4 changes: 0 additions & 4 deletions units/tangd-update.path.in

This file was deleted.

6 changes: 0 additions & 6 deletions units/tangd-update.service.in

This file was deleted.

2 changes: 1 addition & 1 deletion units/tangd.xinetd
@@ -1,7 +1,7 @@
service tangd
{
port = 80
server_args = /var/cache/tang
server_args = /var/db/tang
server = /usr/libexec/tangd
socket_type = stream
protocol = tcp
Expand Down
4 changes: 1 addition & 3 deletions 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@

0 comments on commit c0f080e

Please sign in to comment.