exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

crypto_signatures.c (10778B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2026 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU General Public License as published by the Free Software
      7   Foundation; either version 3, or (at your option) any later version.
      8 
      9   TALER 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 General Public License for more details.
     12 
     13   You should have received a copy of the GNU General Public License along with
     14   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 /**
     17  * @file util/crypto_signatures.c
     18  * @brief POS confirmations that sign a challenge (DD 97)
     19  * @author Bohdan Potuzhnyi
     20  * @author Volodymyr Potuzhnyi
     21  *
     22  * The counterpart to the time-based confirmations in
     23  * crypto_confirmation.c. An offline verifier picks an unpredictable
     24  * challenge, the merchant backend signs it once the order was paid,
     25  * and the verifier checks that signature against a public key it was
     26  * configured with. Only the public key ever leaves the backend.
     27  *
     28  * This file owns the message that gets signed, the encoding of keys
     29  * and signatures, and which curve each algorithm uses. The signature
     30  * operations themselves come from TALER_ecdsa_p256_*() and from
     31  * GNUnet's EdDSA.
     32  */
     33 #include "platform.h"
     34 #include "taler/taler_util.h"
     35 #include <gcrypt.h>
     36 
     37 
     38 /**
     39  * Size of the message that gets signed.
     40  */
     41 #define CHALLENGE_MSG_SIZE \
     42   (sizeof (TALER_POS_CHALLENGE_SALT) - 1 + TALER_POS_CHALLENGE_LENGTH)
     43 
     44 
     45 /**
     46  * Assemble the message a confirmation signs: the domain separation
     47  * salt directly followed by the raw challenge bytes. An offline
     48  * verifier reproduces exactly this.
     49  *
     50  * @param challenge challenge to bind the signature to
     51  * @param[out] msg where to write #CHALLENGE_MSG_SIZE bytes
     52  */
     53 static void
     54 build_message (const struct TALER_PosChallengeP *challenge,
     55                unsigned char *msg)
     56 {
     57   memcpy (msg,
     58           TALER_POS_CHALLENGE_SALT,
     59           sizeof (TALER_POS_CHALLENGE_SALT) - 1);
     60   memcpy (&msg[sizeof (TALER_POS_CHALLENGE_SALT) - 1],
     61           challenge->challenge,
     62           TALER_POS_CHALLENGE_LENGTH);
     63 }
     64 
     65 
     66 /**
     67  * Hash the challenge message, as the ECDSA algorithm signs a digest.
     68  *
     69  * @param challenge challenge to bind the signature to
     70  * @param[out] hash set to the SHA-256 of the challenge message
     71  */
     72 static void
     73 hash_message (const struct TALER_PosChallengeP *challenge,
     74               struct GNUNET_ShortHashCode *hash)
     75 {
     76   unsigned char msg[CHALLENGE_MSG_SIZE];
     77 
     78   build_message (challenge,
     79                  msg);
     80   gcry_md_hash_buffer (GCRY_MD_SHA256,
     81                        hash,
     82                        msg,
     83                        sizeof (msg));
     84 }
     85 
     86 
     87 enum GNUNET_GenericReturnValue
     88 TALER_otp_device_key_create (
     89   enum TALER_MerchantConfirmationAlgorithm pos_alg,
     90   char **pos_key,
     91   char **pos_pub)
     92 {
     93   *pos_key = NULL;
     94   *pos_pub = NULL;
     95   switch (pos_alg)
     96   {
     97   case TALER_MCA_ECDSA_CHALLENGE:
     98     {
     99       struct TALER_EcdsaP256PrivateKeyP priv;
    100       struct TALER_EcdsaP256PublicKeyP pub;
    101 
    102       if (GNUNET_OK !=
    103           TALER_ecdsa_p256_key_create (&priv,
    104                                        &pub))
    105         return GNUNET_SYSERR;
    106       *pos_key = GNUNET_STRINGS_data_to_string_alloc (&priv,
    107                                                       sizeof (priv));
    108       *pos_pub = GNUNET_STRINGS_data_to_string_alloc (&pub,
    109                                                       sizeof (pub));
    110       GNUNET_CRYPTO_zero_keys (&priv,
    111                                sizeof (priv));
    112       break;
    113     }
    114   case TALER_MCA_EDDSA_CHALLENGE:
    115     {
    116       struct GNUNET_CRYPTO_EddsaPrivateKey priv;
    117       struct GNUNET_CRYPTO_EddsaPublicKey pub;
    118 
    119       GNUNET_CRYPTO_eddsa_key_create (&priv);
    120       GNUNET_CRYPTO_eddsa_key_get_public (&priv,
    121                                           &pub);
    122       *pos_key = GNUNET_STRINGS_data_to_string_alloc (&priv,
    123                                                       sizeof (priv));
    124       *pos_pub = GNUNET_STRINGS_data_to_string_alloc (&pub,
    125                                                       sizeof (pub));
    126       GNUNET_CRYPTO_eddsa_key_clear (&priv);
    127       break;
    128     }
    129   case TALER_MCA_NONE:
    130   case TALER_MCA_WITHOUT_PRICE:
    131   case TALER_MCA_WITH_PRICE:
    132     /* TOTP keys are supplied by the merchant, not generated here */
    133     GNUNET_break (0);
    134     return GNUNET_SYSERR;
    135   }
    136   if ( (NULL == *pos_key) ||
    137        (NULL == *pos_pub) )
    138   {
    139     GNUNET_break (0);
    140     GNUNET_free (*pos_key);
    141     GNUNET_free (*pos_pub);
    142     return GNUNET_SYSERR;
    143   }
    144   return GNUNET_OK;
    145 }
    146 
    147 
    148 /**
    149  * Sign @a challenge with the NIST P-256 private key @a pos_key.
    150  *
    151  * @param pos_key Crockford base32-encoded P-256 private scalar
    152  * @param challenge challenge to bind the signature to
    153  * @return Crockford base32-encoded r|s signature, or NULL on error
    154  */
    155 static char *
    156 sign_ecdsa (const char *pos_key,
    157             const struct TALER_PosChallengeP *challenge)
    158 {
    159   struct TALER_EcdsaP256PrivateKeyP priv;
    160   struct TALER_EcdsaP256SignatureP sig;
    161   struct GNUNET_ShortHashCode hash;
    162   char *ret;
    163 
    164   if ( (NULL == pos_key) ||
    165        (NULL == challenge) )
    166   {
    167     GNUNET_break (0);
    168     return NULL;
    169   }
    170   if (GNUNET_OK !=
    171       GNUNET_STRINGS_string_to_data (pos_key,
    172                                      strlen (pos_key),
    173                                      &priv,
    174                                      sizeof (priv)))
    175   {
    176     GNUNET_break (0);
    177     return NULL;
    178   }
    179   hash_message (challenge,
    180                 &hash);
    181   if (GNUNET_OK !=
    182       TALER_ecdsa_p256_sign (&priv,
    183                              &hash,
    184                              &sig))
    185   {
    186     GNUNET_break (0);
    187     GNUNET_CRYPTO_zero_keys (&priv,
    188                              sizeof (priv));
    189     return NULL;
    190   }
    191   GNUNET_CRYPTO_zero_keys (&priv,
    192                            sizeof (priv));
    193   ret = GNUNET_STRINGS_data_to_string_alloc (&sig,
    194                                              sizeof (sig));
    195   return ret;
    196 }
    197 
    198 
    199 /**
    200  * Sign @a challenge with the Ed25519 private key @a pos_key.
    201  *
    202  * @param pos_key Crockford base32-encoded Ed25519 private key
    203  * @param challenge challenge to bind the signature to
    204  * @return Crockford base32-encoded signature, or NULL on error
    205  */
    206 static char *
    207 sign_eddsa (const char *pos_key,
    208             const struct TALER_PosChallengeP *challenge)
    209 {
    210   struct GNUNET_CRYPTO_EddsaPrivateKey priv;
    211   struct GNUNET_CRYPTO_EddsaSignature sig;
    212   unsigned char msg[CHALLENGE_MSG_SIZE];
    213   char *ret;
    214 
    215   if ( (NULL == pos_key) ||
    216        (NULL == challenge) )
    217   {
    218     GNUNET_break (0);
    219     return NULL;
    220   }
    221   if (GNUNET_OK !=
    222       GNUNET_STRINGS_string_to_data (pos_key,
    223                                      strlen (pos_key),
    224                                      &priv,
    225                                      sizeof (priv)))
    226   {
    227     GNUNET_break (0);
    228     return NULL;
    229   }
    230   build_message (challenge,
    231                  msg);
    232   if (GNUNET_OK !=
    233       GNUNET_CRYPTO_eddsa_sign_raw (&priv,
    234                                     msg,
    235                                     sizeof (msg),
    236                                     &sig))
    237   {
    238     GNUNET_break (0);
    239     GNUNET_CRYPTO_eddsa_key_clear (&priv);
    240     return NULL;
    241   }
    242   GNUNET_CRYPTO_eddsa_key_clear (&priv);
    243   ret = GNUNET_STRINGS_data_to_string_alloc (&sig,
    244                                              sizeof (sig));
    245   return ret;
    246 }
    247 
    248 
    249 char *
    250 TALER_build_pos_confirmation_sig (
    251   const char *pos_key,
    252   enum TALER_MerchantConfirmationAlgorithm pos_alg,
    253   const struct TALER_PosChallengeP *challenge)
    254 {
    255   if ( (NULL == pos_key) ||
    256        (NULL == challenge) )
    257   {
    258     GNUNET_break (0);
    259     return NULL;
    260   }
    261   switch (pos_alg)
    262   {
    263   case TALER_MCA_ECDSA_CHALLENGE:
    264     return sign_ecdsa (pos_key,
    265                        challenge);
    266   case TALER_MCA_EDDSA_CHALLENGE:
    267     return sign_eddsa (pos_key,
    268                        challenge);
    269   case TALER_MCA_NONE:
    270   case TALER_MCA_WITHOUT_PRICE:
    271   case TALER_MCA_WITH_PRICE:
    272     /* time-based algorithms are TALER_build_pos_confirmation()'s job */
    273     GNUNET_break (0);
    274     return NULL;
    275   }
    276   GNUNET_break (0);
    277   return NULL;
    278 }
    279 
    280 
    281 enum GNUNET_GenericReturnValue
    282 TALER_check_pos_confirmation_sig (
    283   const char *pos_pub,
    284   enum TALER_MerchantConfirmationAlgorithm pos_alg,
    285   const struct TALER_PosChallengeP *challenge,
    286   const char *pos_confirmation)
    287 {
    288   if ( (NULL == pos_pub) ||
    289        (NULL == challenge) ||
    290        (NULL == pos_confirmation) )
    291   {
    292     GNUNET_break (0);
    293     return GNUNET_SYSERR;
    294   }
    295   switch (pos_alg)
    296   {
    297   case TALER_MCA_ECDSA_CHALLENGE:
    298     {
    299       struct TALER_EcdsaP256PublicKeyP pub;
    300       struct TALER_EcdsaP256SignatureP sig;
    301       struct GNUNET_ShortHashCode hash;
    302 
    303       if ( (GNUNET_OK !=
    304             GNUNET_STRINGS_string_to_data (pos_pub,
    305                                            strlen (pos_pub),
    306                                            &pub,
    307                                            sizeof (pub))) ||
    308            (GNUNET_OK !=
    309             GNUNET_STRINGS_string_to_data (pos_confirmation,
    310                                            strlen (pos_confirmation),
    311                                            &sig,
    312                                            sizeof (sig))) )
    313       {
    314         GNUNET_break_op (0);
    315         return GNUNET_SYSERR;
    316       }
    317       hash_message (challenge,
    318                     &hash);
    319       return TALER_ecdsa_p256_verify (&pub,
    320                                       &hash,
    321                                       &sig);
    322     }
    323   case TALER_MCA_EDDSA_CHALLENGE:
    324     {
    325       struct GNUNET_CRYPTO_EddsaPublicKey pub;
    326       struct GNUNET_CRYPTO_EddsaSignature sig;
    327       unsigned char msg[CHALLENGE_MSG_SIZE];
    328 
    329       if ( (GNUNET_OK !=
    330             GNUNET_STRINGS_string_to_data (pos_pub,
    331                                            strlen (pos_pub),
    332                                            &pub,
    333                                            sizeof (pub))) ||
    334            (GNUNET_OK !=
    335             GNUNET_STRINGS_string_to_data (pos_confirmation,
    336                                            strlen (pos_confirmation),
    337                                            &sig,
    338                                            sizeof (sig))) )
    339       {
    340         GNUNET_break_op (0);
    341         return GNUNET_SYSERR;
    342       }
    343       build_message (challenge,
    344                      msg);
    345       return GNUNET_CRYPTO_eddsa_verify_raw (msg,
    346                                              sizeof (msg),
    347                                              &sig,
    348                                              &pub);
    349     }
    350   case TALER_MCA_NONE:
    351   case TALER_MCA_WITHOUT_PRICE:
    352   case TALER_MCA_WITH_PRICE:
    353     GNUNET_break (0);
    354     return GNUNET_SYSERR;
    355   }
    356   GNUNET_break (0);
    357   return GNUNET_SYSERR;
    358 }
    359 
    360 
    361 /* end of crypto_signatures.c */