crypto_helper_rsa.c (20170B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2020, 2021 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_helper_rsa.c 18 * @brief utility functions for running out-of-process private key operations 19 * @author Christian Grothoff 20 */ 21 #include "platform.h" 22 #include "taler/taler_util.h" 23 #include "taler/taler_signatures.h" 24 #include "crypto_helper_common.h" 25 #include "secmod_rsa.h" 26 #include <poll.h> 27 28 29 struct TALER_CRYPTO_RsaDenominationHelper 30 { 31 /** 32 * Function to call with updates to available key material. 33 */ 34 TALER_CRYPTO_RsaDenominationKeyStatusCallback dkc; 35 36 /** 37 * Closure for @e dkc 38 */ 39 void *dkc_cls; 40 41 /** 42 * Socket address of the denomination helper process. 43 * Used to reconnect if the connection breaks. 44 */ 45 struct sockaddr_un sa; 46 47 /** 48 * The UNIX domain socket, -1 if we are currently not connected. 49 */ 50 int sock; 51 52 /** 53 * Have we ever been sync'ed? 54 */ 55 bool synced; 56 }; 57 58 59 /** 60 * Disconnect from the helper process. Updates 61 * @e sock field in @a dh. 62 * 63 * @param[in,out] dh handle to tear down connection of 64 */ 65 static void 66 do_disconnect (struct TALER_CRYPTO_RsaDenominationHelper *dh) 67 { 68 GNUNET_break (0 == close (dh->sock)); 69 dh->sock = -1; 70 dh->synced = false; 71 } 72 73 74 /** 75 * Try to connect to the helper process. Updates 76 * @e sock field in @a dh. 77 * 78 * @param[in,out] dh handle to establish connection for 79 * @return #GNUNET_OK on success 80 */ 81 static enum GNUNET_GenericReturnValue 82 try_connect (struct TALER_CRYPTO_RsaDenominationHelper *dh) 83 { 84 if (-1 != dh->sock) 85 return GNUNET_OK; 86 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 87 "Establishing connection!\n"); 88 dh->sock = socket (AF_UNIX, 89 SOCK_STREAM, 90 0); 91 if (-1 == dh->sock) 92 { 93 GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, 94 "socket"); 95 return GNUNET_SYSERR; 96 } 97 if (0 != 98 connect (dh->sock, 99 (const struct sockaddr *) &dh->sa, 100 sizeof (dh->sa))) 101 { 102 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, 103 "connect", 104 dh->sa.sun_path); 105 do_disconnect (dh); 106 return GNUNET_SYSERR; 107 } 108 TALER_CRYPTO_helper_rsa_poll (dh); 109 return GNUNET_OK; 110 } 111 112 113 struct TALER_CRYPTO_RsaDenominationHelper * 114 TALER_CRYPTO_helper_rsa_connect ( 115 const struct GNUNET_CONFIGURATION_Handle *cfg, 116 const char *section, 117 TALER_CRYPTO_RsaDenominationKeyStatusCallback dkc, 118 void *dkc_cls) 119 { 120 struct TALER_CRYPTO_RsaDenominationHelper *dh; 121 char *unixpath; 122 char *secname; 123 124 GNUNET_asprintf (&secname, 125 "%s-secmod-rsa", 126 section); 127 128 if (GNUNET_OK != 129 GNUNET_CONFIGURATION_get_value_filename (cfg, 130 secname, 131 "UNIXPATH", 132 &unixpath)) 133 { 134 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 135 secname, 136 "UNIXPATH"); 137 GNUNET_free (secname); 138 return NULL; 139 } 140 /* we use >= here because we want the sun_path to always 141 be 0-terminated */ 142 if (strlen (unixpath) >= sizeof (dh->sa.sun_path)) 143 { 144 GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, 145 secname, 146 "UNIXPATH", 147 "path too long"); 148 GNUNET_free (unixpath); 149 GNUNET_free (secname); 150 return NULL; 151 } 152 GNUNET_free (secname); 153 dh = GNUNET_new (struct TALER_CRYPTO_RsaDenominationHelper); 154 dh->dkc = dkc; 155 dh->dkc_cls = dkc_cls; 156 dh->sa.sun_family = AF_UNIX; 157 strncpy (dh->sa.sun_path, 158 unixpath, 159 sizeof (dh->sa.sun_path) - 1); 160 GNUNET_free (unixpath); 161 dh->sock = -1; 162 if (GNUNET_OK != 163 try_connect (dh)) 164 { 165 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 166 "Could not connect to %s. Will keep trying\n", 167 "taler-exchange-helper-secmod-rsa"); 168 } 169 return dh; 170 } 171 172 173 /** 174 * Handle a #TALER_HELPER_RSA_MT_AVAIL message from the helper. 175 * 176 * @param dh helper context 177 * @param hdr message that we received 178 * @return #GNUNET_OK on success 179 */ 180 static enum GNUNET_GenericReturnValue 181 handle_mt_avail (struct TALER_CRYPTO_RsaDenominationHelper *dh, 182 const struct GNUNET_MessageHeader *hdr) 183 { 184 const struct TALER_CRYPTO_RsaKeyAvailableNotification *kan 185 = (const struct TALER_CRYPTO_RsaKeyAvailableNotification *) hdr; 186 const char *buf = (const char *) &kan[1]; 187 const char *section_name; 188 uint16_t ps; 189 uint16_t snl; 190 191 if (sizeof (*kan) > ntohs (hdr->size)) 192 { 193 GNUNET_break_op (0); 194 return GNUNET_SYSERR; 195 } 196 ps = ntohs (kan->pub_size); 197 snl = ntohs (kan->section_name_len); 198 if (ntohs (hdr->size) != sizeof (*kan) + ps + snl) 199 { 200 GNUNET_break_op (0); 201 return GNUNET_SYSERR; 202 } 203 if (0 == snl) 204 { 205 GNUNET_break_op (0); 206 return GNUNET_SYSERR; 207 } 208 section_name = &buf[ps]; 209 if ('\0' != section_name[snl - 1]) 210 { 211 GNUNET_break_op (0); 212 return GNUNET_SYSERR; 213 } 214 215 { 216 struct GNUNET_CRYPTO_BlindSignPublicKey *bs_pub; 217 struct TALER_RsaPubHashP h_rsa; 218 219 bs_pub = GNUNET_new (struct GNUNET_CRYPTO_BlindSignPublicKey); 220 bs_pub->cipher = GNUNET_CRYPTO_BSA_RSA; 221 bs_pub->details.rsa_public_key 222 = GNUNET_CRYPTO_rsa_public_key_decode (buf, 223 ntohs (kan->pub_size)); 224 if (NULL == bs_pub->details.rsa_public_key) 225 { 226 GNUNET_break_op (0); 227 GNUNET_free (bs_pub); 228 return GNUNET_SYSERR; 229 } 230 bs_pub->rc = 1; 231 GNUNET_CRYPTO_rsa_public_key_hash (bs_pub->details.rsa_public_key, 232 &bs_pub->pub_key_hash); 233 h_rsa.hash = bs_pub->pub_key_hash; 234 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 235 "Received RSA key %s (%s)\n", 236 GNUNET_h2s (&bs_pub->pub_key_hash), 237 section_name); 238 if (GNUNET_OK != 239 TALER_exchange_secmod_rsa_verify ( 240 &h_rsa, 241 section_name, 242 GNUNET_TIME_timestamp_ntoh (kan->anchor_time), 243 GNUNET_TIME_relative_ntoh (kan->duration_withdraw), 244 &kan->secm_pub, 245 &kan->secm_sig)) 246 { 247 GNUNET_break_op (0); 248 GNUNET_CRYPTO_blind_sign_pub_decref (bs_pub); 249 return GNUNET_SYSERR; 250 } 251 dh->dkc (dh->dkc_cls, 252 section_name, 253 GNUNET_TIME_timestamp_ntoh (kan->anchor_time), 254 GNUNET_TIME_relative_ntoh (kan->duration_withdraw), 255 &h_rsa, 256 bs_pub, 257 &kan->secm_pub, 258 &kan->secm_sig); 259 GNUNET_CRYPTO_blind_sign_pub_decref (bs_pub); 260 } 261 return GNUNET_OK; 262 } 263 264 265 /** 266 * Handle a #TALER_HELPER_RSA_MT_PURGE message from the helper. 267 * 268 * @param dh helper context 269 * @param hdr message that we received 270 * @return #GNUNET_OK on success 271 */ 272 static enum GNUNET_GenericReturnValue 273 handle_mt_purge (struct TALER_CRYPTO_RsaDenominationHelper *dh, 274 const struct GNUNET_MessageHeader *hdr) 275 { 276 const struct TALER_CRYPTO_RsaKeyPurgeNotification *pn 277 = (const struct TALER_CRYPTO_RsaKeyPurgeNotification *) hdr; 278 279 if (sizeof (*pn) != ntohs (hdr->size)) 280 { 281 GNUNET_break_op (0); 282 return GNUNET_SYSERR; 283 } 284 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 285 "Received revocation of denomination key %s\n", 286 GNUNET_h2s (&pn->h_rsa.hash)); 287 dh->dkc (dh->dkc_cls, 288 NULL, 289 GNUNET_TIME_UNIT_ZERO_TS, 290 GNUNET_TIME_UNIT_ZERO, 291 &pn->h_rsa, 292 NULL, 293 NULL, 294 NULL); 295 return GNUNET_OK; 296 } 297 298 299 void 300 TALER_CRYPTO_helper_rsa_poll (struct TALER_CRYPTO_RsaDenominationHelper *dh) 301 { 302 char buf[UINT16_MAX]; 303 size_t off = 0; 304 unsigned int retry_limit = 3; 305 const struct GNUNET_MessageHeader *hdr 306 = (const struct GNUNET_MessageHeader *) buf; 307 308 if (GNUNET_OK != 309 try_connect (dh)) 310 return; /* give up */ 311 while (1) 312 { 313 uint16_t msize; 314 ssize_t ret; 315 316 ret = recv (dh->sock, 317 buf + off, 318 sizeof (buf) - off, 319 (dh->synced && (0 == off)) 320 ? MSG_DONTWAIT 321 : 0); 322 if (ret < 0) 323 { 324 if (EINTR == errno) 325 continue; 326 if (EAGAIN == errno) 327 { 328 GNUNET_assert (dh->synced); 329 GNUNET_assert (0 == off); 330 break; 331 } 332 GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, 333 "recv"); 334 do_disconnect (dh); 335 if (0 == retry_limit) 336 return; /* give up */ 337 if (GNUNET_OK != 338 try_connect (dh)) 339 return; /* give up */ 340 retry_limit--; 341 continue; 342 } 343 if (0 == ret) 344 { 345 GNUNET_break (0 == off); 346 return; 347 } 348 off += ret; 349 more: 350 if (off < sizeof (struct GNUNET_MessageHeader)) 351 continue; 352 msize = ntohs (hdr->size); 353 if (off < msize) 354 continue; 355 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 356 "Received message of type %u and length %u\n", 357 (unsigned int) ntohs (hdr->type), 358 (unsigned int) msize); 359 switch (ntohs (hdr->type)) 360 { 361 case TALER_HELPER_RSA_MT_AVAIL: 362 if (GNUNET_OK != 363 handle_mt_avail (dh, 364 hdr)) 365 { 366 GNUNET_break_op (0); 367 do_disconnect (dh); 368 return; 369 } 370 break; 371 case TALER_HELPER_RSA_MT_PURGE: 372 if (GNUNET_OK != 373 handle_mt_purge (dh, 374 hdr)) 375 { 376 GNUNET_break_op (0); 377 do_disconnect (dh); 378 return; 379 } 380 break; 381 case TALER_HELPER_RSA_SYNCED: 382 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 383 "Now synchronized with RSA helper\n"); 384 dh->synced = true; 385 break; 386 default: 387 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 388 "Received unexpected message of type %d (len: %u)\n", 389 (unsigned int) ntohs (hdr->type), 390 (unsigned int) msize); 391 GNUNET_break_op (0); 392 do_disconnect (dh); 393 return; 394 } 395 memmove (buf, 396 &buf[msize], 397 off - msize); 398 off -= msize; 399 goto more; 400 } 401 } 402 403 404 enum TALER_ErrorCode 405 TALER_CRYPTO_helper_rsa_batch_sign ( 406 struct TALER_CRYPTO_RsaDenominationHelper *dh, 407 unsigned int rsrs_length, 408 const struct TALER_CRYPTO_RsaSignRequest rsrs[static rsrs_length], 409 struct TALER_BlindedDenominationSignature bss[static rsrs_length]) 410 { 411 enum TALER_ErrorCode ec = TALER_EC_INVALID; 412 unsigned int rpos; 413 unsigned int rend; 414 unsigned int wpos; 415 416 memset (bss, 417 0, 418 sizeof (*bss) * rsrs_length); 419 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 420 "Starting signature process\n"); 421 if (GNUNET_OK != 422 try_connect (dh)) 423 { 424 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 425 "Failed to connect to helper\n"); 426 return TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE; 427 } 428 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 429 "Requesting %u signatures\n", 430 rsrs_length); 431 rpos = 0; 432 rend = 0; 433 wpos = 0; 434 while (rpos < rsrs_length) 435 { 436 unsigned int mlen = sizeof (struct TALER_CRYPTO_BatchSignRequest); 437 438 while ( (rend < rsrs_length) && 439 (mlen 440 + sizeof (struct TALER_CRYPTO_SignRequest) 441 + rsrs[rend].msg_size < UINT16_MAX) ) 442 { 443 mlen += sizeof (struct TALER_CRYPTO_SignRequest) + rsrs[rend].msg_size; 444 rend++; 445 } 446 { 447 char obuf[mlen] GNUNET_ALIGN; 448 struct TALER_CRYPTO_BatchSignRequest *bsr 449 = (struct TALER_CRYPTO_BatchSignRequest *) obuf; 450 void *wbuf; 451 452 bsr->header.type = htons (TALER_HELPER_RSA_MT_REQ_BATCH_SIGN); 453 bsr->header.size = htons (mlen); 454 bsr->batch_size = htonl (rend - rpos); 455 wbuf = &bsr[1]; 456 for (unsigned int i = rpos; i<rend; i++) 457 { 458 struct TALER_CRYPTO_SignRequest *sr = wbuf; 459 const struct TALER_CRYPTO_RsaSignRequest *rsr = &rsrs[i]; 460 461 sr->header.type = htons (TALER_HELPER_RSA_MT_REQ_SIGN); 462 sr->header.size = htons (sizeof (*sr) + rsr->msg_size); 463 sr->reserved = htonl (0); 464 sr->h_rsa = *rsr->h_rsa; 465 GNUNET_memcpy (&sr[1], 466 rsr->msg, 467 rsr->msg_size); 468 wbuf += sizeof (*sr) + rsr->msg_size; 469 } 470 GNUNET_assert (wbuf == &obuf[mlen]); 471 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 472 "Sending batch request [%u-%u)\n", 473 rpos, 474 rend); 475 if (GNUNET_OK != 476 TALER_crypto_helper_send_all (dh->sock, 477 obuf, 478 sizeof (obuf))) 479 { 480 GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, 481 "send"); 482 do_disconnect (dh); 483 return TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE; 484 } 485 } 486 rpos = rend; 487 { 488 char buf[UINT16_MAX]; 489 size_t off = 0; 490 const struct GNUNET_MessageHeader *hdr 491 = (const struct GNUNET_MessageHeader *) buf; 492 bool finished = false; 493 494 while (1) 495 { 496 uint16_t msize; 497 ssize_t ret; 498 499 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 500 "Awaiting reply at %u (up to %u)\n", 501 wpos, 502 rend); 503 ret = recv (dh->sock, 504 &buf[off], 505 sizeof (buf) - off, 506 (finished && (0 == off)) 507 ? MSG_DONTWAIT 508 : 0); 509 if (ret < 0) 510 { 511 if (EINTR == errno) 512 continue; 513 if (EAGAIN == errno) 514 { 515 GNUNET_assert (finished); 516 GNUNET_assert (0 == off); 517 break; 518 } 519 GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, 520 "recv"); 521 do_disconnect (dh); 522 ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE; 523 break; 524 } 525 if (0 == ret) 526 { 527 GNUNET_break (0 == off); 528 if (! finished) 529 ec = TALER_EC_EXCHANGE_SIGNKEY_HELPER_BUG; 530 if (TALER_EC_NONE == ec) 531 break; 532 return ec; 533 } 534 off += ret; 535 more: 536 if (off < sizeof (struct GNUNET_MessageHeader)) 537 continue; 538 msize = ntohs (hdr->size); 539 if (off < msize) 540 continue; 541 switch (ntohs (hdr->type)) 542 { 543 case TALER_HELPER_RSA_MT_RES_SIGNATURE: 544 if (msize < sizeof (struct TALER_CRYPTO_SignResponse)) 545 { 546 GNUNET_break_op (0); 547 do_disconnect (dh); 548 return TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG; 549 } 550 if (finished) 551 { 552 GNUNET_break_op (0); 553 do_disconnect (dh); 554 return TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG; 555 } 556 { 557 const struct TALER_CRYPTO_SignResponse *sr = 558 (const struct TALER_CRYPTO_SignResponse *) buf; 559 struct GNUNET_CRYPTO_RsaSignature *rsa_signature; 560 struct GNUNET_CRYPTO_BlindedSignature *blind_sig; 561 562 rsa_signature = GNUNET_CRYPTO_rsa_signature_decode ( 563 &sr[1], 564 msize - sizeof (*sr)); 565 if (NULL == rsa_signature) 566 { 567 GNUNET_break_op (0); 568 do_disconnect (dh); 569 return TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG; 570 } 571 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 572 "Received %u signature\n", 573 wpos); 574 blind_sig = GNUNET_new (struct GNUNET_CRYPTO_BlindedSignature); 575 blind_sig->cipher = GNUNET_CRYPTO_BSA_RSA; 576 blind_sig->rc = 1; 577 blind_sig->details.blinded_rsa_signature = rsa_signature; 578 bss[wpos].blinded_sig = blind_sig; 579 wpos++; 580 if (wpos == rend) 581 { 582 if (TALER_EC_INVALID == ec) 583 ec = TALER_EC_NONE; 584 finished = true; 585 } 586 break; 587 } 588 case TALER_HELPER_RSA_MT_RES_SIGN_FAILURE: 589 if (msize != sizeof (struct TALER_CRYPTO_SignFailure)) 590 { 591 GNUNET_break_op (0); 592 do_disconnect (dh); 593 return TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG; 594 } 595 { 596 const struct TALER_CRYPTO_SignFailure *sf = 597 (const struct TALER_CRYPTO_SignFailure *) buf; 598 599 ec = (enum TALER_ErrorCode) (int) ntohl (sf->ec); 600 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 601 "Signing %u failed with status %d!\n", 602 wpos, 603 ec); 604 wpos++; 605 if (wpos == rend) 606 { 607 finished = true; 608 } 609 break; 610 } 611 case TALER_HELPER_RSA_MT_AVAIL: 612 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 613 "Received new key!\n"); 614 if (GNUNET_OK != 615 handle_mt_avail (dh, 616 hdr)) 617 { 618 GNUNET_break_op (0); 619 do_disconnect (dh); 620 return TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG; 621 } 622 break; /* while(1) loop ensures we recvfrom() again */ 623 case TALER_HELPER_RSA_MT_PURGE: 624 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 625 "Received revocation!\n"); 626 if (GNUNET_OK != 627 handle_mt_purge (dh, 628 hdr)) 629 { 630 GNUNET_break_op (0); 631 do_disconnect (dh); 632 return TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG; 633 } 634 break; /* while(1) loop ensures we recvfrom() again */ 635 case TALER_HELPER_RSA_SYNCED: 636 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 637 "Synchronized add odd time with RSA helper!\n"); 638 dh->synced = true; 639 break; 640 default: 641 GNUNET_break_op (0); 642 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 643 "Received unexpected message of type %u\n", 644 ntohs (hdr->type)); 645 do_disconnect (dh); 646 return TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG; 647 } 648 memmove (buf, 649 &buf[msize], 650 off - msize); 651 off -= msize; 652 goto more; 653 } /* while(1) */ 654 } /* scope */ 655 } /* while (rpos < rsrs_length) */ 656 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 657 "Existing with %u signatures and status %d\n", 658 wpos, 659 ec); 660 return ec; 661 } 662 663 664 void 665 TALER_CRYPTO_helper_rsa_revoke ( 666 struct TALER_CRYPTO_RsaDenominationHelper *dh, 667 const struct TALER_RsaPubHashP *h_rsa) 668 { 669 struct TALER_CRYPTO_RevokeRequest rr = { 670 .header.size = htons (sizeof (rr)), 671 .header.type = htons (TALER_HELPER_RSA_MT_REQ_REVOKE), 672 .h_rsa = *h_rsa 673 }; 674 675 if (GNUNET_OK != 676 try_connect (dh)) 677 return; /* give up */ 678 if (GNUNET_OK != 679 TALER_crypto_helper_send_all (dh->sock, 680 &rr, 681 sizeof (rr))) 682 { 683 GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, 684 "send"); 685 do_disconnect (dh); 686 return; 687 } 688 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 689 "Requested revocation of denomination key %s\n", 690 GNUNET_h2s (&h_rsa->hash)); 691 } 692 693 694 void 695 TALER_CRYPTO_helper_rsa_disconnect ( 696 struct TALER_CRYPTO_RsaDenominationHelper *dh) 697 { 698 if (-1 != dh->sock) 699 do_disconnect (dh); 700 GNUNET_free (dh); 701 } 702 703 704 /* end of crypto_helper_denom.c */