From 4ad7364d8d94f5718701c346498042a48d10ff61 Mon Sep 17 00:00:00 2001 From: David Sommerseth Date: Thu, 30 Jun 2022 20:00:59 +0200 Subject: [PATCH 1/2] Add decryption authorization The default behaviour of tangd is to reply correctly to all key recovery requests. In some deployments it may be needed to control when a key recovery should be allowed or not. This patch extends the tangd server with a very simple authorization method. When tangd is started with a second argument, this need to point at a directory to be used for authorizations. When a key recovery request occurs, the tangd server will check if this directory contains the filename of the client key fingerprint (thp/kid). If a file (which can be empty) exists with the name of a client fingerprint, the request is authorized and the decryption can be performed successfully. Signed-off-by: David Sommerseth --- doc/tang.8.adoc | 61 ++++++++++++++++++++++++++++++++++++ src/tangd.c | 83 ++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 125 insertions(+), 19 deletions(-) diff --git a/doc/tang.8.adoc b/doc/tang.8.adoc index 3031a13..c677cb1 100644 --- a/doc/tang.8.adoc +++ b/doc/tang.8.adoc @@ -135,6 +135,67 @@ of the database directory to all your servers. Make sure you don't forget the unadvertised keys! Then set up DNS round-robin so that clients will be load balanced across your servers. +== AUTHORIZATION CONTROL + +By default, tang will respond to any recovery requests. This can be +controlled on a per client key by adding a second argument to the *tang* +command line. This argument needs to be a path to an existing directory. +This directory should be empty on first setup; this directory is called +the 'authorization directory'. + +The concept is that an empty file named by the fingerprint of the client +key needs to be present in the authorization directory to allow a +decryption operation to happen. If this file is missing, the request +will be denied. + +An example: + +1. Configure tangd to use */var/db/tang/auth* as the authorization + directory. + +ifdef::freebsd[] + Edit the *tangd.rc* file in the */etc/rc.d* directory to add the authorization + directory to the *required_dirs* variable: + + required_dirs="${tangd_jwkdir} ${tangd_jwkdir}/auth" + + Then add this directory to the command line to be used for startup: + + command_args="${_tangd_listen_args} SYSTEM:\"${tangd_executable} ${tangd_jwkdir} ${tangd_jwkdir}/auth 2>> ${tangd_logfile} \" &" +endif::[] +ifndef::freebsd[] + Edit the *ExecStart=* line in the *tangd@.service* unit file by + extending it with */var/db/tang/auth*. Like this: + + ExecStart=/usr/libexec/tangd /var/db/tang /var/db/tang/authorized +endif::[] + +2. Encrypt some data: + + $ echo "One of my secrets" | clevis encrypt tang '{"url":"http://localhost"}' > enc.jwe + +3. Decrypting this will now fail: + + $ clevis decrypt < enc.jwe + Error communicating with the server http://127.0.0.1 + +4. Extract the client fingerprint (*kid*) value from *enc.jwe*: + + $ cut -d. -f1 t2.jwe | jose b64 dec -i - | jose fmt -j- -Og kid -Su- + EyIEfKd-_3UFMI5PSAp64UAAKeQ + +5. Authorize this client fingerprint to be used; on the tang server run this: + + # touch /var/db/tang/authorized/EyIEfKd-_3UFMI5PSAp64UAAKeQ + +6. Decrypting this will now work: + + $ clevis decrypt < enc.jwe + One of my secrets + +If the client fingerprint file in the authorization directory is removed on the +server, decryption is not possible. + == COMMANDS The Tang server provides no public commands. diff --git a/src/tangd.c b/src/tangd.c index 7da54e8..499def8 100644 --- a/src/tangd.c +++ b/src/tangd.c @@ -24,12 +24,18 @@ #include #include #include +#include #include #include #include #include "keys.h" +struct tang_config { + char *jwkdir; + char *authorization_dir; +}; + static void str_cleanup(char **str) { @@ -45,9 +51,9 @@ adv(enum http_method method, const char *path, const char *body, __attribute__((cleanup(str_cleanup))) char *thp = NULL; __attribute__((cleanup(cleanup_tang_keys_info))) struct tang_keys_info *tki = NULL; json_auto_t *jws = NULL; - const char *jwkdir = misc; + struct tang_config *tangcfg = misc; - tki = read_keys(jwkdir); + tki = read_keys(tangcfg->jwkdir); if (!tki || tki->m_keys_count == 0) { return http_reply(HTTP_STATUS_INTERNAL_SERVER_ERROR, NULL); } @@ -74,6 +80,20 @@ adv(enum http_method method, const char *path, const char *body, "\r\n%s", strlen(adv), adv); } + +static bool check_rec_authorization(const char *authdir, const char *thp) +{ + char auth_file[PATH_MAX+1]; + snprintf(auth_file, PATH_MAX, "%s/%s", authdir, thp); + + if (access(auth_file, F_OK | R_OK) != 0) { + fprintf(stderr, " ** WARNING ** Authorization check failed for %s\n", thp); + return false; + } + return true; +} + + static int rec(enum http_method method, const char *path, const char *body, regmatch_t matches[], void *misc) @@ -82,7 +102,7 @@ rec(enum http_method method, const char *path, const char *body, __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; - const char *jwkdir = misc; + struct tang_config *tangcfg = misc; json_auto_t *jwk = NULL; json_auto_t *req = NULL; json_auto_t *rep = NULL; @@ -113,7 +133,7 @@ rec(enum http_method method, const char *path, const char *body, /* * Parse and validate the server-side JWK */ - tki = read_keys(jwkdir); + tki = read_keys(tangcfg->jwkdir); if (!tki || tki->m_keys_count == 0) { return http_reply(HTTP_STATUS_INTERNAL_SERVER_ERROR, NULL); } @@ -126,6 +146,13 @@ rec(enum http_method method, const char *path, const char *body, if (!jwk) return http_reply(HTTP_STATUS_NOT_FOUND, NULL); + /* If a authorization directory is given, check if the client thp is authorized */ + if (tangcfg->authorization_dir + && !check_rec_authorization(tangcfg->authorization_dir, thp)) + { + return http_reply(HTTP_STATUS_FORBIDDEN, NULL); + } + if (!jose_jwk_prm(NULL, jwk, true, "deriveKey")) return http_reply(HTTP_STATUS_FORBIDDEN, NULL); @@ -165,33 +192,51 @@ static struct http_dispatch dispatch[] = { {} }; -int -main(int argc, char *argv[]) +static bool check_directory(const char *path) { - struct http_state state = { .dispatch = dispatch, .misc = argv[1] }; - struct http_parser parser = { .data = &state }; struct stat st = {}; - char req[4096] = {}; - size_t rcvd = 0; - int r = 0; - http_parser_init(&parser, HTTP_REQUEST); + if (stat(path, &st) != 0) { + fprintf(stderr, "Error calling stat() on path: %s: %m\n", path); + return false; + } - if (argc != 2) { - fprintf(stderr, "Usage: %s \n", argv[0]); - return EXIT_FAILURE; + if (!S_ISDIR(st.st_mode)) { + fprintf(stderr, "Path is not a directory: %s\n", path); + return false; } + return true; +} - if (stat(argv[1], &st) != 0) { - fprintf(stderr, "Error calling stat() on path: %s: %m\n", argv[1]); + +int +main(int argc, char *argv[]) +{ + if (argc <= 1 || argc >= 4) { + fprintf(stderr, "Usage: %s []\n", argv[0]); return EXIT_FAILURE; } - if (!S_ISDIR(st.st_mode)) { - fprintf(stderr, "Path is not a directory: %s\n", argv[1]); + if (!check_directory(argv[1])) { return EXIT_FAILURE; } + struct tang_config tangcfg = { .jwkdir = argv[1], .authorization_dir = NULL }; + if (argc == 3) { + if (!check_directory(argv[2])) { + return EXIT_FAILURE; + } + tangcfg.authorization_dir = argv[2]; + } + + struct http_state state = { .dispatch = dispatch, .misc = &tangcfg }; + struct http_parser parser = { .data = &state }; + char req[4096] = {}; + size_t rcvd = 0; + int r = 0; + + http_parser_init(&parser, HTTP_REQUEST); + for (;;) { r = read(STDIN_FILENO, &req[rcvd], sizeof(req) - rcvd - 1); if (r == 0) From 907ad364b42b2bd2244e264dae3f483095d4ac4f Mon Sep 17 00:00:00 2001 From: David Sommerseth Date: Mon, 8 Aug 2022 13:11:57 +0200 Subject: [PATCH 2/2] systemd: Make use of a configuration file for tangd args To enable the client authorization directory before this change, the main systemd service unit file needed to be modified. This is less ideal and we can use EnvironmentFile= feature in the unit file to read a file with environmental variables the tangd@.service will use for the command line. Signed-off-by: David Sommerseth --- doc/tang.8.adoc | 6 +++--- units/meson.build | 8 ++++++++ units/tangd.conf.in | 17 +++++++++++++++++ units/tangd@.service.in | 3 ++- 4 files changed, 30 insertions(+), 4 deletions(-) create mode 100644 units/tangd.conf.in diff --git a/doc/tang.8.adoc b/doc/tang.8.adoc index c677cb1..d349262 100644 --- a/doc/tang.8.adoc +++ b/doc/tang.8.adoc @@ -164,10 +164,10 @@ ifdef::freebsd[] command_args="${_tangd_listen_args} SYSTEM:\"${tangd_executable} ${tangd_jwkdir} ${tangd_jwkdir}/auth 2>> ${tangd_logfile} \" &" endif::[] ifndef::freebsd[] - Edit the *ExecStart=* line in the *tangd@.service* unit file by - extending it with */var/db/tang/auth*. Like this: + In the */etc/tangd/tangd.conf* configuration file, set the TANG_AUTHDIR to the + directory of the authorization files: - ExecStart=/usr/libexec/tangd /var/db/tang /var/db/tang/authorized + TANG_AUTHDIR=/var/db/tang/auth endif::[] 2. Encrypt some data: diff --git a/units/meson.build b/units/meson.build index eb40dfd..59dc8ae 100644 --- a/units/meson.build +++ b/units/meson.build @@ -3,6 +3,13 @@ tangd_service = configure_file( output: 'tangd@.service', configuration: data ) +tangd_conf = configure_file( + input: 'tangd.conf.in', + output: 'tangd.conf', + configuration: data, + install_dir: join_paths(sysconfdir, 'tangd'), +) + if host_machine.system() == 'freebsd' tangd_rc = configure_file( input: 'tangd.rc.in', @@ -14,6 +21,7 @@ if host_machine.system() == 'freebsd' else units += join_paths(meson.current_source_dir(), 'tangd.socket') units += tangd_service + units += tangd_conf endif # vim:set ts=2 sw=2 et: diff --git a/units/tangd.conf.in b/units/tangd.conf.in new file mode 100644 index 0000000..92a1d22 --- /dev/null +++ b/units/tangd.conf.in @@ -0,0 +1,17 @@ +# +# tangd configuration file +# +# +# This file is read by the tangd@.service systemd unit file at startup +# + +# Base directory for the JSON Web Key storage +# +TANG_JWKDIR=@jwkdir@ + +# Directory for Tang authorization files +# By enabling adding this directory, clients must have their fingerprints +# registered in this directory to be able to decrypt their secrets. +# See the tang(8) man page for details. +# +# TANG_AUTHDIR=@jwkdir@/auth diff --git a/units/tangd@.service.in b/units/tangd@.service.in index aeb2dc1..6bd47b2 100644 --- a/units/tangd@.service.in +++ b/units/tangd@.service.in @@ -5,5 +5,6 @@ Description=Tang Server StandardInput=socket StandardOutput=socket StandardError=journal -ExecStart=@libexecdir@/tangd @jwkdir@ +EnvironmentFile=@sysconfdir@/tangd/tangd.conf +ExecStart=@libexecdir@/tangd $TANG_JWKDIR $TANG_AUTHDIR User=@user@