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