diff options
author | Schanzenbach, Martin <mschanzenbach@posteo.de> | 2019-03-12 09:36:17 +0100 |
---|---|---|
committer | Schanzenbach, Martin <mschanzenbach@posteo.de> | 2019-03-12 09:36:17 +0100 |
commit | 919bb8c92fa1b7cda53401ff2286b980ca0b12d8 (patch) | |
tree | b0ab4ebf063c0ccb2a4b8095463d1e7a6884bf2e /src/reclaim/oidc_helper.c | |
parent | 76c6ccfdfe09891db424ead5209f041f0e71dc63 (diff) | |
download | gnunet-919bb8c92fa1b7cda53401ff2286b980ca0b12d8.tar.gz gnunet-919bb8c92fa1b7cda53401ff2286b980ca0b12d8.zip |
move reclaim and gns back into subdirs
Diffstat (limited to 'src/reclaim/oidc_helper.c')
-rw-r--r-- | src/reclaim/oidc_helper.c | 436 |
1 files changed, 436 insertions, 0 deletions
diff --git a/src/reclaim/oidc_helper.c b/src/reclaim/oidc_helper.c new file mode 100644 index 000000000..646e58551 --- /dev/null +++ b/src/reclaim/oidc_helper.c | |||
@@ -0,0 +1,436 @@ | |||
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 "gnunet_util_lib.h" | ||
28 | #include "gnunet_signatures.h" | ||
29 | #include "gnunet_reclaim_service.h" | ||
30 | #include "gnunet_reclaim_attribute_lib.h" | ||
31 | #include <jansson.h> | ||
32 | #include <inttypes.h> | ||
33 | #include "oidc_helper.h" | ||
34 | |||
35 | static char* | ||
36 | create_jwt_header(void) | ||
37 | { | ||
38 | json_t *root; | ||
39 | char *json_str; | ||
40 | |||
41 | root = json_object (); | ||
42 | json_object_set_new (root, JWT_ALG, json_string (JWT_ALG_VALUE)); | ||
43 | json_object_set_new (root, JWT_TYP, json_string (JWT_TYP_VALUE)); | ||
44 | |||
45 | json_str = json_dumps (root, JSON_INDENT(0) | JSON_COMPACT); | ||
46 | json_decref (root); | ||
47 | return json_str; | ||
48 | } | ||
49 | |||
50 | static void | ||
51 | replace_char(char* str, char find, char replace){ | ||
52 | char *current_pos = strchr(str,find); | ||
53 | while (current_pos){ | ||
54 | *current_pos = replace; | ||
55 | current_pos = strchr(current_pos,find); | ||
56 | } | ||
57 | } | ||
58 | |||
59 | //RFC4648 | ||
60 | static void | ||
61 | fix_base64(char* str) { | ||
62 | //Replace + with - | ||
63 | replace_char (str, '+', '-'); | ||
64 | |||
65 | //Replace / with _ | ||
66 | replace_char (str, '/', '_'); | ||
67 | |||
68 | } | ||
69 | |||
70 | /** | ||
71 | * Create a JWT from attributes | ||
72 | * | ||
73 | * @param aud_key the public of the audience | ||
74 | * @param sub_key the public key of the subject | ||
75 | * @param attrs the attribute list | ||
76 | * @param expiration_time the validity of the token | ||
77 | * @param secret_key the key used to sign the JWT | ||
78 | * @return a new base64-encoded JWT string. | ||
79 | */ | ||
80 | char* | ||
81 | OIDC_id_token_new (const struct GNUNET_CRYPTO_EcdsaPublicKey *aud_key, | ||
82 | const struct GNUNET_CRYPTO_EcdsaPublicKey *sub_key, | ||
83 | const struct GNUNET_RECLAIM_ATTRIBUTE_ClaimList *attrs, | ||
84 | const struct GNUNET_TIME_Relative *expiration_time, | ||
85 | const char *nonce, | ||
86 | const char *secret_key) | ||
87 | { | ||
88 | struct GNUNET_RECLAIM_ATTRIBUTE_ClaimListEntry *le; | ||
89 | struct GNUNET_HashCode signature; | ||
90 | struct GNUNET_TIME_Absolute exp_time; | ||
91 | struct GNUNET_TIME_Absolute time_now; | ||
92 | char* audience; | ||
93 | char* subject; | ||
94 | char* header; | ||
95 | char* body_str; | ||
96 | char* result; | ||
97 | char* header_base64; | ||
98 | char* body_base64; | ||
99 | char* signature_target; | ||
100 | char* signature_base64; | ||
101 | char* attr_val_str; | ||
102 | json_t* body; | ||
103 | |||
104 | //iat REQUIRED time now | ||
105 | time_now = GNUNET_TIME_absolute_get(); | ||
106 | //exp REQUIRED time expired from config | ||
107 | exp_time = GNUNET_TIME_absolute_add (time_now, *expiration_time); | ||
108 | //auth_time only if max_age | ||
109 | //nonce only if nonce | ||
110 | // OPTIONAL acr,amr,azp | ||
111 | subject = GNUNET_STRINGS_data_to_string_alloc (sub_key, | ||
112 | sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey)); | ||
113 | audience = GNUNET_STRINGS_data_to_string_alloc (aud_key, | ||
114 | sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey)); | ||
115 | header = create_jwt_header (); | ||
116 | body = json_object (); | ||
117 | |||
118 | //iss REQUIRED case sensitive server uri with https | ||
119 | //The issuer is the local reclaim instance (e.g. https://reclaim.id/api/openid) | ||
120 | json_object_set_new (body, | ||
121 | "iss", json_string (SERVER_ADDRESS)); | ||
122 | //sub REQUIRED public key identity, not exceed 255 ASCII length | ||
123 | json_object_set_new (body, | ||
124 | "sub", json_string (subject)); | ||
125 | //aud REQUIRED public key client_id must be there | ||
126 | json_object_set_new (body, | ||
127 | "aud", json_string (audience)); | ||
128 | //iat | ||
129 | json_object_set_new (body, | ||
130 | "iat", json_integer (time_now.abs_value_us / (1000*1000))); | ||
131 | //exp | ||
132 | json_object_set_new (body, | ||
133 | "exp", json_integer (exp_time.abs_value_us / (1000*1000))); | ||
134 | //nbf | ||
135 | json_object_set_new (body, | ||
136 | "nbf", json_integer (time_now.abs_value_us / (1000*1000))); | ||
137 | //nonce | ||
138 | if (NULL != nonce) | ||
139 | json_object_set_new (body, | ||
140 | "nonce", json_string (nonce)); | ||
141 | |||
142 | for (le = attrs->list_head; NULL != le; le = le->next) | ||
143 | { | ||
144 | attr_val_str = GNUNET_RECLAIM_ATTRIBUTE_value_to_string (le->claim->type, | ||
145 | le->claim->data, | ||
146 | le->claim->data_size); | ||
147 | json_object_set_new (body, | ||
148 | le->claim->name, | ||
149 | json_string (attr_val_str)); | ||
150 | GNUNET_free (attr_val_str); | ||
151 | } | ||
152 | body_str = json_dumps (body, JSON_INDENT(0) | JSON_COMPACT); | ||
153 | json_decref (body); | ||
154 | |||
155 | GNUNET_STRINGS_base64_encode (header, | ||
156 | strlen (header), | ||
157 | &header_base64); | ||
158 | fix_base64(header_base64); | ||
159 | |||
160 | GNUNET_STRINGS_base64_encode (body_str, | ||
161 | strlen (body_str), | ||
162 | &body_base64); | ||
163 | fix_base64(body_base64); | ||
164 | |||
165 | GNUNET_free (subject); | ||
166 | GNUNET_free (audience); | ||
167 | |||
168 | /** | ||
169 | * Creating the JWT signature. This might not be | ||
170 | * standards compliant, check. | ||
171 | */ | ||
172 | GNUNET_asprintf (&signature_target, "%s.%s", header_base64, body_base64); | ||
173 | GNUNET_CRYPTO_hmac_raw (secret_key, strlen (secret_key), signature_target, strlen (signature_target), &signature); | ||
174 | GNUNET_STRINGS_base64_encode ((const char*)&signature, | ||
175 | sizeof (struct GNUNET_HashCode), | ||
176 | &signature_base64); | ||
177 | fix_base64(signature_base64); | ||
178 | |||
179 | GNUNET_asprintf (&result, "%s.%s.%s", | ||
180 | header_base64, body_base64, signature_base64); | ||
181 | |||
182 | GNUNET_free (signature_target); | ||
183 | GNUNET_free (header); | ||
184 | GNUNET_free (body_str); | ||
185 | GNUNET_free (signature_base64); | ||
186 | GNUNET_free (body_base64); | ||
187 | GNUNET_free (header_base64); | ||
188 | return result; | ||
189 | } | ||
190 | /** | ||
191 | * Builds an OIDC authorization code including | ||
192 | * a reclaim ticket and nonce | ||
193 | * | ||
194 | * @param issuer the issuer of the ticket, used to sign the ticket and nonce | ||
195 | * @param ticket the ticket to include in the code | ||
196 | * @param nonce the nonce to include in the code | ||
197 | * @return a new authorization code (caller must free) | ||
198 | */ | ||
199 | char* | ||
200 | OIDC_build_authz_code (const struct GNUNET_CRYPTO_EcdsaPrivateKey *issuer, | ||
201 | const struct GNUNET_RECLAIM_Ticket *ticket, | ||
202 | const char* nonce) | ||
203 | { | ||
204 | char *ticket_str; | ||
205 | json_t *code_json; | ||
206 | char *signature_payload; | ||
207 | char *signature_str; | ||
208 | char *authz_code; | ||
209 | size_t signature_payload_len; | ||
210 | struct GNUNET_CRYPTO_EcdsaSignature signature; | ||
211 | struct GNUNET_CRYPTO_EccSignaturePurpose *purpose; | ||
212 | |||
213 | signature_payload_len = sizeof (struct GNUNET_RECLAIM_Ticket); | ||
214 | if (NULL != nonce) | ||
215 | signature_payload_len += strlen (nonce); | ||
216 | |||
217 | signature_payload = GNUNET_malloc (sizeof (struct GNUNET_CRYPTO_EccSignaturePurpose) + signature_payload_len); | ||
218 | purpose = (struct GNUNET_CRYPTO_EccSignaturePurpose *)signature_payload; | ||
219 | purpose->size = htonl (sizeof (struct GNUNET_CRYPTO_EccSignaturePurpose) + signature_payload_len); | ||
220 | purpose->purpose = htonl (GNUNET_SIGNATURE_PURPOSE_RECLAIM_CODE_SIGN); | ||
221 | memcpy (&purpose[1], | ||
222 | ticket, | ||
223 | sizeof (struct GNUNET_RECLAIM_Ticket)); | ||
224 | if (NULL != nonce) | ||
225 | memcpy (((char*)&purpose[1]) + sizeof (struct GNUNET_RECLAIM_Ticket), | ||
226 | nonce, | ||
227 | strlen (nonce)); | ||
228 | if (GNUNET_SYSERR == GNUNET_CRYPTO_ecdsa_sign (issuer, | ||
229 | purpose, | ||
230 | &signature)) | ||
231 | { | ||
232 | GNUNET_free (signature_payload); | ||
233 | return NULL; | ||
234 | } | ||
235 | signature_str = GNUNET_STRINGS_data_to_string_alloc (&signature, | ||
236 | sizeof (signature)); | ||
237 | ticket_str = GNUNET_STRINGS_data_to_string_alloc (ticket, | ||
238 | sizeof (struct GNUNET_RECLAIM_Ticket)); | ||
239 | |||
240 | code_json = json_object (); | ||
241 | json_object_set_new (code_json, | ||
242 | "ticket", | ||
243 | json_string (ticket_str)); | ||
244 | if (NULL != nonce) | ||
245 | json_object_set_new (code_json, | ||
246 | "nonce", | ||
247 | json_string (nonce)); | ||
248 | json_object_set_new (code_json, | ||
249 | "signature", | ||
250 | json_string (signature_str)); | ||
251 | authz_code = json_dumps (code_json, | ||
252 | JSON_INDENT(0) | JSON_COMPACT); | ||
253 | GNUNET_free (signature_payload); | ||
254 | GNUNET_free (signature_str); | ||
255 | GNUNET_free (ticket_str); | ||
256 | json_decref (code_json); | ||
257 | return authz_code; | ||
258 | } | ||
259 | |||
260 | |||
261 | |||
262 | |||
263 | /** | ||
264 | * Parse reclaim ticket and nonce from | ||
265 | * authorization code. | ||
266 | * This also verifies the signature in the code. | ||
267 | * | ||
268 | * @param audience the expected audience of the code | ||
269 | * @param code the string representation of the code | ||
270 | * @param ticket where to store the ticket | ||
271 | * @param nonce where to store the nonce | ||
272 | * @return GNUNET_OK if successful, else GNUNET_SYSERR | ||
273 | */ | ||
274 | int | ||
275 | OIDC_parse_authz_code (const struct GNUNET_CRYPTO_EcdsaPublicKey *audience, | ||
276 | const char* code, | ||
277 | struct GNUNET_RECLAIM_Ticket **ticket, | ||
278 | char **nonce) | ||
279 | { | ||
280 | json_error_t error; | ||
281 | json_t *code_json; | ||
282 | json_t *ticket_json; | ||
283 | json_t *nonce_json; | ||
284 | json_t *signature_json; | ||
285 | const char *ticket_str; | ||
286 | const char *signature_str; | ||
287 | const char *nonce_str; | ||
288 | char *code_output; | ||
289 | struct GNUNET_CRYPTO_EccSignaturePurpose *purpose; | ||
290 | struct GNUNET_CRYPTO_EcdsaSignature signature; | ||
291 | size_t signature_payload_len; | ||
292 | |||
293 | code_output = NULL; | ||
294 | GNUNET_STRINGS_base64_decode (code, | ||
295 | strlen(code), | ||
296 | (void**)&code_output); | ||
297 | code_json = json_loads (code_output, 0 , &error); | ||
298 | GNUNET_free (code_output); | ||
299 | ticket_json = json_object_get (code_json, "ticket"); | ||
300 | nonce_json = json_object_get (code_json, "nonce"); | ||
301 | signature_json = json_object_get (code_json, "signature"); | ||
302 | *ticket = NULL; | ||
303 | *nonce = NULL; | ||
304 | |||
305 | if ((NULL == ticket_json || !json_is_string (ticket_json)) || | ||
306 | (NULL == signature_json || !json_is_string (signature_json))) | ||
307 | { | ||
308 | json_decref (code_json); | ||
309 | return GNUNET_SYSERR; | ||
310 | } | ||
311 | ticket_str = json_string_value (ticket_json); | ||
312 | signature_str = json_string_value (signature_json); | ||
313 | nonce_str = NULL; | ||
314 | if (NULL != nonce_json) | ||
315 | nonce_str = json_string_value (nonce_json); | ||
316 | signature_payload_len = sizeof (struct GNUNET_RECLAIM_Ticket); | ||
317 | if (NULL != nonce_str) | ||
318 | signature_payload_len += strlen (nonce_str); | ||
319 | purpose = GNUNET_malloc (sizeof (struct GNUNET_CRYPTO_EccSignaturePurpose) + | ||
320 | signature_payload_len); | ||
321 | purpose->size = htonl (sizeof (struct GNUNET_CRYPTO_EccSignaturePurpose) + signature_payload_len); | ||
322 | purpose->purpose = htonl (GNUNET_SIGNATURE_PURPOSE_RECLAIM_CODE_SIGN); | ||
323 | if (GNUNET_OK != GNUNET_STRINGS_string_to_data (ticket_str, | ||
324 | strlen (ticket_str), | ||
325 | &purpose[1], | ||
326 | sizeof (struct GNUNET_RECLAIM_Ticket))) | ||
327 | { | ||
328 | GNUNET_free (purpose); | ||
329 | json_decref (code_json); | ||
330 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, | ||
331 | "Cannot parse ticket!\n"); | ||
332 | return GNUNET_SYSERR; | ||
333 | } | ||
334 | if (GNUNET_OK != GNUNET_STRINGS_string_to_data (signature_str, | ||
335 | strlen (signature_str), | ||
336 | &signature, | ||
337 | sizeof (struct GNUNET_CRYPTO_EcdsaSignature))) | ||
338 | { | ||
339 | GNUNET_free (purpose); | ||
340 | json_decref (code_json); | ||
341 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, | ||
342 | "Cannot parse signature!\n"); | ||
343 | return GNUNET_SYSERR; | ||
344 | } | ||
345 | *ticket = GNUNET_new (struct GNUNET_RECLAIM_Ticket); | ||
346 | memcpy (*ticket, | ||
347 | &purpose[1], | ||
348 | sizeof (struct GNUNET_RECLAIM_Ticket)); | ||
349 | if (0 != memcmp (audience, | ||
350 | &(*ticket)->audience, | ||
351 | sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey))) | ||
352 | { | ||
353 | GNUNET_free (purpose); | ||
354 | GNUNET_free (*ticket); | ||
355 | json_decref (code_json); | ||
356 | *ticket = NULL; | ||
357 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, | ||
358 | "Audience in ticket does not match client!\n"); | ||
359 | return GNUNET_SYSERR; | ||
360 | |||
361 | } | ||
362 | if (NULL != nonce_str) | ||
363 | memcpy (((char*)&purpose[1]) + sizeof (struct GNUNET_RECLAIM_Ticket), | ||
364 | nonce_str, | ||
365 | strlen (nonce_str)); | ||
366 | if (GNUNET_OK != GNUNET_CRYPTO_ecdsa_verify (GNUNET_SIGNATURE_PURPOSE_RECLAIM_CODE_SIGN, | ||
367 | purpose, | ||
368 | &signature, | ||
369 | &(*ticket)->identity)) | ||
370 | { | ||
371 | GNUNET_free (purpose); | ||
372 | GNUNET_free (*ticket); | ||
373 | json_decref (code_json); | ||
374 | *ticket = NULL; | ||
375 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, | ||
376 | "Signature of authZ code invalid!\n"); | ||
377 | return GNUNET_SYSERR; | ||
378 | } | ||
379 | *nonce = GNUNET_strdup (nonce_str); | ||
380 | return GNUNET_OK; | ||
381 | } | ||
382 | |||
383 | /** | ||
384 | * Build a token response for a token request | ||
385 | * TODO: Maybe we should add the scope here? | ||
386 | * | ||
387 | * @param access_token the access token to include | ||
388 | * @param id_token the id_token to include | ||
389 | * @param expiration_time the expiration time of the token(s) | ||
390 | * @param token_response where to store the response | ||
391 | */ | ||
392 | void | ||
393 | OIDC_build_token_response (const char *access_token, | ||
394 | const char *id_token, | ||
395 | const struct GNUNET_TIME_Relative *expiration_time, | ||
396 | char **token_response) | ||
397 | { | ||
398 | json_t *root_json; | ||
399 | |||
400 | root_json = json_object (); | ||
401 | |||
402 | GNUNET_assert (NULL != access_token); | ||
403 | GNUNET_assert (NULL != id_token); | ||
404 | GNUNET_assert (NULL != expiration_time); | ||
405 | json_object_set_new (root_json, | ||
406 | "access_token", | ||
407 | json_string (access_token)); | ||
408 | json_object_set_new (root_json, | ||
409 | "token_type", | ||
410 | json_string ("Bearer")); | ||
411 | json_object_set_new (root_json, | ||
412 | "expires_in", | ||
413 | json_integer (expiration_time->rel_value_us / (1000 * 1000))); | ||
414 | json_object_set_new (root_json, | ||
415 | "id_token", | ||
416 | json_string (id_token)); | ||
417 | *token_response = json_dumps (root_json, | ||
418 | JSON_INDENT(0) | JSON_COMPACT); | ||
419 | json_decref (root_json); | ||
420 | } | ||
421 | |||
422 | /** | ||
423 | * Generate a new access token | ||
424 | */ | ||
425 | char* | ||
426 | OIDC_access_token_new () | ||
427 | { | ||
428 | char* access_token_number; | ||
429 | char* access_token; | ||
430 | uint64_t random_number; | ||
431 | |||
432 | random_number = GNUNET_CRYPTO_random_u64(GNUNET_CRYPTO_QUALITY_NONCE, UINT64_MAX); | ||
433 | GNUNET_asprintf (&access_token_number, "%" PRIu64, random_number); | ||
434 | GNUNET_STRINGS_base64_encode(access_token_number,strlen(access_token_number),&access_token); | ||
435 | return access_token; | ||
436 | } | ||