xref: /openbsd-src/usr.sbin/acme-client/json.c (revision 1de67d341f75d4fd5f746e5ba8627ed24df2ec8f)
1 /*	$Id: json.c,v 1.16 2020/01/22 22:25:22 tedu Exp $ */
2 /*
3  * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>
4  *
5  * Permission to use, copy, modify, and 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 THE AUTHORS DISCLAIM ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
12  * 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
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 
18 #include <assert.h>
19 #include <err.h>
20 #include <stdarg.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <unistd.h>
25 
26 #include "jsmn.h"
27 #include "extern.h"
28 
29 struct	jsmnp;
30 
31 /*
32  * A node in the JSMN parse tree.
33  * Each of this corresponds to an object in the original JSMN token
34  * list, although the contents have been extracted properly.
35  */
36 struct	jsmnn {
37 	struct parse	*p; /* parser object */
38 	union {
39 		char *str; /* JSMN_PRIMITIVE, JSMN_STRING */
40 		struct jsmnp *obj; /* JSMN_OBJECT */
41 		struct jsmnn **array; /* JSMN_ARRAY */
42 	} d;
43 	size_t		 fields; /* entries in "d" */
44 	jsmntype_t	 type; /* type of node */
45 };
46 
47 /*
48  * Objects consist of node pairs: the left-hand side (before the colon)
49  * and the right-hand side---the data.
50  */
51 struct	jsmnp {
52 	struct jsmnn	*lhs; /* left of colon */
53 	struct jsmnn	*rhs; /* right of colon */
54 };
55 
56 /*
57  * Object for converting the JSMN token array into a tree.
58  */
59 struct	parse {
60 	struct jsmnn	*nodes; /* all nodes */
61 	size_t		 cur; /* current number */
62 	size_t		 max; /* nodes in "nodes" */
63 };
64 
65 /*
66  * Recursive part for convertin a JSMN token array into a tree.
67  * See "example/jsondump.c" for its construction (it's the same except
68  * for how it handles allocation errors).
69  */
70 static ssize_t
71 build(struct parse *parse, struct jsmnn **np,
72     jsmntok_t *t, const char *js, size_t sz)
73 {
74 	size_t		 i, j;
75 	struct jsmnn	*n;
76 	ssize_t		 tmp;
77 
78 	if (sz == 0)
79 		return 0;
80 
81 	assert(parse->cur < parse->max);
82 	n = *np = &parse->nodes[parse->cur++];
83 	n->p = parse;
84 	n->type = t->type;
85 
86 	switch (t->type) {
87 	case JSMN_STRING:
88 		/* FALLTHROUGH */
89 	case JSMN_PRIMITIVE:
90 		n->fields = 1;
91 		n->d.str = strndup
92 			(js + t->start,
93 			 t->end - t->start);
94 		if (n->d.str == NULL)
95 			break;
96 		return 1;
97 	case JSMN_OBJECT:
98 		n->fields = t->size;
99 		n->d.obj = calloc(n->fields,
100 			sizeof(struct jsmnp));
101 		if (n->d.obj == NULL)
102 			break;
103 		for (i = j = 0; i < (size_t)t->size; i++) {
104 			tmp = build(parse,
105 				&n->d.obj[i].lhs,
106 				t + 1 + j, js, sz - j);
107 			if (tmp < 0)
108 				break;
109 			j += tmp;
110 			tmp = build(parse,
111 				&n->d.obj[i].rhs,
112 				t + 1 + j, js, sz - j);
113 			if (tmp < 0)
114 				break;
115 			j += tmp;
116 		}
117 		if (i < (size_t)t->size)
118 			break;
119 		return j + 1;
120 	case JSMN_ARRAY:
121 		n->fields = t->size;
122 		n->d.array = calloc(n->fields,
123 			sizeof(struct jsmnn *));
124 		if (n->d.array == NULL)
125 			break;
126 		for (i = j = 0; i < (size_t)t->size; i++) {
127 			tmp = build(parse,
128 				&n->d.array[i],
129 				t + 1 + j, js, sz - j);
130 			if (tmp < 0)
131 				break;
132 			j += tmp;
133 		}
134 		if (i < (size_t)t->size)
135 			break;
136 		return j + 1;
137 	default:
138 		break;
139 	}
140 
141 	return -1;
142 }
143 
144 /*
145  * Fully free up a parse sequence.
146  * This handles all nodes sequentially, not recursively.
147  */
148 static void
149 jsmnparse_free(struct parse *p)
150 {
151 	size_t	 i;
152 
153 	if (p == NULL)
154 		return;
155 	for (i = 0; i < p->max; i++) {
156 		struct jsmnn	*n = &p->nodes[i];
157 		switch (n->type) {
158 		case JSMN_ARRAY:
159 			free(n->d.array);
160 			break;
161 		case JSMN_OBJECT:
162 			free(n->d.obj);
163 			break;
164 		case JSMN_PRIMITIVE:
165 			free(n->d.str);
166 			break;
167 		case JSMN_STRING:
168 			free(n->d.str);
169 			break;
170 		case JSMN_UNDEFINED:
171 			break;
172 		}
173 	}
174 	free(p->nodes);
175 	free(p);
176 }
177 
178 /*
179  * Allocate a tree representation of "t".
180  * This returns NULL on allocation failure or when sz is zero, in which
181  * case all resources allocated along the way are freed already.
182  */
183 static struct jsmnn *
184 jsmntree_alloc(jsmntok_t *t, const char *js, size_t sz)
185 {
186 	struct jsmnn	*first;
187 	struct parse	*p;
188 
189 	if (sz == 0)
190 		return NULL;
191 
192 	p = calloc(1, sizeof(struct parse));
193 	if (p == NULL)
194 		return NULL;
195 
196 	p->max = sz;
197 	p->nodes = calloc(p->max, sizeof(struct jsmnn));
198 	if (p->nodes == NULL) {
199 		free(p);
200 		return NULL;
201 	}
202 
203 	if (build(p, &first, t, js, sz) < 0) {
204 		jsmnparse_free(p);
205 		first = NULL;
206 	}
207 
208 	return first;
209 }
210 
211 /*
212  * Call through to free parse contents.
213  */
214 void
215 json_free(struct jsmnn *first)
216 {
217 
218 	if (first != NULL)
219 		jsmnparse_free(first->p);
220 }
221 
222 /*
223  * Just check that the array object is in fact an object.
224  */
225 static struct jsmnn *
226 json_getarrayobj(struct jsmnn *n)
227 {
228 
229 	return n->type != JSMN_OBJECT ? NULL : n;
230 }
231 
232 /*
233  * Get a string element from an array
234  */
235 static char *
236 json_getarraystr(struct jsmnn *n)
237 {
238 	return n->type != JSMN_STRING ? NULL : n->d.str;
239 }
240 
241 /*
242  * Extract an array from the returned JSON object, making sure that it's
243  * the correct type.
244  * Returns NULL on failure.
245  */
246 static struct jsmnn *
247 json_getarray(struct jsmnn *n, const char *name)
248 {
249 	size_t		 i;
250 
251 	if (n->type != JSMN_OBJECT)
252 		return NULL;
253 	for (i = 0; i < n->fields; i++) {
254 		if (n->d.obj[i].lhs->type != JSMN_STRING &&
255 		    n->d.obj[i].lhs->type != JSMN_PRIMITIVE)
256 			continue;
257 		else if (strcmp(name, n->d.obj[i].lhs->d.str))
258 			continue;
259 		break;
260 	}
261 	if (i == n->fields)
262 		return NULL;
263 	if (n->d.obj[i].rhs->type != JSMN_ARRAY)
264 		return NULL;
265 	return n->d.obj[i].rhs;
266 }
267 
268 #ifdef notyet
269 /*
270  * Extract subtree from the returned JSON object, making sure that it's
271  * the correct type.
272  * Returns NULL on failure.
273  */
274 static struct jsmnn *
275 json_getobj(struct jsmnn *n, const char *name)
276 {
277 	size_t		 i;
278 
279 	if (n->type != JSMN_OBJECT)
280 		return NULL;
281 	for (i = 0; i < n->fields; i++) {
282 		if (n->d.obj[i].lhs->type != JSMN_STRING &&
283 		    n->d.obj[i].lhs->type != JSMN_PRIMITIVE)
284 			continue;
285 		else if (strcmp(name, n->d.obj[i].lhs->d.str))
286 			continue;
287 		break;
288 	}
289 	if (i == n->fields)
290 		return NULL;
291 	if (n->d.obj[i].rhs->type != JSMN_OBJECT)
292 		return NULL;
293 	return n->d.obj[i].rhs;
294 }
295 #endif /* notyet */
296 
297 /*
298  * Extract a single string from the returned JSON object, making sure
299  * that it's the correct type.
300  * Returns NULL on failure.
301  */
302 static char *
303 json_getstr(struct jsmnn *n, const char *name)
304 {
305 	size_t		 i;
306 	char		*cp;
307 
308 	if (n->type != JSMN_OBJECT)
309 		return NULL;
310 	for (i = 0; i < n->fields; i++) {
311 		if (n->d.obj[i].lhs->type != JSMN_STRING &&
312 		    n->d.obj[i].lhs->type != JSMN_PRIMITIVE)
313 			continue;
314 		else if (strcmp(name, n->d.obj[i].lhs->d.str))
315 			continue;
316 		break;
317 	}
318 	if (i == n->fields)
319 		return NULL;
320 	if (n->d.obj[i].rhs->type != JSMN_STRING &&
321 	    n->d.obj[i].rhs->type != JSMN_PRIMITIVE)
322 		return NULL;
323 
324 	cp = strdup(n->d.obj[i].rhs->d.str);
325 	if (cp == NULL)
326 		warn("strdup");
327 	return cp;
328 }
329 
330 /*
331  * Completely free the challenge response body.
332  */
333 void
334 json_free_challenge(struct chng *p)
335 {
336 
337 	free(p->uri);
338 	free(p->token);
339 	p->uri = p->token = NULL;
340 }
341 
342 /*
343  * Parse the response from the ACME server when we're waiting to see
344  * whether the challenge has been ok.
345  */
346 enum chngstatus
347 json_parse_response(struct jsmnn *n)
348 {
349 	char		*resp;
350 	enum chngstatus	 rc;
351 
352 	if (n == NULL)
353 		return CHNG_INVALID;
354 	if ((resp = json_getstr(n, "status")) == NULL)
355 		return CHNG_INVALID;
356 
357 	if (strcmp(resp, "valid") == 0)
358 		rc = CHNG_VALID;
359 	else if (strcmp(resp, "pending") == 0)
360 		rc = CHNG_PENDING;
361 	else if (strcmp(resp, "processing") == 0)
362 		rc = CHNG_PROCESSING;
363 	else
364 		rc = CHNG_INVALID;
365 
366 	free(resp);
367 	return rc;
368 }
369 
370 /*
371  * Parse the response from a new-authz, which consists of challenge
372  * information, into a structure.
373  * We only care about the HTTP-01 response.
374  */
375 int
376 json_parse_challenge(struct jsmnn *n, struct chng *p)
377 {
378 	struct jsmnn	*array, *obj;
379 	size_t		 i;
380 	int		 rc;
381 	char		*type;
382 
383 	if (n == NULL)
384 		return 0;
385 
386 	array = json_getarray(n, "challenges");
387 	if (array == NULL)
388 		return 0;
389 
390 	for (i = 0; i < array->fields; i++) {
391 		obj = json_getarrayobj(array->d.array[i]);
392 		if (obj == NULL)
393 			continue;
394 		type = json_getstr(obj, "type");
395 		if (type == NULL)
396 			continue;
397 		rc = strcmp(type, "http-01");
398 		free(type);
399 		if (rc)
400 			continue;
401 		p->uri = json_getstr(obj, "url");
402 		p->token = json_getstr(obj, "token");
403 		p->status = json_parse_response(obj);
404 		return p->uri != NULL && p->token != NULL;
405 	}
406 
407 	return 0;
408 }
409 
410 static enum orderstatus
411 json_parse_order_status(struct jsmnn *n)
412 {
413 	char	*status;
414 
415 	if (n == NULL)
416 		return ORDER_INVALID;
417 
418 	if ((status = json_getstr(n, "status")) == NULL)
419 		return ORDER_INVALID;
420 
421 	if (strcmp(status, "pending") == 0)
422 		return ORDER_PENDING;
423 	else if (strcmp(status, "ready") == 0)
424 		return ORDER_READY;
425 	else if (strcmp(status, "processing") == 0)
426 		return ORDER_PROCESSING;
427 	else if (strcmp(status, "valid") == 0)
428 		return ORDER_VALID;
429 	else if (strcmp(status, "invalid") == 0)
430 		return ORDER_INVALID;
431 	else
432 		return ORDER_INVALID;
433 }
434 
435 /*
436  * Parse the response from a newOrder, which consists of a status
437  * a list of authorization urls and a finalize url into a struct.
438  */
439 int
440 json_parse_order(struct jsmnn *n, struct order *order)
441 {
442 	struct jsmnn	*array;
443 	size_t		 i;
444 	char		*finalize, *str;
445 
446 	order->status = json_parse_order_status(n);
447 
448 	if (n == NULL)
449 		return 0;
450 
451 	if ((finalize = json_getstr(n, "finalize")) == NULL) {
452 		warnx("no finalize field in order response");
453 		return 0;
454 	}
455 
456 	if ((order->finalize = strdup(finalize)) == NULL)
457 		goto err;
458 
459 	if ((array = json_getarray(n, "authorizations")) == NULL)
460 		goto err;
461 
462 	if (array->fields > 0) {
463 		order->auths = calloc(sizeof(*order->auths), array->fields);
464 		if (order->auths == NULL) {
465 			warn("malloc");
466 			goto err;
467 		}
468 		order->authsz = array->fields;
469 	}
470 
471 	for (i = 0; i < array->fields; i++) {
472 		str = json_getarraystr(array->d.array[i]);
473 		if (str == NULL)
474 			continue;
475 		if ((order->auths[i] = strdup(str)) == NULL) {
476 			warn("strdup");
477 			goto err;
478 		}
479 	}
480 	return 1;
481 err:
482 	json_free_order(order);
483 	return 0;
484 }
485 
486 int
487 json_parse_upd_order(struct jsmnn *n, struct order *order)
488 {
489 	char	*certificate;
490 	order->status = json_parse_order_status(n);
491 	if ((certificate = json_getstr(n, "certificate")) != NULL) {
492 		if ((order->certificate = strdup(certificate)) == NULL)
493 			return 0;
494 	}
495 	return 1;
496 }
497 
498 void
499 json_free_order(struct order *order)
500 {
501 	size_t i;
502 
503 	free(order->finalize);
504 	order->finalize = NULL;
505 	for(i = 0; i < order->authsz; i++)
506 		free(order->auths[i]);
507 	free(order->auths);
508 
509 	order->finalize = NULL;
510 	order->auths = NULL;
511 	order->authsz = 0;
512 }
513 
514 /*
515  * Extract the CA paths from the JSON response object.
516  * Return zero on failure, non-zero on success.
517  */
518 int
519 json_parse_capaths(struct jsmnn *n, struct capaths *p)
520 {
521 	if (n == NULL)
522 		return 0;
523 
524 	p->newaccount = json_getstr(n, "newAccount");
525 	p->newnonce = json_getstr(n, "newNonce");
526 	p->neworder = json_getstr(n, "newOrder");
527 	p->revokecert = json_getstr(n, "revokeCert");
528 
529 	return p->newaccount != NULL && p->newnonce != NULL &&
530 	    p->neworder != NULL && p->revokecert != NULL;
531 }
532 
533 /*
534  * Free up all of our CA-noted paths (which may all be NULL).
535  */
536 void
537 json_free_capaths(struct capaths *p)
538 {
539 
540 	free(p->newaccount);
541 	free(p->newnonce);
542 	free(p->neworder);
543 	free(p->revokecert);
544 	memset(p, 0, sizeof(struct capaths));
545 }
546 
547 /*
548  * Parse an HTTP response body from a buffer of size "sz".
549  * Returns an opaque pointer on success, otherwise NULL on error.
550  */
551 struct jsmnn *
552 json_parse(const char *buf, size_t sz)
553 {
554 	struct jsmnn	*n;
555 	jsmn_parser	 p;
556 	jsmntok_t	*tok, *ntok;
557 	int		 r;
558 	size_t		 tokcount;
559 
560 	jsmn_init(&p);
561 	tokcount = 128;
562 
563 	if ((tok = calloc(tokcount, sizeof(jsmntok_t))) == NULL) {
564 		warn("calloc");
565 		return NULL;
566 	}
567 
568 	/* Do this until we don't need any more tokens. */
569 again:
570 	/* Actually try to parse the JSON into the tokens. */
571 	r = jsmn_parse(&p, buf, sz, tok, tokcount);
572 	if (r < 0 && r == JSMN_ERROR_NOMEM) {
573 		if ((ntok = recallocarray(tok, tokcount, tokcount * 2,
574 		    sizeof(jsmntok_t))) == NULL) {
575 			warn("calloc");
576 			free(tok);
577 			return NULL;
578 		}
579 		tok = ntok;
580 		tokcount *= 2;
581 		goto again;
582 	} else if (r < 0) {
583 		warnx("jsmn_parse: %d", r);
584 		free(tok);
585 		return NULL;
586 	}
587 
588 	/* Now parse the tokens into a tree. */
589 
590 	n = jsmntree_alloc(tok, buf, r);
591 	free(tok);
592 	return n;
593 }
594 
595 /*
596  * Format the "newAccount" resource request to check if the account exist.
597  */
598 char *
599 json_fmt_chkacc(void)
600 {
601 	int	 c;
602 	char	*p;
603 
604 	c = asprintf(&p, "{"
605 	    "\"termsOfServiceAgreed\": true, "
606 	    "\"onlyReturnExisting\": true"
607 	    "}");
608 	if (c == -1) {
609 		warn("asprintf");
610 		p = NULL;
611 	}
612 	return p;
613 }
614 
615 /*
616  * Format the "newAccount" resource request.
617  */
618 char *
619 json_fmt_newacc(void)
620 {
621 	int	 c;
622 	char	*p;
623 
624 	c = asprintf(&p, "{"
625 	    "\"termsOfServiceAgreed\": true"
626 	    "}");
627 	if (c == -1) {
628 		warn("asprintf");
629 		p = NULL;
630 	}
631 	return p;
632 }
633 
634 /*
635  * Format the "newOrder" resource request
636  */
637 char *
638 json_fmt_neworder(const char *const *alts, size_t altsz)
639 {
640 	size_t	 i;
641 	int	 c;
642 	char	*p, *t;
643 
644 	if ((p = strdup("{ \"identifiers\": [")) == NULL)
645 		goto err;
646 
647 	t = p;
648 	for (i = 0; i < altsz; i++) {
649 		c = asprintf(&p,
650 		    "%s { \"type\": \"dns\", \"value\": \"%s\" }%s",
651 		    t, alts[i], i + 1 == altsz ? "" : ",");
652 		free(t);
653 		if (c == -1) {
654 			warn("asprintf");
655 			p = NULL;
656 			goto err;
657 		}
658 		t = p;
659 	}
660 	c = asprintf(&p, "%s ] }", t);
661 	free(t);
662 	if (c == -1) {
663 		warn("asprintf");
664 		p = NULL;
665 	}
666 	return p;
667 err:
668 	free(p);
669 	return NULL;
670 }
671 
672 /*
673  * Format the revoke resource request.
674  */
675 char *
676 json_fmt_revokecert(const char *cert)
677 {
678 	int	 c;
679 	char	*p;
680 
681 	c = asprintf(&p, "{"
682 	    "\"certificate\": \"%s\""
683 	    "}",
684 	    cert);
685 	if (c == -1) {
686 		warn("asprintf");
687 		p = NULL;
688 	}
689 	return p;
690 }
691 
692 /*
693  * Format the "new-cert" resource request.
694  */
695 char *
696 json_fmt_newcert(const char *cert)
697 {
698 	int	 c;
699 	char	*p;
700 
701 	c = asprintf(&p, "{"
702 	    "\"csr\": \"%s\""
703 	    "}",
704 	    cert);
705 	if (c == -1) {
706 		warn("asprintf");
707 		p = NULL;
708 	}
709 	return p;
710 }
711 
712 /*
713  * Protected component of json_fmt_signed().
714  */
715 char *
716 json_fmt_protected_rsa(const char *exp, const char *mod, const char *nce,
717     const char *url)
718 {
719 	int	 c;
720 	char	*p;
721 
722 	c = asprintf(&p, "{"
723 	    "\"alg\": \"RS256\", "
724 	    "\"jwk\": "
725 	    "{\"e\": \"%s\", \"kty\": \"RSA\", \"n\": \"%s\"}, "
726 	    "\"nonce\": \"%s\", "
727 	    "\"url\": \"%s\""
728 	    "}",
729 	    exp, mod, nce, url);
730 	if (c == -1) {
731 		warn("asprintf");
732 		p = NULL;
733 	}
734 	return p;
735 }
736 
737 /*
738  * Protected component of json_fmt_signed().
739  */
740 char *
741 json_fmt_protected_ec(const char *x, const char *y, const char *nce,
742     const char *url)
743 {
744 	int	 c;
745 	char	*p;
746 
747 	c = asprintf(&p, "{"
748 	    "\"alg\": \"ES384\", "
749 	    "\"jwk\": "
750 	    "{\"crv\": \"P-384\", \"kty\": \"EC\", \"x\": \"%s\", "
751 	    "\"y\": \"%s\"}, \"nonce\": \"%s\", \"url\": \"%s\""
752 	    "}",
753 	    x, y, nce, url);
754 	if (c == -1) {
755 		warn("asprintf");
756 		p = NULL;
757 	}
758 	return p;
759 }
760 
761 /*
762  * Protected component of json_fmt_signed().
763  */
764 char *
765 json_fmt_protected_kid(const char *alg, const char *kid, const char *nce,
766     const char *url)
767 {
768 	int	 c;
769 	char	*p;
770 
771 	c = asprintf(&p, "{"
772 	    "\"alg\": \"%s\", "
773 	    "\"kid\": \"%s\", "
774 	    "\"nonce\": \"%s\", "
775 	    "\"url\": \"%s\""
776 	    "}",
777 	    alg, kid, nce, url);
778 	if (c == -1) {
779 		warn("asprintf");
780 		p = NULL;
781 	}
782 	return p;
783 }
784 
785 /*
786  * Signed message contents for the CA server.
787  */
788 char *
789 json_fmt_signed(const char *protected, const char *payload, const char *digest)
790 {
791 	int	 c;
792 	char	*p;
793 
794 	c = asprintf(&p, "{"
795 	    "\"protected\": \"%s\", "
796 	    "\"payload\": \"%s\", "
797 	    "\"signature\": \"%s\""
798 	    "}",
799 	    protected, payload, digest);
800 	if (c == -1) {
801 		warn("asprintf");
802 		p = NULL;
803 	}
804 	return p;
805 }
806 
807 /*
808  * Produce thumbprint input.
809  * This isn't technically a JSON string--it's the input we'll use for
810  * hashing and digesting.
811  * However, it's in the form of a JSON string, so do it here.
812  */
813 char *
814 json_fmt_thumb_rsa(const char *exp, const char *mod)
815 {
816 	int	 c;
817 	char	*p;
818 
819 	/*NOTE: WHITESPACE IS IMPORTANT. */
820 
821 	c = asprintf(&p, "{\"e\":\"%s\",\"kty\":\"RSA\",\"n\":\"%s\"}",
822 	    exp, mod);
823 	if (c == -1) {
824 		warn("asprintf");
825 		p = NULL;
826 	}
827 	return p;
828 }
829 
830 /*
831  * Produce thumbprint input.
832  * This isn't technically a JSON string--it's the input we'll use for
833  * hashing and digesting.
834  * However, it's in the form of a JSON string, so do it here.
835  */
836 char *
837 json_fmt_thumb_ec(const char *x, const char *y)
838 {
839 	int	 c;
840 	char	*p;
841 
842 	/*NOTE: WHITESPACE IS IMPORTANT. */
843 
844 	c = asprintf(&p, "{\"crv\":\"P-384\",\"kty\":\"EC\",\"x\":\"%s\","
845 	    "\"y\":\"%s\"}",
846 	    x, y);
847 	if (c == -1) {
848 		warn("asprintf");
849 		p = NULL;
850 	}
851 	return p;
852 }
853