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