summaryrefslogtreecommitdiffstats
path: root/usr.sbin/acme-client/json.c
diff options
context:
space:
mode:
authorflorian <florian@openbsd.org>2016-08-31 22:01:42 +0000
committerflorian <florian@openbsd.org>2016-08-31 22:01:42 +0000
commitde579d12540ec65d8e12e3a9e6a85a5c1fbe09e2 (patch)
tree6f7a6d471eb94336b68a320956e8c21b4cb04413 /usr.sbin/acme-client/json.c
parentRemove dead code. queue_flow_control() has never been used and is (diff)
downloadwireguard-openbsd-de579d12540ec65d8e12e3a9e6a85a5c1fbe09e2.tar.xz
wireguard-openbsd-de579d12540ec65d8e12e3a9e6a85a5c1fbe09e2.zip
Import Kristaps' letskencrypt and call it acme-client in tree.
OK to get it in deraadt@ (and probably beck@) At least deraadt@, beck@ and otto@ are fine with the name and the disagreements stopped.
Diffstat (limited to 'usr.sbin/acme-client/json.c')
-rw-r--r--usr.sbin/acme-client/json.c632
1 files changed, 632 insertions, 0 deletions
diff --git a/usr.sbin/acme-client/json.c b/usr.sbin/acme-client/json.c
new file mode 100644
index 00000000000..0c23f5490d7
--- /dev/null
+++ b/usr.sbin/acme-client/json.c
@@ -0,0 +1,632 @@
+/* $Id: json.c,v 1.1 2016/08/31 22:01:42 florian Exp $ */
+/*
+ * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <assert.h>
+#include <err.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "jsmn.h"
+#include "extern.h"
+
+struct jsmnp;
+
+/*
+ * A node in the JSMN parse tree.
+ * Each of this corresponds to an object in the original JSMN token
+ * list, although the contents have been extracted properly.
+ */
+struct jsmnn {
+ struct parse *p; /* parser object */
+ union {
+ char *str; /* JSMN_PRIMITIVE, JSMN_STRING */
+ struct jsmnp *obj; /* JSMN_OBJECT */
+ struct jsmnn **array; /* JSMN_ARRAY */
+ } d;
+ size_t fields; /* entries in "d" */
+ jsmntype_t type; /* type of node */
+};
+
+/*
+ * Objects consist of node pairs: the left-hand side (before the colon)
+ * and the right-hand side---the data.
+ */
+struct jsmnp {
+ struct jsmnn *lhs; /* left of colon */
+ struct jsmnn *rhs; /* right of colon */
+};
+
+/*
+ * Object for converting the JSMN token array into a tree.
+ */
+struct parse {
+ struct jsmnn *nodes; /* all nodes */
+ size_t cur; /* current number */
+ size_t max; /* nodes in "nodes" */
+};
+
+/*
+ * Recursive part for convertin a JSMN token array into a tree.
+ * See "example/jsondump.c" for its construction (it's the same except
+ * for how it handles allocation errors).
+ */
+static ssize_t
+build(struct parse *parse, struct jsmnn **np,
+ jsmntok_t *t, const char *js, size_t sz)
+{
+ size_t i, j;
+ struct jsmnn *n;
+ ssize_t tmp;
+
+ if (0 == sz)
+ return(0);
+
+ assert(parse->cur < parse->max);
+ n = *np = &parse->nodes[parse->cur++];
+ n->p = parse;
+ n->type = t->type;
+
+ switch (t->type) {
+ case (JSMN_STRING):
+ /* FALLTHROUGH */
+ case (JSMN_PRIMITIVE):
+ n->fields = 1;
+ n->d.str = strndup
+ (js + t->start,
+ t->end - t->start);
+ if (NULL == n->d.str)
+ break;
+ return(1);
+ case (JSMN_OBJECT):
+ n->fields = t->size;
+ n->d.obj = calloc(n->fields,
+ sizeof(struct jsmnp));
+ if (NULL == n->d.obj)
+ break;
+ for (i = j = 0; i < (size_t)t->size; i++) {
+ tmp = build(parse,
+ &n->d.obj[i].lhs,
+ t + 1 + j, js, sz - j);
+ if (tmp < 0)
+ break;
+ j += tmp;
+ tmp = build(parse,
+ &n->d.obj[i].rhs,
+ t + 1 + j, js, sz - j);
+ if (tmp < 0)
+ break;
+ j += tmp;
+ }
+ if (i < (size_t)t->size)
+ break;
+ return(j + 1);
+ case (JSMN_ARRAY):
+ n->fields = t->size;
+ n->d.array = calloc(n->fields,
+ sizeof(struct jsmnn *));
+ if (NULL == n->d.array)
+ break;
+ for (i = j = 0; i < (size_t)t->size; i++) {
+ tmp = build(parse,
+ &n->d.array[i],
+ t + 1 + j, js, sz - j);
+ if (tmp < 0)
+ break;
+ j += tmp;
+ }
+ if (i < (size_t)t->size)
+ break;
+ return(j + 1);
+ default:
+ break;
+ }
+
+ return(-1);
+}
+
+/*
+ * Fully free up a parse sequence.
+ * This handles all nodes sequentially, not recursively.
+ */
+static void
+jsmnparse_free(struct parse *p)
+{
+ size_t i;
+
+ if (NULL == p)
+ return;
+ for (i = 0; i < p->max; i++)
+ if (JSMN_ARRAY == p->nodes[i].type)
+ free(p->nodes[i].d.array);
+ else if (JSMN_OBJECT == p->nodes[i].type)
+ free(p->nodes[i].d.obj);
+ else if (JSMN_PRIMITIVE == p->nodes[i].type)
+ free(p->nodes[i].d.str);
+ else if (JSMN_STRING == p->nodes[i].type)
+ free(p->nodes[i].d.str);
+ free(p->nodes);
+ free(p);
+}
+
+/*
+ * Allocate a tree representation of "t".
+ * This returns NULL on allocation failure or when sz is zero, in which
+ * case all resources allocated along the way are freed already.
+ */
+static struct jsmnn *
+jsmntree_alloc(jsmntok_t *t, const char *js, size_t sz)
+{
+ struct jsmnn *first;
+ struct parse *p;
+
+ if (0 == sz)
+ return(NULL);
+
+ p = calloc(1, sizeof(struct parse));
+ if (NULL == p)
+ return(NULL);
+
+ p->max = sz;
+ p->nodes = calloc(p->max, sizeof(struct jsmnn));
+ if (NULL == p->nodes) {
+ free(p);
+ return(NULL);
+ }
+
+ if (build(p, &first, t, js, sz) < 0) {
+ jsmnparse_free(p);
+ first = NULL;
+ }
+
+ return(first);
+}
+
+/*
+ * Call through to free parse contents.
+ */
+void
+json_free(struct jsmnn *first)
+{
+
+ if (NULL != first)
+ jsmnparse_free(first->p);
+}
+
+/*
+ * Just check that the array object is in fact an object.
+ */
+static struct jsmnn *
+json_getarrayobj(struct jsmnn *n)
+{
+
+ return (JSMN_OBJECT != n->type ? NULL : n);
+}
+
+/*
+ * Extract an array from the returned JSON object, making sure that it's
+ * the correct type.
+ * Returns NULL on failure.
+ */
+static struct jsmnn *
+json_getarray(struct jsmnn *n, const char *name)
+{
+ size_t i;
+
+ if (JSMN_OBJECT != n->type)
+ return(NULL);
+ for (i = 0; i < n->fields; i++) {
+ if (JSMN_STRING != n->d.obj[i].lhs->type &&
+ JSMN_PRIMITIVE != n->d.obj[i].lhs->type)
+ continue;
+ else if (strcmp(name, n->d.obj[i].lhs->d.str))
+ continue;
+ break;
+ }
+ if (i == n->fields)
+ return(NULL);
+ if (JSMN_ARRAY != n->d.obj[i].rhs->type)
+ return(NULL);
+ return(n->d.obj[i].rhs);
+}
+
+/*
+ * Extract a single string from the returned JSON object, making sure
+ * that it's the correct type.
+ * Returns NULL on failure.
+ */
+static char *
+json_getstr(struct jsmnn *n, const char *name)
+{
+ size_t i;
+ char *cp;
+
+ if (JSMN_OBJECT != n->type)
+ return(NULL);
+ for (i = 0; i < n->fields; i++) {
+ if (JSMN_STRING != n->d.obj[i].lhs->type &&
+ JSMN_PRIMITIVE != n->d.obj[i].lhs->type)
+ continue;
+ else if (strcmp(name, n->d.obj[i].lhs->d.str))
+ continue;
+ break;
+ }
+ if (i == n->fields)
+ return(NULL);
+ if (JSMN_STRING != n->d.obj[i].rhs->type &&
+ JSMN_PRIMITIVE != n->d.obj[i].rhs->type)
+ return(NULL);
+
+ cp = strdup(n->d.obj[i].rhs->d.str);
+ if (NULL == cp)
+ warn("strdup");
+ return(cp);
+}
+
+/*
+ * Completely free the challenge response body.
+ */
+void
+json_free_challenge(struct chng *p)
+{
+
+ free(p->uri);
+ free(p->token);
+ p->uri = p->token = NULL;
+}
+
+/*
+ * Parse the response from the ACME server when we're waiting to see
+ * whether the challenge has been ok.
+ */
+int
+json_parse_response(struct jsmnn *n)
+{
+ char *resp;
+ int rc;
+
+ if (NULL == n)
+ return(-1);
+ if (NULL == (resp = json_getstr(n, "status")))
+ return(-1);
+
+ if (0 == strcmp(resp, "valid"))
+ rc = 1;
+ else if (0 == strcmp(resp, "pending"))
+ rc = 0;
+ else
+ rc = -1;
+
+ free(resp);
+ return(rc);
+}
+
+/*
+ * Parse the response from a new-authz, which consists of challenge
+ * information, into a structure.
+ * We only care about the HTTP-01 response.
+ */
+int
+json_parse_challenge(struct jsmnn *n, struct chng *p)
+{
+ struct jsmnn *array, *obj;
+ size_t i;
+ int rc;
+ char *type;
+
+ if (NULL == n)
+ return(0);
+
+ array = json_getarray(n, "challenges");
+ if (NULL == array)
+ return(0);
+
+ for (i = 0; i < array->fields; i++) {
+ obj = json_getarrayobj(array->d.array[i]);
+ if (NULL == obj)
+ continue;
+ type = json_getstr(obj, "type");
+ if (NULL == type)
+ continue;
+ rc = strcmp(type, "http-01");
+ free(type);
+ if (rc)
+ continue;
+ p->uri = json_getstr(obj, "uri");
+ p->token = json_getstr(obj, "token");
+ return(NULL != p->uri &&
+ NULL != p->token);
+ }
+
+ return(0);
+}
+
+/*
+ * Extract the CA paths from the JSON response object.
+ * Return zero on failure, non-zero on success.
+ */
+int
+json_parse_capaths(struct jsmnn *n, struct capaths *p)
+{
+
+ if (NULL == n)
+ return(0);
+
+ p->newauthz = json_getstr(n, "new-authz");
+ p->newcert = json_getstr(n, "new-cert");
+ p->newreg = json_getstr(n, "new-reg");
+ p->revokecert = json_getstr(n, "revoke-cert");
+
+ return(NULL != p->newauthz &&
+ NULL != p->newcert &&
+ NULL != p->newreg &&
+ NULL != p->revokecert);
+}
+
+/*
+ * Free up all of our CA-noted paths (which may all be NULL).
+ */
+void
+json_free_capaths(struct capaths *p)
+{
+
+ free(p->newauthz);
+ free(p->newcert);
+ free(p->newreg);
+ free(p->revokecert);
+ memset(p, 0, sizeof(struct capaths));
+}
+
+/*
+ * Parse an HTTP response body from a buffer of size "sz".
+ * Returns an opaque pointer on success, otherwise NULL on error.
+ */
+struct jsmnn *
+json_parse(const char *buf, size_t sz)
+{
+ struct jsmnn *n;
+ jsmn_parser p;
+ jsmntok_t *tok;
+ int r;
+ size_t tokcount;
+
+ jsmn_init(&p);
+ tokcount = 128;
+
+ /* Do this until we don't need any more tokens. */
+again:
+ tok = calloc(tokcount, sizeof(jsmntok_t));
+ if (NULL == tok) {
+ warn("calloc");
+ return(NULL);
+ }
+
+ /* Actually try to parse the JSON into the tokens. */
+
+ r = jsmn_parse(&p, buf, sz, tok, tokcount);
+ if (r < 0 && JSMN_ERROR_NOMEM == r) {
+ tokcount *= 2;
+ free(tok);
+ goto again;
+ } else if (r < 0) {
+ warnx("jsmn_parse: %d", r);
+ free(tok);
+ return(NULL);
+ }
+
+ /* Now parse the tokens into a tree. */
+
+ n = jsmntree_alloc(tok, buf, r);
+ free(tok);
+ return(n);
+}
+
+/*
+ * Format the "new-reg" resource request.
+ */
+char *
+json_fmt_newreg(const char *license)
+{
+ int c;
+ char *p;
+
+ c = asprintf(&p, "{"
+ "\"resource\": \"new-reg\", "
+ "\"agreement\": \"%s\""
+ "}", license);
+ if (-1 == c) {
+ warn("asprintf");
+ p = NULL;
+ }
+ return(p);
+}
+
+/*
+ * Format the "new-authz" resource request.
+ */
+char *
+json_fmt_newauthz(const char *domain)
+{
+ int c;
+ char *p;
+
+ c = asprintf(&p, "{"
+ "\"resource\": \"new-authz\", "
+ "\"identifier\": "
+ "{\"type\": \"dns\", \"value\": \"%s\"}"
+ "}", domain);
+ if (-1 == c) {
+ warn("asprintf");
+ p = NULL;
+ }
+ return(p);
+}
+
+/*
+ * Format the "challenge" resource request.
+ */
+char *
+json_fmt_challenge(const char *token, const char *thumb)
+{
+ int c;
+ char *p;
+
+ c = asprintf(&p, "{"
+ "\"resource\": \"challenge\", "
+ "\"keyAuthorization\": \"%s.%s\""
+ "}", token, thumb);
+ if (-1 == c) {
+ warn("asprintf");
+ p = NULL;
+ }
+ return(p);
+}
+
+/*
+ * Format the "new-cert" resource request.
+ */
+char *
+json_fmt_revokecert(const char *cert)
+{
+ int c;
+ char *p;
+
+ c = asprintf(&p, "{"
+ "\"resource\": \"revoke-cert\", "
+ "\"certificate\": \"%s\""
+ "}", cert);
+ if (-1 == c) {
+ warn("asprintf");
+ p = NULL;
+ }
+ return(p);
+}
+
+/*
+ * Format the "new-cert" resource request.
+ */
+char *
+json_fmt_newcert(const char *cert)
+{
+ int c;
+ char *p;
+
+ c = asprintf(&p, "{"
+ "\"resource\": \"new-cert\", "
+ "\"csr\": \"%s\""
+ "}", cert);
+ if (-1 == c) {
+ warn("asprintf");
+ p = NULL;
+ }
+ return(p);
+}
+
+/*
+ * Header component of json_fmt_signed().
+ */
+char *
+json_fmt_header_rsa(const char *exp, const char *mod)
+{
+ int c;
+ char *p;
+
+ c = asprintf(&p, "{"
+ "\"alg\": \"RS256\", "
+ "\"jwk\": "
+ "{\"e\": \"%s\", \"kty\": \"RSA\", \"n\": \"%s\"}"
+ "}", exp, mod);
+ if (-1 == c) {
+ warn("asprintf");
+ p = NULL;
+ }
+ return(p);
+}
+
+/*
+ * Protected component of json_fmt_signed().
+ */
+char *
+json_fmt_protected_rsa(const char *exp, const char *mod, const char *nce)
+{
+ int c;
+ char *p;
+
+ c = asprintf(&p, "{"
+ "\"alg\": \"RS256\", "
+ "\"jwk\": "
+ "{\"e\": \"%s\", \"kty\": \"RSA\", \"n\": \"%s\"}, "
+ "\"nonce\": \"%s\""
+ "}", exp, mod, nce);
+ if (-1 == c) {
+ warn("asprintf");
+ p = NULL;
+ }
+ return(p);
+}
+
+/*
+ * Signed message contents for the CA server.
+ */
+char *
+json_fmt_signed(const char *header, const char *protected,
+ const char *payload, const char *digest)
+{
+ int c;
+ char *p;
+
+ c = asprintf(&p, "{"
+ "\"header\": %s, "
+ "\"protected\": \"%s\", "
+ "\"payload\": \"%s\", "
+ "\"signature\": \"%s\""
+ "}", header, protected, payload, digest);
+ if (-1 == c) {
+ warn("asprintf");
+ p = NULL;
+ }
+ return(p);
+}
+
+/*
+ * Produce thumbprint input.
+ * This isn't technically a JSON string--it's the input we'll use for
+ * hashing and digesting.
+ * However, it's in the form of a JSON string, so do it here.
+ */
+char *
+json_fmt_thumb_rsa(const char *exp, const char *mod)
+{
+ int c;
+ char *p;
+
+ /*NOTE: WHITESPACE IS IMPORTANT. */
+
+ c = asprintf(&p,
+ "{\"e\":\"%s\",\"kty\":\"RSA\",\"n\":\"%s\"}",
+ exp, mod);
+ if (-1 == c) {
+ warn("asprintf");
+ p = NULL;
+ }
+ return(p);
+}