1 /* $NetBSD: openssldh_link.c,v 1.9 2015/09/03 07:33:34 christos Exp $ */ 2 3 /* 4 * Portions Copyright (C) 2004-2009, 2011-2014 Internet Systems Consortium, Inc. ("ISC") 5 * Portions Copyright (C) 1999-2002 Internet Software Consortium. 6 * 7 * Permission to use, copy, modify, and/or distribute this software for any 8 * purpose with or without fee is hereby granted, provided that the above 9 * copyright notice and this permission notice appear in all copies. 10 * 11 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC AND NETWORK ASSOCIATES DISCLAIMS 12 * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED 13 * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE 14 * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 17 * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 18 * 19 * Portions Copyright (C) 1995-2000 by Network Associates, Inc. 20 * 21 * Permission to use, copy, modify, and/or distribute this software for any 22 * purpose with or without fee is hereby granted, provided that the above 23 * copyright notice and this permission notice appear in all copies. 24 * 25 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC AND NETWORK ASSOCIATES DISCLAIMS 26 * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED 27 * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE 28 * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 29 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 30 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 31 * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 32 */ 33 34 /* 35 * Principal Author: Brian Wellington 36 * Id: openssldh_link.c,v 1.20 2011/01/11 23:47:13 tbox Exp 37 */ 38 39 #ifdef OPENSSL 40 41 #include <config.h> 42 43 #include <ctype.h> 44 45 #include <isc/mem.h> 46 #include <isc/string.h> 47 #include <isc/util.h> 48 49 #include <dst/result.h> 50 51 #include "dst_internal.h" 52 #include "dst_openssl.h" 53 #include "dst_parse.h" 54 55 #define PRIME768 "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088" \ 56 "A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25" \ 57 "F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A63A3620FFFFFFFFFFFFFFFF" 58 59 #define PRIME1024 "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08" \ 60 "8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF2" \ 61 "5F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406" \ 62 "B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF" 63 64 #define PRIME1536 "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" \ 65 "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" \ 66 "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" \ 67 "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" \ 68 "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" \ 69 "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" \ 70 "83655D23DCA3AD961C62F356208552BB9ED529077096966D" \ 71 "670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF" 72 73 74 static isc_result_t openssldh_todns(const dst_key_t *key, isc_buffer_t *data); 75 76 static BIGNUM bn2, bn768, bn1024, bn1536; 77 78 static isc_result_t 79 openssldh_computesecret(const dst_key_t *pub, const dst_key_t *priv, 80 isc_buffer_t *secret) 81 { 82 DH *dhpub, *dhpriv; 83 int ret; 84 isc_region_t r; 85 unsigned int len; 86 87 REQUIRE(pub->keydata.dh != NULL); 88 REQUIRE(priv->keydata.dh != NULL); 89 90 dhpub = pub->keydata.dh; 91 dhpriv = priv->keydata.dh; 92 93 len = DH_size(dhpriv); 94 isc_buffer_availableregion(secret, &r); 95 if (r.length < len) 96 return (ISC_R_NOSPACE); 97 ret = DH_compute_key(r.base, dhpub->pub_key, dhpriv); 98 if (ret <= 0) 99 return (dst__openssl_toresult2("DH_compute_key", 100 DST_R_COMPUTESECRETFAILURE)); 101 isc_buffer_add(secret, len); 102 return (ISC_R_SUCCESS); 103 } 104 105 static isc_boolean_t 106 openssldh_compare(const dst_key_t *key1, const dst_key_t *key2) { 107 int status; 108 DH *dh1, *dh2; 109 110 dh1 = key1->keydata.dh; 111 dh2 = key2->keydata.dh; 112 113 if (dh1 == NULL && dh2 == NULL) 114 return (ISC_TRUE); 115 else if (dh1 == NULL || dh2 == NULL) 116 return (ISC_FALSE); 117 118 status = BN_cmp(dh1->p, dh2->p) || 119 BN_cmp(dh1->g, dh2->g) || 120 BN_cmp(dh1->pub_key, dh2->pub_key); 121 122 if (status != 0) 123 return (ISC_FALSE); 124 125 if (dh1->priv_key != NULL || dh2->priv_key != NULL) { 126 if (dh1->priv_key == NULL || dh2->priv_key == NULL) 127 return (ISC_FALSE); 128 if (BN_cmp(dh1->priv_key, dh2->priv_key) != 0) 129 return (ISC_FALSE); 130 } 131 return (ISC_TRUE); 132 } 133 134 static isc_boolean_t 135 openssldh_paramcompare(const dst_key_t *key1, const dst_key_t *key2) { 136 int status; 137 DH *dh1, *dh2; 138 139 dh1 = key1->keydata.dh; 140 dh2 = key2->keydata.dh; 141 142 if (dh1 == NULL && dh2 == NULL) 143 return (ISC_TRUE); 144 else if (dh1 == NULL || dh2 == NULL) 145 return (ISC_FALSE); 146 147 status = BN_cmp(dh1->p, dh2->p) || 148 BN_cmp(dh1->g, dh2->g); 149 150 if (status != 0) 151 return (ISC_FALSE); 152 return (ISC_TRUE); 153 } 154 155 #if OPENSSL_VERSION_NUMBER > 0x00908000L 156 static int 157 progress_cb(int p, int n, BN_GENCB *cb) 158 { 159 union { 160 void *dptr; 161 void (*fptr)(int); 162 } u; 163 164 UNUSED(n); 165 166 u.dptr = cb->arg; 167 if (u.fptr != NULL) 168 u.fptr(p); 169 return (1); 170 } 171 #endif 172 173 static isc_result_t 174 openssldh_generate(dst_key_t *key, int generator, void (*callback)(int)) { 175 DH *dh = NULL; 176 #if OPENSSL_VERSION_NUMBER > 0x00908000L 177 BN_GENCB cb; 178 union { 179 void *dptr; 180 void (*fptr)(int); 181 } u; 182 #else 183 184 UNUSED(callback); 185 #endif 186 187 if (generator == 0) { 188 if (key->key_size == 768 || 189 key->key_size == 1024 || 190 key->key_size == 1536) 191 { 192 dh = DH_new(); 193 if (dh == NULL) 194 return (dst__openssl_toresult(ISC_R_NOMEMORY)); 195 if (key->key_size == 768) 196 dh->p = &bn768; 197 else if (key->key_size == 1024) 198 dh->p = &bn1024; 199 else 200 dh->p = &bn1536; 201 dh->g = &bn2; 202 } else 203 generator = 2; 204 } 205 206 if (generator != 0) { 207 #if OPENSSL_VERSION_NUMBER > 0x00908000L 208 dh = DH_new(); 209 if (dh == NULL) 210 return (dst__openssl_toresult(ISC_R_NOMEMORY)); 211 212 if (callback == NULL) { 213 BN_GENCB_set_old(&cb, NULL, NULL); 214 } else { 215 u.fptr = callback; 216 BN_GENCB_set(&cb, &progress_cb, u.dptr); 217 } 218 219 if (!DH_generate_parameters_ex(dh, key->key_size, generator, 220 &cb)) { 221 DH_free(dh); 222 return (dst__openssl_toresult2( 223 "DH_generate_parameters_ex", 224 DST_R_OPENSSLFAILURE)); 225 } 226 #else 227 dh = DH_generate_parameters(key->key_size, generator, 228 NULL, NULL); 229 #endif 230 } 231 232 if (dh == NULL) 233 return (dst__openssl_toresult2("DH_generate_parameters", 234 DST_R_OPENSSLFAILURE)); 235 236 if (DH_generate_key(dh) == 0) { 237 DH_free(dh); 238 return (dst__openssl_toresult2("DH_generate_key", 239 DST_R_OPENSSLFAILURE)); 240 } 241 dh->flags &= ~DH_FLAG_CACHE_MONT_P; 242 243 key->keydata.dh = dh; 244 245 return (ISC_R_SUCCESS); 246 } 247 248 static isc_boolean_t 249 openssldh_isprivate(const dst_key_t *key) { 250 DH *dh = key->keydata.dh; 251 return (ISC_TF(dh != NULL && dh->priv_key != NULL)); 252 } 253 254 static void 255 openssldh_destroy(dst_key_t *key) { 256 DH *dh = key->keydata.dh; 257 258 if (dh == NULL) 259 return; 260 261 if (dh->p == &bn768 || dh->p == &bn1024 || dh->p == &bn1536) 262 dh->p = NULL; 263 if (dh->g == &bn2) 264 dh->g = NULL; 265 DH_free(dh); 266 key->keydata.dh = NULL; 267 } 268 269 static void 270 uint16_toregion(isc_uint16_t val, isc_region_t *region) { 271 *region->base = (val & 0xff00) >> 8; 272 isc_region_consume(region, 1); 273 *region->base = (val & 0x00ff); 274 isc_region_consume(region, 1); 275 } 276 277 static isc_uint16_t 278 uint16_fromregion(isc_region_t *region) { 279 isc_uint16_t val; 280 unsigned char *cp = region->base; 281 282 val = ((unsigned int)(cp[0])) << 8; 283 val |= ((unsigned int)(cp[1])); 284 285 isc_region_consume(region, 2); 286 287 return (val); 288 } 289 290 static isc_result_t 291 openssldh_todns(const dst_key_t *key, isc_buffer_t *data) { 292 DH *dh; 293 isc_region_t r; 294 isc_uint16_t dnslen, plen, glen, publen; 295 296 REQUIRE(key->keydata.dh != NULL); 297 298 dh = key->keydata.dh; 299 300 isc_buffer_availableregion(data, &r); 301 302 if (dh->g == &bn2 && 303 (dh->p == &bn768 || dh->p == &bn1024 || dh->p == &bn1536)) { 304 plen = 1; 305 glen = 0; 306 } 307 else { 308 plen = BN_num_bytes(dh->p); 309 glen = BN_num_bytes(dh->g); 310 } 311 publen = BN_num_bytes(dh->pub_key); 312 dnslen = plen + glen + publen + 6; 313 if (r.length < (unsigned int) dnslen) 314 return (ISC_R_NOSPACE); 315 316 uint16_toregion(plen, &r); 317 if (plen == 1) { 318 if (dh->p == &bn768) 319 *r.base = 1; 320 else if (dh->p == &bn1024) 321 *r.base = 2; 322 else 323 *r.base = 3; 324 } 325 else 326 BN_bn2bin(dh->p, r.base); 327 isc_region_consume(&r, plen); 328 329 uint16_toregion(glen, &r); 330 if (glen > 0) 331 BN_bn2bin(dh->g, r.base); 332 isc_region_consume(&r, glen); 333 334 uint16_toregion(publen, &r); 335 BN_bn2bin(dh->pub_key, r.base); 336 isc_region_consume(&r, publen); 337 338 isc_buffer_add(data, dnslen); 339 340 return (ISC_R_SUCCESS); 341 } 342 343 static isc_result_t 344 openssldh_fromdns(dst_key_t *key, isc_buffer_t *data) { 345 DH *dh; 346 isc_region_t r; 347 isc_uint16_t plen, glen, publen; 348 int special = 0; 349 350 isc_buffer_remainingregion(data, &r); 351 if (r.length == 0) 352 return (ISC_R_SUCCESS); 353 354 dh = DH_new(); 355 if (dh == NULL) 356 return (dst__openssl_toresult(ISC_R_NOMEMORY)); 357 dh->flags &= ~DH_FLAG_CACHE_MONT_P; 358 359 /* 360 * Read the prime length. 1 & 2 are table entries, > 16 means a 361 * prime follows, otherwise an error. 362 */ 363 if (r.length < 2) { 364 DH_free(dh); 365 return (DST_R_INVALIDPUBLICKEY); 366 } 367 plen = uint16_fromregion(&r); 368 if (plen < 16 && plen != 1 && plen != 2) { 369 DH_free(dh); 370 return (DST_R_INVALIDPUBLICKEY); 371 } 372 if (r.length < plen) { 373 DH_free(dh); 374 return (DST_R_INVALIDPUBLICKEY); 375 } 376 if (plen == 1 || plen == 2) { 377 if (plen == 1) { 378 special = *r.base; 379 isc_region_consume(&r, 1); 380 } else { 381 special = uint16_fromregion(&r); 382 } 383 switch (special) { 384 case 1: 385 dh->p = &bn768; 386 break; 387 case 2: 388 dh->p = &bn1024; 389 break; 390 case 3: 391 dh->p = &bn1536; 392 break; 393 default: 394 DH_free(dh); 395 return (DST_R_INVALIDPUBLICKEY); 396 } 397 } else { 398 dh->p = BN_bin2bn(r.base, plen, NULL); 399 isc_region_consume(&r, plen); 400 } 401 402 /* 403 * Read the generator length. This should be 0 if the prime was 404 * special, but it might not be. If it's 0 and the prime is not 405 * special, we have a problem. 406 */ 407 if (r.length < 2) { 408 DH_free(dh); 409 return (DST_R_INVALIDPUBLICKEY); 410 } 411 glen = uint16_fromregion(&r); 412 if (r.length < glen) { 413 DH_free(dh); 414 return (DST_R_INVALIDPUBLICKEY); 415 } 416 if (special != 0) { 417 if (glen == 0) 418 dh->g = &bn2; 419 else { 420 dh->g = BN_bin2bn(r.base, glen, NULL); 421 if (BN_cmp(dh->g, &bn2) == 0) { 422 BN_free(dh->g); 423 dh->g = &bn2; 424 } 425 else { 426 DH_free(dh); 427 return (DST_R_INVALIDPUBLICKEY); 428 } 429 } 430 } else { 431 if (glen == 0) { 432 DH_free(dh); 433 return (DST_R_INVALIDPUBLICKEY); 434 } 435 dh->g = BN_bin2bn(r.base, glen, NULL); 436 } 437 isc_region_consume(&r, glen); 438 439 if (r.length < 2) { 440 DH_free(dh); 441 return (DST_R_INVALIDPUBLICKEY); 442 } 443 publen = uint16_fromregion(&r); 444 if (r.length < publen) { 445 DH_free(dh); 446 return (DST_R_INVALIDPUBLICKEY); 447 } 448 dh->pub_key = BN_bin2bn(r.base, publen, NULL); 449 isc_region_consume(&r, publen); 450 451 key->key_size = BN_num_bits(dh->p); 452 453 isc_buffer_forward(data, plen + glen + publen + 6); 454 455 key->keydata.dh = dh; 456 457 return (ISC_R_SUCCESS); 458 } 459 460 static isc_result_t 461 openssldh_tofile(const dst_key_t *key, const char *directory) { 462 int i; 463 DH *dh; 464 dst_private_t priv; 465 unsigned char *bufs[4]; 466 isc_result_t result; 467 468 if (key->keydata.dh == NULL) 469 return (DST_R_NULLKEY); 470 471 if (key->external) 472 return (DST_R_EXTERNALKEY); 473 474 dh = key->keydata.dh; 475 476 memset(bufs, 0, sizeof(bufs)); 477 for (i = 0; i < 4; i++) { 478 bufs[i] = isc_mem_get(key->mctx, BN_num_bytes(dh->p)); 479 if (bufs[i] == NULL) { 480 result = ISC_R_NOMEMORY; 481 goto fail; 482 } 483 } 484 485 i = 0; 486 487 priv.elements[i].tag = TAG_DH_PRIME; 488 priv.elements[i].length = BN_num_bytes(dh->p); 489 BN_bn2bin(dh->p, bufs[i]); 490 priv.elements[i].data = bufs[i]; 491 i++; 492 493 priv.elements[i].tag = TAG_DH_GENERATOR; 494 priv.elements[i].length = BN_num_bytes(dh->g); 495 BN_bn2bin(dh->g, bufs[i]); 496 priv.elements[i].data = bufs[i]; 497 i++; 498 499 priv.elements[i].tag = TAG_DH_PRIVATE; 500 priv.elements[i].length = BN_num_bytes(dh->priv_key); 501 BN_bn2bin(dh->priv_key, bufs[i]); 502 priv.elements[i].data = bufs[i]; 503 i++; 504 505 priv.elements[i].tag = TAG_DH_PUBLIC; 506 priv.elements[i].length = BN_num_bytes(dh->pub_key); 507 BN_bn2bin(dh->pub_key, bufs[i]); 508 priv.elements[i].data = bufs[i]; 509 i++; 510 511 priv.nelements = i; 512 result = dst__privstruct_writefile(key, &priv, directory); 513 fail: 514 for (i = 0; i < 4; i++) { 515 if (bufs[i] == NULL) 516 break; 517 isc_mem_put(key->mctx, bufs[i], BN_num_bytes(dh->p)); 518 } 519 return (result); 520 } 521 522 static isc_result_t 523 openssldh_parse(dst_key_t *key, isc_lex_t *lexer, dst_key_t *pub) { 524 dst_private_t priv; 525 isc_result_t ret; 526 int i; 527 DH *dh = NULL; 528 isc_mem_t *mctx; 529 #define DST_RET(a) {ret = a; goto err;} 530 531 UNUSED(pub); 532 mctx = key->mctx; 533 534 /* read private key file */ 535 ret = dst__privstruct_parse(key, DST_ALG_DH, lexer, mctx, &priv); 536 if (ret != ISC_R_SUCCESS) 537 return (ret); 538 539 if (key->external) 540 DST_RET(DST_R_EXTERNALKEY); 541 542 dh = DH_new(); 543 if (dh == NULL) 544 DST_RET(ISC_R_NOMEMORY); 545 dh->flags &= ~DH_FLAG_CACHE_MONT_P; 546 key->keydata.dh = dh; 547 548 for (i = 0; i < priv.nelements; i++) { 549 BIGNUM *bn; 550 bn = BN_bin2bn(priv.elements[i].data, 551 priv.elements[i].length, NULL); 552 if (bn == NULL) 553 DST_RET(ISC_R_NOMEMORY); 554 555 switch (priv.elements[i].tag) { 556 case TAG_DH_PRIME: 557 dh->p = bn; 558 break; 559 case TAG_DH_GENERATOR: 560 dh->g = bn; 561 break; 562 case TAG_DH_PRIVATE: 563 dh->priv_key = bn; 564 break; 565 case TAG_DH_PUBLIC: 566 dh->pub_key = bn; 567 break; 568 } 569 } 570 dst__privstruct_free(&priv, mctx); 571 572 key->key_size = BN_num_bits(dh->p); 573 574 if ((key->key_size == 768 || 575 key->key_size == 1024 || 576 key->key_size == 1536) && 577 BN_cmp(dh->g, &bn2) == 0) 578 { 579 if (key->key_size == 768 && BN_cmp(dh->p, &bn768) == 0) { 580 BN_free(dh->p); 581 BN_free(dh->g); 582 dh->p = &bn768; 583 dh->g = &bn2; 584 } else if (key->key_size == 1024 && 585 BN_cmp(dh->p, &bn1024) == 0) { 586 BN_free(dh->p); 587 BN_free(dh->g); 588 dh->p = &bn1024; 589 dh->g = &bn2; 590 } else if (key->key_size == 1536 && 591 BN_cmp(dh->p, &bn1536) == 0) { 592 BN_free(dh->p); 593 BN_free(dh->g); 594 dh->p = &bn1536; 595 dh->g = &bn2; 596 } 597 } 598 599 return (ISC_R_SUCCESS); 600 601 err: 602 openssldh_destroy(key); 603 dst__privstruct_free(&priv, mctx); 604 memset(&priv, 0, sizeof(priv)); 605 return (ret); 606 } 607 608 static void 609 BN_fromhex(BIGNUM *b, const char *str) { 610 static const char hexdigits[] = "0123456789abcdef"; 611 unsigned char data[512]; 612 unsigned int i; 613 BIGNUM *out; 614 615 RUNTIME_CHECK(strlen(str) < 1024U && strlen(str) % 2 == 0U); 616 for (i = 0; i < strlen(str); i += 2) { 617 char *s; 618 unsigned int high, low; 619 620 s = strchr(hexdigits, tolower((unsigned char)str[i])); 621 RUNTIME_CHECK(s != NULL); 622 high = (unsigned int)(s - hexdigits); 623 624 s = strchr(hexdigits, tolower((unsigned char)str[i + 1])); 625 RUNTIME_CHECK(s != NULL); 626 low = (unsigned int)(s - hexdigits); 627 628 data[i/2] = (unsigned char)((high << 4) + low); 629 } 630 out = BN_bin2bn(data, strlen(str)/2, b); 631 RUNTIME_CHECK(out != NULL); 632 } 633 634 static void 635 openssldh_cleanup(void) { 636 BN_free(&bn2); 637 BN_free(&bn768); 638 BN_free(&bn1024); 639 BN_free(&bn1536); 640 } 641 642 static dst_func_t openssldh_functions = { 643 NULL, /*%< createctx */ 644 NULL, /*%< createctx2 */ 645 NULL, /*%< destroyctx */ 646 NULL, /*%< adddata */ 647 NULL, /*%< openssldh_sign */ 648 NULL, /*%< openssldh_verify */ 649 NULL, /*%< openssldh_verify2 */ 650 openssldh_computesecret, 651 openssldh_compare, 652 openssldh_paramcompare, 653 openssldh_generate, 654 openssldh_isprivate, 655 openssldh_destroy, 656 openssldh_todns, 657 openssldh_fromdns, 658 openssldh_tofile, 659 openssldh_parse, 660 openssldh_cleanup, 661 NULL, /*%< fromlabel */ 662 NULL, /*%< dump */ 663 NULL, /*%< restore */ 664 }; 665 666 isc_result_t 667 dst__openssldh_init(dst_func_t **funcp) { 668 REQUIRE(funcp != NULL); 669 if (*funcp == NULL) { 670 BN_init(&bn2); 671 BN_init(&bn768); 672 BN_init(&bn1024); 673 BN_init(&bn1536); 674 BN_set_word(&bn2, 2); 675 BN_fromhex(&bn768, PRIME768); 676 BN_fromhex(&bn1024, PRIME1024); 677 BN_fromhex(&bn1536, PRIME1536); 678 *funcp = &openssldh_functions; 679 } 680 return (ISC_R_SUCCESS); 681 } 682 683 #else /* OPENSSL */ 684 685 #include <isc/util.h> 686 687 EMPTY_TRANSLATION_UNIT 688 689 #endif /* OPENSSL */ 690 /*! \file */ 691