xref: /openbsd-src/usr.sbin/acme-client/json.c (revision c90a81c56dcebd6a1b73fe4aff9b03385b8e63b3)
1 /*	$Id: json.c,v 1.10 2017/11/27 01:58:52 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  * Extract an array from the returned JSON object, making sure that it's
234  * the correct type.
235  * Returns NULL on failure.
236  */
237 static struct jsmnn *
238 json_getarray(struct jsmnn *n, const char *name)
239 {
240 	size_t		 i;
241 
242 	if (n->type != JSMN_OBJECT)
243 		return NULL;
244 	for (i = 0; i < n->fields; i++) {
245 		if (n->d.obj[i].lhs->type != JSMN_STRING &&
246 		    n->d.obj[i].lhs->type != JSMN_PRIMITIVE)
247 			continue;
248 		else if (strcmp(name, n->d.obj[i].lhs->d.str))
249 			continue;
250 		break;
251 	}
252 	if (i == n->fields)
253 		return NULL;
254 	if (n->d.obj[i].rhs->type != JSMN_ARRAY)
255 		return NULL;
256 	return n->d.obj[i].rhs;
257 }
258 
259 /*
260  * Extract subtree from the returned JSON object, making sure that it's
261  * the correct type.
262  * Returns NULL on failure.
263  */
264 static struct jsmnn *
265 json_getobj(struct jsmnn *n, const char *name)
266 {
267 	size_t		 i;
268 
269 	if (n->type != JSMN_OBJECT)
270 		return NULL;
271 	for (i = 0; i < n->fields; i++) {
272 		if (n->d.obj[i].lhs->type != JSMN_STRING &&
273 		    n->d.obj[i].lhs->type != JSMN_PRIMITIVE)
274 			continue;
275 		else if (strcmp(name, n->d.obj[i].lhs->d.str))
276 			continue;
277 		break;
278 	}
279 	if (i == n->fields)
280 		return NULL;
281 	if (n->d.obj[i].rhs->type != JSMN_OBJECT)
282 		return NULL;
283 	return n->d.obj[i].rhs;
284 }
285 
286 /*
287  * Extract a single string from the returned JSON object, making sure
288  * that it's the correct type.
289  * Returns NULL on failure.
290  */
291 static char *
292 json_getstr(struct jsmnn *n, const char *name)
293 {
294 	size_t		 i;
295 	char		*cp;
296 
297 	if (n->type != JSMN_OBJECT)
298 		return NULL;
299 	for (i = 0; i < n->fields; i++) {
300 		if (n->d.obj[i].lhs->type != JSMN_STRING &&
301 		    n->d.obj[i].lhs->type != JSMN_PRIMITIVE)
302 			continue;
303 		else if (strcmp(name, n->d.obj[i].lhs->d.str))
304 			continue;
305 		break;
306 	}
307 	if (i == n->fields)
308 		return NULL;
309 	if (n->d.obj[i].rhs->type != JSMN_STRING &&
310 	    n->d.obj[i].rhs->type != JSMN_PRIMITIVE)
311 		return NULL;
312 
313 	cp = strdup(n->d.obj[i].rhs->d.str);
314 	if (cp == NULL)
315 		warn("strdup");
316 	return cp;
317 }
318 
319 /*
320  * Completely free the challenge response body.
321  */
322 void
323 json_free_challenge(struct chng *p)
324 {
325 
326 	free(p->uri);
327 	free(p->token);
328 	p->uri = p->token = NULL;
329 }
330 
331 /*
332  * Parse the response from the ACME server when we're waiting to see
333  * whether the challenge has been ok.
334  */
335 int
336 json_parse_response(struct jsmnn *n)
337 {
338 	char		*resp;
339 	int		 rc;
340 
341 	if (n == NULL)
342 		return -1;
343 	if ((resp = json_getstr(n, "status")) == NULL)
344 		return -1;
345 
346 	if (strcmp(resp, "valid") == 0)
347 		rc = 1;
348 	else if (strcmp(resp, "pending") == 0)
349 		rc = 0;
350 	else
351 		rc = -1;
352 
353 	free(resp);
354 	return rc;
355 }
356 
357 /*
358  * Parse the response from a new-authz, which consists of challenge
359  * information, into a structure.
360  * We only care about the HTTP-01 response.
361  */
362 int
363 json_parse_challenge(struct jsmnn *n, struct chng *p)
364 {
365 	struct jsmnn	*array, *obj;
366 	size_t		 i;
367 	int		 rc;
368 	char		*type;
369 
370 	if (n == NULL)
371 		return 0;
372 
373 	array = json_getarray(n, "challenges");
374 	if (array == NULL)
375 		return 0;
376 
377 	for (i = 0; i < array->fields; i++) {
378 		obj = json_getarrayobj(array->d.array[i]);
379 		if (obj == NULL)
380 			continue;
381 		type = json_getstr(obj, "type");
382 		if (type == NULL)
383 			continue;
384 		rc = strcmp(type, "http-01");
385 		free(type);
386 		if (rc)
387 			continue;
388 		p->uri = json_getstr(obj, "uri");
389 		p->token = json_getstr(obj, "token");
390 		return p->uri != NULL && p->token != NULL;
391 	}
392 
393 	return 0;
394 }
395 
396 /*
397  * Extract the CA paths from the JSON response object.
398  * Return zero on failure, non-zero on success.
399  */
400 int
401 json_parse_capaths(struct jsmnn *n, struct capaths *p)
402 {
403 	struct jsmnn	*meta;
404 
405 	if (n == NULL)
406 		return 0;
407 
408 	meta = json_getobj(n, "meta");
409 
410 	if (meta == NULL)
411 		return 0;
412 
413 	p->newauthz = json_getstr(n, "new-authz");
414 	p->newcert = json_getstr(n, "new-cert");
415 	p->newreg = json_getstr(n, "new-reg");
416 	p->revokecert = json_getstr(n, "revoke-cert");
417 	p->agreement = json_getstr(meta, "terms-of-service");
418 
419 	return p->newauthz != NULL && p->newcert != NULL &&
420 	    p->newreg != NULL && p->revokecert != NULL && p->agreement != NULL;
421 }
422 
423 /*
424  * Free up all of our CA-noted paths (which may all be NULL).
425  */
426 void
427 json_free_capaths(struct capaths *p)
428 {
429 
430 	free(p->newauthz);
431 	free(p->newcert);
432 	free(p->newreg);
433 	free(p->revokecert);
434 	free(p->agreement);
435 	memset(p, 0, sizeof(struct capaths));
436 }
437 
438 /*
439  * Parse an HTTP response body from a buffer of size "sz".
440  * Returns an opaque pointer on success, otherwise NULL on error.
441  */
442 struct jsmnn *
443 json_parse(const char *buf, size_t sz)
444 {
445 	struct jsmnn	*n;
446 	jsmn_parser	 p;
447 	jsmntok_t	*tok;
448 	int		 r;
449 	size_t		 tokcount;
450 
451 	jsmn_init(&p);
452 	tokcount = 128;
453 
454 	/* Do this until we don't need any more tokens. */
455 again:
456 	tok = calloc(tokcount, sizeof(jsmntok_t));
457 	if (tok == NULL) {
458 		warn("calloc");
459 		return NULL;
460 	}
461 
462 	/* Actually try to parse the JSON into the tokens. */
463 
464 	r = jsmn_parse(&p, buf, sz, tok, tokcount);
465 	if (r < 0 && r == JSMN_ERROR_NOMEM) {
466 		tokcount *= 2;
467 		free(tok);
468 		goto again;
469 	} else if (r < 0) {
470 		warnx("jsmn_parse: %d", r);
471 		free(tok);
472 		return NULL;
473 	}
474 
475 	/* Now parse the tokens into a tree. */
476 
477 	n = jsmntree_alloc(tok, buf, r);
478 	free(tok);
479 	return n;
480 }
481 
482 /*
483  * Format the "new-reg" resource request.
484  */
485 char *
486 json_fmt_newreg(const char *license)
487 {
488 	int	 c;
489 	char	*p;
490 
491 	c = asprintf(&p, "{"
492 	    "\"resource\": \"new-reg\", "
493 	    "\"agreement\": \"%s\""
494 	    "}",
495 	    license);
496 	if (c == -1) {
497 		warn("asprintf");
498 		p = NULL;
499 	}
500 	return p;
501 }
502 
503 /*
504  * Format the "new-authz" resource request.
505  */
506 char *
507 json_fmt_newauthz(const char *domain)
508 {
509 	int	 c;
510 	char	*p;
511 
512 	c = asprintf(&p, "{"
513 	    "\"resource\": \"new-authz\", "
514 	    "\"identifier\": "
515 	    "{\"type\": \"dns\", \"value\": \"%s\"}"
516 	    "}",
517 	    domain);
518 	if (c == -1) {
519 		warn("asprintf");
520 		p = NULL;
521 	}
522 	return p;
523 }
524 
525 /*
526  * Format the "challenge" resource request.
527  */
528 char *
529 json_fmt_challenge(const char *token, const char *thumb)
530 {
531 	int	 c;
532 	char	*p;
533 
534 	c = asprintf(&p, "{"
535 	    "\"resource\": \"challenge\", "
536 	    "\"keyAuthorization\": \"%s.%s\""
537 	    "}",
538 	    token, thumb);
539 	if (c == -1) {
540 		warn("asprintf");
541 		p = NULL;
542 	}
543 	return p;
544 }
545 
546 /*
547  * Format the "new-cert" resource request.
548  */
549 char *
550 json_fmt_revokecert(const char *cert)
551 {
552 	int	 c;
553 	char	*p;
554 
555 	c = asprintf(&p, "{"
556 	    "\"resource\": \"revoke-cert\", "
557 	    "\"certificate\": \"%s\""
558 	    "}",
559 	    cert);
560 	if (c == -1) {
561 		warn("asprintf");
562 		p = NULL;
563 	}
564 	return p;
565 }
566 
567 /*
568  * Format the "new-cert" resource request.
569  */
570 char *
571 json_fmt_newcert(const char *cert)
572 {
573 	int	 c;
574 	char	*p;
575 
576 	c = asprintf(&p, "{"
577 	    "\"resource\": \"new-cert\", "
578 	    "\"csr\": \"%s\""
579 	    "}",
580 	    cert);
581 	if (c == -1) {
582 		warn("asprintf");
583 		p = NULL;
584 	}
585 	return p;
586 }
587 
588 /*
589  * Header component of json_fmt_signed().
590  */
591 char *
592 json_fmt_header_rsa(const char *exp, const char *mod)
593 {
594 	int	 c;
595 	char	*p;
596 
597 	c = asprintf(&p, "{"
598 	    "\"alg\": \"RS256\", "
599 	    "\"jwk\": "
600 	    "{\"e\": \"%s\", \"kty\": \"RSA\", \"n\": \"%s\"}"
601 	    "}",
602 	    exp, mod);
603 	if (c == -1) {
604 		warn("asprintf");
605 		p = NULL;
606 	}
607 	return p;
608 }
609 
610 /*
611  * Protected component of json_fmt_signed().
612  */
613 char *
614 json_fmt_protected_rsa(const char *exp, const char *mod, const char *nce)
615 {
616 	int	 c;
617 	char	*p;
618 
619 	c = asprintf(&p, "{"
620 	    "\"alg\": \"RS256\", "
621 	    "\"jwk\": "
622 	    "{\"e\": \"%s\", \"kty\": \"RSA\", \"n\": \"%s\"}, "
623 	    "\"nonce\": \"%s\""
624 	    "}",
625 	    exp, mod, nce);
626 	if (c == -1) {
627 		warn("asprintf");
628 		p = NULL;
629 	}
630 	return p;
631 }
632 
633 /*
634  * Signed message contents for the CA server.
635  */
636 char *
637 json_fmt_signed(const char *header, const char *protected,
638     const char *payload, const char *digest)
639 {
640 	int	 c;
641 	char	*p;
642 
643 	c = asprintf(&p, "{"
644 	    "\"header\": %s, "
645 	    "\"protected\": \"%s\", "
646 	    "\"payload\": \"%s\", "
647 	    "\"signature\": \"%s\""
648 	    "}",
649 	    header, protected, payload, digest);
650 	if (c == -1) {
651 		warn("asprintf");
652 		p = NULL;
653 	}
654 	return p;
655 }
656 
657 /*
658  * Produce thumbprint input.
659  * This isn't technically a JSON string--it's the input we'll use for
660  * hashing and digesting.
661  * However, it's in the form of a JSON string, so do it here.
662  */
663 char *
664 json_fmt_thumb_rsa(const char *exp, const char *mod)
665 {
666 	int	 c;
667 	char	*p;
668 
669 	/*NOTE: WHITESPACE IS IMPORTANT. */
670 
671 	c = asprintf(&p, "{\"e\":\"%s\",\"kty\":\"RSA\",\"n\":\"%s\"}",
672 	    exp, mod);
673 	if (c == -1) {
674 		warn("asprintf");
675 		p = NULL;
676 	}
677 	return p;
678 }
679