Skip to content

Commit

Permalink
Include parameter to indicate URL prefix
Browse files Browse the repository at this point in the history
Resolves: #116

Signed-off-by: Sergio Arroutbi <[email protected]>
  • Loading branch information
sarroutbi committed May 18, 2023
1 parent 0867603 commit 54ce3b0
Show file tree
Hide file tree
Showing 11 changed files with 213 additions and 56 deletions.
21 changes: 21 additions & 0 deletions doc/tang.8.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,27 @@ be changed with the *-p* option.

tang -l -p 9090

== ENDPOINT

The Tang server can be provided an endpoint. This endpoint will act as a prefix
for the URL to be accessed by the client. This endpoint can be specified with
the *-e* option.

tang -l -p 9090 -e this/is/an/endpoint

When endpoint is specified, the endpoint will be prepended to the normal adv/rec
URL. If no endpoint is provided, and assuming port 9090 is used, Tang server
will listen on next URLs:

http://localhost:9090/adv (GET)
http://localhost:9090/rec (POST)

If endpoint is provided, and assuming endpoint is /this/is/an/endpoint/, and
assuming also port 9090 is used, Tang server will listen on next URLs:

http://localhost:9090/this/is/an/endpoint/adv (GET)
http://localhost:9090/this/is/an/endpoint/rec (POST)

== KEY ROTATION

In order to preserve the security of the system over the long run, you need to
Expand Down
27 changes: 15 additions & 12 deletions src/socket.c
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,8 @@ static int listen_port(socket_list **slist, int port)
return r;
}

static void spawn_process(int fd, const char *jwkdir,
process_request_func pfunc,
static void spawn_process(process_request_func pfunc,
process_request_func_args_t pfunc_args,
socket_list *slist)
{
pid_t pid;
Expand All @@ -159,21 +159,23 @@ static void spawn_process(int fd, const char *jwkdir,
close(ptr->s);
}
/* Ensure that both stdout and stdin are set */
if (dup2(fd, STDOUT_FILENO) < 0) {
if (dup2(pfunc_args.fileno, STDOUT_FILENO) < 0) {
perror("dup2");
close(fd);
close(pfunc_args.fileno);
return;
}

close(fd);
close(pfunc_args.fileno);

pfunc(jwkdir, STDOUT_FILENO);
process_request_func_args_t pf =
{ pfunc_args.jwkdir, STDOUT_FILENO, pfunc_args.endpoint };
pfunc(pf);
free_socket_list(slist);
exit(0);
} else if (pid == -1) {
perror("fork failed");
}
close(fd);
close(pfunc_args.fileno);
}

static void handle_child(int sig)
Expand All @@ -184,7 +186,7 @@ static void handle_child(int sig)
while ((pid = waitpid(-1, &status, WNOHANG)) > 0);
}

int run_service(const char *jwkdir, int port, process_request_func pfunc)
int run_service(run_service_args_t rserv, process_request_func pfunc)
{
socket_list *slist, *ptr;
int r, n = 0, accept_fd;
Expand All @@ -199,9 +201,9 @@ int run_service(const char *jwkdir, int port, process_request_func pfunc)
new_action.sa_flags = 0;
sigaction(SIGCHLD, &new_action, NULL);

r = listen_port(&slist, port);
r = listen_port(&slist, rserv.port);
if (r < 0) {
fprintf(stderr, "Could not listen port (%d)\n", port);
fprintf(stderr, "Could not listen port (%d)\n", rserv.port);
return -1;
}

Expand Down Expand Up @@ -234,8 +236,9 @@ int run_service(const char *jwkdir, int port, process_request_func pfunc)
perror("accept");
continue;
}

spawn_process(accept_fd, jwkdir, pfunc, slist);
process_request_func_args_t pr_args =
{rserv.jwkdir, accept_fd,rserv.endpoint};
spawn_process(pfunc, pr_args, slist);
}
}

Expand Down
15 changes: 13 additions & 2 deletions src/socket.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,18 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
typedef struct process_request_func_args {
const char* jwkdir;
const int fileno;
const char* endpoint;
} process_request_func_args_t;

typedef int (*process_request_func)(const char *jwkdir, int in_fileno);
typedef int (*process_request_func)(const process_request_func_args_t);

int run_service(const char *jwkdir, int port, process_request_func);
typedef struct run_service_args {
const char* jwkdir;
const int port;
const char* endpoint;
} run_service_args_t;

int run_service(const run_service_args_t, process_request_func);
15 changes: 12 additions & 3 deletions src/tang-show-keys
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,23 @@

set -e

if [ $# -gt 1 ]; then
echo "Usage: $0 [<port>]" >&2
if [ $# -gt 2 ]; then
echo "Usage: $0 [<port>] [<endpoint>]" >&2
exit 1
fi

port=${1-80}

adv=$(curl -sSf "localhost:$port/adv")
if test -n "$2"; then
first_letter=$(printf %.1s "$2")
if [ "${first_letter}" = "/" ]; then
adv=$(curl -sSf "localhost:$port$2/adv")
else
adv=$(curl -sSf "localhost:$port/$2/adv")
fi
else
adv=$(curl -sSf "localhost:$port/adv")
fi

THP_DEFAULT_HASH=S256 # SHA-256.
jose fmt --json "${adv}" -g payload -y -o- \
Expand Down
60 changes: 44 additions & 16 deletions src/tangd.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,11 @@
#include "keys.h"
#include "socket.h"

const unsigned int MAX_URL = 256;

static const struct option long_options[] = {
{"port", 1, 0, 'p'},
{"endpoint", 1, 0, 'e'},
{"listen", 0, 0, 'l'},
{"version", 0, 0, 'v'},
{"help", 0, 0, 'h'},
Expand All @@ -45,6 +48,7 @@ print_help(const char *name)
{
fprintf(stderr, "Usage: %s [OPTIONS] <jwkdir>\n", name);
fprintf(stderr, " -p, --port=PORT Specify the port to listen (default 9090)\n");
fprintf(stderr, " -e, --endpoint=ENDPOINT Specify endpoint to listen (empty by default)\n");
fprintf(stderr, " -l, --listen Run as a service and wait for connections\n");
fprintf(stderr, " -v, --version Display program version\n");
fprintf(stderr, " -h, --help Show this help message\n");
Expand Down Expand Up @@ -184,19 +188,37 @@ rec(enum http_method method, const char *path, const char *body,
"\r\n%s", strlen(enc), enc);
}

static struct http_dispatch dispatch[] = {
{ adv, 1 << HTTP_GET, 2, "^/+adv/+([0-9A-Za-z_-]+)$" },
{ adv, 1 << HTTP_GET, 2, "^/+adv/*$" },
{ rec, 1 << HTTP_POST, 2, "^/+rec/+([0-9A-Za-z_-]+)$" },
{}
};

#define DEFAULT_PORT 9090

static int
process_request(const char *jwkdir, int in_fileno)
process_request(const process_request_func_args_t pr_args)
{
struct http_state state = { .dispatch = dispatch, .misc = (char*)jwkdir };
char adv_endpoint[MAX_URL];
char adv2_endpoint[MAX_URL];
char rec_endpoint[MAX_URL];
if (pr_args.endpoint != NULL) {
if (pr_args.endpoint[0] == '/') {
snprintf(adv_endpoint, MAX_URL, "^%s/+adv/+([0-9A-Za-z_-]+)$", pr_args.endpoint);
snprintf(adv2_endpoint, MAX_URL, "^%s/+adv/*$", pr_args.endpoint);
snprintf(rec_endpoint, MAX_URL, "^%s/+rec/+([0-9A-Za-z_-]+)$", pr_args.endpoint);
} else {
snprintf(adv_endpoint, MAX_URL, "^/%s/+adv/+([0-9A-Za-z_-]+)$", pr_args.endpoint);
snprintf(adv2_endpoint, MAX_URL, "^/%s/+adv/*$", pr_args.endpoint);
snprintf(rec_endpoint, MAX_URL, "^/%s/+rec/+([0-9A-Za-z_-]+)$", pr_args.endpoint);
}
} else {
strncpy(adv_endpoint, "^/+adv/+([0-9A-Za-z_-]+)$", MAX_URL);
strncpy(adv2_endpoint, "^/+adv/*$", MAX_URL);
strncpy(rec_endpoint, "^/+rec/+([0-9A-Za-z_-]+)$", MAX_URL);
}
struct http_dispatch dispatch[] = {
{ adv, 1 << HTTP_GET, 2, adv_endpoint },
{ adv, 1 << HTTP_GET, 2, adv2_endpoint },
{ rec, 1 << HTTP_POST, 2, rec_endpoint },
{}
};

struct http_state state = { .dispatch = dispatch, .misc = (char*)pr_args.jwkdir };
struct http_parser parser = { .data = &state };
struct stat st = {};
char req[4096] = {};
Expand All @@ -205,18 +227,18 @@ process_request(const char *jwkdir, int in_fileno)

http_parser_init(&parser, HTTP_REQUEST);

if (stat(jwkdir, &st) != 0) {
fprintf(stderr, "Error calling stat() on path: %s: %m\n", jwkdir);
if (stat(pr_args.jwkdir, &st) != 0) {
fprintf(stderr, "Error calling stat() on path: %s: %m\n", pr_args.jwkdir);
return EXIT_FAILURE;
}

if (!S_ISDIR(st.st_mode)) {
fprintf(stderr, "Path is not a directory: %s\n", jwkdir);
fprintf(stderr, "Path is not a directory: %s\n", pr_args.jwkdir);
return EXIT_FAILURE;
}

for (;;) {
r = read(in_fileno, &req[rcvd], sizeof(req) - rcvd - 1);
r = read(pr_args.fileno, &req[rcvd], sizeof(req) - rcvd - 1);
if (r == 0)
return rcvd > 0 ? EXIT_FAILURE : EXIT_SUCCESS;
if (r < 0)
Expand Down Expand Up @@ -244,9 +266,10 @@ main(int argc, char *argv[])
int listen = 0;
int port = DEFAULT_PORT;
const char *jwkdir = NULL;
const char *endpoint = NULL;

while (1) {
int c = getopt_long(argc, argv, "lp:vh", long_options, NULL);
int c = getopt_long(argc, argv, "lp:e:vh", long_options, NULL);
if (c == -1)
break;

Expand All @@ -260,6 +283,9 @@ main(int argc, char *argv[])
case 'p':
port = atoi(optarg);
break;
case 'e':
endpoint = optarg;
break;
case 'l':
listen = 1;
break;
Expand All @@ -273,8 +299,10 @@ main(int argc, char *argv[])
jwkdir = argv[optind++];

if (listen == 0) { /* process one-shot query from stdin */
return process_request(jwkdir, STDIN_FILENO);
process_request_func_args_t pr = {jwkdir, STDIN_FILENO, endpoint};
return process_request(pr);
} else { /* listen and process all incoming connections */
return run_service(jwkdir, port, process_request);
run_service_args_t rs = {jwkdir, port, endpoint};
return run_service(rs, process_request);
}
}
46 changes: 23 additions & 23 deletions tests/adv
Original file line number Diff line number Diff line change
Expand Up @@ -36,47 +36,47 @@ adv_startup () {

adv_second_phase () {
# Make sure requests on the root fail
fetch / && expected_fail
fetch "${ENDPOINT}"/ && expected_fail

# The request should fail (404) for non-signature key IDs
fetch /adv/`jose jwk thp -i $TMP/db/exc.jwk` && expected_fail
fetch /adv/`jose jwk thp -a S512 -i $TMP/db/exc.jwk` && expected_fail
fetch "${ENDPOINT}"/adv/`jose jwk thp -i $TMP/db/exc.jwk` && expected_fail
fetch "${ENDPOINT}"/adv/`jose jwk thp -a S512 -i $TMP/db/exc.jwk` && expected_fail

# The default advertisement fetch should succeed and pass verification
fetch /adv
fetch /adv | ver $TMP/db/sig.jwk
fetch /adv/ | ver $TMP/db/sig.jwk
fetch "${ENDPOINT}"/adv
fetch "${ENDPOINT}"/adv | ver $TMP/db/sig.jwk
fetch "${ENDPOINT}"/adv/ | ver $TMP/db/sig.jwk

# Fetching by any thumbprint should work
fetch /adv/`jose jwk thp -i $TMP/db/sig.jwk` | ver $TMP/db/sig.jwk
fetch /adv/`jose jwk thp -a S512 -i $TMP/db/sig.jwk` | ver $TMP/db/sig.jwk
fetch "${ENDPOINT}"/adv/`jose jwk thp -i $TMP/db/sig.jwk` | ver $TMP/db/sig.jwk
fetch "${ENDPOINT}"/adv/`jose jwk thp -a S512 -i $TMP/db/sig.jwk` | ver $TMP/db/sig.jwk

# Requesting an adv by an advertised key ID should't be signed by hidden keys
fetch /adv/`jose jwk thp -i $TMP/db/sig.jwk` | ver $TMP/db/.sig.jwk && expected_fail
fetch /adv/`jose jwk thp -i $TMP/db/sig.jwk` | ver $TMP/db/.oth.jwk && expected_fail
fetch "${ENDPOINT}"/adv/`jose jwk thp -i $TMP/db/sig.jwk` | ver $TMP/db/.sig.jwk && expected_fail
fetch "${ENDPOINT}"/adv/`jose jwk thp -i $TMP/db/sig.jwk` | ver $TMP/db/.oth.jwk && expected_fail

# Verify that the default advertisement is not signed with hidden signature keys
fetch /adv/ | ver $TMP/db/.oth.jwk && expected_fail
fetch /adv/ | ver $TMP/db/.sig.jwk && expected_fail
fetch "${ENDPOINT}"/adv/ | ver $TMP/db/.oth.jwk && expected_fail
fetch "${ENDPOINT}"/adv/ | ver $TMP/db/.sig.jwk && expected_fail

# A private key advertisement is signed by all advertised keys and the requested private key
fetch /adv/`jose jwk thp -i $TMP/db/.sig.jwk` | ver $TMP/db/sig.jwk
fetch /adv/`jose jwk thp -i $TMP/db/.sig.jwk` | ver $TMP/db/.sig.jwk
fetch /adv/`jose jwk thp -i $TMP/db/.sig.jwk` | ver $TMP/db/.oth.jwk && expected_fail
fetch "${ENDPOINT}"/adv/`jose jwk thp -i $TMP/db/.sig.jwk` | ver $TMP/db/sig.jwk
fetch "${ENDPOINT}"/adv/`jose jwk thp -i $TMP/db/.sig.jwk` | ver $TMP/db/.sig.jwk
fetch "${ENDPOINT}"/adv/`jose jwk thp -i $TMP/db/.sig.jwk` | ver $TMP/db/.oth.jwk && expected_fail

# Verify that the advertisements contain the cty parameter
fetch /adv | jose fmt -j- -Og protected -SyOg cty -Sq "jwk-set+json" -E
fetch /adv/`jose jwk thp -i $TMP/db/.sig.jwk` \
fetch "${ENDPOINT}"/adv | jose fmt -j- -Og protected -SyOg cty -Sq "jwk-set+json" -E
fetch "${ENDPOINT}"/adv/`jose jwk thp -i $TMP/db/.sig.jwk` \
| jose fmt -j- -Og signatures -A \
-g 0 -Og protected -SyOg cty -Sq "jwk-set+json" -EUUUUU \
-g 1 -Og protected -SyOg cty -Sq "jwk-set+json" -EUUUUU

THP_DEFAULT_HASH=S256 # SHA-256.
test "$(tang-show-keys $PORT)" = "$(jose jwk thp -a "${THP_DEFAULT_HASH}" -i $TMP/db/sig.jwk)"
test "$(tang-show-keys $PORT $ENDPOINT)" = "$(jose jwk thp -a "${THP_DEFAULT_HASH}" -i $TMP/db/sig.jwk)"

# Check that new keys will be created if none exist.
rm -rf "${TMP}/db" && mkdir -p "${TMP}/db"
fetch /adv
fetch "${ENDPOINT}"/adv

# Now let's make sure the new keys were named using our default thumbprint
# hash and then rotate them and check if we still create new keys.
Expand All @@ -88,7 +88,7 @@ adv_second_phase () {
mv -f -- "${k}" ".${k}"
done
cd -
fetch /adv
fetch "${ENDPOINT}"/adv

# Lets's now test with multiple pairs of keys.
for i in 1 2 3 4 5 6 7 8 9; do
Expand All @@ -103,12 +103,12 @@ adv_second_phase () {
done

# Verify the advertisement is correct.
validate "$(fetch /adv)"
validate "$(fetch "${ENDPOINT}"/adv)"

# And make sure we can fetch an adv by its thumbprint.
for jwk in "${TMP}"/db/other-sig-*.jwk; do
for alg in $(jose alg -k hash); do
fetch /adv/"$(jose jwk thp -a "${alg}" -i "${jwk}")" | ver "${jwk}"
fetch "${ENDPOINT}"/adv/"$(jose jwk thp -a "${alg}" -i "${jwk}")" | ver "${jwk}"
done
done

Expand All @@ -130,5 +130,5 @@ adv_second_phase () {
valid_key_perm "${jwk}"
done
[ -z "${thp}" ] && die "There should be valid keys after rotation"
test "$(tang-show-keys $PORT)" = "${thp}"
test "$(tang-show-keys $PORT $ENDPOINT)" = "${thp}"
}
Loading

0 comments on commit 54ce3b0

Please sign in to comment.