~graywolf/acme-client-portable

d1be55cf1936f362f685484d7ce977a210fcf28a — Wolf 2 years ago d97b8fe
Update from openbsd (2019-06-07 00:39:47 UTC)
21 files changed, 1111 insertions(+), 711 deletions(-)

M CVS/Entries
M Makefile
M acctproc.c
M acme-client.1
M acme-client.conf.5
M certproc.c
M chngproc.c
M extern.h
M fileproc.c
M http.c
M http.h
M json.c
R rsa.c => key.c
R rsa.h => key.h
M keyproc.c
M main.c
M netproc.c
M parse.h
M parse.y
M revokeproc.c
M util.c
M CVS/Entries => CVS/Entries +25 -25
@@ 1,26 1,26 @@
/acme-client.1/1.29/Fri Mar  1 18:32:03 2019//
/acme-client.conf.5/1.17/Fri Mar  1 18:32:03 2019//
/Makefile/1.8/Sun Apr 28 09:09:06 2019//
/acctproc.c/1.12/Sun Apr 28 09:09:06 2019//
/base64.c/1.9/Sun Apr 28 09:09:06 2019//
/certproc.c/1.11/Sun Apr 28 09:09:06 2019//
/chngproc.c/1.13/Sun Apr 28 09:09:06 2019//
/dbg.c/1.4/Sun Apr 28 09:09:06 2019//
/dnsproc.c/1.9/Sun Apr 28 09:09:06 2019//
/extern.h/1.10/Sun Apr 28 09:09:06 2019//
/fileproc.c/1.15/Sun Apr 28 09:09:06 2019//
/http.c/1.25/Sun Apr 28 09:09:06 2019//
/http.h/1.7/Sun Apr 28 09:09:06 2019//
/jsmn.c/1.1/Sun Apr 28 09:09:06 2019//
/jsmn.h/1.1/Sun Apr 28 09:09:06 2019//
/json.c/1.11/Sun Apr 28 09:09:06 2019//
/keyproc.c/1.11/Sun Apr 28 09:09:06 2019//
/main.c/1.45/Sun Apr 28 09:09:06 2019//
/netproc.c/1.22/Sun Apr 28 09:09:06 2019//
/parse.h/1.9/Sun Apr 28 09:09:06 2019//
/parse.y/1.33/Sun Apr 28 09:09:06 2019//
/revokeproc.c/1.14/Sun Apr 28 09:09:06 2019//
/rsa.c/1.7/Sun Apr 28 09:09:06 2019//
/rsa.h/1.1/Sun Apr 28 09:09:06 2019//
/util.c/1.11/Sun Apr 28 09:09:06 2019//
/Makefile/1.9/Sat Jul  6 00:39:45 2019//
/acctproc.c/1.20/Sat Jul  6 00:39:45 2019//
/acme-client.1/1.32/Sat Jul  6 00:39:45 2019//
/acme-client.conf.5/1.21/Sat Jul  6 00:39:45 2019//
/base64.c/1.9/Sat Jul  6 00:38:20 2019//
/certproc.c/1.12/Sat Jul  6 00:39:45 2019//
/chngproc.c/1.14/Sat Jul  6 00:39:45 2019//
/dbg.c/1.4/Sat Jul  6 00:38:20 2019//
/dnsproc.c/1.9/Sat Jul  6 00:38:20 2019//
/extern.h/1.16/Sat Jul  6 00:39:45 2019//
/fileproc.c/1.16/Sat Jul  6 00:39:45 2019//
/http.c/1.27/Sat Jul  6 00:39:45 2019//
/http.h/1.8/Sat Jul  6 00:39:45 2019//
/jsmn.c/1.1/Sat Jul  6 00:38:20 2019//
/jsmn.h/1.1/Sat Jul  6 00:38:20 2019//
/json.c/1.14/Sat Jul  6 00:39:45 2019//
/key.c/1.2/Mon Jun 17 15:41:59 2019//
/key.h/1.1/Wed Jun 12 11:09:25 2019//
/keyproc.c/1.15/Sat Jul  6 00:39:46 2019//
/main.c/1.52/Sat Jul  6 00:39:46 2019//
/netproc.c/1.24/Sat Jul  6 00:39:46 2019//
/parse.h/1.13/Sat Jul  6 00:39:46 2019//
/parse.y/1.38/Sat Jul  6 00:39:46 2019//
/revokeproc.c/1.15/Sat Jul  6 00:39:46 2019//
/util.c/1.12/Sat Jul  6 00:39:46 2019//
D

M Makefile => Makefile +2 -2
@@ 1,8 1,8 @@
#	$OpenBSD: Makefile,v 1.7 2017/01/21 08:41:42 benno Exp $
#	$OpenBSD: Makefile,v 1.8 2017/07/03 22:21:47 espie Exp $
PROG=		acme-client
SRCS=		acctproc.c base64.c certproc.c chngproc.c dbg.c dnsproc.c
SRCS+=		fileproc.c http.c jsmn.c json.c keyproc.c main.c netproc.c
SRCS+=		parse.y revokeproc.c rsa.c util.c
SRCS+=		parse.y revokeproc.c key.c util.c

MAN=		acme-client.1 acme-client.conf.5


M acctproc.c => acctproc.c +219 -48
@@ 1,4 1,4 @@
/*	$Id: acctproc.c,v 1.12 2018/07/28 15:25:23 tb Exp $ */
/*	$Id: acctproc.c,v 1.20 2019/06/17 15:20:10 tb Exp $ */
/*
 * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>
 *


@@ 24,12 24,13 @@
#include <unistd.h>

#include <openssl/pem.h>
#include <openssl/evp.h>
#include <openssl/rsa.h>
#include <openssl/rand.h>
#include <openssl/err.h>

#include "extern.h"
#include "rsa.h"
#include "key.h"

/*
 * Converts a BIGNUM to the form used in JWK.


@@ 76,8 77,8 @@ op_thumb_rsa(EVP_PKEY *pkey)
	char	*exp = NULL, *mod = NULL, *json = NULL;
	RSA	*r;

	if ((r = EVP_PKEY_get1_RSA(pkey)) == NULL)
		warnx("EVP_PKEY_get1_RSA");
	if ((r = EVP_PKEY_get0_RSA(pkey)) == NULL)
		warnx("EVP_PKEY_get0_RSA");
	else if ((mod = bn2string(r->n)) == NULL)
		warnx("bn2string");
	else if ((exp = bn2string(r->e)) == NULL)


@@ 91,6 92,41 @@ op_thumb_rsa(EVP_PKEY *pkey)
}

/*
 * Extract the relevant EC components from the key and create the JSON
 * thumbprint from them.
 */
static char *
op_thumb_ec(EVP_PKEY *pkey)
{
	BIGNUM	*X = NULL, *Y = NULL;
	EC_KEY	*ec = NULL;
	char	*x = NULL, *y = NULL;
	char	*json = NULL;

	if ((ec = EVP_PKEY_get0_EC_KEY(pkey)) == NULL)
		warnx("EVP_PKEY_get0_EC_KEY");
	else if ((X = BN_new()) == NULL)
		warnx("BN_new");
	else if ((Y = BN_new()) == NULL)
		warnx("BN_new");
	else if (!EC_POINT_get_affine_coordinates_GFp(EC_KEY_get0_group(ec),
	    EC_KEY_get0_public_key(ec), X, Y, NULL))
		warnx("EC_POINT_get_affine_coordinates_GFp");
	else if ((x = bn2string(X)) == NULL)
		warnx("bn2string");
	else if ((y = bn2string(Y)) == NULL)
		warnx("bn2string");
	else if ((json = json_fmt_thumb_ec(x, y)) == NULL)
		warnx("json_fmt_thumb_rsa");

	BN_free(X);
	BN_free(Y);
	free(x);
	free(y);
	return json;
}

/*
 * The thumbprint operation is used for the challenge sequence.
 */
static int


@@ 109,6 145,10 @@ op_thumbprint(int fd, EVP_PKEY *pkey)
		if ((thumb = op_thumb_rsa(pkey)) != NULL)
			break;
		goto out;
	case EVP_PKEY_EC:
		if ((thumb = op_thumb_ec(pkey)) != NULL)
			break;
		goto out;
	default:
		warnx("EVP_PKEY_type: unknown key type");
		goto out;


@@ 124,8 164,8 @@ op_thumbprint(int fd, EVP_PKEY *pkey)
	if ((dig = malloc(EVP_MAX_MD_SIZE)) == NULL) {
		warn("malloc");
		goto out;
	} else if ((ctx = EVP_MD_CTX_create()) == NULL) {
		warnx("EVP_MD_CTX_create");
	} else if ((ctx = EVP_MD_CTX_new()) == NULL) {
		warnx("EVP_MD_CTX_new");
		goto out;
	} else if (!EVP_DigestInit_ex(ctx, EVP_sha256(), NULL)) {
		warnx("EVP_SignInit_ex");


@@ 144,9 184,7 @@ op_thumbprint(int fd, EVP_PKEY *pkey)

	rc = 1;
out:
	if (ctx != NULL)
		EVP_MD_CTX_destroy(ctx);

	EVP_MD_CTX_free(ctx);
	free(thumb);
	free(dig);
	free(dig64);


@@ 154,30 192,26 @@ out:
}

static int
op_sign_rsa(char **head, char **prot, EVP_PKEY *pkey, const char *nonce)
op_sign_rsa(char **prot, EVP_PKEY *pkey, const char *nonce, const char *url)
{
	char	*exp = NULL, *mod = NULL;
	int	rc = 0;
	RSA	*r;

	*head = NULL;
	*prot = NULL;

	/*
	 * First, extract relevant portions of our private key.
	 * Then construct the public header.
	 * Finally, format the header combined with the nonce.
	 */

	if ((r = EVP_PKEY_get1_RSA(pkey)) == NULL)
		warnx("EVP_PKEY_get1_RSA");
	if ((r = EVP_PKEY_get0_RSA(pkey)) == NULL)
		warnx("EVP_PKEY_get0_RSA");
	else if ((mod = bn2string(r->n)) == NULL)
		warnx("bn2string");
	else if ((exp = bn2string(r->e)) == NULL)
		warnx("bn2string");
	else if ((*head = json_fmt_header_rsa(exp, mod)) == NULL)
		warnx("json_fmt_header_rsa");
	else if ((*prot = json_fmt_protected_rsa(exp, mod, nonce)) == NULL)
	else if ((*prot = json_fmt_protected_rsa(exp, mod, nonce, url)) == NULL)
		warnx("json_fmt_protected_rsa");
	else
		rc = 1;


@@ 187,20 221,61 @@ op_sign_rsa(char **head, char **prot, EVP_PKEY *pkey, const char *nonce)
	return rc;
}

static int
op_sign_ec(char **prot, EVP_PKEY *pkey, const char *nonce, const char *url)
{
	BIGNUM	*X = NULL, *Y = NULL;
	EC_KEY	*ec = NULL;
	char	*x = NULL, *y = NULL;
	int	rc = 0;

	*prot = NULL;

	if ((ec = EVP_PKEY_get0_EC_KEY(pkey)) == NULL)
		warnx("EVP_PKEY_get0_EC_KEY");
	else if ((X = BN_new()) == NULL)
		warnx("BN_new");
	else if ((Y = BN_new()) == NULL)
		warnx("BN_new");
	else if (!EC_POINT_get_affine_coordinates_GFp(EC_KEY_get0_group(ec),
	    EC_KEY_get0_public_key(ec), X, Y, NULL))
		warnx("EC_POINT_get_affine_coordinates_GFp");
	else if ((x = bn2string(X)) == NULL)
		warnx("bn2string");
	else if ((y = bn2string(Y)) == NULL)
		warnx("bn2string");
	else if ((*prot = json_fmt_protected_ec(x, y, nonce, url)) == NULL)
		warnx("json_fmt_protected_ec");
	else
		rc = 1;

	BN_free(X);
	BN_free(Y);
	free(x);
	free(y);
	return rc;
}

/*
 * Operation to sign a message with the account key.
 * This requires the sender ("fd") to provide the payload and a nonce.
 */
static int
op_sign(int fd, EVP_PKEY *pkey)
op_sign(int fd, EVP_PKEY *pkey, enum acctop op)
{
	char		*nonce = NULL, *pay = NULL, *pay64 = NULL;
	char		*prot = NULL, *prot64 = NULL, *head = NULL;
	char		*sign = NULL, *dig64 = NULL, *fin = NULL;
	unsigned char	*dig = NULL;
	EVP_MD_CTX	*ctx = NULL;
	int		 cc, rc = 0;
	unsigned int	 digsz;
	EVP_MD_CTX		*ctx = NULL;
	const EVP_MD		*evp_md = NULL;
	EC_KEY			*ec;
	ECDSA_SIG		*ec_sig = NULL;
	const BIGNUM		*ec_sig_r = NULL, *ec_sig_s = NULL;
	int			 cc, rc = 0;
	unsigned int		 digsz, bufsz, degree, bn_len, r_len, s_len;
	char			*nonce = NULL, *pay = NULL, *pay64 = NULL;
	char			*prot = NULL, *prot64 = NULL;
	char			*sign = NULL, *dig64 = NULL, *fin = NULL;
	char			*url = NULL, *kid = NULL, *alg = NULL;
	unsigned char		*dig = NULL, *buf = NULL;
	const unsigned char	*digp;

	/* Read our payload and nonce from the requestor. */



@@ 208,6 283,12 @@ op_sign(int fd, EVP_PKEY *pkey)
		goto out;
	else if ((nonce = readstr(fd, COMM_NONCE)) == NULL)
		goto out;
	else if ((url = readstr(fd, COMM_URL)) == NULL)
		goto out;

	if (op == ACCT_KID_SIGN)
		if ((kid = readstr(fd, COMM_KID)) == NULL)
			goto out;

	/* Base64-encode the payload. */



@@ 218,14 299,40 @@ op_sign(int fd, EVP_PKEY *pkey)

	switch (EVP_PKEY_type(pkey->type)) {
	case EVP_PKEY_RSA:
		if (!op_sign_rsa(&head, &prot, pkey, nonce))
			goto out;
		alg = "RS256";
		evp_md = EVP_sha256();
		break;
	case EVP_PKEY_EC:
		alg = "ES384";
		evp_md = EVP_sha384();
		break;
	default:
		warnx("EVP_PKEY_type");
		warnx("unknown account key type");
		goto out;
	}

	if (op == ACCT_KID_SIGN) {
		if ((prot = json_fmt_protected_kid(alg, kid, nonce, url)) ==
		    NULL) {
			warnx("json_fmt_protected_kid");
			goto out;
		}
	} else {
		switch (EVP_PKEY_type(pkey->type)) {
		case EVP_PKEY_RSA:
			if (!op_sign_rsa(&prot, pkey, nonce, url))
				goto out;
			break;
		case EVP_PKEY_EC:
			if (!op_sign_ec(&prot, pkey, nonce, url))
				goto out;
			break;
		default:
			warnx("EVP_PKEY_type");
			goto out;
		}
	}

	/* The header combined with the nonce, base64. */

	if ((prot64 = base64buf_url(prot, strlen(prot))) == NULL) {


@@ 252,10 359,10 @@ op_sign(int fd, EVP_PKEY *pkey)
	 * sign a SHA256 digest of our message.
	 */

	if ((ctx = EVP_MD_CTX_create()) == NULL) {
		warnx("EVP_MD_CTX_create");
	if ((ctx = EVP_MD_CTX_new()) == NULL) {
		warnx("EVP_MD_CTX_new");
		goto out;
	} else if (!EVP_SignInit_ex(ctx, EVP_sha256(), NULL)) {
	} else if (!EVP_SignInit_ex(ctx, evp_md, NULL)) {
		warnx("EVP_SignInit_ex");
		goto out;
	} else if (!EVP_SignUpdate(ctx, sign, strlen(sign))) {


@@ 264,8 371,57 @@ op_sign(int fd, EVP_PKEY *pkey)
	} else if (!EVP_SignFinal(ctx, dig, &digsz, pkey)) {
		warnx("EVP_SignFinal");
		goto out;
	} else if ((dig64 = base64buf_url((char *)dig, digsz)) == NULL) {
		warnx("base64buf_url");
	}

	switch (EVP_PKEY_type(pkey->type)) {
	case EVP_PKEY_RSA:
		if ((dig64 = base64buf_url((char *)dig, digsz)) == NULL) {
			warnx("base64buf_url");
			goto out;
		}
		break;
	case EVP_PKEY_EC:
		if ((ec = EVP_PKEY_get0_EC_KEY(pkey)) == NULL) {
			warnx("EVP_PKEY_get0_EC_KEY");
			goto out;
		}
		degree = EC_GROUP_get_degree(EC_KEY_get0_group(ec));
		bn_len = (degree + 7) / 8;

		digp = dig; /* d2i_ECDSA_SIG advances digp */
		if ((ec_sig = d2i_ECDSA_SIG(NULL, &digp, digsz)) == NULL) {
			warnx("d2i_ECDSA_SIG");
			goto out;
		}

		ECDSA_SIG_get0(ec_sig, &ec_sig_r, &ec_sig_s);

		r_len = BN_num_bytes(ec_sig_r);
		s_len = BN_num_bytes(ec_sig_s);

		if((r_len > bn_len) || (s_len > bn_len)) {
			warnx("ECDSA_SIG_get0");
			goto out;
		}

		bufsz = 2 * bn_len;
		if ((buf = calloc(1, bufsz)) == NULL) {
			warnx("calloc");
			goto out;
		}

		/* put r and s in with leading zeros if any */
		BN_bn2bin(ec_sig_r, buf + bn_len - r_len);
		BN_bn2bin(ec_sig_s, buf + bufsz - s_len);

		if ((dig64 = base64buf_url((char *)buf, bufsz)) == NULL) {
			warnx("base64buf_url");
			goto out;
		}

		break;
	default:
		warnx("EVP_PKEY_type");
		goto out;
	}



@@ 275,7 431,7 @@ op_sign(int fd, EVP_PKEY *pkey)
	 * when we next enter the read loop).
	 */

	if ((fin = json_fmt_signed(head, prot64, pay64, dig64)) == NULL) {
	if ((fin = json_fmt_signed(prot64, pay64, dig64)) == NULL) {
		warnx("json_fmt_signed");
		goto out;
	} else if (writestr(fd, COMM_REQ, fin) < 0)


@@ 283,30 439,30 @@ op_sign(int fd, EVP_PKEY *pkey)

	rc = 1;
out:
	if (ctx != NULL)
		EVP_MD_CTX_destroy(ctx);

	EVP_MD_CTX_free(ctx);
	free(pay);
	free(sign);
	free(pay64);
	free(url);
	free(nonce);
	free(head);
	free(kid);
	free(prot);
	free(prot64);
	free(dig);
	free(dig64);
	free(fin);
	free(buf);
	return rc;
}

int
acctproc(int netsock, const char *acctkey, int newacct)
acctproc(int netsock, const char *acctkey, enum keytype keytype)
{
	FILE		*f = NULL;
	EVP_PKEY	*pkey = NULL;
	long		 lval;
	enum acctop	 op;
	int		 rc = 0, cc;
	int		 rc = 0, cc, newacct = 0;
	mode_t		 prev;

	/*


@@ 316,7 472,10 @@ acctproc(int netsock, const char *acctkey, int newacct)
	 */

	prev = umask((S_IWUSR | S_IXUSR) | S_IRWXG | S_IRWXO);
	f = fopen(acctkey, newacct ? "wx" : "r");
	if ((f = fopen(acctkey, "r")) == NULL && errno == ENOENT) {
		f = fopen(acctkey, "wx");
		newacct = 1;
	}
	umask(prev);

	if (f == NULL) {


@@ 334,13 493,23 @@ acctproc(int netsock, const char *acctkey, int newacct)
	}

	if (newacct) {
		if ((pkey = rsa_key_create(f, acctkey)) == NULL)
			goto out;
		dodbg("%s: generated RSA account key", acctkey);
		switch (keytype) {
		case KT_ECDSA:
			if ((pkey = ec_key_create(f, acctkey)) == NULL)
				goto out;
			dodbg("%s: generated ECDSA account key", acctkey);
			break;
		case KT_RSA:
			if ((pkey = rsa_key_create(f, acctkey)) == NULL)
				goto out;
			dodbg("%s: generated RSA account key", acctkey);
			break;
		}
	} else {
		if ((pkey = rsa_key_load(f, acctkey)) == NULL)
		if ((pkey = key_load(f, acctkey)) == NULL)
			goto out;
		doddbg("%s: loaded RSA account key", acctkey);
		/* XXX check if account key type equals configured key type */
		doddbg("%s: loaded account key", acctkey);
	}

	fclose(f);


@@ 363,7 532,8 @@ acctproc(int netsock, const char *acctkey, int newacct)
		op = ACCT__MAX;
		if ((lval = readop(netsock, COMM_ACCT)) == 0)
			op = ACCT_STOP;
		else if (lval == ACCT_SIGN || lval == ACCT_THUMBPRINT)
		else if (lval == ACCT_SIGN || lval == ACCT_KID_SIGN ||
		    lval == ACCT_THUMBPRINT)
			op = lval;

		if (ACCT__MAX == op) {


@@ 374,7 544,8 @@ acctproc(int netsock, const char *acctkey, int newacct)

		switch (op) {
		case ACCT_SIGN:
			if (op_sign(netsock, pkey))
		case ACCT_KID_SIGN:
			if (op_sign(netsock, pkey, op))
				break;
			warnx("op_sign");
			goto out;

M acme-client.1 => acme-client.1 +10 -26
@@ 1,4 1,4 @@
.\"	$OpenBSD: acme-client.1,v 1.28 2019/01/30 21:15:14 jmc Exp $
.\"	$OpenBSD: acme-client.1,v 1.31 2019/06/08 07:52:55 florian Exp $
.\"
.\" Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>
.\"


@@ 14,7 14,7 @@
.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
.\"
.Dd $Mdocdate: February 3 2019 $
.Dd $Mdocdate: June 15 2019 $
.Dt ACME-CLIENT 1
.Os
.Sh NAME


@@ 22,7 22,7 @@
.Nd ACME client
.Sh SYNOPSIS
.Nm acme-client
.Op Fl ADFnrv
.Op Fl Fnrv
.Op Fl f Ar configfile
.Ar domain
.Sh DESCRIPTION


@@ 40,16 40,6 @@ The certificates are typically used to provide HTTPS for web servers,
but can be used in any situation where domain name validation is required
(such as mail servers).
.Pp
Before a certificate can be requested, an account key needs to be
created using the
.Fl A
argument.
The first time a certificate is requested, a domain key needs to be created with
.Fl D .
So a typical invocation the first time it's run would be:
.Pp
.Dl # acme-client -ADv example.com
.Pp
If the certificate already exists and is less than 30 days from expiry,
.Nm
attempts to renew the certificate.


@@ 76,10 66,6 @@ location "/.well-known/acme-challenge/*" {
.Pp
The options are as follows:
.Bl -tag -width Ds
.It Fl A
Create a new RSA account key if one does not already exist.
.It Fl D
Create a new RSA domain key if one does not already exist.
.It Fl F
Force certificate renewal, even if it's too soon.
.It Fl f Ar configfile


@@ 127,7 113,7 @@ and
.Pa httpd.conf
and run:
.Pp
.Dl # acme-client -ADv example.com && rcctl reload httpd
.Dl # acme-client -v example.com && rcctl reload httpd
.Pp
A
.Xr cron 8


@@ 145,7 131,12 @@ is reloaded:
.Xr httpd.conf 5
.Sh STANDARDS
.Rs
.%U https://tools.ietf.org/html/draft-ietf-acme-acme-03
.%A R. Barnes
.%A J. Hoffman-Andrews
.%A D. McCarney
.%A J. Kasten
.%D March 2019
.%R RFC 8555
.%T Automatic Certificate Management Environment (ACME)
.Re
.Sh HISTORY


@@ 158,10 149,3 @@ The
.Nm
utility was written by
.An Kristaps Dzonsons Aq Mt kristaps@bsd.lv .
.Sh BUGS
The challenge and certificate processes currently retain their (root)
privileges.
.Pp
For the time being,
.Nm
only supports RSA as an account key format.

M acme-client.conf.5 => acme-client.conf.5 +19 -5
@@ 1,4 1,4 @@
.\"	$OpenBSD: acme-client.conf.5,v 1.16 2018/08/03 17:48:34 benno Exp $
.\"	$OpenBSD: acme-client.conf.5,v 1.20 2019/06/17 12:42:52 florian Exp $
.\"
.\" Copyright (c) 2005 Esben Norby <norby@openbsd.org>
.\" Copyright (c) 2004 Claudio Jeker <claudio@openbsd.org>


@@ 17,7 17,7 @@
.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
.\"
.Dd $Mdocdate: January 8 2019 $
.Dd $Mdocdate: July 4 2019 $
.Dt ACME-CLIENT.CONF 5
.Os
.Sh NAME


@@ 63,7 63,7 @@ Macros are not expanded inside quotes.
.Pp
For example:
.Bd -literal -offset indent
api_url="https://acme-v01.api.letsencrypt.org/directory"
api_url="https://acme-v02.api.letsencrypt.org/directory"
authority letsencrypt {
	api url $api_url
	account key "/etc/acme/letsencrypt-privkey.pem"


@@ 83,10 83,17 @@ is a string used to reference this certificate authority.
.Pp
It is followed by a block of options enclosed in curly brackets:
.Bl -tag -width Ds
.It Ic account key Ar file
.It Ic account key Ar file Op Ar keytype
Specify a
.Ar file
used to identify the user of this certificate authority.
.Ar keytype
can be
.Cm rsa
or
.Cm ecdsa .
It defaults to
.Cm rsa .
.It Ic api url Ar url
Specify the
.Ar url


@@ 109,8 116,15 @@ Specify a list of alternative names for which the certificate will be valid.
The common name is included automatically if this option is present,
but there is no automatic conversion/inclusion between "www." and
plain domain name forms.
.It Ic domain key Ar file
.It Ic domain key Ar file Op Ar keytype
The private key file for which the certificate will be obtained.
.Ar keytype
can be
.Cm rsa
or
.Cm ecdsa .
It defaults to
.Cm rsa .
.It Ic domain certificate Ar file
The filename of the certificate that will be issued.
This is optional if

M certproc.c => certproc.c +17 -131
@@ 1,4 1,4 @@
/*	$Id: certproc.c,v 1.11 2018/07/28 15:25:23 tb Exp $ */
/*	$Id: certproc.c,v 1.12 2019/06/07 08:07:52 florian Exp $ */
/*
 * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>
 *


@@ 28,73 28,17 @@

#include "extern.h"

#define MARKER "-----BEGIN CERTIFICATE-----"

/*
 * Convert an X509 certificate to a buffer of "sz".
 * We don't guarantee that it's NUL-terminated.
 * Returns NULL on failure.
 */
static char *
x509buf(X509 *x, size_t *sz)
{
	BIO	*bio;
	char	*p;
	int	 ssz;

	/* Convert X509 to PEM in BIO. */

	if ((bio = BIO_new(BIO_s_mem())) == NULL) {
		warnx("BIO_new");
		return NULL;
	} else if (!PEM_write_bio_X509(bio, x)) {
		warnx("PEM_write_bio_X509");
		BIO_free(bio);
		return NULL;
	}

	/*
	 * Now convert bio to string.
	 * Make into NUL-terminated, just in case.
	 */

	if ((p = calloc(1, bio->num_write + 1)) == NULL) {
		warn("calloc");
		BIO_free(bio);
		return NULL;
	}

	ssz = BIO_read(bio, p, bio->num_write);
	if (ssz < 0 || (unsigned)ssz != bio->num_write) {
		warnx("BIO_read");
		BIO_free(bio);
		return NULL;
	}

	*sz = ssz;
	BIO_free(bio);
	return p;
}
#define MARKER "-----END CERTIFICATE-----\n"

int
certproc(int netsock, int filesock)
{
	char		*csr = NULL, *chain = NULL, *url = NULL;
	unsigned char	*csrcp, *chaincp;
	char		*chaincp;
	size_t		 csrsz, chainsz;
	int		 i, rc = 0, idx = -1, cc;
	int		 rc = 0, cc;
	enum certop	 op;
	long		 lval;
	X509		*x = NULL, *chainx = NULL;
	X509_EXTENSION	*ext = NULL;
	X509V3_EXT_METHOD *method = NULL;
	void		*entries;
	STACK_OF(CONF_VALUE) *val;
	CONF_VALUE	*nval;

	/* File-system and sandbox jailing. */

	ERR_load_crypto_strings();

	if (pledge("stdio", NULL) == -1) {
		warn("pledge");


@@ 137,74 81,28 @@ certproc(int netsock, int filesock)
	if ((csr = readbuf(netsock, COMM_CSR, &csrsz)) == NULL)
		goto out;

	csrcp = (u_char *)csr;
	x = d2i_X509(NULL, (const u_char **)&csrcp, csrsz);
	if (x == NULL) {
		warnx("d2i_X509");
	if (csrsz < strlen(MARKER)) {
		warnx("invalid cert");
		goto out;
	}

	/*
	 * Extract the CA Issuers from its NID.
	 * TODO: I have no idea what I'm doing.
	 */

	idx = X509_get_ext_by_NID(x, NID_info_access, idx);
	if (idx >= 0 && (ext = X509_get_ext(x, idx)) != NULL)
		method = (X509V3_EXT_METHOD *)X509V3_EXT_get(ext);

	entries = X509_get_ext_d2i(x, NID_info_access, 0, 0);
	if (method != NULL && entries != NULL) {
		val = method->i2v(method, entries, 0);
		for (i = 0; i < sk_CONF_VALUE_num(val); i++) {
			nval = sk_CONF_VALUE_value(val, i);
			if (strcmp(nval->name, "CA Issuers - URI"))
				continue;
			url = strdup(nval->value);
			if (url == NULL) {
				warn("strdup");
				goto out;
			}
			break;
		}
	}
	chaincp = strstr(csr, MARKER);

	if (url == NULL) {
		warnx("no CA issuer registered with certificate");
	if (chaincp == NULL) {
		warnx("invalid cert");
		goto out;
	}

	/* Write the CA issuer to the netsock. */

	if (writestr(netsock, COMM_ISSUER, url) <= 0)
	chaincp += strlen(MARKER);
	if ((chain = strdup(chaincp)) == NULL) {
		warn("strdup");
		goto out;

	/* Read the full-chain back from the netsock. */

	if ((chain = readbuf(netsock, COMM_CHAIN, &chainsz)) == NULL)
		goto out;

	/*
	 * Then check if the chain is PEM-encoded by looking to see if
	 * it begins with the PEM marker.
	 * If so, ship it as-is; otherwise, convert to a PEM encoded
	 * buffer and ship that.
	 * FIXME: if PEM, re-parse it.
	 */

	if (chainsz <= strlen(MARKER) ||
	    strncmp(chain, MARKER, strlen(MARKER))) {
		chaincp = (u_char *)chain;
		chainx = d2i_X509(NULL, (const u_char **)&chaincp, chainsz);
		if (chainx == NULL) {
			warnx("d2i_X509");
			goto out;
		}
		free(chain);
		if ((chain = x509buf(chainx, &chainsz)) == NULL)
			goto out;
	}

	*chaincp = '\0';
	chainsz = strlen(chain);
	csrsz = strlen(csr);

	/* Allow reader termination to just push us out. */

	if ((cc = writeop(filesock, COMM_CHAIN_OP, FILE_CREATE)) == 0)


@@ 216,27 114,15 @@ certproc(int netsock, int filesock)
	if (cc <= 0)
		goto out;

	/*
	 * Next, convert the X509 to a buffer and send that.
	 * Reader failure doesn't change anything.
	 */

	free(chain);
	if ((chain = x509buf(x, &chainsz)) == NULL)
		goto out;
	if (writebuf(filesock, COMM_CSR, chain, chainsz) < 0)
	if (writebuf(filesock, COMM_CSR, csr, csrsz) < 0)
		goto out;

	rc = 1;
out:
	close(netsock);
	close(filesock);
	X509_free(x);
	X509_free(chainx);
	free(csr);
	free(url);
	free(chain);
	ERR_print_errors_fp(stderr);
	ERR_free_strings();
	return rc;
}

M chngproc.c => chngproc.c +15 -13
@@ 1,4 1,4 @@
/*	$Id: chngproc.c,v 1.13 2019/04/01 04:18:54 naddy Exp $ */
/*	$Id: chngproc.c,v 1.14 2019/06/16 19:49:13 florian Exp $ */
/*
 * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>
 *


@@ 36,14 36,12 @@ chngproc(int netsock, const char *root)
	enum chngop	  op;
	void		 *pp;

	if (chroot(root) == -1) {
		warn("chroot");
		goto out;
	}
	if (chdir("/") == -1) {
		warn("chdir");

	if (unveil(root, "wc") == -1) {
		warn("unveil");
		goto out;
	}

	if (pledge("stdio cpath wpath", NULL) == -1) {
		warn("pledge");
		goto out;


@@ 80,6 78,11 @@ chngproc(int netsock, const char *root)
		else if ((tok = readstr(netsock, COMM_TOK)) == NULL)
			goto out;

		if (asprintf(&fmt, "%s.%s", tok, th) == -1) {
			warn("asprintf");
			goto out;
		}

		/* Vector appending... */

		pp = reallocarray(fs, (fsz + 1), sizeof(char *));


@@ 88,14 91,13 @@ chngproc(int netsock, const char *root)
			goto out;
		}
		fs = pp;
		fs[fsz] = tok;
		tok = NULL;
		fsz++;

		if (asprintf(&fmt, "%s.%s", fs[fsz - 1], th) == -1) {
		if (asprintf(&fs[fsz], "%s/%s", root, tok) == -1) {
			warn("asprintf");
			goto out;
		}
		fsz++;
		free(tok);
		tok = NULL;

		/*
		 * Create and write to our challenge file.


@@ 121,7 123,7 @@ chngproc(int netsock, const char *root)
		free(fmt);
		th = fmt = NULL;

		dodbg("%s/%s: created", root, fs[fsz - 1]);
		dodbg("%s: created", fs[fsz - 1]);

		/*
		 * Write our acknowledgement.

M extern.h => extern.h +45 -19
@@ 1,4 1,4 @@
/*	$Id: extern.h,v 1.10 2019/01/31 15:55:48 benno Exp $ */
/*	$Id: extern.h,v 1.16 2019/06/17 12:42:52 florian Exp $ */
/*
 * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>
 *


@@ 32,6 32,7 @@ enum	acctop {
	ACCT_STOP = 0,
	ACCT_READY,
	ACCT_SIGN,
	ACCT_KID_SIGN,
	ACCT_THUMBPRINT,
	ACCT__MAX
};


@@ 118,6 119,8 @@ enum	comm {
	COMM_CERT,
	COMM_PAY,
	COMM_NONCE,
	COMM_KID,
	COMM_URL,
	COMM_TOK,
	COMM_CHNG_OP,
	COMM_CHNG_ACK,


@@ 149,7 152,8 @@ enum	comm {
enum	chngstatus {
	CHNG_INVALID = -1,
	CHNG_PENDING = 0,
	CHNG_VALID = 1
	CHNG_PROCESSING = 1,
	CHNG_VALID = 2
};

struct	chng {


@@ 159,16 163,32 @@ struct	chng {
	enum chngstatus	 status; /* challenge accepted? */
};

enum	orderstatus {
	ORDER_INVALID = -1,
	ORDER_PENDING = 0,
	ORDER_READY = 1,
	ORDER_PROCESSING = 2,
	ORDER_VALID = 3
};

struct	order {
	char			*uri;		/* uri of the order request */
	char			*finalize;	/* finalize uri */
	char			*certificate;	/* uri for issued certificate */
	enum orderstatus	 status;	/* status of order */
	char			**auths;	/* authorization uris */
	size_t			 authsz;
};

/*
 * This consists of the services offered by the CA.
 * They must all be filled in.
 */
struct	capaths {
	char		*newauthz; /* new authorisation */
	char		*newcert;  /* sign certificate */
	char		*newreg; /* new acme account */
	char		*newaccount;	/* new acme account */
	char		*newnonce;	/* new nonce */
	char		*neworder;	/* order new certificate */
	char		*revokecert; /* revoke certificate */
	char		*agreement; /* terms of service */
};

struct	jsmnn;


@@ 179,17 199,17 @@ __BEGIN_DECLS
 * Start with our components.
 * These are all isolated and talk to each other using sockets.
 */
int		 acctproc(int, const char *, int);
int		 acctproc(int, const char *, enum keytype);
int		 certproc(int, int);
int		 chngproc(int, const char *);
int		 dnsproc(int);
int		 revokeproc(int, const char *, const char *,
			int, int, const char *const *, size_t);
int		 revokeproc(int, const char *, int, int, const char *const *,
			size_t);
int		 fileproc(int, const char *, const char *, const char *,
			const char *);
int		 keyproc(int, const char *,
			const char **, size_t, int);
int		 netproc(int, int, int, int, int, int, int, int,
int		 keyproc(int, const char *, const char **, size_t,
			enum keytype);
int		 netproc(int, int, int, int, int, int, int,
			struct authority_c *, const char *const *,
			size_t);



@@ 233,20 253,26 @@ void		 json_free(struct jsmnn *);
int		 json_parse_response(struct jsmnn *);
void		 json_free_challenge(struct chng *);
int		 json_parse_challenge(struct jsmnn *, struct chng *);
void		 json_free_order(struct order *);
int		 json_parse_order(struct jsmnn *, struct order *);
int		 json_parse_upd_order(struct jsmnn *, struct order *);
void		 json_free_capaths(struct capaths *);
int		 json_parse_capaths(struct jsmnn *, struct capaths *);

char		*json_fmt_challenge(const char *, const char *);
char		*json_fmt_newauthz(const char *);
char		*json_fmt_newcert(const char *);
char		*json_fmt_newreg(const char *);
char		*json_fmt_chkacc(void);
char		*json_fmt_newacc(void);
char		*json_fmt_neworder(const char *const *, size_t);
char		*json_fmt_protected_rsa(const char *,
			const char *, const char *);
			const char *, const char *, const char *);
char		*json_fmt_protected_ec(const char *, const char *, const char *,
			const char *);
char		*json_fmt_protected_kid(const char*, const char *, const char *,
			const char *);
char		*json_fmt_revokecert(const char *);
char		*json_fmt_header_rsa(const char *, const char *);
char		*json_fmt_thumb_rsa(const char *, const char *);
char		*json_fmt_signed(const char *,
			const char *, const char *, const char *);
char		*json_fmt_thumb_ec(const char *, const char *);
char		*json_fmt_signed(const char *, const char *, const char *);

/*
 * Should we print debugging messages?

M fileproc.c => fileproc.c +12 -17
@@ 1,4 1,4 @@
/*	$Id: fileproc.c,v 1.15 2018/07/29 20:15:23 benno Exp $ */
/*	$Id: fileproc.c,v 1.16 2019/06/16 19:49:13 florian Exp $ */
/*
 * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>
 *


@@ 86,12 86,8 @@ fileproc(int certsock, const char *certdir, const char *certfile, const char
	long		 lval;
	enum fileop	 op;

	if (chroot(certdir) == -1) {
		warn("chroot");
		goto out;
	}
	if (chdir("/") == -1) {
		warn("chdir");
	if (unveil(certdir, "rwc") == -1) {
		warn("unveil");
		goto out;
	}



@@ 129,27 125,26 @@ fileproc(int certsock, const char *certdir, const char *certfile, const char
	if (FILE_REMOVE == op) {
		if (certfile) {
			if (unlink(certfile) == -1 && errno != ENOENT) {
				warn("%s/%s", certdir, certfile);
				warn("%s", certfile);
				goto out;
			} else
				dodbg("%s/%s: unlinked", certdir, certfile);
				dodbg("%s: unlinked", certfile);
		}

		if (chainfile) {
			if (unlink(chainfile) == -1 && errno != ENOENT) {
				warn("%s/%s", certdir, chainfile);
				warn("%s", chainfile);
				goto out;
			} else
				dodbg("%s/%s: unlinked", certdir, chainfile);
				dodbg("%s: unlinked", chainfile);
		}

		if (fullchainfile) {
			if (unlink(fullchainfile) == -1 && errno != ENOENT) {
				warn("%s/%s", certdir, fullchainfile);
				warn("%s", fullchainfile);
				goto out;
			} else
				dodbg("%s/%s: unlinked", certdir,
				    fullchainfile);
				dodbg("%s: unlinked", fullchainfile);
		}

		rc = 2;


@@ 168,7 163,7 @@ fileproc(int certsock, const char *certdir, const char *certfile, const char
		if (!serialise(chainfile, ch, chsz, NULL, 0))
			goto out;

		dodbg("%s/%s: created", certdir, chainfile);
		dodbg("%s: created", chainfile);
	}

	/*


@@ 185,7 180,7 @@ fileproc(int certsock, const char *certdir, const char *certfile, const char
		if (!serialise(certfile, csr, csz, NULL, 0))
			goto out;

		dodbg("%s/%s: created", certdir, certfile);
		dodbg("%s: created", certfile);
	}

	/*


@@ 199,7 194,7 @@ fileproc(int certsock, const char *certdir, const char *certfile, const char
		    chsz))
			goto out;

		dodbg("%s/%s: created", certdir, fullchainfile);
		dodbg("%s: created", fullchainfile);
	}

	rc = 2;

M http.c => http.c +22 -13
@@ 1,4 1,4 @@
/*	$Id: http.c,v 1.25 2019/03/04 10:59:04 florian Exp $ */
/*	$Id: http.c,v 1.27 2019/06/28 13:32:46 deraadt Exp $ */
/*
 * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>
 *


@@ 72,7 72,7 @@ dosysread(char *buf, size_t sz, const struct http *http)
	ssize_t	 rc;

	rc = read(http->fd, buf, sz);
	if (rc < 0)
	if (rc == -1)
		warn("%s: read", http->src.ip);
	return rc;
}


@@ 83,7 83,7 @@ dosyswrite(const void *buf, size_t sz, const struct http *http)
	ssize_t	 rc;

	rc = write(http->fd, buf, sz);
	if (rc < 0)
	if (rc == -1)
		warn("%s: write", http->src.ip);
	return rc;
}


@@ 97,7 97,7 @@ dotlsread(char *buf, size_t sz, const struct http *http)
		rc = tls_read(http->ctx, buf, sz);
	} while (rc == TLS_WANT_POLLIN || rc == TLS_WANT_POLLOUT);

	if (rc < 0)
	if (rc == -1)
		warnx("%s: tls_read: %s", http->src.ip,
		    tls_error(http->ctx));
	return rc;


@@ 112,7 112,7 @@ dotlswrite(const void *buf, size_t sz, const struct http *http)
		rc = tls_write(http->ctx, buf, sz);
	} while (rc == TLS_WANT_POLLIN || rc == TLS_WANT_POLLOUT);

	if (rc < 0)
	if (rc == -1)
		warnx("%s: tls_write: %s", http->src.ip,
		    tls_error(http->ctx));
	return rc;


@@ 333,26 333,35 @@ err:
}

struct httpxfer *
http_open(const struct http *http, const void *p, size_t psz)
http_open(const struct http *http, int headreq, const void *p, size_t psz)
{
	char		*req;
	int		 c;
	struct httpxfer	*trans;

	if (p == NULL) {
		c = asprintf(&req,
		    "GET %s HTTP/1.0\r\n"
		    "Host: %s\r\n"
		    "\r\n",
		    http->path, http->host);
		if (headreq)
			c = asprintf(&req,
			    "HEAD %s HTTP/1.0\r\n"
			    "Host: %s\r\n"
			    "\r\n",
			    http->path, http->host);
		else
			c = asprintf(&req,
			    "GET %s HTTP/1.0\r\n"
			    "Host: %s\r\n"
			    "\r\n",
			    http->path, http->host);
	} else {
		c = asprintf(&req,
		    "POST %s HTTP/1.0\r\n"
		    "Host: %s\r\n"
		    "Content-Length: %zu\r\n"
		    "Content-Type: application/jose+json\r\n"
		    "\r\n",
		    http->path, http->host, psz);
	}

	if (c == -1) {
		warn("asprintf");
		return NULL;


@@ 676,7 685,7 @@ http_get_free(struct httpget *g)

struct httpget *
http_get(const struct source *addrs, size_t addrsz, const char *domain,
    short port, const char *path, const void *post, size_t postsz)
    short port, const char *path, int headreq, const void *post, size_t postsz)
{
	struct http	*h;
	struct httpxfer	*x;


@@ 690,7 699,7 @@ http_get(const struct source *addrs, size_t addrsz, const char *domain,
	if (h == NULL)
		return NULL;

	if ((x = http_open(h, post, postsz)) == NULL) {
	if ((x = http_open(h, headreq, post, postsz)) == NULL) {
		http_free(h);
		return NULL;
	} else if ((headr = http_head_read(h, x, &headrsz)) == NULL) {

M http.h => http.h +3 -3
@@ 1,4 1,4 @@
/*	$Id: http.h,v 1.7 2018/11/06 20:40:49 jsing Exp $ */
/*	$Id: http.h,v 1.8 2019/06/07 08:07:52 florian Exp $ */
/*
 * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>
 *


@@ 65,7 65,7 @@ int		 http_init(void);

/* Convenience functions. */
struct httpget	*http_get(const struct source *, size_t,
			const char *, short, const char *,
			const char *, short, const char *, int,
			const void *, size_t);
void		 http_get_free(struct httpget *);



@@ 73,7 73,7 @@ void		 http_get_free(struct httpget *);
struct http	*http_alloc(const struct source *, size_t,
			const char *, short, const char *);
void		 http_free(struct http *);
struct httpxfer	*http_open(const struct http *, const void *, size_t);
struct httpxfer	*http_open(const struct http *, int, const void *, size_t);
void		 http_close(struct httpxfer *);
void		 http_disconnect(struct http *);


M json.c => json.c +237 -64
@@ 1,4 1,4 @@
/*	$Id: json.c,v 1.11 2019/01/31 15:55:48 benno Exp $ */
/*	$Id: json.c,v 1.14 2019/06/18 18:50:07 florian Exp $ */
/*
 * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>
 *


@@ 230,6 230,15 @@ json_getarrayobj(struct jsmnn *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.


@@ 256,6 265,7 @@ json_getarray(struct jsmnn *n, const char *name)
	return n->d.obj[i].rhs;
}

#ifdef notyet
/*
 * Extract subtree from the returned JSON object, making sure that it's
 * the correct type.


@@ 282,6 292,7 @@ json_getobj(struct jsmnn *n, const char *name)
		return NULL;
	return n->d.obj[i].rhs;
}
#endif /* notyet */

/*
 * Extract a single string from the returned JSON object, making sure


@@ 347,6 358,8 @@ json_parse_response(struct jsmnn *n)
		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;



@@ 385,39 398,135 @@ json_parse_challenge(struct jsmnn *n, struct chng *p)
		free(type);
		if (rc)
			continue;
		p->uri = json_getstr(obj, "uri");
		p->uri = json_getstr(obj, "url");
		p->token = json_getstr(obj, "token");
		p->status = json_parse_response(obj);
		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;
}

/*
 * Extract the CA paths from the JSON response object.
 * Return zero on failure, non-zero on success.
 * 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_capaths(struct jsmnn *n, struct capaths *p)
json_parse_order(struct jsmnn *n, struct order *order)
{
	struct jsmnn	*meta;
	struct jsmnn	*array;
	size_t		 i;
	char		*finalize, *str;

	order->status = json_parse_order_status(n);

	if (n == NULL)
		return 0;

	meta = json_getobj(n, "meta");
	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 ((order->authsz = array->fields) > 0) {
		order->auths = calloc(sizeof(*order->auths), order->authsz);
		if (order->auths == NULL) {
			warn("malloc");
			goto err;
		}
	}

	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;
}

	if (meta == NULL)
/*
 * 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->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");
	p->agreement = json_getstr(meta, "terms-of-service");
	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->newauthz != NULL && p->newcert != NULL &&
	    p->newreg != NULL && p->revokecert != NULL && p->agreement != NULL;
	return p->newaccount != NULL && p->newnonce != NULL &&
	    p->neworder != NULL && p->revokecert != NULL;
}

/*


@@ 427,11 536,10 @@ void
json_free_capaths(struct capaths *p)
{

	free(p->newauthz);
	free(p->newcert);
	free(p->newreg);
	free(p->newaccount);
	free(p->newnonce);
	free(p->neworder);
	free(p->revokecert);
	free(p->agreement);
	memset(p, 0, sizeof(struct capaths));
}



@@ 444,27 552,31 @@ json_parse(const char *buf, size_t sz)
{
	struct jsmnn	*n;
	jsmn_parser	 p;
	jsmntok_t	*tok;
	jsmntok_t	*tok, *ntok;
	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 (tok == NULL) {
	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;
		free(tok);
		goto again;
	} else if (r < 0) {
		warnx("jsmn_parse: %d", r);


@@ 480,19 592,18 @@ again:
}

/*
 * Format the "new-reg" resource request.
 * Format the "newAccount" resource request to check if the account exist.
 */
char *
json_fmt_newreg(const char *license)
json_fmt_chkacc(void)
{
	int	 c;
	char	*p;

	c = asprintf(&p, "{"
	    "\"resource\": \"new-reg\", "
	    "\"agreement\": \"%s\""
	    "}",
	    license);
	    "\"termsOfServiceAgreed\": true, "
	    "\"onlyReturnExisting\": true"
	    "}");
	if (c == -1) {
		warn("asprintf");
		p = NULL;


@@ 501,20 612,17 @@ json_fmt_newreg(const char *license)
}

/*
 * Format the "new-authz" resource request.
 * Format the "newAccount" resource request.
 */
char *
json_fmt_newauthz(const char *domain)
json_fmt_newacc(void)
{
	int	 c;
	char	*p;

	c = asprintf(&p, "{"
	    "\"resource\": \"new-authz\", "
	    "\"identifier\": "
	    "{\"type\": \"dns\", \"value\": \"%s\"}"
	    "}",
	    domain);
	    "\"termsOfServiceAgreed\": true"
	    "}");
	if (c == -1) {
		warn("asprintf");
		p = NULL;


@@ 523,28 631,45 @@ json_fmt_newauthz(const char *domain)
}

/*
 * Format the "challenge" resource request.
 * Format the "newOrder" resource request
 */
char *
json_fmt_challenge(const char *token, const char *thumb)
json_fmt_neworder(const char *const *alts, size_t altsz)
{
	size_t	 i;
	int	 c;
	char	*p;

	c = asprintf(&p, "{"
	    "\"resource\": \"challenge\", "
	    "\"keyAuthorization\": \"%s.%s\""
	    "}",
	    token, thumb);
	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 "new-cert" resource request.
 * Format the revoke resource request.
 */
char *
json_fmt_revokecert(const char *cert)


@@ 553,7 678,6 @@ json_fmt_revokecert(const char *cert)
	char	*p;

	c = asprintf(&p, "{"
	    "\"resource\": \"revoke-cert\", "
	    "\"certificate\": \"%s\""
	    "}",
	    cert);


@@ 574,7 698,6 @@ json_fmt_newcert(const char *cert)
	char	*p;

	c = asprintf(&p, "{"
	    "\"resource\": \"new-cert\", "
	    "\"csr\": \"%s\""
	    "}",
	    cert);


@@ 586,10 709,11 @@ json_fmt_newcert(const char *cert)
}

/*
 * Header component of json_fmt_signed().
 * Protected component of json_fmt_signed().
 */
char *
json_fmt_header_rsa(const char *exp, const char *mod)
json_fmt_protected_rsa(const char *exp, const char *mod, const char *nce,
    const char *url)
{
	int	 c;
	char	*p;


@@ 597,9 721,11 @@ json_fmt_header_rsa(const char *exp, const char *mod)
	c = asprintf(&p, "{"
	    "\"alg\": \"RS256\", "
	    "\"jwk\": "
	    "{\"e\": \"%s\", \"kty\": \"RSA\", \"n\": \"%s\"}"
	    "{\"e\": \"%s\", \"kty\": \"RSA\", \"n\": \"%s\"}, "
	    "\"nonce\": \"%s\", "
	    "\"url\": \"%s\""
	    "}",
	    exp, mod);
	    exp, mod, nce, url);
	if (c == -1) {
		warn("asprintf");
		p = NULL;


@@ 611,18 737,43 @@ json_fmt_header_rsa(const char *exp, const char *mod)
 * Protected component of json_fmt_signed().
 */
char *
json_fmt_protected_rsa(const char *exp, const char *mod, const char *nce)
json_fmt_protected_ec(const char *x, const char *y, const char *nce,
    const char *url)
{
	int	 c;
	char	*p;

	c = asprintf(&p, "{"
	    "\"alg\": \"RS256\", "
	    "\"alg\": \"ES384\", "
	    "\"jwk\": "
	    "{\"e\": \"%s\", \"kty\": \"RSA\", \"n\": \"%s\"}, "
	    "\"nonce\": \"%s\""
	    "{\"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\""
	    "}",
	    exp, mod, nce);
	    alg, kid, nce, url);
	if (c == -1) {
		warn("asprintf");
		p = NULL;


@@ 634,19 785,17 @@ json_fmt_protected_rsa(const char *exp, const char *mod, const char *nce)
 * Signed message contents for the CA server.
 */
char *
json_fmt_signed(const char *header, const char *protected,
    const char *payload, const char *digest)
json_fmt_signed(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);
	    protected, payload, digest);
	if (c == -1) {
		warn("asprintf");
		p = NULL;


@@ 676,3 825,27 @@ json_fmt_thumb_rsa(const char *exp, const char *mod)
	}
	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;
}

R rsa.c => key.c +63 -5
@@ 1,5 1,6 @@
/*	$Id: rsa.c,v 1.7 2018/07/28 15:25:23 tb Exp $ */
/*	$Id: key.c,v 1.2 2019/06/17 15:41:59 florian Exp $ */
/*
 * Copyright (c) 2019 Renaud Allard <renaud@allard.it>
 * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>
 *
 * Permission to use, copy, modify, and distribute this software for any


@@ 22,13 23,17 @@
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/rsa.h>
#include <openssl/ecdsa.h>
#include <openssl/ec.h>
#include <openssl/obj_mac.h>

#include "rsa.h"
#include "key.h"

/*
 * Default number of bits when creating a new key.
 * Default number of bits when creating a new RSA key.
 */
#define	KBITS 4096
#define ECCTYPE NID_secp384r1

/*
 * Create an RSA key with the default KBITS number of bits.


@@ 61,6 66,7 @@ rsa_key_create(FILE *f, const char *fname)
		goto out;

	warnx("%s: PEM_write_PrivateKey", fname);

err:
	EVP_PKEY_free(pkey);
	pkey = NULL;


@@ 69,9 75,60 @@ out:
	return pkey;
}

EVP_PKEY *
ec_key_create(FILE *f, const char *fname)
{
	EC_KEY		*eckey = NULL;
	EVP_PKEY	*pkey = NULL;

	if ((eckey = EC_KEY_new_by_curve_name(ECCTYPE)) == NULL ) {
		warnx("EC_KEY_new_by_curve_name");
		goto err;
	}

	if (!EC_KEY_generate_key(eckey)) {
		warnx("EC_KEY_generate_key");
		goto err;
	}
	
	/* set OPENSSL_EC_NAMED_CURVE to be able to load the key */

	EC_KEY_set_asn1_flag(eckey, OPENSSL_EC_NAMED_CURVE);

	/* Serialise the key to the disc in EC format */

	if (!PEM_write_ECPrivateKey(f, eckey, NULL, NULL, 0, NULL, NULL)) {
		warnx("PEM_write_ECPrivateKey");
		goto err;
	}

	/* Convert the EC key into a PKEY structure */

	if ((pkey=EVP_PKEY_new()) == NULL) {
		warnx("EVP_PKEY_new");
		goto err;
	}
	if (!EVP_PKEY_set1_EC_KEY(pkey, eckey)) {
		warnx("EVP_PKEY_assign_EC_KEY");
		goto err;
	}

	warnx("%s: PEM_write_ECPrivateKey", fname);

	goto out;

err:
	EC_KEY_free(eckey);
	EVP_PKEY_free(pkey);
	pkey = NULL;
out:
	return pkey;
}



EVP_PKEY *
rsa_key_load(FILE *f, const char *fname)
key_load(FILE *f, const char *fname)
{
	EVP_PKEY	*pkey;



@@ 79,7 136,8 @@ rsa_key_load(FILE *f, const char *fname)
	if (pkey == NULL) {
		warnx("%s: PEM_read_PrivateKey", fname);
		return NULL;
	} else if (EVP_PKEY_type(pkey->type) == EVP_PKEY_RSA)
	} else if (EVP_PKEY_type(pkey->type) == EVP_PKEY_RSA ||
		   EVP_PKEY_type(pkey->type) == EVP_PKEY_EC )
		return pkey;

	warnx("%s: unsupported key type", fname);

R rsa.h => key.h +7 -5
@@ 1,5 1,6 @@
/*	$Id: rsa.h,v 1.1 2016/08/31 22:01:42 florian Exp $ */
/*	$Id: key.h,v 1.1 2019/06/12 11:09:25 gilles Exp $ */
/*
 * Copyright (c) 2019 Renaud Allard <renaud@allard.it>
 * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>
 *
 * Permission to use, copy, modify, and distribute this software for any


@@ 14,10 15,11 @@
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */
#ifndef RSA_H
#define RSA_H
#ifndef KEY_H
#define KEY_H

EVP_PKEY	*rsa_key_create(FILE *, const char *);
EVP_PKEY	*rsa_key_load(FILE *, const char *);
EVP_PKEY	*ec_key_create(FILE *, const char *);
EVP_PKEY	*key_load(FILE *, const char *);

#endif /* ! RSA_H */
#endif /* ! KEY_H */

M keyproc.c => keyproc.c +24 -11
@@ 1,4 1,4 @@
/*	$Id: keyproc.c,v 1.11 2018/07/29 20:22:02 tb Exp $ */
/*	$Id: keyproc.c,v 1.15 2019/06/15 16:16:31 florian Exp $ */
/*
 * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>
 *


@@ 30,7 30,7 @@
#include <openssl/x509v3.h>

#include "extern.h"
#include "rsa.h"
#include "key.h"

/*
 * This was lifted more or less directly from demos/x509/mkreq.c of the


@@ 74,8 74,8 @@ add_ext(STACK_OF(X509_EXTENSION) *sk, int nid, const char *value)
 * jail and, on success, ship it to "netsock" as an X509 request.
 */
int
keyproc(int netsock, const char *keyfile,
    const char **alts, size_t altsz, int newkey)
keyproc(int netsock, const char *keyfile, const char **alts, size_t altsz,
    enum keytype keytype)
{
	char		*der64 = NULL, *der = NULL, *dercp;
	char		*sans = NULL, *san = NULL;


@@ 85,7 85,7 @@ keyproc(int netsock, const char *keyfile,
	EVP_PKEY	*pkey = NULL;
	X509_REQ	*x = NULL;
	X509_NAME	*name = NULL;
	int		 len, rc = 0, cc, nid;
	int		 len, rc = 0, cc, nid, newkey = 0;
	mode_t		 prev;
	STACK_OF(X509_EXTENSION) *exts = NULL;



@@ 96,7 96,10 @@ keyproc(int netsock, const char *keyfile,
	 */

	prev = umask((S_IWUSR | S_IXUSR) | S_IRWXG | S_IRWXO);
	f = fopen(keyfile, newkey ? "wx" : "r");
	if ((f = fopen(keyfile, "r")) == NULL && errno == ENOENT) {
		f = fopen(keyfile, "wx");
		newkey = 1;
	}
	umask(prev);

	if (f == NULL) {


@@ 114,13 117,23 @@ keyproc(int netsock, const char *keyfile,
	}

	if (newkey) {
		if ((pkey = rsa_key_create(f, keyfile)) == NULL)
			goto out;
		dodbg("%s: generated RSA domain key", keyfile);
		switch (keytype) {
		case KT_ECDSA:
			if ((pkey = ec_key_create(f, keyfile)) == NULL)
				goto out;
			dodbg("%s: generated ECDSA domain key", keyfile);
			break;
		case KT_RSA:
			if ((pkey = rsa_key_create(f, keyfile)) == NULL)
				goto out;
			dodbg("%s: generated RSA domain key", keyfile);
			break;
		}
	} else {
		if ((pkey = rsa_key_load(f, keyfile)) == NULL)
		if ((pkey = key_load(f, keyfile)) == NULL)
			goto out;
		doddbg("%s: loaded RSA domain key", keyfile);
		/* XXX check if domain key type equals configured key type */
		doddbg("%s: loaded domain key", keyfile);
	}

	fclose(f);

M main.c => main.c +30 -63
@@ 1,4 1,4 @@
/*	$Id: main.c,v 1.45 2019/03/09 18:07:40 benno Exp $ */
/*	$Id: main.c,v 1.52 2019/06/17 12:42:52 florian Exp $ */
/*
 * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>
 *


@@ 36,9 36,7 @@ int
main(int argc, char *argv[])
{
	const char	 **alts = NULL;
	char		 *certdir = NULL, *certfile = NULL;
	char		 *chainfile = NULL, *fullchainfile = NULL;
	char		 *acctkey = NULL;
	char		 *certdir = NULL;
	char		 *chngdir = NULL, *auth = NULL;
	char		 *conffile = CONF_FILE;
	char		 *tmps, *tmpsd;


@@ 57,14 55,8 @@ main(int argc, char *argv[])
	struct domain_c		*domain = NULL;
	struct altname_c	*ac;

	while ((c = getopt(argc, argv, "ADFnrvf:")) != -1)
	while ((c = getopt(argc, argv, "Fnrvf:")) != -1)
		switch (c) {
		case 'A':
			popts |= ACME_OPT_NEWACCT;
			break;
		case 'D':
			popts |= ACME_OPT_NEWDKEY;
			break;
		case 'F':
			force = 1;
			break;


@@ 104,7 96,10 @@ main(int argc, char *argv[])
	argc--;
	argv++;

	/* the parser enforces that at least cert or fullchain is set */
	/*
	 * The parser enforces that at least cert or fullchain is set.
	 * XXX Test if cert, chain and fullchain have the same dirname?
	 */
	tmps = domain->cert ? domain->cert : domain->fullchain;
	if ((tmps = strdup(tmps)) == NULL)
		err(EXIT_FAILURE, "strdup");


@@ 115,31 110,21 @@ main(int argc, char *argv[])
	free(tmps);
	tmps = tmpsd = NULL;

	if (domain->cert != NULL) {
		if ((tmps = strdup(domain->cert)) == NULL)
			err(EXIT_FAILURE, "strdup");
		if ((certfile = basename(tmps)) == NULL)
			err(EXIT_FAILURE, "basename");
		if ((certfile = strdup(certfile)) == NULL)
			err(EXIT_FAILURE, "strdup");
	}

	if (domain->chain != NULL) {
		if ((tmps = strdup(domain->chain)) == NULL)
			err(EXIT_FAILURE, "strdup");
		if ((chainfile = basename(tmps)) == NULL)
			err(EXIT_FAILURE, "basename");
		if ((chainfile = strdup(chainfile)) == NULL)
			err(EXIT_FAILURE, "strdup");
	/* chain or fullchain can be relative paths according */
	if (domain->chain && domain->chain[0] != '/') {
		if (asprintf(&tmps, "%s/%s", certdir, domain->chain) == -1)
			err(EXIT_FAILURE, "asprintf");
		free(domain->chain);
		domain->chain = tmps;
		tmps = NULL;
	}

	if (domain->fullchain != NULL) {
		if ((tmps = strdup(domain->fullchain)) == NULL)
			err(EXIT_FAILURE, "strdup");
		if ((fullchainfile = basename(tmps)) == NULL)
			err(EXIT_FAILURE, "basename");
		if ((fullchainfile = strdup(fullchainfile)) == NULL)
			err(EXIT_FAILURE, "strdup");
	if (domain->fullchain && domain->fullchain[0] != '/') {
		if (asprintf(&tmps, "%s/%s", certdir, domain->fullchain) == -1)
			err(EXIT_FAILURE, "asprintf");
		free(domain->fullchain);
		domain->fullchain = tmps;
		tmps = NULL;
	}

	if ((auth = domain->auth) == NULL) {


@@ 153,8 138,6 @@ main(int argc, char *argv[])
			errx(EXIT_FAILURE, "authority %s not found", auth);
	}

	acctkey = authority->account;

	if ((chngdir = domain->challengedir) == NULL)
		if ((chngdir = strdup(WWW_DIR)) == NULL)
			err(EXIT_FAILURE, "strdup");


@@ 173,28 156,11 @@ main(int argc, char *argv[])
		ne++;
	}

	if (!(popts & ACME_OPT_NEWDKEY) && access(domain->key, R_OK) == -1) {
		warnx("%s: domain key file must exist", domain->key);
		ne++;
	} else if ((popts & ACME_OPT_NEWDKEY) && access(domain->key, R_OK)
	    != -1) {
		dodbg("%s: domain key exists (not creating)", domain->key);
		popts &= ~ACME_OPT_NEWDKEY;
	}

	if (access(chngdir, R_OK) == -1) {
		warnx("%s: challenge directory must exist", chngdir);
		ne++;
	}

	if (!(popts & ACME_OPT_NEWACCT) && access(acctkey, R_OK) == -1) {
		warnx("%s: account key file must exist", acctkey);
		ne++;
	} else if ((popts & ACME_OPT_NEWACCT) && access(acctkey, R_OK) != -1) {
		dodbg("%s: account key exists (not creating)", acctkey);
		popts &= ~ACME_OPT_NEWACCT;
	}

	if (ne > 0)
		return EXIT_FAILURE;



@@ 249,7 215,7 @@ main(int argc, char *argv[])
		c = netproc(key_fds[1], acct_fds[1],
		    chng_fds[1], cert_fds[1],
		    dns_fds[1], rvk_fds[1],
		    (popts & ACME_OPT_NEWACCT), revocate, authority,
		    revocate, authority,
		    (const char *const *)alts, altsz);
		exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
	}


@@ 276,7 242,8 @@ main(int argc, char *argv[])
		close(file_fds[0]);
		close(file_fds[1]);
		c = keyproc(key_fds[0], domain->key,
		    (const char **)alts, altsz, (popts & ACME_OPT_NEWDKEY));
		    (const char **)alts, altsz,
		    domain->keytype);
		exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
	}



@@ 295,7 262,8 @@ main(int argc, char *argv[])
		close(chng_fds[0]);
		close(file_fds[0]);
		close(file_fds[1]);
		c = acctproc(acct_fds[0], acctkey, (popts & ACME_OPT_NEWACCT));
		c = acctproc(acct_fds[0], authority->account,
		    authority->keytype);
		exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
	}



@@ 345,8 313,8 @@ main(int argc, char *argv[])
		proccomp = COMP_FILE;
		close(dns_fds[0]);
		close(rvk_fds[0]);
		c = fileproc(file_fds[1], certdir, certfile, chainfile,
		    fullchainfile);
		c = fileproc(file_fds[1], certdir, domain->cert, domain->chain,
		    domain->fullchain);
		/*
		 * This is different from the other processes in that it
		 * can return 2 if the certificates were updated.


@@ 377,9 345,8 @@ main(int argc, char *argv[])

	if (pids[COMP_REVOKE] == 0) {
		proccomp = COMP_REVOKE;
		c = revokeproc(rvk_fds[0], certdir,
		    certfile != NULL ? certfile : fullchainfile,
		    force, revocate,
		c = revokeproc(rvk_fds[0], domain->cert != NULL ? domain->cert :
		    domain->fullchain, force, revocate,
		    (const char *const *)alts, altsz);
		exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
	}


@@ 408,6 375,6 @@ main(int argc, char *argv[])
	return rc != COMP__MAX ? EXIT_FAILURE : (c == 2 ? EXIT_SUCCESS : 2);
usage:
	fprintf(stderr,
	    "usage: acme-client [-ADFnrv] [-f configfile] domain\n");
	    "usage: acme-client [-Fnrv] [-f configfile] domain\n");
	return EXIT_FAILURE;
}

M netproc.c => netproc.c +279 -203
@@ 1,4 1,4 @@
/*	$Id: netproc.c,v 1.22 2019/02/01 10:16:04 benno Exp $ */
/*	$Id: netproc.c,v 1.24 2019/06/08 10:38:03 florian Exp $ */
/*
 * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>
 *


@@ 43,10 43,11 @@ struct	buf {
 * Used for CURL communications.
 */
struct	conn {
	const char	  *na; /* nonce authority */
	const char	  *newnonce; /* nonce authority */
	char		  *kid; /* kid when account exists */
	int		   fd; /* acctproc handle */
	int		   dfd; /* dnsproc handle */
	struct buf	   buf; /* transfer buffer */
	struct buf	   buf; /* http body buffer */
};

/*


@@ 198,7 199,7 @@ again:
	}
	srcsz = ssz;

	g = http_get(src, srcsz, host, port, path, NULL, 0);
	g = http_get(src, srcsz, host, port, path, 0, NULL, 0);
	free(host);
	free(path);
	if (g == NULL)


@@ 223,7 224,6 @@ again:
			return -1;
		}

		dodbg("Location: %s", st->val);
		host = url2host(st->val, &port, &path);
		http_get_free(g);
		if (host == NULL)


@@ 254,7 254,7 @@ again:
 * Return <0 on failure on the HTTP error code otherwise.
 */
static long
sreq(struct conn *c, const char *addr, const char *req)
sreq(struct conn *c, const char *addr, int kid, const char *req, char **loc)
{
	struct httpget	*g;
	struct source	 src[MAX_SERVERS_DNS];


@@ 264,7 264,7 @@ sreq(struct conn *c, const char *addr, const char *req)
	ssize_t		 ssz;
	long		 code;

	if ((host = url2host(c->na, &port, &path)) == NULL)
	if ((host = url2host(c->newnonce, &port, &path)) == NULL)
		return -1;

	if ((ssz = urlresolve(c->dfd, host, src)) < 0) {


@@ 273,7 273,7 @@ sreq(struct conn *c, const char *addr, const char *req)
		return -1;
	}

	g = http_get(src, (size_t)ssz, host, port, path, NULL, 0);
	g = http_get(src, (size_t)ssz, host, port, path, 1, NULL, 0);
	free(host);
	free(path);
	if (g == NULL)


@@ 281,7 281,7 @@ sreq(struct conn *c, const char *addr, const char *req)

	h = http_head_get("Replay-Nonce", g->head, g->headsz);
	if (h == NULL) {
		warnx("%s: no replay nonce", c->na);
		warnx("%s: no replay nonce", c->newnonce);
		http_get_free(g);
		return -1;
	} else if ((nonce = strdup(h->val)) == NULL) {


@@ 292,10 292,10 @@ sreq(struct conn *c, const char *addr, const char *req)
	http_get_free(g);

	/*
	 * Send the nonce and request payload to the acctproc.
	 * Send the url, nonce and request payload to the acctproc.
	 * This will create the proper JSON object we need.
	 */
	if (writeop(c->fd, COMM_ACCT, ACCT_SIGN) <= 0) {
	if (writeop(c->fd, COMM_ACCT, kid ? ACCT_KID_SIGN : ACCT_SIGN) <= 0) {
		free(nonce);
		return -1;
	} else if (writestr(c->fd, COMM_PAY, req) <= 0) {


@@ 304,9 304,15 @@ sreq(struct conn *c, const char *addr, const char *req)
	} else if (writestr(c->fd, COMM_NONCE, nonce) <= 0) {
		free(nonce);
		return -1;
	} else if (writestr(c->fd, COMM_URL, addr) <= 0) {
		free(nonce);
		return -1;
	}
	free(nonce);

	if (kid && writestr(c->fd, COMM_KID, c->kid) <= 0)
		return -1;

	/* Now read back the signed payload. */
	if ((reqsn = readstr(c->fd, COMM_REQ)) == NULL)
		return -1;


@@ 322,7 328,8 @@ sreq(struct conn *c, const char *addr, const char *req)
		return -1;
	}

	g = http_get(src, (size_t)ssz, host, port, path, reqsn, strlen(reqsn));
	g = http_get(src, (size_t)ssz, host, port, path, 0, reqsn,
	    strlen(reqsn));

	free(host);
	free(path);


@@ 341,6 348,16 @@ sreq(struct conn *c, const char *addr, const char *req)
		code = -1;
	} else
		memcpy(c->buf.buf, g->bodypart, c->buf.sz);

	if (loc != NULL) {
		free(*loc);
		*loc = NULL;
		h = http_head_get("Location", g->head, g->headsz);
		/* error checking done by caller */
		if (h != NULL)
			*loc = strdup(h->val);
	}

	http_get_free(g);
	return code;
}


@@ 351,22 368,20 @@ sreq(struct conn *c, const char *addr, const char *req)
 * Returns non-zero on success.
 */
static int
donewreg(struct conn *c, const struct capaths *p)
donewacc(struct conn *c, const struct capaths *p)
{
	int		 rc = 0;
	char		*req;
	long		 lc;

	dodbg("%s: new-reg", p->newreg);

	if ((req = json_fmt_newreg(p->agreement)) == NULL)
		warnx("json_fmt_newreg");
	else if ((lc = sreq(c, p->newreg, req)) < 0)
		warnx("%s: bad comm", p->newreg);
	if ((req = json_fmt_newacc()) == NULL)
		warnx("json_fmt_newacc");
	else if ((lc = sreq(c, p->newaccount, 0, req, &c->kid)) < 0)
		warnx("%s: bad comm", p->newaccount);
	else if (lc != 200 && lc != 201)
		warnx("%s: bad HTTP: %ld", p->newreg, lc);
		warnx("%s: bad HTTP: %ld", p->newaccount, lc);
	else if (c->buf.buf == NULL || c->buf.sz == 0)
		warnx("%s: empty response", p->newreg);
		warnx("%s: empty response", p->newaccount);
	else
		rc = 1;



@@ 377,153 392,176 @@ donewreg(struct conn *c, const struct capaths *p)
}

/*
 * Request a challenge for the given domain name.
 * This must be called for each name "alt".
 * On non-zero exit, fills in "chng" with the challenge.
 * Check if our account already exists, if not create it.
 * Populates conn->kid.
 * Returns non-zero on success.
 */
static int
dochngreq(struct conn *c, const char *alt, struct chng *chng,
    const struct capaths *p)
dochkacc(struct conn *c, const struct capaths *p)
{
	int		 rc = 0;
	char		*req;
	long		 lc;
	struct jsmnn	*j = NULL;

	dodbg("%s: req-auth: %s", p->newauthz, alt);

	if ((req = json_fmt_newauthz(alt)) == NULL)
		warnx("json_fmt_newauthz");
	else if ((lc = sreq(c, p->newauthz, req)) < 0)
		warnx("%s: bad comm", p->newauthz);
	else if (lc != 200 && lc != 201)
		warnx("%s: bad HTTP: %ld", p->newauthz, lc);
	else if ((j = json_parse(c->buf.buf, c->buf.sz)) == NULL)
		warnx("%s: bad JSON object", p->newauthz);
	else if (!json_parse_challenge(j, chng))
		warnx("%s: bad challenge", p->newauthz);
	if ((req = json_fmt_chkacc()) == NULL)
		warnx("json_fmt_chkacc");
	else if ((lc = sreq(c, p->newaccount, 0, req, &c->kid)) < 0)
		warnx("%s: bad comm", p->newaccount);
	else if (lc != 200 && lc != 400)
		warnx("%s: bad HTTP: %ld", p->newaccount, lc);
	else if (c->buf.buf == NULL || c->buf.sz == 0)
		warnx("%s: empty response", p->newaccount);
	else if (lc == 400)
		rc = donewacc(c, p);
	else
		rc = 1;

	if (c->kid == NULL)
		rc = 0;

	if (rc == 0 || verbose > 1)
		buf_dump(&c->buf);
	json_free(j);
	free(req);
	return rc;
}

/*
 * Tell the CA that a challenge response is in place.
 * Submit a new order for a certificate.
 */
static int
dochngresp(struct conn *c, const struct chng *chng, const char *th)
doneworder(struct conn *c, const char *const *alts, size_t altsz,
    struct order *order, const struct capaths *p)
{
	int	 rc = 0;
	long	 lc;
	char	*req;

	dodbg("%s: challenge", chng->uri);
	struct jsmnn	*j = NULL;
	int		 rc = 0;
	char		*req;
	long		 lc;

	if ((req = json_fmt_challenge(chng->token, th)) == NULL)
		warnx("json_fmt_challenge");
	else if ((lc = sreq(c, chng->uri, req)) < 0)
		warnx("%s: bad comm", chng->uri);
	else if (lc != 200 && lc != 201 && lc != 202)
		warnx("%s: bad HTTP: %ld", chng->uri, lc);
	if ((req = json_fmt_neworder(alts, altsz)) == NULL)
		warnx("json_fmt_neworder");
	else if ((lc = sreq(c, p->neworder, 1, req, &order->uri)) < 0)
		warnx("%s: bad comm", p->neworder);
	else if (lc != 201)
		warnx("%s: bad HTTP: %ld", p->neworder, lc);
	else if ((j = json_parse(c->buf.buf, c->buf.sz)) == NULL)
		warnx("%s: bad JSON object", p->neworder);
	else if (!json_parse_order(j, order))
		warnx("%s: bad order", p->neworder);
	else if (order->status == ORDER_INVALID)
		warnx("%s: order invalid", p->neworder);
	else
		rc = 1;

	if (rc == 0 || verbose > 1)
		buf_dump(&c->buf);

	free(req);
	json_free(j);
	return rc;
}

/*
 * Check with the CA whether a challenge has been processed.
 * Note: we'll only do this a limited number of times, and pause for a
 * time between checks, but this happens in the caller.
 * Update order status
 */
static int
dochngcheck(struct conn *c, struct chng *chng)
doupdorder(struct conn *c, struct order *order)
{
	enum chngstatus	 cc;
	struct jsmnn	*j = NULL;
	int		 rc = 0;
	long		 lc;
	struct jsmnn	*j;

	dodbg("%s: status", chng->uri);
	if ((lc = sreq(c, order->uri, 1, "", NULL)) < 0)
		warnx("%s: bad comm", order->uri);
	else if (lc != 200)
		warnx("%s: bad HTTP: %ld", order->uri, lc);
	else if ((j = json_parse(c->buf.buf, c->buf.sz)) == NULL)
		warnx("%s: bad JSON object", order->uri);
	else if (!json_parse_upd_order(j, order))
		warnx("%s: bad order", order->uri);
	else
		rc = 1;

	if ((lc = nreq(c, chng->uri)) < 0) {
		warnx("%s: bad comm", chng->uri);
		return 0;
	} else if (lc != 200 && lc != 201 && lc != 202) {
		warnx("%s: bad HTTP: %ld", chng->uri, lc);
		buf_dump(&c->buf);
		return 0;
	} else if ((j = json_parse(c->buf.buf, c->buf.sz)) == NULL) {
		warnx("%s: bad JSON object", chng->uri);
		buf_dump(&c->buf);
		return 0;
	} else if ((cc = json_parse_response(j)) == -1) {
		warnx("%s: bad response", chng->uri);
	if (rc == 0 || verbose > 1)
		buf_dump(&c->buf);
		json_free(j);
		return 0;
	} else if (cc > 0)
		chng->status = CHNG_VALID;

	json_free(j);
	return 1;
	return rc;
}

/*
 * Request a challenge for the given domain name.
 * This must be called for each name "alt".
 * On non-zero exit, fills in "chng" with the challenge.
 */
static int
dorevoke(struct conn *c, const char *addr, const char *cert)
dochngreq(struct conn *c, const char *auth, struct chng *chng)
{
	char		*req;
	int		 rc = 0;
	long		 lc = 0;
	long		 lc;
	struct jsmnn	*j = NULL;

	dodbg("%s: revocation", addr);
	dodbg("%s: %s", __func__, auth);

	if ((req = json_fmt_revokecert(cert)) == NULL)
		warnx("json_fmt_revokecert");
	else if ((lc = sreq(c, addr, req)) < 0)
		warnx("%s: bad comm", addr);
	else if (lc != 200 && lc != 201 && lc != 409)
		warnx("%s: bad HTTP: %ld", addr, lc);
	if ((lc = sreq(c, auth, 1, "", NULL)) < 0)
		warnx("%s: bad comm", auth);
	else if (lc != 200)
		warnx("%s: bad HTTP: %ld", auth, lc);
	else if ((j = json_parse(c->buf.buf, c->buf.sz)) == NULL)
		warnx("%s: bad JSON object", auth);
	else if (!json_parse_challenge(j, chng))
		warnx("%s: bad challenge", auth);
	else
		rc = 1;

	if (lc == 409)
		warnx("%s: already revoked", addr);
	if (rc == 0 || verbose > 1)
		buf_dump(&c->buf);
	json_free(j);
	return rc;
}

/*
 * Tell the CA that a challenge response is in place.
 */
static int
dochngresp(struct conn *c, const struct chng *chng)
{
	int	 rc = 0;
	long	 lc;

	dodbg("%s: challenge", chng->uri);

	if ((lc = sreq(c, chng->uri, 1, "{}", NULL)) < 0)
		warnx("%s: bad comm", chng->uri);
	else if (lc != 200 && lc != 201 && lc != 202)
		warnx("%s: bad HTTP: %ld", chng->uri, lc);
	else
		rc = 1;

	if (rc == 0 || verbose > 1)
		buf_dump(&c->buf);
	free(req);
	return rc;
}

/*
 * Submit our certificate to the CA.
 * This, upon success, will return the signed CA.
 * Submit our csr to the CA.
 */
static int
docert(struct conn *c, const char *addr, const char *cert)
docert(struct conn *c, const char *uri, const char *csr)
{
	char	*req;
	int	 rc = 0;
	long	 lc;

	dodbg("%s: certificate", addr);
	dodbg("%s: certificate", uri);

	if ((req = json_fmt_newcert(cert)) == NULL)
	if ((req = json_fmt_newcert(csr)) == NULL)
		warnx("json_fmt_newcert");
	else if ((lc = sreq(c, addr, req)) < 0)
		warnx("%s: bad comm", addr);
	else if (lc != 200 && lc != 201)
		warnx("%s: bad HTTP: %ld", addr, lc);
	else if ((lc = sreq(c, uri, 1, req, NULL)) < 0)
		warnx("%s: bad comm", uri);
	else if (lc != 200)
		warnx("%s: bad HTTP: %ld", uri, lc);
	else if (c->buf.sz == 0 || c->buf.buf == NULL)
		warnx("%s: empty response", addr);
		warnx("%s: empty response", uri);
	else
		rc = 1;



@@ 534,54 572,84 @@ docert(struct conn *c, const char *addr, const char *cert)
}

/*
 * Look up directories from the certificate authority.
 * Get certificate from CA
 */
static int
dodirs(struct conn *c, const char *addr, struct capaths *paths)
dogetcert(struct conn *c, const char *uri)
{
	struct jsmnn	*j = NULL;
	long		 lc;
	int	 rc = 0;
	long	 lc;

	dodbg("%s: certificate", uri);

	if ((lc = sreq(c, uri, 1, "", NULL)) < 0)
		warnx("%s: bad comm", uri);
	else if (lc != 200)
		warnx("%s: bad HTTP: %ld", uri, lc);
	else if (c->buf.sz == 0 || c->buf.buf == NULL)
		warnx("%s: empty response", uri);
	else
		rc = 1;

	if (rc == 0 || verbose > 1)
		buf_dump(&c->buf);

	return rc;
}

static int
dorevoke(struct conn *c, const char *addr, const char *cert)
{
	char		*req;
	int		 rc = 0;
	long		 lc = 0;

	dodbg("%s: directories", addr);
	dodbg("%s: revocation", addr);

	if ((lc = nreq(c, addr)) < 0)
	if ((req = json_fmt_revokecert(cert)) == NULL)
		warnx("json_fmt_revokecert");
	else if ((lc = sreq(c, addr, 1, req, NULL)) < 0)
		warnx("%s: bad comm", addr);
	else if (lc != 200 && lc != 201)
	else if (lc != 200 && lc != 201 && lc != 409)
		warnx("%s: bad HTTP: %ld", addr, lc);
	else if ((j = json_parse(c->buf.buf, c->buf.sz)) == NULL)
		warnx("json_parse");
	else if (!json_parse_capaths(j, paths))
		warnx("%s: bad CA paths", addr);
	else
		rc = 1;

	if (lc == 409)
		warnx("%s: already revoked", addr);

	if (rc == 0 || verbose > 1)
		buf_dump(&c->buf);
	json_free(j);
	free(req);
	return rc;
}

/*
 * Request the full chain certificate.
 * Look up directories from the certificate authority.
 */
static int
dofullchain(struct conn *c, const char *addr)
dodirs(struct conn *c, const char *addr, struct capaths *paths)
{
	int	 rc = 0;
	long	 lc;
	struct jsmnn	*j = NULL;
	long		 lc;
	int		 rc = 0;

	dodbg("%s: full chain", addr);
	dodbg("%s: directories", addr);

	if ((lc = nreq(c, addr)) < 0)
		warnx("%s: bad comm", addr);
	else if (lc != 200 && lc != 201)
		warnx("%s: bad HTTP: %ld", addr, lc);
	else if ((j = json_parse(c->buf.buf, c->buf.sz)) == NULL)
		warnx("json_parse");
	else if (!json_parse_capaths(j, paths))
		warnx("%s: bad CA paths", addr);
	else
		rc = 1;

	if (rc == 0 || verbose > 1)
		buf_dump(&c->buf);
	json_free(j);
	return rc;
}



@@ 591,14 659,15 @@ dofullchain(struct conn *c, const char *addr)
 */
int
netproc(int kfd, int afd, int Cfd, int cfd, int dfd, int rfd,
    int newacct, int revocate, struct authority_c *authority,
    int revocate, struct authority_c *authority,
    const char *const *alts, size_t altsz)
{
	int		 rc = 0;
	size_t		 i, done = 0;
	size_t		 i;
	char		*cert = NULL, *thumb = NULL, *url = NULL;
	struct conn	 c;
	struct capaths	 paths;
	struct order	 order;
	struct chng	*chngs = NULL;
	long		 lval;



@@ 661,21 730,19 @@ netproc(int kfd, int afd, int Cfd, int cfd, int dfd, int rfd,
		goto out;
	}

	/* Allocate main state. */
	chngs = calloc(altsz, sizeof(struct chng));
	if (chngs == NULL) {
		warn("calloc");
		goto out;
	}

	c.dfd = dfd;
	c.fd = afd;
	c.na = authority->api;

	/*
	 * Look up the API urls of the ACME server.
	 */
	if (!dodirs(&c, c.na, &paths))
	if (!dodirs(&c, authority->api, &paths))
		goto out;

	c.newnonce = paths.newnonce;

	/* Check if our account already exists or create it. */
	if (!dochkacc(&c, &paths))
		goto out;

	/*


@@ 683,6 750,8 @@ netproc(int kfd, int afd, int Cfd, int cfd, int dfd, int rfd,
	 * the certificate (if it's found at all).
	 * Following that, submit the request to the CA then notify the
	 * certproc, which will in turn notify the fileproc.
	 * XXX currently we can only sign with the account key, the RFC
	 * also mentions signing with the privat key of the cert itself.
	 */
	if (revocate) {
		if ((cert = readstr(rfd, COMM_CSR)) == NULL)


@@ 694,109 763,115 @@ netproc(int kfd, int afd, int Cfd, int cfd, int dfd, int rfd,
		goto out;
	}

	/* If new, register with the CA server. */
	if (newacct && ! donewreg(&c, &paths))
	memset(&order, 0, sizeof(order));

	if (!doneworder(&c, alts, altsz, &order, &paths))
		goto out;

	/* Pre-authorise all domains with CA server. */
	for (i = 0; i < altsz; i++)
		if (!dochngreq(&c, alts[i], &chngs[i], &paths))
			goto out;
	chngs = calloc(order.authsz, sizeof(struct chng));
	if (chngs == NULL) {
		warn("calloc");
		goto out;
	}

	/*
	 * We now have our challenges.
	 * We need to ask the acctproc for the thumbprint.
	 * We'll combine this to the challenge to create our response,
	 * which will be orchestrated by the chngproc.
	 * Get thumbprint from acctproc. We will need it to construct
	 * a response to the challenge
	 */
	if (writeop(afd, COMM_ACCT, ACCT_THUMBPRINT) <= 0)
		goto out;
	else if ((thumb = readstr(afd, COMM_THUMB)) == NULL)
		goto out;

	/* We'll now ask chngproc to build the challenge. */
	for (i = 0; i < altsz; i++) {
		if (writeop(Cfd, COMM_CHNG_OP, CHNG_SYN) <= 0)
			goto out;
		else if (writestr(Cfd, COMM_THUMB, thumb) <= 0)
			goto out;
		else if (writestr(Cfd, COMM_TOK, chngs[i].token) <= 0)
			goto out;

		/* Read that the challenge has been made. */
		if (readop(Cfd, COMM_CHNG_ACK) != CHNG_ACK)
	while(order.status != ORDER_VALID && order.status != ORDER_INVALID) {
		switch (order.status) {
		case ORDER_INVALID:
			warnx("order invalid");
			goto out;

		/* Write to the CA that it's ready. */
		if (!dochngresp(&c, &chngs[i], thumb))
			goto out;
	}

	/*
	 * We now wait on the ACME server for each domain.
	 * Connect to the server (assume it's the same server) once
	 * every five seconds.
	 */
	for (;;) {
		for (i = 0; i < altsz; i++) {
			doddbg("%s: done %lu, altsz %lu, i %lu, status %d",
			    __func__, done, altsz, i, chngs[i].status);

			if (chngs[i].status == CHNG_VALID)
				continue;

			if (chngs[i].retry++ >= RETRY_MAX) {
				warnx("%s: too many tries", chngs[i].uri);
		case ORDER_VALID:
			rc = 1;
			continue;
		case ORDER_PENDING:
			if (order.authsz < 1) {
				warnx("order is in state pending but no "
				    "authorizations know");
				goto out;
			}
			for (i = 0; i < order.authsz; i++) {
				if (!dochngreq(&c, order.auths[i], &chngs[i]))
					goto out;

				dodbg("challenge, token: %s, uri: %s, status: "
				    "%d", chngs[i].token, chngs[i].uri,
				    chngs[i].status);

			sleep(RETRY_DELAY);
			if (dochngcheck(&c, &chngs[i])) {
				if (chngs[i].status == CHNG_VALID)
					done++;
				continue;
			} else
					continue;

				if (chngs[i].retry++ >= RETRY_MAX) {
					warnx("%s: too many tries",
					    chngs[i].uri);
					goto out;
				}

				if (writeop(Cfd, COMM_CHNG_OP, CHNG_SYN) <= 0)
					goto out;
				else if (writestr(Cfd, COMM_THUMB, thumb) <= 0)
					goto out;
				else if (writestr(Cfd, COMM_TOK,
				    chngs[i].token) <= 0)
					goto out;

				/* Read that the challenge has been made. */
				if (readop(Cfd, COMM_CHNG_ACK) != CHNG_ACK)
					goto out;

				/* Write to the CA that it's ready. */
				if (!dochngresp(&c, &chngs[i]))
					goto out;
			}
			break;
		case ORDER_READY:
			/*
			 * Write our acknowledgement that the challenges are
			 * over.
			 * The challenge process will remove all of the files.
			 */
			if (writeop(Cfd, COMM_CHNG_OP, CHNG_STOP) <= 0)
				goto out;
		}

		if (done == altsz)
			/* Wait to receive the certificate itself. */
			if ((cert = readstr(kfd, COMM_CERT)) == NULL)
				goto out;
			if (!docert(&c, order.finalize, cert))
				goto out;
			break;
		default:
			warnx("unhandled status: %d", order.status);
			goto out;
		}
		if (!doupdorder(&c, &order))
			goto out;

		dodbg("order.status %d", order.status);
		if (order.status == ORDER_PENDING)
			sleep(RETRY_DELAY);
	}

	/*
	 * Write our acknowledgement that the challenges are over.
	 * The challenge process will remove all of the files.
	 */
	if (writeop(Cfd, COMM_CHNG_OP, CHNG_STOP) <= 0)
	if (order.status != ORDER_VALID)
		goto out;

	/* Wait to receive the certificate itself. */
	if ((cert = readstr(kfd, COMM_CERT)) == NULL)
	if (order.certificate == NULL) {
		warnx("no certificate url received");
		goto out;
	}

	/*
	 * Otherwise, submit the CA for signing, download the signed
	 * copy, and ship that into the certificate process for copying.
	 */
	if (!docert(&c, paths.newcert, cert))
	if (!dogetcert(&c, order.certificate))
		goto out;
	else if (writeop(cfd, COMM_CSR_OP, CERT_UPDATE) <= 0)
		goto out;
	else if (writebuf(cfd, COMM_CSR, c.buf.buf, c.buf.sz) <= 0)
		goto out;

	/*
	 * Read back the issuer from the certproc.
	 * Then contact the issuer to get the certificate chain.
	 * Write this chain directly back to the certproc.
	 */
	if ((url = readstr(cfd, COMM_ISSUER)) == NULL)
		goto out;
	else if (!dofullchain(&c, url))
		goto out;
	else if (writebuf(cfd, COMM_CHAIN, c.buf.buf, c.buf.sz) <= 0)
		goto out;

	rc = 1;
out:
	close(cfd);


@@ 808,6 883,7 @@ out:
	free(cert);
	free(url);
	free(thumb);
	free(c.kid);
	free(c.buf.buf);
	if (chngs != NULL)
		for (i = 0; i < altsz; i++)

M parse.h => parse.h +19 -14
@@ 1,4 1,4 @@
/*	$OpenBSD: parse.h,v 1.8 2017/11/27 01:58:52 florian Exp $ */
/*	$OpenBSD: parse.h,v 1.12 2019/06/14 19:55:08 florian Exp $ */
/*
 * Copyright (c) 2016 Sebastian Benoit <benno@openbsd.org>
 *


@@ 27,24 27,31 @@
 * limit all paths to PATH_MAX
 */

enum keytype {
	KT_RSA = 0,
	KT_ECDSA
};

struct authority_c {
	TAILQ_ENTRY(authority_c)	 entry;
	char		       	*name;
	char		       	*api;
	char		       	*account;
	char				*name;
	char				*api;
	char				*account;
	enum keytype			 keytype;
};

struct domain_c {
	TAILQ_ENTRY(domain_c)	 entry;
	TAILQ_HEAD(, altname_c)	altname_list;
	int			altname_count;
	char		       	*domain;
	char		       	*key;
	char		       	*cert;
	TAILQ_HEAD(, altname_c)	 altname_list;
	int			 altname_count;
	enum keytype		 keytype;
	char			*domain;
	char			*key;
	char			*cert;
	char			*chain;
	char			*fullchain;
	char		       	*auth;
	char		       	*challengedir;
	char			*auth;
	char			*challengedir;
};

struct altname_c {


@@ 58,9 65,7 @@ struct keyfile {
};

#define ACME_OPT_VERBOSE	0x00000001
#define ACME_OPT_NEWACCT	0x00000002
#define ACME_OPT_NEWDKEY	0x00000004
#define ACME_OPT_CHECK		0x00000008
#define ACME_OPT_CHECK		0x00000004

struct acme_conf {
	int			 opts;

M parse.y => parse.y +39 -11
@@ 1,4 1,4 @@
/*	$OpenBSD: parse.y,v 1.32 2019/01/29 16:38:29 benno Exp $ */
/*	$OpenBSD: parse.y,v 1.37 2019/06/15 12:51:19 florian Exp $ */

/*
 * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>


@@ 29,6 29,7 @@
#include <sys/stat.h>
#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <limits.h>
#include <stdarg.h>
#include <stdio.h>


@@ 37,6 38,7 @@
#include <unistd.h>

#include "parse.h"
#include "extern.h"

TAILQ_HEAD(files, file)		 files = TAILQ_HEAD_INITIALIZER(files);
static struct file {


@@ 68,8 70,9 @@ struct authority_c	*conf_new_authority(struct acme_conf *, char *);
struct domain_c		*conf_new_domain(struct acme_conf *, char *);
struct keyfile		*conf_new_keyfile(struct acme_conf *, char *);
void			 clear_config(struct acme_conf *);
const char*		 kt2txt(enum keytype);
void			 print_config(struct acme_conf *);
int			 conf_check_file(char *, int);
int			 conf_check_file(char *);

TAILQ_HEAD(symhead, sym)	 symhead = TAILQ_HEAD_INITIALIZER(symhead);
struct sym {


@@ 102,9 105,11 @@ typedef struct {
%token	YES NO
%token	INCLUDE
%token	ERROR
%token	RSA ECDSA
%token	<v.string>	STRING
%token	<v.number>	NUMBER
%type	<v.string>	string
%type	<v.number>	keytype

%%



@@ 214,7 219,7 @@ authorityoptsl	: API URL STRING {
				err(EXIT_FAILURE, "strdup");
			auth->api = s;
		}
		| ACCOUNT KEY STRING {
		| ACCOUNT KEY STRING keytype{
			char *s;
			if (auth->account != NULL) {
				yyerror("duplicate account");


@@ 223,6 228,7 @@ authorityoptsl	: API URL STRING {
			if ((s = strdup($3)) == NULL)
				err(EXIT_FAILURE, "strdup");
			auth->account = s;
			auth->keytype = $4;
		}
		;



@@ 257,12 263,17 @@ domain		: DOMAIN STRING {
		}
		;

keytype		: RSA	{ $$ = KT_RSA; }
		| ECDSA	{ $$ = KT_ECDSA; }
		|	{ $$ = KT_RSA; }
		;

domainopts_l	: domainopts_l domainoptsl nl
		| domainoptsl optnl
		;

domainoptsl	: ALTERNATIVE NAMES '{' altname_l '}'
		| DOMAIN KEY STRING {
		| DOMAIN KEY STRING keytype {
			char *s;
			if (domain->key != NULL) {
				yyerror("duplicate key");


@@ 270,8 281,7 @@ domainoptsl	: ALTERNATIVE NAMES '{' altname_l '}'
			}
			if ((s = strdup($3)) == NULL)
				err(EXIT_FAILURE, "strdup");
			if (!conf_check_file(s,
			    (conf->opts & ACME_OPT_NEWDKEY))) {
			if (!conf_check_file(s)) {
				free(s);
				YYERROR;
			}


@@ 281,6 291,7 @@ domainoptsl	: ALTERNATIVE NAMES '{' altname_l '}'
				YYERROR;
			}
			domain->key = s;
			domain->keytype = $4;
		}
		| DOMAIN CERT STRING {
			char *s;


@@ 427,10 438,12 @@ lookup(char *s)
		{"chain",		CHAIN},
		{"challengedir",	CHALLENGEDIR},
		{"domain",		DOMAIN},
		{"ecdsa",		ECDSA},
		{"full",		FULL},
		{"include",		INCLUDE},
		{"key",			KEY},
		{"names",		NAMES},
		{"rsa",			RSA},
		{"sign",		SIGN},
		{"url",			URL},
		{"with",		WITH},


@@ 982,6 995,19 @@ clear_config(struct acme_conf *xconf)
	free(xconf);
}

const char*
kt2txt(enum keytype kt)
{
	switch (kt) {
	case KT_RSA:
		return "rsa";
	case KT_ECDSA:
		return "ecdsa";
	default:
		return "<unknown>";
	}
}

void
print_config(struct acme_conf *xconf)
{


@@ 995,7 1021,8 @@ print_config(struct acme_conf *xconf)
		if (a->api != NULL)
			printf("\tapi url \"%s\"\n", a->api);
		if (a->account != NULL)
			printf("\taccount key \"%s\"\n", a->account);
			printf("\taccount key \"%s\" %s\n", a->account,
			    kt2txt(a->keytype));
		printf("}\n\n");
	}
	TAILQ_FOREACH(d, &xconf->domain_list, entry) {


@@ 1012,7 1039,8 @@ print_config(struct acme_conf *xconf)
		if (f)
			printf(" }\n");
		if (d->key != NULL)
			printf("\tdomain key \"%s\"\n", d->key);
			printf("\tdomain key \"%s\" %s\n", d->key, kt2txt(
			    d->keytype));
		if (d->cert != NULL)
			printf("\tdomain certificate \"%s\"\n", d->cert);
		if (d->chain != NULL)


@@ 1046,7 1074,7 @@ domain_valid(const char *cp)
}

int
conf_check_file(char *s, int dontstat)
conf_check_file(char *s)
{
	struct stat st;



@@ 1054,9 1082,9 @@ conf_check_file(char *s, int dontstat)
		warnx("%s: not an absolute path", s);
		return 0;
	}
	if (dontstat)
		return 1;
	if (stat(s, &st)) {
		if (errno == ENOENT)
			return 1;
		warn("cannot stat %s", s);
		return 0;
	}

M revokeproc.c => revokeproc.c +17 -26
@@ 1,4 1,4 @@
/*	$Id: revokeproc.c,v 1.14 2018/07/28 15:25:23 tb Exp $ */
/*	$Id: revokeproc.c,v 1.15 2019/06/16 19:49:13 florian Exp $ */
/*
 * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>
 *


@@ 91,10 91,10 @@ X509expires(X509 *x)
}

int
revokeproc(int fd, const char *certdir, const char *certfile, int force,
revokeproc(int fd, const char *certfile, int force,
    int revocate, const char *const *alts, size_t altsz)
{
	char		*path = NULL, *der = NULL, *dercp, *der64 = NULL;
	char		*der = NULL, *dercp, *der64 = NULL;
	char		*san = NULL, *str, *tok;
	int		 rc = 0, cc, i, extsz, ssz, len;
	size_t		*found = NULL;


@@ 114,11 114,8 @@ revokeproc(int fd, const char *certdir, const char *certfile, int force,
	 * We allow "f" to be NULL IFF the cert doesn't exist yet.
	 */

	if (asprintf(&path, "%s/%s", certdir, certfile) == -1) {
		warn("asprintf");
		goto out;
	} else if ((f = fopen(path, "r")) == NULL && errno != ENOENT) {
		warn("%s", path);
	if ((f = fopen(certfile, "r")) == NULL && errno != ENOENT) {
		warn("%s", certfile);
		goto out;
	}



@@ 140,7 137,7 @@ revokeproc(int fd, const char *certdir, const char *certfile, int force,
	 */

	if (f == NULL && revocate) {
		warnx("%s/%s: no certificate found", certdir, certfile);
		warnx("%s: no certificate found", certfile);
		(void)writeop(fd, COMM_REVOKE_RESP, REVOKE_OK);
		goto out;
	} else if (f == NULL && !revocate) {


@@ 181,7 178,7 @@ revokeproc(int fd, const char *certdir, const char *certfile, int force,
			continue;

		if (san != NULL) {
			warnx("%s/%s: two SAN entries", certdir, certfile);
			warnx("%s: two SAN entries", certfile);
			goto out;
		}



@@ 204,7 201,7 @@ revokeproc(int fd, const char *certdir, const char *certfile, int force,
	}

	if (san == NULL) {
		warnx("%s/%s: does not have a SAN entry", certdir, certfile);
		warnx("%s: does not have a SAN entry", certfile);
		goto out;
	}



@@ 233,13 230,11 @@ revokeproc(int fd, const char *certdir, const char *certfile, int force,
			if (strcmp(tok, alts[j]) == 0)
				break;
		if (j == altsz) {
			warnx("%s/%s: unknown SAN entry: %s",
			    certdir, certfile, tok);
			warnx("%s: unknown SAN entry: %s", certfile, tok);
			goto out;
		}
		if (found[j]++) {
			warnx("%s/%s: duplicate SAN entry: %s",
			    certdir, certfile, tok);
			warnx("%s: duplicate SAN entry: %s", certfile, tok);
			goto out;
		}
	}


@@ 247,8 242,7 @@ revokeproc(int fd, const char *certdir, const char *certfile, int force,
	for (j = 0; j < altsz; j++) {
		if (found[j])
			continue;
		warnx("%s/%s: domain not listed: %s",
		    certdir, certfile, alts[j]);
		warnx("%s: domain not listed: %s", certfile, alts[j]);
		goto out;
	}



@@ 259,7 253,7 @@ revokeproc(int fd, const char *certdir, const char *certfile, int force,
	 */

	if (revocate) {
		dodbg("%s/%s: revocation", certdir, certfile);
		dodbg("%s: revocation", certfile);

		/*
		 * First, tell netproc we're online.


@@ 293,16 287,14 @@ revokeproc(int fd, const char *certdir, const char *certfile, int force,
	rop = time(NULL) >= (t - RENEW_ALLOW) ? REVOKE_EXP : REVOKE_OK;

	if (rop == REVOKE_EXP)
		dodbg("%s/%s: certificate renewable: %lld days left",
		    certdir, certfile,
		    (long long)(t - time(NULL)) / 24 / 60 / 60);
		dodbg("%s: certificate renewable: %lld days left",
		    certfile, (long long)(t - time(NULL)) / 24 / 60 / 60);
	else
		dodbg("%s/%s: certificate valid: %lld days left",
		    certdir, certfile,
		    (long long)(t - time(NULL)) / 24 / 60 / 60);
		dodbg("%s: certificate valid: %lld days left",
		    certfile, (long long)(t - time(NULL)) / 24 / 60 / 60);

	if (rop == REVOKE_OK && force) {
		warnx("%s/%s: forcing renewal", certdir, certfile);
		warnx("%s: forcing renewal", certfile);
		rop = REVOKE_EXP;
	}



@@ 338,7 330,6 @@ out:
	X509_free(x);
	BIO_free(bio);
	free(san);
	free(path);
	free(der);
	free(found);
	free(der64);

M util.c => util.c +7 -7
@@ 1,4 1,4 @@
/*	$Id: util.c,v 1.11 2018/03/15 18:26:47 otto Exp $ */
/*	$Id: util.c,v 1.12 2019/06/28 13:32:46 deraadt Exp $ */
/*
 * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>
 *


@@ 91,7 91,7 @@ readop(int fd, enum comm comm)
	long		 op;

	ssz = read(fd, &op, sizeof(long));
	if (ssz < 0) {
	if (ssz == -1) {
		warn("read: %s", comms[comm]);
		return LONG_MAX;
	} else if (ssz && ssz != sizeof(long)) {


@@ 124,7 124,7 @@ readbuf(int fd, enum comm comm, size_t *sz)
	size_t		 rsz, lsz;
	char		*p = NULL;

	if ((ssz = read(fd, sz, sizeof(size_t))) < 0) {
	if ((ssz = read(fd, sz, sizeof(size_t))) == -1) {
		warn("read: %s length", comms[comm]);
		return NULL;
	} else if ((size_t)ssz != sizeof(size_t)) {


@@ 143,7 143,7 @@ readbuf(int fd, enum comm comm, size_t *sz)
	rsz = 0;
	lsz = *sz;
	while (lsz) {
		if ((ssz = read(fd, p + rsz, lsz)) < 0) {
		if ((ssz = read(fd, p + rsz, lsz)) == -1) {
			warn("read: %s", comms[comm]);
			break;
		} else if (ssz > 0) {


@@ 175,7 175,7 @@ writeop(int fd, enum comm comm, long op)

	sigfp = signal(SIGPIPE, sigpipe);

	if ((ssz = write(fd, &op, sizeof(long))) < 0) {
	if ((ssz = write(fd, &op, sizeof(long))) == -1) {
		if ((er = errno) != EPIPE)
			warn("write: %s", comms[comm]);
		signal(SIGPIPE, sigfp);


@@ 212,7 212,7 @@ writebuf(int fd, enum comm comm, const void *v, size_t sz)

	sigfp = signal(SIGPIPE, sigpipe);

	if ((ssz = write(fd, &sz, sizeof(size_t))) < 0) {
	if ((ssz = write(fd, &sz, sizeof(size_t))) == -1) {
		if ((er = errno) != EPIPE)
			warn("write: %s length", comms[comm]);
		signal(SIGPIPE, sigfp);


@@ 223,7 223,7 @@ writebuf(int fd, enum comm comm, const void *v, size_t sz)

	if ((size_t)ssz != sizeof(size_t))
		warnx("short write: %s length", comms[comm]);
	else if ((ssz = write(fd, v, sz)) < 0) {
	else if ((ssz = write(fd, v, sz)) == -1) {
		if (errno == EPIPE)
			rc = 0;
		else