1
0

tangd.c 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. /* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
  2. /*
  3. * Copyright (c) 2016 Red Hat, Inc.
  4. * Author: Nathaniel McCallum <npmccallum@redhat.com>
  5. *
  6. * This program is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. */
  19. #include "http.h"
  20. #include <sys/types.h>
  21. #include <sys/stat.h>
  22. #include <limits.h>
  23. #include <stdio.h>
  24. #include <stdlib.h>
  25. #include <string.h>
  26. #include <unistd.h>
  27. #include <getopt.h>
  28. #include <jose/jose.h>
  29. #include "keys.h"
  30. #include "socket.h"
  31. #define MAX_URL 256
  32. static const struct option long_options[] = {
  33. {"port", 1, 0, 'p'},
  34. {"endpoint", 1, 0, 'e'},
  35. {"listen", 0, 0, 'l'},
  36. {"version", 0, 0, 'v'},
  37. {"help", 0, 0, 'h'},
  38. {NULL, 0, 0, 0}
  39. };
  40. static void
  41. print_help(const char *name)
  42. {
  43. fprintf(stderr, "Usage: %s [OPTIONS] <jwkdir>\n", name);
  44. fprintf(stderr, " -p, --port=PORT Specify the port to listen (default 9090)\n");
  45. fprintf(stderr, " -e, --endpoint=ENDPOINT Specify endpoint to listen (empty by default)\n");
  46. fprintf(stderr, " -l, --listen Run as a service and wait for connections\n");
  47. fprintf(stderr, " -v, --version Display program version\n");
  48. fprintf(stderr, " -h, --help Show this help message\n");
  49. }
  50. static void
  51. print_version(void)
  52. {
  53. fprintf(stderr, "tangd %s\n", VERSION);
  54. }
  55. static void
  56. str_cleanup(char **str)
  57. {
  58. if (str)
  59. free(*str);
  60. }
  61. static int
  62. adv(http_method_t method, const char *path, const char *body,
  63. regmatch_t matches[], void *misc)
  64. {
  65. __attribute__((cleanup(str_cleanup))) char *adv = NULL;
  66. __attribute__((cleanup(str_cleanup))) char *thp = NULL;
  67. __attribute__((cleanup(cleanup_tang_keys_info))) struct tang_keys_info *tki = NULL;
  68. json_auto_t *jws = NULL;
  69. const char *jwkdir = misc;
  70. tki = read_keys(jwkdir);
  71. if (!tki || tki->m_keys_count == 0) {
  72. return http_reply(HTTP_STATUS_INTERNAL_SERVER_ERROR, NULL);
  73. }
  74. if (matches[1].rm_so < matches[1].rm_eo) {
  75. size_t size = matches[1].rm_eo - matches[1].rm_so;
  76. thp = strndup(&path[matches[1].rm_so], size);
  77. if (!thp)
  78. return http_reply(HTTP_STATUS_INTERNAL_SERVER_ERROR, NULL);
  79. }
  80. jws = find_jws(tki, thp);
  81. if (!jws) {
  82. return http_reply(HTTP_STATUS_NOT_FOUND, NULL);
  83. }
  84. adv = json_dumps(jws, 0);
  85. if (!adv)
  86. return http_reply(HTTP_STATUS_INTERNAL_SERVER_ERROR, NULL);
  87. return http_reply(HTTP_STATUS_OK,
  88. "Content-Type: application/jose+json\r\n"
  89. "Content-Length: %zu\r\n"
  90. "\r\n%s", strlen(adv), adv);
  91. }
  92. static int
  93. rec(http_method_t method, const char *path, const char *body,
  94. regmatch_t matches[], void *misc)
  95. {
  96. __attribute__((cleanup(str_cleanup))) char *enc = NULL;
  97. __attribute__((cleanup(str_cleanup))) char *thp = NULL;
  98. __attribute__((cleanup(cleanup_tang_keys_info))) struct tang_keys_info *tki = NULL;
  99. size_t size = matches[1].rm_eo - matches[1].rm_so;
  100. const char *jwkdir = misc;
  101. json_auto_t *jwk = NULL;
  102. json_auto_t *req = NULL;
  103. json_auto_t *rep = NULL;
  104. const char *alg = NULL;
  105. const char *kty = NULL;
  106. const char *d = NULL;
  107. /*
  108. * Parse and validate the request JWK
  109. */
  110. req = json_loads(body, 0, NULL);
  111. if (!req)
  112. return http_reply(HTTP_STATUS_BAD_REQUEST, NULL);
  113. if (!jose_jwk_prm(NULL, req, false, "deriveKey"))
  114. return http_reply(HTTP_STATUS_FORBIDDEN, NULL);
  115. if (json_unpack(req, "{s:s,s?s}", "kty", &kty, "alg", &alg) < 0)
  116. return http_reply(HTTP_STATUS_BAD_REQUEST, NULL);
  117. if (strcmp(kty, "EC") != 0)
  118. return http_reply(HTTP_STATUS_BAD_REQUEST, NULL);
  119. if (alg && strcmp(alg, "ECMR") != 0)
  120. return http_reply(HTTP_STATUS_BAD_REQUEST, NULL);
  121. /*
  122. * Parse and validate the server-side JWK
  123. */
  124. tki = read_keys(jwkdir);
  125. if (!tki || tki->m_keys_count == 0) {
  126. return http_reply(HTTP_STATUS_INTERNAL_SERVER_ERROR, NULL);
  127. }
  128. thp = strndup(&path[matches[1].rm_so], size);
  129. if (!thp)
  130. return http_reply(HTTP_STATUS_INTERNAL_SERVER_ERROR, NULL);
  131. jwk = find_jwk(tki, thp);
  132. if (!jwk)
  133. return http_reply(HTTP_STATUS_NOT_FOUND, NULL);
  134. if (!jose_jwk_prm(NULL, jwk, true, "deriveKey"))
  135. return http_reply(HTTP_STATUS_FORBIDDEN, NULL);
  136. if (json_unpack(jwk, "{s:s,s?s}", "d", &d, "alg", &alg) < 0)
  137. return http_reply(HTTP_STATUS_FORBIDDEN, NULL);
  138. if (alg && strcmp(alg, "ECMR") != 0)
  139. return http_reply(HTTP_STATUS_FORBIDDEN, NULL);
  140. /*
  141. * Perform the exchange and return
  142. */
  143. rep = jose_jwk_exc(NULL, jwk, req);
  144. if (!rep)
  145. return http_reply(HTTP_STATUS_BAD_REQUEST, NULL);
  146. if (json_object_set_new(rep, "alg", json_string("ECMR")) < 0)
  147. return http_reply(HTTP_STATUS_INTERNAL_SERVER_ERROR, NULL);
  148. if (json_object_set_new(rep, "key_ops", json_pack("[s]", "deriveKey")) < 0)
  149. return http_reply(HTTP_STATUS_INTERNAL_SERVER_ERROR, NULL);
  150. enc = json_dumps(rep, JSON_SORT_KEYS | JSON_COMPACT);
  151. if (!enc)
  152. return http_reply(HTTP_STATUS_INTERNAL_SERVER_ERROR, NULL);
  153. return http_reply(HTTP_STATUS_OK,
  154. "Content-Type: application/jwk+json\r\n"
  155. "Content-Length: %zu\r\n"
  156. "\r\n%s", strlen(enc), enc);
  157. }
  158. static struct http_dispatch s_dispatch[] = {
  159. { adv, 1 << HTTP_GET, 2, "^/+adv/+([0-9A-Za-z_-]+)$" },
  160. { adv, 1 << HTTP_GET, 2, "^/+adv/*$" },
  161. { rec, 1 << HTTP_POST, 2, "^/+rec/+([0-9A-Za-z_-]+)$" },
  162. {}
  163. };
  164. #define DEFAULT_PORT 9090
  165. static size_t
  166. tang_http_parser_execute(http_parser_t *parser, const char* data, size_t len)
  167. {
  168. #ifdef USE_LLHTTP
  169. llhttp_errno_t error;
  170. size_t parsed_len;
  171. /*
  172. * Unlike http_parser, which returns the number of parsed
  173. * bytes in the _execute() call, llhttp returns an error
  174. * code.
  175. */
  176. if (data == NULL || len == 0) {
  177. error = llhttp_finish(parser);
  178. } else {
  179. error = llhttp_execute(parser, data, len);
  180. }
  181. parsed_len = len;
  182. /*
  183. * Adjust number of parsed bytes in case of error.
  184. */
  185. if (error != HPE_OK) {
  186. parsed_len = llhttp_get_error_pos(parser) - data;
  187. /* This isn't a real pause, just a way to stop parsing early. */
  188. if (error == HPE_PAUSED_UPGRADE) {
  189. llhttp_resume_after_upgrade(parser);
  190. }
  191. }
  192. return parsed_len;
  193. #else
  194. return http_parser_execute(parser, &http_settings, data, len);
  195. #endif
  196. }
  197. static int
  198. process_request(const char *jwkdir, int in_fileno)
  199. {
  200. struct http_state state = { .dispatch = s_dispatch, .misc = (char*)jwkdir };
  201. http_parser_t parser;
  202. struct stat st = {};
  203. char req[4096] = {};
  204. size_t rcvd = 0;
  205. int r = 0;
  206. tang_http_parser_init(&parser, &http_settings);
  207. parser.data = &state;
  208. if (stat(jwkdir, &st) != 0) {
  209. fprintf(stderr, "Error calling stat() on path: %s: %m\n", jwkdir);
  210. return EXIT_FAILURE;
  211. }
  212. if (!S_ISDIR(st.st_mode)) {
  213. fprintf(stderr, "Path is not a directory: %s\n", jwkdir);
  214. return EXIT_FAILURE;
  215. }
  216. for (;;) {
  217. r = read(in_fileno, &req[rcvd], sizeof(req) - rcvd - 1);
  218. if (r == 0)
  219. return rcvd > 0 ? EXIT_FAILURE : EXIT_SUCCESS;
  220. if (r < 0)
  221. return EXIT_FAILURE;
  222. rcvd += r;
  223. r = tang_http_parser_execute(&parser, req, rcvd);
  224. switch (tang_http_parser_errno(parser)) {
  225. case HPE_OK:
  226. break;
  227. case HPE_PAUSED:
  228. tang_http_parser_resume(&parser);
  229. break;
  230. default:
  231. fprintf(stderr, "HTTP Parsing Error: %s\n",
  232. tang_http_errno_description(&parser, tang_http_parser_errno(parser)));
  233. return EXIT_SUCCESS;
  234. }
  235. memmove(req, &req[r], rcvd - r);
  236. rcvd -= r;
  237. }
  238. return EXIT_SUCCESS;
  239. }
  240. int
  241. main(int argc, char *argv[])
  242. {
  243. int listen = 0;
  244. int port = DEFAULT_PORT;
  245. const char *jwkdir = NULL;
  246. const char *endpoint = NULL;
  247. while (1) {
  248. int c = getopt_long(argc, argv, "lp:e:vh", long_options, NULL);
  249. if (c == -1)
  250. break;
  251. switch(c) {
  252. case 'v':
  253. print_version();
  254. return EXIT_SUCCESS;
  255. case 'h':
  256. print_help(argv[0]);
  257. return EXIT_SUCCESS;
  258. case 'p':
  259. port = atoi(optarg);
  260. break;
  261. case 'e':
  262. endpoint = optarg;
  263. break;
  264. case 'l':
  265. listen = 1;
  266. break;
  267. }
  268. }
  269. if (optind >= argc) {
  270. fprintf(stderr, "Usage: %s [OPTION] <jwkdir>\n", argv[0]);
  271. return EXIT_FAILURE;
  272. }
  273. jwkdir = argv[optind++];
  274. char adv_thp_endpoint[MAX_URL] = {};
  275. char adv_endpoint[MAX_URL] = {};
  276. char rec_endpoint[MAX_URL] = {};
  277. if (endpoint != NULL) {
  278. char *endpoint_ptr = (char*)endpoint;
  279. while (*endpoint_ptr == '/') {
  280. endpoint_ptr++;
  281. }
  282. snprintf(adv_thp_endpoint, MAX_URL, "^/%s/+adv/+([0-9A-Za-z_-]+)$", endpoint_ptr);
  283. snprintf(adv_endpoint, MAX_URL, "^/%s/+adv/*$", endpoint_ptr);
  284. snprintf(rec_endpoint, MAX_URL, "^/%s/+rec/+([0-9A-Za-z_-]+)$", endpoint_ptr);
  285. s_dispatch[0].re = adv_thp_endpoint;
  286. s_dispatch[1].re = adv_endpoint;
  287. s_dispatch[2].re = rec_endpoint;
  288. }
  289. if (listen == 0) { /* process one-shot query from stdin */
  290. return process_request(jwkdir, STDIN_FILENO);
  291. } else { /* listen and process all incoming connections */
  292. return run_service(jwkdir, port, process_request);
  293. }
  294. }