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