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