Skip to content

Commit

Permalink
Add decryption authorization
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
dsommers committed Jun 30, 2022
1 parent e2059ee commit ca82648
Show file tree
Hide file tree
Showing 2 changed files with 118 additions and 19 deletions.
54 changes: 54 additions & 0 deletions doc/tang.8.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,60 @@ 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 need 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 authentication
directory.

ifdef::freebsd[]
# TODO
endif::[]
ifndef::freebsd[]
Edit the *ExecStart=* line in the *[email protected]* 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 works:

$ 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.
Expand Down
83 changes: 64 additions & 19 deletions src/tangd.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,18 @@
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>

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

struct tang_config {
char *jwkdir;
char *authorization_dir;
};

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

Expand Down Expand Up @@ -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 <jwkdir>\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 <jwkdir> [<authorization_dir>]\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)
Expand Down

0 comments on commit ca82648

Please sign in to comment.