LCOV - code coverage report
Current view: top level - util - crypto_signatures.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 79.1 % 115 91
Test Date: 2026-09-11 18:55:36 Functions: 100.0 % 7 7

            Line data    Source code
       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          676 : build_message (const struct TALER_PosChallengeP *challenge,
      55              :                unsigned char *msg)
      56              : {
      57          676 :   memcpy (msg,
      58              :           TALER_POS_CHALLENGE_SALT,
      59              :           sizeof (TALER_POS_CHALLENGE_SALT) - 1);
      60          676 :   memcpy (&msg[sizeof (TALER_POS_CHALLENGE_SALT) - 1],
      61          676 :           challenge->challenge,
      62              :           TALER_POS_CHALLENGE_LENGTH);
      63          676 : }
      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          663 : hash_message (const struct TALER_PosChallengeP *challenge,
      74              :               struct GNUNET_ShortHashCode *hash)
      75              : {
      76              :   unsigned char msg[CHALLENGE_MSG_SIZE];
      77              : 
      78          663 :   build_message (challenge,
      79              :                  msg);
      80          663 :   gcry_md_hash_buffer (GCRY_MD_SHA256,
      81              :                        hash,
      82              :                        msg,
      83              :                        sizeof (msg));
      84          663 : }
      85              : 
      86              : 
      87              : enum GNUNET_GenericReturnValue
      88          314 : TALER_otp_device_key_create (
      89              :   enum TALER_MerchantConfirmationAlgorithm pos_alg,
      90              :   char **pos_key,
      91              :   char **pos_pub)
      92              : {
      93          314 :   *pos_key = NULL;
      94          314 :   *pos_pub = NULL;
      95          314 :   switch (pos_alg)
      96              :   {
      97          306 :   case TALER_MCA_ECDSA_CHALLENGE:
      98              :     {
      99              :       struct TALER_EcdsaP256PrivateKeyP priv;
     100              :       struct TALER_EcdsaP256PublicKeyP pub;
     101              : 
     102          306 :       if (GNUNET_OK !=
     103          306 :           TALER_ecdsa_p256_key_create (&priv,
     104              :                                        &pub))
     105            0 :         return GNUNET_SYSERR;
     106          306 :       *pos_key = GNUNET_STRINGS_data_to_string_alloc (&priv,
     107              :                                                       sizeof (priv));
     108          306 :       *pos_pub = GNUNET_STRINGS_data_to_string_alloc (&pub,
     109              :                                                       sizeof (pub));
     110          306 :       GNUNET_CRYPTO_zero_keys (&priv,
     111              :                                sizeof (priv));
     112          306 :       break;
     113              :     }
     114            5 :   case TALER_MCA_EDDSA_CHALLENGE:
     115              :     {
     116              :       struct GNUNET_CRYPTO_EddsaPrivateKey priv;
     117              :       struct GNUNET_CRYPTO_EddsaPublicKey pub;
     118              : 
     119            5 :       GNUNET_CRYPTO_eddsa_key_create (&priv);
     120            5 :       GNUNET_CRYPTO_eddsa_key_get_public (&priv,
     121              :                                           &pub);
     122            5 :       *pos_key = GNUNET_STRINGS_data_to_string_alloc (&priv,
     123              :                                                       sizeof (priv));
     124            5 :       *pos_pub = GNUNET_STRINGS_data_to_string_alloc (&pub,
     125              :                                                       sizeof (pub));
     126            5 :       GNUNET_CRYPTO_eddsa_key_clear (&priv);
     127            5 :       break;
     128              :     }
     129            3 :   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            3 :     GNUNET_break (0);
     134            3 :     return GNUNET_SYSERR;
     135              :   }
     136          311 :   if ( (NULL == *pos_key) ||
     137          311 :        (NULL == *pos_pub) )
     138              :   {
     139            0 :     GNUNET_break (0);
     140            0 :     GNUNET_free (*pos_key);
     141            0 :     GNUNET_free (*pos_pub);
     142            0 :     return GNUNET_SYSERR;
     143              :   }
     144          311 :   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          308 : 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          308 :   if ( (NULL == pos_key) ||
     165              :        (NULL == challenge) )
     166              :   {
     167            0 :     GNUNET_break (0);
     168            0 :     return NULL;
     169              :   }
     170          308 :   if (GNUNET_OK !=
     171          308 :       GNUNET_STRINGS_string_to_data (pos_key,
     172              :                                      strlen (pos_key),
     173              :                                      &priv,
     174              :                                      sizeof (priv)))
     175              :   {
     176            1 :     GNUNET_break (0);
     177            1 :     return NULL;
     178              :   }
     179          307 :   hash_message (challenge,
     180              :                 &hash);
     181          307 :   if (GNUNET_OK !=
     182          307 :       TALER_ecdsa_p256_sign (&priv,
     183              :                              &hash,
     184              :                              &sig))
     185              :   {
     186            0 :     GNUNET_break (0);
     187            0 :     GNUNET_CRYPTO_zero_keys (&priv,
     188              :                              sizeof (priv));
     189            0 :     return NULL;
     190              :   }
     191          307 :   GNUNET_CRYPTO_zero_keys (&priv,
     192              :                            sizeof (priv));
     193          307 :   ret = GNUNET_STRINGS_data_to_string_alloc (&sig,
     194              :                                              sizeof (sig));
     195          307 :   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            8 : 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            8 :   if ( (NULL == pos_key) ||
     216              :        (NULL == challenge) )
     217              :   {
     218            0 :     GNUNET_break (0);
     219            0 :     return NULL;
     220              :   }
     221            8 :   if (GNUNET_OK !=
     222            8 :       GNUNET_STRINGS_string_to_data (pos_key,
     223              :                                      strlen (pos_key),
     224              :                                      &priv,
     225              :                                      sizeof (priv)))
     226              :   {
     227            1 :     GNUNET_break (0);
     228            1 :     return NULL;
     229              :   }
     230            7 :   build_message (challenge,
     231              :                  msg);
     232            7 :   if (GNUNET_OK !=
     233            7 :       GNUNET_CRYPTO_eddsa_sign_raw (&priv,
     234              :                                     msg,
     235              :                                     sizeof (msg),
     236              :                                     &sig))
     237              :   {
     238            0 :     GNUNET_break (0);
     239            0 :     GNUNET_CRYPTO_eddsa_key_clear (&priv);
     240            0 :     return NULL;
     241              :   }
     242            7 :   GNUNET_CRYPTO_eddsa_key_clear (&priv);
     243            7 :   ret = GNUNET_STRINGS_data_to_string_alloc (&sig,
     244              :                                              sizeof (sig));
     245            7 :   return ret;
     246              : }
     247              : 
     248              : 
     249              : char *
     250          318 : TALER_build_pos_confirmation_sig (
     251              :   const char *pos_key,
     252              :   enum TALER_MerchantConfirmationAlgorithm pos_alg,
     253              :   const struct TALER_PosChallengeP *challenge)
     254              : {
     255          318 :   if ( (NULL == pos_key) ||
     256              :        (NULL == challenge) )
     257              :   {
     258            2 :     GNUNET_break (0);
     259            2 :     return NULL;
     260              :   }
     261          316 :   switch (pos_alg)
     262              :   {
     263          308 :   case TALER_MCA_ECDSA_CHALLENGE:
     264          308 :     return sign_ecdsa (pos_key,
     265              :                        challenge);
     266            8 :   case TALER_MCA_EDDSA_CHALLENGE:
     267            8 :     return sign_eddsa (pos_key,
     268              :                        challenge);
     269            0 :   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            0 :     GNUNET_break (0);
     274            0 :     return NULL;
     275              :   }
     276            0 :   GNUNET_break (0);
     277            0 :   return NULL;
     278              : }
     279              : 
     280              : 
     281              : enum GNUNET_GenericReturnValue
     282          369 : 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          369 :   if ( (NULL == pos_pub) ||
     289          369 :        (NULL == challenge) ||
     290              :        (NULL == pos_confirmation) )
     291              :   {
     292            0 :     GNUNET_break (0);
     293            0 :     return GNUNET_SYSERR;
     294              :   }
     295          369 :   switch (pos_alg)
     296              :   {
     297          357 :   case TALER_MCA_ECDSA_CHALLENGE:
     298              :     {
     299              :       struct TALER_EcdsaP256PublicKeyP pub;
     300              :       struct TALER_EcdsaP256SignatureP sig;
     301              :       struct GNUNET_ShortHashCode hash;
     302              : 
     303          357 :       if ( (GNUNET_OK !=
     304          357 :             GNUNET_STRINGS_string_to_data (pos_pub,
     305              :                                            strlen (pos_pub),
     306              :                                            &pub,
     307          356 :                                            sizeof (pub))) ||
     308              :            (GNUNET_OK !=
     309          356 :             GNUNET_STRINGS_string_to_data (pos_confirmation,
     310              :                                            strlen (pos_confirmation),
     311              :                                            &sig,
     312              :                                            sizeof (sig))) )
     313              :       {
     314            1 :         GNUNET_break_op (0);
     315            1 :         return GNUNET_SYSERR;
     316              :       }
     317          356 :       hash_message (challenge,
     318              :                     &hash);
     319          356 :       return TALER_ecdsa_p256_verify (&pub,
     320              :                                       &hash,
     321              :                                       &sig);
     322              :     }
     323           11 :   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           11 :       if ( (GNUNET_OK !=
     330           11 :             GNUNET_STRINGS_string_to_data (pos_pub,
     331              :                                            strlen (pos_pub),
     332              :                                            &pub,
     333            8 :                                            sizeof (pub))) ||
     334              :            (GNUNET_OK !=
     335            8 :             GNUNET_STRINGS_string_to_data (pos_confirmation,
     336              :                                            strlen (pos_confirmation),
     337              :                                            &sig,
     338              :                                            sizeof (sig))) )
     339              :       {
     340            5 :         GNUNET_break_op (0);
     341            5 :         return GNUNET_SYSERR;
     342              :       }
     343            6 :       build_message (challenge,
     344              :                      msg);
     345            6 :       return GNUNET_CRYPTO_eddsa_verify_raw (msg,
     346              :                                              sizeof (msg),
     347              :                                              &sig,
     348              :                                              &pub);
     349              :     }
     350            1 :   case TALER_MCA_NONE:
     351              :   case TALER_MCA_WITHOUT_PRICE:
     352              :   case TALER_MCA_WITH_PRICE:
     353            1 :     GNUNET_break (0);
     354            1 :     return GNUNET_SYSERR;
     355              :   }
     356            0 :   GNUNET_break (0);
     357            0 :   return GNUNET_SYSERR;
     358              : }
     359              : 
     360              : 
     361              : /* end of crypto_signatures.c */
        

Generated by: LCOV version 2.0-1