RFC 8555 for acme-client(1)

classic Classic list List threaded Threaded
2 messages Options
Reply | Threaded
Open this post in threaded view
|

RFC 8555 for acme-client(1)

Florian Obser-2
This switches acme-client(1) from draft-03 to the final standard.

Let's Encrypt claims - and this seems to be true in my testing - that
accounts created on the v01 api (the draf one) work on the v02 api
(rfc).

This drops support for v01, thinks got shuffled around too much to
support both. acme-client(1) complains fairly loudly when it's pointed
at an old server:

        acme-client: https://acme-staging.api.letsencrypt.org/directory: bad CA paths

To test it switch the api url:
-       api url "https://acme-v01.api.letsencrypt.org/directory"
+       api url "https://acme-v02.api.letsencrypt.org/directory"

Tests, Comments, OKs?

There is certainly more stuff to do here, but it's already quite big.

diff --git etc/examples/acme-client.conf etc/examples/acme-client.conf
index 53bc65e17f3..94f1b3b5c72 100644
--- etc/examples/acme-client.conf
+++ etc/examples/acme-client.conf
@@ -2,12 +2,12 @@
 # $OpenBSD: acme-client.conf,v 1.1 2019/01/08 07:14:10 florian Exp $
 #
 authority letsencrypt {
- api url "https://acme-v01.api.letsencrypt.org/directory"
+ api url "https://acme-v02.api.letsencrypt.org/directory"
  account key "/etc/acme/letsencrypt-privkey.pem"
 }
 
 authority letsencrypt-staging {
- api url "https://acme-staging.api.letsencrypt.org/directory"
+ api url "https://acme-staging-v02.api.letsencrypt.org/directory"
  account key "/etc/acme/letsencrypt-staging-privkey.pem"
 }
 
diff --git usr.sbin/acme-client/acctproc.c usr.sbin/acme-client/acctproc.c
index 2b71fb2ec79..733cf8a0032 100644
--- usr.sbin/acme-client/acctproc.c
+++ usr.sbin/acme-client/acctproc.c
@@ -154,18 +154,16 @@ 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.
  */
 
@@ -175,9 +173,7 @@ op_sign_rsa(char **head, char **prot, EVP_PKEY *pkey, const char *nonce)
  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;
@@ -192,11 +188,12 @@ op_sign_rsa(char **head, char **prot, EVP_PKEY *pkey, const char *nonce)
  * 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 *prot = NULL, *prot64 = NULL;
  char *sign = NULL, *dig64 = NULL, *fin = NULL;
+ char *url = NULL, *kid = NULL;
  unsigned char *dig = NULL;
  EVP_MD_CTX *ctx = NULL;
  int cc, rc = 0;
@@ -208,6 +205,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. */
 
@@ -216,14 +219,21 @@ op_sign(int fd, EVP_PKEY *pkey)
  goto out;
  }
 
- switch (EVP_PKEY_type(pkey->type)) {
- case EVP_PKEY_RSA:
- if (!op_sign_rsa(&head, &prot, pkey, nonce))
+ if (op == ACCT_KID_SIGN) {
+ if ((prot = json_fmt_protected_kid(kid, nonce, url)) == NULL) {
+ warnx("json_fmt_protected_kid");
  goto out;
- break;
- default:
- warnx("EVP_PKEY_type");
- goto out;
+ }
+ } else {
+ switch (EVP_PKEY_type(pkey->type)) {
+ case EVP_PKEY_RSA:
+ if (!op_sign_rsa(&prot, pkey, nonce, url))
+ goto out;
+ break;
+ default:
+ warnx("EVP_PKEY_type");
+ goto out;
+ }
  }
 
  /* The header combined with the nonce, base64. */
@@ -275,7 +285,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)
@@ -289,8 +299,9 @@ out:
  free(pay);
  free(sign);
  free(pay64);
+ free(url);
  free(nonce);
- free(head);
+ free(kid);
  free(prot);
  free(prot64);
  free(dig);
@@ -363,7 +374,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 +386,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;
diff --git usr.sbin/acme-client/acme-client.1 usr.sbin/acme-client/acme-client.1
index 9d198f7abdc..e2ee7269a94 100644
--- usr.sbin/acme-client/acme-client.1
+++ usr.sbin/acme-client/acme-client.1
@@ -145,7 +145,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
diff --git usr.sbin/acme-client/certproc.c usr.sbin/acme-client/certproc.c
index f2cc4e311a9..48f638d1f08 100644
--- usr.sbin/acme-client/certproc.c
+++ usr.sbin/acme-client/certproc.c
@@ -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;
 }
diff --git usr.sbin/acme-client/extern.h usr.sbin/acme-client/extern.h
index eaaae0cfb1d..62cc65e7175 100644
--- usr.sbin/acme-client/extern.h
+++ usr.sbin/acme-client/extern.h
@@ -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 ordrstatus {
+ ORDR_INVALID = -1,
+ ORDR_PENDING = 0,
+ ORDR_READY = 1,
+ ORDR_PROCESSING = 2,
+ ORDR_VALID = 3
+};
+
+struct ordr {
+ char *uri; /* location of the order request */
+ char *finalize; /* finalize uri */
+ char *certificate; /* uri for issued certificate */
+ enum ordrstatus status; /* status of order */
+ char **auths; /* array of 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;
@@ -189,7 +209,7 @@ 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 netproc(int, int, int, int, int, int, int,
  struct authority_c *, const char *const *,
  size_t);
 
@@ -233,20 +253,23 @@ 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_ordr(struct ordr *);
+int json_parse_ordr(struct jsmnn *, struct ordr *);
+int json_parse_upd_ordr(struct jsmnn *, struct ordr *);
 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_newordr(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_kid(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_signed(const char *, const char *, const char *);
 
 /*
  * Should we print debugging messages?
diff --git usr.sbin/acme-client/http.c usr.sbin/acme-client/http.c
index 22b7a717156..cf18c802187 100644
--- usr.sbin/acme-client/http.c
+++ usr.sbin/acme-client/http.c
@@ -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) {
diff --git usr.sbin/acme-client/http.h usr.sbin/acme-client/http.h
index aebfb73edda..31c563c69f9 100644
--- usr.sbin/acme-client/http.h
+++ usr.sbin/acme-client/http.h
@@ -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 *);
 
diff --git usr.sbin/acme-client/json.c usr.sbin/acme-client/json.c
index a86990c59a4..717faf31c35 100644
--- usr.sbin/acme-client/json.c
+++ usr.sbin/acme-client/json.c
@@ -229,6 +229,15 @@ 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.
@@ -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 ordrstatus
+json_parse_ordr_status(struct jsmnn *n)
+{
+ char *status;
+
+ if (n == NULL)
+ return ORDR_INVALID;
+
+ if ((status = json_getstr(n, "status")) == NULL)
+ return ORDR_INVALID;
+
+ if (strcmp(status, "pending") == 0)
+ return ORDR_PENDING;
+ else if (strcmp(status, "ready") == 0)
+ return ORDR_READY;
+ else if (strcmp(status, "processing") == 0)
+ return ORDR_PROCESSING;
+ else if (strcmp(status, "valid") == 0)
+ return ORDR_VALID;
+ else if (strcmp(status, "invalid") == 0)
+ return ORDR_INVALID;
+ else
+ return ORDR_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_ordr(struct jsmnn *n, struct ordr *ordr)
 {
- struct jsmnn *meta;
+ struct jsmnn *array;
+ size_t i;
+ char *finalize, *str;
+
+ ordr->status = json_parse_ordr_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 ((ordr->finalize = strdup(finalize)) == NULL)
+ goto err;
+
+ if ((array = json_getarray(n, "authorizations")) == NULL)
+ goto err;
+
+ if ((ordr->authsz = array->fields) > 0) {
+ ordr->auths = calloc(sizeof(*ordr->auths), ordr->authsz);
+ if (ordr->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 ((ordr->auths[i] = strdup(str)) == NULL) {
+ warn("strdup");
+ goto err;
+ }
+ }
+ return 1;
+err:
+ json_free_ordr(ordr);
+ return 0;
+}
+
+int
+json_parse_upd_ordr(struct jsmnn *n, struct ordr *ordr)
+{
+ char *certificate;
+ ordr->status = json_parse_ordr_status(n);
+ if ((certificate = json_getstr(n, "certificate")) != NULL) {
+ if ((ordr->certificate = strdup(certificate)) == NULL)
+ return 0;
+ }
+ return 1;
+}
+
+void
+json_free_ordr(struct ordr *ordr)
+{
+ size_t i;
+
+ free(ordr->finalize);
+ ordr->finalize = NULL;
+ for(i = 0; i < ordr->authsz; i++)
+ free(ordr->auths[i]);
+ free(ordr->auths);
+
+ ordr->finalize = NULL;
+ ordr->auths = NULL;
+ ordr->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));
 }
 
@@ -480,19 +588,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 +608,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 +627,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_newordr(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 +674,6 @@ json_fmt_revokecert(const char *cert)
  char *p;
 
  c = asprintf(&p, "{"
-    "\"resource\": \"revoke-cert\", "
     "\"certificate\": \"%s\""
     "}",
     cert);
@@ -574,7 +694,6 @@ json_fmt_newcert(const char *cert)
  char *p;
 
  c = asprintf(&p, "{"
-    "\"resource\": \"new-cert\", "
     "\"csr\": \"%s\""
     "}",
     cert);
@@ -586,10 +705,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 +717,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 +733,18 @@ 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_kid(const char *kid, const char *nce, const char *url)
 {
  int c;
  char *p;
 
  c = asprintf(&p, "{"
     "\"alg\": \"RS256\", "
-    "\"jwk\": "
-    "{\"e\": \"%s\", \"kty\": \"RSA\", \"n\": \"%s\"}, "
-    "\"nonce\": \"%s\""
+    "\"kid\": \"%s\", "
+    "\"nonce\": \"%s\", "
+    "\"url\": \"%s\""
     "}",
-    exp, mod, nce);
+    kid, nce, url);
  if (c == -1) {
  warn("asprintf");
  p = NULL;
@@ -634,19 +756,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;
diff --git usr.sbin/acme-client/main.c usr.sbin/acme-client/main.c
index bbedeb76357..1735ff26d98 100644
--- usr.sbin/acme-client/main.c
+++ usr.sbin/acme-client/main.c
@@ -249,7 +249,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);
  }
diff --git usr.sbin/acme-client/netproc.c usr.sbin/acme-client/netproc.c
index 3275dcfc793..067cf3cd454 100644
--- usr.sbin/acme-client/netproc.c
+++ usr.sbin/acme-client/netproc.c
@@ -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)
+donewordr(struct conn *c, const char *const *alts, size_t altsz,
+    struct ordr *ordr, 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_newordr(alts, altsz)) == NULL)
+ warnx("json_fmt_newordr");
+ else if ((lc = sreq(c, p->neworder, 1, req, &ordr->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_ordr(j, ordr))
+ warnx("%s: bad order", p->neworder);
+ else if (ordr->status == ORDR_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)
+doupdordr(struct conn *c, struct ordr *ordr)
 {
- enum chngstatus cc;
+ struct jsmnn *j = NULL;
+ int rc = 0;
  long lc;
- struct jsmnn *j;
 
- dodbg("%s: status", chng->uri);
+ if ((lc = sreq(c, ordr->uri, 1, "", NULL)) < 0)
+ warnx("%s: bad comm", ordr->uri);
+ else if (lc != 200)
+ warnx("%s: bad HTTP: %ld", ordr->uri, lc);
+ else if ((j = json_parse(c->buf.buf, c->buf.sz)) == NULL)
+ warnx("%s: bad JSON object", ordr->uri);
+ else if (!json_parse_upd_ordr(j, ordr))
+ warnx("%s: bad order", ordr->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;
- char *cert = NULL, *thumb = NULL, *url = NULL;
+ size_t i;
+ char *cert = NULL, *thumb = NULL, *url = NULL, *token = NULL;
  struct conn c;
  struct capaths paths;
+ struct ordr ordr;
  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(&ordr, 0, sizeof(ordr));
+
+ if (!donewordr(&c, alts, altsz, &ordr, &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(ordr.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(ordr.status != ORDR_VALID && ordr.status != ORDR_INVALID) {
+ switch (ordr.status) {
+ case ORDR_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 ORDR_VALID:
+ rc = 1;
+ continue;
+ case ORDR_PENDING:
+ if (ordr.authsz < 1) {
+ warnx("order is in state pending but no "
+    "authorizations know");
  goto out;
  }
+ for (i = 0; i < ordr.authsz; i++) {
+ if (!dochngreq(&c, ordr.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 ORDR_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, ordr.finalize, cert))
+ goto out;
  break;
+ default:
+ warnx("unhandled status: %d", ordr.status);
+ goto out;
+ }
+ if (!doupdordr(&c, &ordr))
+ goto out;
+
+ dodbg("ordr.status %d", ordr.status);
+ if (ordr.status == ORDR_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 (ordr.status != ORDR_VALID)
  goto out;
 
- /* Wait to receive the certificate itself. */
- if ((cert = readstr(kfd, COMM_CERT)) == NULL)
+ if (ordr.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, ordr.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,8 @@ out:
  free(cert);
  free(url);
  free(thumb);
+ free(token);
+ free(c.kid);
  free(c.buf.buf);
  if (chngs != NULL)
  for (i = 0; i < altsz; i++)



--
I'm not entirely sure you are real.

Reply | Threaded
Open this post in threaded view
|

Re: RFC 8555 for acme-client(1)

Renaud Allard-2


On 6/6/19 4:02 PM, Florian Obser wrote:

> This switches acme-client(1) from draft-03 to the final standard.
>
> Let's Encrypt claims - and this seems to be true in my testing - that
> accounts created on the v01 api (the draf one) work on the v02 api
> (rfc).
>
> This drops support for v01, thinks got shuffled around too much to
> support both. acme-client(1) complains fairly loudly when it's pointed
> at an old server:
>
> acme-client: https://acme-staging.api.letsencrypt.org/directory: bad CA paths
>
> To test it switch the api url:
> -       api url "https://acme-v01.api.letsencrypt.org/directory"
> +       api url "https://acme-v02.api.letsencrypt.org/directory"
>
> Tests, Comments, OKs?
Builds and works fine for me. I also tested with the creation of a new
account key.


smime.p7s (5K) Download Attachment