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 (¶ms,
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 */
|