anastasis

Credential backup and recovery protocol and service
Log | Files | Refs | Submodules | README | LICENSE

anastasis_api_policy_store.c (16542B)


      1 /*
      2   This file is part of ANASTASIS
      3   Copyright (C) 2014-2022 Anastasis SARL
      4 
      5   ANASTASIS is free software; you can redistribute it and/or modify
      6   it under the terms of the GNU General Public License as
      7   published by the Free Software Foundation; either version 2.1,
      8   or (at your option) any later version.
      9 
     10   ANASTASIS 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
     13   GNU General Public License for more details.
     14 
     15   You should have received a copy of the GNU General Public
     16   License along with ANASTASIS; see the file COPYING.LGPL.  If not,
     17   see <http://www.gnu.org/licenses/>
     18 */
     19 
     20 /**
     21  * @file restclient/anastasis_api_policy_store.c
     22  * @brief Implementation of the /policy GET and POST
     23  * @author Christian Grothoff
     24  * @author Dennis Neufeld
     25  * @author Dominik Meister
     26  */
     27 #include "platform.h"
     28 #include <curl/curl.h>
     29 #include <microhttpd.h> /* just for HTTP status codes */
     30 #include "anastasis_service.h"
     31 #include "anastasis_api_curl_defaults.h"
     32 #include <taler/taler_signatures.h>
     33 #include <taler/taler_merchant_service.h>
     34 #include <taler/taler_json_lib.h>
     35 
     36 
     37 struct ANASTASIS_PolicyStoreOperation
     38 {
     39   /**
     40    * Complete URL where the backend offers /policy
     41    */
     42   char *url;
     43 
     44   /**
     45    * Handle for the request.
     46    */
     47   struct GNUNET_CURL_Job *job;
     48 
     49   /**
     50    * The CURL context to connect to the backend
     51    */
     52   struct GNUNET_CURL_Context *ctx;
     53 
     54   /**
     55    * The callback to pass the backend response to
     56    */
     57   ANASTASIS_PolicyStoreCallback cb;
     58 
     59   /**
     60    * Closure for @e cb.
     61    */
     62   void *cb_cls;
     63 
     64   /**
     65    * Payment URI we received from the service, or NULL.
     66    */
     67   char *pay_uri;
     68 
     69   /**
     70    * Policy version we received from the service, or NULL.
     71    */
     72   char *policy_version;
     73 
     74   /**
     75    * Policy expiration we received from the service, or NULL.
     76    */
     77   char *policy_expiration;
     78 
     79   /**
     80    * Copy of the uploaded data. Needed by curl.
     81    */
     82   void *postcopy;
     83 
     84   /**
     85    * Hash of the data we are uploading.
     86    */
     87   struct GNUNET_HashCode new_upload_hash;
     88 };
     89 
     90 
     91 void
     92 ANASTASIS_policy_store_cancel (
     93   struct ANASTASIS_PolicyStoreOperation *pso)
     94 {
     95   if (NULL != pso->job)
     96   {
     97     GNUNET_CURL_job_cancel (pso->job);
     98     pso->job = NULL;
     99   }
    100   GNUNET_free (pso->policy_version);
    101   GNUNET_free (pso->policy_expiration);
    102   GNUNET_free (pso->pay_uri);
    103   GNUNET_free (pso->url);
    104   GNUNET_free (pso->postcopy);
    105   GNUNET_free (pso);
    106 }
    107 
    108 
    109 /**
    110  * Callback to process POST /policy response
    111  *
    112  * @param cls the `struct ANASTASIS_PolicyStoreOperation`
    113  * @param response_code HTTP response code, 0 on error
    114  * @param data response body
    115  * @param data_size number of bytes in @a data
    116  */
    117 static void
    118 handle_policy_store_finished (void *cls,
    119                               long response_code,
    120                               const void *data,
    121                               size_t data_size)
    122 {
    123   struct ANASTASIS_PolicyStoreOperation *pso = cls;
    124   struct ANASTASIS_UploadDetails ud;
    125 
    126   pso->job = NULL;
    127   memset (&ud, 0, sizeof (ud));
    128   ud.http_status = response_code;
    129   ud.ec = TALER_EC_NONE;
    130 
    131   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    132               "Policy store finished with HTTP status %u\n",
    133               (unsigned int) response_code);
    134   switch (response_code)
    135   {
    136   case 0:
    137     ud.us = ANASTASIS_US_SERVER_ERROR;
    138     ud.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    139     break;
    140   case MHD_HTTP_NO_CONTENT:
    141   case MHD_HTTP_NOT_MODIFIED:
    142     {
    143       unsigned long long version;
    144       unsigned long long expiration;
    145       char dummy;
    146 
    147       if (1 != sscanf (pso->policy_version,
    148                        "%llu%c",
    149                        &version,
    150                        &dummy))
    151       {
    152         ud.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    153         ud.us = ANASTASIS_US_SERVER_ERROR;
    154         break;
    155       }
    156       if (1 != sscanf (pso->policy_expiration,
    157                        "%llu%c",
    158                        &expiration,
    159                        &dummy))
    160       {
    161         ud.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    162         ud.us = ANASTASIS_US_SERVER_ERROR;
    163         break;
    164       }
    165       ud.us = ANASTASIS_US_SUCCESS;
    166       ud.details.success.curr_backup_hash = &pso->new_upload_hash;
    167       ud.details.success.policy_expiration
    168         = GNUNET_TIME_relative_to_timestamp (
    169             GNUNET_TIME_relative_multiply (
    170               GNUNET_TIME_UNIT_SECONDS,
    171               expiration));
    172       ud.details.success.policy_version = version;
    173     }
    174     break;
    175   case MHD_HTTP_BAD_REQUEST:
    176     GNUNET_break (0);
    177     ud.us = ANASTASIS_US_CLIENT_ERROR;
    178     ud.ec = TALER_JSON_get_error_code2 (data,
    179                                         data_size);
    180     break;
    181   case MHD_HTTP_PAYMENT_REQUIRED:
    182     {
    183       struct TALER_MERCHANT_PayUriData pd;
    184 
    185       if ( (NULL == pso->pay_uri) ||
    186            (GNUNET_OK !=
    187             TALER_MERCHANT_parse_pay_uri (pso->pay_uri,
    188                                           &pd)) )
    189       {
    190         GNUNET_break_op (0);
    191         ud.ec = TALER_EC_ANASTASIS_GENERIC_INVALID_PAYMENT_REQUEST;
    192         break;
    193       }
    194       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    195                   "Policy store operation requires payment `%s'\n",
    196                   pso->pay_uri);
    197       if (GNUNET_OK !=
    198           GNUNET_STRINGS_string_to_data (
    199             pd.order_id,
    200             strlen (pd.order_id),
    201             &ud.details.payment.ps,
    202             sizeof (ud.details.payment.ps)))
    203       {
    204         GNUNET_break (0);
    205         ud.ec = TALER_EC_ANASTASIS_GENERIC_INVALID_PAYMENT_REQUEST;
    206         TALER_MERCHANT_parse_pay_uri_free (&pd);
    207         break;
    208       }
    209       TALER_MERCHANT_parse_pay_uri_free (&pd);
    210     }
    211     ud.us = ANASTASIS_US_PAYMENT_REQUIRED;
    212     ud.details.payment.payment_request = pso->pay_uri;
    213     break;
    214   case MHD_HTTP_REQUEST_TIMEOUT:
    215     ud.us = ANASTASIS_US_CLIENT_ERROR;
    216     ud.ec = TALER_EC_ANASTASIS_PAYMENT_GENERIC_TIMEOUT;
    217     break;
    218   case MHD_HTTP_PAYLOAD_TOO_LARGE:
    219     ud.us = ANASTASIS_US_CLIENT_ERROR;
    220     ud.ec = TALER_EC_GENERIC_UPLOAD_EXCEEDS_LIMIT;
    221     break;
    222   case MHD_HTTP_LENGTH_REQUIRED:
    223     GNUNET_break (0);
    224     ud.ec = TALER_JSON_get_error_code2 (data,
    225                                         data_size);
    226     ud.us = ANASTASIS_US_SERVER_ERROR;
    227     break;
    228   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    229     ud.ec = TALER_JSON_get_error_code2 (data,
    230                                         data_size);
    231     ud.us = ANASTASIS_US_SERVER_ERROR;
    232     break;
    233   case MHD_HTTP_BAD_GATEWAY:
    234     ud.ec = TALER_JSON_get_error_code2 (data,
    235                                         data_size);
    236     ud.us = ANASTASIS_US_SERVER_ERROR;
    237     break;
    238   default:
    239     ud.ec = TALER_JSON_get_error_code2 (data,
    240                                         data_size);
    241     ud.us = ANASTASIS_US_SERVER_ERROR;
    242     break;
    243   }
    244   pso->cb (pso->cb_cls,
    245            &ud);
    246   pso->cb = NULL;
    247   ANASTASIS_policy_store_cancel (pso);
    248 }
    249 
    250 
    251 /**
    252  * Handle HTTP header received by curl.
    253  *
    254  * @param buffer one line of HTTP header data
    255  * @param size size of an item
    256  * @param nitems number of items passed
    257  * @param userdata our `struct ANASTASIS_StorePolicyOperation *`
    258  * @return `size * nitems`
    259  */
    260 static size_t
    261 handle_header (char *buffer,
    262                size_t size,
    263                size_t nitems,
    264                void *userdata)
    265 {
    266   struct ANASTASIS_PolicyStoreOperation *pso = userdata;
    267   size_t total = size * nitems;
    268   char *ndup;
    269   const char *hdr_type;
    270   char *hdr_val;
    271   char *sp;
    272 
    273   ndup = GNUNET_strndup (buffer,
    274                          total);
    275   hdr_type = strtok_r (ndup,
    276                        ":",
    277                        &sp);
    278   if (NULL == hdr_type)
    279   {
    280     GNUNET_free (ndup);
    281     return total;
    282   }
    283   hdr_val = strtok_r (NULL,
    284                       "",
    285                       &sp);
    286   if (NULL == hdr_val)
    287   {
    288     GNUNET_free (ndup);
    289     return total;
    290   }
    291   if (' ' == *hdr_val)
    292     hdr_val++;
    293   if (0 == strcasecmp (hdr_type,
    294                        "Taler"))
    295   {
    296     size_t len;
    297 
    298     /* found payment URI we care about! */
    299     GNUNET_free (pso->pay_uri); /* In case of duplicate header */
    300     pso->pay_uri = GNUNET_strdup (hdr_val);
    301     len = strlen (pso->pay_uri);
    302     while ( (len > 0) &&
    303             ( ('\n' == pso->pay_uri[len - 1]) ||
    304               ('\r' == pso->pay_uri[len - 1]) ) )
    305     {
    306       len--;
    307       pso->pay_uri[len] = '\0';
    308     }
    309   }
    310 
    311   if (0 == strcasecmp (hdr_type,
    312                        ANASTASIS_HTTP_HEADER_POLICY_VERSION))
    313   {
    314     size_t len;
    315 
    316     /* found policy version we care about! */
    317     GNUNET_free (pso->policy_version); /* In case of duplicate header */
    318     pso->policy_version = GNUNET_strdup (hdr_val);
    319     len = strlen (pso->policy_version);
    320     while ( (len > 0) &&
    321             ( ('\n' == pso->policy_version[len - 1]) ||
    322               ('\r' == pso->policy_version[len - 1]) ) )
    323     {
    324       len--;
    325       pso->policy_version[len] = '\0';
    326     }
    327   }
    328 
    329   if (0 == strcasecmp (hdr_type,
    330                        ANASTASIS_HTTP_HEADER_POLICY_EXPIRATION))
    331   {
    332     size_t len;
    333 
    334     /* found policy expiration we care about! */
    335     GNUNET_free (pso->policy_expiration); /* In case of duplicate header */
    336     pso->policy_expiration = GNUNET_strdup (hdr_val);
    337     len = strlen (pso->policy_expiration);
    338     while ( (len > 0) &&
    339             ( ('\n' == pso->policy_expiration[len - 1]) ||
    340               ('\r' == pso->policy_expiration[len - 1]) ) )
    341     {
    342       len--;
    343       pso->policy_expiration[len] = '\0';
    344     }
    345   }
    346 
    347   GNUNET_free (ndup);
    348   return total;
    349 }
    350 
    351 
    352 struct ANASTASIS_PolicyStoreOperation *
    353 ANASTASIS_policy_store (
    354   struct GNUNET_CURL_Context *ctx,
    355   const char *backend_url,
    356   const struct ANASTASIS_CRYPTO_AccountPrivateKeyP *anastasis_priv,
    357   const void *recovery_data,
    358   size_t recovery_data_size,
    359   const void *recovery_meta_data,
    360   size_t recovery_meta_data_size,
    361   uint32_t payment_years_requested,
    362   const struct ANASTASIS_PaymentSecretP *payment_secret,
    363   struct GNUNET_TIME_Relative payment_timeout,
    364   ANASTASIS_PolicyStoreCallback cb,
    365   void *cb_cls)
    366 {
    367   struct ANASTASIS_PolicyStoreOperation *pso;
    368   struct ANASTASIS_AccountSignatureP account_sig;
    369   unsigned long long tms;
    370   CURL *eh;
    371   struct curl_slist *job_headers;
    372   struct ANASTASIS_UploadSignaturePS usp = {
    373     .purpose.purpose = htonl (TALER_SIGNATURE_ANASTASIS_POLICY_UPLOAD),
    374     .purpose.size = htonl (sizeof (usp))
    375   };
    376 
    377   if (NULL == recovery_meta_data)
    378   {
    379     GNUNET_break (0);
    380     return NULL;
    381   }
    382   tms = (unsigned long long) (payment_timeout.rel_value_us
    383                               / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us);
    384   GNUNET_CRYPTO_hash (recovery_data,
    385                       recovery_data_size,
    386                       &usp.new_recovery_data_hash);
    387   GNUNET_CRYPTO_eddsa_sign (&anastasis_priv->priv,
    388                             &usp,
    389                             &account_sig.eddsa_sig);
    390   /* setup our HTTP headers */
    391   job_headers = NULL;
    392   {
    393     struct curl_slist *ext;
    394     char *val;
    395     char *hdr;
    396 
    397     /* Set Anastasis-Policy-Signature header */
    398     val = GNUNET_STRINGS_data_to_string_alloc (&account_sig,
    399                                                sizeof (account_sig));
    400     GNUNET_asprintf (&hdr,
    401                      "%s: %s",
    402                      ANASTASIS_HTTP_HEADER_POLICY_SIGNATURE,
    403                      val);
    404     GNUNET_free (val);
    405     ext = curl_slist_append (job_headers,
    406                              hdr);
    407     GNUNET_free (hdr);
    408     if (NULL == ext)
    409     {
    410       GNUNET_break (0);
    411       curl_slist_free_all (job_headers);
    412       return NULL;
    413     }
    414     job_headers = ext;
    415 
    416     /* set Etag header */
    417     val = GNUNET_STRINGS_data_to_string_alloc (&usp.new_recovery_data_hash,
    418                                                sizeof (struct GNUNET_HashCode));
    419     GNUNET_asprintf (&hdr,
    420                      "%s: \"%s\"",
    421                      MHD_HTTP_HEADER_IF_NONE_MATCH,
    422                      val);
    423     GNUNET_free (val);
    424     ext = curl_slist_append (job_headers,
    425                              hdr);
    426     GNUNET_free (hdr);
    427     if (NULL == ext)
    428     {
    429       GNUNET_break (0);
    430       curl_slist_free_all (job_headers);
    431       return NULL;
    432     }
    433     job_headers = ext;
    434 
    435     /* Setup meta-data header */
    436     {
    437       char *meta_val;
    438 
    439       meta_val = GNUNET_STRINGS_data_to_string_alloc (
    440         recovery_meta_data,
    441         recovery_meta_data_size);
    442       GNUNET_asprintf (&hdr,
    443                        "%s: %s",
    444                        ANASTASIS_HTTP_HEADER_POLICY_META_DATA,
    445                        meta_val);
    446       GNUNET_free (meta_val);
    447       ext = curl_slist_append (job_headers,
    448                                hdr);
    449       GNUNET_free (hdr);
    450       if (NULL == ext)
    451       {
    452         GNUNET_break (0);
    453         curl_slist_free_all (job_headers);
    454         return NULL;
    455       }
    456       job_headers = ext;
    457     }
    458 
    459     /* Setup Payment-Identifier header */
    460     if (NULL != payment_secret)
    461     {
    462       char *paid_order_id;
    463 
    464       paid_order_id = GNUNET_STRINGS_data_to_string_alloc (
    465         payment_secret,
    466         sizeof (*payment_secret));
    467       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    468                   "Beginning policy store operation with payment secret `%s'\n",
    469                   paid_order_id);
    470       GNUNET_asprintf (&hdr,
    471                        "%s: %s",
    472                        ANASTASIS_HTTP_HEADER_PAYMENT_IDENTIFIER,
    473                        paid_order_id);
    474       GNUNET_free (paid_order_id);
    475       ext = curl_slist_append (job_headers,
    476                                hdr);
    477       GNUNET_free (hdr);
    478       if (NULL == ext)
    479       {
    480         GNUNET_break (0);
    481         curl_slist_free_all (job_headers);
    482         return NULL;
    483       }
    484       job_headers = ext;
    485     }
    486     else
    487     {
    488       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    489                   "Beginning policy store operation without payment secret\n");
    490     }
    491   }
    492   /* Finished setting up headers */
    493   pso = GNUNET_new (struct ANASTASIS_PolicyStoreOperation);
    494   pso->postcopy = GNUNET_memdup (recovery_data,
    495                                  recovery_data_size);
    496   pso->new_upload_hash = usp.new_recovery_data_hash;
    497   {
    498     char *acc_pub_str;
    499     char *path;
    500     struct ANASTASIS_CRYPTO_AccountPublicKeyP pub;
    501     char timeout_ms[32];
    502     char pyrs[32];
    503 
    504     GNUNET_snprintf (timeout_ms,
    505                      sizeof (timeout_ms),
    506                      "%llu",
    507                      tms);
    508     GNUNET_snprintf (pyrs,
    509                      sizeof (pyrs),
    510                      "%u",
    511                      (unsigned int) payment_years_requested);
    512     GNUNET_CRYPTO_eddsa_key_get_public (&anastasis_priv->priv,
    513                                         &pub.pub);
    514     acc_pub_str
    515       = GNUNET_STRINGS_data_to_string_alloc (&pub,
    516                                              sizeof (pub));
    517     GNUNET_asprintf (&path,
    518                      "policy/%s",
    519                      acc_pub_str);
    520     GNUNET_free (acc_pub_str);
    521     pso->url = TALER_url_join (backend_url,
    522                                path,
    523                                "storage_duration",
    524                                (0 != payment_years_requested)
    525                                ? pyrs
    526                                : NULL,
    527                                "timeout_ms",
    528                                (0 != payment_timeout.rel_value_us)
    529                                ? timeout_ms
    530                                : NULL,
    531                                NULL);
    532     GNUNET_free (path);
    533   }
    534   pso->ctx = ctx;
    535   pso->cb = cb;
    536   pso->cb_cls = cb_cls;
    537   eh = ANASTASIS_curl_easy_get_ (pso->url);
    538   if (0 != tms)
    539     GNUNET_assert (CURLE_OK ==
    540                    curl_easy_setopt (eh,
    541                                      CURLOPT_TIMEOUT_MS,
    542                                      (long) (tms + 5000)));
    543   GNUNET_assert (CURLE_OK ==
    544                  curl_easy_setopt (eh,
    545                                    CURLOPT_POSTFIELDS,
    546                                    pso->postcopy));
    547   GNUNET_assert (CURLE_OK ==
    548                  curl_easy_setopt (eh,
    549                                    CURLOPT_POSTFIELDSIZE,
    550                                    (long) recovery_data_size));
    551   GNUNET_assert (CURLE_OK ==
    552                  curl_easy_setopt (eh,
    553                                    CURLOPT_HEADERFUNCTION,
    554                                    &handle_header));
    555   GNUNET_assert (CURLE_OK ==
    556                  curl_easy_setopt (eh,
    557                                    CURLOPT_HEADERDATA,
    558                                    pso));
    559   pso->job = GNUNET_CURL_job_add_raw (ctx,
    560                                       eh,
    561                                       job_headers,
    562                                       &handle_policy_store_finished,
    563                                       pso);
    564   curl_slist_free_all (job_headers);
    565   return pso;
    566 }