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

Include parameter to indicate endpoint #117

Merged
Merged
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
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
Loading