Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add decryption authorization for clevis clients #92

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions doc/tang.8.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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.
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>
dsommers marked this conversation as resolved.
Show resolved Hide resolved
#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
8 changes: 8 additions & 0 deletions units/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@ tangd_service = configure_file(
output: '[email protected]',
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',
Expand All @@ -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:
17 changes: 17 additions & 0 deletions units/tangd.conf.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#
# tangd configuration file
#
#
# This file is read by the [email protected] 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
3 changes: 2 additions & 1 deletion units/[email protected]
Original file line number Diff line number Diff line change
Expand Up @@ -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@