diff --git a/doc/tang.8.adoc b/doc/tang.8.adoc index 3031a13..d349262 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[] + In the */etc/tangd/tangd.conf* configuration file, set the TANG_AUTHDIR to the + directory of the authorization files: + + TANG_AUTHDIR=/var/db/tang/auth +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) 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@