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