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
30 #include "gnunet_util_lib.h"
32 #include "gnunet_reclaim_attribute_lib.h"
33 #include "gnunet_reclaim_service.h"
34 #include "gnunet_signatures.h"
35 #include "oidc_helper.h"
37 create_jwt_header (void)
42 root = json_object ();
43 json_object_set_new (root, JWT_ALG, json_string (JWT_ALG_VALUE));
44 json_object_set_new (root, JWT_TYP, json_string (JWT_TYP_VALUE));
46 json_str = json_dumps (root, JSON_INDENT (0) | JSON_COMPACT);
52 replace_char (char *str, char find, char replace)
54 char *current_pos = strchr (str, find);
56 *current_pos = replace;
57 current_pos = strchr (current_pos, find);
63 fix_base64 (char *str)
66 replace_char (str, '+', '-');
69 replace_char (str, '/', '_');
73 * Create a JWT from attributes
75 * @param aud_key the public of the audience
76 * @param sub_key the public key of the subject
77 * @param attrs the attribute list
78 * @param expiration_time the validity of the token
79 * @param secret_key the key used to sign the JWT
80 * @return a new base64-encoded JWT string.
83 OIDC_id_token_new (const struct GNUNET_CRYPTO_EcdsaPublicKey *aud_key,
84 const struct GNUNET_CRYPTO_EcdsaPublicKey *sub_key,
85 const struct GNUNET_RECLAIM_ATTRIBUTE_ClaimList *attrs,
86 const struct GNUNET_TIME_Relative *expiration_time,
87 const char *nonce, const char *secret_key)
89 struct GNUNET_RECLAIM_ATTRIBUTE_ClaimListEntry *le;
90 struct GNUNET_HashCode signature;
91 struct GNUNET_TIME_Absolute exp_time;
92 struct GNUNET_TIME_Absolute time_now;
100 char *signature_target;
101 char *signature_base64;
105 // iat REQUIRED time now
106 time_now = GNUNET_TIME_absolute_get ();
107 // exp REQUIRED time expired from config
108 exp_time = GNUNET_TIME_absolute_add (time_now, *expiration_time);
109 // auth_time only if max_age
110 // nonce only if nonce
111 // OPTIONAL acr,amr,azp
112 subject = GNUNET_STRINGS_data_to_string_alloc (
113 sub_key, sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey));
114 audience = GNUNET_STRINGS_data_to_string_alloc (
115 aud_key, sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey));
116 header = create_jwt_header ();
117 body = json_object ();
119 // iss REQUIRED case sensitive server uri with https
120 // The issuer is the local reclaim instance (e.g.
121 // https://reclaim.id/api/openid)
122 json_object_set_new (body, "iss", json_string (SERVER_ADDRESS));
123 // sub REQUIRED public key identity, not exceed 255 ASCII length
124 json_object_set_new (body, "sub", json_string (subject));
125 // aud REQUIRED public key client_id must be there
126 json_object_set_new (body, "aud", json_string (audience));
128 json_object_set_new (body, "iat",
129 json_integer (time_now.abs_value_us / (1000 * 1000)));
131 json_object_set_new (body, "exp",
132 json_integer (exp_time.abs_value_us / (1000 * 1000)));
134 json_object_set_new (body, "nbf",
135 json_integer (time_now.abs_value_us / (1000 * 1000)));
138 json_object_set_new (body, "nonce", json_string (nonce));
140 for (le = attrs->list_head; NULL != le; le = le->next) {
141 attr_val_str = GNUNET_RECLAIM_ATTRIBUTE_value_to_string (
142 le->claim->type, le->claim->data, le->claim->data_size);
143 json_object_set_new (body, le->claim->name, json_string (attr_val_str));
144 GNUNET_free (attr_val_str);
146 body_str = json_dumps (body, JSON_INDENT (0) | JSON_COMPACT);
149 GNUNET_STRINGS_base64_encode (header, strlen (header), &header_base64);
150 fix_base64 (header_base64);
152 GNUNET_STRINGS_base64_encode (body_str, strlen (body_str), &body_base64);
153 fix_base64 (body_base64);
155 GNUNET_free (subject);
156 GNUNET_free (audience);
159 * Creating the JWT signature. This might not be
160 * standards compliant, check.
162 GNUNET_asprintf (&signature_target, "%s.%s", header_base64, body_base64);
163 GNUNET_CRYPTO_hmac_raw (secret_key, strlen (secret_key), signature_target,
164 strlen (signature_target), &signature);
165 GNUNET_STRINGS_base64_encode ((const char *)&signature,
166 sizeof (struct GNUNET_HashCode),
168 fix_base64 (signature_base64);
170 GNUNET_asprintf (&result, "%s.%s.%s", header_base64, body_base64,
173 GNUNET_free (signature_target);
174 GNUNET_free (header);
175 GNUNET_free (body_str);
176 GNUNET_free (signature_base64);
177 GNUNET_free (body_base64);
178 GNUNET_free (header_base64);
182 * Builds an OIDC authorization code including
183 * a reclaim ticket and nonce
185 * @param issuer the issuer of the ticket, used to sign the ticket and nonce
186 * @param ticket the ticket to include in the code
187 * @param nonce the nonce to include in the code
188 * @return a new authorization code (caller must free)
191 OIDC_build_authz_code (const struct GNUNET_CRYPTO_EcdsaPrivateKey *issuer,
192 const struct GNUNET_RECLAIM_Ticket *ticket,
197 char *signature_payload;
200 size_t signature_payload_len;
201 struct GNUNET_CRYPTO_EcdsaSignature signature;
202 struct GNUNET_CRYPTO_EccSignaturePurpose *purpose;
204 signature_payload_len = sizeof (struct GNUNET_RECLAIM_Ticket);
206 signature_payload_len += strlen (nonce);
209 GNUNET_malloc (sizeof (struct GNUNET_CRYPTO_EccSignaturePurpose) +
210 signature_payload_len);
211 purpose = (struct GNUNET_CRYPTO_EccSignaturePurpose *)signature_payload;
212 purpose->size = htonl (sizeof (struct GNUNET_CRYPTO_EccSignaturePurpose) +
213 signature_payload_len);
214 purpose->purpose = htonl (GNUNET_SIGNATURE_PURPOSE_RECLAIM_CODE_SIGN);
215 memcpy (&purpose[1], ticket, sizeof (struct GNUNET_RECLAIM_Ticket));
217 memcpy (((char *)&purpose[1]) + sizeof (struct GNUNET_RECLAIM_Ticket),
218 nonce, strlen (nonce));
219 if (GNUNET_SYSERR == GNUNET_CRYPTO_ecdsa_sign (issuer, purpose, &signature)) {
220 GNUNET_free (signature_payload);
224 GNUNET_STRINGS_data_to_string_alloc (&signature, sizeof (signature));
225 ticket_str = GNUNET_STRINGS_data_to_string_alloc (
226 ticket, sizeof (struct GNUNET_RECLAIM_Ticket));
228 code_json = json_object ();
229 json_object_set_new (code_json, "ticket", json_string (ticket_str));
231 json_object_set_new (code_json, "nonce", json_string (nonce));
232 json_object_set_new (code_json, "signature", json_string (signature_str));
233 authz_code = json_dumps (code_json, JSON_INDENT (0) | JSON_COMPACT);
234 GNUNET_free (signature_payload);
235 GNUNET_free (signature_str);
236 GNUNET_free (ticket_str);
237 json_decref (code_json);
243 * Parse reclaim ticket and nonce from
244 * authorization code.
245 * This also verifies the signature in the code.
247 * @param audience the expected audience of the code
248 * @param code the string representation of the code
249 * @param ticket where to store the ticket
250 * @param nonce where to store the nonce
251 * @return GNUNET_OK if successful, else GNUNET_SYSERR
254 OIDC_parse_authz_code (const struct GNUNET_CRYPTO_EcdsaPublicKey *audience,
255 const char *code, struct GNUNET_RECLAIM_Ticket **ticket,
262 json_t *signature_json;
263 const char *ticket_str;
264 const char *signature_str;
265 const char *nonce_str;
267 struct GNUNET_CRYPTO_EccSignaturePurpose *purpose;
268 struct GNUNET_CRYPTO_EcdsaSignature signature;
269 size_t signature_payload_len;
272 GNUNET_STRINGS_base64_decode (code, strlen (code), (void **)&code_output);
273 code_json = json_loads (code_output, 0, &error);
274 GNUNET_free (code_output);
275 ticket_json = json_object_get (code_json, "ticket");
276 nonce_json = json_object_get (code_json, "nonce");
277 signature_json = json_object_get (code_json, "signature");
281 if ((NULL == ticket_json || !json_is_string (ticket_json)) ||
282 (NULL == signature_json || !json_is_string (signature_json))) {
283 json_decref (code_json);
284 return GNUNET_SYSERR;
286 ticket_str = json_string_value (ticket_json);
287 signature_str = json_string_value (signature_json);
289 if (NULL != nonce_json)
290 nonce_str = json_string_value (nonce_json);
291 signature_payload_len = sizeof (struct GNUNET_RECLAIM_Ticket);
292 if (NULL != nonce_str)
293 signature_payload_len += strlen (nonce_str);
294 purpose = GNUNET_malloc (sizeof (struct GNUNET_CRYPTO_EccSignaturePurpose) +
295 signature_payload_len);
296 purpose->size = htonl (sizeof (struct GNUNET_CRYPTO_EccSignaturePurpose) +
297 signature_payload_len);
298 purpose->purpose = htonl (GNUNET_SIGNATURE_PURPOSE_RECLAIM_CODE_SIGN);
299 if (GNUNET_OK != GNUNET_STRINGS_string_to_data (
300 ticket_str, strlen (ticket_str), &purpose[1],
301 sizeof (struct GNUNET_RECLAIM_Ticket))) {
302 GNUNET_free (purpose);
303 json_decref (code_json);
304 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Cannot parse ticket!\n");
305 return GNUNET_SYSERR;
307 if (GNUNET_OK != GNUNET_STRINGS_string_to_data (
308 signature_str, strlen (signature_str), &signature,
309 sizeof (struct GNUNET_CRYPTO_EcdsaSignature))) {
310 GNUNET_free (purpose);
311 json_decref (code_json);
312 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Cannot parse signature!\n");
313 return GNUNET_SYSERR;
315 *ticket = GNUNET_new (struct GNUNET_RECLAIM_Ticket);
316 memcpy (*ticket, &purpose[1], sizeof (struct GNUNET_RECLAIM_Ticket));
317 if (0 != GNUNET_memcmp (audience, &(*ticket)->audience)) {
318 GNUNET_free (purpose);
319 GNUNET_free (*ticket);
320 json_decref (code_json);
322 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
323 "Audience in ticket does not match client!\n");
324 return GNUNET_SYSERR;
326 if (NULL != nonce_str)
327 memcpy (((char *)&purpose[1]) + sizeof (struct GNUNET_RECLAIM_Ticket),
328 nonce_str, strlen (nonce_str));
330 GNUNET_CRYPTO_ecdsa_verify (GNUNET_SIGNATURE_PURPOSE_RECLAIM_CODE_SIGN,
331 purpose, &signature, &(*ticket)->identity)) {
332 GNUNET_free (purpose);
333 GNUNET_free (*ticket);
334 json_decref (code_json);
336 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Signature of authZ code invalid!\n");
337 return GNUNET_SYSERR;
339 *nonce = GNUNET_strdup (nonce_str);
344 * Build a token response for a token request
345 * TODO: Maybe we should add the scope here?
347 * @param access_token the access token to include
348 * @param id_token the id_token to include
349 * @param expiration_time the expiration time of the token(s)
350 * @param token_response where to store the response
353 OIDC_build_token_response (const char *access_token, const char *id_token,
354 const struct GNUNET_TIME_Relative *expiration_time,
355 char **token_response)
359 root_json = json_object ();
361 GNUNET_assert (NULL != access_token);
362 GNUNET_assert (NULL != id_token);
363 GNUNET_assert (NULL != expiration_time);
364 json_object_set_new (root_json, "access_token", json_string (access_token));
365 json_object_set_new (root_json, "token_type", json_string ("Bearer"));
366 json_object_set_new (
367 root_json, "expires_in",
368 json_integer (expiration_time->rel_value_us / (1000 * 1000)));
369 json_object_set_new (root_json, "id_token", json_string (id_token));
370 *token_response = json_dumps (root_json, JSON_INDENT (0) | JSON_COMPACT);
371 json_decref (root_json);
375 * Generate a new access token
378 OIDC_access_token_new ()
380 char *access_token_number;
382 uint64_t random_number;
385 GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_NONCE, UINT64_MAX);
386 GNUNET_asprintf (&access_token_number, "%" PRIu64, random_number);
387 GNUNET_STRINGS_base64_encode (access_token_number,
388 strlen (access_token_number), &access_token);