fix signature check
[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
19 /**
20  * @file reclaim/oidc_helper.c
21  * @brief helper library for OIDC related functions
22  * @author Martin Schanzenbach
23  */
24 #include "platform.h"
25 #include "gnunet_util_lib.h"
26 #include "gnunet_signatures.h"
27 #include "gnunet_reclaim_service.h"
28 #include "gnunet_reclaim_attribute_lib.h"
29 #include <jansson.h>
30 #include <inttypes.h>
31 #include "oidc_helper.h"
32
33 static char*
34 create_jwt_header(void)
35 {
36   json_t *root;
37   char *json_str;
38
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));
42
43   json_str = json_dumps (root, JSON_INDENT(0) | JSON_COMPACT);
44   json_decref (root);
45   return json_str;
46 }
47
48 static void
49 replace_char(char* str, char find, char replace){
50   char *current_pos = strchr(str,find);
51   while (current_pos){
52     *current_pos = replace;
53     current_pos = strchr(current_pos,find);
54   }
55 }
56
57 //RFC4648
58 static void
59 fix_base64(char* str) {
60   char *padding;
61   //First, remove trailing padding '='
62   padding = strtok(str, "=");
63   while (NULL != padding)
64     padding = strtok(NULL, "=");
65
66   //Replace + with -
67   replace_char (str, '+', '-');
68
69   //Replace / with _
70   replace_char (str, '/', '_');
71
72 }
73
74 /**
75  * Create a JWT from attributes
76  *
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.
83  */
84 char*
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,
89                    const char *nonce,
90                    const char *secret_key)
91 {
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;
96   char* audience;
97   char* subject;
98   char* header;
99   char* body_str;
100   char* result;
101   char* header_base64;
102   char* body_base64;
103   char* signature_target;
104   char* signature_base64;
105   char* attr_val_str;
106   json_t* body;
107
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 ();
121
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));
132   //iat
133   json_object_set_new (body,
134                        "iat", json_integer (time_now.abs_value_us / (1000*1000)));
135   //exp
136   json_object_set_new (body,
137                        "exp", json_integer (exp_time.abs_value_us / (1000*1000)));
138   //nbf
139   json_object_set_new (body,
140                        "nbf", json_integer (time_now.abs_value_us / (1000*1000)));
141   //nonce
142   if (NULL != nonce)
143     json_object_set_new (body,
144                          "nonce", json_string (nonce));
145
146   for (le = attrs->list_head; NULL != le; le = le->next)
147   {
148     attr_val_str = GNUNET_RECLAIM_ATTRIBUTE_value_to_string (le->claim->type,
149                                                              le->claim->data,
150                                                              le->claim->data_size);
151     json_object_set_new (body,
152                          le->claim->name,
153                          json_string (attr_val_str));
154     GNUNET_free (attr_val_str);
155   }
156   body_str = json_dumps (body, JSON_INDENT(0) | JSON_COMPACT);
157   json_decref (body);
158
159   GNUNET_STRINGS_base64_encode (header,
160                                 strlen (header),
161                                 &header_base64);
162   fix_base64(header_base64);
163
164   GNUNET_STRINGS_base64_encode (body_str,
165                                 strlen (body_str),
166                                 &body_base64);
167   fix_base64(body_base64);
168
169   GNUNET_free (subject);
170   GNUNET_free (audience);
171
172   /**
173    * Creating the JWT signature. This might not be
174    * standards compliant, check.
175    */
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),
180                                 &signature_base64);
181   fix_base64(signature_base64);
182
183   GNUNET_asprintf (&result, "%s.%s.%s",
184                    header_base64, body_base64, signature_base64);
185
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);
192   return result;
193 }
194 /**
195  * Builds an OIDC authorization code including
196  * a reclaim ticket and nonce
197  *
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)
202  */
203 char*
204 OIDC_build_authz_code (const struct GNUNET_CRYPTO_EcdsaPrivateKey *issuer,
205                        const struct GNUNET_RECLAIM_Ticket *ticket,
206                        const char* nonce)
207 {
208   char *ticket_str;
209   json_t *code_json;
210   char *signature_payload;
211   char *signature_str;
212   char *authz_code;
213   size_t signature_payload_len;
214   struct GNUNET_CRYPTO_EcdsaSignature signature;
215   struct GNUNET_CRYPTO_EccSignaturePurpose *purpose;
216
217   signature_payload_len = sizeof (struct GNUNET_RECLAIM_Ticket);
218   if (NULL != nonce)
219     signature_payload_len += strlen (nonce);
220
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);
225   memcpy (&purpose[1],
226           ticket,
227           sizeof (struct GNUNET_RECLAIM_Ticket));
228   if (NULL != nonce)
229     memcpy (((char*)&purpose[1]) + sizeof (struct GNUNET_RECLAIM_Ticket),
230             nonce,
231             strlen (nonce));
232   if (GNUNET_SYSERR == GNUNET_CRYPTO_ecdsa_sign (issuer,
233                                                  purpose,
234                                                  &signature))
235   {
236     GNUNET_free (signature_payload);
237     return NULL;
238   }
239   signature_str = GNUNET_STRINGS_data_to_string_alloc (&signature,
240                                                        sizeof (signature));
241   ticket_str = GNUNET_STRINGS_data_to_string_alloc (ticket,
242                                                     sizeof (struct GNUNET_RECLAIM_Ticket));
243
244   code_json = json_object ();
245   json_object_set_new (code_json,
246                        "ticket",
247                        json_string (ticket_str));
248   if (NULL != nonce)
249     json_object_set_new (code_json,
250                          "nonce",
251                          json_string (nonce));
252   json_object_set_new (code_json,
253                        "signature",
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);
261   return authz_code;
262 }
263
264
265
266
267 /**
268  * Parse reclaim ticket and nonce from
269  * authorization code.
270  * This also verifies the signature in the code.
271  *
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
277  */
278 int
279 OIDC_parse_authz_code (const struct GNUNET_CRYPTO_EcdsaPublicKey *audience,
280                        const char* code,
281                        struct GNUNET_RECLAIM_Ticket **ticket,
282                        char **nonce)
283 {
284   json_error_t error;
285   json_t *code_json;
286   json_t *ticket_json;
287   json_t *nonce_json;
288   json_t *signature_json;
289   const char *ticket_str;
290   const char *signature_str;
291   const char *nonce_str;
292   char *code_output;
293   struct GNUNET_CRYPTO_EccSignaturePurpose *purpose;
294   struct GNUNET_CRYPTO_EcdsaSignature signature;
295   size_t signature_payload_len;
296
297   code_output = NULL; 
298   GNUNET_STRINGS_base64_decode (code,
299                                 strlen(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");
306   *ticket = NULL;
307   *nonce = NULL;
308
309   if ((NULL == ticket_json || !json_is_string (ticket_json)) ||
310       (NULL == signature_json || !json_is_string (signature_json)))
311   {
312     json_decref (code_json);
313     return GNUNET_SYSERR;
314   }
315   ticket_str = json_string_value (ticket_json);
316   signature_str = json_string_value (signature_json);
317   nonce_str = NULL;
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,
328                                                   strlen (ticket_str),
329                                                   &purpose[1],
330                                                   sizeof (struct GNUNET_RECLAIM_Ticket)))
331   {
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;
337   }
338   if (GNUNET_OK != GNUNET_STRINGS_string_to_data (signature_str,
339                                                   strlen (signature_str),
340                                                   &signature,
341                                                   sizeof (struct GNUNET_CRYPTO_EcdsaSignature)))
342   {
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;
348   }
349   *ticket = GNUNET_new (struct GNUNET_RECLAIM_Ticket);
350   memcpy (*ticket,
351           &purpose[1],
352           sizeof (struct GNUNET_RECLAIM_Ticket));
353   if (0 != memcmp (audience,
354                    &(*ticket)->audience,
355                    sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey)))
356   {
357     GNUNET_free (purpose);
358     GNUNET_free (*ticket);
359     json_decref (code_json);
360     *ticket = NULL;
361     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
362                 "Audience in ticket does not match client!\n");
363     return GNUNET_SYSERR;
364
365   }
366   if (NULL != nonce_str)
367     memcpy (((char*)&purpose[1]) + sizeof (struct GNUNET_RECLAIM_Ticket),
368             nonce_str,
369             strlen (nonce_str));
370   if (GNUNET_OK != GNUNET_CRYPTO_ecdsa_verify (GNUNET_SIGNATURE_PURPOSE_RECLAIM_CODE_SIGN,
371                                                purpose,
372                                                &signature,
373                                                &(*ticket)->identity))
374   {
375     GNUNET_free (purpose);
376     GNUNET_free (*ticket);
377     json_decref (code_json);
378     *ticket = NULL;
379     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
380                 "Signature of authZ code invalid!\n");
381     return GNUNET_SYSERR;
382   }
383   *nonce = GNUNET_strdup (nonce_str);
384   return GNUNET_OK;
385 }
386
387 /**
388  * Build a token response for a token request
389  * TODO: Maybe we should add the scope here?
390  *
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
395  */
396 void
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)
401 {
402   json_t *root_json;
403
404   root_json = json_object ();
405
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,
410                        "access_token",
411                        json_string (access_token));
412   json_object_set_new (root_json,
413                        "token_type",
414                        json_string ("Bearer"));
415   json_object_set_new (root_json,
416                        "expires_in",
417                        json_integer (expiration_time->rel_value_us / (1000 * 1000)));
418   json_object_set_new (root_json,
419                        "id_token",
420                        json_string (id_token));
421   *token_response = json_dumps (root_json,
422                                 JSON_INDENT(0) | JSON_COMPACT);
423   json_decref (root_json);
424 }
425
426 /**
427  * Generate a new access token
428  */
429 char*
430 OIDC_access_token_new ()
431 {
432   char* access_token_number;
433   char* access_token;
434   uint64_t random_number;
435
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);
439   return access_token;
440 }