Skip to content

Commit

Permalink
Include parameter to indicate endpoint
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 committed Feb 12, 2024
1 parent df3cc46 commit 3335e5f
Show file tree
Hide file tree
Showing 13 changed files with 265 additions and 49 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
14 changes: 7 additions & 7 deletions src/socket.c
Original file line number Diff line number Diff line change
Expand Up @@ -146,9 +146,10 @@ static int listen_port(socket_list **slist, int port)
return r;
}

static void spawn_process(int fd, const char *jwkdir,
static void spawn_process(int fd, const char* jwkdir,
process_request_func pfunc,
socket_list *slist)
socket_list *slist,
const char* endpoint)
{
pid_t pid;
socket_list *ptr;
Expand All @@ -166,8 +167,7 @@ static void spawn_process(int fd, const char *jwkdir,
}

close(fd);

pfunc(jwkdir, STDOUT_FILENO);
pfunc(jwkdir, STDOUT_FILENO, endpoint);
free_socket_list(slist);
exit(0);
} else if (pid == -1) {
Expand All @@ -183,7 +183,8 @@ static void handle_child(int sig)
while ((waitpid(-1, &status, WNOHANG)) > 0);
}

int run_service(const char *jwkdir, int port, process_request_func pfunc)
int run_service(const char *jwkdir, int port, process_request_func pfunc,
const char *endpoint)
{
socket_list *slist, *ptr;
int r, n = 0, accept_fd;
Expand Down Expand Up @@ -233,8 +234,7 @@ int run_service(const char *jwkdir, int port, process_request_func pfunc)
perror("accept");
continue;
}

spawn_process(accept_fd, jwkdir, pfunc, slist);
spawn_process(accept_fd, jwkdir, pfunc, slist, endpoint);
}
}

Expand Down
5 changes: 2 additions & 3 deletions src/socket.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
* 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 int (*process_request_func)(const char *jwkdir, int in_fileno, const char *endpoint);

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

int run_service(const char *jwkdir, int port, process_request_func);
int run_service(const char *jwkdir, int port, process_request_func, const char *endpoint);
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
48 changes: 36 additions & 12 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,35 @@ rec(http_method_t 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 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_-]+)$"},
{}
};

static int
process_request(const char *jwkdir, int in_fileno)
process_request(const char *jwkdir, int in_fileno, const char *endpoint)
{
struct http_state state = { .dispatch = dispatch, .misc = (char*)jwkdir };
char adv_endpoint[MAX_URL];
char adv_thp_endpoint[MAX_URL];
char rec_endpoint[MAX_URL];
if (endpoint != NULL) {
char *endpoint_ptr = (char*)endpoint;
while (*endpoint_ptr == '/') {
endpoint_ptr++;
}
snprintf(adv_endpoint, MAX_URL, "^/%s/+adv/+([0-9A-Za-z_-]+)$", endpoint_ptr);
snprintf(adv_thp_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_endpoint;
s_dispatch[1].re = adv_thp_endpoint;
s_dispatch[2].re = rec_endpoint;
}

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 +264,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 +281,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 +297,8 @@ main(int argc, char *argv[])
jwkdir = argv[optind++];

if (listen == 0) { /* process one-shot query from stdin */
return process_request(jwkdir, STDIN_FILENO);
return process_request(jwkdir, STDIN_FILENO, endpoint);
} else { /* listen and process all incoming connections */
return run_service(jwkdir, port, process_request);
return run_service(jwkdir, port, process_request, endpoint);
}
}
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
Loading

0 comments on commit 3335e5f

Please sign in to comment.