diff options
Diffstat (limited to 'src/reclaim/oidc_helper.c')
-rw-r--r-- | src/reclaim/oidc_helper.c | 1030 |
1 files changed, 0 insertions, 1030 deletions
diff --git a/src/reclaim/oidc_helper.c b/src/reclaim/oidc_helper.c deleted file mode 100644 index cfa71b26c..000000000 --- a/src/reclaim/oidc_helper.c +++ /dev/null | |||
@@ -1,1030 +0,0 @@ | |||
1 | /* | ||
2 | This file is part of GNUnet | ||
3 | Copyright (C) 2010-2015 GNUnet e.V. | ||
4 | |||
5 | GNUnet is free software: you can redistribute it and/or modify it | ||
6 | under the terms of the GNU Affero General Public License as published | ||
7 | by the Free Software Foundation, either version 3 of the License, | ||
8 | or (at your option) any later version. | ||
9 | |||
10 | GNUnet is distributed in the hope that it will be useful, but | ||
11 | WITHOUT ANY WARRANTY; without even the implied warranty of | ||
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
13 | Affero General Public License for more details. | ||
14 | |||
15 | You should have received a copy of the GNU Affero General Public License | ||
16 | along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
17 | |||
18 | SPDX-License-Identifier: AGPL3.0-or-later | ||
19 | */ | ||
20 | |||
21 | /** | ||
22 | * @file reclaim/oidc_helper.c | ||
23 | * @brief helper library for OIDC related functions | ||
24 | * @author Martin Schanzenbach | ||
25 | * @author Tristan Schwieren | ||
26 | */ | ||
27 | #include "platform.h" | ||
28 | #include <inttypes.h> | ||
29 | #include <jansson.h> | ||
30 | #include <jose/jose.h> | ||
31 | #include "gnunet_util_lib.h" | ||
32 | #include "gnunet_reclaim_lib.h" | ||
33 | #include "gnunet_reclaim_service.h" | ||
34 | #include "gnunet_signatures.h" | ||
35 | #include "oidc_helper.h" | ||
36 | // #include "benchmark.h" | ||
37 | #include <gcrypt.h> | ||
38 | |||
39 | GNUNET_NETWORK_STRUCT_BEGIN | ||
40 | |||
41 | /** | ||
42 | * The signature used to generate the authorization code | ||
43 | */ | ||
44 | struct OIDC_Parameters | ||
45 | { | ||
46 | /** | ||
47 | * The reclaim ticket | ||
48 | */ | ||
49 | struct GNUNET_RECLAIM_Ticket ticket; | ||
50 | |||
51 | /** | ||
52 | * The nonce length | ||
53 | */ | ||
54 | uint32_t nonce_len GNUNET_PACKED; | ||
55 | |||
56 | /** | ||
57 | * The length of the PKCE code_challenge | ||
58 | */ | ||
59 | uint32_t code_challenge_len GNUNET_PACKED; | ||
60 | |||
61 | /** | ||
62 | * The length of the attributes list | ||
63 | */ | ||
64 | uint32_t attr_list_len GNUNET_PACKED; | ||
65 | |||
66 | /** | ||
67 | * The length of the presentation list | ||
68 | */ | ||
69 | uint32_t pres_list_len GNUNET_PACKED; | ||
70 | }; | ||
71 | |||
72 | GNUNET_NETWORK_STRUCT_END | ||
73 | |||
74 | /** | ||
75 | * Standard claims represented by the "profile" scope in OIDC | ||
76 | */ | ||
77 | static char OIDC_profile_claims[14][32] = { | ||
78 | "name", "family_name", "given_name", "middle_name", "nickname", | ||
79 | "preferred_username", "profile", "picture", "website", "gender", "birthdate", | ||
80 | "zoneinfo", "locale", "updated_at" | ||
81 | }; | ||
82 | |||
83 | /** | ||
84 | * Standard claims represented by the "email" scope in OIDC | ||
85 | */ | ||
86 | static char OIDC_email_claims[2][16] = { | ||
87 | "email", "email_verified" | ||
88 | }; | ||
89 | |||
90 | /** | ||
91 | * Standard claims represented by the "phone" scope in OIDC | ||
92 | */ | ||
93 | static char OIDC_phone_claims[2][32] = { | ||
94 | "phone_number", "phone_number_verified" | ||
95 | }; | ||
96 | |||
97 | /** | ||
98 | * Standard claims represented by the "address" scope in OIDC | ||
99 | */ | ||
100 | static char OIDC_address_claims[5][32] = { | ||
101 | "street_address", "locality", "region", "postal_code", "country" | ||
102 | }; | ||
103 | |||
104 | static enum GNUNET_GenericReturnValue | ||
105 | is_claim_in_address_scope (const char *claim) | ||
106 | { | ||
107 | int i; | ||
108 | for (i = 0; i < 5; i++) | ||
109 | { | ||
110 | if (0 == strcmp (claim, OIDC_address_claims[i])) | ||
111 | { | ||
112 | return GNUNET_YES; | ||
113 | } | ||
114 | } | ||
115 | return GNUNET_NO; | ||
116 | } | ||
117 | |||
118 | |||
119 | static char * | ||
120 | create_jwt_hmac_header (void) | ||
121 | { | ||
122 | json_t *root; | ||
123 | char *json_str; | ||
124 | |||
125 | root = json_object (); | ||
126 | json_object_set_new (root, JWT_ALG, json_string (JWT_ALG_VALUE_HMAC)); | ||
127 | json_object_set_new (root, JWT_TYP, json_string (JWT_TYP_VALUE)); | ||
128 | |||
129 | json_str = json_dumps (root, JSON_INDENT (0) | JSON_COMPACT); | ||
130 | json_decref (root); | ||
131 | return json_str; | ||
132 | } | ||
133 | |||
134 | |||
135 | static void | ||
136 | replace_char (char *str, char find, char replace) | ||
137 | { | ||
138 | char *current_pos = strchr (str, find); | ||
139 | |||
140 | while (current_pos) | ||
141 | { | ||
142 | *current_pos = replace; | ||
143 | current_pos = strchr (current_pos, find); | ||
144 | } | ||
145 | } | ||
146 | |||
147 | |||
148 | // RFC4648 | ||
149 | static void | ||
150 | fix_base64 (char *str) | ||
151 | { | ||
152 | // Replace + with - | ||
153 | replace_char (str, '+', '-'); | ||
154 | |||
155 | // Replace / with _ | ||
156 | replace_char (str, '/', '_'); | ||
157 | } | ||
158 | |||
159 | |||
160 | static json_t* | ||
161 | generate_userinfo_json (const struct GNUNET_IDENTITY_PublicKey *sub_key, | ||
162 | const struct GNUNET_RECLAIM_AttributeList *attrs, | ||
163 | const struct | ||
164 | GNUNET_RECLAIM_PresentationList *presentations) | ||
165 | { | ||
166 | struct GNUNET_RECLAIM_AttributeListEntry *le; | ||
167 | struct GNUNET_RECLAIM_PresentationListEntry *ple; | ||
168 | char *subject; | ||
169 | char *source_name; | ||
170 | char *attr_val_str; | ||
171 | char *pres_val_str; | ||
172 | json_t *body; | ||
173 | json_t *aggr_names; | ||
174 | json_t *aggr_sources; | ||
175 | json_t *aggr_sources_jwt; | ||
176 | json_t *addr_claim = NULL; | ||
177 | int num_presentations = 0; | ||
178 | for (le = attrs->list_head; NULL != le; le = le->next) | ||
179 | { | ||
180 | if (GNUNET_NO == GNUNET_RECLAIM_id_is_zero (&le->attribute->credential)) | ||
181 | num_presentations++; | ||
182 | } | ||
183 | |||
184 | subject = | ||
185 | GNUNET_STRINGS_data_to_string_alloc (sub_key, | ||
186 | sizeof(struct | ||
187 | GNUNET_IDENTITY_PublicKey)); | ||
188 | body = json_object (); | ||
189 | aggr_names = json_object (); | ||
190 | aggr_sources = json_object (); | ||
191 | |||
192 | // iss REQUIRED case sensitive server uri with https | ||
193 | // The issuer is the local reclaim instance (e.g. | ||
194 | // https://reclaim.id/api/openid) | ||
195 | json_object_set_new (body, "iss", json_string (SERVER_ADDRESS)); | ||
196 | // sub REQUIRED public key identity, not exceed 255 ASCII length | ||
197 | json_object_set_new (body, "sub", json_string (subject)); | ||
198 | GNUNET_free (subject); | ||
199 | pres_val_str = NULL; | ||
200 | source_name = NULL; | ||
201 | int i = 0; | ||
202 | for (ple = presentations->list_head; NULL != ple; ple = ple->next) | ||
203 | { | ||
204 | // New presentation | ||
205 | GNUNET_asprintf (&source_name, | ||
206 | "src%d", | ||
207 | i); | ||
208 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, | ||
209 | "Adding new presentation source #%d\n", i); | ||
210 | aggr_sources_jwt = json_object (); | ||
211 | pres_val_str = | ||
212 | GNUNET_RECLAIM_presentation_value_to_string (ple->presentation->type, | ||
213 | ple->presentation->data, | ||
214 | ple->presentation->data_size); | ||
215 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, | ||
216 | "Presentation is: %s\n", pres_val_str); | ||
217 | json_object_set_new (aggr_sources_jwt, | ||
218 | GNUNET_RECLAIM_presentation_number_to_typename ( | ||
219 | ple->presentation->type), | ||
220 | json_string (pres_val_str) ); | ||
221 | json_object_set_new (aggr_sources, source_name, aggr_sources_jwt); | ||
222 | GNUNET_free (pres_val_str); | ||
223 | GNUNET_free (source_name); | ||
224 | source_name = NULL; | ||
225 | i++; | ||
226 | } | ||
227 | |||
228 | int addr_is_aggregated = GNUNET_NO; | ||
229 | int addr_is_normal = GNUNET_NO; | ||
230 | for (le = attrs->list_head; NULL != le; le = le->next) | ||
231 | { | ||
232 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, | ||
233 | "Processing %s for userinfo body\n", | ||
234 | le->attribute->name); | ||
235 | if (GNUNET_YES == GNUNET_RECLAIM_id_is_zero (&le->attribute->credential)) | ||
236 | { | ||
237 | attr_val_str = | ||
238 | GNUNET_RECLAIM_attribute_value_to_string (le->attribute->type, | ||
239 | le->attribute->data, | ||
240 | le->attribute->data_size); | ||
241 | /** | ||
242 | * There is this weird quirk that the individual address claim(s) must be | ||
243 | * inside a JSON object of the "address" claim. | ||
244 | */ | ||
245 | if (GNUNET_YES == is_claim_in_address_scope (le->attribute->name)) | ||
246 | { | ||
247 | if (GNUNET_YES == addr_is_aggregated) | ||
248 | { | ||
249 | GNUNET_log (GNUNET_ERROR_TYPE_WARNING, | ||
250 | "Address is set as aggregated claim. Skipping self-issued value...\n"); | ||
251 | GNUNET_free (attr_val_str); | ||
252 | continue; | ||
253 | } | ||
254 | addr_is_normal = GNUNET_YES; | ||
255 | |||
256 | if (NULL == addr_claim) | ||
257 | { | ||
258 | addr_claim = json_object (); | ||
259 | json_object_set_new (body, "address", addr_claim); | ||
260 | } | ||
261 | json_object_set_new (addr_claim, le->attribute->name, | ||
262 | json_string (attr_val_str)); | ||
263 | |||
264 | } | ||
265 | else | ||
266 | { | ||
267 | json_object_set_new (body, le->attribute->name, | ||
268 | json_string (attr_val_str)); | ||
269 | } | ||
270 | GNUNET_free (attr_val_str); | ||
271 | } | ||
272 | else | ||
273 | { | ||
274 | // Check if presentation is there | ||
275 | int j = 0; | ||
276 | for (ple = presentations->list_head; NULL != ple; ple = ple->next) | ||
277 | { | ||
278 | if (GNUNET_YES == | ||
279 | GNUNET_RECLAIM_id_is_equal (&ple->presentation->credential_id, | ||
280 | &le->attribute->credential)) | ||
281 | break; | ||
282 | j++; | ||
283 | } | ||
284 | if (NULL == ple) | ||
285 | { | ||
286 | GNUNET_log (GNUNET_ERROR_TYPE_WARNING, | ||
287 | "Presentation for `%s' missing...\n", | ||
288 | le->attribute->name); | ||
289 | continue; | ||
290 | } | ||
291 | /** | ||
292 | * There is this weird quirk that the individual address claim(s) must be | ||
293 | * inside a JSON object of the "address" claim. | ||
294 | */ | ||
295 | if (GNUNET_YES == is_claim_in_address_scope (le->attribute->name)) | ||
296 | { | ||
297 | if (GNUNET_YES == addr_is_normal) | ||
298 | { | ||
299 | GNUNET_log (GNUNET_ERROR_TYPE_WARNING, | ||
300 | "Address is already set as normal claim. Skipping attested value...\n"); | ||
301 | continue; | ||
302 | } | ||
303 | addr_is_aggregated = GNUNET_YES; | ||
304 | /** This is/can only be set once! **/ | ||
305 | if (NULL != addr_claim) | ||
306 | continue; | ||
307 | addr_claim = json_object (); | ||
308 | GNUNET_asprintf (&source_name, | ||
309 | "src%d", | ||
310 | j); | ||
311 | json_object_set_new (aggr_names, "address", | ||
312 | json_string (source_name)); | ||
313 | GNUNET_free (source_name); | ||
314 | } | ||
315 | else | ||
316 | { | ||
317 | // Presentation exists, hence take the respective source str | ||
318 | GNUNET_asprintf (&source_name, | ||
319 | "src%d", | ||
320 | j); | ||
321 | json_object_set_new (aggr_names, le->attribute->name, | ||
322 | json_string (source_name)); | ||
323 | GNUNET_free (source_name); | ||
324 | } | ||
325 | } | ||
326 | } | ||
327 | if (0 != i) | ||
328 | { | ||
329 | json_object_set_new (body, "_claim_names", aggr_names); | ||
330 | json_object_set_new (body, "_claim_sources", aggr_sources); | ||
331 | } | ||
332 | |||
333 | return body; | ||
334 | } | ||
335 | |||
336 | |||
337 | /** | ||
338 | * Generate userinfo JSON as string | ||
339 | * | ||
340 | * @param sub_key the subject (user) | ||
341 | * @param attrs user attribute list | ||
342 | * @param presentations credential presentation list (may be empty) | ||
343 | * @return Userinfo JSON | ||
344 | */ | ||
345 | char * | ||
346 | OIDC_generate_userinfo (const struct GNUNET_IDENTITY_PublicKey *sub_key, | ||
347 | const struct GNUNET_RECLAIM_AttributeList *attrs, | ||
348 | const struct | ||
349 | GNUNET_RECLAIM_PresentationList *presentations) | ||
350 | { | ||
351 | char *body_str; | ||
352 | json_t*body = generate_userinfo_json (sub_key, | ||
353 | attrs, | ||
354 | presentations); | ||
355 | body_str = json_dumps (body, JSON_INDENT (0) | JSON_COMPACT); | ||
356 | json_decref (body); | ||
357 | return body_str; | ||
358 | } | ||
359 | |||
360 | |||
361 | char * | ||
362 | generate_id_token_body (const struct GNUNET_IDENTITY_PublicKey *aud_key, | ||
363 | const struct GNUNET_IDENTITY_PublicKey *sub_key, | ||
364 | const struct GNUNET_RECLAIM_AttributeList *attrs, | ||
365 | const struct | ||
366 | GNUNET_RECLAIM_PresentationList *presentations, | ||
367 | const struct GNUNET_TIME_Relative *expiration_time, | ||
368 | const char *nonce) | ||
369 | { | ||
370 | struct GNUNET_HashCode signature; | ||
371 | struct GNUNET_TIME_Absolute exp_time; | ||
372 | struct GNUNET_TIME_Absolute time_now; | ||
373 | json_t *body; | ||
374 | char *audience; | ||
375 | char *subject; | ||
376 | char *body_str; | ||
377 | |||
378 | body = generate_userinfo_json (sub_key, | ||
379 | attrs, | ||
380 | presentations); | ||
381 | // iat REQUIRED time now | ||
382 | time_now = GNUNET_TIME_absolute_get (); | ||
383 | // exp REQUIRED time expired from config | ||
384 | exp_time = GNUNET_TIME_absolute_add (time_now, *expiration_time); | ||
385 | // auth_time only if max_age | ||
386 | // nonce only if nonce | ||
387 | // OPTIONAL acr,amr,azp | ||
388 | subject = | ||
389 | GNUNET_STRINGS_data_to_string_alloc (sub_key, | ||
390 | sizeof(struct | ||
391 | GNUNET_IDENTITY_PublicKey)); | ||
392 | audience = | ||
393 | GNUNET_STRINGS_data_to_string_alloc (aud_key, | ||
394 | sizeof(struct | ||
395 | GNUNET_IDENTITY_PublicKey)); | ||
396 | |||
397 | // aud REQUIRED public key client_id must be there | ||
398 | json_object_set_new (body, "aud", json_string (audience)); | ||
399 | // iat | ||
400 | json_object_set_new (body, | ||
401 | "iat", | ||
402 | json_integer (time_now.abs_value_us / (1000 * 1000))); | ||
403 | // exp | ||
404 | json_object_set_new (body, | ||
405 | "exp", | ||
406 | json_integer (exp_time.abs_value_us / (1000 * 1000))); | ||
407 | // nbf | ||
408 | json_object_set_new (body, | ||
409 | "nbf", | ||
410 | json_integer (time_now.abs_value_us / (1000 * 1000))); | ||
411 | // nonce | ||
412 | if (NULL != nonce) | ||
413 | json_object_set_new (body, "nonce", json_string (nonce)); | ||
414 | |||
415 | // Error checking | ||
416 | body_str = json_dumps (body, JSON_INDENT (2) | JSON_COMPACT); | ||
417 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,"ID-Token: %s\n", body_str); | ||
418 | |||
419 | json_decref (body); | ||
420 | GNUNET_free (subject); | ||
421 | GNUNET_free (audience); | ||
422 | |||
423 | return body_str; | ||
424 | } | ||
425 | |||
426 | |||
427 | /** | ||
428 | * Create a JWT using RSA256 algorithm from attributes | ||
429 | * | ||
430 | * @param aud_key the public of the audience | ||
431 | * @param sub_key the public key of the subject | ||
432 | * @param attrs the attribute list | ||
433 | * @param presentations credential presentation list (may be empty) | ||
434 | * @param expiration_time the validity of the token | ||
435 | * @param secret_rsa_key the key used to sign the JWT | ||
436 | * @return a new base64-encoded JWT string. | ||
437 | */ | ||
438 | char * | ||
439 | OIDC_generate_id_token_rsa (const struct GNUNET_IDENTITY_PublicKey *aud_key, | ||
440 | const struct GNUNET_IDENTITY_PublicKey *sub_key, | ||
441 | const struct GNUNET_RECLAIM_AttributeList *attrs, | ||
442 | const struct | ||
443 | GNUNET_RECLAIM_PresentationList *presentations, | ||
444 | const struct GNUNET_TIME_Relative *expiration_time, | ||
445 | const char *nonce, | ||
446 | const json_t *secret_rsa_key) | ||
447 | { | ||
448 | json_t *jws; | ||
449 | char *body_str; | ||
450 | char *result; | ||
451 | |||
452 | // Generate the body of the JSON Web Signature | ||
453 | body_str = generate_id_token_body (aud_key, | ||
454 | sub_key, | ||
455 | attrs, | ||
456 | presentations, | ||
457 | expiration_time, | ||
458 | nonce); | ||
459 | |||
460 | if (! body_str) | ||
461 | { | ||
462 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, | ||
463 | "Body for the JWS could not be generated\n"); | ||
464 | } | ||
465 | |||
466 | // Creating the JSON Web Signature. | ||
467 | jws = json_pack ("{s:o}", "payload", | ||
468 | jose_b64_enc (body_str, strlen (body_str))); | ||
469 | |||
470 | if (! jose_jws_sig (NULL, jws, NULL, secret_rsa_key)) | ||
471 | { | ||
472 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, | ||
473 | "Signature generation failed\n"); | ||
474 | } | ||
475 | |||
476 | // Encoding JSON as compact JSON Web Signature | ||
477 | GNUNET_asprintf (&result, "%s.%s.%s", | ||
478 | json_string_value (json_object_get (jws, "protected")), | ||
479 | json_string_value (json_object_get (jws, "payload")), | ||
480 | json_string_value (json_object_get (jws, "signature")) ); | ||
481 | |||
482 | json_decref(jws); | ||
483 | GNUNET_free(body_str); | ||
484 | return result; | ||
485 | } | ||
486 | |||
487 | /** | ||
488 | * Create a JWT using HMAC (HS256) from attributes | ||
489 | * | ||
490 | * @param aud_key the public of the audience | ||
491 | * @param sub_key the public key of the subject | ||
492 | * @param attrs the attribute list | ||
493 | * @param presentations credential presentation list (may be empty) | ||
494 | * @param expiration_time the validity of the token | ||
495 | * @param secret_key the key used to sign the JWT | ||
496 | * @return a new base64-encoded JWT string. | ||
497 | */ | ||
498 | char * | ||
499 | OIDC_generate_id_token_hmac (const struct GNUNET_IDENTITY_PublicKey *aud_key, | ||
500 | const struct GNUNET_IDENTITY_PublicKey *sub_key, | ||
501 | const struct GNUNET_RECLAIM_AttributeList *attrs, | ||
502 | const struct | ||
503 | GNUNET_RECLAIM_PresentationList *presentations, | ||
504 | const struct GNUNET_TIME_Relative *expiration_time, | ||
505 | const char *nonce, | ||
506 | const char *secret_key) | ||
507 | { | ||
508 | struct GNUNET_HashCode signature; | ||
509 | struct GNUNET_TIME_Absolute exp_time; | ||
510 | struct GNUNET_TIME_Absolute time_now; | ||
511 | char *header; | ||
512 | char *header_base64; | ||
513 | char *body_str; | ||
514 | char *body_base64; | ||
515 | char *signature_target; | ||
516 | char *signature_base64; | ||
517 | char *result; | ||
518 | |||
519 | // Generate and encode Header | ||
520 | header = create_jwt_hmac_header (); | ||
521 | GNUNET_STRINGS_base64url_encode (header, strlen (header), &header_base64); | ||
522 | fix_base64 (header_base64); | ||
523 | |||
524 | // Generate and encode the body of the JSON Web Signature | ||
525 | body_str = generate_id_token_body (aud_key, | ||
526 | sub_key, | ||
527 | attrs, | ||
528 | presentations, | ||
529 | expiration_time, | ||
530 | nonce); | ||
531 | |||
532 | if (! body_str) | ||
533 | { | ||
534 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, | ||
535 | "Body for the JWS could not be generated\n"); | ||
536 | } | ||
537 | |||
538 | GNUNET_STRINGS_base64url_encode (body_str, strlen (body_str), &body_base64); | ||
539 | fix_base64 (body_base64); | ||
540 | |||
541 | /** | ||
542 | * Creating the JWT signature. This might not be | ||
543 | * standards compliant, check. | ||
544 | */ | ||
545 | GNUNET_asprintf (&signature_target, "%s.%s", header_base64, body_base64); | ||
546 | GNUNET_CRYPTO_hmac_raw (secret_key, | ||
547 | strlen (secret_key), | ||
548 | signature_target, | ||
549 | strlen (signature_target), | ||
550 | &signature); | ||
551 | GNUNET_STRINGS_base64url_encode ((const char *) &signature, | ||
552 | sizeof(struct GNUNET_HashCode), | ||
553 | &signature_base64); | ||
554 | fix_base64 (signature_base64); | ||
555 | |||
556 | GNUNET_asprintf (&result, | ||
557 | "%s.%s.%s", | ||
558 | header_base64, | ||
559 | body_base64, | ||
560 | signature_base64); | ||
561 | |||
562 | GNUNET_free (header); | ||
563 | GNUNET_free (header_base64); | ||
564 | GNUNET_free (body_str); | ||
565 | GNUNET_free (body_base64); | ||
566 | GNUNET_free (signature_target); | ||
567 | GNUNET_free (signature_base64); | ||
568 | return result; | ||
569 | } | ||
570 | |||
571 | |||
572 | /** | ||
573 | * Builds an OIDC authorization code including | ||
574 | * a reclaim ticket and nonce | ||
575 | * | ||
576 | * @param issuer the issuer of the ticket, used to sign the ticket and nonce | ||
577 | * @param ticket the ticket to include in the code | ||
578 | * @param attrs list of attributes which are shared | ||
579 | * @param presentations credential presentation list (may be empty) | ||
580 | * @param nonce the nonce to include in the code | ||
581 | * @param code_challenge PKCE code challenge | ||
582 | * @return a new authorization code (caller must free) | ||
583 | */ | ||
584 | char * | ||
585 | OIDC_build_authz_code (const struct GNUNET_IDENTITY_PrivateKey *issuer, | ||
586 | const struct GNUNET_RECLAIM_Ticket *ticket, | ||
587 | const struct GNUNET_RECLAIM_AttributeList *attrs, | ||
588 | const struct | ||
589 | GNUNET_RECLAIM_PresentationList *presentations, | ||
590 | const char *nonce_str, | ||
591 | const char *code_challenge) | ||
592 | { | ||
593 | struct OIDC_Parameters params; | ||
594 | char *code_payload; | ||
595 | char *payload; | ||
596 | char *tmp; | ||
597 | char *code_str; | ||
598 | char *buf_ptr = NULL; | ||
599 | size_t payload_len; | ||
600 | size_t code_payload_len; | ||
601 | size_t attr_list_len = 0; | ||
602 | size_t pres_list_len = 0; | ||
603 | size_t code_challenge_len = 0; | ||
604 | uint32_t nonce_len = 0; | ||
605 | struct GNUNET_CRYPTO_EccSignaturePurpose *purpose; | ||
606 | |||
607 | /** PLAINTEXT **/ | ||
608 | // Assign ticket | ||
609 | memset (¶ms, 0, sizeof(params)); | ||
610 | params.ticket = *ticket; | ||
611 | // Assign nonce | ||
612 | payload_len = sizeof(struct OIDC_Parameters); | ||
613 | if ((NULL != nonce_str) && (strcmp ("", nonce_str) != 0)) | ||
614 | { | ||
615 | nonce_len = strlen (nonce_str); | ||
616 | payload_len += nonce_len; | ||
617 | } | ||
618 | params.nonce_len = htonl (nonce_len); | ||
619 | // Assign code challenge | ||
620 | if (NULL != code_challenge) | ||
621 | code_challenge_len = strlen (code_challenge); | ||
622 | payload_len += code_challenge_len; | ||
623 | params.code_challenge_len = htonl (code_challenge_len); | ||
624 | // Assign attributes | ||
625 | if (NULL != attrs) | ||
626 | { | ||
627 | // Get length | ||
628 | attr_list_len = GNUNET_RECLAIM_attribute_list_serialize_get_size (attrs); | ||
629 | params.attr_list_len = htonl (attr_list_len); | ||
630 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, | ||
631 | "Length of serialized attributes: %lu\n", | ||
632 | attr_list_len); | ||
633 | // Get serialized attributes | ||
634 | payload_len += attr_list_len; | ||
635 | } | ||
636 | if (NULL != presentations) | ||
637 | { | ||
638 | // Get length | ||
639 | // FIXME only add presentations relevant for attribute list!!! | ||
640 | // This is important because of the distinction between id_token and | ||
641 | // userinfo in OIDC | ||
642 | pres_list_len = | ||
643 | GNUNET_RECLAIM_presentation_list_serialize_get_size (presentations); | ||
644 | params.pres_list_len = htonl (pres_list_len); | ||
645 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, | ||
646 | "Length of serialized presentations: %lu\n", | ||
647 | pres_list_len); | ||
648 | // Get serialized attributes | ||
649 | payload_len += pres_list_len; | ||
650 | } | ||
651 | |||
652 | // Get plaintext length | ||
653 | payload = GNUNET_malloc (payload_len); | ||
654 | memcpy (payload, ¶ms, sizeof(params)); | ||
655 | tmp = payload + sizeof(params); | ||
656 | if (0 < code_challenge_len) | ||
657 | { | ||
658 | memcpy (tmp, code_challenge, code_challenge_len); | ||
659 | tmp += code_challenge_len; | ||
660 | } | ||
661 | if (0 < nonce_len) | ||
662 | { | ||
663 | memcpy (tmp, nonce_str, nonce_len); | ||
664 | tmp += nonce_len; | ||
665 | } | ||
666 | if (0 < attr_list_len) | ||
667 | GNUNET_RECLAIM_attribute_list_serialize (attrs, tmp); | ||
668 | tmp += attr_list_len; | ||
669 | if (0 < pres_list_len) | ||
670 | GNUNET_RECLAIM_presentation_list_serialize (presentations, tmp); | ||
671 | tmp += pres_list_len; | ||
672 | |||
673 | /** END **/ | ||
674 | |||
675 | // Get length | ||
676 | code_payload_len = sizeof(struct GNUNET_CRYPTO_EccSignaturePurpose) | ||
677 | + payload_len + sizeof(struct | ||
678 | GNUNET_IDENTITY_Signature); | ||
679 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, | ||
680 | "Length of data to encode: %lu\n", | ||
681 | code_payload_len); | ||
682 | |||
683 | // Initialize code payload | ||
684 | code_payload = GNUNET_malloc (code_payload_len); | ||
685 | GNUNET_assert (NULL != code_payload); | ||
686 | purpose = (struct GNUNET_CRYPTO_EccSignaturePurpose *) code_payload; | ||
687 | purpose->size = htonl (sizeof(struct GNUNET_CRYPTO_EccSignaturePurpose) | ||
688 | + payload_len); | ||
689 | purpose->purpose = htonl (GNUNET_SIGNATURE_PURPOSE_RECLAIM_CODE_SIGN); | ||
690 | // Store pubkey | ||
691 | buf_ptr = (char *) &purpose[1]; | ||
692 | memcpy (buf_ptr, payload, payload_len); | ||
693 | GNUNET_free (payload); | ||
694 | buf_ptr += payload_len; | ||
695 | // Sign and store signature | ||
696 | if (GNUNET_SYSERR == | ||
697 | GNUNET_IDENTITY_sign_ (issuer, | ||
698 | purpose, | ||
699 | (struct GNUNET_IDENTITY_Signature *) | ||
700 | buf_ptr)) | ||
701 | { | ||
702 | GNUNET_break (0); | ||
703 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unable to sign code\n"); | ||
704 | GNUNET_free (code_payload); | ||
705 | return NULL; | ||
706 | } | ||
707 | GNUNET_STRINGS_base64url_encode (code_payload, code_payload_len, &code_str); | ||
708 | GNUNET_free (code_payload); | ||
709 | return code_str; | ||
710 | } | ||
711 | |||
712 | |||
713 | enum GNUNET_GenericReturnValue | ||
714 | check_code_challenge (const char *code_challenge, | ||
715 | uint32_t code_challenge_len, | ||
716 | const char *code_verifier) | ||
717 | { | ||
718 | char *code_verifier_hash; | ||
719 | char *expected_code_challenge; | ||
720 | |||
721 | if (0 == code_challenge_len) /* Only check if this code requires a CV */ | ||
722 | return GNUNET_OK; | ||
723 | if (NULL == code_verifier) | ||
724 | { | ||
725 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, | ||
726 | "Expected code verifier!\n"); | ||
727 | return GNUNET_SYSERR; | ||
728 | } | ||
729 | code_verifier_hash = GNUNET_malloc (256 / 8); | ||
730 | // hash code verifier | ||
731 | gcry_md_hash_buffer (GCRY_MD_SHA256, | ||
732 | code_verifier_hash, | ||
733 | code_verifier, | ||
734 | strlen (code_verifier)); | ||
735 | // encode code verifier | ||
736 | GNUNET_STRINGS_base64url_encode (code_verifier_hash, 256 / 8, | ||
737 | &expected_code_challenge); | ||
738 | GNUNET_free (code_verifier_hash); | ||
739 | if (0 != | ||
740 | strncmp (expected_code_challenge, code_challenge, code_challenge_len)) | ||
741 | { | ||
742 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, | ||
743 | "Invalid code verifier! Expected: %s, Got: %.*s\n", | ||
744 | expected_code_challenge, | ||
745 | code_challenge_len, | ||
746 | code_challenge); | ||
747 | GNUNET_free (expected_code_challenge); | ||
748 | return GNUNET_SYSERR; | ||
749 | } | ||
750 | GNUNET_free (expected_code_challenge); | ||
751 | return GNUNET_OK; | ||
752 | } | ||
753 | |||
754 | |||
755 | /** | ||
756 | * Parse reclaim ticket and nonce from | ||
757 | * authorization code. | ||
758 | * This also verifies the signature in the code. | ||
759 | * | ||
760 | * @param audience the expected audience of the code | ||
761 | * @param code the string representation of the code | ||
762 | * @param code_verfier PKCE code verifier. Optional, must be provided | ||
763 | * if used in request. | ||
764 | * @param ticket where to store the ticket | ||
765 | * @param attrs the attributes in the code | ||
766 | * @param presentations credential presentation list | ||
767 | * @param nonce_str where to store the nonce (if contained) | ||
768 | * @return GNUNET_OK if successful, else GNUNET_SYSERR | ||
769 | */ | ||
770 | int | ||
771 | OIDC_parse_authz_code (const struct GNUNET_IDENTITY_PublicKey *audience, | ||
772 | const char *code, | ||
773 | const char *code_verifier, | ||
774 | struct GNUNET_RECLAIM_Ticket *ticket, | ||
775 | struct GNUNET_RECLAIM_AttributeList **attrs, | ||
776 | struct GNUNET_RECLAIM_PresentationList **presentations, | ||
777 | char **nonce_str, | ||
778 | enum OIDC_VerificationOptions opts) | ||
779 | { | ||
780 | char *code_payload; | ||
781 | char *ptr; | ||
782 | char *plaintext; | ||
783 | char *attrs_ser; | ||
784 | char *presentations_ser; | ||
785 | char *code_challenge; | ||
786 | struct GNUNET_CRYPTO_EccSignaturePurpose *purpose; | ||
787 | struct GNUNET_IDENTITY_Signature *signature; | ||
788 | uint32_t code_challenge_len; | ||
789 | uint32_t attrs_ser_len; | ||
790 | uint32_t pres_ser_len; | ||
791 | size_t plaintext_len; | ||
792 | size_t code_payload_len; | ||
793 | uint32_t nonce_len = 0; | ||
794 | struct OIDC_Parameters *params; | ||
795 | |||
796 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Trying to decode `%s'\n", code); | ||
797 | code_payload = NULL; | ||
798 | code_payload_len = | ||
799 | GNUNET_STRINGS_base64url_decode (code, strlen (code), | ||
800 | (void **) &code_payload); | ||
801 | if (code_payload_len < sizeof(struct GNUNET_CRYPTO_EccSignaturePurpose) | ||
802 | + sizeof(struct OIDC_Parameters) | ||
803 | + sizeof(struct GNUNET_IDENTITY_Signature)) | ||
804 | { | ||
805 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Authorization code malformed\n"); | ||
806 | GNUNET_free (code_payload); | ||
807 | return GNUNET_SYSERR; | ||
808 | } | ||
809 | |||
810 | purpose = (struct GNUNET_CRYPTO_EccSignaturePurpose *) code_payload; | ||
811 | plaintext_len = code_payload_len; | ||
812 | plaintext_len -= sizeof(struct GNUNET_CRYPTO_EccSignaturePurpose); | ||
813 | ptr = (char *) &purpose[1]; | ||
814 | plaintext_len -= sizeof(struct GNUNET_IDENTITY_Signature); | ||
815 | plaintext = ptr; | ||
816 | ptr += plaintext_len; | ||
817 | signature = (struct GNUNET_IDENTITY_Signature *) ptr; | ||
818 | params = (struct OIDC_Parameters *) plaintext; | ||
819 | |||
820 | // cmp code_challenge code_verifier | ||
821 | code_challenge_len = ntohl (params->code_challenge_len); | ||
822 | code_challenge = ((char *) ¶ms[1]); | ||
823 | if (! (opts & OIDC_VERIFICATION_NO_CODE_VERIFIER)) | ||
824 | { | ||
825 | if (GNUNET_OK != check_code_challenge (code_challenge, | ||
826 | code_challenge_len, | ||
827 | code_verifier)) | ||
828 | { | ||
829 | GNUNET_free (code_payload); | ||
830 | return GNUNET_SYSERR; | ||
831 | } | ||
832 | } | ||
833 | nonce_len = ntohl (params->nonce_len); | ||
834 | if (0 != nonce_len) | ||
835 | { | ||
836 | *nonce_str = GNUNET_strndup (code_challenge + code_challenge_len, | ||
837 | nonce_len); | ||
838 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Got nonce: %s\n", *nonce_str); | ||
839 | } | ||
840 | |||
841 | // Ticket | ||
842 | memcpy (ticket, ¶ms->ticket, sizeof(params->ticket)); | ||
843 | // Signature | ||
844 | // GNUNET_CRYPTO_ecdsa_key_get_public (ecdsa_priv, &ecdsa_pub); | ||
845 | if (0 != GNUNET_memcmp (audience, &ticket->audience)) | ||
846 | { | ||
847 | GNUNET_free (code_payload); | ||
848 | if (NULL != *nonce_str) | ||
849 | GNUNET_free (*nonce_str); | ||
850 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, | ||
851 | "Audience in ticket does not match client!\n"); | ||
852 | return GNUNET_SYSERR; | ||
853 | } | ||
854 | if (GNUNET_OK != | ||
855 | GNUNET_IDENTITY_signature_verify_ ( | ||
856 | GNUNET_SIGNATURE_PURPOSE_RECLAIM_CODE_SIGN, | ||
857 | purpose, | ||
858 | signature, | ||
859 | &(ticket->identity))) | ||
860 | { | ||
861 | GNUNET_free (code_payload); | ||
862 | if (NULL != *nonce_str) | ||
863 | GNUNET_free (*nonce_str); | ||
864 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Signature of AuthZ code invalid!\n"); | ||
865 | return GNUNET_SYSERR; | ||
866 | } | ||
867 | // Attributes | ||
868 | attrs_ser = ((char *) ¶ms[1]) + code_challenge_len + nonce_len; | ||
869 | attrs_ser_len = ntohl (params->attr_list_len); | ||
870 | *attrs = GNUNET_RECLAIM_attribute_list_deserialize (attrs_ser, attrs_ser_len); | ||
871 | presentations_ser = ((char*) attrs_ser) + attrs_ser_len; | ||
872 | pres_ser_len = ntohl (params->pres_list_len); | ||
873 | *presentations = | ||
874 | GNUNET_RECLAIM_presentation_list_deserialize (presentations_ser, | ||
875 | pres_ser_len); | ||
876 | |||
877 | GNUNET_free (code_payload); | ||
878 | return GNUNET_OK; | ||
879 | } | ||
880 | |||
881 | |||
882 | /** | ||
883 | * Build a token response for a token request | ||
884 | * TODO: Maybe we should add the scope here? | ||
885 | * | ||
886 | * @param access_token the access token to include | ||
887 | * @param id_token the id_token to include | ||
888 | * @param expiration_time the expiration time of the token(s) | ||
889 | * @param token_response where to store the response | ||
890 | */ | ||
891 | void | ||
892 | OIDC_build_token_response (const char *access_token, | ||
893 | const char *id_token, | ||
894 | const struct GNUNET_TIME_Relative *expiration_time, | ||
895 | char **token_response) | ||
896 | { | ||
897 | json_t *root_json; | ||
898 | |||
899 | root_json = json_object (); | ||
900 | |||
901 | GNUNET_assert (NULL != access_token); | ||
902 | GNUNET_assert (NULL != id_token); | ||
903 | GNUNET_assert (NULL != expiration_time); | ||
904 | json_object_set_new (root_json, "access_token", json_string (access_token)); | ||
905 | json_object_set_new (root_json, "token_type", json_string ("Bearer")); | ||
906 | json_object_set_new (root_json, | ||
907 | "expires_in", | ||
908 | json_integer (expiration_time->rel_value_us | ||
909 | / (1000 * 1000))); | ||
910 | json_object_set_new (root_json, "id_token", json_string (id_token)); | ||
911 | *token_response = json_dumps (root_json, JSON_INDENT (0) | JSON_COMPACT); | ||
912 | json_decref (root_json); | ||
913 | } | ||
914 | |||
915 | |||
916 | /** | ||
917 | * Generate a new access token | ||
918 | */ | ||
919 | char * | ||
920 | OIDC_access_token_new (const struct GNUNET_RECLAIM_Ticket *ticket) | ||
921 | { | ||
922 | char *access_token; | ||
923 | |||
924 | GNUNET_STRINGS_base64_encode (ticket, | ||
925 | sizeof(*ticket), | ||
926 | &access_token); | ||
927 | return access_token; | ||
928 | } | ||
929 | |||
930 | |||
931 | /** | ||
932 | * Parse an access token | ||
933 | */ | ||
934 | int | ||
935 | OIDC_access_token_parse (const char *token, | ||
936 | struct GNUNET_RECLAIM_Ticket **ticket) | ||
937 | { | ||
938 | size_t sret; | ||
939 | char *decoded; | ||
940 | sret = GNUNET_STRINGS_base64_decode (token, | ||
941 | strlen (token), | ||
942 | (void**) &decoded); | ||
943 | if (sizeof (struct GNUNET_RECLAIM_Ticket) != sret) | ||
944 | { | ||
945 | GNUNET_free (decoded); | ||
946 | return GNUNET_SYSERR; | ||
947 | } | ||
948 | *ticket = (struct GNUNET_RECLAIM_Ticket *) decoded; | ||
949 | return GNUNET_OK; | ||
950 | } | ||
951 | |||
952 | |||
953 | /** | ||
954 | * Checks if a claim is implicitly requested through standard | ||
955 | * scope(s) or explicitly through non-standard scope. | ||
956 | * | ||
957 | * @param scopes the scopes which have been requested | ||
958 | * @param attr the attribute name to check | ||
959 | * @return GNUNET_YES if attribute is implcitly requested | ||
960 | */ | ||
961 | enum GNUNET_GenericReturnValue | ||
962 | OIDC_check_scopes_for_claim_request (const char*scopes, | ||
963 | const char*attr) | ||
964 | { | ||
965 | char *scope_variables; | ||
966 | char *scope_variable; | ||
967 | char delimiter[] = " "; | ||
968 | int i; | ||
969 | |||
970 | scope_variables = GNUNET_strdup (scopes); | ||
971 | scope_variable = strtok (scope_variables, delimiter); | ||
972 | while (NULL != scope_variable) | ||
973 | { | ||
974 | if (0 == strcmp ("profile", scope_variable)) | ||
975 | { | ||
976 | for (i = 0; i < 14; i++) | ||
977 | { | ||
978 | if (0 == strcmp (attr, OIDC_profile_claims[i])) | ||
979 | { | ||
980 | GNUNET_free (scope_variables); | ||
981 | return GNUNET_YES; | ||
982 | } | ||
983 | } | ||
984 | } | ||
985 | else if (0 == strcmp ("address", scope_variable)) | ||
986 | { | ||
987 | for (i = 0; i < 5; i++) | ||
988 | { | ||
989 | if (0 == strcmp (attr, OIDC_address_claims[i])) | ||
990 | { | ||
991 | GNUNET_free (scope_variables); | ||
992 | return GNUNET_YES; | ||
993 | } | ||
994 | } | ||
995 | } | ||
996 | else if (0 == strcmp ("email", scope_variable)) | ||
997 | { | ||
998 | for (i = 0; i < 2; i++) | ||
999 | { | ||
1000 | if (0 == strcmp (attr, OIDC_email_claims[i])) | ||
1001 | { | ||
1002 | GNUNET_free (scope_variables); | ||
1003 | return GNUNET_YES; | ||
1004 | } | ||
1005 | } | ||
1006 | } | ||
1007 | else if (0 == strcmp ("phone", scope_variable)) | ||
1008 | { | ||
1009 | for (i = 0; i < 2; i++) | ||
1010 | { | ||
1011 | if (0 == strcmp (attr, OIDC_phone_claims[i])) | ||
1012 | { | ||
1013 | GNUNET_free (scope_variables); | ||
1014 | return GNUNET_YES; | ||
1015 | } | ||
1016 | } | ||
1017 | |||
1018 | } | ||
1019 | else if (0 == strcmp (attr, scope_variable)) | ||
1020 | { | ||
1021 | /** attribute matches requested scope **/ | ||
1022 | GNUNET_free (scope_variables); | ||
1023 | return GNUNET_YES; | ||
1024 | } | ||
1025 | scope_variable = strtok (NULL, delimiter); | ||
1026 | } | ||
1027 | GNUNET_free (scope_variables); | ||
1028 | return GNUNET_NO; | ||
1029 | |||
1030 | } | ||