1 /*
2  * Portions Copyright (C) Internet Systems Consortium, Inc. ("ISC")
3  *
4  * This Source Code Form is subject to the terms of the Mozilla Public
5  * License, v. 2.0. If a copy of the MPL was not distributed with this
6  * file, you can obtain one at https://mozilla.org/MPL/2.0/.
7  *
8  * See the COPYRIGHT file distributed with this work for additional
9  * information regarding copyright ownership.
10  *
11  * Portions Copyright (C) Network Associates, Inc.
12  *
13  * Permission to use, copy, modify, and/or distribute this software for any
14  * purpose with or without fee is hereby granted, provided that the above
15  * copyright notice and this permission notice appear in all copies.
16  *
17  * THE SOFTWARE IS PROVIDED "AS IS" AND ISC AND NETWORK ASSOCIATES DISCLAIMS
18  * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE
20  * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
21  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
22  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
23  * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
24  */
25 
26 #include "dst_parse.h"
27 #include <inttypes.h>
28 #include <stdbool.h>
29 
30 #include <isc/base64.h>
31 #include <isc/dir.h>
32 #include <isc/file.h>
33 #include <isc/fsaccess.h>
34 #include <isc/lex.h>
35 #include <isc/mem.h>
36 #include <isc/print.h>
37 #include <isc/stdtime.h>
38 #include <isc/string.h>
39 #include <isc/util.h>
40 
41 #include <dns/log.h>
42 #include <dns/time.h>
43 
44 #include "dst_internal.h"
45 #include "isc/result.h"
46 
47 #define DST_AS_STR(t) ((t).value.as_textregion.base)
48 
49 #define PRIVATE_KEY_STR "Private-key-format:"
50 #define ALGORITHM_STR	"Algorithm:"
51 
52 #define TIMING_NTAGS (DST_MAX_TIMES + 1)
53 static const char *timetags[TIMING_NTAGS] = {
54 	"Created:",    "Publish:", "Activate:",	 "Revoke:",
55 	"Inactive:",   "Delete:",  "DSPublish:", "SyncPublish:",
56 	"SyncDelete:", NULL,	   NULL,	 NULL,
57 	NULL
58 };
59 
60 #define NUMERIC_NTAGS (DST_MAX_NUMERIC + 1)
61 static const char *numerictags[NUMERIC_NTAGS] = {
62 	"Predecessor:", "Successor:", "MaxTTL:", "RollPeriod:", NULL, NULL, NULL
63 };
64 
65 struct parse_map {
66 	const int value;
67 	const char *tag;
68 };
69 
70 static struct parse_map map[] = { { TAG_RSA_MODULUS, "Modulus:" },
71 				  { TAG_RSA_PUBLICEXPONENT, "PublicExponent:" },
72 				  { TAG_RSA_PRIVATEEXPONENT, "PrivateExponent"
73 							     ":" },
74 				  { TAG_RSA_PRIME1, "Prime1:" },
75 				  { TAG_RSA_PRIME2, "Prime2:" },
76 				  { TAG_RSA_EXPONENT1, "Exponent1:" },
77 				  { TAG_RSA_EXPONENT2, "Exponent2:" },
78 				  { TAG_RSA_COEFFICIENT, "Coefficient:" },
79 				  { TAG_RSA_ENGINE, "Engine:" },
80 				  { TAG_RSA_LABEL, "Label:" },
81 
82 				  { TAG_DH_PRIME, "Prime(p):" },
83 				  { TAG_DH_GENERATOR, "Generator(g):" },
84 				  { TAG_DH_PRIVATE, "Private_value(x):" },
85 				  { TAG_DH_PUBLIC, "Public_value(y):" },
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
find_value(const char * s,const unsigned int alg)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 			return (map[i].value);
123 		}
124 	}
125 	return (-1);
126 }
127 
128 static const char *
find_tag(const int value)129 find_tag(const int value) {
130 	int i;
131 
132 	for (i = 0;; i++) {
133 		if (map[i].tag == NULL) {
134 			return (NULL);
135 		} else if (value == map[i].value) {
136 			return (map[i].tag);
137 		}
138 	}
139 }
140 
141 static int
find_metadata(const char * s,const char * tags[],int ntags)142 find_metadata(const char *s, const char *tags[], int ntags) {
143 	int i;
144 
145 	for (i = 0; i < ntags; i++) {
146 		if (tags[i] != NULL && strcasecmp(s, tags[i]) == 0) {
147 			return (i);
148 		}
149 	}
150 
151 	return (-1);
152 }
153 
154 static int
find_timedata(const char * s)155 find_timedata(const char *s) {
156 	return (find_metadata(s, timetags, TIMING_NTAGS));
157 }
158 
159 static int
find_numericdata(const char * s)160 find_numericdata(const char *s) {
161 	return (find_metadata(s, numerictags, NUMERIC_NTAGS));
162 }
163 
164 static int
check_rsa(const dst_private_t * priv,bool external)165 check_rsa(const dst_private_t *priv, bool external) {
166 	int i, j;
167 	bool have[RSA_NTAGS];
168 	bool ok;
169 	unsigned int mask;
170 
171 	if (external) {
172 		return ((priv->nelements == 0) ? 0 : -1);
173 	}
174 
175 	for (i = 0; i < RSA_NTAGS; i++) {
176 		have[i] = false;
177 	}
178 
179 	for (j = 0; j < priv->nelements; j++) {
180 		for (i = 0; i < RSA_NTAGS; i++) {
181 			if (priv->elements[j].tag == TAG(DST_ALG_RSA, i)) {
182 				break;
183 			}
184 		}
185 		if (i == RSA_NTAGS) {
186 			return (-1);
187 		}
188 		have[i] = true;
189 	}
190 
191 	mask = (1ULL << TAG_SHIFT) - 1;
192 
193 	if (have[TAG_RSA_ENGINE & mask]) {
194 		ok = have[TAG_RSA_MODULUS & mask] &&
195 		     have[TAG_RSA_PUBLICEXPONENT & mask] &&
196 		     have[TAG_RSA_LABEL & 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
check_dh(const dst_private_t * priv)211 check_dh(const dst_private_t *priv) {
212 	int i, j;
213 	if (priv->nelements != DH_NTAGS) {
214 		return (-1);
215 	}
216 	for (i = 0; i < DH_NTAGS; i++) {
217 		for (j = 0; j < priv->nelements; j++) {
218 			if (priv->elements[j].tag == TAG(DST_ALG_DH, i)) {
219 				break;
220 			}
221 		}
222 		if (j == priv->nelements) {
223 			return (-1);
224 		}
225 	}
226 	return (0);
227 }
228 
229 static int
check_ecdsa(const dst_private_t * priv,bool external)230 check_ecdsa(const dst_private_t *priv, bool external) {
231 	int i, j;
232 	bool have[ECDSA_NTAGS];
233 	bool ok;
234 	unsigned int mask;
235 
236 	if (external) {
237 		return ((priv->nelements == 0) ? 0 : -1);
238 	}
239 
240 	for (i = 0; i < ECDSA_NTAGS; i++) {
241 		have[i] = false;
242 	}
243 	for (j = 0; j < priv->nelements; j++) {
244 		for (i = 0; i < ECDSA_NTAGS; i++) {
245 			if (priv->elements[j].tag == TAG(DST_ALG_ECDSA256, i)) {
246 				break;
247 			}
248 		}
249 		if (i == ECDSA_NTAGS) {
250 			return (-1);
251 		}
252 		have[i] = true;
253 	}
254 
255 	mask = (1ULL << TAG_SHIFT) - 1;
256 
257 	if (have[TAG_ECDSA_ENGINE & mask]) {
258 		ok = have[TAG_ECDSA_LABEL & mask];
259 	} else {
260 		ok = have[TAG_ECDSA_PRIVATEKEY & mask];
261 	}
262 	return (ok ? 0 : -1);
263 }
264 
265 static int
check_eddsa(const dst_private_t * priv,bool external)266 check_eddsa(const dst_private_t *priv, bool external) {
267 	int i, j;
268 	bool have[EDDSA_NTAGS];
269 	bool ok;
270 	unsigned int mask;
271 
272 	if (external) {
273 		return ((priv->nelements == 0) ? 0 : -1);
274 	}
275 
276 	for (i = 0; i < EDDSA_NTAGS; i++) {
277 		have[i] = false;
278 	}
279 	for (j = 0; j < priv->nelements; j++) {
280 		for (i = 0; i < EDDSA_NTAGS; i++) {
281 			if (priv->elements[j].tag == TAG(DST_ALG_ED25519, i)) {
282 				break;
283 			}
284 		}
285 		if (i == EDDSA_NTAGS) {
286 			return (-1);
287 		}
288 		have[i] = true;
289 	}
290 
291 	mask = (1ULL << TAG_SHIFT) - 1;
292 
293 	if (have[TAG_EDDSA_ENGINE & mask]) {
294 		ok = have[TAG_EDDSA_LABEL & mask];
295 	} else {
296 		ok = have[TAG_EDDSA_PRIVATEKEY & mask];
297 	}
298 	return (ok ? 0 : -1);
299 }
300 
301 static int
check_hmac_md5(const dst_private_t * priv,bool old)302 check_hmac_md5(const dst_private_t *priv, bool old) {
303 	int i, j;
304 
305 	if (priv->nelements != HMACMD5_NTAGS) {
306 		/*
307 		 * If this is a good old format and we are accepting
308 		 * the old format return success.
309 		 */
310 		if (old && priv->nelements == OLD_HMACMD5_NTAGS &&
311 		    priv->elements[0].tag == TAG_HMACMD5_KEY)
312 		{
313 			return (0);
314 		}
315 		return (-1);
316 	}
317 	/*
318 	 * We must be new format at this point.
319 	 */
320 	for (i = 0; i < HMACMD5_NTAGS; i++) {
321 		for (j = 0; j < priv->nelements; j++) {
322 			if (priv->elements[j].tag == TAG(DST_ALG_HMACMD5, i)) {
323 				break;
324 			}
325 		}
326 		if (j == priv->nelements) {
327 			return (-1);
328 		}
329 	}
330 	return (0);
331 }
332 
333 static int
check_hmac_sha(const dst_private_t * priv,unsigned int ntags,unsigned int alg)334 check_hmac_sha(const dst_private_t *priv, unsigned int ntags,
335 	       unsigned int alg) {
336 	unsigned int i, j;
337 	if (priv->nelements != ntags) {
338 		return (-1);
339 	}
340 	for (i = 0; i < ntags; i++) {
341 		for (j = 0; j < priv->nelements; j++) {
342 			if (priv->elements[j].tag == TAG(alg, i)) {
343 				break;
344 			}
345 		}
346 		if (j == priv->nelements) {
347 			return (-1);
348 		}
349 	}
350 	return (0);
351 }
352 
353 static int
check_data(const dst_private_t * priv,const unsigned int alg,bool old,bool external)354 check_data(const dst_private_t *priv, const unsigned int alg, bool old,
355 	   bool external) {
356 	/* XXXVIX this switch statement is too sparse to gen a jump table. */
357 	switch (alg) {
358 	case DST_ALG_RSA:
359 	case DST_ALG_RSASHA1:
360 	case DST_ALG_NSEC3RSASHA1:
361 	case DST_ALG_RSASHA256:
362 	case DST_ALG_RSASHA512:
363 		return (check_rsa(priv, external));
364 	case DST_ALG_DH:
365 		return (check_dh(priv));
366 	case DST_ALG_ECDSA256:
367 	case DST_ALG_ECDSA384:
368 		return (check_ecdsa(priv, external));
369 	case DST_ALG_ED25519:
370 	case DST_ALG_ED448:
371 		return (check_eddsa(priv, external));
372 	case DST_ALG_HMACMD5:
373 		return (check_hmac_md5(priv, old));
374 	case DST_ALG_HMACSHA1:
375 		return (check_hmac_sha(priv, HMACSHA1_NTAGS, alg));
376 	case DST_ALG_HMACSHA224:
377 		return (check_hmac_sha(priv, HMACSHA224_NTAGS, alg));
378 	case DST_ALG_HMACSHA256:
379 		return (check_hmac_sha(priv, HMACSHA256_NTAGS, alg));
380 	case DST_ALG_HMACSHA384:
381 		return (check_hmac_sha(priv, HMACSHA384_NTAGS, alg));
382 	case DST_ALG_HMACSHA512:
383 		return (check_hmac_sha(priv, HMACSHA512_NTAGS, alg));
384 	default:
385 		return (DST_R_UNSUPPORTEDALG);
386 	}
387 }
388 
389 void
dst__privstruct_free(dst_private_t * priv,isc_mem_t * mctx)390 dst__privstruct_free(dst_private_t *priv, isc_mem_t *mctx) {
391 	int i;
392 
393 	if (priv == NULL) {
394 		return;
395 	}
396 	for (i = 0; i < priv->nelements; i++) {
397 		if (priv->elements[i].data == NULL) {
398 			continue;
399 		}
400 		memset(priv->elements[i].data, 0, MAXFIELDSIZE);
401 		isc_mem_put(mctx, priv->elements[i].data, MAXFIELDSIZE);
402 	}
403 	priv->nelements = 0;
404 }
405 
406 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)407 dst__privstruct_parse(dst_key_t *key, unsigned int alg, isc_lex_t *lex,
408 		      isc_mem_t *mctx, dst_private_t *priv) {
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 	bool external = 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 (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 || (DST_AS_STR(token))[0] != 'v')
452 	{
453 		ret = DST_R_INVALIDPRIVATEKEY;
454 		goto fail;
455 	}
456 	if (sscanf(DST_AS_STR(token), "v%d.%d", &major, &minor) != 2) {
457 		ret = DST_R_INVALIDPRIVATEKEY;
458 		goto fail;
459 	}
460 
461 	if (major > DST_MAJOR_VERSION) {
462 		ret = DST_R_INVALIDPRIVATEKEY;
463 		goto fail;
464 	}
465 
466 	/*
467 	 * Store the private key format version number
468 	 */
469 	dst_key_setprivateformat(key, major, minor);
470 
471 	READLINE(lex, opt, &token);
472 
473 	/*
474 	 * Read the algorithm line.
475 	 */
476 	NEXTTOKEN(lex, opt, &token);
477 	if (token.type != isc_tokentype_string ||
478 	    strcmp(DST_AS_STR(token), ALGORITHM_STR) != 0)
479 	{
480 		ret = DST_R_INVALIDPRIVATEKEY;
481 		goto fail;
482 	}
483 
484 	NEXTTOKEN(lex, opt | ISC_LEXOPT_NUMBER, &token);
485 	if (token.type != isc_tokentype_number ||
486 	    token.value.as_ulong != (unsigned long)dst_key_alg(key))
487 	{
488 		ret = DST_R_INVALIDPRIVATEKEY;
489 		goto fail;
490 	}
491 
492 	READLINE(lex, opt, &token);
493 
494 	/*
495 	 * Read the key data.
496 	 */
497 	for (n = 0; n < MAXFIELDS; n++) {
498 		int tag;
499 		isc_region_t r;
500 		do {
501 			ret = isc_lex_gettoken(lex, opt, &token);
502 			if (ret == ISC_R_EOF) {
503 				goto done;
504 			}
505 			if (ret != ISC_R_SUCCESS) {
506 				goto fail;
507 			}
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 = 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 
551 			dst_key_settime(key, tag, when);
552 
553 			goto next;
554 		}
555 
556 		/* Key data */
557 		tag = find_value(DST_AS_STR(token), alg);
558 		if (tag < 0 && minor > DST_MINOR_VERSION) {
559 			goto next;
560 		} else if (tag < 0) {
561 			ret = DST_R_INVALIDPRIVATEKEY;
562 			goto fail;
563 		}
564 
565 		priv->elements[n].tag = tag;
566 
567 		data = isc_mem_get(mctx, MAXFIELDSIZE);
568 
569 		isc_buffer_init(&b, data, MAXFIELDSIZE);
570 		ret = isc_base64_tobuffer(lex, &b, -1);
571 		if (ret != ISC_R_SUCCESS) {
572 			goto fail;
573 		}
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, 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 
610 	return (ret);
611 }
612 
613 isc_result_t
dst__privstruct_writefile(const dst_key_t * key,const dst_private_t * priv,const char * directory)614 dst__privstruct_writefile(const dst_key_t *key, const dst_private_t *priv,
615 			  const char *directory) {
616 	FILE *fp;
617 	isc_result_t result;
618 	char filename[NAME_MAX];
619 	char buffer[MAXFIELDSIZE * 2];
620 	isc_fsaccess_t access;
621 	isc_stdtime_t when;
622 	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), false, key->external);
632 	if (ret < 0) {
633 		return (DST_R_INVALIDPRIVATEKEY);
634 	} else if (ret != ISC_R_SUCCESS) {
635 		return (ret);
636 	}
637 
638 	isc_buffer_init(&b, filename, sizeof(filename));
639 	result = dst_key_buildfilename(key, DST_TYPE_PRIVATE, directory, &b);
640 	if (result != ISC_R_SUCCESS) {
641 		return (result);
642 	}
643 
644 	result = isc_file_mode(filename, &mode);
645 	if (result == ISC_R_SUCCESS && mode != 0600) {
646 		/* File exists; warn that we are changing its permissions */
647 		int level;
648 
649 		level = ISC_LOG_WARNING;
650 		isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
651 			      DNS_LOGMODULE_DNSSEC, level,
652 			      "Permissions on the file %s "
653 			      "have changed from 0%o to 0600 as "
654 			      "a result of this operation.",
655 			      filename, (unsigned int)mode);
656 	}
657 
658 	if ((fp = fopen(filename, "w")) == NULL) {
659 		return (DST_R_WRITEERROR);
660 	}
661 
662 	access = 0;
663 	isc_fsaccess_add(ISC_FSACCESS_OWNER,
664 			 ISC_FSACCESS_READ | ISC_FSACCESS_WRITE, &access);
665 	(void)isc_fsaccess_set(filename, access);
666 
667 	dst_key_getprivateformat(key, &major, &minor);
668 	if (major == 0 && minor == 0) {
669 		major = DST_MAJOR_VERSION;
670 		minor = DST_MINOR_VERSION;
671 	}
672 
673 	/* XXXDCL return value should be checked for full filesystem */
674 	fprintf(fp, "%s v%d.%d\n", PRIVATE_KEY_STR, major, minor);
675 
676 	fprintf(fp, "%s %u ", ALGORITHM_STR, dst_key_alg(key));
677 
678 	/* XXXVIX this switch statement is too sparse to gen a jump table. */
679 	switch (dst_key_alg(key)) {
680 	case DST_ALG_DH:
681 		fprintf(fp, "(DH)\n");
682 		break;
683 	case DST_ALG_RSASHA1:
684 		fprintf(fp, "(RSASHA1)\n");
685 		break;
686 	case DST_ALG_NSEC3RSASHA1:
687 		fprintf(fp, "(NSEC3RSASHA1)\n");
688 		break;
689 	case DST_ALG_RSASHA256:
690 		fprintf(fp, "(RSASHA256)\n");
691 		break;
692 	case DST_ALG_RSASHA512:
693 		fprintf(fp, "(RSASHA512)\n");
694 		break;
695 	case DST_ALG_ECDSA256:
696 		fprintf(fp, "(ECDSAP256SHA256)\n");
697 		break;
698 	case DST_ALG_ECDSA384:
699 		fprintf(fp, "(ECDSAP384SHA384)\n");
700 		break;
701 	case DST_ALG_ED25519:
702 		fprintf(fp, "(ED25519)\n");
703 		break;
704 	case DST_ALG_ED448:
705 		fprintf(fp, "(ED448)\n");
706 		break;
707 	case DST_ALG_HMACMD5:
708 		fprintf(fp, "(HMAC_MD5)\n");
709 		break;
710 	case DST_ALG_HMACSHA1:
711 		fprintf(fp, "(HMAC_SHA1)\n");
712 		break;
713 	case DST_ALG_HMACSHA224:
714 		fprintf(fp, "(HMAC_SHA224)\n");
715 		break;
716 	case DST_ALG_HMACSHA256:
717 		fprintf(fp, "(HMAC_SHA256)\n");
718 		break;
719 	case DST_ALG_HMACSHA384:
720 		fprintf(fp, "(HMAC_SHA384)\n");
721 		break;
722 	case DST_ALG_HMACSHA512:
723 		fprintf(fp, "(HMAC_SHA512)\n");
724 		break;
725 	default:
726 		fprintf(fp, "(?)\n");
727 		break;
728 	}
729 
730 	for (i = 0; i < priv->nelements; i++) {
731 		const char *s;
732 
733 		s = find_tag(priv->elements[i].tag);
734 
735 		r.base = priv->elements[i].data;
736 		r.length = priv->elements[i].length;
737 		isc_buffer_init(&b, buffer, sizeof(buffer));
738 		result = isc_base64_totext(&r, sizeof(buffer), "", &b);
739 		if (result != ISC_R_SUCCESS) {
740 			fclose(fp);
741 			return (DST_R_INVALIDPRIVATEKEY);
742 		}
743 		isc_buffer_usedregion(&b, &r);
744 
745 		fprintf(fp, "%s %.*s\n", s, (int)r.length, r.base);
746 	}
747 
748 	if (key->external) {
749 		fprintf(fp, "External:\n");
750 	}
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 			}
759 			if (numerictags[i] != NULL) {
760 				fprintf(fp, "%s %u\n", numerictags[i], value);
761 			}
762 		}
763 		for (i = 0; i < TIMING_NTAGS; i++) {
764 			result = dst_key_gettime(key, i, &when);
765 			if (result != ISC_R_SUCCESS) {
766 				continue;
767 			}
768 
769 			isc_buffer_init(&b, buffer, sizeof(buffer));
770 			result = dns_time32_totext(when, &b);
771 			if (result != ISC_R_SUCCESS) {
772 				fclose(fp);
773 				return (DST_R_INVALIDPRIVATEKEY);
774 			}
775 
776 			isc_buffer_usedregion(&b, &r);
777 
778 			if (timetags[i] != NULL) {
779 				fprintf(fp, "%s %.*s\n", timetags[i],
780 					(int)r.length, r.base);
781 			}
782 		}
783 	}
784 
785 	fflush(fp);
786 	result = ferror(fp) ? DST_R_WRITEERROR : ISC_R_SUCCESS;
787 	fclose(fp);
788 	return (result);
789 }
790 
791 /*! \file */
792