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/>.
20 * @file reclaim/oidc_helper.c
21 * @brief helper library for OIDC related functions
22 * @author Martin Schanzenbach
25 #include "gnunet_util_lib.h"
26 #include "gnunet_signatures.h"
27 #include "gnunet_reclaim_service.h"
28 #include "gnunet_reclaim_attribute_lib.h"
31 #include "oidc_helper.h"
34 create_jwt_header(void)
39 root = json_object ();
40 json_object_set_new (root, JWT_ALG, json_string (JWT_ALG_VALUE));
41 json_object_set_new (root, JWT_TYP, json_string (JWT_TYP_VALUE));
43 json_str = json_dumps (root, JSON_INDENT(0) | JSON_COMPACT);
49 replace_char(char* str, char find, char replace){
50 char *current_pos = strchr(str,find);
52 *current_pos = replace;
53 current_pos = strchr(current_pos,find);
59 fix_base64(char* str) {
61 //First, remove trailing padding '='
62 padding = strtok(str, "=");
63 while (NULL != padding)
64 padding = strtok(NULL, "=");
67 replace_char (str, '+', '-');
70 replace_char (str, '/', '_');
75 * Create a JWT from attributes
77 * @param aud_key the public of the audience
78 * @param sub_key the public key of the subject
79 * @param attrs the attribute list
80 * @param expiration_time the validity of the token
81 * @param secret_key the key used to sign the JWT
82 * @return a new base64-encoded JWT string.
85 OIDC_id_token_new (const struct GNUNET_CRYPTO_EcdsaPublicKey *aud_key,
86 const struct GNUNET_CRYPTO_EcdsaPublicKey *sub_key,
87 const struct GNUNET_RECLAIM_ATTRIBUTE_ClaimList *attrs,
88 const struct GNUNET_TIME_Relative *expiration_time,
90 const char *secret_key)
92 struct GNUNET_RECLAIM_ATTRIBUTE_ClaimListEntry *le;
93 struct GNUNET_HashCode signature;
94 struct GNUNET_TIME_Absolute exp_time;
95 struct GNUNET_TIME_Absolute time_now;
103 char* signature_target;
104 char* signature_base64;
108 //iat REQUIRED time now
109 time_now = GNUNET_TIME_absolute_get();
110 //exp REQUIRED time expired from config
111 exp_time = GNUNET_TIME_absolute_add (time_now, *expiration_time);
112 //auth_time only if max_age
113 //nonce only if nonce
114 // OPTIONAL acr,amr,azp
115 subject = GNUNET_STRINGS_data_to_string_alloc (sub_key,
116 sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey));
117 audience = GNUNET_STRINGS_data_to_string_alloc (aud_key,
118 sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey));
119 header = create_jwt_header ();
120 body = json_object ();
122 //iss REQUIRED case sensitive server uri with https
123 //The issuer is the local reclaim instance (e.g. https://reclaim.id/api/openid)
124 json_object_set_new (body,
125 "iss", json_string (SERVER_ADDRESS));
126 //sub REQUIRED public key identity, not exceed 255 ASCII length
127 json_object_set_new (body,
128 "sub", json_string (subject));
129 //aud REQUIRED public key client_id must be there
130 json_object_set_new (body,
131 "aud", json_string (audience));
133 json_object_set_new (body,
134 "iat", json_integer (time_now.abs_value_us / (1000*1000)));
136 json_object_set_new (body,
137 "exp", json_integer (exp_time.abs_value_us / (1000*1000)));
139 json_object_set_new (body,
140 "nbf", json_integer (time_now.abs_value_us / (1000*1000)));
143 json_object_set_new (body,
144 "nonce", json_string (nonce));
146 for (le = attrs->list_head; NULL != le; le = le->next)
148 attr_val_str = GNUNET_RECLAIM_ATTRIBUTE_value_to_string (le->claim->type,
150 le->claim->data_size);
151 json_object_set_new (body,
153 json_string (attr_val_str));
154 GNUNET_free (attr_val_str);
156 body_str = json_dumps (body, JSON_INDENT(0) | JSON_COMPACT);
159 GNUNET_STRINGS_base64_encode (header,
162 fix_base64(header_base64);
164 GNUNET_STRINGS_base64_encode (body_str,
167 fix_base64(body_base64);
169 GNUNET_free (subject);
170 GNUNET_free (audience);
173 * Creating the JWT signature. This might not be
174 * standards compliant, check.
176 GNUNET_asprintf (&signature_target, "%s.%s", header_base64, body_base64);
177 GNUNET_CRYPTO_hmac_raw (secret_key, strlen (secret_key), signature_target, strlen (signature_target), &signature);
178 GNUNET_STRINGS_base64_encode ((const char*)&signature,
179 sizeof (struct GNUNET_HashCode),
181 fix_base64(signature_base64);
183 GNUNET_asprintf (&result, "%s.%s.%s",
184 header_base64, body_base64, signature_base64);
186 GNUNET_free (signature_target);
187 GNUNET_free (header);
188 GNUNET_free (body_str);
189 GNUNET_free (signature_base64);
190 GNUNET_free (body_base64);
191 GNUNET_free (header_base64);
195 * Builds an OIDC authorization code including
196 * a reclaim ticket and nonce
198 * @param issuer the issuer of the ticket, used to sign the ticket and nonce
199 * @param ticket the ticket to include in the code
200 * @param nonce the nonce to include in the code
201 * @return a new authorization code (caller must free)
204 OIDC_build_authz_code (const struct GNUNET_CRYPTO_EcdsaPrivateKey *issuer,
205 const struct GNUNET_RECLAIM_Ticket *ticket,
210 char *signature_payload;
213 size_t signature_payload_len;
214 struct GNUNET_CRYPTO_EcdsaSignature signature;
215 struct GNUNET_CRYPTO_EccSignaturePurpose *purpose;
217 signature_payload_len = sizeof (struct GNUNET_RECLAIM_Ticket);
219 signature_payload_len += strlen (nonce);
221 signature_payload = GNUNET_malloc (sizeof (struct GNUNET_CRYPTO_EccSignaturePurpose) + signature_payload_len);
222 purpose = (struct GNUNET_CRYPTO_EccSignaturePurpose *)signature_payload;
223 purpose->size = htonl (sizeof (struct GNUNET_CRYPTO_EccSignaturePurpose) + signature_payload_len);
224 purpose->purpose = htonl (GNUNET_SIGNATURE_PURPOSE_RECLAIM_CODE_SIGN);
227 sizeof (struct GNUNET_RECLAIM_Ticket));
229 memcpy (((char*)&purpose[1]) + sizeof (struct GNUNET_RECLAIM_Ticket),
232 if (GNUNET_SYSERR == GNUNET_CRYPTO_ecdsa_sign (issuer,
236 GNUNET_free (signature_payload);
239 signature_str = GNUNET_STRINGS_data_to_string_alloc (&signature,
241 ticket_str = GNUNET_STRINGS_data_to_string_alloc (ticket,
242 sizeof (struct GNUNET_RECLAIM_Ticket));
244 code_json = json_object ();
245 json_object_set_new (code_json,
247 json_string (ticket_str));
249 json_object_set_new (code_json,
251 json_string (nonce));
252 json_object_set_new (code_json,
254 json_string (signature_str));
255 authz_code = json_dumps (code_json,
256 JSON_INDENT(0) | JSON_COMPACT);
257 GNUNET_free (signature_payload);
258 GNUNET_free (signature_str);
259 GNUNET_free (ticket_str);
260 json_decref (code_json);
268 * Parse reclaim ticket and nonce from
269 * authorization code.
270 * This also verifies the signature in the code.
272 * @param audience the expected audience of the code
273 * @param code the string representation of the code
274 * @param ticket where to store the ticket
275 * @param nonce where to store the nonce
276 * @return GNUNET_OK if successful, else GNUNET_SYSERR
279 OIDC_parse_authz_code (const struct GNUNET_CRYPTO_EcdsaPublicKey *audience,
281 struct GNUNET_RECLAIM_Ticket **ticket,
288 json_t *signature_json;
289 const char *ticket_str;
290 const char *signature_str;
291 const char *nonce_str;
293 struct GNUNET_CRYPTO_EccSignaturePurpose *purpose;
294 struct GNUNET_CRYPTO_EcdsaSignature signature;
295 size_t signature_payload_len;
298 GNUNET_STRINGS_base64_decode (code,
300 (void**)&code_output);
301 code_json = json_loads (code_output, 0 , &error);
302 GNUNET_free (code_output);
303 ticket_json = json_object_get (code_json, "ticket");
304 nonce_json = json_object_get (code_json, "nonce");
305 signature_json = json_object_get (code_json, "signature");
309 if ((NULL == ticket_json || !json_is_string (ticket_json)) ||
310 (NULL == signature_json || !json_is_string (signature_json)))
312 json_decref (code_json);
313 return GNUNET_SYSERR;
315 ticket_str = json_string_value (ticket_json);
316 signature_str = json_string_value (signature_json);
318 if (NULL != nonce_json)
319 nonce_str = json_string_value (nonce_json);
320 signature_payload_len = sizeof (struct GNUNET_RECLAIM_Ticket);
321 if (NULL != nonce_str)
322 signature_payload_len += strlen (nonce_str);
323 purpose = GNUNET_malloc (sizeof (struct GNUNET_CRYPTO_EccSignaturePurpose) +
324 signature_payload_len);
325 purpose->size = htonl (sizeof (struct GNUNET_CRYPTO_EccSignaturePurpose) + signature_payload_len);
326 purpose->purpose = htonl (GNUNET_SIGNATURE_PURPOSE_RECLAIM_CODE_SIGN);
327 if (GNUNET_OK != GNUNET_STRINGS_string_to_data (ticket_str,
330 sizeof (struct GNUNET_RECLAIM_Ticket)))
332 GNUNET_free (purpose);
333 json_decref (code_json);
334 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
335 "Cannot parse ticket!\n");
336 return GNUNET_SYSERR;
338 if (GNUNET_OK != GNUNET_STRINGS_string_to_data (signature_str,
339 strlen (signature_str),
341 sizeof (struct GNUNET_CRYPTO_EcdsaSignature)))
343 GNUNET_free (purpose);
344 json_decref (code_json);
345 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
346 "Cannot parse signature!\n");
347 return GNUNET_SYSERR;
349 *ticket = GNUNET_new (struct GNUNET_RECLAIM_Ticket);
352 sizeof (struct GNUNET_RECLAIM_Ticket));
353 if (0 != memcmp (audience,
354 &(*ticket)->audience,
355 sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey)))
357 GNUNET_free (purpose);
358 GNUNET_free (*ticket);
359 json_decref (code_json);
361 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
362 "Audience in ticket does not match client!\n");
363 return GNUNET_SYSERR;
366 if (NULL != nonce_str)
367 memcpy (((char*)&purpose[1]) + sizeof (struct GNUNET_RECLAIM_Ticket),
370 if (GNUNET_OK != GNUNET_CRYPTO_ecdsa_verify (GNUNET_SIGNATURE_PURPOSE_RECLAIM_CODE_SIGN,
373 &(*ticket)->identity))
375 GNUNET_free (purpose);
376 GNUNET_free (*ticket);
377 json_decref (code_json);
379 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
380 "Signature of authZ code invalid!\n");
381 return GNUNET_SYSERR;
383 *nonce = GNUNET_strdup (nonce_str);
388 * Build a token response for a token request
389 * TODO: Maybe we should add the scope here?
391 * @param access_token the access token to include
392 * @param id_token the id_token to include
393 * @param expiration_time the expiration time of the token(s)
394 * @param token_response where to store the response
397 OIDC_build_token_response (const char *access_token,
398 const char *id_token,
399 const struct GNUNET_TIME_Relative *expiration_time,
400 char **token_response)
404 root_json = json_object ();
406 GNUNET_assert (NULL != access_token);
407 GNUNET_assert (NULL != id_token);
408 GNUNET_assert (NULL != expiration_time);
409 json_object_set_new (root_json,
411 json_string (access_token));
412 json_object_set_new (root_json,
414 json_string ("Bearer"));
415 json_object_set_new (root_json,
417 json_integer (expiration_time->rel_value_us / (1000 * 1000)));
418 json_object_set_new (root_json,
420 json_string (id_token));
421 *token_response = json_dumps (root_json,
422 JSON_INDENT(0) | JSON_COMPACT);
423 json_decref (root_json);
427 * Generate a new access token
430 OIDC_access_token_new ()
432 char* access_token_number;
434 uint64_t random_number;
436 random_number = GNUNET_CRYPTO_random_u64(GNUNET_CRYPTO_QUALITY_NONCE, UINT64_MAX);
437 GNUNET_asprintf (&access_token_number, "%" PRIu64, random_number);
438 GNUNET_STRINGS_base64_encode(access_token_number,strlen(access_token_number),&access_token);