PageRenderTime 64ms CodeModel.GetById 26ms app.highlight 33ms RepoModel.GetById 1ms app.codeStats 0ms

/contrib/bind9/lib/dns/dst_parse.c

https://bitbucket.org/freebsd/freebsd-head/
C | 680 lines | 524 code | 95 blank | 61 comment | 148 complexity | 4576770070b2716cbdad1d2e7bd02e74 MD5 | raw file
  1/*
  2 * Portions Copyright (C) 2004-2012  Internet Systems Consortium, Inc. ("ISC")
  3 * Portions Copyright (C) 1999-2002  Internet Software Consortium.
  4 *
  5 * Permission to use, copy, modify, and/or distribute this software for any
  6 * purpose with or without fee is hereby granted, provided that the above
  7 * copyright notice and this permission notice appear in all copies.
  8 *
  9 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC AND NETWORK ASSOCIATES DISCLAIMS
 10 * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
 11 * WARRANTIES OF MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE
 12 * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
 15 * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 16 *
 17 * Portions Copyright (C) 1995-2000 by 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/*%
 33 * Principal Author: Brian Wellington
 34 * $Id$
 35 */
 36
 37#include <config.h>
 38
 39#include <isc/base64.h>
 40#include <isc/dir.h>
 41#include <isc/fsaccess.h>
 42#include <isc/lex.h>
 43#include <isc/mem.h>
 44#include <isc/stdtime.h>
 45#include <isc/string.h>
 46#include <isc/util.h>
 47
 48#include <dns/time.h>
 49
 50#include "dst_internal.h"
 51#include "dst_parse.h"
 52#include "dst/result.h"
 53
 54#define DST_AS_STR(t) ((t).value.as_textregion.base)
 55
 56#define PRIVATE_KEY_STR "Private-key-format:"
 57#define ALGORITHM_STR "Algorithm:"
 58
 59#define TIMING_NTAGS (DST_MAX_TIMES + 1)
 60static const char *timetags[TIMING_NTAGS] = {
 61	"Created:",
 62	"Publish:",
 63	"Activate:",
 64	"Revoke:",
 65	"Inactive:",
 66	"Delete:",
 67	"DSPublish:"
 68};
 69
 70#define NUMERIC_NTAGS (DST_MAX_NUMERIC + 1)
 71static const char *numerictags[NUMERIC_NTAGS] = {
 72	"Predecessor:",
 73	"Successor:",
 74	"MaxTTL:",
 75	"RollPeriod:"
 76};
 77
 78struct parse_map {
 79	const int value;
 80	const char *tag;
 81};
 82
 83static struct parse_map map[] = {
 84	{TAG_RSA_MODULUS, "Modulus:"},
 85	{TAG_RSA_PUBLICEXPONENT, "PublicExponent:"},
 86	{TAG_RSA_PRIVATEEXPONENT, "PrivateExponent:"},
 87	{TAG_RSA_PRIME1, "Prime1:"},
 88	{TAG_RSA_PRIME2, "Prime2:"},
 89	{TAG_RSA_EXPONENT1, "Exponent1:"},
 90	{TAG_RSA_EXPONENT2, "Exponent2:"},
 91	{TAG_RSA_COEFFICIENT, "Coefficient:"},
 92	{TAG_RSA_ENGINE, "Engine:" },
 93	{TAG_RSA_LABEL, "Label:" },
 94	{TAG_RSA_PIN, "PIN:" },
 95
 96	{TAG_DH_PRIME, "Prime(p):"},
 97	{TAG_DH_GENERATOR, "Generator(g):"},
 98	{TAG_DH_PRIVATE, "Private_value(x):"},
 99	{TAG_DH_PUBLIC, "Public_value(y):"},
100
101	{TAG_DSA_PRIME, "Prime(p):"},
102	{TAG_DSA_SUBPRIME, "Subprime(q):"},
103	{TAG_DSA_BASE, "Base(g):"},
104	{TAG_DSA_PRIVATE, "Private_value(x):"},
105	{TAG_DSA_PUBLIC, "Public_value(y):"},
106
107	{TAG_GOST_PRIVASN1, "GostAsn1:"},
108
109	{TAG_HMACMD5_KEY, "Key:"},
110	{TAG_HMACMD5_BITS, "Bits:"},
111
112	{TAG_HMACSHA1_KEY, "Key:"},
113	{TAG_HMACSHA1_BITS, "Bits:"},
114
115	{TAG_HMACSHA224_KEY, "Key:"},
116	{TAG_HMACSHA224_BITS, "Bits:"},
117
118	{TAG_HMACSHA256_KEY, "Key:"},
119	{TAG_HMACSHA256_BITS, "Bits:"},
120
121	{TAG_HMACSHA384_KEY, "Key:"},
122	{TAG_HMACSHA384_BITS, "Bits:"},
123
124	{TAG_HMACSHA512_KEY, "Key:"},
125	{TAG_HMACSHA512_BITS, "Bits:"},
126
127	{0, NULL}
128};
129
130static int
131find_value(const char *s, const unsigned int alg) {
132	int i;
133
134	for (i = 0; map[i].tag != NULL; i++) {
135		if (strcasecmp(s, map[i].tag) == 0 &&
136		    (TAG_ALG(map[i].value) == alg))
137			return (map[i].value);
138	}
139	return (-1);
140}
141
142static const char *
143find_tag(const int value) {
144	int i;
145
146	for (i = 0; ; i++) {
147		if (map[i].tag == NULL)
148			return (NULL);
149		else if (value == map[i].value)
150			return (map[i].tag);
151	}
152}
153
154static int
155find_metadata(const char *s, const char *tags[], int ntags) {
156	int i;
157
158	for (i = 0; i < ntags; i++) {
159		if (strcasecmp(s, tags[i]) == 0)
160			return (i);
161	}
162
163	return (-1);
164}
165
166static int
167find_timedata(const char *s) {
168	return (find_metadata(s, timetags, TIMING_NTAGS));
169}
170
171static int
172find_numericdata(const char *s) {
173	return (find_metadata(s, numerictags, NUMERIC_NTAGS));
174}
175
176static int
177check_rsa(const dst_private_t *priv) {
178	int i, j;
179	isc_boolean_t have[RSA_NTAGS];
180	isc_boolean_t ok;
181	unsigned int mask;
182
183	for (i = 0; i < RSA_NTAGS; i++)
184		have[i] = ISC_FALSE;
185	for (j = 0; j < priv->nelements; j++) {
186		for (i = 0; i < RSA_NTAGS; i++)
187			if (priv->elements[j].tag == TAG(DST_ALG_RSAMD5, i))
188				break;
189		if (i == RSA_NTAGS)
190			return (-1);
191		have[i] = ISC_TRUE;
192	}
193
194	mask = ~0;
195	mask <<= sizeof(mask) * 8 - TAG_SHIFT;
196	mask >>= sizeof(mask) * 8 - TAG_SHIFT;
197
198	if (have[TAG_RSA_ENGINE & mask])
199		ok = have[TAG_RSA_MODULUS & mask] &&
200		     have[TAG_RSA_PUBLICEXPONENT & mask] &&
201		     have[TAG_RSA_LABEL & mask];
202	else
203		ok = have[TAG_RSA_MODULUS & mask] &&
204		     have[TAG_RSA_PUBLICEXPONENT & mask] &&
205		     have[TAG_RSA_PRIVATEEXPONENT & mask] &&
206		     have[TAG_RSA_PRIME1 & mask] &&
207		     have[TAG_RSA_PRIME2 & mask] &&
208		     have[TAG_RSA_EXPONENT1 & mask] &&
209		     have[TAG_RSA_EXPONENT2 & mask] &&
210		     have[TAG_RSA_COEFFICIENT & mask];
211	return (ok ? 0 : -1 );
212}
213
214static int
215check_dh(const dst_private_t *priv) {
216	int i, j;
217	if (priv->nelements != DH_NTAGS)
218		return (-1);
219	for (i = 0; i < DH_NTAGS; i++) {
220		for (j = 0; j < priv->nelements; j++)
221			if (priv->elements[j].tag == TAG(DST_ALG_DH, i))
222				break;
223		if (j == priv->nelements)
224			return (-1);
225	}
226	return (0);
227}
228
229static int
230check_dsa(const dst_private_t *priv) {
231	int i, j;
232	if (priv->nelements != DSA_NTAGS)
233		return (-1);
234	for (i = 0; i < DSA_NTAGS; i++) {
235		for (j = 0; j < priv->nelements; j++)
236			if (priv->elements[j].tag == TAG(DST_ALG_DSA, i))
237				break;
238		if (j == priv->nelements)
239			return (-1);
240	}
241	return (0);
242}
243
244static int
245check_gost(const dst_private_t *priv) {
246	if (priv->nelements != GOST_NTAGS)
247		return (-1);
248	if (priv->elements[0].tag != TAG(DST_ALG_ECCGOST, 0))
249		return (-1);
250	return (0);
251}
252
253static int
254check_hmac_md5(const dst_private_t *priv, isc_boolean_t old) {
255	int i, j;
256
257	if (priv->nelements != HMACMD5_NTAGS) {
258		/*
259		 * If this is a good old format and we are accepting
260		 * the old format return success.
261		 */
262		if (old && priv->nelements == OLD_HMACMD5_NTAGS &&
263		    priv->elements[0].tag == TAG_HMACMD5_KEY)
264			return (0);
265		return (-1);
266	}
267	/*
268	 * We must be new format at this point.
269	 */
270	for (i = 0; i < HMACMD5_NTAGS; i++) {
271		for (j = 0; j < priv->nelements; j++)
272			if (priv->elements[j].tag == TAG(DST_ALG_HMACMD5, i))
273				break;
274		if (j == priv->nelements)
275			return (-1);
276	}
277	return (0);
278}
279
280static int
281check_hmac_sha(const dst_private_t *priv, unsigned int ntags,
282	       unsigned int alg)
283{
284	unsigned int i, j;
285	if (priv->nelements != ntags)
286		return (-1);
287	for (i = 0; i < ntags; i++) {
288		for (j = 0; j < priv->nelements; j++)
289			if (priv->elements[j].tag == TAG(alg, i))
290				break;
291		if (j == priv->nelements)
292			return (-1);
293	}
294	return (0);
295}
296
297static int
298check_data(const dst_private_t *priv, const unsigned int alg,
299	   isc_boolean_t old)
300{
301	/* XXXVIX this switch statement is too sparse to gen a jump table. */
302	switch (alg) {
303	case DST_ALG_RSAMD5:
304	case DST_ALG_RSASHA1:
305		return (check_rsa(priv));
306	case DST_ALG_DH:
307		return (check_dh(priv));
308	case DST_ALG_DSA:
309		return (check_dsa(priv));
310	case DST_ALG_ECCGOST:
311		return (check_gost(priv));
312	case DST_ALG_HMACMD5:
313		return (check_hmac_md5(priv, old));
314	case DST_ALG_HMACSHA1:
315		return (check_hmac_sha(priv, HMACSHA1_NTAGS, alg));
316	case DST_ALG_HMACSHA224:
317		return (check_hmac_sha(priv, HMACSHA224_NTAGS, alg));
318	case DST_ALG_HMACSHA256:
319		return (check_hmac_sha(priv, HMACSHA256_NTAGS, alg));
320	case DST_ALG_HMACSHA384:
321		return (check_hmac_sha(priv, HMACSHA384_NTAGS, alg));
322	case DST_ALG_HMACSHA512:
323		return (check_hmac_sha(priv, HMACSHA512_NTAGS, alg));
324	default:
325		return (DST_R_UNSUPPORTEDALG);
326	}
327}
328
329void
330dst__privstruct_free(dst_private_t *priv, isc_mem_t *mctx) {
331	int i;
332
333	if (priv == NULL)
334		return;
335	for (i = 0; i < priv->nelements; i++) {
336		if (priv->elements[i].data == NULL)
337			continue;
338		memset(priv->elements[i].data, 0, MAXFIELDSIZE);
339		isc_mem_put(mctx, priv->elements[i].data, MAXFIELDSIZE);
340	}
341	priv->nelements = 0;
342}
343
344isc_result_t
345dst__privstruct_parse(dst_key_t *key, unsigned int alg, isc_lex_t *lex,
346		      isc_mem_t *mctx, dst_private_t *priv)
347{
348	int n = 0, major, minor;
349	isc_buffer_t b;
350	isc_token_t token;
351	unsigned char *data = NULL;
352	unsigned int opt = ISC_LEXOPT_EOL;
353	isc_stdtime_t when;
354	isc_result_t ret;
355
356	REQUIRE(priv != NULL);
357
358	priv->nelements = 0;
359	memset(priv->elements, 0, sizeof(priv->elements));
360
361#define NEXTTOKEN(lex, opt, token)				\
362	do {							\
363		ret = isc_lex_gettoken(lex, opt, token);	\
364		if (ret != ISC_R_SUCCESS)			\
365			goto fail;				\
366	} while (0)
367
368#define READLINE(lex, opt, token)				\
369	do {							\
370		ret = isc_lex_gettoken(lex, opt, token);	\
371		if (ret == ISC_R_EOF)				\
372			break;					\
373		else if (ret != ISC_R_SUCCESS)			\
374			goto fail;				\
375	} while ((*token).type != isc_tokentype_eol)
376
377	/*
378	 * Read the description line.
379	 */
380	NEXTTOKEN(lex, opt, &token);
381	if (token.type != isc_tokentype_string ||
382	    strcmp(DST_AS_STR(token), PRIVATE_KEY_STR) != 0)
383	{
384		ret = DST_R_INVALIDPRIVATEKEY;
385		goto fail;
386	}
387
388	NEXTTOKEN(lex, opt, &token);
389	if (token.type != isc_tokentype_string ||
390	    (DST_AS_STR(token))[0] != 'v')
391	{
392		ret = DST_R_INVALIDPRIVATEKEY;
393		goto fail;
394	}
395	if (sscanf(DST_AS_STR(token), "v%d.%d", &major, &minor) != 2)
396	{
397		ret = DST_R_INVALIDPRIVATEKEY;
398		goto fail;
399	}
400
401	if (major > DST_MAJOR_VERSION) {
402		ret = DST_R_INVALIDPRIVATEKEY;
403		goto fail;
404	}
405
406	/*
407	 * Store the private key format version number
408	 */
409	dst_key_setprivateformat(key, major, minor);
410
411	READLINE(lex, opt, &token);
412
413	/*
414	 * Read the algorithm line.
415	 */
416	NEXTTOKEN(lex, opt, &token);
417	if (token.type != isc_tokentype_string ||
418	    strcmp(DST_AS_STR(token), ALGORITHM_STR) != 0)
419	{
420		ret = DST_R_INVALIDPRIVATEKEY;
421		goto fail;
422	}
423
424	NEXTTOKEN(lex, opt | ISC_LEXOPT_NUMBER, &token);
425	if (token.type != isc_tokentype_number ||
426	    token.value.as_ulong != (unsigned long) dst_key_alg(key))
427	{
428		ret = DST_R_INVALIDPRIVATEKEY;
429		goto fail;
430	}
431
432	READLINE(lex, opt, &token);
433
434	/*
435	 * Read the key data.
436	 */
437	for (n = 0; n < MAXFIELDS; n++) {
438		int tag;
439		isc_region_t r;
440		do {
441			ret = isc_lex_gettoken(lex, opt, &token);
442			if (ret == ISC_R_EOF)
443				goto done;
444			if (ret != ISC_R_SUCCESS)
445				goto fail;
446		} while (token.type == isc_tokentype_eol);
447
448		if (token.type != isc_tokentype_string) {
449			ret = DST_R_INVALIDPRIVATEKEY;
450			goto fail;
451		}
452
453		/* Numeric metadata */
454		tag = find_numericdata(DST_AS_STR(token));
455		if (tag >= 0) {
456			INSIST(tag < NUMERIC_NTAGS);
457
458			NEXTTOKEN(lex, opt | ISC_LEXOPT_NUMBER, &token);
459			if (token.type != isc_tokentype_number) {
460				ret = DST_R_INVALIDPRIVATEKEY;
461				goto fail;
462			}
463
464			dst_key_setnum(key, tag, token.value.as_ulong);
465			goto next;
466		}
467
468		/* Timing metadata */
469		tag = find_timedata(DST_AS_STR(token));
470		if (tag >= 0) {
471			INSIST(tag < TIMING_NTAGS);
472
473			NEXTTOKEN(lex, opt, &token);
474			if (token.type != isc_tokentype_string) {
475				ret = DST_R_INVALIDPRIVATEKEY;
476				goto fail;
477			}
478
479			ret = dns_time32_fromtext(DST_AS_STR(token), &when);
480			if (ret != ISC_R_SUCCESS)
481				goto fail;
482
483			dst_key_settime(key, tag, when);
484
485			goto next;
486		}
487
488		/* Key data */
489		tag = find_value(DST_AS_STR(token), alg);
490		if (tag < 0 && minor > DST_MINOR_VERSION)
491			goto next;
492		else if (tag < 0) {
493			ret = DST_R_INVALIDPRIVATEKEY;
494			goto fail;
495		}
496
497		priv->elements[n].tag = tag;
498
499		data = (unsigned char *) isc_mem_get(mctx, MAXFIELDSIZE);
500		if (data == NULL)
501			goto fail;
502
503		isc_buffer_init(&b, data, MAXFIELDSIZE);
504		ret = isc_base64_tobuffer(lex, &b, -1);
505		if (ret != ISC_R_SUCCESS)
506			goto fail;
507
508		isc_buffer_usedregion(&b, &r);
509		priv->elements[n].length = r.length;
510		priv->elements[n].data = r.base;
511		priv->nelements++;
512
513	  next:
514		READLINE(lex, opt, &token);
515		data = NULL;
516	}
517 done:
518	if (check_data(priv, alg, ISC_TRUE) < 0)
519		goto fail;
520
521	return (ISC_R_SUCCESS);
522
523fail:
524	dst__privstruct_free(priv, mctx);
525	if (data != NULL)
526		isc_mem_put(mctx, data, MAXFIELDSIZE);
527
528	return (ret);
529}
530
531isc_result_t
532dst__privstruct_writefile(const dst_key_t *key, const dst_private_t *priv,
533			  const char *directory)
534{
535	FILE *fp;
536	int ret, i;
537	isc_result_t result;
538	char filename[ISC_DIR_NAMEMAX];
539	char buffer[MAXFIELDSIZE * 2];
540	isc_fsaccess_t access;
541	isc_stdtime_t when;
542	isc_uint32_t value;
543	isc_buffer_t b;
544	isc_region_t r;
545	int major, minor;
546
547	REQUIRE(priv != NULL);
548
549	if (check_data(priv, dst_key_alg(key), ISC_FALSE) < 0)
550		return (DST_R_INVALIDPRIVATEKEY);
551
552	isc_buffer_init(&b, filename, sizeof(filename));
553	ret = dst_key_buildfilename(key, DST_TYPE_PRIVATE, directory, &b);
554	if (ret != ISC_R_SUCCESS)
555		return (ret);
556
557	if ((fp = fopen(filename, "w")) == NULL)
558		return (DST_R_WRITEERROR);
559
560	access = 0;
561	isc_fsaccess_add(ISC_FSACCESS_OWNER,
562			 ISC_FSACCESS_READ | ISC_FSACCESS_WRITE,
563			 &access);
564	(void)isc_fsaccess_set(filename, access);
565
566	dst_key_getprivateformat(key, &major, &minor);
567	if (major == 0 && minor == 0) {
568		major = DST_MAJOR_VERSION;
569		minor = DST_MINOR_VERSION;
570	}
571
572	/* XXXDCL return value should be checked for full filesystem */
573	fprintf(fp, "%s v%d.%d\n", PRIVATE_KEY_STR, major, minor);
574
575	fprintf(fp, "%s %d ", ALGORITHM_STR, dst_key_alg(key));
576
577	/* XXXVIX this switch statement is too sparse to gen a jump table. */
578	switch (dst_key_alg(key)) {
579	case DST_ALG_RSAMD5:
580		fprintf(fp, "(RSA)\n");
581		break;
582	case DST_ALG_DH:
583		fprintf(fp, "(DH)\n");
584		break;
585	case DST_ALG_DSA:
586		fprintf(fp, "(DSA)\n");
587		break;
588	case DST_ALG_RSASHA1:
589		fprintf(fp, "(RSASHA1)\n");
590		break;
591	case DST_ALG_NSEC3RSASHA1:
592		fprintf(fp, "(NSEC3RSASHA1)\n");
593		break;
594	case DST_ALG_NSEC3DSA:
595		fprintf(fp, "(NSEC3DSA)\n");
596		break;
597	case DST_ALG_RSASHA256:
598		fprintf(fp, "(RSASHA256)\n");
599		break;
600	case DST_ALG_RSASHA512:
601		fprintf(fp, "(RSASHA512)\n");
602		break;
603	case DST_ALG_ECCGOST:
604		fprintf(fp, "(ECC-GOST)\n");
605		break;
606	case DST_ALG_HMACMD5:
607		fprintf(fp, "(HMAC_MD5)\n");
608		break;
609	case DST_ALG_HMACSHA1:
610		fprintf(fp, "(HMAC_SHA1)\n");
611		break;
612	case DST_ALG_HMACSHA224:
613		fprintf(fp, "(HMAC_SHA224)\n");
614		break;
615	case DST_ALG_HMACSHA256:
616		fprintf(fp, "(HMAC_SHA256)\n");
617		break;
618	case DST_ALG_HMACSHA384:
619		fprintf(fp, "(HMAC_SHA384)\n");
620		break;
621	case DST_ALG_HMACSHA512:
622		fprintf(fp, "(HMAC_SHA512)\n");
623		break;
624	default:
625		fprintf(fp, "(?)\n");
626		break;
627	}
628
629	for (i = 0; i < priv->nelements; i++) {
630		const char *s;
631
632		s = find_tag(priv->elements[i].tag);
633
634		r.base = priv->elements[i].data;
635		r.length = priv->elements[i].length;
636		isc_buffer_init(&b, buffer, sizeof(buffer));
637		result = isc_base64_totext(&r, sizeof(buffer), "", &b);
638		if (result != ISC_R_SUCCESS) {
639			fclose(fp);
640			return (DST_R_INVALIDPRIVATEKEY);
641		}
642		isc_buffer_usedregion(&b, &r);
643
644	       fprintf(fp, "%s %.*s\n", s, (int)r.length, r.base);
645	}
646
647	/* Add the metadata tags */
648	if (major > 1 || (major == 1 && minor >= 3)) {
649		for (i = 0; i < NUMERIC_NTAGS; i++) {
650			result = dst_key_getnum(key, i, &value);
651			if (result != ISC_R_SUCCESS)
652				continue;
653			fprintf(fp, "%s %u\n", numerictags[i], value);
654		}
655		for (i = 0; i < TIMING_NTAGS; i++) {
656			result = dst_key_gettime(key, i, &when);
657			if (result != ISC_R_SUCCESS)
658				continue;
659
660			isc_buffer_init(&b, buffer, sizeof(buffer));
661			result = dns_time32_totext(when, &b);
662		       if (result != ISC_R_SUCCESS) {
663			       fclose(fp);
664			       return (DST_R_INVALIDPRIVATEKEY);
665		       }
666
667			isc_buffer_usedregion(&b, &r);
668
669		       fprintf(fp, "%s %.*s\n", timetags[i], (int)r.length,
670				r.base);
671		}
672	}
673
674	fflush(fp);
675	result = ferror(fp) ? DST_R_WRITEERROR : ISC_R_SUCCESS;
676	fclose(fp);
677	return (result);
678}
679
680/*! \file */