Skip to content

Commit

Permalink
Include parameter to indicate endpoint (#117)
Browse files Browse the repository at this point in the history
This change allows specifying -e option, so that
endpoint parameter is used. This parameter gives
the possibility to change URL where Tang listens,
so that endpoint provided is prepended between
host port information and advertisement/recovery
suffix (rec/adv).
Without endpoint, advertisement URL is:
http://localhost:port/adv
Meanwhile, if using endpoint (-e this/is/endpoint),
advertisement URL is:
http://localhost:port/this/is/endpoint/adv
For more information, check man page

Resolves: #116

Signed-off-by: Sergio Arroutbi <[email protected]>
  • Loading branch information
sarroutbi authored Feb 12, 2024
1 parent 05ac375 commit 4b7656b
Show file tree
Hide file tree
Showing 11 changed files with 248 additions and 32 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
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
33 changes: 28 additions & 5 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"

#define 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,7 +188,7 @@ rec(http_method_t method, const char *path, const char *body,
"\r\n%s", strlen(enc), enc);
}

static struct http_dispatch dispatch[] = {
static struct http_dispatch s_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_-]+)$" },
Expand All @@ -196,7 +200,7 @@ static struct http_dispatch dispatch[] = {
static int
process_request(const char *jwkdir, int in_fileno)
{
struct http_state state = { .dispatch = dispatch, .misc = (char*)jwkdir };
struct http_state state = { .dispatch = s_dispatch, .misc = (char*)jwkdir };
http_parser_t parser;
struct stat st = {};
char req[4096] = {};
Expand Down Expand Up @@ -244,9 +248,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 +265,9 @@ main(int argc, char *argv[])
case 'p':
port = atoi(optarg);
break;
case 'e':
endpoint = optarg;
break;
case 'l':
listen = 1;
break;
Expand All @@ -272,9 +280,24 @@ main(int argc, char *argv[])
}
jwkdir = argv[optind++];

char adv_thp_endpoint[MAX_URL] = {};
char adv_endpoint[MAX_URL] = {};
char rec_endpoint[MAX_URL] = {};
if (endpoint != NULL) {
char *endpoint_ptr = (char*)endpoint;
while (*endpoint_ptr == '/') {
endpoint_ptr++;
}
snprintf(adv_thp_endpoint, MAX_URL, "^/%s/+adv/+([0-9A-Za-z_-]+)$", endpoint_ptr);
snprintf(adv_endpoint, MAX_URL, "^/%s/+adv/*$", endpoint_ptr);
snprintf(rec_endpoint, MAX_URL, "^/%s/+rec/+([0-9A-Za-z_-]+)$", endpoint_ptr);
s_dispatch[0].re = adv_thp_endpoint;
s_dispatch[1].re = adv_endpoint;
s_dispatch[2].re = rec_endpoint;
}
if (listen == 0) { /* process one-shot query from stdin */
return process_request(jwkdir, STDIN_FILENO);
return process_request(jwkdir, STDIN_FILENO);
} else { /* listen and process all incoming connections */
return run_service(jwkdir, port, process_request);
return run_service(jwkdir, port, 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}"
}
33 changes: 33 additions & 0 deletions tests/adv-socat-endpoint
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/bin/sh -ex
#
# Copyright (c) 2023 Red Hat, Inc.
# Author: Sergio Arroutbi <[email protected]>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#

. adv

sanity_check

adv_startup

port=$(random_port)
export PORT=$((port+3))
export ENDPOINT="/api/dee-hms"
start_server_endpoint "${PORT}" "${ENDPOINT}"
export PID=$!
wait_for_port ${PORT}

adv_second_phase
31 changes: 31 additions & 0 deletions tests/adv-standalone-endpoint
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/bin/sh -ex
#
# Copyright (c) 2023 Red Hat, Inc.
# Author: Sergio Arroutbi <[email protected]>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#

. adv

adv_startup

port=$(random_port)
export PORT=$((port+1))
export ENDPOINT="/api/dee-hms"
start_standalone_server_endpoint "${PORT}" "${ENDPOINT}"
export PID=$!
wait_for_port ${PORT}

adv_second_phase
15 changes: 14 additions & 1 deletion tests/helpers
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,12 @@ random_port() {
if [ -n "${TANG_BSD}" ]; then
jot -r 1 1024 65536
else
shuf -i 1024-65536 -n 1
if test -f /dev/urandom;
then
shuf -i 1024-65535 -n 1 --random-file=/dev/urandom
else
shuf -i 1024-65535 -n 1
fi
fi
}

Expand Down Expand Up @@ -62,10 +67,18 @@ start_server() {
"${SOCAT}" TCP-LISTEN:"${1}",bind=127.0.0.1,fork SYSTEM:"${VALGRIND} tangd ${TMP}/db" &
}

start_server_endpoint() {
"${SOCAT}" TCP-LISTEN:"${1}",bind=127.0.0.1,fork SYSTEM:"${VALGRIND} tangd ${TMP}/db -e ${ENDPOINT}" &
}

start_standalone_server() {
${VALGRIND} tangd -p ${1} -l ${TMP}/db &
}

start_standalone_server_endpoint() {
${VALGRIND} tangd -p ${1} -l ${TMP}/db -e ${2} &
}

on_exit() {
if [ "${PID}" ]; then
kill "${PID}" || true
Expand Down
4 changes: 4 additions & 0 deletions tests/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,13 @@ if socat.found()
endif

test('adv-standalone', find_program('adv-standalone'), env: env, timeout: 360)
test('adv-standalone-endpoint', find_program('adv-standalone-endpoint'), env: env, timeout: 360)
test('adv-socat', find_program('adv-socat'), env: env, timeout: 360)
test('adv-socat-endpoint', find_program('adv-socat-endpoint'), env: env, timeout: 360)
test('rec-standalone', find_program('rec-standalone'), env: env, timeout: 360)
test('rec-standalone-endpoint', find_program('rec-standalone-endpoint'), env: env, timeout: 360)
test('rec-socat', find_program('rec-socat'), env: env, timeout: 360)
test('rec-socat-endpoint', find_program('rec-socat-endpoint'), env: env, timeout: 360)
test('test-keys', test_keys, env: env, timeout: 360)

# vim:set ts=2 sw=2 et:
14 changes: 14 additions & 0 deletions tests/rec
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,17 @@ rec_second_phase () {
http://127.0.0.1:$PORT/rec/${exc_kid} < $TMP/exc.pub.jwk`
[ "$good" = "$test" ]
}

rec_second_phase_endpoint () {
# Make sure that GET fails
curl -sf http://127.0.0.1:$PORT/$ENDPOINT/rec && expected_fail
curl -sf http://127.0.0.1:$PORT/$ENDPOINT/rec/ && expected_fail

# Make a recovery request (NOTE: this is insecure! Don't do this in real code!)
good=`jose jwk exc -i '{"alg":"ECMR","key_ops":["deriveKey"]}' -l $TMP/exc.jwk -r $TMP/db/exc.jwk`
test=`curl -sf -X POST \
-H "Content-Type: application/jwk+json" \
--data-binary @- \
http://127.0.0.1:$PORT/$ENDPOINT/rec/${exc_kid} < $TMP/exc.pub.jwk`
[ "$good" = "$test" ]
}
Loading

0 comments on commit 4b7656b

Please sign in to comment.