1 /* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
2 /*
3  * Copyright 2016 Red Hat, Inc.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *     http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 #include <jose/b64.h>
19 #include <jose/jwk.h>
20 #include "hooks.h"
21 #include "misc.h"
22 #include "hsh.h"
23 
24 #include <stddef.h>
25 #include <stdlib.h>
26 #include <string.h>
27 
28 static bool
jwk_hook(jose_cfg_t * cfg,json_t * jwk,jose_hook_jwk_kind_t kind)29 jwk_hook(jose_cfg_t *cfg, json_t *jwk, jose_hook_jwk_kind_t kind)
30 {
31     for (const jose_hook_jwk_t *j = jose_hook_jwk_list(); j; j = j->next) {
32         if (j->kind != kind)
33             continue;
34 
35         switch (kind) {
36         case JOSE_HOOK_JWK_KIND_PREP:
37             if (j->prep.handles(cfg, jwk) && !j->prep.execute(cfg, jwk))
38                 return false;
39             break;
40 
41         case JOSE_HOOK_JWK_KIND_MAKE:
42             if (j->make.handles(cfg, jwk))
43                 return j->make.execute(cfg, jwk);
44             break;
45 
46         default:
47             continue;
48         }
49     }
50 
51     return kind == JOSE_HOOK_JWK_KIND_PREP;
52 }
53 
54 bool
jose_jwk_gen(jose_cfg_t * cfg,json_t * jwk)55 jose_jwk_gen(jose_cfg_t *cfg, json_t *jwk)
56 {
57     const json_t *ko = NULL;
58     const char *alg = NULL;
59     const char *kty = NULL;
60     const char *use = NULL;
61 
62     if (!jwk_hook(cfg, jwk, JOSE_HOOK_JWK_KIND_PREP))
63         return false;
64 
65     if (!jwk_hook(cfg, jwk, JOSE_HOOK_JWK_KIND_MAKE))
66         return false;
67 
68     if (json_unpack(jwk, "{s?s,s:s,s?s,s?o}",
69                     "alg", &alg, "kty", &kty, "use", &use, "key_ops", &ko) < 0)
70         return false;
71 
72     for (const jose_hook_alg_t *a = jose_hook_alg_list();
73          a && alg && !use && !ko; a = a->next) {
74         json_auto_t *ops = NULL;
75 
76         if (strcmp(alg, a->name) != 0)
77             continue;
78 
79         ops = json_array();
80         if (!ops)
81             return false;
82 
83         switch (a->kind) {
84         case JOSE_HOOK_ALG_KIND_SIGN:
85             if (json_array_append_new(ops, json_string("sign")) < 0)
86                 return false;
87             if (json_array_append_new(ops, json_string("verify")) < 0)
88                 return false;
89             break;
90         case JOSE_HOOK_ALG_KIND_WRAP:
91             if (json_array_append_new(ops, json_string("wrapKey")) < 0)
92                 return false;
93             if (json_array_append_new(ops, json_string("unwrapKey")) < 0)
94                 return false;
95             break;
96         case JOSE_HOOK_ALG_KIND_ENCR:
97             if (json_array_append_new(ops, json_string("encrypt")) < 0)
98                 return false;
99             if (json_array_append_new(ops, json_string("decrypt")) < 0)
100                 return false;
101             break;
102         case JOSE_HOOK_ALG_KIND_EXCH:
103             if (json_array_append_new(ops, json_string("deriveKey")) < 0)
104                 return false;
105             break;
106         default:
107             break;
108         }
109 
110         if (json_array_size(ops) > 0 &&
111             json_object_set(jwk, "key_ops", ops) < 0)
112             return false;
113 
114         break;
115     }
116 
117     for (const jose_hook_jwk_t *j = jose_hook_jwk_list(); j; j = j->next) {
118         if (j->kind != JOSE_HOOK_JWK_KIND_TYPE)
119             continue;
120 
121         if (strcmp(j->type.kty, kty) == 0) {
122             for (size_t i = 0; j->type.req[i]; i++) {
123                 if (!json_object_get(jwk, j->type.req[i]))
124                     return false;
125             }
126 
127             return true;
128         }
129     }
130 
131     return false;
132 }
133 
134 static bool
jwk_clean(jose_cfg_t * cfg,json_t * jwk)135 jwk_clean(jose_cfg_t *cfg, json_t *jwk)
136 {
137     const jose_hook_jwk_t *type = NULL;
138     const char *kty = NULL;
139     bool sym = false;
140 
141     if (json_unpack(jwk, "{s:s}", "kty", &kty) == -1)
142         return false;
143 
144     for (type = jose_hook_jwk_list(); type; type = type->next) {
145         if (type->kind != JOSE_HOOK_JWK_KIND_TYPE)
146             continue;
147 
148         if (strcasecmp(kty, type->type.kty) == 0)
149             break;
150     }
151 
152     if (!type)
153         return false;
154 
155     sym = !type->type.pub || !type->type.pub[0];
156 
157     for (size_t i = 0; type->type.prv[i]; i++) {
158         if (!json_object_get(jwk, type->type.prv[i]))
159             continue;
160 
161         if (json_object_del(jwk, type->type.prv[i]) == -1)
162             return false;
163     }
164 
165     for (const jose_hook_jwk_t *o = jose_hook_jwk_list(); o; o = o->next) {
166         json_t *arr = NULL;
167 
168         if (o->kind != JOSE_HOOK_JWK_KIND_OPER)
169             continue;
170 
171         if (!o->oper.prv && (!sym || !o->oper.pub))
172             continue;
173 
174         arr = json_object_get(jwk, "key_ops");
175         for (size_t i = 0; i < json_array_size(arr); i++) {
176             const char *ko = NULL;
177 
178             ko = json_string_value(json_array_get(arr, i));
179             if (!ko)
180                 continue;
181 
182             if ((!o->oper.prv || strcmp(o->oper.prv, ko) != 0) &&
183                 (!sym || !o->oper.pub || strcmp(o->oper.pub, ko) != 0))
184                 continue;
185 
186             if (json_array_remove(arr, i--) == -1)
187                 return false;
188         }
189     }
190 
191     return true;
192 }
193 
194 bool
jose_jwk_pub(jose_cfg_t * cfg,json_t * jwk)195 jose_jwk_pub(jose_cfg_t *cfg, json_t *jwk)
196 {
197     json_t *keys = NULL;
198 
199     if (json_is_array(jwk))
200         keys = jwk;
201     else if (json_is_array(json_object_get(jwk, "keys")))
202         keys = json_object_get(jwk, "keys");
203 
204     if (!keys)
205         return jwk_clean(cfg, jwk);
206 
207     for (size_t i = 0; i < json_array_size(keys); i++) {
208         if (!jwk_clean(cfg, json_array_get(keys, i)))
209             return false;
210     }
211 
212     return true;
213 }
214 
215 bool
jose_jwk_prm(jose_cfg_t * cfg,const json_t * jwk,bool req,const char * op)216 jose_jwk_prm(jose_cfg_t *cfg, const json_t *jwk, bool req, const char *op)
217 {
218     const char *use = NULL;
219     json_t *ko = NULL;
220 
221     if (!json_is_object(jwk))
222         return true;
223 
224     if (!op)
225         return false;
226 
227     if (json_unpack((json_t *) jwk, "{s?s,s?o}",
228                     "use", &use, "key_ops", &ko) != 0)
229         return false;
230 
231     if (!use && !ko)
232         return !req;
233 
234     for (size_t i = 0; i < json_array_size(ko); i++) {
235         json_t *v = json_array_get(ko, i);
236 
237         if (json_is_string(v) && strcmp(op, json_string_value(v)) == 0)
238             return true;
239     }
240 
241     for (const jose_hook_jwk_t *o = jose_hook_jwk_list(); use && o; o = o->next) {
242         if (o->kind != JOSE_HOOK_JWK_KIND_OPER)
243             continue;
244 
245         if (!o->oper.use || strcmp(use, o->oper.use) != 0)
246             continue;
247 
248         if (o->oper.pub && strcmp(op, o->oper.pub) == 0)
249             return true;
250 
251         if (o->oper.prv && strcmp(op, o->oper.prv) == 0)
252             return true;
253     }
254 
255     return false;
256 }
257 
258 static const jose_hook_jwk_t *
find_type(const json_t * jwk)259 find_type(const json_t *jwk)
260 {
261     const char *kty = NULL;
262 
263     if (json_unpack((json_t *) jwk, "{s:s}", "kty", &kty) < 0)
264         return NULL;
265 
266     for (const jose_hook_jwk_t *t = jose_hook_jwk_list(); t; t = t->next) {
267         if (t->kind != JOSE_HOOK_JWK_KIND_TYPE)
268             continue;
269         if (strcasecmp(kty, t->type.kty) == 0)
270             return t;
271     }
272 
273     return NULL;
274 }
275 
276 bool
jose_jwk_eql(jose_cfg_t * cfg,const json_t * a,const json_t * b)277 jose_jwk_eql(jose_cfg_t *cfg, const json_t *a, const json_t *b)
278 {
279     const jose_hook_jwk_t *type = NULL;
280 
281     type = find_type(a);
282     if (!type)
283         return false;
284 
285     if (!json_equal(json_object_get(a, "kty"), json_object_get(b, "kty")))
286         return false;
287 
288     for (size_t i = 0; type->type.req[i]; i++) {
289         json_t *aa = json_object_get(a, type->type.req[i]);
290         json_t *bb = json_object_get(b, type->type.req[i]);
291 
292         if (!aa || !bb || !json_equal(aa, bb))
293             return false;
294     }
295 
296     return true;
297 }
298 
299 static char *
jwk_str(const json_t * jwk)300 jwk_str(const json_t *jwk)
301 {
302     const jose_hook_jwk_t *type = NULL;
303     json_auto_t *key = NULL;
304 
305     type = find_type(jwk);
306     if (!type)
307         return NULL;
308 
309     key = json_object();
310     if (!key)
311         return NULL;
312 
313     if (json_object_set(key, "kty", json_object_get(jwk, "kty")) < 0)
314         return NULL;
315 
316     for (size_t i = 0; type->type.req[i]; i++) {
317         json_t *tmp = NULL;
318 
319         tmp = json_object_get(jwk, type->type.req[i]);
320         if (!tmp)
321             return NULL;
322 
323         if (json_object_set(key, type->type.req[i], tmp) < 0)
324             return NULL;
325     }
326 
327     return json_dumps(key, JSON_SORT_KEYS | JSON_COMPACT);
328 }
329 
330 json_t *
jose_jwk_thp(jose_cfg_t * cfg,const json_t * jwk,const char * hash)331 jose_jwk_thp(jose_cfg_t *cfg, const json_t *jwk, const char *hash)
332 {
333     json_t *thp = NULL;
334     char *str = NULL;
335 
336     str = jwk_str(jwk);
337     if (!str)
338         return NULL;
339 
340     thp = hsh(cfg, hash, str, strlen(str));
341     zero(str, strlen(str));
342     free(str);
343     return thp;
344 }
345 
346 size_t
jose_jwk_thp_buf(jose_cfg_t * cfg,const json_t * jwk,const char * alg,uint8_t * thp,size_t len)347 jose_jwk_thp_buf(jose_cfg_t *cfg, const json_t *jwk,
348                  const char *alg, uint8_t *thp, size_t len)
349 {
350     char *str = NULL;
351 
352     if (!thp || len == 0)
353         return hsh_buf(cfg, alg, NULL, 0, NULL, 0);
354 
355     str = jwk_str(jwk);
356     if (!str)
357         return SIZE_MAX;
358 
359     len = hsh_buf(cfg, alg, str, strlen(str), thp, len);
360     zero(str, strlen(str));
361     free(str);
362     return len;
363 }
364 
365 json_t *
jose_jwk_exc(jose_cfg_t * cfg,const json_t * prv,const json_t * pub)366 jose_jwk_exc(jose_cfg_t *cfg, const json_t *prv, const json_t *pub)
367 {
368     const char *alga = NULL;
369     const char *algb = NULL;
370     const char *ktya = NULL;
371     const char *ktyb = NULL;
372 
373     if (json_unpack((json_t *) prv, "{s:s,s?s}",
374                     "kty", &ktya, "alg", &alga) < 0) {
375         jose_cfg_err(cfg, JOSE_CFG_ERR_JWK_INVALID, "Private JWK is invalid");
376         return NULL;
377     }
378 
379     if (json_unpack((json_t *) pub, "{s:s,s?s}",
380                     "kty", &ktyb, "alg", &algb) < 0) {
381         jose_cfg_err(cfg, JOSE_CFG_ERR_JWK_INVALID, "Public JWK is invalid");
382         return NULL;
383     }
384 
385     if (strcmp(ktya, ktyb) != 0) {
386         jose_cfg_err(cfg, JOSE_CFG_ERR_JWK_MISMATCH,
387                      "Public and private JWKs are different types");
388         return NULL;
389     }
390 
391     if (alga && algb && strcmp(alga, algb) != 0) {
392         jose_cfg_err(cfg, JOSE_CFG_ERR_JWK_MISMATCH,
393                      "Public and private JWKs have different algorithms");
394         return NULL;
395     }
396 
397     for (const jose_hook_alg_t *a = jose_hook_alg_list();
398          !alga && !algb && a; a = a->next) {
399         if (a->kind != JOSE_HOOK_ALG_KIND_EXCH)
400             continue;
401 
402         alga = a->exch.sug(a, cfg, prv, pub);
403     }
404 
405     if (!alga && !algb) {
406         jose_cfg_err(cfg, JOSE_CFG_ERR_ALG_NOINFER,
407                      "Exchange algorithm cannot be inferred");
408         return NULL;
409     }
410 
411     for (const jose_hook_alg_t *a = jose_hook_alg_list(); a; a = a->next) {
412         if (a->kind != JOSE_HOOK_ALG_KIND_EXCH)
413             continue;
414 
415         if (strcmp(alga ? alga : algb, a->name) != 0)
416             continue;
417 
418         if (!jose_jwk_prm(cfg, prv, false, a->exch.prm)) {
419             jose_cfg_err(cfg, JOSE_CFG_ERR_JWK_DENIED,
420                          "Private JWK cannot be used to derive keys");
421             return NULL;
422         }
423 
424         if (!jose_jwk_prm(cfg, pub, false, a->exch.prm)) {
425             jose_cfg_err(cfg, JOSE_CFG_ERR_JWK_DENIED,
426                          "Public JWK cannot be used to derive keys");
427             return NULL;
428         }
429 
430         return a->exch.exc(a, cfg, prv, pub);
431     }
432 
433     jose_cfg_err(cfg, JOSE_CFG_ERR_ALG_NOTSUP,
434                  "Exchange algorithm %s is unsupported", alga ? alga : algb);
435     return NULL;
436 }
437 
438 static void __attribute__((constructor))
constructor(void)439 constructor(void)
440 {
441     static const char *oct_req[] = { "k", NULL };
442     static const char *oct_prv[] = { "k", NULL };
443 
444     static const char *rsa_req[] = { "e", "n", NULL };
445     static const char *rsa_pub[] = { "e", "n", NULL };
446     static const char *rsa_prv[] = { "p", "d", "q", "dp", "dq", "qi", "oth", NULL };
447 
448     static const char *ec_req[] = { "crv", "x", "y", NULL };
449     static const char *ec_pub[] = { "x", "y", NULL };
450     static const char *ec_prv[] = { "d", NULL };
451 
452     static jose_hook_jwk_t hooks[] = {
453         { .kind = JOSE_HOOK_JWK_KIND_TYPE,
454           .type = { .kty = "oct", .req = oct_req, .prv = oct_prv } },
455         { .kind = JOSE_HOOK_JWK_KIND_TYPE,
456           .type = { .kty = "RSA", .req = rsa_req, .pub = rsa_pub, .prv = rsa_prv } },
457         { .kind = JOSE_HOOK_JWK_KIND_TYPE,
458           .type = { .kty = "EC", .req = ec_req, .pub = ec_pub, .prv = ec_prv } },
459         { .kind = JOSE_HOOK_JWK_KIND_OPER,
460           .oper = { .pub = "verify", .prv = "sign", .use = "sig" } },
461         { .kind = JOSE_HOOK_JWK_KIND_OPER,
462           .oper = { .pub = "encrypt", .prv = "decrypt", .use = "enc" } },
463         { .kind = JOSE_HOOK_JWK_KIND_OPER,
464           .oper = { .pub = "wrapKey", .prv = "unwrapKey", .use = "enc" } },
465         { .kind = JOSE_HOOK_JWK_KIND_OPER,
466           .oper = { .pub = "deriveKey" } },
467         { .kind = JOSE_HOOK_JWK_KIND_OPER,
468           .oper = { .pub = "deriveBits" } },
469         {}
470     };
471 
472     for (size_t i = 0; hooks[i].kind != JOSE_HOOK_JWK_KIND_NONE; i++)
473         jose_hook_jwk_push(&hooks[i]);
474 }
475