xref: /netbsd-src/external/mpl/bind/dist/lib/dns/dst_parse.c (revision bcda20f65a8566e103791ec395f7f499ef322704)
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