Skip to content

Commit 58691c1

Browse files
committed
Include parameter to indicate endpoint
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]>
1 parent df3cc46 commit 58691c1

13 files changed

+292
-57
lines changed

doc/tang.8.adoc

+21
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,27 @@ be changed with the *-p* option.
7171

7272
tang -l -p 9090
7373

74+
== ENDPOINT
75+
76+
The Tang server can be provided an endpoint. This endpoint will act as a prefix
77+
for the URL to be accessed by the client. This endpoint can be specified with
78+
the *-e* option.
79+
80+
tang -l -p 9090 -e this/is/an/endpoint
81+
82+
When endpoint is specified, the endpoint will be prepended to the normal adv/rec
83+
URL. If no endpoint is provided, and assuming port 9090 is used, Tang server
84+
will listen on next URLs:
85+
86+
http://localhost:9090/adv (GET)
87+
http://localhost:9090/rec (POST)
88+
89+
If endpoint is provided, and assuming endpoint is /this/is/an/endpoint/, and
90+
assuming also port 9090 is used, Tang server will listen on next URLs:
91+
92+
http://localhost:9090/this/is/an/endpoint/adv (GET)
93+
http://localhost:9090/this/is/an/endpoint/rec (POST)
94+
7495
== KEY ROTATION
7596

7697
In order to preserve the security of the system over the long run, you need to

src/socket.c

+15-12
Original file line numberDiff line numberDiff line change
@@ -146,8 +146,8 @@ static int listen_port(socket_list **slist, int port)
146146
return r;
147147
}
148148

149-
static void spawn_process(int fd, const char *jwkdir,
150-
process_request_func pfunc,
149+
static void spawn_process(process_request_func pfunc,
150+
process_request_func_args_t pfunc_args,
151151
socket_list *slist)
152152
{
153153
pid_t pid;
@@ -159,21 +159,23 @@ static void spawn_process(int fd, const char *jwkdir,
159159
close(ptr->s);
160160
}
161161
/* Ensure that both stdout and stdin are set */
162-
if (dup2(fd, STDOUT_FILENO) < 0) {
162+
if (dup2(pfunc_args.fileno, STDOUT_FILENO) < 0) {
163163
perror("dup2");
164-
close(fd);
164+
close(pfunc_args.fileno);
165165
return;
166166
}
167167

168-
close(fd);
168+
close(pfunc_args.fileno);
169169

170-
pfunc(jwkdir, STDOUT_FILENO);
170+
process_request_func_args_t pf =
171+
{ pfunc_args.jwkdir, STDOUT_FILENO, pfunc_args.endpoint };
172+
pfunc(pf);
171173
free_socket_list(slist);
172174
exit(0);
173175
} else if (pid == -1) {
174176
perror("fork failed");
175177
}
176-
close(fd);
178+
close(pfunc_args.fileno);
177179
}
178180

179181
static void handle_child(int sig)
@@ -183,7 +185,7 @@ static void handle_child(int sig)
183185
while ((waitpid(-1, &status, WNOHANG)) > 0);
184186
}
185187

186-
int run_service(const char *jwkdir, int port, process_request_func pfunc)
188+
int run_service(run_service_args_t rserv, process_request_func pfunc)
187189
{
188190
socket_list *slist, *ptr;
189191
int r, n = 0, accept_fd;
@@ -198,9 +200,9 @@ int run_service(const char *jwkdir, int port, process_request_func pfunc)
198200
new_action.sa_flags = 0;
199201
sigaction(SIGCHLD, &new_action, NULL);
200202

201-
r = listen_port(&slist, port);
203+
r = listen_port(&slist, rserv.port);
202204
if (r < 0) {
203-
fprintf(stderr, "Could not listen port (%d)\n", port);
205+
fprintf(stderr, "Could not listen port (%d)\n", rserv.port);
204206
return -1;
205207
}
206208

@@ -233,8 +235,9 @@ int run_service(const char *jwkdir, int port, process_request_func pfunc)
233235
perror("accept");
234236
continue;
235237
}
236-
237-
spawn_process(accept_fd, jwkdir, pfunc, slist);
238+
process_request_func_args_t pr_args =
239+
{rserv.jwkdir, accept_fd,rserv.endpoint};
240+
spawn_process(pfunc, pr_args, slist);
238241
}
239242
}
240243

src/socket.h

+13-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,18 @@
1515
* You should have received a copy of the GNU General Public License
1616
* along with this program. If not, see <http://www.gnu.org/licenses/>.
1717
*/
18+
typedef struct process_request_func_args {
19+
const char* jwkdir;
20+
const int fileno;
21+
const char* endpoint;
22+
} process_request_func_args_t;
1823

19-
typedef int (*process_request_func)(const char *jwkdir, int in_fileno);
24+
typedef int (*process_request_func)(const process_request_func_args_t);
2025

21-
int run_service(const char *jwkdir, int port, process_request_func);
26+
typedef struct run_service_args {
27+
const char* jwkdir;
28+
const int port;
29+
const char* endpoint;
30+
} run_service_args_t;
31+
32+
int run_service(const run_service_args_t, process_request_func);

src/tang-show-keys

+12-3
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,23 @@
2020

2121
set -e
2222

23-
if [ $# -gt 1 ]; then
24-
echo "Usage: $0 [<port>]" >&2
23+
if [ $# -gt 2 ]; then
24+
echo "Usage: $0 [<port>] [<endpoint>]" >&2
2525
exit 1
2626
fi
2727

2828
port=${1-80}
2929

30-
adv=$(curl -sSf "localhost:$port/adv")
30+
if test -n "$2"; then
31+
first_letter=$(printf %.1s "$2")
32+
if [ "${first_letter}" = "/" ]; then
33+
adv=$(curl -sSf "localhost:$port$2/adv")
34+
else
35+
adv=$(curl -sSf "localhost:$port/$2/adv")
36+
fi
37+
else
38+
adv=$(curl -sSf "localhost:$port/adv")
39+
fi
3140

3241
THP_DEFAULT_HASH=S256 # SHA-256.
3342
jose fmt --json "${adv}" -g payload -y -o- \

src/tangd.c

+44-16
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,11 @@
3232
#include "keys.h"
3333
#include "socket.h"
3434

35+
const unsigned int MAX_URL = 256;
36+
3537
static const struct option long_options[] = {
3638
{"port", 1, 0, 'p'},
39+
{"endpoint", 1, 0, 'e'},
3740
{"listen", 0, 0, 'l'},
3841
{"version", 0, 0, 'v'},
3942
{"help", 0, 0, 'h'},
@@ -45,6 +48,7 @@ print_help(const char *name)
4548
{
4649
fprintf(stderr, "Usage: %s [OPTIONS] <jwkdir>\n", name);
4750
fprintf(stderr, " -p, --port=PORT Specify the port to listen (default 9090)\n");
51+
fprintf(stderr, " -e, --endpoint=ENDPOINT Specify endpoint to listen (empty by default)\n");
4852
fprintf(stderr, " -l, --listen Run as a service and wait for connections\n");
4953
fprintf(stderr, " -v, --version Display program version\n");
5054
fprintf(stderr, " -h, --help Show this help message\n");
@@ -184,19 +188,37 @@ rec(http_method_t method, const char *path, const char *body,
184188
"\r\n%s", strlen(enc), enc);
185189
}
186190

187-
static struct http_dispatch dispatch[] = {
188-
{ adv, 1 << HTTP_GET, 2, "^/+adv/+([0-9A-Za-z_-]+)$" },
189-
{ adv, 1 << HTTP_GET, 2, "^/+adv/*$" },
190-
{ rec, 1 << HTTP_POST, 2, "^/+rec/+([0-9A-Za-z_-]+)$" },
191-
{}
192-
};
193-
194191
#define DEFAULT_PORT 9090
195192

196193
static int
197-
process_request(const char *jwkdir, int in_fileno)
194+
process_request(const process_request_func_args_t pr_args)
198195
{
199-
struct http_state state = { .dispatch = dispatch, .misc = (char*)jwkdir };
196+
char adv_endpoint[MAX_URL];
197+
char adv2_endpoint[MAX_URL];
198+
char rec_endpoint[MAX_URL];
199+
if (pr_args.endpoint != NULL) {
200+
if (pr_args.endpoint[0] == '/') {
201+
snprintf(adv_endpoint, MAX_URL, "^%s/+adv/+([0-9A-Za-z_-]+)$", pr_args.endpoint);
202+
snprintf(adv2_endpoint, MAX_URL, "^%s/+adv/*$", pr_args.endpoint);
203+
snprintf(rec_endpoint, MAX_URL, "^%s/+rec/+([0-9A-Za-z_-]+)$", pr_args.endpoint);
204+
} else {
205+
snprintf(adv_endpoint, MAX_URL, "^/%s/+adv/+([0-9A-Za-z_-]+)$", pr_args.endpoint);
206+
snprintf(adv2_endpoint, MAX_URL, "^/%s/+adv/*$", pr_args.endpoint);
207+
snprintf(rec_endpoint, MAX_URL, "^/%s/+rec/+([0-9A-Za-z_-]+)$", pr_args.endpoint);
208+
}
209+
} else {
210+
strncpy(adv_endpoint, "^/+adv/+([0-9A-Za-z_-]+)$", MAX_URL);
211+
strncpy(adv2_endpoint, "^/+adv/*$", MAX_URL);
212+
strncpy(rec_endpoint, "^/+rec/+([0-9A-Za-z_-]+)$", MAX_URL);
213+
}
214+
struct http_dispatch dispatch[] = {
215+
{ adv, 1 << HTTP_GET, 2, adv_endpoint },
216+
{ adv, 1 << HTTP_GET, 2, adv2_endpoint },
217+
{ rec, 1 << HTTP_POST, 2, rec_endpoint },
218+
{}
219+
};
220+
221+
struct http_state state = { .dispatch = dispatch, .misc = (char*)pr_args.jwkdir };
200222
http_parser_t parser;
201223
struct stat st = {};
202224
char req[4096] = {};
@@ -206,18 +228,18 @@ process_request(const char *jwkdir, int in_fileno)
206228
tang_http_parser_init(&parser, &http_settings);
207229
parser.data = &state;
208230

209-
if (stat(jwkdir, &st) != 0) {
210-
fprintf(stderr, "Error calling stat() on path: %s: %m\n", jwkdir);
231+
if (stat(pr_args.jwkdir, &st) != 0) {
232+
fprintf(stderr, "Error calling stat() on path: %s: %m\n", pr_args.jwkdir);
211233
return EXIT_FAILURE;
212234
}
213235

214236
if (!S_ISDIR(st.st_mode)) {
215-
fprintf(stderr, "Path is not a directory: %s\n", jwkdir);
237+
fprintf(stderr, "Path is not a directory: %s\n", pr_args.jwkdir);
216238
return EXIT_FAILURE;
217239
}
218240

219241
for (;;) {
220-
r = read(in_fileno, &req[rcvd], sizeof(req) - rcvd - 1);
242+
r = read(pr_args.fileno, &req[rcvd], sizeof(req) - rcvd - 1);
221243
if (r == 0)
222244
return rcvd > 0 ? EXIT_FAILURE : EXIT_SUCCESS;
223245
if (r < 0)
@@ -244,9 +266,10 @@ main(int argc, char *argv[])
244266
int listen = 0;
245267
int port = DEFAULT_PORT;
246268
const char *jwkdir = NULL;
269+
const char *endpoint = NULL;
247270

248271
while (1) {
249-
int c = getopt_long(argc, argv, "lp:vh", long_options, NULL);
272+
int c = getopt_long(argc, argv, "lp:e:vh", long_options, NULL);
250273
if (c == -1)
251274
break;
252275

@@ -260,6 +283,9 @@ main(int argc, char *argv[])
260283
case 'p':
261284
port = atoi(optarg);
262285
break;
286+
case 'e':
287+
endpoint = optarg;
288+
break;
263289
case 'l':
264290
listen = 1;
265291
break;
@@ -273,8 +299,10 @@ main(int argc, char *argv[])
273299
jwkdir = argv[optind++];
274300

275301
if (listen == 0) { /* process one-shot query from stdin */
276-
return process_request(jwkdir, STDIN_FILENO);
302+
process_request_func_args_t pr = {jwkdir, STDIN_FILENO, endpoint};
303+
return process_request(pr);
277304
} else { /* listen and process all incoming connections */
278-
return run_service(jwkdir, port, process_request);
305+
run_service_args_t rs = {jwkdir, port, endpoint};
306+
return run_service(rs, process_request);
279307
}
280308
}

tests/adv

+23-23
Original file line numberDiff line numberDiff line change
@@ -36,47 +36,47 @@ adv_startup () {
3636

3737
adv_second_phase () {
3838
# Make sure requests on the root fail
39-
fetch / && expected_fail
39+
fetch "${ENDPOINT}"/ && expected_fail
4040

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

4545
# The default advertisement fetch should succeed and pass verification
46-
fetch /adv
47-
fetch /adv | ver $TMP/db/sig.jwk
48-
fetch /adv/ | ver $TMP/db/sig.jwk
46+
fetch "${ENDPOINT}"/adv
47+
fetch "${ENDPOINT}"/adv | ver $TMP/db/sig.jwk
48+
fetch "${ENDPOINT}"/adv/ | ver $TMP/db/sig.jwk
4949

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

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

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

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

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

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

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

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

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

105105
# Verify the advertisement is correct.
106-
validate "$(fetch /adv)"
106+
validate "$(fetch "${ENDPOINT}"/adv)"
107107

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

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

0 commit comments

Comments
 (0)