test_crypto_confirmation.c (31057B)
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 check (bool ok, 40 const char *label) 41 { 42 if (! ok) 43 { 44 fprintf (stderr, 45 "FAIL %s\n", 46 label); 47 fails++; 48 return; 49 } 50 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 decodes_to (const char *enc, 65 size_t len) 66 { 67 unsigned char buf[128]; 68 69 GNUNET_assert (len <= sizeof (buf)); 70 return (GNUNET_OK == 71 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 test_challenge_alg (enum TALER_MerchantConfirmationAlgorithm alg, 87 const char *name, 88 size_t pub_len) 89 { 90 char *priv = NULL; 91 char *pub = NULL; 92 char *priv2 = NULL; 93 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 fprintf (stderr, 104 "-- %s --\n", 105 name); 106 LABEL ("keygen"); 107 check (GNUNET_OK == 108 TALER_otp_device_key_create (alg, 109 &priv, 110 &pub), 111 label); 112 if ( (NULL == priv) || 113 (NULL == pub) ) 114 return; 115 LABEL ("public key has the documented length"); 116 check (decodes_to (pub, 117 pub_len), 118 label); 119 120 GNUNET_CRYPTO_random_block (&ch, 121 sizeof (ch)); 122 conf = TALER_build_pos_confirmation_sig (priv, 123 alg, 124 &ch); 125 LABEL ("sign"); 126 check (NULL != conf, 127 label); 128 if (NULL == conf) 129 return; 130 LABEL ("signature is 64 bytes"); 131 check (decodes_to (conf, 132 64), 133 label); 134 LABEL ("verify accepts a fresh confirmation"); 135 check (GNUNET_OK == 136 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 ch2 = ch; 144 ch2.challenge[0] ^= 0x01; 145 LABEL ("mutated challenge is rejected"); 146 check (GNUNET_OK != 147 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 check (GNUNET_OK == 155 TALER_otp_device_key_create (alg, 156 &priv2, 157 &pub2), 158 "second keygen"); 159 LABEL ("confirmation is rejected under a different key"); 160 check (GNUNET_OK != 161 TALER_check_pos_confirmation_sig (pub2, 162 alg, 163 &ch, 164 conf), 165 label); 166 167 conf2 = TALER_build_pos_confirmation_sig (priv2, 168 alg, 169 &ch); 170 LABEL ("a different key yields a different confirmation"); 171 check ( (NULL != conf2) && 172 (0 != strcmp (conf, 173 conf2)), 174 label); 175 176 { 177 char *bad = GNUNET_strdup (conf); 178 179 bad[0] = ('A' == bad[0]) ? 'B' : 'A'; 180 LABEL ("mauled confirmation is rejected"); 181 check (GNUNET_OK != 182 TALER_check_pos_confirmation_sig (pub, 183 alg, 184 &ch, 185 bad), 186 label); 187 GNUNET_free (bad); 188 } 189 190 /* DD 97: must not return a confirmation we cannot compute */ 191 LABEL ("missing challenge fails closed"); 192 check (NULL == 193 TALER_build_pos_confirmation_sig (priv, 194 alg, 195 NULL), 196 label); 197 #undef LABEL 198 GNUNET_free (conf); 199 GNUNET_free (conf2); 200 GNUNET_free (priv); 201 GNUNET_free (pub); 202 GNUNET_free (priv2); 203 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 test_known_answers (void) 230 { 231 struct TALER_PosChallengeP ch; 232 char *conf; 233 234 fprintf (stderr, 235 "-- known-answer vectors --\n"); 236 for (unsigned int i = 0; i < sizeof (ch.challenge); i++) 237 ch.challenge[i] = (unsigned char) (0xF0 - i); 238 239 /* EdDSA signing is deterministic, so the exact bytes are pinned */ 240 conf = TALER_build_pos_confirmation_sig (KAT_EDDSA_PRIV, 241 TALER_MCA_EDDSA_CHALLENGE, 242 &ch); 243 check ( (NULL != conf) && 244 (0 == strcmp (conf, 245 KAT_EDDSA_SIG)), 246 "EdDSA confirmation matches the known-answer vector"); 247 GNUNET_free (conf); 248 check (GNUNET_OK == 249 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 check (GNUNET_OK == 257 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 } 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 encode_sig (const unsigned char *raw) 273 { 274 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 decode_sig (const char *enc, 287 unsigned char *raw) 288 { 289 GNUNET_assert (GNUNET_OK == 290 GNUNET_STRINGS_string_to_data (enc, 291 strlen (enc), 292 raw, 293 64)); 294 } 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 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 unsigned int carry = 0; 315 316 for (unsigned int i = 0; i < 32; i++) 317 { 318 unsigned int v = s[i] + L[i] + carry; 319 320 s[i] = (unsigned char) (v & 0xff); 321 carry = v >> 8; 322 } 323 } 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 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 int borrow = 0; 343 344 for (int i = 31; i >= 0; i--) 345 { 346 int v = (int) n[i] - (int) s[i] - borrow; 347 348 if (v < 0) 349 { 350 v += 256; 351 borrow = 1; 352 } 353 else 354 { 355 borrow = 0; 356 } 357 s[i] = (unsigned char) v; 358 } 359 } 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 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 fprintf (stderr, 379 "-- malleability --\n"); 380 GNUNET_CRYPTO_random_block (&ch, 381 sizeof (ch)); 382 383 /* Ed25519: S+L encodes the same scalar; RFC 8032 requires S < L */ 384 GNUNET_assert (GNUNET_OK == 385 TALER_otp_device_key_create (TALER_MCA_EDDSA_CHALLENGE, 386 &priv, 387 &pub)); 388 conf = TALER_build_pos_confirmation_sig (priv, 389 TALER_MCA_EDDSA_CHALLENGE, 390 &ch); 391 GNUNET_assert (NULL != conf); 392 decode_sig (conf, 393 raw); 394 add_ed25519_order (&raw[32]); 395 mauled = encode_sig (raw); 396 check (GNUNET_OK != 397 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 GNUNET_free (mauled); 403 GNUNET_free (conf); 404 GNUNET_free (priv); 405 GNUNET_free (pub); 406 407 /* P-256: (r, n-s) is a valid signature that we reject by policy */ 408 GNUNET_assert (GNUNET_OK == 409 TALER_otp_device_key_create (TALER_MCA_ECDSA_CHALLENGE, 410 &priv, 411 &pub)); 412 conf = TALER_build_pos_confirmation_sig (priv, 413 TALER_MCA_ECDSA_CHALLENGE, 414 &ch); 415 GNUNET_assert (NULL != conf); 416 decode_sig (conf, 417 raw); 418 negate_p256_scalar (&raw[32]); 419 mauled = encode_sig (raw); 420 check (GNUNET_OK != 421 TALER_check_pos_confirmation_sig (pub, 422 TALER_MCA_ECDSA_CHALLENGE, 423 &ch, 424 mauled), 425 "ECDSA high-s variant is rejected"); 426 GNUNET_free (mauled); 427 GNUNET_free (conf); 428 GNUNET_free (priv); 429 GNUNET_free (pub); 430 } 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 test_ecdsa_encoding (void) 441 { 442 unsigned int low_s_failures = 0; 443 unsigned int prefix_failures = 0; 444 unsigned int verify_failures = 0; 445 unsigned int short_scalars = 0; 446 447 fprintf (stderr, 448 "-- ECDSA encoding (300 iterations) --\n"); 449 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 GNUNET_assert (GNUNET_OK == 460 TALER_otp_device_key_create (TALER_MCA_ECDSA_CHALLENGE, 461 &priv, 462 &pub)); 463 GNUNET_CRYPTO_random_block (&ch, 464 sizeof (ch)); 465 conf = TALER_build_pos_confirmation_sig (priv, 466 TALER_MCA_ECDSA_CHALLENGE, 467 &ch); 468 GNUNET_assert (NULL != conf); 469 GNUNET_assert (GNUNET_OK == 470 GNUNET_STRINGS_string_to_data (pub, 471 strlen (pub), 472 pubraw, 473 sizeof (pubraw))); 474 if ( (0x02 != pubraw[0]) && 475 (0x03 != pubraw[0]) ) 476 prefix_failures++; 477 decode_sig (conf, 478 raw); 479 /* s is low exactly when it is smaller than n-s */ 480 memcpy (neg, 481 &raw[32], 482 sizeof (neg)); 483 negate_p256_scalar (neg); 484 if (memcmp (&raw[32], 485 neg, 486 sizeof (neg)) >= 0) 487 low_s_failures++; 488 /* count fixed-width scalars that needed zero padding */ 489 if ( (0x00 == raw[0]) || 490 (0x00 == raw[32]) ) 491 short_scalars++; 492 if (GNUNET_OK != 493 TALER_check_pos_confirmation_sig (pub, 494 TALER_MCA_ECDSA_CHALLENGE, 495 &ch, 496 conf)) 497 verify_failures++; 498 GNUNET_free (conf); 499 GNUNET_free (priv); 500 GNUNET_free (pub); 501 } 502 check (0 == prefix_failures, 503 "every public key is a compressed point (0x02/0x03)"); 504 check (0 == low_s_failures, 505 "every signature uses the canonical low-s form"); 506 check (0 == verify_failures, 507 "every signature verifies"); 508 fprintf (stderr, 509 " (%u/300 signatures had a scalar needing zero padding)\n", 510 short_scalars); 511 } 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 test_signing_determinism (void) 521 { 522 char *priv; 523 char *pub; 524 char *a; 525 char *b; 526 struct TALER_PosChallengeP ch; 527 528 fprintf (stderr, 529 "-- signing determinism --\n"); 530 GNUNET_CRYPTO_random_block (&ch, 531 sizeof (ch)); 532 GNUNET_assert (GNUNET_OK == 533 TALER_otp_device_key_create (TALER_MCA_EDDSA_CHALLENGE, 534 &priv, 535 &pub)); 536 a = TALER_build_pos_confirmation_sig (priv, 537 TALER_MCA_EDDSA_CHALLENGE, 538 &ch); 539 b = TALER_build_pos_confirmation_sig (priv, 540 TALER_MCA_EDDSA_CHALLENGE, 541 &ch); 542 check ( (NULL != a) && 543 (NULL != b) && 544 (0 == strcmp (a, 545 b)), 546 "EdDSA signing is deterministic"); 547 GNUNET_free (a); 548 GNUNET_free (b); 549 GNUNET_free (priv); 550 GNUNET_free (pub); 551 552 GNUNET_assert (GNUNET_OK == 553 TALER_otp_device_key_create (TALER_MCA_ECDSA_CHALLENGE, 554 &priv, 555 &pub)); 556 a = TALER_build_pos_confirmation_sig (priv, 557 TALER_MCA_ECDSA_CHALLENGE, 558 &ch); 559 b = TALER_build_pos_confirmation_sig (priv, 560 TALER_MCA_ECDSA_CHALLENGE, 561 &ch); 562 check ( (NULL != a) && 563 (NULL != b) && 564 (0 != strcmp (a, 565 b)), 566 "ECDSA signing uses a fresh nonce each time"); 567 check ( (GNUNET_OK == 568 TALER_check_pos_confirmation_sig (pub, 569 TALER_MCA_ECDSA_CHALLENGE, 570 &ch, 571 a)) && 572 (GNUNET_OK == 573 TALER_check_pos_confirmation_sig (pub, 574 TALER_MCA_ECDSA_CHALLENGE, 575 &ch, 576 b)), 577 "both ECDSA signatures verify"); 578 GNUNET_free (a); 579 GNUNET_free (b); 580 GNUNET_free (priv); 581 GNUNET_free (pub); 582 } 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 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 enc = GNUNET_STRINGS_data_to_string_alloc (raw, 603 33); 604 r = TALER_check_pos_confirmation_sig (enc, 605 TALER_MCA_ECDSA_CHALLENGE, 606 ch, 607 sig); 608 GNUNET_free (enc); 609 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 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 unsigned int accepted = 0; 631 632 fprintf (stderr, 633 "-- ECDSA invalid public points --\n"); 634 GNUNET_CRYPTO_random_block (&ch, 635 sizeof (ch)); 636 GNUNET_assert (GNUNET_OK == 637 TALER_otp_device_key_create (TALER_MCA_ECDSA_CHALLENGE, 638 &priv, 639 &pub)); 640 conf = TALER_build_pos_confirmation_sig (priv, 641 TALER_MCA_ECDSA_CHALLENGE, 642 &ch); 643 GNUNET_assert (NULL != conf); 644 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 memset (raw, 653 0, 654 sizeof (raw)); 655 raw[0] = 0x02; 656 raw[32] = 1; 657 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 memset (raw, 665 0, 666 sizeof (raw)); 667 raw[0] = 0x02; 668 raw[32] = 5; 669 check (pub_rejected (raw, 670 &ch, 671 conf), 672 "a valid but unrelated point is rejected"); 673 674 /* X beyond the field prime */ 675 raw[0] = 0x02; 676 memset (&raw[1], 677 0xFF, 678 32); 679 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 memcpy (raw, 686 real, 687 sizeof (raw)); 688 raw[0] = 0x00; 689 check (pub_rejected (raw, 690 &ch, 691 conf), 692 "prefix 0x00 is rejected"); 693 memcpy (raw, 694 real, 695 sizeof (raw)); 696 raw[0] = 0x04; 697 check (pub_rejected (raw, 698 &ch, 699 conf), 700 "the uncompressed-point prefix 0x04 is rejected"); 701 memcpy (raw, 702 real, 703 sizeof (raw)); 704 raw[0] = 0x05; 705 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 memcpy (raw, 713 real, 714 sizeof (raw)); 715 raw[0] = (0x02 == real[0]) ? 0x03 : 0x02; 716 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 for (unsigned int x = 1; x <= 20; x++) 729 { 730 for (unsigned int parity = 0; parity < 2; parity++) 731 { 732 memset (raw, 733 0, 734 sizeof (raw)); 735 raw[0] = (0 == parity) ? 0x02 : 0x03; 736 raw[32] = (unsigned char) x; 737 if (! pub_rejected (raw, 738 &ch, 739 conf)) 740 accepted++; 741 } 742 } 743 check (0 == accepted, 744 "40 small compressed points are all rejected"); 745 746 GNUNET_free (conf); 747 GNUNET_free (priv); 748 GNUNET_free (pub); 749 } 750 751 752 /** 753 * Malformed and mismatched inputs must be refused rather than 754 * misinterpreted. 755 */ 756 static void 757 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 fprintf (stderr, 768 "-- malformed and mismatched inputs --\n"); 769 GNUNET_CRYPTO_random_block (&ch, 770 sizeof (ch)); 771 772 /* key generation is only defined for the challenge algorithms */ 773 { 774 char *k = NULL; 775 char *p = NULL; 776 777 check (GNUNET_OK != 778 TALER_otp_device_key_create (TALER_MCA_NONE, 779 &k, 780 &p), 781 "keygen refuses NONE"); 782 check (GNUNET_OK != 783 TALER_otp_device_key_create (TALER_MCA_WITHOUT_PRICE, 784 &k, 785 &p), 786 "keygen refuses TOTP_WITHOUT_PRICE"); 787 check (GNUNET_OK != 788 TALER_otp_device_key_create (TALER_MCA_WITH_PRICE, 789 &k, 790 &p), 791 "keygen refuses TOTP_WITH_PRICE"); 792 } 793 794 GNUNET_assert (GNUNET_OK == 795 TALER_otp_device_key_create (TALER_MCA_EDDSA_CHALLENGE, 796 &ed_priv, 797 &ed_pub)); 798 GNUNET_assert (GNUNET_OK == 799 TALER_otp_device_key_create (TALER_MCA_ECDSA_CHALLENGE, 800 &ec_priv, 801 &ec_pub)); 802 ed_sig = TALER_build_pos_confirmation_sig (ed_priv, 803 TALER_MCA_EDDSA_CHALLENGE, 804 &ch); 805 ec_sig = TALER_build_pos_confirmation_sig (ec_priv, 806 TALER_MCA_ECDSA_CHALLENGE, 807 &ch); 808 GNUNET_assert ( (NULL != ed_sig) && 809 (NULL != ec_sig) ); 810 811 check (GNUNET_OK != 812 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 check (GNUNET_OK != 820 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 check (GNUNET_OK != 826 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 check (GNUNET_OK != 832 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 check (GNUNET_OK != 838 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 check (GNUNET_OK != 844 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 char *truncated = GNUNET_strdup (ed_sig); 851 852 truncated[strlen (truncated) - 4] = '\0'; 853 check (GNUNET_OK != 854 TALER_check_pos_confirmation_sig (ed_pub, 855 TALER_MCA_EDDSA_CHALLENGE, 856 &ch, 857 truncated), 858 "a truncated confirmation is rejected"); 859 GNUNET_free (truncated); 860 } 861 { 862 char *truncated = GNUNET_strdup (ed_pub); 863 864 truncated[strlen (truncated) - 4] = '\0'; 865 check (GNUNET_OK != 866 TALER_check_pos_confirmation_sig (truncated, 867 TALER_MCA_EDDSA_CHALLENGE, 868 &ch, 869 ed_sig), 870 "a truncated public key is rejected"); 871 GNUNET_free (truncated); 872 } 873 /* signing must refuse a key that is not the right size */ 874 check (NULL == 875 TALER_build_pos_confirmation_sig ("TOOSHORT", 876 TALER_MCA_EDDSA_CHALLENGE, 877 &ch), 878 "signing refuses a short EdDSA key"); 879 check (NULL == 880 TALER_build_pos_confirmation_sig ("TOOSHORT", 881 TALER_MCA_ECDSA_CHALLENGE, 882 &ch), 883 "signing refuses a short ECDSA key"); 884 885 GNUNET_free (ed_sig); 886 GNUNET_free (ec_sig); 887 GNUNET_free (ed_priv); 888 GNUNET_free (ed_pub); 889 GNUNET_free (ec_priv); 890 GNUNET_free (ec_pub); 891 } 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 test_totp_unchanged (void) 900 { 901 /* RFC 3548 base32, as a TOTP application would provide it */ 902 const char *pos_key = "JBSWY3DPEHPK3PXP"; 903 struct TALER_Amount total; 904 char *code; 905 906 fprintf (stderr, 907 "-- TOTP regression --\n"); 908 code = TALER_build_pos_confirmation (pos_key, 909 TALER_MCA_WITHOUT_PRICE, 910 NULL, 911 GNUNET_TIME_UNIT_ZERO_TS); 912 check (NULL != code, 913 "TOTP_WITHOUT_PRICE still produces a code"); 914 GNUNET_free (code); 915 916 GNUNET_assert (GNUNET_OK == 917 TALER_string_to_amount ("EUR:1.5", 918 &total)); 919 code = TALER_build_pos_confirmation (pos_key, 920 TALER_MCA_WITH_PRICE, 921 &total, 922 GNUNET_TIME_UNIT_ZERO_TS); 923 check (NULL != code, 924 "TOTP_WITH_PRICE still produces a code"); 925 GNUNET_free (code); 926 927 /* an invalid amount must fail closed rather than sign nothing */ 928 code = TALER_build_pos_confirmation (pos_key, 929 TALER_MCA_WITH_PRICE, 930 NULL, 931 GNUNET_TIME_UNIT_ZERO_TS); 932 check (NULL == code, 933 "TOTP_WITH_PRICE without an amount fails closed"); 934 GNUNET_free (code); 935 } 936 937 938 int 939 main (int argc, 940 char *const *argv) 941 { 942 (void) argc; 943 (void) argv; 944 GNUNET_log_setup ("test-crypto-confirmation", 945 "WARNING", 946 NULL); 947 test_challenge_alg (TALER_MCA_EDDSA_CHALLENGE, 948 "EDDSA_CHALLENGE", 949 32); 950 test_challenge_alg (TALER_MCA_ECDSA_CHALLENGE, 951 "ECDSA_CHALLENGE", 952 33); 953 test_known_answers (); 954 test_malleability (); 955 test_ecdsa_encoding (); 956 test_signing_determinism (); 957 test_ecdsa_invalid_points (); 958 test_bad_inputs (); 959 test_totp_unchanged (); 960 return (0 == fails) ? 0 : 1; 961 } 962 963 964 /* end of test_crypto_confirmation.c */