LCOV - code coverage report
Current view: top level - util - crypto_ecdsa.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 74.3 % 183 136
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_ecdsa.c
      18              :  * @brief ECDSA over NIST P-256
      19              :  * @author Bohdan Potuzhnyi
      20              :  * @author Volodymyr Potuzhnyi
      21              :  *
      22              :  * GNUnet's ECDSA is over Ed25519, so P-256 is implemented here on top
      23              :  * of libgcrypt. This module knows nothing about what is being
      24              :  * signed: callers pass a hash and receive raw fixed-width scalars.
      25              :  */
      26              : #include "platform.h"
      27              : #include "taler/taler_util.h"
      28              : #include <gcrypt.h>
      29              : 
      30              : 
      31              : /**
      32              :  * Curve name libgcrypt uses.
      33              :  */
      34              : #define P256_CURVE "NIST P-256"
      35              : 
      36              : /**
      37              :  * Size of a scalar: the private key, and each of r and s.
      38              :  */
      39              : #define P256_SCALAR_LEN 32
      40              : 
      41              : /**
      42              :  * Order of the group, used to canonicalize signatures to low-s.
      43              :  */
      44              : #define P256_ORDER_HEX \
      45              :   "FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551"
      46              : 
      47              : 
      48              : /**
      49              :  * Write @a v into @a buf, zero-padded on the left to exactly @a len
      50              :  * bytes. Needed because libgcrypt returns MPIs without leading
      51              :  * zeros, while the wire format uses fixed-width scalars.
      52              :  *
      53              :  * @param v value to write
      54              :  * @param[out] buf where to write the value
      55              :  * @param len number of bytes to write
      56              :  * @return #GNUNET_OK on success
      57              :  */
      58              : static enum GNUNET_GenericReturnValue
      59         1226 : mpi_to_fixed (gcry_mpi_t v,
      60              :               unsigned char *buf,
      61              :               size_t len)
      62              : {
      63              :   size_t nbytes;
      64              : 
      65         1226 :   if (0 != gcry_mpi_print (GCRYMPI_FMT_USG,
      66              :                            NULL,
      67              :                            0,
      68              :                            &nbytes,
      69              :                            v))
      70              :   {
      71            0 :     GNUNET_break (0);
      72            0 :     return GNUNET_SYSERR;
      73              :   }
      74         1226 :   if (nbytes > len)
      75              :   {
      76            0 :     GNUNET_break (0);
      77            0 :     return GNUNET_SYSERR;
      78              :   }
      79         1226 :   memset (buf,
      80              :           0,
      81              :           len);
      82         1226 :   if (0 != gcry_mpi_print (GCRYMPI_FMT_USG,
      83         1226 :                            &buf[len - nbytes],
      84              :                            nbytes,
      85              :                            &nbytes,
      86              :                            v))
      87              :   {
      88            0 :     GNUNET_break (0);
      89            0 :     return GNUNET_SYSERR;
      90              :   }
      91         1226 :   return GNUNET_OK;
      92              : }
      93              : 
      94              : 
      95              : /**
      96              :  * Extract the MPI @a token from the s-expression @a sexp.
      97              :  *
      98              :  * @param sexp s-expression to search
      99              :  * @param token name of the token to extract
     100              :  * @return the value, or NULL on error; caller must release
     101              :  */
     102              : static gcry_mpi_t
     103         1226 : sexp_extract_mpi (gcry_sexp_t sexp,
     104              :                   const char *token)
     105              : {
     106              :   gcry_sexp_t t;
     107              :   gcry_mpi_t ret;
     108              : 
     109         1226 :   t = gcry_sexp_find_token (sexp,
     110              :                             token,
     111              :                             0);
     112         1226 :   if (NULL == t)
     113              :   {
     114            0 :     GNUNET_break (0);
     115            0 :     return NULL;
     116              :   }
     117         1226 :   ret = gcry_sexp_nth_mpi (t,
     118              :                            1,
     119              :                            GCRYMPI_FMT_USG);
     120         1226 :   gcry_sexp_release (t);
     121         1226 :   if (NULL == ret)
     122            0 :     GNUNET_break (0);
     123         1226 :   return ret;
     124              : }
     125              : 
     126              : 
     127              : /**
     128              :  * Is @a s in the upper half of the group order? ECDSA accepts both
     129              :  * @a s and n-s; only the low variant is canonical.
     130              :  *
     131              :  * @param s scalar to check
     132              :  * @param[out] high set to true if @a s is the high variant
     133              :  * @return #GNUNET_OK on success
     134              :  */
     135              : static enum GNUNET_GenericReturnValue
     136          663 : s_is_high (gcry_mpi_t s,
     137              :            bool *high)
     138              : {
     139              :   gcry_mpi_t n;
     140              :   gcry_mpi_t half;
     141              : 
     142          663 :   if (0 != gcry_mpi_scan (&n,
     143              :                           GCRYMPI_FMT_HEX,
     144              :                           P256_ORDER_HEX,
     145              :                           0,
     146              :                           NULL))
     147              :   {
     148            0 :     GNUNET_break (0);
     149            0 :     return GNUNET_SYSERR;
     150              :   }
     151          663 :   half = gcry_mpi_new (256);
     152          663 :   gcry_mpi_rshift (half,
     153              :                    n,
     154              :                    1);
     155          663 :   *high = (gcry_mpi_cmp (s,
     156          663 :                          half) > 0);
     157          663 :   gcry_mpi_release (half);
     158          663 :   gcry_mpi_release (n);
     159          663 :   return GNUNET_OK;
     160              : }
     161              : 
     162              : 
     163              : /**
     164              :  * Replace @a s by n-s, canonicalizing a signature to low-s.
     165              :  *
     166              :  * @param[in,out] s scalar to negate modulo the group order
     167              :  * @return #GNUNET_OK on success
     168              :  */
     169              : static enum GNUNET_GenericReturnValue
     170          142 : s_to_low (gcry_mpi_t s)
     171              : {
     172              :   gcry_mpi_t n;
     173              : 
     174          142 :   if (0 != gcry_mpi_scan (&n,
     175              :                           GCRYMPI_FMT_HEX,
     176              :                           P256_ORDER_HEX,
     177              :                           0,
     178              :                           NULL))
     179              :   {
     180            0 :     GNUNET_break (0);
     181            0 :     return GNUNET_SYSERR;
     182              :   }
     183          142 :   gcry_mpi_sub (s,
     184              :                 n,
     185              :                 s);
     186          142 :   gcry_mpi_release (n);
     187          142 :   return GNUNET_OK;
     188              : }
     189              : 
     190              : 
     191              : enum GNUNET_GenericReturnValue
     192          306 : TALER_ecdsa_p256_key_create (
     193              :   struct TALER_EcdsaP256PrivateKeyP *priv,
     194              :   struct TALER_EcdsaP256PublicKeyP *pub)
     195              : {
     196          306 :   gcry_sexp_t params = NULL;
     197          306 :   gcry_sexp_t keypair = NULL;
     198          306 :   gcry_mpi_t d = NULL;
     199          306 :   gcry_mpi_t q = NULL;
     200              :   unsigned char qbuf[2 * P256_SCALAR_LEN + 1];
     201          306 :   enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR;
     202              : 
     203          306 :   if (0 != gcry_sexp_build (&params,
     204              :                             NULL,
     205              :                             "(genkey(ecc(curve \"" P256_CURVE "\")))"))
     206              :   {
     207            0 :     GNUNET_break (0);
     208            0 :     goto cleanup;
     209              :   }
     210          306 :   if (0 != gcry_pk_genkey (&keypair,
     211              :                            params))
     212              :   {
     213            0 :     GNUNET_break (0);
     214            0 :     goto cleanup;
     215              :   }
     216          306 :   d = sexp_extract_mpi (keypair,
     217              :                         "d");
     218          306 :   q = sexp_extract_mpi (keypair,
     219              :                         "q");
     220          306 :   if ( (NULL == d) ||
     221              :        (NULL == q) )
     222            0 :     goto cleanup;
     223          306 :   if (GNUNET_OK !=
     224          306 :       mpi_to_fixed (d,
     225          306 :                     priv->d,
     226              :                     sizeof (priv->d)))
     227            0 :     goto cleanup;
     228              :   /* libgcrypt hands us the uncompressed point 0x04|X|Y; we store the
     229              :      compressed form, whose prefix encodes the parity of Y */
     230          306 :   if (GNUNET_OK !=
     231          306 :       mpi_to_fixed (q,
     232              :                     qbuf,
     233              :                     sizeof (qbuf)))
     234            0 :     goto cleanup;
     235          306 :   if (0x04 != qbuf[0])
     236              :   {
     237            0 :     GNUNET_break (0);
     238            0 :     goto cleanup;
     239              :   }
     240          306 :   pub->q[0] = (qbuf[sizeof (qbuf) - 1] & 1)
     241              :               ? 0x03
     242              :               : 0x02;
     243          306 :   memcpy (&pub->q[1],
     244              :           &qbuf[1],
     245              :           P256_SCALAR_LEN);
     246          306 :   ret = GNUNET_OK;
     247          306 : cleanup:
     248          306 :   if (NULL != d)
     249          306 :     gcry_mpi_release (d);
     250          306 :   if (NULL != q)
     251          306 :     gcry_mpi_release (q);
     252          306 :   if (NULL != keypair)
     253          306 :     gcry_sexp_release (keypair);
     254          306 :   if (NULL != params)
     255          306 :     gcry_sexp_release (params);
     256          306 :   return ret;
     257              : }
     258              : 
     259              : 
     260              : enum GNUNET_GenericReturnValue
     261          307 : TALER_ecdsa_p256_sign (
     262              :   const struct TALER_EcdsaP256PrivateKeyP *priv,
     263              :   const struct GNUNET_ShortHashCode *hash,
     264              :   struct TALER_EcdsaP256SignatureP *sig)
     265              : {
     266          307 :   gcry_sexp_t skey = NULL;
     267          307 :   gcry_sexp_t data = NULL;
     268          307 :   gcry_sexp_t sigs = NULL;
     269          307 :   gcry_mpi_t hm = NULL;
     270          307 :   gcry_mpi_t r = NULL;
     271          307 :   gcry_mpi_t s = NULL;
     272          307 :   enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR;
     273              :   bool high;
     274              : 
     275          307 :   if (0 != gcry_sexp_build (&skey,
     276              :                             NULL,
     277              :                             "(private-key(ecc(curve \"" P256_CURVE "\")"
     278              :                             "(d %b)))",
     279              :                             (int) sizeof (priv->d),
     280          307 :                             (const char *) priv->d))
     281              :   {
     282            0 :     GNUNET_break (0);
     283            0 :     goto cleanup;
     284              :   }
     285          307 :   if (0 != gcry_mpi_scan (&hm,
     286              :                           GCRYMPI_FMT_USG,
     287              :                           hash,
     288              :                           sizeof (*hash),
     289              :                           NULL))
     290              :   {
     291            0 :     GNUNET_break (0);
     292            0 :     goto cleanup;
     293              :   }
     294          307 :   if (0 != gcry_sexp_build (&data,
     295              :                             NULL,
     296              :                             "(data(flags raw)(value %m))",
     297              :                             hm))
     298              :   {
     299            0 :     GNUNET_break (0);
     300            0 :     goto cleanup;
     301              :   }
     302          307 :   if (0 != gcry_pk_sign (&sigs,
     303              :                          data,
     304              :                          skey))
     305              :   {
     306            0 :     GNUNET_break (0);
     307            0 :     goto cleanup;
     308              :   }
     309          307 :   r = sexp_extract_mpi (sigs,
     310              :                         "r");
     311          307 :   s = sexp_extract_mpi (sigs,
     312              :                         "s");
     313          307 :   if ( (NULL == r) ||
     314              :        (NULL == s) )
     315            0 :     goto cleanup;
     316          307 :   if (GNUNET_OK !=
     317          307 :       s_is_high (s,
     318              :                  &high))
     319            0 :     goto cleanup;
     320          449 :   if (high &&
     321              :       (GNUNET_OK !=
     322          142 :        s_to_low (s)) )
     323            0 :     goto cleanup;
     324          307 :   if ( (GNUNET_OK !=
     325          307 :         mpi_to_fixed (r,
     326          307 :                       sig->r_s,
     327          307 :                       P256_SCALAR_LEN)) ||
     328              :        (GNUNET_OK !=
     329          307 :         mpi_to_fixed (s,
     330              :                       &sig->r_s[P256_SCALAR_LEN],
     331              :                       P256_SCALAR_LEN)) )
     332            0 :     goto cleanup;
     333          307 :   ret = GNUNET_OK;
     334          307 : cleanup:
     335          307 :   if (NULL != r)
     336          307 :     gcry_mpi_release (r);
     337          307 :   if (NULL != s)
     338          307 :     gcry_mpi_release (s);
     339          307 :   if (NULL != hm)
     340          307 :     gcry_mpi_release (hm);
     341          307 :   if (NULL != sigs)
     342          307 :     gcry_sexp_release (sigs);
     343          307 :   if (NULL != data)
     344          307 :     gcry_sexp_release (data);
     345          307 :   if (NULL != skey)
     346          307 :     gcry_sexp_release (skey);
     347          307 :   return ret;
     348              : }
     349              : 
     350              : 
     351              : enum GNUNET_GenericReturnValue
     352          356 : TALER_ecdsa_p256_verify (
     353              :   const struct TALER_EcdsaP256PublicKeyP *pub,
     354              :   const struct GNUNET_ShortHashCode *hash,
     355              :   const struct TALER_EcdsaP256SignatureP *sig)
     356              : {
     357          356 :   gcry_sexp_t pkey = NULL;
     358          356 :   gcry_sexp_t data = NULL;
     359          356 :   gcry_sexp_t sigs = NULL;
     360          356 :   gcry_mpi_t hm = NULL;
     361          356 :   gcry_mpi_t r = NULL;
     362          356 :   gcry_mpi_t s = NULL;
     363          356 :   enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR;
     364              :   bool high;
     365              : 
     366          356 :   if (0 != gcry_mpi_scan (&r,
     367              :                           GCRYMPI_FMT_USG,
     368          356 :                           sig->r_s,
     369              :                           P256_SCALAR_LEN,
     370              :                           NULL))
     371              :   {
     372            0 :     GNUNET_break_op (0);
     373            0 :     goto cleanup;
     374              :   }
     375          356 :   if (0 != gcry_mpi_scan (&s,
     376              :                           GCRYMPI_FMT_USG,
     377          356 :                           &sig->r_s[P256_SCALAR_LEN],
     378              :                           P256_SCALAR_LEN,
     379              :                           NULL))
     380              :   {
     381            0 :     GNUNET_break_op (0);
     382            0 :     goto cleanup;
     383              :   }
     384              :   /* only the canonical low-s form is accepted, so that a signature
     385              :      cannot be mauled into a second valid encoding */
     386          356 :   if (GNUNET_OK !=
     387          356 :       s_is_high (s,
     388              :                  &high))
     389            0 :     goto cleanup;
     390          356 :   if (high)
     391              :   {
     392            2 :     GNUNET_break_op (0);
     393            2 :     goto cleanup;
     394              :   }
     395          354 :   if (0 != gcry_mpi_scan (&hm,
     396              :                           GCRYMPI_FMT_USG,
     397              :                           hash,
     398              :                           sizeof (*hash),
     399              :                           NULL))
     400              :   {
     401            0 :     GNUNET_break (0);
     402            0 :     goto cleanup;
     403              :   }
     404          354 :   if (0 != gcry_sexp_build (&data,
     405              :                             NULL,
     406              :                             "(data(flags raw)(value %m))",
     407              :                             hm))
     408              :   {
     409            0 :     GNUNET_break (0);
     410            0 :     goto cleanup;
     411              :   }
     412              :   /* libgcrypt decompresses the point and rejects anything that is not
     413              :      on the curve, so an invalid public key fails here */
     414          354 :   if (0 != gcry_sexp_build (&pkey,
     415              :                             NULL,
     416              :                             "(public-key(ecc(curve \"" P256_CURVE "\")"
     417              :                             "(q %b)))",
     418              :                             (int) sizeof (pub->q),
     419          354 :                             (const char *) pub->q))
     420              :   {
     421            0 :     GNUNET_break_op (0);
     422            0 :     goto cleanup;
     423              :   }
     424          354 :   if (0 != gcry_sexp_build (&sigs,
     425              :                             NULL,
     426              :                             "(sig-val(ecdsa(r %m)(s %m)))",
     427              :                             r,
     428              :                             s))
     429              :   {
     430            0 :     GNUNET_break (0);
     431            0 :     goto cleanup;
     432              :   }
     433          708 :   ret = (0 == gcry_pk_verify (sigs,
     434              :                               data,
     435              :                               pkey))
     436              :         ? GNUNET_OK
     437          354 :         : GNUNET_SYSERR;
     438          356 : cleanup:
     439          356 :   if (NULL != r)
     440          356 :     gcry_mpi_release (r);
     441          356 :   if (NULL != s)
     442          356 :     gcry_mpi_release (s);
     443          356 :   if (NULL != hm)
     444          354 :     gcry_mpi_release (hm);
     445          356 :   if (NULL != sigs)
     446          354 :     gcry_sexp_release (sigs);
     447          356 :   if (NULL != data)
     448          354 :     gcry_sexp_release (data);
     449          356 :   if (NULL != pkey)
     450          354 :     gcry_sexp_release (pkey);
     451          356 :   return ret;
     452              : }
     453              : 
     454              : 
     455              : /* end of crypto_ecdsa.c */
        

Generated by: LCOV version 2.0-1