LCOV - code coverage report
Current view: top level - util - test_crypto_confirmation.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 97.2 % 320 311
Test Date: 2026-09-11 18:55:36 Functions: 100.0 % 16 16

            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/test_crypto_confirmation.c
      18              :  * @brief tests for POS confirmation computation
      19              :  * @author Bohdan Potuzhnyi
      20              :  * @author Volodymyr Potuzhnyi
      21              :  */
      22              : #include "platform.h"
      23              : #include "taler/taler_util.h"
      24              : 
      25              : 
      26              : /**
      27              :  * Number of checks that failed.
      28              :  */
      29              : static unsigned int fails;
      30              : 
      31              : 
      32              : /**
      33              :  * Record the outcome of a single check.
      34              :  *
      35              :  * @param ok true if the check passed
      36              :  * @param label human-readable description of the check
      37              :  */
      38              : static void
      39           57 : check (bool ok,
      40              :        const char *label)
      41              : {
      42           57 :   if (! ok)
      43              :   {
      44            0 :     fprintf (stderr,
      45              :              "FAIL %s\n",
      46              :              label);
      47            0 :     fails++;
      48            0 :     return;
      49              :   }
      50           57 :   fprintf (stderr,
      51              :            "ok   %s\n",
      52              :            label);
      53              : }
      54              : 
      55              : 
      56              : /**
      57              :  * Check that @a enc decodes to exactly @a len bytes.
      58              :  *
      59              :  * @param enc Crockford base32-encoded value
      60              :  * @param len expected number of bytes
      61              :  * @return true if the length matches
      62              :  */
      63              : static bool
      64            4 : decodes_to (const char *enc,
      65              :             size_t len)
      66              : {
      67              :   unsigned char buf[128];
      68              : 
      69            4 :   GNUNET_assert (len <= sizeof (buf));
      70            4 :   return (GNUNET_OK ==
      71            4 :           GNUNET_STRINGS_string_to_data (enc,
      72              :                                          strlen (enc),
      73              :                                          buf,
      74              :                                          len));
      75              : }
      76              : 
      77              : 
      78              : /**
      79              :  * Run the full challenge-signature test suite for one algorithm.
      80              :  *
      81              :  * @param alg algorithm to exercise
      82              :  * @param name human-readable name of @a alg
      83              :  * @param pub_len expected public key length in bytes
      84              :  */
      85              : static void
      86            2 : test_challenge_alg (enum TALER_MerchantConfirmationAlgorithm alg,
      87              :                     const char *name,
      88              :                     size_t pub_len)
      89              : {
      90            2 :   char *priv = NULL;
      91            2 :   char *pub = NULL;
      92            2 :   char *priv2 = NULL;
      93            2 :   char *pub2 = NULL;
      94              :   char *conf;
      95              :   char *conf2;
      96              :   struct TALER_PosChallengeP ch;
      97              :   struct TALER_PosChallengeP ch2;
      98              :   char label[256];
      99              : 
     100              : #define LABEL(what) \
     101              :   GNUNET_snprintf (label, sizeof (label), "%s: %s", name, what)
     102              : 
     103            2 :   fprintf (stderr,
     104              :            "-- %s --\n",
     105              :            name);
     106            2 :   LABEL ("keygen");
     107            2 :   check (GNUNET_OK ==
     108            2 :          TALER_otp_device_key_create (alg,
     109              :                                       &priv,
     110              :                                       &pub),
     111              :          label);
     112            2 :   if ( (NULL == priv) ||
     113            2 :        (NULL == pub) )
     114            0 :     return;
     115            2 :   LABEL ("public key has the documented length");
     116            2 :   check (decodes_to (pub,
     117              :                      pub_len),
     118              :          label);
     119              : 
     120            2 :   GNUNET_CRYPTO_random_block (&ch,
     121              :                               sizeof (ch));
     122            2 :   conf = TALER_build_pos_confirmation_sig (priv,
     123              :                                            alg,
     124              :                                            &ch);
     125            2 :   LABEL ("sign");
     126            2 :   check (NULL != conf,
     127              :          label);
     128            2 :   if (NULL == conf)
     129            0 :     return;
     130            2 :   LABEL ("signature is 64 bytes");
     131            2 :   check (decodes_to (conf,
     132              :                      64),
     133              :          label);
     134            2 :   LABEL ("verify accepts a fresh confirmation");
     135            2 :   check (GNUNET_OK ==
     136            2 :          TALER_check_pos_confirmation_sig (pub,
     137              :                                            alg,
     138              :                                            &ch,
     139              :                                            conf),
     140              :          label);
     141              : 
     142              :   /* DD 97: modifying the challenge must invalidate the confirmation */
     143            2 :   ch2 = ch;
     144            2 :   ch2.challenge[0] ^= 0x01;
     145            2 :   LABEL ("mutated challenge is rejected");
     146            2 :   check (GNUNET_OK !=
     147            2 :          TALER_check_pos_confirmation_sig (pub,
     148              :                                            alg,
     149              :                                            &ch2,
     150              :                                            conf),
     151              :          label);
     152              : 
     153              :   /* DD 97: another device's key must not accept the confirmation */
     154            2 :   check (GNUNET_OK ==
     155            2 :          TALER_otp_device_key_create (alg,
     156              :                                       &priv2,
     157              :                                       &pub2),
     158              :          "second keygen");
     159            2 :   LABEL ("confirmation is rejected under a different key");
     160            2 :   check (GNUNET_OK !=
     161            2 :          TALER_check_pos_confirmation_sig (pub2,
     162              :                                            alg,
     163              :                                            &ch,
     164              :                                            conf),
     165              :          label);
     166              : 
     167            2 :   conf2 = TALER_build_pos_confirmation_sig (priv2,
     168              :                                             alg,
     169              :                                             &ch);
     170            2 :   LABEL ("a different key yields a different confirmation");
     171            4 :   check ( (NULL != conf2) &&
     172            2 :           (0 != strcmp (conf,
     173              :                         conf2)),
     174            2 :           label);
     175              : 
     176              :   {
     177            2 :     char *bad = GNUNET_strdup (conf);
     178              : 
     179            2 :     bad[0] = ('A' == bad[0]) ? 'B' : 'A';
     180            2 :     LABEL ("mauled confirmation is rejected");
     181            2 :     check (GNUNET_OK !=
     182            2 :            TALER_check_pos_confirmation_sig (pub,
     183              :                                              alg,
     184              :                                              &ch,
     185              :                                              bad),
     186              :            label);
     187            2 :     GNUNET_free (bad);
     188              :   }
     189              : 
     190              :   /* DD 97: must not return a confirmation we cannot compute */
     191            2 :   LABEL ("missing challenge fails closed");
     192            2 :   check (NULL ==
     193            2 :          TALER_build_pos_confirmation_sig (priv,
     194              :                                            alg,
     195              :                                            NULL),
     196              :          label);
     197              : #undef LABEL
     198            2 :   GNUNET_free (conf);
     199            2 :   GNUNET_free (conf2);
     200            2 :   GNUNET_free (priv);
     201            2 :   GNUNET_free (pub);
     202            2 :   GNUNET_free (priv2);
     203            2 :   GNUNET_free (pub2);
     204              : }
     205              : 
     206              : 
     207              : /**
     208              :  * Known-answer vectors. These pin the on-the-wire format an offline
     209              :  * verifier depends on: a round-trip test would happily follow us if
     210              :  * the salt or the field order ever changed.
     211              :  */
     212              : #define KAT_EDDSA_PRIV "041061050R3GG28A1C60T3GF208H44RM2MB1E60S38DHR78Y3WG0"
     213              : #define KAT_EDDSA_PUB  "F6TNCBMFWSAFJG3RP49EHACBMY81Z19TWTAVXNZ0WE8GQB84JSJ0"
     214              : #define KAT_EDDSA_SIG \
     215              :   "VKTQA94GZCQ6R5AWV51X51X4P6Q4EE8F0MXWWZXMGRKJKR2KV2BRBXJG8YV4E0VY56GG" \
     216              :   "CBCG5XAMEV1C1AD9FNXHDBXK4JXNZ008E08"
     217              : #define KAT_ECDSA_PUB  "0CZV95QR7R1GKGFFDAW2N4ZMAMXTWYF4AHX7XR87F3XW5KZV9C922"
     218              : #define KAT_ECDSA_SIG \
     219              :   "03Q7RNNBBRPKG4YNH5JH92H557JHHGV92M7MMFXD9MH0MQFJG46QADH7GXNFNGTPD6MD" \
     220              :   "80Y4CC3ESKTRM7AF9PRBXVN4FMYEY7JWMM8"
     221              : 
     222              : 
     223              : /**
     224              :  * Check the confirmations we produce against fixed vectors, so that a
     225              :  * change to the signed message is caught instead of silently breaking
     226              :  * every already-deployed offline verifier.
     227              :  */
     228              : static void
     229            1 : test_known_answers (void)
     230              : {
     231              :   struct TALER_PosChallengeP ch;
     232              :   char *conf;
     233              : 
     234            1 :   fprintf (stderr,
     235              :            "-- known-answer vectors --\n");
     236           33 :   for (unsigned int i = 0; i < sizeof (ch.challenge); i++)
     237           32 :     ch.challenge[i] = (unsigned char) (0xF0 - i);
     238              : 
     239              :   /* EdDSA signing is deterministic, so the exact bytes are pinned */
     240            1 :   conf = TALER_build_pos_confirmation_sig (KAT_EDDSA_PRIV,
     241              :                                            TALER_MCA_EDDSA_CHALLENGE,
     242              :                                            &ch);
     243            2 :   check ( (NULL != conf) &&
     244            1 :           (0 == strcmp (conf,
     245              :                         KAT_EDDSA_SIG)),
     246            1 :           "EdDSA confirmation matches the known-answer vector");
     247            1 :   GNUNET_free (conf);
     248            1 :   check (GNUNET_OK ==
     249            1 :          TALER_check_pos_confirmation_sig (KAT_EDDSA_PUB,
     250              :                                            TALER_MCA_EDDSA_CHALLENGE,
     251              :                                            &ch,
     252              :                                            KAT_EDDSA_SIG),
     253              :          "EdDSA known-answer vector verifies");
     254              : 
     255              :   /* ECDSA signing is randomized, so only verification can be pinned */
     256            1 :   check (GNUNET_OK ==
     257            1 :          TALER_check_pos_confirmation_sig (KAT_ECDSA_PUB,
     258              :                                            TALER_MCA_ECDSA_CHALLENGE,
     259              :                                            &ch,
     260              :                                            KAT_ECDSA_SIG),
     261              :          "ECDSA known-answer vector verifies");
     262            1 : }
     263              : 
     264              : 
     265              : /**
     266              :  * Re-encode @a raw as a confirmation string.
     267              :  *
     268              :  * @param raw 64 raw signature bytes
     269              :  * @return encoded signature, to be freed by the caller
     270              :  */
     271              : static char *
     272            2 : encode_sig (const unsigned char *raw)
     273              : {
     274            2 :   return GNUNET_STRINGS_data_to_string_alloc (raw,
     275              :                                               64);
     276              : }
     277              : 
     278              : 
     279              : /**
     280              :  * Decode the confirmation @a enc into its 64 raw bytes.
     281              :  *
     282              :  * @param enc encoded signature
     283              :  * @param[out] raw where to write the raw signature
     284              :  */
     285              : static void
     286          302 : decode_sig (const char *enc,
     287              :             unsigned char *raw)
     288              : {
     289          302 :   GNUNET_assert (GNUNET_OK ==
     290              :                  GNUNET_STRINGS_string_to_data (enc,
     291              :                                                 strlen (enc),
     292              :                                                 raw,
     293              :                                                 64));
     294          302 : }
     295              : 
     296              : 
     297              : /**
     298              :  * Add the Ed25519 group order L to the little-endian scalar S in
     299              :  * place.  The result is a different encoding of the same scalar
     300              :  * modulo L, which a verifier that omits the canonicality check would
     301              :  * wrongly accept.
     302              :  *
     303              :  * @param[in,out] s the 32 little-endian bytes of S
     304              :  */
     305              : static void
     306            1 : add_ed25519_order (unsigned char *s)
     307              : {
     308              :   static const unsigned char L[32] = {
     309              :     0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58,
     310              :     0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14,
     311              :     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
     312              :     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10
     313              :   };
     314            1 :   unsigned int carry = 0;
     315              : 
     316           33 :   for (unsigned int i = 0; i < 32; i++)
     317              :   {
     318           32 :     unsigned int v = s[i] + L[i] + carry;
     319              : 
     320           32 :     s[i] = (unsigned char) (v & 0xff);
     321           32 :     carry = v >> 8;
     322              :   }
     323            1 : }
     324              : 
     325              : 
     326              : /**
     327              :  * Replace the big-endian P-256 scalar @a s by n-s in place.  Both
     328              :  * (r,s) and (r,n-s) are mathematically valid ECDSA signatures; we
     329              :  * accept only the smaller one.
     330              :  *
     331              :  * @param[in,out] s the 32 big-endian bytes of s
     332              :  */
     333              : static void
     334          301 : negate_p256_scalar (unsigned char *s)
     335              : {
     336              :   static const unsigned char n[32] = {
     337              :     0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
     338              :     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
     339              :     0xbc, 0xe6, 0xfa, 0xad, 0xa7, 0x17, 0x9e, 0x84,
     340              :     0xf3, 0xb9, 0xca, 0xc2, 0xfc, 0x63, 0x25, 0x51
     341              :   };
     342          301 :   int borrow = 0;
     343              : 
     344         9933 :   for (int i = 31; i >= 0; i--)
     345              :   {
     346         9632 :     int v = (int) n[i] - (int) s[i] - borrow;
     347              : 
     348         9632 :     if (v < 0)
     349              :     {
     350         2886 :       v += 256;
     351         2886 :       borrow = 1;
     352              :     }
     353              :     else
     354              :     {
     355         6746 :       borrow = 0;
     356              :     }
     357         9632 :     s[i] = (unsigned char) v;
     358              :   }
     359          301 : }
     360              : 
     361              : 
     362              : /**
     363              :  * A confirmation must not be transformable into a second confirmation
     364              :  * that a verifier would also accept: an offline device that tracked
     365              :  * spent confirmations instead of spent challenges would otherwise be
     366              :  * replayable.
     367              :  */
     368              : static void
     369            1 : test_malleability (void)
     370              : {
     371              :   char *priv;
     372              :   char *pub;
     373              :   char *conf;
     374              :   char *mauled;
     375              :   struct TALER_PosChallengeP ch;
     376              :   unsigned char raw[64];
     377              : 
     378            1 :   fprintf (stderr,
     379              :            "-- malleability --\n");
     380            1 :   GNUNET_CRYPTO_random_block (&ch,
     381              :                               sizeof (ch));
     382              : 
     383              :   /* Ed25519: S+L encodes the same scalar; RFC 8032 requires S < L */
     384            1 :   GNUNET_assert (GNUNET_OK ==
     385              :                  TALER_otp_device_key_create (TALER_MCA_EDDSA_CHALLENGE,
     386              :                                               &priv,
     387              :                                               &pub));
     388            1 :   conf = TALER_build_pos_confirmation_sig (priv,
     389              :                                            TALER_MCA_EDDSA_CHALLENGE,
     390              :                                            &ch);
     391            1 :   GNUNET_assert (NULL != conf);
     392            1 :   decode_sig (conf,
     393              :               raw);
     394            1 :   add_ed25519_order (&raw[32]);
     395            1 :   mauled = encode_sig (raw);
     396            1 :   check (GNUNET_OK !=
     397            1 :          TALER_check_pos_confirmation_sig (pub,
     398              :                                            TALER_MCA_EDDSA_CHALLENGE,
     399              :                                            &ch,
     400              :                                            mauled),
     401              :          "EdDSA non-canonical S (S+L) is rejected");
     402            1 :   GNUNET_free (mauled);
     403            1 :   GNUNET_free (conf);
     404            1 :   GNUNET_free (priv);
     405            1 :   GNUNET_free (pub);
     406              : 
     407              :   /* P-256: (r, n-s) is a valid signature that we reject by policy */
     408            1 :   GNUNET_assert (GNUNET_OK ==
     409              :                  TALER_otp_device_key_create (TALER_MCA_ECDSA_CHALLENGE,
     410              :                                               &priv,
     411              :                                               &pub));
     412            1 :   conf = TALER_build_pos_confirmation_sig (priv,
     413              :                                            TALER_MCA_ECDSA_CHALLENGE,
     414              :                                            &ch);
     415            1 :   GNUNET_assert (NULL != conf);
     416            1 :   decode_sig (conf,
     417              :               raw);
     418            1 :   negate_p256_scalar (&raw[32]);
     419            1 :   mauled = encode_sig (raw);
     420            1 :   check (GNUNET_OK !=
     421            1 :          TALER_check_pos_confirmation_sig (pub,
     422              :                                            TALER_MCA_ECDSA_CHALLENGE,
     423              :                                            &ch,
     424              :                                            mauled),
     425              :          "ECDSA high-s variant is rejected");
     426            1 :   GNUNET_free (mauled);
     427            1 :   GNUNET_free (conf);
     428            1 :   GNUNET_free (priv);
     429            1 :   GNUNET_free (pub);
     430            1 : }
     431              : 
     432              : 
     433              : /**
     434              :  * Every ECDSA signature we emit must be the canonical low-s one, and
     435              :  * the public key must be a compressed point.  Runs enough iterations
     436              :  * that a scalar with a leading zero byte -- which the fixed-width
     437              :  * encoding has to pad -- is hit with near certainty.
     438              :  */
     439              : static void
     440            1 : test_ecdsa_encoding (void)
     441              : {
     442            1 :   unsigned int low_s_failures = 0;
     443            1 :   unsigned int prefix_failures = 0;
     444            1 :   unsigned int verify_failures = 0;
     445            1 :   unsigned int short_scalars = 0;
     446              : 
     447            1 :   fprintf (stderr,
     448              :            "-- ECDSA encoding (300 iterations) --\n");
     449          301 :   for (unsigned int i = 0; i < 300; i++)
     450              :   {
     451              :     char *priv;
     452              :     char *pub;
     453              :     char *conf;
     454              :     struct TALER_PosChallengeP ch;
     455              :     unsigned char raw[64];
     456              :     unsigned char pubraw[33];
     457              :     unsigned char neg[32];
     458              : 
     459          300 :     GNUNET_assert (GNUNET_OK ==
     460              :                    TALER_otp_device_key_create (TALER_MCA_ECDSA_CHALLENGE,
     461              :                                                 &priv,
     462              :                                                 &pub));
     463          300 :     GNUNET_CRYPTO_random_block (&ch,
     464              :                                 sizeof (ch));
     465          300 :     conf = TALER_build_pos_confirmation_sig (priv,
     466              :                                              TALER_MCA_ECDSA_CHALLENGE,
     467              :                                              &ch);
     468          300 :     GNUNET_assert (NULL != conf);
     469          300 :     GNUNET_assert (GNUNET_OK ==
     470              :                    GNUNET_STRINGS_string_to_data (pub,
     471              :                                                   strlen (pub),
     472              :                                                   pubraw,
     473              :                                                   sizeof (pubraw)));
     474          300 :     if ( (0x02 != pubraw[0]) &&
     475          154 :          (0x03 != pubraw[0]) )
     476            0 :       prefix_failures++;
     477          300 :     decode_sig (conf,
     478              :                 raw);
     479              :     /* s is low exactly when it is smaller than n-s */
     480          300 :     memcpy (neg,
     481              :             &raw[32],
     482              :             sizeof (neg));
     483          300 :     negate_p256_scalar (neg);
     484          300 :     if (memcmp (&raw[32],
     485              :                 neg,
     486              :                 sizeof (neg)) >= 0)
     487            0 :       low_s_failures++;
     488              :     /* count fixed-width scalars that needed zero padding */
     489          300 :     if ( (0x00 == raw[0]) ||
     490          298 :          (0x00 == raw[32]) )
     491            5 :       short_scalars++;
     492          300 :     if (GNUNET_OK !=
     493          300 :         TALER_check_pos_confirmation_sig (pub,
     494              :                                           TALER_MCA_ECDSA_CHALLENGE,
     495              :                                           &ch,
     496              :                                           conf))
     497            0 :       verify_failures++;
     498          300 :     GNUNET_free (conf);
     499          300 :     GNUNET_free (priv);
     500          300 :     GNUNET_free (pub);
     501              :   }
     502            1 :   check (0 == prefix_failures,
     503              :          "every public key is a compressed point (0x02/0x03)");
     504            1 :   check (0 == low_s_failures,
     505              :          "every signature uses the canonical low-s form");
     506            1 :   check (0 == verify_failures,
     507              :          "every signature verifies");
     508            1 :   fprintf (stderr,
     509              :            "     (%u/300 signatures had a scalar needing zero padding)\n",
     510              :            short_scalars);
     511            1 : }
     512              : 
     513              : 
     514              : /**
     515              :  * Ed25519 signing is deterministic while ECDSA is randomized; a
     516              :  * repeated ECDSA nonce would be catastrophic, so the signatures must
     517              :  * differ.
     518              :  */
     519              : static void
     520            1 : test_signing_determinism (void)
     521              : {
     522              :   char *priv;
     523              :   char *pub;
     524              :   char *a;
     525              :   char *b;
     526              :   struct TALER_PosChallengeP ch;
     527              : 
     528            1 :   fprintf (stderr,
     529              :            "-- signing determinism --\n");
     530            1 :   GNUNET_CRYPTO_random_block (&ch,
     531              :                               sizeof (ch));
     532            1 :   GNUNET_assert (GNUNET_OK ==
     533              :                  TALER_otp_device_key_create (TALER_MCA_EDDSA_CHALLENGE,
     534              :                                               &priv,
     535              :                                               &pub));
     536            1 :   a = TALER_build_pos_confirmation_sig (priv,
     537              :                                         TALER_MCA_EDDSA_CHALLENGE,
     538              :                                         &ch);
     539            1 :   b = TALER_build_pos_confirmation_sig (priv,
     540              :                                         TALER_MCA_EDDSA_CHALLENGE,
     541              :                                         &ch);
     542            1 :   check ( (NULL != a) &&
     543            2 :           (NULL != b) &&
     544            1 :           (0 == strcmp (a,
     545              :                         b)),
     546            1 :           "EdDSA signing is deterministic");
     547            1 :   GNUNET_free (a);
     548            1 :   GNUNET_free (b);
     549            1 :   GNUNET_free (priv);
     550            1 :   GNUNET_free (pub);
     551              : 
     552            1 :   GNUNET_assert (GNUNET_OK ==
     553              :                  TALER_otp_device_key_create (TALER_MCA_ECDSA_CHALLENGE,
     554              :                                               &priv,
     555              :                                               &pub));
     556            1 :   a = TALER_build_pos_confirmation_sig (priv,
     557              :                                         TALER_MCA_ECDSA_CHALLENGE,
     558              :                                         &ch);
     559            1 :   b = TALER_build_pos_confirmation_sig (priv,
     560              :                                         TALER_MCA_ECDSA_CHALLENGE,
     561              :                                         &ch);
     562            1 :   check ( (NULL != a) &&
     563            2 :           (NULL != b) &&
     564            1 :           (0 != strcmp (a,
     565              :                         b)),
     566            1 :           "ECDSA signing uses a fresh nonce each time");
     567            1 :   check ( (GNUNET_OK ==
     568            1 :            TALER_check_pos_confirmation_sig (pub,
     569              :                                              TALER_MCA_ECDSA_CHALLENGE,
     570              :                                              &ch,
     571            2 :                                              a)) &&
     572              :           (GNUNET_OK ==
     573            1 :            TALER_check_pos_confirmation_sig (pub,
     574              :                                              TALER_MCA_ECDSA_CHALLENGE,
     575              :                                              &ch,
     576              :                                              b)),
     577            1 :           "both ECDSA signatures verify");
     578            1 :   GNUNET_free (a);
     579            1 :   GNUNET_free (b);
     580            1 :   GNUNET_free (priv);
     581            1 :   GNUNET_free (pub);
     582            1 : }
     583              : 
     584              : 
     585              : /**
     586              :  * Is the compressed point @a raw refused as the public key for the
     587              :  * confirmation @a sig?
     588              :  *
     589              :  * @param raw 33 bytes to offer as a public key
     590              :  * @param ch challenge the confirmation is bound to
     591              :  * @param sig a confirmation that is valid under the real key
     592              :  * @return true if verification refused @a raw
     593              :  */
     594              : static bool
     595           47 : pub_rejected (const unsigned char *raw,
     596              :               const struct TALER_PosChallengeP *ch,
     597              :               const char *sig)
     598              : {
     599              :   char *enc;
     600              :   enum GNUNET_GenericReturnValue r;
     601              : 
     602           47 :   enc = GNUNET_STRINGS_data_to_string_alloc (raw,
     603              :                                              33);
     604           47 :   r = TALER_check_pos_confirmation_sig (enc,
     605              :                                         TALER_MCA_ECDSA_CHALLENGE,
     606              :                                         ch,
     607              :                                         sig);
     608           47 :   GNUNET_free (enc);
     609           47 :   return (GNUNET_OK != r);
     610              : }
     611              : 
     612              : 
     613              : /**
     614              :  * A public key that is not a valid curve point must be refused.  The
     615              :  * compressed encoding bounds what can even be expressed, so the cases
     616              :  * are an X with no square root, an X at or beyond the field prime,
     617              :  * and an undefined prefix byte.  libgcrypt refuses all of them today
     618              :  * and this pins that, so that changing how the point is handed to the
     619              :  * backend cannot silently start accepting garbage.
     620              :  */
     621              : static void
     622            1 : test_ecdsa_invalid_points (void)
     623              : {
     624              :   char *priv;
     625              :   char *pub;
     626              :   char *conf;
     627              :   struct TALER_PosChallengeP ch;
     628              :   unsigned char real[33];
     629              :   unsigned char raw[33];
     630            1 :   unsigned int accepted = 0;
     631              : 
     632            1 :   fprintf (stderr,
     633              :            "-- ECDSA invalid public points --\n");
     634            1 :   GNUNET_CRYPTO_random_block (&ch,
     635              :                               sizeof (ch));
     636            1 :   GNUNET_assert (GNUNET_OK ==
     637              :                  TALER_otp_device_key_create (TALER_MCA_ECDSA_CHALLENGE,
     638              :                                               &priv,
     639              :                                               &pub));
     640            1 :   conf = TALER_build_pos_confirmation_sig (priv,
     641              :                                            TALER_MCA_ECDSA_CHALLENGE,
     642              :                                            &ch);
     643            1 :   GNUNET_assert (NULL != conf);
     644            1 :   GNUNET_assert (GNUNET_OK ==
     645              :                  GNUNET_STRINGS_string_to_data (pub,
     646              :                                                 strlen (pub),
     647              :                                                 real,
     648              :                                                 sizeof (real)));
     649              : 
     650              :   /* X = 1 has no square root modulo p, so these 33 bytes do not
     651              :      encode a point at all (cross-checked against OpenSSL) */
     652            1 :   memset (raw,
     653              :           0,
     654              :           sizeof (raw));
     655            1 :   raw[0] = 0x02;
     656            1 :   raw[32] = 1;
     657            1 :   check (pub_rejected (raw,
     658              :                        &ch,
     659              :                        conf),
     660              :          "an X with no valid Y is rejected");
     661              : 
     662              :   /* X = 5 is on the curve, but it is not our key: this must fail as a
     663              :      signature check rather than as a decoding error */
     664            1 :   memset (raw,
     665              :           0,
     666              :           sizeof (raw));
     667            1 :   raw[0] = 0x02;
     668            1 :   raw[32] = 5;
     669            1 :   check (pub_rejected (raw,
     670              :                        &ch,
     671              :                        conf),
     672              :          "a valid but unrelated point is rejected");
     673              : 
     674              :   /* X beyond the field prime */
     675            1 :   raw[0] = 0x02;
     676            1 :   memset (&raw[1],
     677              :           0xFF,
     678              :           32);
     679            1 :   check (pub_rejected (raw,
     680              :                        &ch,
     681              :                        conf),
     682              :          "an X beyond the field prime is rejected");
     683              : 
     684              :   /* undefined or wrong prefix bytes on an otherwise real key */
     685            1 :   memcpy (raw,
     686              :           real,
     687              :           sizeof (raw));
     688            1 :   raw[0] = 0x00;
     689            1 :   check (pub_rejected (raw,
     690              :                        &ch,
     691              :                        conf),
     692              :          "prefix 0x00 is rejected");
     693            1 :   memcpy (raw,
     694              :           real,
     695              :           sizeof (raw));
     696            1 :   raw[0] = 0x04;
     697            1 :   check (pub_rejected (raw,
     698              :                        &ch,
     699              :                        conf),
     700              :          "the uncompressed-point prefix 0x04 is rejected");
     701            1 :   memcpy (raw,
     702              :           real,
     703              :           sizeof (raw));
     704            1 :   raw[0] = 0x05;
     705            1 :   check (pub_rejected (raw,
     706              :                        &ch,
     707              :                        conf),
     708              :          "an undefined prefix is rejected");
     709              : 
     710              :   /* flipping the parity bit selects the other point with the same X,
     711              :      which is a valid point but the wrong public key */
     712            1 :   memcpy (raw,
     713              :           real,
     714              :           sizeof (raw));
     715            1 :   raw[0] = (0x02 == real[0]) ? 0x03 : 0x02;
     716            1 :   check (pub_rejected (raw,
     717              :                        &ch,
     718              :                        conf),
     719              :          "flipping the point parity is rejected");
     720              : 
     721              :   /* Small X values, both prefixes.  Some are points that are simply
     722              :      not our key; the rest have no square root modulo p and do not
     723              :      decode at all. None of them may verify. The prefix picks between
     724              :      +Y and -Y, so it does not affect whether a Y exists.
     725              : 
     726              :      on the curve:  5, 6, 8, 9, 12, 13, 17
     727              :      off the curve: 1, 2, 3, 4, 7, 10, 11, 14, 15, 16, 18, 19, 20 */
     728           21 :   for (unsigned int x = 1; x <= 20; x++)
     729              :   {
     730           60 :     for (unsigned int parity = 0; parity < 2; parity++)
     731              :     {
     732           40 :       memset (raw,
     733              :               0,
     734              :               sizeof (raw));
     735           40 :       raw[0] = (0 == parity) ? 0x02 : 0x03;
     736           40 :       raw[32] = (unsigned char) x;
     737           40 :       if (! pub_rejected (raw,
     738              :                           &ch,
     739              :                           conf))
     740            0 :         accepted++;
     741              :     }
     742              :   }
     743            1 :   check (0 == accepted,
     744              :          "40 small compressed points are all rejected");
     745              : 
     746            1 :   GNUNET_free (conf);
     747            1 :   GNUNET_free (priv);
     748            1 :   GNUNET_free (pub);
     749            1 : }
     750              : 
     751              : 
     752              : /**
     753              :  * Malformed and mismatched inputs must be refused rather than
     754              :  * misinterpreted.
     755              :  */
     756              : static void
     757            1 : test_bad_inputs (void)
     758              : {
     759              :   char *ed_priv;
     760              :   char *ed_pub;
     761              :   char *ec_priv;
     762              :   char *ec_pub;
     763              :   char *ed_sig;
     764              :   char *ec_sig;
     765              :   struct TALER_PosChallengeP ch;
     766              : 
     767            1 :   fprintf (stderr,
     768              :            "-- malformed and mismatched inputs --\n");
     769            1 :   GNUNET_CRYPTO_random_block (&ch,
     770              :                               sizeof (ch));
     771              : 
     772              :   /* key generation is only defined for the challenge algorithms */
     773              :   {
     774            1 :     char *k = NULL;
     775            1 :     char *p = NULL;
     776              : 
     777            1 :     check (GNUNET_OK !=
     778            1 :            TALER_otp_device_key_create (TALER_MCA_NONE,
     779              :                                         &k,
     780              :                                         &p),
     781              :            "keygen refuses NONE");
     782            1 :     check (GNUNET_OK !=
     783            1 :            TALER_otp_device_key_create (TALER_MCA_WITHOUT_PRICE,
     784              :                                         &k,
     785              :                                         &p),
     786              :            "keygen refuses TOTP_WITHOUT_PRICE");
     787            1 :     check (GNUNET_OK !=
     788            1 :            TALER_otp_device_key_create (TALER_MCA_WITH_PRICE,
     789              :                                         &k,
     790              :                                         &p),
     791              :            "keygen refuses TOTP_WITH_PRICE");
     792              :   }
     793              : 
     794            1 :   GNUNET_assert (GNUNET_OK ==
     795              :                  TALER_otp_device_key_create (TALER_MCA_EDDSA_CHALLENGE,
     796              :                                               &ed_priv,
     797              :                                               &ed_pub));
     798            1 :   GNUNET_assert (GNUNET_OK ==
     799              :                  TALER_otp_device_key_create (TALER_MCA_ECDSA_CHALLENGE,
     800              :                                               &ec_priv,
     801              :                                               &ec_pub));
     802            1 :   ed_sig = TALER_build_pos_confirmation_sig (ed_priv,
     803              :                                              TALER_MCA_EDDSA_CHALLENGE,
     804              :                                              &ch);
     805            1 :   ec_sig = TALER_build_pos_confirmation_sig (ec_priv,
     806              :                                              TALER_MCA_ECDSA_CHALLENGE,
     807              :                                              &ch);
     808            1 :   GNUNET_assert ( (NULL != ed_sig) &&
     809              :                   (NULL != ec_sig) );
     810              : 
     811            1 :   check (GNUNET_OK !=
     812            1 :          TALER_check_pos_confirmation_sig (ed_pub,
     813              :                                            TALER_MCA_WITH_PRICE,
     814              :                                            &ch,
     815              :                                            ed_sig),
     816              :          "verify refuses a TOTP algorithm");
     817              :   /* an Ed25519 key is 32 bytes and a P-256 key 33, so the encodings
     818              :      are not interchangeable even before the curve differs */
     819            1 :   check (GNUNET_OK !=
     820            1 :          TALER_check_pos_confirmation_sig (ed_pub,
     821              :                                            TALER_MCA_ECDSA_CHALLENGE,
     822              :                                            &ch,
     823              :                                            ed_sig),
     824              :          "an EdDSA confirmation does not verify as ECDSA");
     825            1 :   check (GNUNET_OK !=
     826            1 :          TALER_check_pos_confirmation_sig (ec_pub,
     827              :                                            TALER_MCA_EDDSA_CHALLENGE,
     828              :                                            &ch,
     829              :                                            ec_sig),
     830              :          "an ECDSA confirmation does not verify as EdDSA");
     831            1 :   check (GNUNET_OK !=
     832            1 :          TALER_check_pos_confirmation_sig (ec_pub,
     833              :                                            TALER_MCA_ECDSA_CHALLENGE,
     834              :                                            &ch,
     835              :                                            ed_sig),
     836              :          "a confirmation made with the wrong key is rejected");
     837            1 :   check (GNUNET_OK !=
     838            1 :          TALER_check_pos_confirmation_sig ("!!!not base32!!!",
     839              :                                            TALER_MCA_EDDSA_CHALLENGE,
     840              :                                            &ch,
     841              :                                            ed_sig),
     842              :          "a malformed public key is rejected");
     843            1 :   check (GNUNET_OK !=
     844            1 :          TALER_check_pos_confirmation_sig (ed_pub,
     845              :                                            TALER_MCA_EDDSA_CHALLENGE,
     846              :                                            &ch,
     847              :                                            "!!!not base32!!!"),
     848              :          "a malformed confirmation is rejected");
     849              :   {
     850            1 :     char *truncated = GNUNET_strdup (ed_sig);
     851              : 
     852            1 :     truncated[strlen (truncated) - 4] = '\0';
     853            1 :     check (GNUNET_OK !=
     854            1 :            TALER_check_pos_confirmation_sig (ed_pub,
     855              :                                              TALER_MCA_EDDSA_CHALLENGE,
     856              :                                              &ch,
     857              :                                              truncated),
     858              :            "a truncated confirmation is rejected");
     859            1 :     GNUNET_free (truncated);
     860              :   }
     861              :   {
     862            1 :     char *truncated = GNUNET_strdup (ed_pub);
     863              : 
     864            1 :     truncated[strlen (truncated) - 4] = '\0';
     865            1 :     check (GNUNET_OK !=
     866            1 :            TALER_check_pos_confirmation_sig (truncated,
     867              :                                              TALER_MCA_EDDSA_CHALLENGE,
     868              :                                              &ch,
     869              :                                              ed_sig),
     870              :            "a truncated public key is rejected");
     871            1 :     GNUNET_free (truncated);
     872              :   }
     873              :   /* signing must refuse a key that is not the right size */
     874            1 :   check (NULL ==
     875            1 :          TALER_build_pos_confirmation_sig ("TOOSHORT",
     876              :                                            TALER_MCA_EDDSA_CHALLENGE,
     877              :                                            &ch),
     878              :          "signing refuses a short EdDSA key");
     879            1 :   check (NULL ==
     880            1 :          TALER_build_pos_confirmation_sig ("TOOSHORT",
     881              :                                            TALER_MCA_ECDSA_CHALLENGE,
     882              :                                            &ch),
     883              :          "signing refuses a short ECDSA key");
     884              : 
     885            1 :   GNUNET_free (ed_sig);
     886            1 :   GNUNET_free (ec_sig);
     887            1 :   GNUNET_free (ed_priv);
     888            1 :   GNUNET_free (ed_pub);
     889            1 :   GNUNET_free (ec_priv);
     890            1 :   GNUNET_free (ec_pub);
     891            1 : }
     892              : 
     893              : 
     894              : /**
     895              :  * The challenge algorithms must not have disturbed the TOTP
     896              :  * algorithms, which stay bit-for-bit as they were.
     897              :  */
     898              : static void
     899            1 : test_totp_unchanged (void)
     900              : {
     901              :   /* RFC 3548 base32, as a TOTP application would provide it */
     902            1 :   const char *pos_key = "JBSWY3DPEHPK3PXP";
     903              :   struct TALER_Amount total;
     904              :   char *code;
     905              : 
     906            1 :   fprintf (stderr,
     907              :            "-- TOTP regression --\n");
     908            1 :   code = TALER_build_pos_confirmation (pos_key,
     909              :                                        TALER_MCA_WITHOUT_PRICE,
     910              :                                        NULL,
     911            1 :                                        GNUNET_TIME_UNIT_ZERO_TS);
     912            1 :   check (NULL != code,
     913              :          "TOTP_WITHOUT_PRICE still produces a code");
     914            1 :   GNUNET_free (code);
     915              : 
     916            1 :   GNUNET_assert (GNUNET_OK ==
     917              :                  TALER_string_to_amount ("EUR:1.5",
     918              :                                          &total));
     919            1 :   code = TALER_build_pos_confirmation (pos_key,
     920              :                                        TALER_MCA_WITH_PRICE,
     921              :                                        &total,
     922            1 :                                        GNUNET_TIME_UNIT_ZERO_TS);
     923            1 :   check (NULL != code,
     924              :          "TOTP_WITH_PRICE still produces a code");
     925            1 :   GNUNET_free (code);
     926              : 
     927              :   /* an invalid amount must fail closed rather than sign nothing */
     928            1 :   code = TALER_build_pos_confirmation (pos_key,
     929              :                                        TALER_MCA_WITH_PRICE,
     930              :                                        NULL,
     931            1 :                                        GNUNET_TIME_UNIT_ZERO_TS);
     932            1 :   check (NULL == code,
     933              :          "TOTP_WITH_PRICE without an amount fails closed");
     934            1 :   GNUNET_free (code);
     935            1 : }
     936              : 
     937              : 
     938              : int
     939            1 : main (int argc,
     940              :       char *const *argv)
     941              : {
     942              :   (void) argc;
     943              :   (void) argv;
     944            1 :   GNUNET_log_setup ("test-crypto-confirmation",
     945              :                     "WARNING",
     946              :                     NULL);
     947            1 :   test_challenge_alg (TALER_MCA_EDDSA_CHALLENGE,
     948              :                       "EDDSA_CHALLENGE",
     949              :                       32);
     950            1 :   test_challenge_alg (TALER_MCA_ECDSA_CHALLENGE,
     951              :                       "ECDSA_CHALLENGE",
     952              :                       33);
     953            1 :   test_known_answers ();
     954            1 :   test_malleability ();
     955            1 :   test_ecdsa_encoding ();
     956            1 :   test_signing_determinism ();
     957            1 :   test_ecdsa_invalid_points ();
     958            1 :   test_bad_inputs ();
     959            1 :   test_totp_unchanged ();
     960            1 :   return (0 == fails) ? 0 : 1;
     961              : }
     962              : 
     963              : 
     964              : /* end of test_crypto_confirmation.c */
        

Generated by: LCOV version 2.0-1