aboutsummaryrefslogtreecommitdiff
path: root/src/reclaim/oidc_helper.c
diff options
context:
space:
mode:
authorSchanzenbach, Martin <mschanzenbach@posteo.de>2019-03-12 09:36:17 +0100
committerSchanzenbach, Martin <mschanzenbach@posteo.de>2019-03-12 09:36:17 +0100
commit919bb8c92fa1b7cda53401ff2286b980ca0b12d8 (patch)
treeb0ab4ebf063c0ccb2a4b8095463d1e7a6884bf2e /src/reclaim/oidc_helper.c
parent76c6ccfdfe09891db424ead5209f041f0e71dc63 (diff)
downloadgnunet-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.c436
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
35static char*
36create_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
50static void
51replace_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
60static void
61fix_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 */
80char*
81OIDC_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 */
199char*
200OIDC_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 */
274int
275OIDC_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 */
392void
393OIDC_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 */
425char*
426OIDC_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}