1 /* $NetBSD: dst_parse.c,v 1.12 2025/01/26 16:25:22 christos Exp $ */ 2 3 /* 4 * Copyright (C) Internet Systems Consortium, Inc. ("ISC") 5 * 6 * SPDX-License-Identifier: MPL-2.0 AND ISC 7 * 8 * This Source Code Form is subject to the terms of the Mozilla Public 9 * License, v. 2.0. If a copy of the MPL was not distributed with this 10 * file, you can obtain one at https://mozilla.org/MPL/2.0/. 11 * 12 * See the COPYRIGHT file distributed with this work for additional 13 * information regarding copyright ownership. 14 */ 15 16 /* 17 * Copyright (C) Network Associates, Inc. 18 * 19 * Permission to use, copy, modify, and/or distribute this software for any 20 * purpose with or without fee is hereby granted, provided that the above 21 * copyright notice and this permission notice appear in all copies. 22 * 23 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC AND NETWORK ASSOCIATES DISCLAIMS 24 * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED 25 * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE 26 * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 27 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 28 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 29 * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 30 */ 31 32 #include "dst_parse.h" 33 #include <inttypes.h> 34 #include <stdbool.h> 35 #include <unistd.h> 36 37 #include <isc/base64.h> 38 #include <isc/dir.h> 39 #include <isc/file.h> 40 #include <isc/lex.h> 41 #include <isc/mem.h> 42 #include <isc/stdtime.h> 43 #include <isc/string.h> 44 #include <isc/util.h> 45 46 #include <dns/log.h> 47 #include <dns/time.h> 48 49 #include "dst_internal.h" 50 #include "isc/result.h" 51 52 #define DST_AS_STR(t) ((t).value.as_textregion.base) 53 54 #define PRIVATE_KEY_STR "Private-key-format:" 55 #define ALGORITHM_STR "Algorithm:" 56 57 #define TIMING_NTAGS (DST_MAX_TIMES + 1) 58 static const char *timetags[TIMING_NTAGS] = { 59 "Created:", "Publish:", "Activate:", "Revoke:", 60 "Inactive:", "Delete:", "DSPublish:", "SyncPublish:", 61 "SyncDelete:", NULL, NULL, NULL, 62 NULL 63 }; 64 65 #define NUMERIC_NTAGS (DST_MAX_NUMERIC + 1) 66 static const char *numerictags[NUMERIC_NTAGS] = { 67 "Predecessor:", "Successor:", "MaxTTL:", "RollPeriod:", NULL, NULL, NULL 68 }; 69 70 struct parse_map { 71 const int value; 72 const char *tag; 73 }; 74 75 static struct parse_map map[] = { { TAG_RSA_MODULUS, "Modulus:" }, 76 { TAG_RSA_PUBLICEXPONENT, "PublicExponent:" }, 77 { TAG_RSA_PRIVATEEXPONENT, "PrivateExponent" 78 ":" }, 79 { TAG_RSA_PRIME1, "Prime1:" }, 80 { TAG_RSA_PRIME2, "Prime2:" }, 81 { TAG_RSA_EXPONENT1, "Exponent1:" }, 82 { TAG_RSA_EXPONENT2, "Exponent2:" }, 83 { TAG_RSA_COEFFICIENT, "Coefficient:" }, 84 { TAG_RSA_ENGINE, "Engine:" }, 85 { TAG_RSA_LABEL, "Label:" }, 86 87 { TAG_ECDSA_PRIVATEKEY, "PrivateKey:" }, 88 { TAG_ECDSA_ENGINE, "Engine:" }, 89 { TAG_ECDSA_LABEL, "Label:" }, 90 91 { TAG_EDDSA_PRIVATEKEY, "PrivateKey:" }, 92 { TAG_EDDSA_ENGINE, "Engine:" }, 93 { TAG_EDDSA_LABEL, "Label:" }, 94 95 { TAG_HMACMD5_KEY, "Key:" }, 96 { TAG_HMACMD5_BITS, "Bits:" }, 97 98 { TAG_HMACSHA1_KEY, "Key:" }, 99 { TAG_HMACSHA1_BITS, "Bits:" }, 100 101 { TAG_HMACSHA224_KEY, "Key:" }, 102 { TAG_HMACSHA224_BITS, "Bits:" }, 103 104 { TAG_HMACSHA256_KEY, "Key:" }, 105 { TAG_HMACSHA256_BITS, "Bits:" }, 106 107 { TAG_HMACSHA384_KEY, "Key:" }, 108 { TAG_HMACSHA384_BITS, "Bits:" }, 109 110 { TAG_HMACSHA512_KEY, "Key:" }, 111 { TAG_HMACSHA512_BITS, "Bits:" }, 112 113 { 0, NULL } }; 114 115 static int 116 find_value(const char *s, const unsigned int alg) { 117 int i; 118 119 for (i = 0; map[i].tag != NULL; i++) { 120 if (strcasecmp(s, map[i].tag) == 0 && 121 (TAG_ALG(map[i].value) == alg)) 122 { 123 return map[i].value; 124 } 125 } 126 return -1; 127 } 128 129 static const char * 130 find_tag(const int value) { 131 int i; 132 133 for (i = 0;; i++) { 134 if (map[i].tag == NULL) { 135 return NULL; 136 } else if (value == map[i].value) { 137 return map[i].tag; 138 } 139 } 140 } 141 142 static int 143 find_metadata(const char *s, const char *tags[], int ntags) { 144 int i; 145 146 for (i = 0; i < ntags; i++) { 147 if (tags[i] != NULL && strcasecmp(s, tags[i]) == 0) { 148 return i; 149 } 150 } 151 152 return -1; 153 } 154 155 static int 156 find_timedata(const char *s) { 157 return find_metadata(s, timetags, TIMING_NTAGS); 158 } 159 160 static int 161 find_numericdata(const char *s) { 162 return find_metadata(s, numerictags, NUMERIC_NTAGS); 163 } 164 165 static int 166 check_rsa(const dst_private_t *priv, bool external) { 167 int i, j; 168 bool have[RSA_NTAGS]; 169 bool ok; 170 unsigned int mask; 171 172 if (external) { 173 return (priv->nelements == 0) ? 0 : -1; 174 } 175 176 for (i = 0; i < RSA_NTAGS; i++) { 177 have[i] = false; 178 } 179 180 for (j = 0; j < priv->nelements; j++) { 181 for (i = 0; i < RSA_NTAGS; i++) { 182 if (priv->elements[j].tag == TAG(DST_ALG_RSA, i)) { 183 break; 184 } 185 } 186 if (i == RSA_NTAGS) { 187 return -1; 188 } 189 have[i] = true; 190 } 191 192 mask = (1ULL << TAG_SHIFT) - 1; 193 194 if (have[TAG_RSA_LABEL & mask]) { 195 ok = have[TAG_RSA_MODULUS & mask] && 196 have[TAG_RSA_PUBLICEXPONENT & mask]; 197 } else { 198 ok = have[TAG_RSA_MODULUS & mask] && 199 have[TAG_RSA_PUBLICEXPONENT & mask] && 200 have[TAG_RSA_PRIVATEEXPONENT & mask] && 201 have[TAG_RSA_PRIME1 & mask] && 202 have[TAG_RSA_PRIME2 & mask] && 203 have[TAG_RSA_EXPONENT1 & mask] && 204 have[TAG_RSA_EXPONENT2 & mask] && 205 have[TAG_RSA_COEFFICIENT & mask]; 206 } 207 return ok ? 0 : -1; 208 } 209 210 static int 211 check_ecdsa(const dst_private_t *priv, bool external) { 212 int i, j; 213 bool have[ECDSA_NTAGS]; 214 bool ok; 215 unsigned int mask; 216 217 if (external) { 218 return (priv->nelements == 0) ? 0 : -1; 219 } 220 221 for (i = 0; i < ECDSA_NTAGS; i++) { 222 have[i] = false; 223 } 224 for (j = 0; j < priv->nelements; j++) { 225 for (i = 0; i < ECDSA_NTAGS; i++) { 226 if (priv->elements[j].tag == TAG(DST_ALG_ECDSA256, i)) { 227 break; 228 } 229 } 230 if (i == ECDSA_NTAGS) { 231 return -1; 232 } 233 have[i] = true; 234 } 235 236 mask = (1ULL << TAG_SHIFT) - 1; 237 238 ok = have[TAG_ECDSA_LABEL & mask] || have[TAG_ECDSA_PRIVATEKEY & mask]; 239 240 return ok ? 0 : -1; 241 } 242 243 static int 244 check_eddsa(const dst_private_t *priv, bool external) { 245 int i, j; 246 bool have[EDDSA_NTAGS]; 247 bool ok; 248 unsigned int mask; 249 250 if (external) { 251 return (priv->nelements == 0) ? 0 : -1; 252 } 253 254 for (i = 0; i < EDDSA_NTAGS; i++) { 255 have[i] = false; 256 } 257 for (j = 0; j < priv->nelements; j++) { 258 for (i = 0; i < EDDSA_NTAGS; i++) { 259 if (priv->elements[j].tag == TAG(DST_ALG_ED25519, i)) { 260 break; 261 } 262 } 263 if (i == EDDSA_NTAGS) { 264 return -1; 265 } 266 have[i] = true; 267 } 268 269 mask = (1ULL << TAG_SHIFT) - 1; 270 271 ok = have[TAG_EDDSA_LABEL & mask] || have[TAG_EDDSA_PRIVATEKEY & mask]; 272 273 return ok ? 0 : -1; 274 } 275 276 static int 277 check_hmac_md5(const dst_private_t *priv, bool old) { 278 int i, j; 279 280 if (priv->nelements != HMACMD5_NTAGS) { 281 /* 282 * If this is a good old format and we are accepting 283 * the old format return success. 284 */ 285 if (old && priv->nelements == OLD_HMACMD5_NTAGS && 286 priv->elements[0].tag == TAG_HMACMD5_KEY) 287 { 288 return 0; 289 } 290 return -1; 291 } 292 /* 293 * We must be new format at this point. 294 */ 295 for (i = 0; i < HMACMD5_NTAGS; i++) { 296 for (j = 0; j < priv->nelements; j++) { 297 if (priv->elements[j].tag == TAG(DST_ALG_HMACMD5, i)) { 298 break; 299 } 300 } 301 if (j == priv->nelements) { 302 return -1; 303 } 304 } 305 return 0; 306 } 307 308 static int 309 check_hmac_sha(const dst_private_t *priv, unsigned int ntags, 310 unsigned int alg) { 311 unsigned int i, j; 312 if (priv->nelements != ntags) { 313 return -1; 314 } 315 for (i = 0; i < ntags; i++) { 316 for (j = 0; j < priv->nelements; j++) { 317 if (priv->elements[j].tag == TAG(alg, i)) { 318 break; 319 } 320 } 321 if (j == priv->nelements) { 322 return -1; 323 } 324 } 325 return 0; 326 } 327 328 static int 329 check_data(const dst_private_t *priv, const unsigned int alg, bool old, 330 bool external) { 331 switch (alg) { 332 case DST_ALG_RSA: 333 case DST_ALG_RSASHA1: 334 case DST_ALG_NSEC3RSASHA1: 335 case DST_ALG_RSASHA256: 336 case DST_ALG_RSASHA512: 337 return check_rsa(priv, external); 338 case DST_ALG_ECDSA256: 339 case DST_ALG_ECDSA384: 340 return check_ecdsa(priv, external); 341 case DST_ALG_ED25519: 342 case DST_ALG_ED448: 343 return check_eddsa(priv, external); 344 case DST_ALG_HMACMD5: 345 return check_hmac_md5(priv, old); 346 case DST_ALG_HMACSHA1: 347 return check_hmac_sha(priv, HMACSHA1_NTAGS, alg); 348 case DST_ALG_HMACSHA224: 349 return check_hmac_sha(priv, HMACSHA224_NTAGS, alg); 350 case DST_ALG_HMACSHA256: 351 return check_hmac_sha(priv, HMACSHA256_NTAGS, alg); 352 case DST_ALG_HMACSHA384: 353 return check_hmac_sha(priv, HMACSHA384_NTAGS, alg); 354 case DST_ALG_HMACSHA512: 355 return check_hmac_sha(priv, HMACSHA512_NTAGS, alg); 356 default: 357 return DST_R_UNSUPPORTEDALG; 358 } 359 } 360 361 void 362 dst__privstruct_free(dst_private_t *priv, isc_mem_t *mctx) { 363 int i; 364 365 if (priv == NULL) { 366 return; 367 } 368 for (i = 0; i < priv->nelements; i++) { 369 if (priv->elements[i].data == NULL) { 370 continue; 371 } 372 memset(priv->elements[i].data, 0, MAXFIELDSIZE); 373 isc_mem_put(mctx, priv->elements[i].data, MAXFIELDSIZE); 374 } 375 priv->nelements = 0; 376 } 377 378 isc_result_t 379 dst__privstruct_parse(dst_key_t *key, unsigned int alg, isc_lex_t *lex, 380 isc_mem_t *mctx, dst_private_t *priv) { 381 int n = 0, major, minor, check; 382 isc_buffer_t b; 383 isc_token_t token; 384 unsigned char *data = NULL; 385 unsigned int opt = ISC_LEXOPT_EOL; 386 isc_stdtime_t when; 387 isc_result_t ret; 388 bool external = false; 389 390 REQUIRE(priv != NULL); 391 392 priv->nelements = 0; 393 memset(priv->elements, 0, sizeof(priv->elements)); 394 395 #define NEXTTOKEN(lex, opt, token) \ 396 do { \ 397 ret = isc_lex_gettoken(lex, opt, token); \ 398 if (ret != ISC_R_SUCCESS) \ 399 goto fail; \ 400 } while (0) 401 402 #define READLINE(lex, opt, token) \ 403 do { \ 404 ret = isc_lex_gettoken(lex, opt, token); \ 405 if (ret == ISC_R_EOF) \ 406 break; \ 407 else if (ret != ISC_R_SUCCESS) \ 408 goto fail; \ 409 } while ((*token).type != isc_tokentype_eol) 410 411 /* 412 * Read the description line. 413 */ 414 NEXTTOKEN(lex, opt, &token); 415 if (token.type != isc_tokentype_string || 416 strcmp(DST_AS_STR(token), PRIVATE_KEY_STR) != 0) 417 { 418 ret = DST_R_INVALIDPRIVATEKEY; 419 goto fail; 420 } 421 422 NEXTTOKEN(lex, opt, &token); 423 if (token.type != isc_tokentype_string || (DST_AS_STR(token))[0] != 'v') 424 { 425 ret = DST_R_INVALIDPRIVATEKEY; 426 goto fail; 427 } 428 if (sscanf(DST_AS_STR(token), "v%d.%d", &major, &minor) != 2) { 429 ret = DST_R_INVALIDPRIVATEKEY; 430 goto fail; 431 } 432 433 if (major > DST_MAJOR_VERSION) { 434 ret = DST_R_INVALIDPRIVATEKEY; 435 goto fail; 436 } 437 438 /* 439 * Store the private key format version number 440 */ 441 dst_key_setprivateformat(key, major, minor); 442 443 READLINE(lex, opt, &token); 444 445 /* 446 * Read the algorithm line. 447 */ 448 NEXTTOKEN(lex, opt, &token); 449 if (token.type != isc_tokentype_string || 450 strcmp(DST_AS_STR(token), ALGORITHM_STR) != 0) 451 { 452 ret = DST_R_INVALIDPRIVATEKEY; 453 goto fail; 454 } 455 456 NEXTTOKEN(lex, opt | ISC_LEXOPT_NUMBER, &token); 457 if (token.type != isc_tokentype_number || 458 token.value.as_ulong != (unsigned long)dst_key_alg(key)) 459 { 460 ret = DST_R_INVALIDPRIVATEKEY; 461 goto fail; 462 } 463 464 READLINE(lex, opt, &token); 465 466 /* 467 * Read the key data. 468 */ 469 for (n = 0; n < MAXFIELDS; n++) { 470 int tag; 471 isc_region_t r; 472 do { 473 ret = isc_lex_gettoken(lex, opt, &token); 474 if (ret == ISC_R_EOF) { 475 goto done; 476 } 477 if (ret != ISC_R_SUCCESS) { 478 goto fail; 479 } 480 } while (token.type == isc_tokentype_eol); 481 482 if (token.type != isc_tokentype_string) { 483 ret = DST_R_INVALIDPRIVATEKEY; 484 goto fail; 485 } 486 487 if (strcmp(DST_AS_STR(token), "External:") == 0) { 488 external = true; 489 goto next; 490 } 491 492 /* Numeric metadata */ 493 tag = find_numericdata(DST_AS_STR(token)); 494 if (tag >= 0) { 495 INSIST(tag < NUMERIC_NTAGS); 496 497 NEXTTOKEN(lex, opt | ISC_LEXOPT_NUMBER, &token); 498 if (token.type != isc_tokentype_number) { 499 ret = DST_R_INVALIDPRIVATEKEY; 500 goto fail; 501 } 502 503 dst_key_setnum(key, tag, token.value.as_ulong); 504 goto next; 505 } 506 507 /* Timing metadata */ 508 tag = find_timedata(DST_AS_STR(token)); 509 if (tag >= 0) { 510 INSIST(tag < TIMING_NTAGS); 511 512 NEXTTOKEN(lex, opt, &token); 513 if (token.type != isc_tokentype_string) { 514 ret = DST_R_INVALIDPRIVATEKEY; 515 goto fail; 516 } 517 518 ret = dns_time32_fromtext(DST_AS_STR(token), &when); 519 if (ret != ISC_R_SUCCESS) { 520 goto fail; 521 } 522 523 dst_key_settime(key, tag, when); 524 525 goto next; 526 } 527 528 /* Key data */ 529 tag = find_value(DST_AS_STR(token), alg); 530 if (tag < 0 && minor > DST_MINOR_VERSION) { 531 goto next; 532 } else if (tag < 0) { 533 ret = DST_R_INVALIDPRIVATEKEY; 534 goto fail; 535 } 536 537 priv->elements[n].tag = tag; 538 539 data = isc_mem_get(mctx, MAXFIELDSIZE); 540 541 isc_buffer_init(&b, data, MAXFIELDSIZE); 542 ret = isc_base64_tobuffer(lex, &b, -1); 543 if (ret != ISC_R_SUCCESS) { 544 goto fail; 545 } 546 547 isc_buffer_usedregion(&b, &r); 548 priv->elements[n].length = r.length; 549 priv->elements[n].data = r.base; 550 priv->nelements++; 551 552 next: 553 READLINE(lex, opt, &token); 554 data = NULL; 555 } 556 557 done: 558 if (external && priv->nelements != 0) { 559 ret = DST_R_INVALIDPRIVATEKEY; 560 goto fail; 561 } 562 563 check = check_data(priv, alg, true, external); 564 if (check < 0) { 565 ret = DST_R_INVALIDPRIVATEKEY; 566 goto fail; 567 } else if (check != ISC_R_SUCCESS) { 568 ret = check; 569 goto fail; 570 } 571 572 key->external = external; 573 574 return ISC_R_SUCCESS; 575 576 fail: 577 dst__privstruct_free(priv, mctx); 578 if (data != NULL) { 579 isc_mem_put(mctx, data, MAXFIELDSIZE); 580 } 581 582 return ret; 583 } 584 585 isc_result_t 586 dst__privstruct_writefile(const dst_key_t *key, const dst_private_t *priv, 587 const char *directory) { 588 FILE *fp; 589 isc_result_t result; 590 char filename[NAME_MAX]; 591 char tmpname[NAME_MAX]; 592 char buffer[MAXFIELDSIZE * 2]; 593 isc_stdtime_t when; 594 uint32_t value; 595 isc_buffer_t b; 596 isc_buffer_t fileb; 597 isc_buffer_t tmpb; 598 isc_region_t r; 599 int major, minor; 600 mode_t mode; 601 int i, ret; 602 603 REQUIRE(priv != NULL); 604 605 ret = check_data(priv, dst_key_alg(key), false, key->external); 606 if (ret < 0) { 607 return DST_R_INVALIDPRIVATEKEY; 608 } else if (ret != ISC_R_SUCCESS) { 609 return ret; 610 } 611 612 isc_buffer_init(&fileb, filename, sizeof(filename)); 613 result = dst_key_buildfilename(key, DST_TYPE_PRIVATE, directory, 614 &fileb); 615 if (result != ISC_R_SUCCESS) { 616 return result; 617 } 618 619 result = isc_file_mode(filename, &mode); 620 if (result == ISC_R_SUCCESS && mode != (S_IRUSR | S_IWUSR)) { 621 /* File exists; warn that we are changing its permissions */ 622 int level; 623 624 level = ISC_LOG_WARNING; 625 isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, 626 DNS_LOGMODULE_DNSSEC, level, 627 "Permissions on the file %s " 628 "have changed from 0%o to 0600 as " 629 "a result of this operation.", 630 filename, (unsigned int)mode); 631 } 632 633 isc_buffer_init(&tmpb, tmpname, sizeof(tmpname)); 634 result = dst_key_buildfilename(key, DST_TYPE_TEMPLATE, directory, 635 &tmpb); 636 if (result != ISC_R_SUCCESS) { 637 return result; 638 } 639 640 fp = dst_key_open(tmpname, S_IRUSR | S_IWUSR); 641 if (fp == NULL) { 642 return DST_R_WRITEERROR; 643 } 644 645 dst_key_getprivateformat(key, &major, &minor); 646 if (major == 0 && minor == 0) { 647 major = DST_MAJOR_VERSION; 648 minor = DST_MINOR_VERSION; 649 } 650 651 /* XXXDCL return value should be checked for full filesystem */ 652 fprintf(fp, "%s v%d.%d\n", PRIVATE_KEY_STR, major, minor); 653 654 fprintf(fp, "%s %u ", ALGORITHM_STR, dst_key_alg(key)); 655 656 switch (dst_key_alg(key)) { 657 case DST_ALG_RSASHA1: 658 fprintf(fp, "(RSASHA1)\n"); 659 break; 660 case DST_ALG_NSEC3RSASHA1: 661 fprintf(fp, "(NSEC3RSASHA1)\n"); 662 break; 663 case DST_ALG_RSASHA256: 664 fprintf(fp, "(RSASHA256)\n"); 665 break; 666 case DST_ALG_RSASHA512: 667 fprintf(fp, "(RSASHA512)\n"); 668 break; 669 case DST_ALG_ECDSA256: 670 fprintf(fp, "(ECDSAP256SHA256)\n"); 671 break; 672 case DST_ALG_ECDSA384: 673 fprintf(fp, "(ECDSAP384SHA384)\n"); 674 break; 675 case DST_ALG_ED25519: 676 fprintf(fp, "(ED25519)\n"); 677 break; 678 case DST_ALG_ED448: 679 fprintf(fp, "(ED448)\n"); 680 break; 681 case DST_ALG_HMACMD5: 682 fprintf(fp, "(HMAC_MD5)\n"); 683 break; 684 case DST_ALG_HMACSHA1: 685 fprintf(fp, "(HMAC_SHA1)\n"); 686 break; 687 case DST_ALG_HMACSHA224: 688 fprintf(fp, "(HMAC_SHA224)\n"); 689 break; 690 case DST_ALG_HMACSHA256: 691 fprintf(fp, "(HMAC_SHA256)\n"); 692 break; 693 case DST_ALG_HMACSHA384: 694 fprintf(fp, "(HMAC_SHA384)\n"); 695 break; 696 case DST_ALG_HMACSHA512: 697 fprintf(fp, "(HMAC_SHA512)\n"); 698 break; 699 default: 700 fprintf(fp, "(?)\n"); 701 break; 702 } 703 704 for (i = 0; i < priv->nelements; i++) { 705 const char *s; 706 707 s = find_tag(priv->elements[i].tag); 708 709 r.base = priv->elements[i].data; 710 r.length = priv->elements[i].length; 711 isc_buffer_init(&b, buffer, sizeof(buffer)); 712 result = isc_base64_totext(&r, sizeof(buffer), "", &b); 713 if (result != ISC_R_SUCCESS) { 714 return dst_key_cleanup(tmpname, fp); 715 } 716 isc_buffer_usedregion(&b, &r); 717 718 fprintf(fp, "%s %.*s\n", s, (int)r.length, r.base); 719 } 720 721 if (key->external) { 722 fprintf(fp, "External:\n"); 723 } 724 725 /* Add the metadata tags */ 726 if (major > 1 || (major == 1 && minor >= 3)) { 727 for (i = 0; i < NUMERIC_NTAGS; i++) { 728 result = dst_key_getnum(key, i, &value); 729 if (result != ISC_R_SUCCESS) { 730 continue; 731 } 732 if (numerictags[i] != NULL) { 733 fprintf(fp, "%s %u\n", numerictags[i], value); 734 } 735 } 736 for (i = 0; i < TIMING_NTAGS; i++) { 737 result = dst_key_gettime(key, i, &when); 738 if (result != ISC_R_SUCCESS) { 739 continue; 740 } 741 742 isc_buffer_init(&b, buffer, sizeof(buffer)); 743 result = dns_time32_totext(when, &b); 744 if (result != ISC_R_SUCCESS) { 745 return dst_key_cleanup(tmpname, fp); 746 } 747 748 isc_buffer_usedregion(&b, &r); 749 750 if (timetags[i] != NULL) { 751 fprintf(fp, "%s %.*s\n", timetags[i], 752 (int)r.length, r.base); 753 } 754 } 755 } 756 757 result = dst_key_close(tmpname, fp, filename); 758 return result; 759 } 760 761 /*! \file */ 762