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 */
|