Skip to content
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
38 changes: 35 additions & 3 deletions src/tangd.c
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,13 @@ rec(http_method_t method, const char *path, const char *body,
if (json_object_set_new(rep, "key_ops", json_pack("[s]", "deriveKey")) < 0)
return http_reply(HTTP_STATUS_INTERNAL_SERVER_ERROR, NULL);

json_auto_t *pub = json_deep_copy(jwk);
if (!pub || !jose_jwk_pub(NULL, pub))
return http_reply(HTTP_STATUS_INTERNAL_SERVER_ERROR, NULL);

if (json_object_set(rep, "tang_pub", pub) < 0)
return http_reply(HTTP_STATUS_INTERNAL_SERVER_ERROR, NULL);

enc = json_dumps(rep, JSON_SORT_KEYS | JSON_COMPACT);
if (!enc)
return http_reply(HTTP_STATUS_INTERNAL_SERVER_ERROR, NULL);
Expand All @@ -188,10 +195,32 @@ rec(http_method_t method, const char *path, const char *body,
"\r\n%s", strlen(enc), enc);
}

static int
tang_version(http_method_t method, const char *path, const char *body,
regmatch_t matches[], void *misc)
{
__attribute__((cleanup(str_cleanup))) char *out = NULL;
json_auto_t *ver = json_pack("{s:s, s:{s:b}}",
"tang_version", VERSION,
"features", "tang_pub", 1);
if (!ver)
return http_reply(HTTP_STATUS_INTERNAL_SERVER_ERROR, NULL);

out = json_dumps(ver, JSON_SORT_KEYS | JSON_COMPACT);
if (!out)
return http_reply(HTTP_STATUS_INTERNAL_SERVER_ERROR, NULL);

return http_reply(HTTP_STATUS_OK,
"Content-Type: application/json\r\n"
"Content-Length: %zu\r\n"
"\r\n%s", strlen(out), out);
}

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_-]+)$" },
{ 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_-]+)$" },
{ tang_version, 1 << HTTP_GET, 1, "^/+version/*$" },
{}
};

Expand Down Expand Up @@ -327,6 +356,7 @@ main(int argc, char *argv[])
char adv_thp_endpoint[MAX_URL] = {};
char adv_endpoint[MAX_URL] = {};
char rec_endpoint[MAX_URL] = {};
char ver_endpoint[MAX_URL] = {};
if (endpoint != NULL) {
char *endpoint_ptr = (char*)endpoint;
while (*endpoint_ptr == '/') {
Expand All @@ -335,9 +365,11 @@ main(int argc, char *argv[])
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);
snprintf(ver_endpoint, MAX_URL, "^/%s/+version/*$", endpoint_ptr);
s_dispatch[0].re = adv_thp_endpoint;
s_dispatch[1].re = adv_endpoint;
s_dispatch[2].re = rec_endpoint;
s_dispatch[3].re = ver_endpoint;
}
if (listen == 0) { /* process one-shot query from stdin */
return process_request(jwkdir, STDIN_FILENO);
Expand Down
5 changes: 5 additions & 0 deletions tests/adv
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ adv_second_phase () {
fetch "${ENDPOINT}"/adv | ver $TMP/db/sig.jwk
fetch "${ENDPOINT}"/adv/ | ver $TMP/db/sig.jwk

# Verify /version endpoint reports tang_pub capability
ver_resp=$(fetch "${ENDPOINT}"/version)
printf '%s' "$ver_resp" | jose fmt -j- -Og tang_version -u- >/dev/null
printf '%s' "$ver_resp" | jose fmt -j- -Og features -Og tang_pub >/dev/null

# Fetching by any thumbprint should work
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
Expand Down
24 changes: 22 additions & 2 deletions tests/rec
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,21 @@ rec_second_phase () {

# 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 \
full_resp=`curl -sf -X POST \
-H "Content-Type: application/jwk+json" \
--data-binary @- \
http://127.0.0.1:$PORT/rec/${exc_kid} < $TMP/exc.pub.jwk`

# Strip tang_pub from response and verify the exchange result
test=`printf '%s' "$full_resp" | jose fmt -j- -Od tang_pub -o-`
[ "$good" = "$test" ]

# Verify tang_pub is present and contains no private material
printf '%s' "$full_resp" | jose fmt -j- -Og tang_pub -o- > $TMP/tang_pub.jwk
! jose fmt -j $TMP/tang_pub.jwk -Og d 2>/dev/null

# Verify tang_pub thumbprint matches the requested key identifier
[ "$(jose jwk thp -i $TMP/tang_pub.jwk)" = "$exc_kid" ]
}

rec_second_phase_endpoint () {
Expand All @@ -59,9 +69,19 @@ rec_second_phase_endpoint () {

# 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 \
full_resp=`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`

# Strip tang_pub from response and verify the exchange result
test=`printf '%s' "$full_resp" | jose fmt -j- -Od tang_pub -o-`
[ "$good" = "$test" ]

# Verify tang_pub is present and contains no private material
printf '%s' "$full_resp" | jose fmt -j- -Og tang_pub -o- > $TMP/tang_pub.jwk
! jose fmt -j $TMP/tang_pub.jwk -Og d 2>/dev/null

# Verify tang_pub thumbprint matches the requested key identifier
[ "$(jose jwk thp -i $TMP/tang_pub.jwk)" = "$exc_kid" ]
}
78 changes: 78 additions & 0 deletions tests/verify-tang-pub
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#!/bin/sh -e
# vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80:
#
# Verify that a running Tang server returns tang_pub in /rec/$kid responses.
#
# Usage: verify-tang-pub [jwkdir] [url]
# jwkdir: path to Tang key directory (default: /var/db/tang)
# url: Tang server URL (default: http://localhost)

JWKDIR="${1:-/var/db/tang}"
URL="${2:-http://localhost}"

if [ ! -d "$JWKDIR" ]; then
echo "Error: key directory $JWKDIR does not exist" >&2
exit 1
fi

exc_file=""
for f in "$JWKDIR"/*.jwk; do
[ -f "$f" ] || continue
alg=$(jose fmt -j "$f" -Og alg -u- 2>/dev/null) || continue
if [ "$alg" = "ECMR" ]; then
exc_file="$f"
break
fi
done

if [ -z "$exc_file" ]; then
echo "Error: no ECMR exchange key found in $JWKDIR" >&2
exit 1
fi

exc_kid=$(jose jwk thp -i "$exc_file")
echo "Exchange key: $(basename "$exc_file")"
echo "Thumbprint: $exc_kid"

TMP=$(mktemp -d)
trap 'rm -rf "$TMP"' EXIT

ver_resp=$(curl -sf "$URL/version" 2>/dev/null) || {
echo "FAIL: /version endpoint not available (server may not support tang_pub)" >&2
exit 1
}

if ! printf '%s' "$ver_resp" | jose fmt -j- -Og features -Og tang_pub >/dev/null 2>&1; then
echo "FAIL: /version does not report tang_pub capability" >&2
exit 1
fi
echo "OK: /version reports tang_pub capability"

jose jwk gen -i '{"alg":"ECMR"}' -o "$TMP/client.jwk"
jose jwk pub -i "$TMP/client.jwk" -o "$TMP/client.pub.jwk"

resp=$(curl -sf -X POST \
-H "Content-Type: application/jwk+json" \
--data-binary @"$TMP/client.pub.jwk" \
"$URL/rec/$exc_kid" 2>/dev/null) || {
echo "Error: /rec/$exc_kid request failed" >&2
exit 1
}

tang_pub=$(printf '%s' "$resp" | jose fmt -j- -Og tang_pub -o- 2>/dev/null) || {
echo "FAIL: tang_pub not present in /rec/$exc_kid response" >&2
exit 1
}

if printf '%s' "$tang_pub" | jose fmt -j- -Og d 2>/dev/null >/dev/null; then
echo "FAIL: tang_pub contains private material" >&2
exit 1
fi

tang_pub_thp=$(printf '%s' "$tang_pub" | jose jwk thp -i-)
if [ "$tang_pub_thp" != "$exc_kid" ]; then
echo "FAIL: tang_pub thumbprint ($tang_pub_thp) does not match key identifier ($exc_kid)" >&2
exit 1
fi

echo "OK: tang_pub is present, public-only, and matches key identifier"
Loading