merge conflict resolution
[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 "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
35 static char*
36 create_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
50 static void
51 replace_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
60 static void
61 fix_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  */
80 char*
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,
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  */
199 char*
200 OIDC_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  */
274 int
275 OIDC_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  */
392 void
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)
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  */
425 char*
426 OIDC_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 }