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