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 #define _GNU_SOURCE
19 #include "misc.h"
20 #include <jose/b64.h>
21 #include <jose/jwk.h>
22 #include "../hooks.h"
23 #include <jose/openssl.h>
24 
25 #include <openssl/rand.h>
26 
27 #include <string.h>
28 
29 #define NAMES "ECDH-ES", "ECDH-ES+A128KW", "ECDH-ES+A192KW", "ECDH-ES+A256KW"
30 
31 #include <assert.h>
32 
33 static uint32_t
h2be32(uint32_t x)34 h2be32(uint32_t x)
35 {
36     union swap {
37         uint32_t i;
38         uint8_t  b[8];
39     } y;
40 
41     y.b[0] = x >> 0x18;
42     y.b[1] = x >> 0x10;
43     y.b[2] = x >> 0x08;
44     y.b[3] = x >> 0x00;
45 
46     return y.i;
47 }
48 
49 static bool
concatkdf(const jose_hook_alg_t * alg,jose_cfg_t * cfg,uint8_t dk[],size_t dkl,const uint8_t z[],size_t zl,...)50 concatkdf(const jose_hook_alg_t *alg, jose_cfg_t *cfg, uint8_t dk[], size_t dkl,
51           const uint8_t z[], size_t zl, ...)
52 {
53     jose_io_auto_t *b = NULL;
54     uint8_t hsh[alg->hash.size];
55     size_t hshl = sizeof(hsh);
56     size_t reps = 0;
57     size_t left = 0;
58 
59     reps = dkl / sizeof(hsh);
60     left = dkl % sizeof(hsh);
61 
62     b = jose_io_buffer(cfg, &hsh, &hshl);
63     if (!b)
64         return false;
65 
66     for (uint32_t c = 0; c <= reps; c++) {
67         uint32_t cnt = h2be32(c + 1);
68         jose_io_auto_t *h = NULL;
69         va_list ap;
70 
71         h = alg->hash.hsh(alg, cfg, b);
72         if (!h)
73             return false;
74 
75         if (!h->feed(h, &cnt, sizeof(cnt)))
76             return false;
77 
78         if (!h->feed(h, z, zl))
79             return false;
80 
81         va_start(ap, zl);
82         for (void *a = va_arg(ap, void *); a; a = va_arg(ap, void *)) {
83             size_t l = va_arg(ap, size_t);
84             uint32_t e = h2be32(l);
85 
86             if (!h->feed(h, &e, sizeof(e))) {
87                 va_end(ap);
88                 return false;
89             }
90 
91             if (!h->feed(h, a, l)) {
92                 va_end(ap);
93                 return false;
94             }
95         }
96         va_end(ap);
97 
98         if (!h->feed(h, &(uint32_t) { h2be32(dkl * 8) }, 4))
99             return false;
100 
101         if (!h->done(h))
102             return false;
103 
104         assert(hshl == alg->hash.size);
105 
106         memcpy(&dk[c * hshl], hsh, c == reps ? left : hshl);
107         OPENSSL_cleanse(hsh, sizeof(hsh));
108         hshl = 0;
109     }
110 
111     return true;
112 }
113 
114 static size_t
encr_alg_keylen(jose_cfg_t * cfg,const char * enc)115 encr_alg_keylen(jose_cfg_t *cfg, const char *enc)
116 {
117     json_auto_t *tmpl = NULL;
118 
119     if (!jose_hook_alg_find(JOSE_HOOK_ALG_KIND_ENCR, enc))
120         return SIZE_MAX;
121 
122     tmpl = json_pack("{s:s}", "alg", enc);
123     if (!tmpl)
124         return SIZE_MAX;
125 
126     for (const jose_hook_jwk_t *j = jose_hook_jwk_list(); j; j = j->next) {
127         const char *kty = NULL;
128         json_int_t len = 0;
129 
130         if (j->kind != JOSE_HOOK_JWK_KIND_PREP)
131             continue;
132 
133         if (!j->prep.handles(cfg, tmpl))
134             continue;
135 
136         if (!j->prep.execute(cfg, tmpl))
137             return SIZE_MAX;
138 
139         if (json_unpack(tmpl, "{s:s,s:I}", "kty", &kty, "bytes", &len) < 0)
140             return SIZE_MAX;
141 
142         if (strcmp(kty, "oct") != 0)
143             return SIZE_MAX;
144 
145         return len;
146     }
147 
148     return SIZE_MAX;
149 }
150 
151 static size_t
decode(const json_t * obj,const char * name,uint8_t * buf,size_t len)152 decode(const json_t *obj, const char *name, uint8_t *buf, size_t len)
153 {
154     const char *tmp = NULL;
155     size_t tmpl = 0;
156     size_t dlen = 0;
157 
158     if (json_unpack((json_t *) obj, "{s?s%}", name, &tmp, &tmpl) < 0)
159         return SIZE_MAX;
160 
161     if (!tmp)
162         return 0;
163 
164     dlen = jose_b64_dec_buf(tmp, tmpl, NULL, 0);
165     if (dlen > len)
166         return dlen;
167 
168     return jose_b64_dec_buf(tmp, tmpl, buf, len);
169 }
170 
171 static json_t *
derive(const jose_hook_alg_t * alg,jose_cfg_t * cfg,json_t * hdr,json_t * cek,const json_t * key)172 derive(const jose_hook_alg_t *alg, jose_cfg_t *cfg,
173        json_t *hdr, json_t *cek, const json_t *key)
174 {
175     const jose_hook_alg_t *halg = NULL;
176     const char *name = alg->name;
177     uint8_t pu[KEYMAX] = {};
178     uint8_t pv[KEYMAX] = {};
179     uint8_t dk[KEYMAX] = {};
180     uint8_t ky[KEYMAX] = {};
181     const char *enc = NULL;
182     json_t *out = NULL;
183     size_t dkl = 0;
184     size_t pul = 0;
185     size_t pvl = 0;
186     size_t kyl = 0;
187 
188     halg = jose_hook_alg_find(JOSE_HOOK_ALG_KIND_HASH, "S256");
189     if (!halg)
190         goto egress;
191 
192     if (json_unpack(hdr, "{s?s}", "enc", &enc) < 0)
193         goto egress;
194 
195     if (!enc && json_unpack(cek, "{s:s}", "alg", &enc) < 0)
196         goto egress;
197 
198     switch (str2enum(alg->name, NAMES, NULL)) {
199     case 0: dkl = encr_alg_keylen(cfg, enc); name = enc; break;
200     case 1: dkl = 16; break;
201     case 2: dkl = 24; break;
202     case 3: dkl = 32; break;
203     default:
204         goto egress;
205     }
206 
207     if (dkl < 16 || dkl > sizeof(dk))
208         goto egress;
209 
210     pul = decode(hdr, "apu", pu, sizeof(pu));
211     if (pul > sizeof(pu))
212         goto egress;
213 
214     pvl = decode(hdr, "apv", pv, sizeof(pv));
215     if (pvl > sizeof(pv))
216         goto egress;
217 
218     kyl = decode(key, "x", ky, sizeof(ky));
219     if (kyl > sizeof(ky))
220         goto egress;
221 
222     if (!concatkdf(halg, cfg,
223                    dk, dkl,
224                    ky, kyl,
225                    name, strlen(name),
226                    pu, pul,
227                    pv, pvl,
228                    NULL))
229         goto egress;
230 
231     out = json_pack("{s:s,s:s,s:o}", "kty", "oct", "alg", enc,
232                     "k", jose_b64_enc(dk, dkl));
233 
234 egress:
235     OPENSSL_cleanse(ky, sizeof(ky));
236     OPENSSL_cleanse(pu, sizeof(pu));
237     OPENSSL_cleanse(pv, sizeof(pv));
238     OPENSSL_cleanse(dk, sizeof(dk));
239     return out;
240 }
241 
242 static const char *
alg2crv(const char * alg)243 alg2crv(const char *alg)
244 {
245     switch (str2enum(alg, NAMES, NULL)) {
246     case 0: return "P-521";
247     case 1: return "P-256";
248     case 2: return "P-384";
249     case 3: return "P-521";
250     default: return NULL;
251     }
252 }
253 
254 static bool
jwk_prep_handles(jose_cfg_t * cfg,const json_t * jwk)255 jwk_prep_handles(jose_cfg_t *cfg, const json_t *jwk)
256 {
257     const char *alg = NULL;
258 
259     if (json_unpack((json_t *) jwk, "{s:s}", "alg", &alg) == -1)
260         return false;
261 
262     return alg2crv(alg) != NULL;
263 }
264 
265 static bool
jwk_prep_execute(jose_cfg_t * cfg,json_t * jwk)266 jwk_prep_execute(jose_cfg_t *cfg, json_t *jwk)
267 {
268     const char *alg = NULL;
269     const char *crv = NULL;
270     const char *kty = NULL;
271     const char *grp = NULL;
272 
273     if (json_unpack(jwk, "{s:s,s?s,s?s}",
274                     "alg", &alg, "kty", &kty, "crv", &crv) == -1)
275         return false;
276 
277     grp = alg2crv(alg);
278     if (!grp)
279         return false;
280 
281     if (kty && strcmp(kty, "EC") != 0)
282         return false;
283 
284     if (crv && strcmp(crv, grp) != 0)
285         return false;
286 
287     if (json_object_set_new(jwk, "kty", json_string("EC")) < 0)
288         return false;
289 
290     if (json_object_set_new(jwk, "crv", json_string(grp)) < 0)
291         return false;
292 
293     return true;
294 }
295 
296 static const char *
alg_wrap_alg(const jose_hook_alg_t * alg,jose_cfg_t * cfg,const json_t * jwk)297 alg_wrap_alg(const jose_hook_alg_t *alg, jose_cfg_t *cfg, const json_t *jwk)
298 {
299     const char *name = NULL;
300     const char *type = NULL;
301     const char *curv = NULL;
302 
303     if (json_unpack((json_t *) jwk, "{s?s,s?s,s?s}",
304                     "alg", &name, "kty", &type, "crv", &curv) < 0)
305         return NULL;
306 
307     if (name)
308         return str2enum(name, NAMES, NULL) != SIZE_MAX ? name : NULL;
309 
310     if (!type || strcmp(type, "EC") != 0)
311         return NULL;
312 
313     switch (str2enum(curv, "P-256", "P-384", "P-521", NULL)) {
314     case 0: return "ECDH-ES+A128KW";
315     case 1: return "ECDH-ES+A192KW";
316     case 2: return "ECDH-ES+A256KW";
317     default: return NULL;
318     }
319 }
320 
321 static const char *
alg_wrap_enc(const jose_hook_alg_t * alg,jose_cfg_t * cfg,const json_t * jwk)322 alg_wrap_enc(const jose_hook_alg_t *alg, jose_cfg_t *cfg, const json_t *jwk)
323 {
324     const char *crv = NULL;
325 
326     if (json_unpack((json_t *) jwk, "{s?s}", "crv", &crv) < 0)
327         return NULL;
328 
329     switch (str2enum(crv, "P-256", "P-384", "P-521", NULL)) {
330     case 0: return "A128CBC-HS256";
331     case 1: return "A192CBC-HS384";
332     case 2: return "A256CBC-HS512";
333     default: return NULL;
334     }
335 }
336 
337 static bool
alg_wrap_wrp(const jose_hook_alg_t * alg,jose_cfg_t * cfg,json_t * jwe,json_t * rcp,const json_t * jwk,json_t * cek)338 alg_wrap_wrp(const jose_hook_alg_t *alg, jose_cfg_t *cfg, json_t *jwe,
339              json_t *rcp, const json_t *jwk, json_t *cek)
340 {
341     const jose_hook_alg_t *ecdh = NULL;
342     json_auto_t *exc = NULL;
343     json_auto_t *hdr = NULL;
344     json_auto_t *epk = NULL;
345     json_auto_t *der = NULL;
346     const char *wrap = NULL;
347     json_t *h = NULL;
348 
349     if (json_object_get(cek, "k")) {
350         if (strcmp(alg->name, "ECDH-ES") == 0)
351             return false;
352     } else if (!jose_jwk_gen(cfg, cek)) {
353         return false;
354     }
355 
356     hdr = jose_jwe_hdr(jwe, rcp);
357     if (!hdr)
358         return false;
359 
360     h = json_object_get(rcp, "header");
361     if (!h && json_object_set_new(rcp, "header", h = json_object()) == -1)
362         return false;
363 
364     epk = json_pack("{s:s,s:O}", "kty", "EC", "crv",
365                     json_object_get(jwk, "crv"));
366     if (!epk)
367         return false;
368 
369     if (!jose_jwk_gen(cfg, epk))
370         return false;
371 
372     ecdh = jose_hook_alg_find(JOSE_HOOK_ALG_KIND_EXCH, "ECDH");
373     if (!ecdh)
374         return false;
375 
376     exc = ecdh->exch.exc(ecdh, cfg, epk, jwk);
377     if (!exc)
378         return false;
379 
380     if (!jose_jwk_pub(cfg, epk))
381         return false;
382 
383     if (json_object_set(h, "epk", epk) == -1)
384         return false;
385 
386     der = derive(alg, cfg, hdr, cek, exc);
387     if (!der)
388         return false;
389 
390     wrap = strchr(alg->name, '+');
391     if (wrap) {
392         const jose_hook_alg_t *kw = NULL;
393 
394         kw = jose_hook_alg_find(JOSE_HOOK_ALG_KIND_WRAP, &wrap[1]);
395         if (!kw)
396             return false;
397 
398         return kw->wrap.wrp(kw, cfg, jwe, rcp, der, cek);
399     }
400 
401     if (json_object_update(cek, der) < 0)
402         return false;
403 
404     return add_entity(jwe, rcp, "recipients", "header", "encrypted_key", NULL);
405 }
406 
407 static bool
alg_wrap_unw(const jose_hook_alg_t * alg,jose_cfg_t * cfg,const json_t * jwe,const json_t * rcp,const json_t * jwk,json_t * cek)408 alg_wrap_unw(const jose_hook_alg_t *alg, jose_cfg_t *cfg, const json_t *jwe,
409              const json_t *rcp, const json_t *jwk, json_t *cek)
410 {
411     const json_t *epk = NULL;
412     json_auto_t *exc = NULL;
413     json_auto_t *der = NULL;
414     json_auto_t *hdr = NULL;
415     const char *wrap = NULL;
416 
417     hdr = jose_jwe_hdr(jwe, rcp);
418     epk = json_object_get(hdr, "epk");
419     if (!hdr || !epk)
420         return false;
421 
422     /* If the JWK has a private key, perform the normal exchange. */
423     if (json_object_get(jwk, "d")) {
424         const jose_hook_alg_t *ecdh = NULL;
425 
426         ecdh = jose_hook_alg_find(JOSE_HOOK_ALG_KIND_EXCH, "ECDH");
427         if (!ecdh)
428             return false;
429 
430         exc = ecdh->exch.exc(ecdh, cfg, jwk, epk);
431 
432     /* Otherwise, allow external exchanges. */
433     } else if (json_equal(json_object_get(jwk, "crv"),
434                           json_object_get(epk, "crv"))) {
435         exc = json_deep_copy(jwk);
436     }
437     if (!exc)
438         return false;
439 
440     der = derive(alg, cfg, hdr, cek, exc);
441     if (!der)
442         return false;
443 
444     wrap = strchr(alg->name, '+');
445     if (wrap) {
446         const jose_hook_alg_t *kw = NULL;
447 
448         kw = jose_hook_alg_find(JOSE_HOOK_ALG_KIND_WRAP, &wrap[1]);
449         if (!kw)
450             return false;
451 
452         return kw->wrap.unw(kw, cfg, jwe, rcp, der, cek);
453     }
454 
455     return json_object_update(cek, der) == 0;
456 }
457 
458 static void __attribute__((constructor))
constructor(void)459 constructor(void)
460 {
461     static jose_hook_jwk_t jwk = {
462         .kind = JOSE_HOOK_JWK_KIND_PREP,
463         .prep.handles = jwk_prep_handles,
464         .prep.execute = jwk_prep_execute
465     };
466 
467     static jose_hook_alg_t algs[] = {
468         { .kind = JOSE_HOOK_ALG_KIND_WRAP,
469           .name = "ECDH-ES",
470           .wrap.eprm = "wrapKey",
471           .wrap.dprm = "unwrapKey",
472           .wrap.alg = alg_wrap_alg,
473           .wrap.enc = alg_wrap_enc,
474           .wrap.wrp = alg_wrap_wrp,
475           .wrap.unw = alg_wrap_unw },
476         { .kind = JOSE_HOOK_ALG_KIND_WRAP,
477           .name = "ECDH-ES+A128KW",
478           .wrap.eprm = "wrapKey",
479           .wrap.dprm = "unwrapKey",
480           .wrap.alg = alg_wrap_alg,
481           .wrap.enc = alg_wrap_enc,
482           .wrap.wrp = alg_wrap_wrp,
483           .wrap.unw = alg_wrap_unw },
484         { .kind = JOSE_HOOK_ALG_KIND_WRAP,
485           .name = "ECDH-ES+A192KW",
486           .wrap.eprm = "wrapKey",
487           .wrap.dprm = "unwrapKey",
488           .wrap.alg = alg_wrap_alg,
489           .wrap.enc = alg_wrap_enc,
490           .wrap.wrp = alg_wrap_wrp,
491           .wrap.unw = alg_wrap_unw },
492         { .kind = JOSE_HOOK_ALG_KIND_WRAP,
493           .name = "ECDH-ES+A256KW",
494           .wrap.eprm = "wrapKey",
495           .wrap.dprm = "unwrapKey",
496           .wrap.alg = alg_wrap_alg,
497           .wrap.enc = alg_wrap_enc,
498           .wrap.wrp = alg_wrap_wrp,
499           .wrap.unw = alg_wrap_unw },
500         {}
501     };
502 
503     jose_hook_jwk_push(&jwk);
504     for (size_t i = 0; algs[i].name; i++)
505         jose_hook_alg_push(&algs[i]);
506 }
507