memcmp() -> GNUNET_memcmp(), first take
[oweals/gnunet.git] / src / reclaim / oidc_helper.c
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 <inttypes.h>
28 #include <jansson.h>
29
30 #include "gnunet_util_lib.h"
31
32 #include "gnunet_reclaim_attribute_lib.h"
33 #include "gnunet_reclaim_service.h"
34 #include "gnunet_signatures.h"
35 #include "oidc_helper.h"
36 static char *
37 create_jwt_header (void)
38 {
39   json_t *root;
40   char *json_str;
41
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));
45
46   json_str = json_dumps (root, JSON_INDENT (0) | JSON_COMPACT);
47   json_decref (root);
48   return json_str;
49 }
50
51 static void
52 replace_char (char *str, char find, char replace)
53 {
54   char *current_pos = strchr (str, find);
55   while (current_pos) {
56     *current_pos = replace;
57     current_pos = strchr (current_pos, find);
58   }
59 }
60
61 // RFC4648
62 static void
63 fix_base64 (char *str)
64 {
65   // Replace + with -
66   replace_char (str, '+', '-');
67
68   // Replace / with _
69   replace_char (str, '/', '_');
70 }
71
72 /**
73  * Create a JWT from attributes
74  *
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.
81  */
82 char *
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)
88 {
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;
93   char *audience;
94   char *subject;
95   char *header;
96   char *body_str;
97   char *result;
98   char *header_base64;
99   char *body_base64;
100   char *signature_target;
101   char *signature_base64;
102   char *attr_val_str;
103   json_t *body;
104
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 ();
118
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));
127   // iat
128   json_object_set_new (body, "iat",
129                        json_integer (time_now.abs_value_us / (1000 * 1000)));
130   // exp
131   json_object_set_new (body, "exp",
132                        json_integer (exp_time.abs_value_us / (1000 * 1000)));
133   // nbf
134   json_object_set_new (body, "nbf",
135                        json_integer (time_now.abs_value_us / (1000 * 1000)));
136   // nonce
137   if (NULL != nonce)
138     json_object_set_new (body, "nonce", json_string (nonce));
139
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);
145   }
146   body_str = json_dumps (body, JSON_INDENT (0) | JSON_COMPACT);
147   json_decref (body);
148
149   GNUNET_STRINGS_base64_encode (header, strlen (header), &header_base64);
150   fix_base64 (header_base64);
151
152   GNUNET_STRINGS_base64_encode (body_str, strlen (body_str), &body_base64);
153   fix_base64 (body_base64);
154
155   GNUNET_free (subject);
156   GNUNET_free (audience);
157
158   /**
159    * Creating the JWT signature. This might not be
160    * standards compliant, check.
161    */
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),
167                                 &signature_base64);
168   fix_base64 (signature_base64);
169
170   GNUNET_asprintf (&result, "%s.%s.%s", header_base64, body_base64,
171                    signature_base64);
172
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);
179   return result;
180 }
181 /**
182  * Builds an OIDC authorization code including
183  * a reclaim ticket and nonce
184  *
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)
189  */
190 char *
191 OIDC_build_authz_code (const struct GNUNET_CRYPTO_EcdsaPrivateKey *issuer,
192                        const struct GNUNET_RECLAIM_Ticket *ticket,
193                        const char *nonce)
194 {
195   char *ticket_str;
196   json_t *code_json;
197   char *signature_payload;
198   char *signature_str;
199   char *authz_code;
200   size_t signature_payload_len;
201   struct GNUNET_CRYPTO_EcdsaSignature signature;
202   struct GNUNET_CRYPTO_EccSignaturePurpose *purpose;
203
204   signature_payload_len = sizeof (struct GNUNET_RECLAIM_Ticket);
205   if (NULL != nonce)
206     signature_payload_len += strlen (nonce);
207
208   signature_payload =
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));
216   if (NULL != nonce)
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);
221     return NULL;
222   }
223   signature_str =
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));
227
228   code_json = json_object ();
229   json_object_set_new (code_json, "ticket", json_string (ticket_str));
230   if (NULL != nonce)
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);
238   return authz_code;
239 }
240
241
242 /**
243  * Parse reclaim ticket and nonce from
244  * authorization code.
245  * This also verifies the signature in the code.
246  *
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
252  */
253 int
254 OIDC_parse_authz_code (const struct GNUNET_CRYPTO_EcdsaPublicKey *audience,
255                        const char *code, struct GNUNET_RECLAIM_Ticket **ticket,
256                        char **nonce)
257 {
258   json_error_t error;
259   json_t *code_json;
260   json_t *ticket_json;
261   json_t *nonce_json;
262   json_t *signature_json;
263   const char *ticket_str;
264   const char *signature_str;
265   const char *nonce_str;
266   char *code_output;
267   struct GNUNET_CRYPTO_EccSignaturePurpose *purpose;
268   struct GNUNET_CRYPTO_EcdsaSignature signature;
269   size_t signature_payload_len;
270
271   code_output = NULL;
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");
278   *ticket = NULL;
279   *nonce = NULL;
280
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;
285   }
286   ticket_str = json_string_value (ticket_json);
287   signature_str = json_string_value (signature_json);
288   nonce_str = NULL;
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;
306   }
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;
314   }
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);
321     *ticket = NULL;
322     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
323                 "Audience in ticket does not match client!\n");
324     return GNUNET_SYSERR;
325   }
326   if (NULL != nonce_str)
327     memcpy (((char *)&purpose[1]) + sizeof (struct GNUNET_RECLAIM_Ticket),
328             nonce_str, strlen (nonce_str));
329   if (GNUNET_OK !=
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);
335     *ticket = NULL;
336     GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Signature of authZ code invalid!\n");
337     return GNUNET_SYSERR;
338   }
339   *nonce = GNUNET_strdup (nonce_str);
340   return GNUNET_OK;
341 }
342
343 /**
344  * Build a token response for a token request
345  * TODO: Maybe we should add the scope here?
346  *
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
351  */
352 void
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)
356 {
357   json_t *root_json;
358
359   root_json = json_object ();
360
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);
372 }
373
374 /**
375  * Generate a new access token
376  */
377 char *
378 OIDC_access_token_new ()
379 {
380   char *access_token_number;
381   char *access_token;
382   uint64_t random_number;
383
384   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);
389   return access_token;
390 }