xref: /openbsd-src/usr.sbin/acme-client/json.c (revision fd36792fdcf4a0c7235b470efde3dfc1d00c2e51)
1 /*	$Id: json.c,v 1.14 2019/06/18 18:50:07 florian 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 ((order->authsz = array->fields) > 0) {
463 		order->auths = calloc(sizeof(*order->auths), order->authsz);
464 		if (order->auths == NULL) {
465 			warn("malloc");
466 			goto err;
467 		}
468 	}
469 
470 	for (i = 0; i < array->fields; i++) {
471 		str = json_getarraystr(array->d.array[i]);
472 		if (str == NULL)
473 			continue;
474 		if ((order->auths[i] = strdup(str)) == NULL) {
475 			warn("strdup");
476 			goto err;
477 		}
478 	}
479 	return 1;
480 err:
481 	json_free_order(order);
482 	return 0;
483 }
484 
485 int
486 json_parse_upd_order(struct jsmnn *n, struct order *order)
487 {
488 	char	*certificate;
489 	order->status = json_parse_order_status(n);
490 	if ((certificate = json_getstr(n, "certificate")) != NULL) {
491 		if ((order->certificate = strdup(certificate)) == NULL)
492 			return 0;
493 	}
494 	return 1;
495 }
496 
497 void
498 json_free_order(struct order *order)
499 {
500 	size_t i;
501 
502 	free(order->finalize);
503 	order->finalize = NULL;
504 	for(i = 0; i < order->authsz; i++)
505 		free(order->auths[i]);
506 	free(order->auths);
507 
508 	order->finalize = NULL;
509 	order->auths = NULL;
510 	order->authsz = 0;
511 }
512 
513 /*
514  * Extract the CA paths from the JSON response object.
515  * Return zero on failure, non-zero on success.
516  */
517 int
518 json_parse_capaths(struct jsmnn *n, struct capaths *p)
519 {
520 	if (n == NULL)
521 		return 0;
522 
523 	p->newaccount = json_getstr(n, "newAccount");
524 	p->newnonce = json_getstr(n, "newNonce");
525 	p->neworder = json_getstr(n, "newOrder");
526 	p->revokecert = json_getstr(n, "revokeCert");
527 
528 	return p->newaccount != NULL && p->newnonce != NULL &&
529 	    p->neworder != NULL && p->revokecert != NULL;
530 }
531 
532 /*
533  * Free up all of our CA-noted paths (which may all be NULL).
534  */
535 void
536 json_free_capaths(struct capaths *p)
537 {
538 
539 	free(p->newaccount);
540 	free(p->newnonce);
541 	free(p->neworder);
542 	free(p->revokecert);
543 	memset(p, 0, sizeof(struct capaths));
544 }
545 
546 /*
547  * Parse an HTTP response body from a buffer of size "sz".
548  * Returns an opaque pointer on success, otherwise NULL on error.
549  */
550 struct jsmnn *
551 json_parse(const char *buf, size_t sz)
552 {
553 	struct jsmnn	*n;
554 	jsmn_parser	 p;
555 	jsmntok_t	*tok, *ntok;
556 	int		 r;
557 	size_t		 tokcount;
558 
559 	jsmn_init(&p);
560 	tokcount = 128;
561 
562 	if ((tok = calloc(tokcount, sizeof(jsmntok_t))) == NULL) {
563 		warn("calloc");
564 		return NULL;
565 	}
566 
567 	/* Do this until we don't need any more tokens. */
568 again:
569 	/* Actually try to parse the JSON into the tokens. */
570 	r = jsmn_parse(&p, buf, sz, tok, tokcount);
571 	if (r < 0 && r == JSMN_ERROR_NOMEM) {
572 		if ((ntok = recallocarray(tok, tokcount, tokcount * 2,
573 		    sizeof(jsmntok_t))) == NULL) {
574 			warn("calloc");
575 			free(tok);
576 			return NULL;
577 		}
578 		tok = ntok;
579 		tokcount *= 2;
580 		goto again;
581 	} else if (r < 0) {
582 		warnx("jsmn_parse: %d", r);
583 		free(tok);
584 		return NULL;
585 	}
586 
587 	/* Now parse the tokens into a tree. */
588 
589 	n = jsmntree_alloc(tok, buf, r);
590 	free(tok);
591 	return n;
592 }
593 
594 /*
595  * Format the "newAccount" resource request to check if the account exist.
596  */
597 char *
598 json_fmt_chkacc(void)
599 {
600 	int	 c;
601 	char	*p;
602 
603 	c = asprintf(&p, "{"
604 	    "\"termsOfServiceAgreed\": true, "
605 	    "\"onlyReturnExisting\": true"
606 	    "}");
607 	if (c == -1) {
608 		warn("asprintf");
609 		p = NULL;
610 	}
611 	return p;
612 }
613 
614 /*
615  * Format the "newAccount" resource request.
616  */
617 char *
618 json_fmt_newacc(void)
619 {
620 	int	 c;
621 	char	*p;
622 
623 	c = asprintf(&p, "{"
624 	    "\"termsOfServiceAgreed\": true"
625 	    "}");
626 	if (c == -1) {
627 		warn("asprintf");
628 		p = NULL;
629 	}
630 	return p;
631 }
632 
633 /*
634  * Format the "newOrder" resource request
635  */
636 char *
637 json_fmt_neworder(const char *const *alts, size_t altsz)
638 {
639 	size_t	 i;
640 	int	 c;
641 	char	*p, *t;
642 
643 	if ((p = strdup("{ \"identifiers\": [")) == NULL)
644 		goto err;
645 
646 	t = p;
647 	for (i = 0; i < altsz; i++) {
648 		c = asprintf(&p,
649 		    "%s { \"type\": \"dns\", \"value\": \"%s\" }%s",
650 		    t, alts[i], i + 1 == altsz ? "" : ",");
651 		free(t);
652 		if (c == -1) {
653 			warn("asprintf");
654 			p = NULL;
655 			goto err;
656 		}
657 		t = p;
658 	}
659 	c = asprintf(&p, "%s ] }", t);
660 	free(t);
661 	if (c == -1) {
662 		warn("asprintf");
663 		p = NULL;
664 	}
665 	return p;
666 err:
667 	free(p);
668 	return NULL;
669 }
670 
671 /*
672  * Format the revoke resource request.
673  */
674 char *
675 json_fmt_revokecert(const char *cert)
676 {
677 	int	 c;
678 	char	*p;
679 
680 	c = asprintf(&p, "{"
681 	    "\"certificate\": \"%s\""
682 	    "}",
683 	    cert);
684 	if (c == -1) {
685 		warn("asprintf");
686 		p = NULL;
687 	}
688 	return p;
689 }
690 
691 /*
692  * Format the "new-cert" resource request.
693  */
694 char *
695 json_fmt_newcert(const char *cert)
696 {
697 	int	 c;
698 	char	*p;
699 
700 	c = asprintf(&p, "{"
701 	    "\"csr\": \"%s\""
702 	    "}",
703 	    cert);
704 	if (c == -1) {
705 		warn("asprintf");
706 		p = NULL;
707 	}
708 	return p;
709 }
710 
711 /*
712  * Protected component of json_fmt_signed().
713  */
714 char *
715 json_fmt_protected_rsa(const char *exp, const char *mod, const char *nce,
716     const char *url)
717 {
718 	int	 c;
719 	char	*p;
720 
721 	c = asprintf(&p, "{"
722 	    "\"alg\": \"RS256\", "
723 	    "\"jwk\": "
724 	    "{\"e\": \"%s\", \"kty\": \"RSA\", \"n\": \"%s\"}, "
725 	    "\"nonce\": \"%s\", "
726 	    "\"url\": \"%s\""
727 	    "}",
728 	    exp, mod, nce, url);
729 	if (c == -1) {
730 		warn("asprintf");
731 		p = NULL;
732 	}
733 	return p;
734 }
735 
736 /*
737  * Protected component of json_fmt_signed().
738  */
739 char *
740 json_fmt_protected_ec(const char *x, const char *y, const char *nce,
741     const char *url)
742 {
743 	int	 c;
744 	char	*p;
745 
746 	c = asprintf(&p, "{"
747 	    "\"alg\": \"ES384\", "
748 	    "\"jwk\": "
749 	    "{\"crv\": \"P-384\", \"kty\": \"EC\", \"x\": \"%s\", "
750 	    "\"y\": \"%s\"}, \"nonce\": \"%s\", \"url\": \"%s\""
751 	    "}",
752 	    x, y, nce, url);
753 	if (c == -1) {
754 		warn("asprintf");
755 		p = NULL;
756 	}
757 	return p;
758 }
759 
760 /*
761  * Protected component of json_fmt_signed().
762  */
763 char *
764 json_fmt_protected_kid(const char *alg, const char *kid, const char *nce,
765     const char *url)
766 {
767 	int	 c;
768 	char	*p;
769 
770 	c = asprintf(&p, "{"
771 	    "\"alg\": \"%s\", "
772 	    "\"kid\": \"%s\", "
773 	    "\"nonce\": \"%s\", "
774 	    "\"url\": \"%s\""
775 	    "}",
776 	    alg, kid, nce, url);
777 	if (c == -1) {
778 		warn("asprintf");
779 		p = NULL;
780 	}
781 	return p;
782 }
783 
784 /*
785  * Signed message contents for the CA server.
786  */
787 char *
788 json_fmt_signed(const char *protected, const char *payload, const char *digest)
789 {
790 	int	 c;
791 	char	*p;
792 
793 	c = asprintf(&p, "{"
794 	    "\"protected\": \"%s\", "
795 	    "\"payload\": \"%s\", "
796 	    "\"signature\": \"%s\""
797 	    "}",
798 	    protected, payload, digest);
799 	if (c == -1) {
800 		warn("asprintf");
801 		p = NULL;
802 	}
803 	return p;
804 }
805 
806 /*
807  * Produce thumbprint input.
808  * This isn't technically a JSON string--it's the input we'll use for
809  * hashing and digesting.
810  * However, it's in the form of a JSON string, so do it here.
811  */
812 char *
813 json_fmt_thumb_rsa(const char *exp, const char *mod)
814 {
815 	int	 c;
816 	char	*p;
817 
818 	/*NOTE: WHITESPACE IS IMPORTANT. */
819 
820 	c = asprintf(&p, "{\"e\":\"%s\",\"kty\":\"RSA\",\"n\":\"%s\"}",
821 	    exp, mod);
822 	if (c == -1) {
823 		warn("asprintf");
824 		p = NULL;
825 	}
826 	return p;
827 }
828 
829 /*
830  * Produce thumbprint input.
831  * This isn't technically a JSON string--it's the input we'll use for
832  * hashing and digesting.
833  * However, it's in the form of a JSON string, so do it here.
834  */
835 char *
836 json_fmt_thumb_ec(const char *x, const char *y)
837 {
838 	int	 c;
839 	char	*p;
840 
841 	/*NOTE: WHITESPACE IS IMPORTANT. */
842 
843 	c = asprintf(&p, "{\"crv\":\"P-384\",\"kty\":\"EC\",\"x\":\"%s\","
844 	    "\"y\":\"%s\"}",
845 	    x, y);
846 	if (c == -1) {
847 		warn("asprintf");
848 		p = NULL;
849 	}
850 	return p;
851 }
852