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