1 /* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
2 /*
3 * Copyright (c) 2020 Red Hat, Inc.
4 * Author: Sergio Correia <scorreia@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
20 #include <stdlib.h>
21 #include <string.h>
22 #include <dirent.h>
23 #include <stdio.h>
24
25 #include <jose/b64.h>
26 #include <jose/jwk.h>
27 #include <jose/jws.h>
28
29 #include "keys.h"
30
31 #ifndef PATH_MAX
32 #define PATH_MAX 4096
33 #endif
34
35 /* Default hash to use with JWK thumbprints (S256 = SHA-256). */
36 #define DEFAULT_THP_HASH "S256"
37
38 static const char**
supported_hashes(void)39 supported_hashes(void)
40 {
41 /* TODO: check if jose has a way to export the hash algorithms it
42 * supports. */
43 static const char* hashes[] = {"S1", "S224", "S256", "S384", "S512", NULL};
44 return hashes;
45 }
46
47 static int
is_hash(const char * alg)48 is_hash(const char* alg)
49 {
50 if (!alg) {
51 return 0;
52 }
53
54 const char** algs = supported_hashes();
55 for (size_t a = 0; algs[a]; a++) {
56 if (strcmp(alg, algs[a]) == 0) {
57 return 1;
58 }
59 }
60 return 0;
61 }
62
63 static json_t*
jwk_generate(const char * alg)64 jwk_generate(const char* alg)
65 {
66 json_auto_t* jalg = json_pack("{s:s}", "alg", alg);
67 if (!jalg) {
68 fprintf(stderr, "Error packing JSON with alg %s\n", alg);
69 return NULL;
70 }
71
72 if (!jose_jwk_gen(NULL, jalg)) {
73 fprintf(stderr, "Error generating JWK with alg %s\n", alg);
74 return NULL;
75 }
76
77 return json_incref(jalg);
78 }
79
80 static char*
jwk_thumbprint(const json_t * jwk,const char * alg)81 jwk_thumbprint(const json_t* jwk, const char* alg)
82 {
83 size_t elen = 0;
84 size_t dlen = 0;
85
86 if (!jwk) {
87 fprintf(stderr, "Invalid JWK\n");
88 return NULL;
89 }
90
91 if (!alg || !is_hash(alg)) {
92 fprintf(stderr, "Invalid hash algorithm (%s)\n", alg);
93 return NULL;
94 }
95
96 dlen = jose_jwk_thp_buf(NULL, NULL, alg, NULL, 0);
97 if (dlen == SIZE_MAX) {
98 fprintf(stderr, "Error determining hash size for %s\n", alg);
99 return NULL;
100 }
101
102 elen = jose_b64_enc_buf(NULL, dlen, NULL, 0);
103 if (elen == SIZE_MAX) {
104 fprintf(stderr, "Error determining encoded size for %s\n", alg);
105 return NULL;
106 }
107
108 uint8_t dec[dlen];
109 char enc[elen];
110
111 if (!jose_jwk_thp_buf(NULL, jwk, alg, dec, sizeof(dec))) {
112 fprintf(stderr, "Error making thumbprint\n");
113 return NULL;
114 }
115
116 if (jose_b64_enc_buf(dec, dlen, enc, sizeof(enc)) != elen) {
117 fprintf(stderr, "Error encoding data Base64\n");
118 return NULL;
119 }
120
121 return strndup(enc, elen);
122 }
123
124 void
free_tang_keys_info(struct tang_keys_info * tki)125 free_tang_keys_info(struct tang_keys_info* tki)
126 {
127 if (!tki) {
128 return;
129 }
130
131 json_t* to_free[] = {tki->m_keys, tki->m_rotated_keys,
132 tki->m_payload, tki->m_sign
133 };
134 size_t len = sizeof(to_free) / sizeof(to_free[0]);
135
136 for (size_t i = 0; i < len; i++) {
137 if (to_free[i] == NULL) {
138 continue;
139 }
140 json_decref(to_free[i]);
141 }
142 free(tki);
143 }
144
145 void
cleanup_tang_keys_info(struct tang_keys_info ** tki)146 cleanup_tang_keys_info(struct tang_keys_info** tki)
147 {
148 if (!tki || !*tki) {
149 return;
150 }
151 free_tang_keys_info(*tki);
152 *tki = NULL;
153 }
154
155 static struct tang_keys_info*
new_tang_keys_info(void)156 new_tang_keys_info(void)
157 {
158 struct tang_keys_info* tki = calloc(1, sizeof(*tki));
159 if (!tki) {
160 return NULL;
161 }
162
163 tki->m_keys = json_array();
164 tki->m_rotated_keys = json_array();
165 tki->m_payload = json_array();
166 tki->m_sign = json_array();
167
168 if (!tki->m_keys || !tki->m_rotated_keys ||
169 !tki->m_payload || !tki->m_sign) {
170 free_tang_keys_info(tki);
171 return NULL;
172 }
173 tki->m_keys_count = 0;
174 return tki;
175 }
176
177 static int
jwk_valid_for(const json_t * jwk,const char * use)178 jwk_valid_for(const json_t* jwk, const char* use)
179 {
180 if (!jwk || !use) {
181 return 0;
182 }
183 return jose_jwk_prm(NULL, jwk, false, use);
184 }
185
186 static int
jwk_valid_for_signing_and_verifying(const json_t * jwk)187 jwk_valid_for_signing_and_verifying(const json_t* jwk)
188 {
189 const char* uses[] = {"sign", "verify", NULL};
190 int ret = 1;
191 for (int i = 0; uses[i]; i++) {
192 if (!jwk_valid_for(jwk, uses[i])) {
193 ret = 0;
194 break;
195 }
196 }
197 return ret;
198 }
199
200 static int
jwk_valid_for_signing(const json_t * jwk)201 jwk_valid_for_signing(const json_t* jwk)
202 {
203 return jwk_valid_for(jwk, "sign");
204 }
205
206 static int
jwk_valid_for_deriving_keys(const json_t * jwk)207 jwk_valid_for_deriving_keys(const json_t* jwk)
208 {
209 return jwk_valid_for(jwk, "deriveKey");
210 }
211
212 static void
cleanup_str(char ** str)213 cleanup_str(char** str)
214 {
215 if (!str || !*str) {
216 return;
217 }
218 free(*str);
219 *str = NULL;
220 }
221
222 static json_t*
jwk_sign(const json_t * to_sign,const json_t * sig_keys)223 jwk_sign(const json_t* to_sign, const json_t* sig_keys)
224 {
225 if (!sig_keys || !json_is_array(sig_keys) || !json_is_array(to_sign)) {
226 return NULL;
227 }
228
229 json_auto_t* to_sign_copy = json_deep_copy(to_sign);
230 if (!jose_jwk_pub(NULL, to_sign_copy)) {
231 fprintf(stderr, "Error removing private material from data to sign\n");
232 return NULL;
233 }
234
235 json_auto_t* payload = json_pack("{s:O}", "keys", to_sign_copy);
236 json_auto_t* sig_template = json_pack("{s:{s:s}}",
237 "protected", "cty", "jwk-set+json");
238
239 __attribute__ ((__cleanup__(cleanup_str))) char* data_to_sign = json_dumps(payload, 0);
240 json_auto_t* jws = json_pack("{s:o}", "payload",
241 jose_b64_enc(data_to_sign, strlen(data_to_sign)));
242
243 if (!jose_jws_sig(NULL, jws, sig_template, sig_keys)) {
244 fprintf(stderr, "Error trying to jose_jws_sign\n");
245 return NULL;
246 }
247 return json_incref(jws);
248 }
249
250 static json_t*
find_by_thp(struct tang_keys_info * tki,const char * target)251 find_by_thp(struct tang_keys_info* tki, const char* target)
252 {
253 if (!tki) {
254 return NULL;
255 }
256
257 json_auto_t* keys = json_deep_copy(tki->m_keys);
258 json_array_extend(keys, tki->m_rotated_keys);
259
260 size_t idx;
261 json_t* jwk;
262 const char** hashes = supported_hashes();
263 json_array_foreach(keys, idx, jwk) {
264 for (int i = 0; hashes[i]; i++) {
265 __attribute__ ((__cleanup__(cleanup_str))) char* thumbprint = jwk_thumbprint(jwk, hashes[i]);
266 if (strcmp(thumbprint, target) != 0) {
267 continue;
268 }
269
270 if (jwk_valid_for_deriving_keys(jwk)) {
271 return json_incref(jwk);
272 } else if (jwk_valid_for_signing(jwk)) {
273 json_auto_t* sign = json_deep_copy(tki->m_sign);
274 if (json_array_append(sign, jwk) == -1) {
275 return NULL;
276 }
277 json_auto_t* jws = jwk_sign(tki->m_payload, sign);
278 if (!jws) {
279 return NULL;
280 }
281 return json_incref(jws);
282 }
283 }
284 }
285 return NULL;
286 }
287
288 static int
prepare_payload_and_sign(struct tang_keys_info * tki)289 prepare_payload_and_sign(struct tang_keys_info* tki)
290 {
291 if (!tki) {
292 return 0;
293 }
294
295 size_t idx;
296 json_t* jwk;
297 json_array_foreach(tki->m_keys, idx, jwk) {
298 if (jwk_valid_for_signing_and_verifying(jwk)) {
299 if (json_array_append(tki->m_sign, jwk) == -1) {
300 continue;
301 }
302 if (json_array_append(tki->m_payload, jwk) == -1) {
303 continue;
304 }
305 } else if (jwk_valid_for_deriving_keys(jwk)) {
306 if (json_array_append(tki->m_payload, jwk) == -1) {
307 continue;
308 }
309 }
310 }
311 if (json_array_size(tki->m_sign) == 0 || json_array_size(tki->m_payload) == 0) {
312 return 0;
313 }
314 return 1;
315 }
316
317 static int
create_new_keys(const char * jwkdir)318 create_new_keys(const char* jwkdir)
319 {
320 const char* alg[] = {"ES512", "ECMR", NULL};
321 char path[PATH_MAX];
322 for (int i = 0; alg[i] != NULL; i++) {
323 json_auto_t* jwk = jwk_generate(alg[i]);
324 if (!jwk) {
325 return 0;
326 }
327 __attribute__ ((__cleanup__(cleanup_str))) char* thp = jwk_thumbprint(jwk, DEFAULT_THP_HASH);
328 if (!thp) {
329 return 0;
330 }
331 if (snprintf(path, PATH_MAX, "%s/%s.jwk", jwkdir, thp) < 0) {
332 fprintf(stderr, "Unable to prepare variable with file full path (%s)\n", thp);
333 return 0;
334 }
335 path[sizeof(path) - 1] = '\0';
336 if (json_dump_file(jwk, path, 0) == -1) {
337 fprintf(stderr, "Error saving JWK to file (%s)\n", path);
338 return 0;
339 }
340 }
341 return 1;
342 }
343
344 static struct tang_keys_info*
load_keys(const char * jwkdir)345 load_keys(const char* jwkdir)
346 {
347 struct tang_keys_info* tki = new_tang_keys_info();
348 if (!tki) {
349 return NULL;
350 }
351
352 struct dirent* d;
353 DIR* dir = opendir(jwkdir);
354 if (!dir) {
355 free_tang_keys_info(tki);
356 return NULL;
357 }
358
359 char filepath[PATH_MAX];
360 const char* pattern = ".jwk";
361 while ((d = readdir(dir)) != NULL) {
362 if (strcmp(d->d_name, ".") == 0 || strcmp(d->d_name, "..") == 0) {
363 continue;
364 }
365
366 char* dot = strrchr(d->d_name, '.');
367 if (!dot) {
368 continue;
369 }
370
371 if (strcmp(dot, pattern) == 0) {
372 /* Found a file with .jwk extension. */
373 if (snprintf(filepath, PATH_MAX, "%s/%s", jwkdir, d->d_name) < 0) {
374 fprintf(stderr, "Unable to prepare variable with file full path (%s); skipping\n", d->d_name);
375 continue;
376 }
377 filepath[sizeof(filepath) - 1] = '\0';
378 json_auto_t* json = json_load_file(filepath, 0, NULL);
379 if (!json) {
380 fprintf(stderr, "Invalid JSON file (%s); skipping\n", filepath);
381 continue;
382 }
383
384 json_t* arr = tki->m_keys;
385 if (d->d_name[0] == '.') {
386 arr = tki->m_rotated_keys;
387 tki->m_rotated_keys_count++;
388 } else {
389 tki->m_keys_count++;
390 }
391
392 if (json_array_append(arr, json) == -1) {
393 fprintf(stderr, "Unable to append JSON (%s) to array; skipping\n", d->d_name);
394 continue;
395 }
396 }
397 }
398 closedir(dir);
399 return tki;
400 }
401
402 struct tang_keys_info*
read_keys(const char * jwkdir)403 read_keys(const char* jwkdir)
404 {
405 struct tang_keys_info* tki = load_keys(jwkdir);
406 if (!tki) {
407 return NULL;
408 }
409
410 if (tki->m_keys_count == 0) {
411 /* Let's attempt to create a new pair of keys. */
412 free_tang_keys_info(tki);
413 if (!create_new_keys(jwkdir)) {
414 return NULL;
415 }
416 tki = load_keys(jwkdir);
417 }
418
419 if (!prepare_payload_and_sign(tki)) {
420 free_tang_keys_info(tki);
421 return NULL;
422 }
423 return tki;
424 }
425
426 json_t*
find_jws(struct tang_keys_info * tki,const char * thp)427 find_jws(struct tang_keys_info* tki, const char* thp)
428 {
429 if (!tki) {
430 return NULL;
431 }
432
433 if (thp == NULL) {
434 /* Default advertisement. */
435 json_auto_t* jws = jwk_sign(tki->m_payload, tki->m_sign);
436 if (!jws) {
437 return NULL;
438 }
439 return json_incref(jws);
440 }
441 return find_by_thp(tki, thp);
442 }
443
444 json_t*
find_jwk(struct tang_keys_info * tki,const char * thp)445 find_jwk(struct tang_keys_info* tki, const char* thp)
446 {
447 if (!tki || !thp) {
448 return NULL;
449 }
450 return find_by_thp(tki, thp);
451 }
452