2 This file is part of GNUnet
3 Copyright (C) 2010-2015 GNUnet e.V.
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.
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.
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/>.
18 SPDX-License-Identifier: AGPL3.0-or-later
22 * @file reclaim/oidc_helper.c
23 * @brief helper library for OIDC related functions
24 * @author Martin Schanzenbach
27 #include "gnunet_util_lib.h"
28 #include "gnunet_signatures.h"
29 #include "gnunet_reclaim_service.h"
30 #include "gnunet_reclaim_attribute_lib.h"
33 #include "oidc_helper.h"
36 create_jwt_header(void)
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));
45 json_str = json_dumps (root, JSON_INDENT(0) | JSON_COMPACT);
51 replace_char(char* str, char find, char replace){
52 char *current_pos = strchr(str,find);
54 *current_pos = replace;
55 current_pos = strchr(current_pos,find);
61 fix_base64(char* str) {
63 replace_char (str, '+', '-');
66 replace_char (str, '/', '_');
71 * Create a JWT from attributes
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.
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,
86 const char *secret_key)
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;
99 char* signature_target;
100 char* signature_base64;
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 ();
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));
129 json_object_set_new (body,
130 "iat", json_integer (time_now.abs_value_us / (1000*1000)));
132 json_object_set_new (body,
133 "exp", json_integer (exp_time.abs_value_us / (1000*1000)));
135 json_object_set_new (body,
136 "nbf", json_integer (time_now.abs_value_us / (1000*1000)));
139 json_object_set_new (body,
140 "nonce", json_string (nonce));
142 for (le = attrs->list_head; NULL != le; le = le->next)
144 attr_val_str = GNUNET_RECLAIM_ATTRIBUTE_value_to_string (le->claim->type,
146 le->claim->data_size);
147 json_object_set_new (body,
149 json_string (attr_val_str));
150 GNUNET_free (attr_val_str);
152 body_str = json_dumps (body, JSON_INDENT(0) | JSON_COMPACT);
155 GNUNET_STRINGS_base64_encode (header,
158 fix_base64(header_base64);
160 GNUNET_STRINGS_base64_encode (body_str,
163 fix_base64(body_base64);
165 GNUNET_free (subject);
166 GNUNET_free (audience);
169 * Creating the JWT signature. This might not be
170 * standards compliant, check.
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),
177 fix_base64(signature_base64);
179 GNUNET_asprintf (&result, "%s.%s.%s",
180 header_base64, body_base64, signature_base64);
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);
191 * Builds an OIDC authorization code including
192 * a reclaim ticket and nonce
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)
200 OIDC_build_authz_code (const struct GNUNET_CRYPTO_EcdsaPrivateKey *issuer,
201 const struct GNUNET_RECLAIM_Ticket *ticket,
206 char *signature_payload;
209 size_t signature_payload_len;
210 struct GNUNET_CRYPTO_EcdsaSignature signature;
211 struct GNUNET_CRYPTO_EccSignaturePurpose *purpose;
213 signature_payload_len = sizeof (struct GNUNET_RECLAIM_Ticket);
215 signature_payload_len += strlen (nonce);
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);
223 sizeof (struct GNUNET_RECLAIM_Ticket));
225 memcpy (((char*)&purpose[1]) + sizeof (struct GNUNET_RECLAIM_Ticket),
228 if (GNUNET_SYSERR == GNUNET_CRYPTO_ecdsa_sign (issuer,
232 GNUNET_free (signature_payload);
235 signature_str = GNUNET_STRINGS_data_to_string_alloc (&signature,
237 ticket_str = GNUNET_STRINGS_data_to_string_alloc (ticket,
238 sizeof (struct GNUNET_RECLAIM_Ticket));
240 code_json = json_object ();
241 json_object_set_new (code_json,
243 json_string (ticket_str));
245 json_object_set_new (code_json,
247 json_string (nonce));
248 json_object_set_new (code_json,
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);
264 * Parse reclaim ticket and nonce from
265 * authorization code.
266 * This also verifies the signature in the code.
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
275 OIDC_parse_authz_code (const struct GNUNET_CRYPTO_EcdsaPublicKey *audience,
277 struct GNUNET_RECLAIM_Ticket **ticket,
284 json_t *signature_json;
285 const char *ticket_str;
286 const char *signature_str;
287 const char *nonce_str;
289 struct GNUNET_CRYPTO_EccSignaturePurpose *purpose;
290 struct GNUNET_CRYPTO_EcdsaSignature signature;
291 size_t signature_payload_len;
294 GNUNET_STRINGS_base64_decode (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");
305 if ((NULL == ticket_json || !json_is_string (ticket_json)) ||
306 (NULL == signature_json || !json_is_string (signature_json)))
308 json_decref (code_json);
309 return GNUNET_SYSERR;
311 ticket_str = json_string_value (ticket_json);
312 signature_str = json_string_value (signature_json);
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,
326 sizeof (struct GNUNET_RECLAIM_Ticket)))
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;
334 if (GNUNET_OK != GNUNET_STRINGS_string_to_data (signature_str,
335 strlen (signature_str),
337 sizeof (struct GNUNET_CRYPTO_EcdsaSignature)))
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;
345 *ticket = GNUNET_new (struct GNUNET_RECLAIM_Ticket);
348 sizeof (struct GNUNET_RECLAIM_Ticket));
349 if (0 != memcmp (audience,
350 &(*ticket)->audience,
351 sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey)))
353 GNUNET_free (purpose);
354 GNUNET_free (*ticket);
355 json_decref (code_json);
357 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
358 "Audience in ticket does not match client!\n");
359 return GNUNET_SYSERR;
362 if (NULL != nonce_str)
363 memcpy (((char*)&purpose[1]) + sizeof (struct GNUNET_RECLAIM_Ticket),
366 if (GNUNET_OK != GNUNET_CRYPTO_ecdsa_verify (GNUNET_SIGNATURE_PURPOSE_RECLAIM_CODE_SIGN,
369 &(*ticket)->identity))
371 GNUNET_free (purpose);
372 GNUNET_free (*ticket);
373 json_decref (code_json);
375 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
376 "Signature of authZ code invalid!\n");
377 return GNUNET_SYSERR;
379 *nonce = GNUNET_strdup (nonce_str);
384 * Build a token response for a token request
385 * TODO: Maybe we should add the scope here?
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
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)
400 root_json = json_object ();
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,
407 json_string (access_token));
408 json_object_set_new (root_json,
410 json_string ("Bearer"));
411 json_object_set_new (root_json,
413 json_integer (expiration_time->rel_value_us / (1000 * 1000)));
414 json_object_set_new (root_json,
416 json_string (id_token));
417 *token_response = json_dumps (root_json,
418 JSON_INDENT(0) | JSON_COMPACT);
419 json_decref (root_json);
423 * Generate a new access token
426 OIDC_access_token_new ()
428 char* access_token_number;
430 uint64_t random_number;
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);