File: [local] / src / usr.sbin / acme-client / json.c (download)
Revision 1.21, Mon Sep 14 16:00:17 2020 UTC (3 years, 8 months ago) by florian
Branch: MAIN
CVS Tags: OPENBSD_7_5_BASE, OPENBSD_7_5, OPENBSD_7_4_BASE, OPENBSD_7_4, OPENBSD_7_3_BASE, OPENBSD_7_3, OPENBSD_7_2_BASE, OPENBSD_7_2, OPENBSD_7_1_BASE, OPENBSD_7_1, OPENBSD_7_0_BASE, OPENBSD_7_0, OPENBSD_6_9_BASE, OPENBSD_6_9, OPENBSD_6_8_BASE, OPENBSD_6_8, HEAD Changes since 1.20: +14 -4 lines
We need to be able to provide contact information to use the
buypass.com acme api.
From Bartosz Kuzma (bartosz.kuzma AT release11.com), thanks!
OK beck, deraadt
|
/* $Id: json.c,v 1.21 2020/09/14 16:00:17 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.
*/
#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 (sz == 0)
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 (n->d.str == NULL)
break;
return 1;
case JSMN_OBJECT:
n->fields = t->size;
n->d.obj = calloc(n->fields,
sizeof(struct jsmnp));
if (n->d.obj == NULL)
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 (n->d.array == NULL)
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 (p == NULL)
return;
for (i = 0; i < p->max; i++) {
struct jsmnn *n = &p->nodes[i];
switch (n->type) {
case JSMN_ARRAY:
free(n->d.array);
break;
case JSMN_OBJECT:
free(n->d.obj);
break;
case JSMN_PRIMITIVE:
free(n->d.str);
break;
case JSMN_STRING:
free(n->d.str);
break;
case JSMN_UNDEFINED:
break;
}
}
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 (sz == 0)
return NULL;
p = calloc(1, sizeof(struct parse));
if (p == NULL)
return NULL;
p->max = sz;
p->nodes = calloc(p->max, sizeof(struct jsmnn));
if (p->nodes == NULL) {
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 (first != NULL)
jsmnparse_free(first->p);
}
/*
* Just check that the array object is in fact an object.
*/
static struct jsmnn *
json_getarrayobj(struct jsmnn *n)
{
return n->type != JSMN_OBJECT ? NULL : n;
}
/*
* Get a string element from an array
*/
static char *
json_getarraystr(struct jsmnn *n)
{
return n->type != JSMN_STRING ? NULL : n->d.str;
}
/*
* 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 (n->type != JSMN_OBJECT)
return NULL;
for (i = 0; i < n->fields; i++) {
if (n->d.obj[i].lhs->type != JSMN_STRING &&
n->d.obj[i].lhs->type != JSMN_PRIMITIVE)
continue;
else if (strcmp(name, n->d.obj[i].lhs->d.str))
continue;
break;
}
if (i == n->fields)
return NULL;
if (n->d.obj[i].rhs->type != JSMN_ARRAY)
return NULL;
return n->d.obj[i].rhs;
}
/*
* Extract subtree from the returned JSON object, making sure that it's
* the correct type.
* Returns NULL on failure.
*/
static struct jsmnn *
json_getobj(struct jsmnn *n, const char *name)
{
size_t i;
if (n->type != JSMN_OBJECT)
return NULL;
for (i = 0; i < n->fields; i++) {
if (n->d.obj[i].lhs->type != JSMN_STRING &&
n->d.obj[i].lhs->type != JSMN_PRIMITIVE)
continue;
else if (strcmp(name, n->d.obj[i].lhs->d.str))
continue;
break;
}
if (i == n->fields)
return NULL;
if (n->d.obj[i].rhs->type != JSMN_OBJECT)
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.
*/
char *
json_getstr(struct jsmnn *n, const char *name)
{
size_t i;
char *cp;
if (n->type != JSMN_OBJECT)
return NULL;
for (i = 0; i < n->fields; i++) {
if (n->d.obj[i].lhs->type != JSMN_STRING &&
n->d.obj[i].lhs->type != JSMN_PRIMITIVE)
continue;
else if (strcmp(name, n->d.obj[i].lhs->d.str))
continue;
break;
}
if (i == n->fields)
return NULL;
if (n->d.obj[i].rhs->type != JSMN_STRING &&
n->d.obj[i].rhs->type != JSMN_PRIMITIVE)
return NULL;
cp = strdup(n->d.obj[i].rhs->d.str);
if (cp == NULL)
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.
*/
enum chngstatus
json_parse_response(struct jsmnn *n)
{
char *resp;
enum chngstatus rc;
if (n == NULL)
return CHNG_INVALID;
if ((resp = json_getstr(n, "status")) == NULL)
return CHNG_INVALID;
if (strcmp(resp, "valid") == 0)
rc = CHNG_VALID;
else if (strcmp(resp, "pending") == 0)
rc = CHNG_PENDING;
else if (strcmp(resp, "processing") == 0)
rc = CHNG_PROCESSING;
else
rc = CHNG_INVALID;
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, *error;
size_t i;
int rc;
char *type;
if (n == NULL)
return 0;
array = json_getarray(n, "challenges");
if (array == NULL)
return 0;
for (i = 0; i < array->fields; i++) {
obj = json_getarrayobj(array->d.array[i]);
if (obj == NULL)
continue;
type = json_getstr(obj, "type");
if (type == NULL)
continue;
rc = strcmp(type, "http-01");
free(type);
if (rc)
continue;
p->uri = json_getstr(obj, "url");
p->token = json_getstr(obj, "token");
p->status = json_parse_response(obj);
if (p->status == CHNG_INVALID) {
error = json_getobj(obj, "error");
p->error = json_getstr(error, "detail");
}
return p->uri != NULL && p->token != NULL;
}
return 0;
}
static enum orderstatus
json_parse_order_status(struct jsmnn *n)
{
char *status;
if (n == NULL)
return ORDER_INVALID;
if ((status = json_getstr(n, "status")) == NULL)
return ORDER_INVALID;
if (strcmp(status, "pending") == 0)
return ORDER_PENDING;
else if (strcmp(status, "ready") == 0)
return ORDER_READY;
else if (strcmp(status, "processing") == 0)
return ORDER_PROCESSING;
else if (strcmp(status, "valid") == 0)
return ORDER_VALID;
else if (strcmp(status, "invalid") == 0)
return ORDER_INVALID;
else
return ORDER_INVALID;
}
/*
* Parse the response from a newOrder, which consists of a status
* a list of authorization urls and a finalize url into a struct.
*/
int
json_parse_order(struct jsmnn *n, struct order *order)
{
struct jsmnn *array;
size_t i;
char *finalize, *str;
order->status = json_parse_order_status(n);
if (n == NULL)
return 0;
if ((finalize = json_getstr(n, "finalize")) == NULL) {
warnx("no finalize field in order response");
return 0;
}
if ((order->finalize = strdup(finalize)) == NULL)
goto err;
if ((array = json_getarray(n, "authorizations")) == NULL)
goto err;
if (array->fields > 0) {
order->auths = calloc(array->fields, sizeof(*order->auths));
if (order->auths == NULL) {
warn("malloc");
goto err;
}
order->authsz = array->fields;
}
for (i = 0; i < array->fields; i++) {
str = json_getarraystr(array->d.array[i]);
if (str == NULL)
continue;
if ((order->auths[i] = strdup(str)) == NULL) {
warn("strdup");
goto err;
}
}
return 1;
err:
json_free_order(order);
return 0;
}
int
json_parse_upd_order(struct jsmnn *n, struct order *order)
{
char *certificate;
order->status = json_parse_order_status(n);
if ((certificate = json_getstr(n, "certificate")) != NULL) {
if ((order->certificate = strdup(certificate)) == NULL)
return 0;
}
return 1;
}
void
json_free_order(struct order *order)
{
size_t i;
free(order->finalize);
order->finalize = NULL;
for(i = 0; i < order->authsz; i++)
free(order->auths[i]);
free(order->auths);
order->finalize = NULL;
order->auths = NULL;
order->authsz = 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 (n == NULL)
return 0;
p->newaccount = json_getstr(n, "newAccount");
p->newnonce = json_getstr(n, "newNonce");
p->neworder = json_getstr(n, "newOrder");
p->revokecert = json_getstr(n, "revokeCert");
return p->newaccount != NULL && p->newnonce != NULL &&
p->neworder != NULL && p->revokecert != NULL;
}
/*
* Free up all of our CA-noted paths (which may all be NULL).
*/
void
json_free_capaths(struct capaths *p)
{
free(p->newaccount);
free(p->newnonce);
free(p->neworder);
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, *ntok;
int r;
size_t tokcount;
jsmn_init(&p);
tokcount = 128;
if ((tok = calloc(tokcount, sizeof(jsmntok_t))) == NULL) {
warn("calloc");
return NULL;
}
/* Do this until we don't need any more tokens. */
again:
/* Actually try to parse the JSON into the tokens. */
r = jsmn_parse(&p, buf, sz, tok, tokcount);
if (r < 0 && r == JSMN_ERROR_NOMEM) {
if ((ntok = recallocarray(tok, tokcount, tokcount * 2,
sizeof(jsmntok_t))) == NULL) {
warn("calloc");
free(tok);
return NULL;
}
tok = ntok;
tokcount *= 2;
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 "newAccount" resource request to check if the account exist.
*/
char *
json_fmt_chkacc(void)
{
int c;
char *p;
c = asprintf(&p, "{"
"\"termsOfServiceAgreed\": true, "
"\"onlyReturnExisting\": true"
"}");
if (c == -1) {
warn("asprintf");
p = NULL;
}
return p;
}
/*
* Format the "newAccount" resource request.
*/
char *
json_fmt_newacc(const char *contact)
{
int c;
char *p, *cnt = NULL;
if (contact != NULL) {
c = asprintf(&cnt, "\"contact\": [ \"%s\" ], ", contact);
if (c == -1) {
warn("asprintf");
return NULL;
}
}
c = asprintf(&p, "{"
"%s"
"\"termsOfServiceAgreed\": true"
"}", cnt == NULL ? "" : cnt);
free(cnt);
if (c == -1) {
warn("asprintf");
p = NULL;
}
return p;
}
/*
* Format the "newOrder" resource request
*/
char *
json_fmt_neworder(const char *const *alts, size_t altsz)
{
size_t i;
int c;
char *p, *t;
if ((p = strdup("{ \"identifiers\": [")) == NULL)
goto err;
t = p;
for (i = 0; i < altsz; i++) {
c = asprintf(&p,
"%s { \"type\": \"dns\", \"value\": \"%s\" }%s",
t, alts[i], i + 1 == altsz ? "" : ",");
free(t);
if (c == -1) {
warn("asprintf");
p = NULL;
goto err;
}
t = p;
}
c = asprintf(&p, "%s ] }", t);
free(t);
if (c == -1) {
warn("asprintf");
p = NULL;
}
return p;
err:
free(p);
return NULL;
}
/*
* Format the revoke resource request.
*/
char *
json_fmt_revokecert(const char *cert)
{
int c;
char *p;
c = asprintf(&p, "{"
"\"certificate\": \"%s\""
"}",
cert);
if (c == -1) {
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, "{"
"\"csr\": \"%s\""
"}",
cert);
if (c == -1) {
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,
const char *url)
{
int c;
char *p;
c = asprintf(&p, "{"
"\"alg\": \"RS256\", "
"\"jwk\": "
"{\"e\": \"%s\", \"kty\": \"RSA\", \"n\": \"%s\"}, "
"\"nonce\": \"%s\", "
"\"url\": \"%s\""
"}",
exp, mod, nce, url);
if (c == -1) {
warn("asprintf");
p = NULL;
}
return p;
}
/*
* Protected component of json_fmt_signed().
*/
char *
json_fmt_protected_ec(const char *x, const char *y, const char *nce,
const char *url)
{
int c;
char *p;
c = asprintf(&p, "{"
"\"alg\": \"ES384\", "
"\"jwk\": "
"{\"crv\": \"P-384\", \"kty\": \"EC\", \"x\": \"%s\", "
"\"y\": \"%s\"}, \"nonce\": \"%s\", \"url\": \"%s\""
"}",
x, y, nce, url);
if (c == -1) {
warn("asprintf");
p = NULL;
}
return p;
}
/*
* Protected component of json_fmt_signed().
*/
char *
json_fmt_protected_kid(const char *alg, const char *kid, const char *nce,
const char *url)
{
int c;
char *p;
c = asprintf(&p, "{"
"\"alg\": \"%s\", "
"\"kid\": \"%s\", "
"\"nonce\": \"%s\", "
"\"url\": \"%s\""
"}",
alg, kid, nce, url);
if (c == -1) {
warn("asprintf");
p = NULL;
}
return p;
}
/*
* Signed message contents for the CA server.
*/
char *
json_fmt_signed(const char *protected, const char *payload, const char *digest)
{
int c;
char *p;
c = asprintf(&p, "{"
"\"protected\": \"%s\", "
"\"payload\": \"%s\", "
"\"signature\": \"%s\""
"}",
protected, payload, digest);
if (c == -1) {
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 (c == -1) {
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_ec(const char *x, const char *y)
{
int c;
char *p;
/*NOTE: WHITESPACE IS IMPORTANT. */
c = asprintf(&p, "{\"crv\":\"P-384\",\"kty\":\"EC\",\"x\":\"%s\","
"\"y\":\"%s\"}",
x, y);
if (c == -1) {
warn("asprintf");
p = NULL;
}
return p;
}