anastasis

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

anastasis-db_store_recovery_document.c (10876B)


      1 /*
      2   This file is part of Anastasis
      3   Copyright (C) 2020-2022 Anastasis SARL
      4 
      5   Anastasis is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU Affero General Public License as published by the Free Software
      7   Foundation; either version 3, or (at your option) any later version.
      8 
      9   Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY
     10   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11   A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details.
     12 
     13   You should have received a copy of the GNU Affero General Public License along with
     14   Anastasis; see the file COPYING.GPL.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 /**
     17  * @file stasis/anastasis-db_store_recovery_document.c
     18  * @brief Anastasis database: store recovery document
     19  * @author Christian Grothoff
     20  */
     21 #include "platform.h"
     22 #include "anastasis-db_pg.h"
     23 #include "anastasis/anastasis-database/store_recovery_document.h"
     24 #include "anastasis/anastasis-database/transaction.h"
     25 #include "anastasis/anastasis-database/preflight.h"
     26 #include <taler/taler_pq_lib.h>
     27 
     28 
     29 /**
     30  * Store encrypted recovery document.
     31  *
     32  * @param account_pub public key of the user's account
     33  * @param account_sig signature affirming storage request
     34  * @param recovery_data_hash hash of @a data
     35  * @param recovery_data contains encrypted_recovery_document
     36  * @param recovery_data_size size of data blob
     37  * @param payment_secret identifier for the payment, used to later charge on uploads
     38  * @param[out] version set to the version assigned to the document by the database
     39  * @return transaction status, 0 if upload could not be finished because @a payment_secret
     40  *         did not have enough upload left; HARD error if @a payment_secret is unknown, ...
     41  */
     42 enum ANASTASIS_DB_StoreStatus
     43 ANASTASIS_DB_store_recovery_document (
     44   const struct ANASTASIS_CRYPTO_AccountPublicKeyP *account_pub,
     45   const struct ANASTASIS_AccountSignatureP *account_sig,
     46   const struct GNUNET_HashCode *recovery_data_hash,
     47   const void *recovery_data,
     48   size_t recovery_data_size,
     49   const void *recovery_meta_data,
     50   size_t recovery_meta_data_size,
     51   const struct ANASTASIS_PaymentSecretP *payment_secret,
     52   uint32_t *version)
     53 {
     54   enum GNUNET_DB_QueryStatus qs;
     55 
     56   GNUNET_break (GNUNET_OK ==
     57                 ANASTASIS_DB_preflight ());
     58 
     59   PREPARE ("user_select5",
     60            "SELECT"
     61            " expiration_date "
     62            "FROM anastasis_user"
     63            " WHERE user_id=$1"
     64            " FOR UPDATE;");
     65   PREPARE ("latest_recovery_version_select2",
     66            "SELECT"
     67            " version"
     68            ",recovery_data_hash"
     69            ",expiration_date"
     70            " FROM anastasis_recoverydocument"
     71            " JOIN anastasis_user USING (user_id)"
     72            " WHERE user_id=$1"
     73            " ORDER BY version DESC"
     74            " LIMIT 1;");
     75   PREPARE ("postcounter_select",
     76            "SELECT"
     77            " post_counter"
     78            " FROM anastasis_recdoc_payment"
     79            " WHERE user_id=$1"
     80            "  AND payment_identifier=$2;");
     81   PREPARE ("postcounter_update",
     82            "UPDATE"
     83            " anastasis_recdoc_payment"
     84            " SET"
     85            " post_counter=$1"
     86            " WHERE user_id =$2"
     87            "   AND payment_identifier=$3;");
     88   PREPARE ("recovery_document_insert",
     89            "INSERT INTO anastasis_recoverydocument "
     90            "(user_id"
     91            ",version"
     92            ",account_sig"
     93            ",recovery_data_hash"
     94            ",recovery_data"
     95            ",recovery_meta_data"
     96            ",creation_date"
     97            ") VALUES "
     98            "($1, $2, $3, $4, $5, $6, $7);");
     99   for (unsigned int retry = 0; retry<MAX_RETRIES; retry++)
    100   {
    101     if (GNUNET_OK !=
    102         ANASTASIS_DB_start (
    103           "store_recovery_document"))
    104     {
    105       GNUNET_break (0);
    106       return ANASTASIS_DB_STORE_STATUS_HARD_ERROR;
    107     }
    108     /* get the current version and hash of the latest recovery document
    109        for this account */
    110     {
    111       struct GNUNET_HashCode dh;
    112       struct GNUNET_PQ_QueryParam params[] = {
    113         GNUNET_PQ_query_param_auto_from_type (account_pub),
    114         GNUNET_PQ_query_param_end
    115       };
    116       struct GNUNET_PQ_ResultSpec rs[] = {
    117         GNUNET_PQ_result_spec_uint32 ("version",
    118                                       version),
    119         GNUNET_PQ_result_spec_auto_from_type ("recovery_data_hash",
    120                                               &dh),
    121         GNUNET_PQ_result_spec_end
    122       };
    123 
    124       qs = GNUNET_PQ_eval_prepared_singleton_select (
    125         pg->conn,
    126         "latest_recovery_version_select2",
    127         params,
    128         rs);
    129       switch (qs)
    130       {
    131       case GNUNET_DB_STATUS_HARD_ERROR:
    132         GNUNET_break (0);
    133         ANASTASIS_DB_rollback ();
    134         return ANASTASIS_DB_STORE_STATUS_HARD_ERROR;
    135       case GNUNET_DB_STATUS_SOFT_ERROR:
    136         goto retry;
    137       case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    138         *version = 1;
    139         break;
    140       case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    141         /* had an existing recovery_data, is it identical? */
    142         if (0 == GNUNET_memcmp (&dh,
    143                                 recovery_data_hash))
    144         {
    145           /* Yes. Previous identical recovery data exists */
    146           ANASTASIS_DB_rollback ();
    147           return ANASTASIS_DB_STORE_STATUS_NO_RESULTS;
    148         }
    149         (*version)++;
    150         break;
    151       default:
    152         GNUNET_break (0);
    153         ANASTASIS_DB_rollback ();
    154         return ANASTASIS_DB_STORE_STATUS_HARD_ERROR;
    155       }
    156     }
    157 
    158     /* First, check if account exists */
    159     {
    160       struct GNUNET_PQ_QueryParam params[] = {
    161         GNUNET_PQ_query_param_auto_from_type (account_pub),
    162         GNUNET_PQ_query_param_end
    163       };
    164       struct GNUNET_PQ_ResultSpec rs[] = {
    165         GNUNET_PQ_result_spec_end
    166       };
    167 
    168       qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
    169                                                      "user_select5",
    170                                                      params,
    171                                                      rs);
    172     }
    173     switch (qs)
    174     {
    175     case GNUNET_DB_STATUS_HARD_ERROR:
    176       ANASTASIS_DB_rollback ();
    177       return ANASTASIS_DB_STORE_STATUS_HARD_ERROR;
    178     case GNUNET_DB_STATUS_SOFT_ERROR:
    179       goto retry;
    180     case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    181       ANASTASIS_DB_rollback ();
    182       return ANASTASIS_DB_STORE_STATUS_PAYMENT_REQUIRED;
    183     case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    184       /* handle interesting case below */
    185       break;
    186     }
    187 
    188     {
    189       uint32_t postcounter;
    190 
    191       /* lookup if the user has enough uploads left and decrement */
    192       {
    193         struct GNUNET_PQ_QueryParam params[] = {
    194           GNUNET_PQ_query_param_auto_from_type (account_pub),
    195           GNUNET_PQ_query_param_auto_from_type (payment_secret),
    196           GNUNET_PQ_query_param_end
    197         };
    198         struct GNUNET_PQ_ResultSpec rs[] = {
    199           GNUNET_PQ_result_spec_uint32 ("post_counter",
    200                                         &postcounter),
    201           GNUNET_PQ_result_spec_end
    202         };
    203 
    204         qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
    205                                                        "postcounter_select",
    206                                                        params,
    207                                                        rs);
    208         switch (qs)
    209         {
    210         case GNUNET_DB_STATUS_HARD_ERROR:
    211           ANASTASIS_DB_rollback ();
    212           return ANASTASIS_DB_STORE_STATUS_HARD_ERROR;
    213         case GNUNET_DB_STATUS_SOFT_ERROR:
    214           goto retry;
    215         case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    216           ANASTASIS_DB_rollback ();
    217           return ANASTASIS_DB_STORE_STATUS_HARD_ERROR;
    218         case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    219           break;
    220         }
    221       }
    222 
    223       if (0 == postcounter)
    224       {
    225         ANASTASIS_DB_rollback ();
    226         return ANASTASIS_DB_STORE_STATUS_STORE_LIMIT_EXCEEDED;
    227       }
    228       /* Decrement the postcounter by one */
    229       postcounter--;
    230 
    231       /* Update the postcounter in the Database */
    232       {
    233         struct GNUNET_PQ_QueryParam params[] = {
    234           GNUNET_PQ_query_param_uint32 (&postcounter),
    235           GNUNET_PQ_query_param_auto_from_type (account_pub),
    236           GNUNET_PQ_query_param_auto_from_type (payment_secret),
    237           GNUNET_PQ_query_param_end
    238         };
    239 
    240         qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
    241                                                  "postcounter_update",
    242                                                  params);
    243         switch (qs)
    244         {
    245         case GNUNET_DB_STATUS_HARD_ERROR:
    246           GNUNET_break (0);
    247           ANASTASIS_DB_rollback ();
    248           return ANASTASIS_DB_STORE_STATUS_HARD_ERROR;
    249         case GNUNET_DB_STATUS_SOFT_ERROR:
    250           goto retry;
    251         case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    252           GNUNET_break (0);
    253           ANASTASIS_DB_rollback ();
    254           return ANASTASIS_DB_STORE_STATUS_HARD_ERROR;
    255         case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    256           break;
    257         default:
    258           GNUNET_break (0);
    259           ANASTASIS_DB_rollback ();
    260           return ANASTASIS_DB_STORE_STATUS_HARD_ERROR;
    261         }
    262       }
    263     }
    264 
    265     /* finally, actually insert the recovery document */
    266     {
    267       struct GNUNET_TIME_Timestamp now
    268         = GNUNET_TIME_timestamp_get ();
    269       struct GNUNET_PQ_QueryParam params[] = {
    270         GNUNET_PQ_query_param_auto_from_type (account_pub),
    271         GNUNET_PQ_query_param_uint32 (version),
    272         GNUNET_PQ_query_param_auto_from_type (account_sig),
    273         GNUNET_PQ_query_param_auto_from_type (recovery_data_hash),
    274         GNUNET_PQ_query_param_fixed_size (recovery_data,
    275                                           recovery_data_size),
    276         GNUNET_PQ_query_param_fixed_size (recovery_meta_data,
    277                                           recovery_meta_data_size),
    278         GNUNET_PQ_query_param_timestamp (&now),
    279         GNUNET_PQ_query_param_end
    280       };
    281 
    282       qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
    283                                                "recovery_document_insert",
    284                                                params);
    285       switch (qs)
    286       {
    287       case GNUNET_DB_STATUS_HARD_ERROR:
    288         ANASTASIS_DB_rollback ();
    289         return ANASTASIS_DB_STORE_STATUS_HARD_ERROR;
    290       case GNUNET_DB_STATUS_SOFT_ERROR:
    291         goto retry;
    292       case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    293         GNUNET_break (0);
    294         ANASTASIS_DB_rollback ();
    295         return ANASTASIS_DB_STORE_STATUS_HARD_ERROR;
    296       case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    297         qs = ANASTASIS_DB_commit ();
    298         if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
    299           goto retry;
    300         if (qs < 0)
    301           return ANASTASIS_DB_STORE_STATUS_HARD_ERROR;
    302         return ANASTASIS_DB_STORE_STATUS_SUCCESS;
    303       }
    304     }
    305 retry:
    306     ANASTASIS_DB_rollback ();
    307   }
    308   return ANASTASIS_DB_STORE_STATUS_SOFT_ERROR;
    309 }
    310 
    311 
    312 /* end of anastasis-db_store_recovery_document.c */