1606480249.v7-6-g6090505.add-functions-for-key-manipulation.patch 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528
  1. Subject: Add functions for key manipulation
  2. Origin: v7-6-g6090505 <https://github.com/latchset/tang/commit/v7-6-g6090505>
  3. Upstream-Author: Sergio Correia <scorreia@redhat.com>
  4. Date: Fri Nov 27 09:30:49 2020 -0300
  5. We currently rely on the tangd-update script to read the keys and
  6. generate signed advertisements as well as JWKs for key derivation.
  7. Whenever there is a change in the directory containing the actual
  8. keys, we run tangd-update through a systemd file watching mechanism,
  9. so that we can have a cache directory with updated advertisements +
  10. JWKs.
  11. As reported in #23 and #24, this mechanism can be unreliable in
  12. certain situations, and having up-to-date information on the keys that
  13. are available is critical to tang, so the idea here is to remove this
  14. dependency on external scripts (e.g. tangd-update) and move this
  15. computation to tang itself.
  16. In this commit we add the related functions for key manipulation so
  17. that in a next step we can start using it in tang.
  18. --- /dev/null
  19. +++ b/src/keys.c
  20. @@ -0,0 +1,455 @@
  21. +/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
  22. +/*
  23. + * Copyright (c) 2020 Red Hat, Inc.
  24. + * Author: Sergio Correia <scorreia@redhat.com>
  25. + *
  26. + * This program is free software: you can redistribute it and/or modify
  27. + * it under the terms of the GNU General Public License as published by
  28. + * the Free Software Foundation, either version 3 of the License, or
  29. + * (at your option) any later version.
  30. + *
  31. + * This program is distributed in the hope that it will be useful,
  32. + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  33. + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  34. + * GNU General Public License for more details.
  35. + *
  36. + * You should have received a copy of the GNU General Public License
  37. + * along with this program. If not, see <http://www.gnu.org/licenses/>.
  38. + */
  39. +
  40. +#include <stdlib.h>
  41. +#include <string.h>
  42. +#include <dirent.h>
  43. +#include <stdio.h>
  44. +
  45. +#include <jose/b64.h>
  46. +#include <jose/jwk.h>
  47. +#include <jose/jws.h>
  48. +
  49. +#include "keys.h"
  50. +
  51. +#ifndef PATH_MAX
  52. +#define PATH_MAX 4096
  53. +#endif
  54. +
  55. +static const char**
  56. +supported_hashes(void)
  57. +{
  58. + /* TODO: check if jose has a way to export the hash algorithms it
  59. + * supports. */
  60. + static const char* hashes[] = {"S1", "S224", "S256", "S384", "S512", NULL};
  61. + return hashes;
  62. +}
  63. +
  64. +static int
  65. +is_hash(const char* alg)
  66. +{
  67. + if (!alg) {
  68. + return 0;
  69. + }
  70. +
  71. + const char** algs = supported_hashes();
  72. + for (size_t a = 0; algs[a]; a++) {
  73. + if (strcmp(alg, algs[a]) == 0) {
  74. + return 1;
  75. + }
  76. + }
  77. + return 0;
  78. +}
  79. +
  80. +static json_t*
  81. +jwk_generate(const char* alg)
  82. +{
  83. + json_auto_t* jalg = json_pack("{s:s}", "alg", alg);
  84. + if (!jalg) {
  85. + fprintf(stderr, "Error packing JSON with alg %s\n", alg);
  86. + return NULL;
  87. + }
  88. +
  89. + if (!jose_jwk_gen(NULL, jalg)) {
  90. + fprintf(stderr, "Error generating JWK with alg %s\n", alg);
  91. + return NULL;
  92. + }
  93. +
  94. + return json_incref(jalg);
  95. +}
  96. +
  97. +static char*
  98. +jwk_thumbprint(const json_t* jwk, const char* alg)
  99. +{
  100. + size_t elen = 0;
  101. + size_t dlen = 0;
  102. +
  103. + if (!jwk) {
  104. + fprintf(stderr, "Invalid JWK\n");
  105. + return NULL;
  106. + }
  107. +
  108. + if (!alg || !is_hash(alg)) {
  109. + fprintf(stderr, "Invalid hash algorithm (%s)\n", alg);
  110. + return NULL;
  111. + }
  112. +
  113. + dlen = jose_jwk_thp_buf(NULL, NULL, alg, NULL, 0);
  114. + if (dlen == SIZE_MAX) {
  115. + fprintf(stderr, "Error determining hash size for %s\n", alg);
  116. + return NULL;
  117. + }
  118. +
  119. + elen = jose_b64_enc_buf(NULL, dlen, NULL, 0);
  120. + if (elen == SIZE_MAX) {
  121. + fprintf(stderr, "Error determining encoded size for %s\n", alg);
  122. + return NULL;
  123. + }
  124. +
  125. + uint8_t dec[dlen];
  126. + char enc[elen];
  127. +
  128. + if (!jose_jwk_thp_buf(NULL, jwk, alg, dec, sizeof(dec))) {
  129. + fprintf(stderr, "Error making thumbprint\n");
  130. + return NULL;
  131. + }
  132. +
  133. + if (jose_b64_enc_buf(dec, dlen, enc, sizeof(enc)) != elen) {
  134. + fprintf(stderr, "Error encoding data Base64\n");
  135. + return NULL;
  136. + }
  137. +
  138. + return strndup(enc, elen);
  139. +}
  140. +
  141. +void
  142. +free_tang_keys_info(struct tang_keys_info* tki)
  143. +{
  144. + if (!tki) {
  145. + return;
  146. + }
  147. +
  148. + json_t* to_free[] = {tki->m_keys, tki->m_rotated_keys,
  149. + tki->m_payload, tki->m_sign
  150. + };
  151. + size_t len = sizeof(to_free) / sizeof(to_free[0]);
  152. +
  153. + for (size_t i = 0; i < len; i++) {
  154. + if (to_free[i] == NULL) {
  155. + continue;
  156. + }
  157. + json_decref(to_free[i]);
  158. + }
  159. + free(tki);
  160. +}
  161. +
  162. +void
  163. +cleanup_tang_keys_info(struct tang_keys_info** tki)
  164. +{
  165. + if (!tki || !*tki) {
  166. + return;
  167. + }
  168. + free_tang_keys_info(*tki);
  169. + *tki = NULL;
  170. +}
  171. +
  172. +static struct tang_keys_info*
  173. +new_tang_keys_info(void)
  174. +{
  175. + struct tang_keys_info* tki = calloc(1, sizeof(*tki));
  176. + if (!tki) {
  177. + return NULL;
  178. + }
  179. +
  180. + tki->m_keys = json_array();
  181. + tki->m_rotated_keys = json_array();
  182. + tki->m_payload = json_array();
  183. + tki->m_sign = json_array();
  184. +
  185. + if (!tki->m_keys || !tki->m_rotated_keys ||
  186. + !tki->m_payload || !tki->m_sign) {
  187. + free_tang_keys_info(tki);
  188. + return NULL;
  189. + }
  190. + tki->m_keys_count = 0;
  191. + return tki;
  192. +}
  193. +
  194. +static int
  195. +jwk_valid_for(const json_t* jwk, const char* use)
  196. +{
  197. + if (!jwk || !use) {
  198. + return 0;
  199. + }
  200. + return jose_jwk_prm(NULL, jwk, false, use);
  201. +}
  202. +
  203. +static int
  204. +jwk_valid_for_signing_and_verifying(const json_t* jwk)
  205. +{
  206. + const char* uses[] = {"sign", "verify", NULL};
  207. + int ret = 1;
  208. + for (int i = 0; uses[i]; i++) {
  209. + if (!jwk_valid_for(jwk, uses[i])) {
  210. + ret = 0;
  211. + break;
  212. + }
  213. + }
  214. + return ret;
  215. +}
  216. +
  217. +static int
  218. +jwk_valid_for_signing(const json_t* jwk)
  219. +{
  220. + return jwk_valid_for(jwk, "sign");
  221. +}
  222. +
  223. +static int
  224. +jwk_valid_for_deriving_keys(const json_t* jwk)
  225. +{
  226. + return jwk_valid_for(jwk, "deriveKey");
  227. +}
  228. +
  229. +static void
  230. +cleanup_str(char** str)
  231. +{
  232. + if (!str || !*str) {
  233. + return;
  234. + }
  235. + free(*str);
  236. + *str = NULL;
  237. +}
  238. +
  239. +static json_t*
  240. +jwk_sign(const json_t* to_sign, const json_t* sig_keys)
  241. +{
  242. + if (!sig_keys || !json_is_array(sig_keys) || !json_is_array(to_sign)) {
  243. + return NULL;
  244. + }
  245. +
  246. + json_auto_t* to_sign_copy = json_deep_copy(to_sign);
  247. + if (!jose_jwk_pub(NULL, to_sign_copy)) {
  248. + fprintf(stderr, "Error removing private material from data to sign\n");
  249. + }
  250. +
  251. + json_auto_t* payload = json_pack("{s:O}", "keys", to_sign_copy);
  252. + json_auto_t* sig_template = json_pack("{s:{s:s}}",
  253. + "protected", "cty", "jwk-set+json");
  254. +
  255. + /* Use the template with the signing keys. */
  256. + json_auto_t* sig_template_arr = json_array();
  257. + size_t arr_size = json_array_size(sig_keys);
  258. + for (size_t i = 0; i < arr_size; i++) {
  259. + if (json_array_append(sig_template_arr, sig_template) == -1) {
  260. + fprintf(stderr, "Unable to append sig template to array\n");
  261. + return NULL;
  262. + }
  263. + }
  264. +
  265. + __attribute__ ((__cleanup__(cleanup_str))) char* data_to_sign = json_dumps(payload, 0);
  266. + json_auto_t* jws = json_pack("{s:o}", "payload",
  267. + jose_b64_enc(data_to_sign, strlen(data_to_sign)));
  268. +
  269. + if (!jose_jws_sig(NULL, jws, sig_template_arr, sig_keys)) {
  270. + fprintf(stderr, "Error trying to jose_jws_sign\n");
  271. + return NULL;
  272. + }
  273. + return json_incref(jws);
  274. +}
  275. +
  276. +static json_t*
  277. +find_by_thp(struct tang_keys_info* tki, const char* target)
  278. +{
  279. + if (!tki) {
  280. + return NULL;
  281. + }
  282. +
  283. + json_auto_t* keys = json_deep_copy(tki->m_keys);
  284. + json_array_extend(keys, tki->m_rotated_keys);
  285. +
  286. + size_t idx;
  287. + json_t* jwk;
  288. + const char** hashes = supported_hashes();
  289. + json_array_foreach(keys, idx, jwk) {
  290. + for (int i = 0; hashes[i]; i++) {
  291. + __attribute__ ((__cleanup__(cleanup_str))) char* thumbprint = jwk_thumbprint(jwk, hashes[i]);
  292. + if (strcmp(thumbprint, target) != 0) {
  293. + continue;
  294. + }
  295. +
  296. + if (jwk_valid_for_deriving_keys(jwk)) {
  297. + return json_incref(jwk);
  298. + } else if (jwk_valid_for_signing(jwk)) {
  299. + json_auto_t* sign = json_deep_copy(tki->m_sign);
  300. + if (json_array_append(sign, jwk) == -1) {
  301. + return NULL;
  302. + }
  303. + json_auto_t* jws = jwk_sign(tki->m_payload, sign);
  304. + if (!jws) {
  305. + return NULL;
  306. + }
  307. + return json_incref(jws);
  308. + }
  309. + }
  310. + }
  311. + return NULL;
  312. +}
  313. +
  314. +static int
  315. +prepare_payload_and_sign(struct tang_keys_info* tki)
  316. +{
  317. + if (!tki) {
  318. + return 0;
  319. + }
  320. +
  321. + size_t idx;
  322. + json_t* jwk;
  323. + json_array_foreach(tki->m_keys, idx, jwk) {
  324. + if (jwk_valid_for_signing_and_verifying(jwk)) {
  325. + if (json_array_append(tki->m_sign, jwk) == -1) {
  326. + continue;
  327. + }
  328. + if (json_array_append(tki->m_payload, jwk) == -1) {
  329. + continue;
  330. + }
  331. + } else if (jwk_valid_for_deriving_keys(jwk)) {
  332. + if (json_array_append(tki->m_payload, jwk) == -1) {
  333. + continue;
  334. + }
  335. + }
  336. + }
  337. + if (json_array_size(tki->m_sign) == 0 || json_array_size(tki->m_payload) == 0) {
  338. + return 0;
  339. + }
  340. + return 1;
  341. +}
  342. +
  343. +static int
  344. +create_new_keys(const char* jwkdir)
  345. +{
  346. + const char** hashes = supported_hashes();
  347. + const char* alg[] = {"ES512", "ECMR", NULL};
  348. + char path[PATH_MAX];
  349. + for (int i = 0; alg[i] != NULL; i++) {
  350. + json_auto_t* jwk = jwk_generate(alg[i]);
  351. + if (!jwk) {
  352. + return 0;
  353. + }
  354. + __attribute__ ((__cleanup__(cleanup_str))) char* thp = jwk_thumbprint(jwk, hashes[0]);
  355. + if (!thp) {
  356. + return 0;
  357. + }
  358. + if (snprintf(path, PATH_MAX, "%s/%s.jwk", jwkdir, thp) < 0) {
  359. + fprintf(stderr, "Unable to prepare variable with file full path (%s)\n", thp);
  360. + return 0;
  361. + }
  362. + path[sizeof(path) - 1] = '\0';
  363. + if (json_dump_file(jwk, path, 0) == -1) {
  364. + fprintf(stderr, "Error saving JWK to file (%s)\n", path);
  365. + return 0;
  366. + }
  367. + }
  368. + return 1;
  369. +}
  370. +
  371. +static struct tang_keys_info*
  372. +load_keys(const char* jwkdir)
  373. +{
  374. + struct tang_keys_info* tki = new_tang_keys_info();
  375. + if (!tki) {
  376. + return NULL;
  377. + }
  378. +
  379. + struct dirent* d;
  380. + DIR* dir = opendir(jwkdir);
  381. + if (!dir) {
  382. + free_tang_keys_info(tki);
  383. + return NULL;
  384. + }
  385. +
  386. + char filepath[PATH_MAX];
  387. + const char* pattern = ".jwk";
  388. + while ((d = readdir(dir)) != NULL) {
  389. + if (strcmp(d->d_name, ".") == 0 || strcmp(d->d_name, "..") == 0) {
  390. + continue;
  391. + }
  392. +
  393. + char* dot = strrchr(d->d_name, '.');
  394. + if (!dot) {
  395. + continue;
  396. + }
  397. +
  398. + if (strcmp(dot, pattern) == 0) {
  399. + /* Found a file with .jwk extension. */
  400. + if (snprintf(filepath, PATH_MAX, "%s/%s", jwkdir, d->d_name) < 0) {
  401. + fprintf(stderr, "Unable to prepare variable with file full path (%s); skipping\n", d->d_name);
  402. + continue;
  403. + }
  404. + filepath[sizeof(filepath) - 1] = '\0';
  405. + json_auto_t* json = json_load_file(filepath, 0, NULL);
  406. + if (!json) {
  407. + fprintf(stderr, "Invalid JSON file (%s); skipping\n", filepath);
  408. + continue;
  409. + }
  410. +
  411. + json_t* arr = tki->m_keys;
  412. + if (d->d_name[0] == '.') {
  413. + arr = tki->m_rotated_keys;
  414. + }
  415. + if (json_array_append(arr, json) == -1) {
  416. + fprintf(stderr, "Unable to append JSON (%s) to array; skipping\n", d->d_name);
  417. + continue;
  418. + }
  419. + tki->m_keys_count++;
  420. + }
  421. + }
  422. + closedir(dir);
  423. + return tki;
  424. +}
  425. +
  426. +struct tang_keys_info*
  427. +read_keys(const char* jwkdir)
  428. +{
  429. + struct tang_keys_info* tki = load_keys(jwkdir);
  430. + if (!tki) {
  431. + return NULL;
  432. + }
  433. +
  434. + if (tki->m_keys_count == 0) {
  435. + /* Let's attempt to create a new pair of keys. */
  436. + free_tang_keys_info(tki);
  437. + if (!create_new_keys(jwkdir)) {
  438. + return NULL;
  439. + }
  440. + tki = load_keys(jwkdir);
  441. + }
  442. +
  443. + if (!prepare_payload_and_sign(tki)) {
  444. + free_tang_keys_info(tki);
  445. + return NULL;
  446. + }
  447. + return tki;
  448. +}
  449. +
  450. +json_t*
  451. +find_jws(struct tang_keys_info* tki, const char* thp)
  452. +{
  453. + if (!tki) {
  454. + return NULL;
  455. + }
  456. +
  457. + if (thp == NULL) {
  458. + /* Default advertisement. */
  459. + json_auto_t* jws = jwk_sign(tki->m_payload, tki->m_sign);
  460. + if (!jws) {
  461. + return NULL;
  462. + }
  463. + return json_incref(jws);
  464. + }
  465. + return find_by_thp(tki, thp);
  466. +}
  467. +
  468. +json_t*
  469. +find_jwk(struct tang_keys_info* tki, const char* thp)
  470. +{
  471. + if (!tki || !thp) {
  472. + return NULL;
  473. + }
  474. + return find_by_thp(tki, thp);
  475. +}
  476. --- /dev/null
  477. +++ b/src/keys.h
  478. @@ -0,0 +1,45 @@
  479. +/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
  480. +/*
  481. + * Copyright (c) 2020 Red Hat, Inc.
  482. + * Author: Sergio Correia <scorreia@redhat.com>
  483. + *
  484. + * This program is free software: you can redistribute it and/or modify
  485. + * it under the terms of the GNU General Public License as published by
  486. + * the Free Software Foundation, either version 3 of the License, or
  487. + * (at your option) any later version.
  488. + *
  489. + * This program is distributed in the hope that it will be useful,
  490. + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  491. + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  492. + * GNU General Public License for more details.
  493. + *
  494. + * You should have received a copy of the GNU General Public License
  495. + * along with this program. If not, see <http://www.gnu.org/licenses/>.
  496. + */
  497. +
  498. +#pragma once
  499. +
  500. +#include <jansson.h>
  501. +#include <stddef.h>
  502. +
  503. +struct tang_keys_info {
  504. + /* Arrays. */
  505. + json_t* m_keys; /* Regular keys. */
  506. + json_t* m_rotated_keys; /* Rotated keys. */
  507. +
  508. + json_t* m_payload; /* Payload made of regular keys capable of
  509. + * either signing+verifying or deriving new
  510. + * keys. */
  511. +
  512. + json_t* m_sign; /* Set of signing keys made from regular
  513. + keys. */
  514. +
  515. + size_t m_keys_count; /* Number of keys (regular + rotated). */
  516. +
  517. +};
  518. +
  519. +void cleanup_tang_keys_info(struct tang_keys_info**);
  520. +void free_tang_keys_info(struct tang_keys_info*);
  521. +struct tang_keys_info* read_keys(const char* /* jwkdir */);
  522. +json_t* find_jws(struct tang_keys_info* /* tki */, const char* /* thp */);
  523. +json_t* find_jwk(struct tang_keys_info* /* tki */, const char* /* thp */);