xref: /openbsd-src/usr.sbin/acme-client/json.c (revision a869955907a92e9a9bc4920ebf06fad69f0bf35e)
1 /*	$Id: json.c,v 1.4 2016/09/13 16:04:51 deraadt 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 (0 == sz)
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 (NULL == n->d.str)
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 (NULL == n->d.obj)
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 (NULL == n->d.array)
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 (NULL == p)
154 		return;
155 	for (i = 0; i < p->max; i++)
156 		if (JSMN_ARRAY == p->nodes[i].type)
157 			free(p->nodes[i].d.array);
158 		else if (JSMN_OBJECT == p->nodes[i].type)
159 			free(p->nodes[i].d.obj);
160 		else if (JSMN_PRIMITIVE == p->nodes[i].type)
161 			free(p->nodes[i].d.str);
162 		else if (JSMN_STRING == p->nodes[i].type)
163 			free(p->nodes[i].d.str);
164 	free(p->nodes);
165 	free(p);
166 }
167 
168 /*
169  * Allocate a tree representation of "t".
170  * This returns NULL on allocation failure or when sz is zero, in which
171  * case all resources allocated along the way are freed already.
172  */
173 static struct jsmnn *
174 jsmntree_alloc(jsmntok_t *t, const char *js, size_t sz)
175 {
176 	struct jsmnn	*first;
177 	struct parse	*p;
178 
179 	if (0 == sz)
180 		return (NULL);
181 
182 	p = calloc(1, sizeof(struct parse));
183 	if (NULL == p)
184 		return (NULL);
185 
186 	p->max = sz;
187 	p->nodes = calloc(p->max, sizeof(struct jsmnn));
188 	if (NULL == p->nodes) {
189 		free(p);
190 		return (NULL);
191 	}
192 
193 	if (build(p, &first, t, js, sz) < 0) {
194 		jsmnparse_free(p);
195 		first = NULL;
196 	}
197 
198 	return (first);
199 }
200 
201 /*
202  * Call through to free parse contents.
203  */
204 void
205 json_free(struct jsmnn *first)
206 {
207 
208 	if (NULL != first)
209 		jsmnparse_free(first->p);
210 }
211 
212 /*
213  * Just check that the array object is in fact an object.
214  */
215 static struct jsmnn *
216 json_getarrayobj(struct jsmnn *n)
217 {
218 
219 	return (JSMN_OBJECT != n->type ? NULL : n);
220 }
221 
222 /*
223  * Extract an array from the returned JSON object, making sure that it's
224  * the correct type.
225  * Returns NULL on failure.
226  */
227 static struct jsmnn *
228 json_getarray(struct jsmnn *n, const char *name)
229 {
230 	size_t		 i;
231 
232 	if (JSMN_OBJECT != n->type)
233 		return (NULL);
234 	for (i = 0; i < n->fields; i++) {
235 		if (JSMN_STRING != n->d.obj[i].lhs->type &&
236 		    JSMN_PRIMITIVE != n->d.obj[i].lhs->type)
237 			continue;
238 		else if (strcmp(name, n->d.obj[i].lhs->d.str))
239 			continue;
240 		break;
241 	}
242 	if (i == n->fields)
243 		return (NULL);
244 	if (JSMN_ARRAY != n->d.obj[i].rhs->type)
245 		return (NULL);
246 	return (n->d.obj[i].rhs);
247 }
248 
249 /*
250  * Extract a single string from the returned JSON object, making sure
251  * that it's the correct type.
252  * Returns NULL on failure.
253  */
254 static char *
255 json_getstr(struct jsmnn *n, const char *name)
256 {
257 	size_t		 i;
258 	char		*cp;
259 
260 	if (JSMN_OBJECT != n->type)
261 		return (NULL);
262 	for (i = 0; i < n->fields; i++) {
263 		if (JSMN_STRING != n->d.obj[i].lhs->type &&
264 		    JSMN_PRIMITIVE != n->d.obj[i].lhs->type)
265 			continue;
266 		else if (strcmp(name, n->d.obj[i].lhs->d.str))
267 			continue;
268 		break;
269 	}
270 	if (i == n->fields)
271 		return (NULL);
272 	if (JSMN_STRING != n->d.obj[i].rhs->type &&
273 	    JSMN_PRIMITIVE != n->d.obj[i].rhs->type)
274 		return (NULL);
275 
276 	cp = strdup(n->d.obj[i].rhs->d.str);
277 	if (NULL == cp)
278 		warn("strdup");
279 	return (cp);
280 }
281 
282 /*
283  * Completely free the challenge response body.
284  */
285 void
286 json_free_challenge(struct chng *p)
287 {
288 
289 	free(p->uri);
290 	free(p->token);
291 	p->uri = p->token = NULL;
292 }
293 
294 /*
295  * Parse the response from the ACME server when we're waiting to see
296  * whether the challenge has been ok.
297  */
298 int
299 json_parse_response(struct jsmnn *n)
300 {
301 	char		*resp;
302 	int		 rc;
303 
304 	if (NULL == n)
305 		return (-1);
306 	if (NULL == (resp = json_getstr(n, "status")))
307 		return (-1);
308 
309 	if (0 == strcmp(resp, "valid"))
310 		rc = 1;
311 	else if (0 == strcmp(resp, "pending"))
312 		rc = 0;
313 	else
314 		rc = -1;
315 
316 	free(resp);
317 	return (rc);
318 }
319 
320 /*
321  * Parse the response from a new-authz, which consists of challenge
322  * information, into a structure.
323  * We only care about the HTTP-01 response.
324  */
325 int
326 json_parse_challenge(struct jsmnn *n, struct chng *p)
327 {
328 	struct jsmnn	*array, *obj;
329 	size_t		 i;
330 	int		 rc;
331 	char		*type;
332 
333 	if (NULL == n)
334 		return (0);
335 
336 	array = json_getarray(n, "challenges");
337 	if (NULL == array)
338 		return (0);
339 
340 	for (i = 0; i < array->fields; i++) {
341 		obj = json_getarrayobj(array->d.array[i]);
342 		if (NULL == obj)
343 			continue;
344 		type = json_getstr(obj, "type");
345 		if (NULL == type)
346 			continue;
347 		rc = strcmp(type, "http-01");
348 		free(type);
349 		if (rc)
350 			continue;
351 		p->uri = json_getstr(obj, "uri");
352 		p->token = json_getstr(obj, "token");
353 		return (NULL != p->uri &&
354 		       NULL != p->token);
355 	}
356 
357 	return (0);
358 }
359 
360 /*
361  * Extract the CA paths from the JSON response object.
362  * Return zero on failure, non-zero on success.
363  */
364 int
365 json_parse_capaths(struct jsmnn *n, struct capaths *p)
366 {
367 
368 	if (NULL == n)
369 		return (0);
370 
371 	p->newauthz = json_getstr(n, "new-authz");
372 	p->newcert = json_getstr(n, "new-cert");
373 	p->newreg = json_getstr(n, "new-reg");
374 	p->revokecert = json_getstr(n, "revoke-cert");
375 
376 	return (NULL != p->newauthz &&
377 	       NULL != p->newcert &&
378 	       NULL != p->newreg &&
379 	       NULL != p->revokecert);
380 }
381 
382 /*
383  * Free up all of our CA-noted paths (which may all be NULL).
384  */
385 void
386 json_free_capaths(struct capaths *p)
387 {
388 
389 	free(p->newauthz);
390 	free(p->newcert);
391 	free(p->newreg);
392 	free(p->revokecert);
393 	memset(p, 0, sizeof(struct capaths));
394 }
395 
396 /*
397  * Parse an HTTP response body from a buffer of size "sz".
398  * Returns an opaque pointer on success, otherwise NULL on error.
399  */
400 struct jsmnn *
401 json_parse(const char *buf, size_t sz)
402 {
403 	struct jsmnn	*n;
404 	jsmn_parser	 p;
405 	jsmntok_t	*tok;
406 	int		 r;
407 	size_t		 tokcount;
408 
409 	jsmn_init(&p);
410 	tokcount = 128;
411 
412 	/* Do this until we don't need any more tokens. */
413 again:
414 	tok = calloc(tokcount, sizeof(jsmntok_t));
415 	if (NULL == tok) {
416 		warn("calloc");
417 		return (NULL);
418 	}
419 
420 	/* Actually try to parse the JSON into the tokens. */
421 
422 	r = jsmn_parse(&p, buf, sz, tok, tokcount);
423 	if (r < 0 && JSMN_ERROR_NOMEM == r) {
424 		tokcount *= 2;
425 		free(tok);
426 		goto again;
427 	} else if (r < 0) {
428 		warnx("jsmn_parse: %d", r);
429 		free(tok);
430 		return (NULL);
431 	}
432 
433 	/* Now parse the tokens into a tree. */
434 
435 	n = jsmntree_alloc(tok, buf, r);
436 	free(tok);
437 	return (n);
438 }
439 
440 /*
441  * Format the "new-reg" resource request.
442  */
443 char *
444 json_fmt_newreg(const char *license)
445 {
446 	int	 c;
447 	char	*p;
448 
449 	c = asprintf(&p, "{"
450 		"\"resource\": \"new-reg\", "
451 		"\"agreement\": \"%s\""
452 		"}", license);
453 	if (-1 == c) {
454 		warn("asprintf");
455 		p = NULL;
456 	}
457 	return (p);
458 }
459 
460 /*
461  * Format the "new-authz" resource request.
462  */
463 char *
464 json_fmt_newauthz(const char *domain)
465 {
466 	int	 c;
467 	char	*p;
468 
469 	c = asprintf(&p, "{"
470 		"\"resource\": \"new-authz\", "
471 		"\"identifier\": "
472 		"{\"type\": \"dns\", \"value\": \"%s\"}"
473 		"}", domain);
474 	if (-1 == c) {
475 		warn("asprintf");
476 		p = NULL;
477 	}
478 	return (p);
479 }
480 
481 /*
482  * Format the "challenge" resource request.
483  */
484 char *
485 json_fmt_challenge(const char *token, const char *thumb)
486 {
487 	int	 c;
488 	char	*p;
489 
490 	c = asprintf(&p, "{"
491 		"\"resource\": \"challenge\", "
492 		"\"keyAuthorization\": \"%s.%s\""
493 		"}", token, thumb);
494 	if (-1 == c) {
495 		warn("asprintf");
496 		p = NULL;
497 	}
498 	return (p);
499 }
500 
501 /*
502  * Format the "new-cert" resource request.
503  */
504 char *
505 json_fmt_revokecert(const char *cert)
506 {
507 	int	 c;
508 	char	*p;
509 
510 	c = asprintf(&p, "{"
511 		"\"resource\": \"revoke-cert\", "
512 		"\"certificate\": \"%s\""
513 		"}", cert);
514 	if (-1 == c) {
515 		warn("asprintf");
516 		p = NULL;
517 	}
518 	return (p);
519 }
520 
521 /*
522  * Format the "new-cert" resource request.
523  */
524 char *
525 json_fmt_newcert(const char *cert)
526 {
527 	int	 c;
528 	char	*p;
529 
530 	c = asprintf(&p, "{"
531 		"\"resource\": \"new-cert\", "
532 		"\"csr\": \"%s\""
533 		"}", cert);
534 	if (-1 == c) {
535 		warn("asprintf");
536 		p = NULL;
537 	}
538 	return (p);
539 }
540 
541 /*
542  * Header component of json_fmt_signed().
543  */
544 char *
545 json_fmt_header_rsa(const char *exp, const char *mod)
546 {
547 	int	 c;
548 	char	*p;
549 
550 	c = asprintf(&p, "{"
551 		"\"alg\": \"RS256\", "
552 		"\"jwk\": "
553 		"{\"e\": \"%s\", \"kty\": \"RSA\", \"n\": \"%s\"}"
554 		"}", exp, mod);
555 	if (-1 == c) {
556 		warn("asprintf");
557 		p = NULL;
558 	}
559 	return (p);
560 }
561 
562 /*
563  * Protected component of json_fmt_signed().
564  */
565 char *
566 json_fmt_protected_rsa(const char *exp, const char *mod, const char *nce)
567 {
568 	int	 c;
569 	char	*p;
570 
571 	c = asprintf(&p, "{"
572 		"\"alg\": \"RS256\", "
573 		"\"jwk\": "
574 		"{\"e\": \"%s\", \"kty\": \"RSA\", \"n\": \"%s\"}, "
575 		"\"nonce\": \"%s\""
576 		"}", exp, mod, nce);
577 	if (-1 == c) {
578 		warn("asprintf");
579 		p = NULL;
580 	}
581 	return (p);
582 }
583 
584 /*
585  * Signed message contents for the CA server.
586  */
587 char *
588 json_fmt_signed(const char *header, const char *protected,
589 	const char *payload, const char *digest)
590 {
591 	int	 c;
592 	char	*p;
593 
594 	c = asprintf(&p, "{"
595 		"\"header\": %s, "
596 		"\"protected\": \"%s\", "
597 		"\"payload\": \"%s\", "
598 		"\"signature\": \"%s\""
599 		"}", header, protected, payload, digest);
600 	if (-1 == c) {
601 		warn("asprintf");
602 		p = NULL;
603 	}
604 	return (p);
605 }
606 
607 /*
608  * Produce thumbprint input.
609  * This isn't technically a JSON string--it's the input we'll use for
610  * hashing and digesting.
611  * However, it's in the form of a JSON string, so do it here.
612  */
613 char *
614 json_fmt_thumb_rsa(const char *exp, const char *mod)
615 {
616 	int	 c;
617 	char	*p;
618 
619 	/*NOTE: WHITESPACE IS IMPORTANT. */
620 
621 	c = asprintf(&p,
622 		"{\"e\":\"%s\",\"kty\":\"RSA\",\"n\":\"%s\"}",
623 		exp, mod);
624 	if (-1 == c) {
625 		warn("asprintf");
626 		p = NULL;
627 	}
628 	return (p);
629 }
630