xref: /openbsd-src/usr.sbin/acme-client/json.c (revision 25f96a3583ce99d05ae1d9e90eed826379441a70)
1 /*	$Id: json.c,v 1.2 2016/08/31 23:19:50 benno 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 #ifdef HAVE_CONFIG_H
18 # include "config.h"
19 #endif
20 
21 #include <assert.h>
22 #include <err.h>
23 #include <stdarg.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <unistd.h>
28 
29 #include "jsmn.h"
30 #include "extern.h"
31 
32 struct	jsmnp;
33 
34 /*
35  * A node in the JSMN parse tree.
36  * Each of this corresponds to an object in the original JSMN token
37  * list, although the contents have been extracted properly.
38  */
39 struct	jsmnn {
40 	struct parse	*p; /* parser object */
41 	union {
42 		char *str; /* JSMN_PRIMITIVE, JSMN_STRING */
43 		struct jsmnp *obj; /* JSMN_OBJECT */
44 		struct jsmnn **array; /* JSMN_ARRAY */
45 	} d;
46 	size_t		 fields; /* entries in "d" */
47 	jsmntype_t	 type; /* type of node */
48 };
49 
50 /*
51  * Objects consist of node pairs: the left-hand side (before the colon)
52  * and the right-hand side---the data.
53  */
54 struct	jsmnp {
55 	struct jsmnn	*lhs; /* left of colon */
56 	struct jsmnn	*rhs; /* right of colon */
57 };
58 
59 /*
60  * Object for converting the JSMN token array into a tree.
61  */
62 struct	parse {
63 	struct jsmnn	*nodes; /* all nodes */
64 	size_t		 cur; /* current number */
65 	size_t		 max; /* nodes in "nodes" */
66 };
67 
68 /*
69  * Recursive part for convertin a JSMN token array into a tree.
70  * See "example/jsondump.c" for its construction (it's the same except
71  * for how it handles allocation errors).
72  */
73 static ssize_t
74 build(struct parse *parse, struct jsmnn **np,
75 	jsmntok_t *t, const char *js, size_t sz)
76 {
77 	size_t		 i, j;
78 	struct jsmnn	*n;
79 	ssize_t		 tmp;
80 
81 	if (0 == sz)
82 		return (0);
83 
84 	assert(parse->cur < parse->max);
85 	n = *np = &parse->nodes[parse->cur++];
86 	n->p = parse;
87 	n->type = t->type;
88 
89 	switch (t->type) {
90 	case (JSMN_STRING):
91 		/* FALLTHROUGH */
92 	case (JSMN_PRIMITIVE):
93 		n->fields = 1;
94 		n->d.str = strndup
95 			(js + t->start,
96 			 t->end - t->start);
97 		if (NULL == n->d.str)
98 			break;
99 		return (1);
100 	case (JSMN_OBJECT):
101 		n->fields = t->size;
102 		n->d.obj = calloc(n->fields,
103 			sizeof(struct jsmnp));
104 		if (NULL == n->d.obj)
105 			break;
106 		for (i = j = 0; i < (size_t)t->size; i++) {
107 			tmp = build(parse,
108 				&n->d.obj[i].lhs,
109 				t + 1 + j, js, sz - j);
110 			if (tmp < 0)
111 				break;
112 			j += tmp;
113 			tmp = build(parse,
114 				&n->d.obj[i].rhs,
115 				t + 1 + j, js, sz - j);
116 			if (tmp < 0)
117 				break;
118 			j += tmp;
119 		}
120 		if (i < (size_t)t->size)
121 			break;
122 		return (j + 1);
123 	case (JSMN_ARRAY):
124 		n->fields = t->size;
125 		n->d.array = calloc(n->fields,
126 			sizeof(struct jsmnn *));
127 		if (NULL == n->d.array)
128 			break;
129 		for (i = j = 0; i < (size_t)t->size; i++) {
130 			tmp = build(parse,
131 				&n->d.array[i],
132 				t + 1 + j, js, sz - j);
133 			if (tmp < 0)
134 				break;
135 			j += tmp;
136 		}
137 		if (i < (size_t)t->size)
138 			break;
139 		return (j + 1);
140 	default:
141 		break;
142 	}
143 
144 	return (-1);
145 }
146 
147 /*
148  * Fully free up a parse sequence.
149  * This handles all nodes sequentially, not recursively.
150  */
151 static void
152 jsmnparse_free(struct parse *p)
153 {
154 	size_t	 i;
155 
156 	if (NULL == p)
157 		return;
158 	for (i = 0; i < p->max; i++)
159 		if (JSMN_ARRAY == p->nodes[i].type)
160 			free(p->nodes[i].d.array);
161 		else if (JSMN_OBJECT == p->nodes[i].type)
162 			free(p->nodes[i].d.obj);
163 		else if (JSMN_PRIMITIVE == p->nodes[i].type)
164 			free(p->nodes[i].d.str);
165 		else if (JSMN_STRING == p->nodes[i].type)
166 			free(p->nodes[i].d.str);
167 	free(p->nodes);
168 	free(p);
169 }
170 
171 /*
172  * Allocate a tree representation of "t".
173  * This returns NULL on allocation failure or when sz is zero, in which
174  * case all resources allocated along the way are freed already.
175  */
176 static struct jsmnn *
177 jsmntree_alloc(jsmntok_t *t, const char *js, size_t sz)
178 {
179 	struct jsmnn	*first;
180 	struct parse	*p;
181 
182 	if (0 == sz)
183 		return (NULL);
184 
185 	p = calloc(1, sizeof(struct parse));
186 	if (NULL == p)
187 		return (NULL);
188 
189 	p->max = sz;
190 	p->nodes = calloc(p->max, sizeof(struct jsmnn));
191 	if (NULL == p->nodes) {
192 		free(p);
193 		return (NULL);
194 	}
195 
196 	if (build(p, &first, t, js, sz) < 0) {
197 		jsmnparse_free(p);
198 		first = NULL;
199 	}
200 
201 	return (first);
202 }
203 
204 /*
205  * Call through to free parse contents.
206  */
207 void
208 json_free(struct jsmnn *first)
209 {
210 
211 	if (NULL != first)
212 		jsmnparse_free(first->p);
213 }
214 
215 /*
216  * Just check that the array object is in fact an object.
217  */
218 static struct jsmnn *
219 json_getarrayobj(struct jsmnn *n)
220 {
221 
222 	return (JSMN_OBJECT != n->type ? NULL : n);
223 }
224 
225 /*
226  * Extract an array from the returned JSON object, making sure that it's
227  * the correct type.
228  * Returns NULL on failure.
229  */
230 static struct jsmnn *
231 json_getarray(struct jsmnn *n, const char *name)
232 {
233 	size_t		 i;
234 
235 	if (JSMN_OBJECT != n->type)
236 		return (NULL);
237 	for (i = 0; i < n->fields; i++) {
238 		if (JSMN_STRING != n->d.obj[i].lhs->type &&
239 		    JSMN_PRIMITIVE != n->d.obj[i].lhs->type)
240 			continue;
241 		else if (strcmp(name, n->d.obj[i].lhs->d.str))
242 			continue;
243 		break;
244 	}
245 	if (i == n->fields)
246 		return (NULL);
247 	if (JSMN_ARRAY != n->d.obj[i].rhs->type)
248 		return (NULL);
249 	return (n->d.obj[i].rhs);
250 }
251 
252 /*
253  * Extract a single string from the returned JSON object, making sure
254  * that it's the correct type.
255  * Returns NULL on failure.
256  */
257 static char *
258 json_getstr(struct jsmnn *n, const char *name)
259 {
260 	size_t		 i;
261 	char		*cp;
262 
263 	if (JSMN_OBJECT != n->type)
264 		return (NULL);
265 	for (i = 0; i < n->fields; i++) {
266 		if (JSMN_STRING != n->d.obj[i].lhs->type &&
267 		    JSMN_PRIMITIVE != n->d.obj[i].lhs->type)
268 			continue;
269 		else if (strcmp(name, n->d.obj[i].lhs->d.str))
270 			continue;
271 		break;
272 	}
273 	if (i == n->fields)
274 		return (NULL);
275 	if (JSMN_STRING != n->d.obj[i].rhs->type &&
276 	    JSMN_PRIMITIVE != n->d.obj[i].rhs->type)
277 		return (NULL);
278 
279 	cp = strdup(n->d.obj[i].rhs->d.str);
280 	if (NULL == cp)
281 		warn("strdup");
282 	return (cp);
283 }
284 
285 /*
286  * Completely free the challenge response body.
287  */
288 void
289 json_free_challenge(struct chng *p)
290 {
291 
292 	free(p->uri);
293 	free(p->token);
294 	p->uri = p->token = NULL;
295 }
296 
297 /*
298  * Parse the response from the ACME server when we're waiting to see
299  * whether the challenge has been ok.
300  */
301 int
302 json_parse_response(struct jsmnn *n)
303 {
304 	char		*resp;
305 	int		 rc;
306 
307 	if (NULL == n)
308 		return (-1);
309 	if (NULL == (resp = json_getstr(n, "status")))
310 		return (-1);
311 
312 	if (0 == strcmp(resp, "valid"))
313 		rc = 1;
314 	else if (0 == strcmp(resp, "pending"))
315 		rc = 0;
316 	else
317 		rc = -1;
318 
319 	free(resp);
320 	return (rc);
321 }
322 
323 /*
324  * Parse the response from a new-authz, which consists of challenge
325  * information, into a structure.
326  * We only care about the HTTP-01 response.
327  */
328 int
329 json_parse_challenge(struct jsmnn *n, struct chng *p)
330 {
331 	struct jsmnn	*array, *obj;
332 	size_t		 i;
333 	int		 rc;
334 	char		*type;
335 
336 	if (NULL == n)
337 		return (0);
338 
339 	array = json_getarray(n, "challenges");
340 	if (NULL == array)
341 		return (0);
342 
343 	for (i = 0; i < array->fields; i++) {
344 		obj = json_getarrayobj(array->d.array[i]);
345 		if (NULL == obj)
346 			continue;
347 		type = json_getstr(obj, "type");
348 		if (NULL == type)
349 			continue;
350 		rc = strcmp(type, "http-01");
351 		free(type);
352 		if (rc)
353 			continue;
354 		p->uri = json_getstr(obj, "uri");
355 		p->token = json_getstr(obj, "token");
356 		return (NULL != p->uri &&
357 		       NULL != p->token);
358 	}
359 
360 	return (0);
361 }
362 
363 /*
364  * Extract the CA paths from the JSON response object.
365  * Return zero on failure, non-zero on success.
366  */
367 int
368 json_parse_capaths(struct jsmnn *n, struct capaths *p)
369 {
370 
371 	if (NULL == n)
372 		return (0);
373 
374 	p->newauthz = json_getstr(n, "new-authz");
375 	p->newcert = json_getstr(n, "new-cert");
376 	p->newreg = json_getstr(n, "new-reg");
377 	p->revokecert = json_getstr(n, "revoke-cert");
378 
379 	return (NULL != p->newauthz &&
380 	       NULL != p->newcert &&
381 	       NULL != p->newreg &&
382 	       NULL != p->revokecert);
383 }
384 
385 /*
386  * Free up all of our CA-noted paths (which may all be NULL).
387  */
388 void
389 json_free_capaths(struct capaths *p)
390 {
391 
392 	free(p->newauthz);
393 	free(p->newcert);
394 	free(p->newreg);
395 	free(p->revokecert);
396 	memset(p, 0, sizeof(struct capaths));
397 }
398 
399 /*
400  * Parse an HTTP response body from a buffer of size "sz".
401  * Returns an opaque pointer on success, otherwise NULL on error.
402  */
403 struct jsmnn *
404 json_parse(const char *buf, size_t sz)
405 {
406 	struct jsmnn	*n;
407 	jsmn_parser	 p;
408 	jsmntok_t	*tok;
409 	int		 r;
410 	size_t		 tokcount;
411 
412 	jsmn_init(&p);
413 	tokcount = 128;
414 
415 	/* Do this until we don't need any more tokens. */
416 again:
417 	tok = calloc(tokcount, sizeof(jsmntok_t));
418 	if (NULL == tok) {
419 		warn("calloc");
420 		return (NULL);
421 	}
422 
423 	/* Actually try to parse the JSON into the tokens. */
424 
425 	r = jsmn_parse(&p, buf, sz, tok, tokcount);
426 	if (r < 0 && JSMN_ERROR_NOMEM == r) {
427 		tokcount *= 2;
428 		free(tok);
429 		goto again;
430 	} else if (r < 0) {
431 		warnx("jsmn_parse: %d", r);
432 		free(tok);
433 		return (NULL);
434 	}
435 
436 	/* Now parse the tokens into a tree. */
437 
438 	n = jsmntree_alloc(tok, buf, r);
439 	free(tok);
440 	return (n);
441 }
442 
443 /*
444  * Format the "new-reg" resource request.
445  */
446 char *
447 json_fmt_newreg(const char *license)
448 {
449 	int	 c;
450 	char	*p;
451 
452 	c = asprintf(&p, "{"
453 		"\"resource\": \"new-reg\", "
454 		"\"agreement\": \"%s\""
455 		"}", license);
456 	if (-1 == c) {
457 		warn("asprintf");
458 		p = NULL;
459 	}
460 	return (p);
461 }
462 
463 /*
464  * Format the "new-authz" resource request.
465  */
466 char *
467 json_fmt_newauthz(const char *domain)
468 {
469 	int	 c;
470 	char	*p;
471 
472 	c = asprintf(&p, "{"
473 		"\"resource\": \"new-authz\", "
474 		"\"identifier\": "
475 		"{\"type\": \"dns\", \"value\": \"%s\"}"
476 		"}", domain);
477 	if (-1 == c) {
478 		warn("asprintf");
479 		p = NULL;
480 	}
481 	return (p);
482 }
483 
484 /*
485  * Format the "challenge" resource request.
486  */
487 char *
488 json_fmt_challenge(const char *token, const char *thumb)
489 {
490 	int	 c;
491 	char	*p;
492 
493 	c = asprintf(&p, "{"
494 		"\"resource\": \"challenge\", "
495 		"\"keyAuthorization\": \"%s.%s\""
496 		"}", token, thumb);
497 	if (-1 == c) {
498 		warn("asprintf");
499 		p = NULL;
500 	}
501 	return (p);
502 }
503 
504 /*
505  * Format the "new-cert" resource request.
506  */
507 char *
508 json_fmt_revokecert(const char *cert)
509 {
510 	int	 c;
511 	char	*p;
512 
513 	c = asprintf(&p, "{"
514 		"\"resource\": \"revoke-cert\", "
515 		"\"certificate\": \"%s\""
516 		"}", cert);
517 	if (-1 == c) {
518 		warn("asprintf");
519 		p = NULL;
520 	}
521 	return (p);
522 }
523 
524 /*
525  * Format the "new-cert" resource request.
526  */
527 char *
528 json_fmt_newcert(const char *cert)
529 {
530 	int	 c;
531 	char	*p;
532 
533 	c = asprintf(&p, "{"
534 		"\"resource\": \"new-cert\", "
535 		"\"csr\": \"%s\""
536 		"}", cert);
537 	if (-1 == c) {
538 		warn("asprintf");
539 		p = NULL;
540 	}
541 	return (p);
542 }
543 
544 /*
545  * Header component of json_fmt_signed().
546  */
547 char *
548 json_fmt_header_rsa(const char *exp, const char *mod)
549 {
550 	int	 c;
551 	char	*p;
552 
553 	c = asprintf(&p, "{"
554 		"\"alg\": \"RS256\", "
555 		"\"jwk\": "
556 		"{\"e\": \"%s\", \"kty\": \"RSA\", \"n\": \"%s\"}"
557 		"}", exp, mod);
558 	if (-1 == c) {
559 		warn("asprintf");
560 		p = NULL;
561 	}
562 	return (p);
563 }
564 
565 /*
566  * Protected component of json_fmt_signed().
567  */
568 char *
569 json_fmt_protected_rsa(const char *exp, const char *mod, const char *nce)
570 {
571 	int	 c;
572 	char	*p;
573 
574 	c = asprintf(&p, "{"
575 		"\"alg\": \"RS256\", "
576 		"\"jwk\": "
577 		"{\"e\": \"%s\", \"kty\": \"RSA\", \"n\": \"%s\"}, "
578 		"\"nonce\": \"%s\""
579 		"}", exp, mod, nce);
580 	if (-1 == c) {
581 		warn("asprintf");
582 		p = NULL;
583 	}
584 	return (p);
585 }
586 
587 /*
588  * Signed message contents for the CA server.
589  */
590 char *
591 json_fmt_signed(const char *header, const char *protected,
592 	const char *payload, const char *digest)
593 {
594 	int	 c;
595 	char	*p;
596 
597 	c = asprintf(&p, "{"
598 		"\"header\": %s, "
599 		"\"protected\": \"%s\", "
600 		"\"payload\": \"%s\", "
601 		"\"signature\": \"%s\""
602 		"}", header, protected, payload, digest);
603 	if (-1 == c) {
604 		warn("asprintf");
605 		p = NULL;
606 	}
607 	return (p);
608 }
609 
610 /*
611  * Produce thumbprint input.
612  * This isn't technically a JSON string--it's the input we'll use for
613  * hashing and digesting.
614  * However, it's in the form of a JSON string, so do it here.
615  */
616 char *
617 json_fmt_thumb_rsa(const char *exp, const char *mod)
618 {
619 	int	 c;
620 	char	*p;
621 
622 	/*NOTE: WHITESPACE IS IMPORTANT. */
623 
624 	c = asprintf(&p,
625 		"{\"e\":\"%s\",\"kty\":\"RSA\",\"n\":\"%s\"}",
626 		exp, mod);
627 	if (-1 == c) {
628 		warn("asprintf");
629 		p = NULL;
630 	}
631 	return (p);
632 }
633