1*f2270df8Sflorian /* $Id: netproc.c,v 1.37 2024/10/10 09:39:35 florian Exp $ */ 2de579d12Sflorian /* 3de579d12Sflorian * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv> 4de579d12Sflorian * 5de579d12Sflorian * Permission to use, copy, modify, and distribute this software for any 6de579d12Sflorian * purpose with or without fee is hereby granted, provided that the above 7de579d12Sflorian * copyright notice and this permission notice appear in all copies. 8de579d12Sflorian * 9de579d12Sflorian * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES 10de579d12Sflorian * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11de579d12Sflorian * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR 12de579d12Sflorian * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13de579d12Sflorian * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14de579d12Sflorian * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15de579d12Sflorian * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16de579d12Sflorian */ 17de579d12Sflorian 18de579d12Sflorian #include <assert.h> 19de579d12Sflorian #include <ctype.h> 20de579d12Sflorian #include <err.h> 21de579d12Sflorian #include <errno.h> 22*f2270df8Sflorian #include <stdio.h> 23de579d12Sflorian #include <stdlib.h> 24de579d12Sflorian #include <string.h> 25de579d12Sflorian #include <unistd.h> 26aad8b57bSjsing #include <tls.h> 2742c2cc51Sflorian #include <vis.h> 28de579d12Sflorian 29de579d12Sflorian #include "http.h" 30de579d12Sflorian #include "extern.h" 31383e31e9Sbenno #include "parse.h" 32de579d12Sflorian 33de579d12Sflorian #define RETRY_DELAY 5 34de579d12Sflorian #define RETRY_MAX 10 35de579d12Sflorian 36de579d12Sflorian /* 37b6c74373Sbenno * Buffer used when collecting the results of an http transfer. 38de579d12Sflorian */ 39de579d12Sflorian struct buf { 40de579d12Sflorian char *buf; /* binary buffer */ 41de579d12Sflorian size_t sz; /* length of buffer */ 42de579d12Sflorian }; 43de579d12Sflorian 44de579d12Sflorian /* 45b6c74373Sbenno * Used for communication with other processes. 46de579d12Sflorian */ 47de579d12Sflorian struct conn { 487b00f4e9Sflorian const char *newnonce; /* nonce authority */ 497b00f4e9Sflorian char *kid; /* kid when account exists */ 50de579d12Sflorian int fd; /* acctproc handle */ 51de579d12Sflorian int dfd; /* dnsproc handle */ 527b00f4e9Sflorian struct buf buf; /* http body buffer */ 53de579d12Sflorian }; 54de579d12Sflorian 55de579d12Sflorian /* 56de579d12Sflorian * If something goes wrong (or we're tracing output), we dump the 57de579d12Sflorian * current transfer's data as a debug message. 58de579d12Sflorian */ 59de579d12Sflorian static void 60de579d12Sflorian buf_dump(const struct buf *buf) 61de579d12Sflorian { 62de579d12Sflorian char *nbuf; 63de579d12Sflorian 647cd8f039Sjsing if (buf->sz == 0) 65de579d12Sflorian return; 66b98581f5Sflorian /* must be at least 4 * srclen + 1 long */ 67b98581f5Sflorian if ((nbuf = calloc(buf->sz + 1, 4)) == NULL) 68b98581f5Sflorian err(EXIT_FAILURE, "calloc"); 69b98581f5Sflorian strvisx(nbuf, buf->buf, buf->sz, VIS_SAFE); 70b98581f5Sflorian dodbg("transfer buffer: [%s] (%zu bytes)", nbuf, buf->sz); 71de579d12Sflorian free(nbuf); 72de579d12Sflorian } 73de579d12Sflorian 74de579d12Sflorian /* 75de579d12Sflorian * Extract the domain and port from a URL. 76de579d12Sflorian * The url must be formatted as schema://address[/stuff]. 77de579d12Sflorian * This returns NULL on failure. 78de579d12Sflorian */ 79de579d12Sflorian static char * 80de579d12Sflorian url2host(const char *host, short *port, char **path) 81de579d12Sflorian { 82de579d12Sflorian char *url, *ep; 83de579d12Sflorian 84de579d12Sflorian /* We only understand HTTP and HTTPS. */ 857cd8f039Sjsing if (strncmp(host, "https://", 8) == 0) { 86de579d12Sflorian *port = 443; 877cd8f039Sjsing if ((url = strdup(host + 8)) == NULL) { 88de579d12Sflorian warn("strdup"); 8934335c11Sjsing return NULL; 90de579d12Sflorian } 917cd8f039Sjsing } else if (strncmp(host, "http://", 7) == 0) { 92de579d12Sflorian *port = 80; 937cd8f039Sjsing if ((url = strdup(host + 7)) == NULL) { 94de579d12Sflorian warn("strdup"); 9534335c11Sjsing return NULL; 96de579d12Sflorian } 97de579d12Sflorian } else { 98de579d12Sflorian warnx("%s: unknown schema", host); 9934335c11Sjsing return NULL; 100de579d12Sflorian } 101de579d12Sflorian 102de579d12Sflorian /* Terminate path part. */ 1037cd8f039Sjsing if ((ep = strchr(url, '/')) != NULL) { 104de579d12Sflorian *path = strdup(ep); 105de579d12Sflorian *ep = '\0'; 106de579d12Sflorian } else 107de579d12Sflorian *path = strdup(""); 108de579d12Sflorian 1097cd8f039Sjsing if (*path == NULL) { 110de579d12Sflorian warn("strdup"); 111de579d12Sflorian free(url); 11234335c11Sjsing return NULL; 113de579d12Sflorian } 114de579d12Sflorian 11534335c11Sjsing return url; 116de579d12Sflorian } 117de579d12Sflorian 118de579d12Sflorian /* 119de579d12Sflorian * Contact dnsproc and resolve a host. 120de579d12Sflorian * Place the answers in "v" and return the number of answers, which can 121de579d12Sflorian * be at most MAX_SERVERS_DNS. 122de579d12Sflorian * Return <0 on failure. 123de579d12Sflorian */ 124de579d12Sflorian static ssize_t 125de579d12Sflorian urlresolve(int fd, const char *host, struct source *v) 126de579d12Sflorian { 127de579d12Sflorian char *addr; 128de579d12Sflorian size_t i, sz; 129de579d12Sflorian long lval; 130de579d12Sflorian 131de579d12Sflorian if (writeop(fd, COMM_DNS, DNS_LOOKUP) <= 0) 13234335c11Sjsing return -1; 133de579d12Sflorian else if (writestr(fd, COMM_DNSQ, host) <= 0) 13434335c11Sjsing return -1; 135de579d12Sflorian else if ((lval = readop(fd, COMM_DNSLEN)) < 0) 13634335c11Sjsing return -1; 137de579d12Sflorian 138de579d12Sflorian sz = lval; 139de579d12Sflorian assert(sz <= MAX_SERVERS_DNS); 140de579d12Sflorian 141de579d12Sflorian for (i = 0; i < sz; i++) { 142de579d12Sflorian memset(&v[i], 0, sizeof(struct source)); 143de579d12Sflorian if ((lval = readop(fd, COMM_DNSF)) < 0) 144de579d12Sflorian goto err; 1457cd8f039Sjsing else if (lval != 4 && lval != 6) 146de579d12Sflorian goto err; 1477cd8f039Sjsing else if ((addr = readstr(fd, COMM_DNSA)) == NULL) 148de579d12Sflorian goto err; 149de579d12Sflorian v[i].family = lval; 150de579d12Sflorian v[i].ip = addr; 151de579d12Sflorian } 152de579d12Sflorian 15334335c11Sjsing return sz; 154de579d12Sflorian err: 155de579d12Sflorian for (i = 0; i < sz; i++) 156de579d12Sflorian free(v[i].ip); 15734335c11Sjsing return -1; 158de579d12Sflorian } 159de579d12Sflorian 160de579d12Sflorian /* 161de579d12Sflorian * Send a "regular" HTTP GET message to "addr" and stuff the response 162de579d12Sflorian * into the connection buffer. 163de579d12Sflorian * Return the HTTP error code or <0 on failure. 164de579d12Sflorian */ 165de579d12Sflorian static long 166de579d12Sflorian nreq(struct conn *c, const char *addr) 167de579d12Sflorian { 168de579d12Sflorian struct httpget *g; 169de579d12Sflorian struct source src[MAX_SERVERS_DNS]; 1705dc748e0Sflorian struct httphead *st; 171de579d12Sflorian char *host, *path; 172de579d12Sflorian short port; 173de579d12Sflorian size_t srcsz; 174de579d12Sflorian ssize_t ssz; 175de579d12Sflorian long code; 1765dc748e0Sflorian int redirects = 0; 177de579d12Sflorian 1787cd8f039Sjsing if ((host = url2host(addr, &port, &path)) == NULL) 17934335c11Sjsing return -1; 180de579d12Sflorian 1815dc748e0Sflorian again: 182de579d12Sflorian if ((ssz = urlresolve(c->dfd, host, src)) < 0) { 183de579d12Sflorian free(host); 184de579d12Sflorian free(path); 18534335c11Sjsing return -1; 186de579d12Sflorian } 187de579d12Sflorian srcsz = ssz; 188de579d12Sflorian 1897b00f4e9Sflorian g = http_get(src, srcsz, host, port, path, 0, NULL, 0); 190de579d12Sflorian free(host); 191de579d12Sflorian free(path); 1927cd8f039Sjsing if (g == NULL) 19334335c11Sjsing return -1; 194de579d12Sflorian 1955dc748e0Sflorian switch (g->code) { 1965dc748e0Sflorian case 301: 1975dc748e0Sflorian case 302: 1985dc748e0Sflorian case 303: 1995dc748e0Sflorian case 307: 2005dc748e0Sflorian case 308: 2015dc748e0Sflorian redirects++; 2025dc748e0Sflorian if (redirects > 3) { 2035dc748e0Sflorian warnx("too many redirects"); 2045dc748e0Sflorian http_get_free(g); 2055dc748e0Sflorian return -1; 2065dc748e0Sflorian } 2075dc748e0Sflorian 2085dc748e0Sflorian if ((st = http_head_get("Location", g->head, g->headsz)) == 2095dc748e0Sflorian NULL) { 2105dc748e0Sflorian warnx("redirect without location header"); 211f44c78d3Smbuhl http_get_free(g); 2125dc748e0Sflorian return -1; 2135dc748e0Sflorian } 2145dc748e0Sflorian 2155dc748e0Sflorian host = url2host(st->val, &port, &path); 2165dc748e0Sflorian http_get_free(g); 2175dc748e0Sflorian if (host == NULL) 2185dc748e0Sflorian return -1; 2195dc748e0Sflorian goto again; 2205dc748e0Sflorian break; 2215dc748e0Sflorian default: 222de579d12Sflorian code = g->code; 2235dc748e0Sflorian break; 2245dc748e0Sflorian } 225de579d12Sflorian 226de579d12Sflorian /* Copy the body part into our buffer. */ 227de579d12Sflorian free(c->buf.buf); 228de579d12Sflorian c->buf.sz = g->bodypartsz; 229de579d12Sflorian c->buf.buf = malloc(c->buf.sz); 2307cd8f039Sjsing if (c->buf.buf == NULL) { 231de579d12Sflorian warn("malloc"); 2322963b436Sflorian code = -1; 2332963b436Sflorian } else 2342963b436Sflorian memcpy(c->buf.buf, g->bodypart, c->buf.sz); 2352963b436Sflorian http_get_free(g); 23634335c11Sjsing return code; 237de579d12Sflorian } 238de579d12Sflorian 239de579d12Sflorian /* 240de579d12Sflorian * Create and send a signed communication to the ACME server. 241de579d12Sflorian * Stuff the response into the communication buffer. 242de579d12Sflorian * Return <0 on failure on the HTTP error code otherwise. 243de579d12Sflorian */ 244de579d12Sflorian static long 2457b00f4e9Sflorian sreq(struct conn *c, const char *addr, int kid, const char *req, char **loc) 246de579d12Sflorian { 247de579d12Sflorian struct httpget *g; 248de579d12Sflorian struct source src[MAX_SERVERS_DNS]; 249de579d12Sflorian char *host, *path, *nonce, *reqsn; 250de579d12Sflorian short port; 251de579d12Sflorian struct httphead *h; 252de579d12Sflorian ssize_t ssz; 253de579d12Sflorian long code; 254de579d12Sflorian 2557b00f4e9Sflorian if ((host = url2host(c->newnonce, &port, &path)) == NULL) 25634335c11Sjsing return -1; 257de579d12Sflorian 258de579d12Sflorian if ((ssz = urlresolve(c->dfd, host, src)) < 0) { 259de579d12Sflorian free(host); 260de579d12Sflorian free(path); 26134335c11Sjsing return -1; 262de579d12Sflorian } 263de579d12Sflorian 2647b00f4e9Sflorian g = http_get(src, (size_t)ssz, host, port, path, 1, NULL, 0); 265de579d12Sflorian free(host); 266de579d12Sflorian free(path); 2677cd8f039Sjsing if (g == NULL) 26834335c11Sjsing return -1; 269de579d12Sflorian 270de579d12Sflorian h = http_head_get("Replay-Nonce", g->head, g->headsz); 2717cd8f039Sjsing if (h == NULL) { 2727b00f4e9Sflorian warnx("%s: no replay nonce", c->newnonce); 273de579d12Sflorian http_get_free(g); 27434335c11Sjsing return -1; 2757cd8f039Sjsing } else if ((nonce = strdup(h->val)) == NULL) { 276de579d12Sflorian warn("strdup"); 277de579d12Sflorian http_get_free(g); 27834335c11Sjsing return -1; 279de579d12Sflorian } 280de579d12Sflorian http_get_free(g); 281de579d12Sflorian 282de579d12Sflorian /* 2837b00f4e9Sflorian * Send the url, nonce and request payload to the acctproc. 284de579d12Sflorian * This will create the proper JSON object we need. 285de579d12Sflorian */ 2867b00f4e9Sflorian if (writeop(c->fd, COMM_ACCT, kid ? ACCT_KID_SIGN : ACCT_SIGN) <= 0) { 287de579d12Sflorian free(nonce); 28834335c11Sjsing return -1; 289de579d12Sflorian } else if (writestr(c->fd, COMM_PAY, req) <= 0) { 290de579d12Sflorian free(nonce); 29134335c11Sjsing return -1; 292de579d12Sflorian } else if (writestr(c->fd, COMM_NONCE, nonce) <= 0) { 293de579d12Sflorian free(nonce); 29434335c11Sjsing return -1; 2957b00f4e9Sflorian } else if (writestr(c->fd, COMM_URL, addr) <= 0) { 2967b00f4e9Sflorian free(nonce); 2977b00f4e9Sflorian return -1; 298de579d12Sflorian } 299de579d12Sflorian free(nonce); 300de579d12Sflorian 3017b00f4e9Sflorian if (kid && writestr(c->fd, COMM_KID, c->kid) <= 0) 3027b00f4e9Sflorian return -1; 3037b00f4e9Sflorian 304de579d12Sflorian /* Now read back the signed payload. */ 3057cd8f039Sjsing if ((reqsn = readstr(c->fd, COMM_REQ)) == NULL) 30634335c11Sjsing return -1; 307de579d12Sflorian 308de579d12Sflorian /* Now send the signed payload to the CA. */ 3097cd8f039Sjsing if ((host = url2host(addr, &port, &path)) == NULL) { 310de579d12Sflorian free(reqsn); 31134335c11Sjsing return -1; 312de579d12Sflorian } else if ((ssz = urlresolve(c->dfd, host, src)) < 0) { 313de579d12Sflorian free(host); 314de579d12Sflorian free(path); 315de579d12Sflorian free(reqsn); 31634335c11Sjsing return -1; 317de579d12Sflorian } 318de579d12Sflorian 3197b00f4e9Sflorian g = http_get(src, (size_t)ssz, host, port, path, 0, reqsn, 3207b00f4e9Sflorian strlen(reqsn)); 321de579d12Sflorian 322de579d12Sflorian free(host); 323de579d12Sflorian free(path); 324de579d12Sflorian free(reqsn); 3257cd8f039Sjsing if (g == NULL) 32634335c11Sjsing return -1; 327de579d12Sflorian 328de579d12Sflorian /* Stuff response into parse buffer. */ 329de579d12Sflorian code = g->code; 330de579d12Sflorian 331de579d12Sflorian free(c->buf.buf); 332de579d12Sflorian c->buf.sz = g->bodypartsz; 333de579d12Sflorian c->buf.buf = malloc(c->buf.sz); 3347cd8f039Sjsing if (c->buf.buf == NULL) { 335de579d12Sflorian warn("malloc"); 3362963b436Sflorian code = -1; 3372963b436Sflorian } else 3382963b436Sflorian memcpy(c->buf.buf, g->bodypart, c->buf.sz); 3397b00f4e9Sflorian 3407b00f4e9Sflorian if (loc != NULL) { 3417b00f4e9Sflorian free(*loc); 3427b00f4e9Sflorian *loc = NULL; 3437b00f4e9Sflorian h = http_head_get("Location", g->head, g->headsz); 3447b00f4e9Sflorian /* error checking done by caller */ 3457b00f4e9Sflorian if (h != NULL) 3467b00f4e9Sflorian *loc = strdup(h->val); 3477b00f4e9Sflorian } 3487b00f4e9Sflorian 3492963b436Sflorian http_get_free(g); 35034335c11Sjsing return code; 351de579d12Sflorian } 352de579d12Sflorian 353de579d12Sflorian /* 354de579d12Sflorian * Send to the CA that we want to authorise a new account. 355de579d12Sflorian * This only happens once for a new account key. 356de579d12Sflorian * Returns non-zero on success. 357de579d12Sflorian */ 358de579d12Sflorian static int 3596736ff2bSflorian donewacc(struct conn *c, const struct capaths *p, const char *contact) 360de579d12Sflorian { 361ec77e55dSflorian struct jsmnn *j = NULL; 3627bce6888Sderaadt int rc = 0; 363de0ff358Ssthen char *req, *detail, *error = NULL, *accturi = NULL; 364de579d12Sflorian long lc; 365de579d12Sflorian 3666736ff2bSflorian if ((req = json_fmt_newacc(contact)) == NULL) 3677b00f4e9Sflorian warnx("json_fmt_newacc"); 3687b00f4e9Sflorian else if ((lc = sreq(c, p->newaccount, 0, req, &c->kid)) < 0) 3697b00f4e9Sflorian warnx("%s: bad comm", p->newaccount); 370ec77e55dSflorian else if (lc == 400) { 371ec77e55dSflorian if ((j = json_parse(c->buf.buf, c->buf.sz)) == NULL) 372ec77e55dSflorian warnx("%s: bad JSON object", p->newaccount); 373ec77e55dSflorian else { 374ec77e55dSflorian detail = json_getstr(j, "detail"); 375ec77e55dSflorian if (detail != NULL && stravis(&error, detail, VIS_SAFE) 376ec77e55dSflorian != -1) { 377ec77e55dSflorian warnx("%s", error); 378ec77e55dSflorian free(error); 379ec77e55dSflorian } 380ec77e55dSflorian } 381ec77e55dSflorian } else if (lc != 200 && lc != 201) 3827b00f4e9Sflorian warnx("%s: bad HTTP: %ld", p->newaccount, lc); 3837cd8f039Sjsing else if (c->buf.buf == NULL || c->buf.sz == 0) 3847b00f4e9Sflorian warnx("%s: empty response", p->newaccount); 385de579d12Sflorian else 386de579d12Sflorian rc = 1; 387de579d12Sflorian 388de0ff358Ssthen if (c->kid != NULL) { 389de0ff358Ssthen if (stravis(&accturi, c->kid, VIS_SAFE) != -1) 390*f2270df8Sflorian printf("account key: %s\n", accturi); 391de0ff358Ssthen free(accturi); 392de0ff358Ssthen } 393de0ff358Ssthen 3947cd8f039Sjsing if (rc == 0 || verbose > 1) 395de579d12Sflorian buf_dump(&c->buf); 396de579d12Sflorian free(req); 39734335c11Sjsing return rc; 398de579d12Sflorian } 399de579d12Sflorian 400de579d12Sflorian /* 4017b00f4e9Sflorian * Check if our account already exists, if not create it. 4027b00f4e9Sflorian * Populates conn->kid. 4037b00f4e9Sflorian * Returns non-zero on success. 4047b00f4e9Sflorian */ 4057b00f4e9Sflorian static int 4066736ff2bSflorian dochkacc(struct conn *c, const struct capaths *p, const char *contact) 4077b00f4e9Sflorian { 4087b00f4e9Sflorian int rc = 0; 409de0ff358Ssthen char *req, *accturi = NULL; 4107b00f4e9Sflorian long lc; 4117b00f4e9Sflorian 4127b00f4e9Sflorian if ((req = json_fmt_chkacc()) == NULL) 4137b00f4e9Sflorian warnx("json_fmt_chkacc"); 4147b00f4e9Sflorian else if ((lc = sreq(c, p->newaccount, 0, req, &c->kid)) < 0) 4157b00f4e9Sflorian warnx("%s: bad comm", p->newaccount); 4167b00f4e9Sflorian else if (lc != 200 && lc != 400) 4177b00f4e9Sflorian warnx("%s: bad HTTP: %ld", p->newaccount, lc); 4187b00f4e9Sflorian else if (c->buf.buf == NULL || c->buf.sz == 0) 4197b00f4e9Sflorian warnx("%s: empty response", p->newaccount); 4207b00f4e9Sflorian else if (lc == 400) 4216736ff2bSflorian rc = donewacc(c, p, contact); 4227b00f4e9Sflorian else 4237b00f4e9Sflorian rc = 1; 4247b00f4e9Sflorian 4257b00f4e9Sflorian if (c->kid == NULL) 4267b00f4e9Sflorian rc = 0; 427de0ff358Ssthen else { 428de0ff358Ssthen if (stravis(&accturi, c->kid, VIS_SAFE) != -1) 429de0ff358Ssthen dodbg("account key: %s", accturi); 430de0ff358Ssthen free(accturi); 431de0ff358Ssthen } 4327b00f4e9Sflorian 4337b00f4e9Sflorian if (rc == 0 || verbose > 1) 4347b00f4e9Sflorian buf_dump(&c->buf); 4357b00f4e9Sflorian free(req); 4367b00f4e9Sflorian return rc; 4377b00f4e9Sflorian } 4387b00f4e9Sflorian 4397b00f4e9Sflorian /* 4407b00f4e9Sflorian * Submit a new order for a certificate. 4417b00f4e9Sflorian */ 4427b00f4e9Sflorian static int 4437b00f4e9Sflorian doneworder(struct conn *c, const char *const *alts, size_t altsz, 4447b00f4e9Sflorian struct order *order, const struct capaths *p) 4457b00f4e9Sflorian { 4467b00f4e9Sflorian struct jsmnn *j = NULL; 4477b00f4e9Sflorian int rc = 0; 4487b00f4e9Sflorian char *req; 4497b00f4e9Sflorian long lc; 4507b00f4e9Sflorian 4517b00f4e9Sflorian if ((req = json_fmt_neworder(alts, altsz)) == NULL) 4527b00f4e9Sflorian warnx("json_fmt_neworder"); 4537b00f4e9Sflorian else if ((lc = sreq(c, p->neworder, 1, req, &order->uri)) < 0) 4547b00f4e9Sflorian warnx("%s: bad comm", p->neworder); 4557b00f4e9Sflorian else if (lc != 201) 4567b00f4e9Sflorian warnx("%s: bad HTTP: %ld", p->neworder, lc); 4577b00f4e9Sflorian else if ((j = json_parse(c->buf.buf, c->buf.sz)) == NULL) 4587b00f4e9Sflorian warnx("%s: bad JSON object", p->neworder); 4597b00f4e9Sflorian else if (!json_parse_order(j, order)) 4607b00f4e9Sflorian warnx("%s: bad order", p->neworder); 4617b00f4e9Sflorian else if (order->status == ORDER_INVALID) 4627b00f4e9Sflorian warnx("%s: order invalid", p->neworder); 4637b00f4e9Sflorian else 4647b00f4e9Sflorian rc = 1; 4657b00f4e9Sflorian 4667b00f4e9Sflorian if (rc == 0 || verbose > 1) 4677b00f4e9Sflorian buf_dump(&c->buf); 4687b00f4e9Sflorian 4697b00f4e9Sflorian free(req); 4707b00f4e9Sflorian json_free(j); 4717b00f4e9Sflorian return rc; 4727b00f4e9Sflorian } 4737b00f4e9Sflorian 4747b00f4e9Sflorian /* 4757b00f4e9Sflorian * Update order status 4767b00f4e9Sflorian */ 4777b00f4e9Sflorian static int 4787b00f4e9Sflorian doupdorder(struct conn *c, struct order *order) 4797b00f4e9Sflorian { 4807b00f4e9Sflorian struct jsmnn *j = NULL; 4817b00f4e9Sflorian int rc = 0; 4827b00f4e9Sflorian long lc; 4837b00f4e9Sflorian 4847b00f4e9Sflorian if ((lc = sreq(c, order->uri, 1, "", NULL)) < 0) 4857b00f4e9Sflorian warnx("%s: bad comm", order->uri); 4867b00f4e9Sflorian else if (lc != 200) 4877b00f4e9Sflorian warnx("%s: bad HTTP: %ld", order->uri, lc); 4887b00f4e9Sflorian else if ((j = json_parse(c->buf.buf, c->buf.sz)) == NULL) 4897b00f4e9Sflorian warnx("%s: bad JSON object", order->uri); 4907b00f4e9Sflorian else if (!json_parse_upd_order(j, order)) 4917b00f4e9Sflorian warnx("%s: bad order", order->uri); 4927b00f4e9Sflorian else 4937b00f4e9Sflorian rc = 1; 4947b00f4e9Sflorian 4957b00f4e9Sflorian if (rc == 0 || verbose > 1) 4967b00f4e9Sflorian buf_dump(&c->buf); 4977b00f4e9Sflorian 4987b00f4e9Sflorian json_free(j); 4997b00f4e9Sflorian return rc; 5007b00f4e9Sflorian } 5017b00f4e9Sflorian 5027b00f4e9Sflorian /* 503de579d12Sflorian * Request a challenge for the given domain name. 5048a38e73eSbenno * This must be called for each name "alt". 505de579d12Sflorian * On non-zero exit, fills in "chng" with the challenge. 506de579d12Sflorian */ 507de579d12Sflorian static int 5087b00f4e9Sflorian dochngreq(struct conn *c, const char *auth, struct chng *chng) 509de579d12Sflorian { 5107bce6888Sderaadt int rc = 0; 511de579d12Sflorian long lc; 5127bce6888Sderaadt struct jsmnn *j = NULL; 513de579d12Sflorian 5147b00f4e9Sflorian dodbg("%s: %s", __func__, auth); 515de579d12Sflorian 5167b00f4e9Sflorian if ((lc = sreq(c, auth, 1, "", NULL)) < 0) 5177b00f4e9Sflorian warnx("%s: bad comm", auth); 5187b00f4e9Sflorian else if (lc != 200) 5197b00f4e9Sflorian warnx("%s: bad HTTP: %ld", auth, lc); 5207cd8f039Sjsing else if ((j = json_parse(c->buf.buf, c->buf.sz)) == NULL) 5217b00f4e9Sflorian warnx("%s: bad JSON object", auth); 522de579d12Sflorian else if (!json_parse_challenge(j, chng)) 5237b00f4e9Sflorian warnx("%s: bad challenge", auth); 524de579d12Sflorian else 525de579d12Sflorian rc = 1; 526de579d12Sflorian 5277cd8f039Sjsing if (rc == 0 || verbose > 1) 528de579d12Sflorian buf_dump(&c->buf); 529de579d12Sflorian json_free(j); 53034335c11Sjsing return rc; 531de579d12Sflorian } 532de579d12Sflorian 533de579d12Sflorian /* 5348a38e73eSbenno * Tell the CA that a challenge response is in place. 535de579d12Sflorian */ 536de579d12Sflorian static int 5377b00f4e9Sflorian dochngresp(struct conn *c, const struct chng *chng) 538de579d12Sflorian { 5397bce6888Sderaadt int rc = 0; 540de579d12Sflorian long lc; 541de579d12Sflorian 542de579d12Sflorian dodbg("%s: challenge", chng->uri); 543de579d12Sflorian 5447b00f4e9Sflorian if ((lc = sreq(c, chng->uri, 1, "{}", NULL)) < 0) 545de579d12Sflorian warnx("%s: bad comm", chng->uri); 5467cd8f039Sjsing else if (lc != 200 && lc != 201 && lc != 202) 547de579d12Sflorian warnx("%s: bad HTTP: %ld", chng->uri, lc); 548de579d12Sflorian else 549de579d12Sflorian rc = 1; 550de579d12Sflorian 5517cd8f039Sjsing if (rc == 0 || verbose > 1) 552de579d12Sflorian buf_dump(&c->buf); 5537b00f4e9Sflorian return rc; 5547b00f4e9Sflorian } 5557b00f4e9Sflorian 5567b00f4e9Sflorian /* 5577b00f4e9Sflorian * Submit our csr to the CA. 5587b00f4e9Sflorian */ 5597b00f4e9Sflorian static int 5607b00f4e9Sflorian docert(struct conn *c, const char *uri, const char *csr) 5617b00f4e9Sflorian { 5627b00f4e9Sflorian char *req; 5637b00f4e9Sflorian int rc = 0; 5647b00f4e9Sflorian long lc; 5657b00f4e9Sflorian 5667b00f4e9Sflorian dodbg("%s: certificate", uri); 5677b00f4e9Sflorian 5687b00f4e9Sflorian if ((req = json_fmt_newcert(csr)) == NULL) 5697b00f4e9Sflorian warnx("json_fmt_newcert"); 5707b00f4e9Sflorian else if ((lc = sreq(c, uri, 1, req, NULL)) < 0) 5717b00f4e9Sflorian warnx("%s: bad comm", uri); 5727b00f4e9Sflorian else if (lc != 200) 5737b00f4e9Sflorian warnx("%s: bad HTTP: %ld", uri, lc); 5747b00f4e9Sflorian else if (c->buf.sz == 0 || c->buf.buf == NULL) 5757b00f4e9Sflorian warnx("%s: empty response", uri); 5767b00f4e9Sflorian else 5777b00f4e9Sflorian rc = 1; 5787b00f4e9Sflorian 5797b00f4e9Sflorian if (rc == 0 || verbose > 1) 5807b00f4e9Sflorian buf_dump(&c->buf); 581de579d12Sflorian free(req); 58234335c11Sjsing return rc; 583de579d12Sflorian } 584de579d12Sflorian 585de579d12Sflorian /* 5867b00f4e9Sflorian * Get certificate from CA 587de579d12Sflorian */ 588de579d12Sflorian static int 5897b00f4e9Sflorian dogetcert(struct conn *c, const char *uri) 590de579d12Sflorian { 5917b00f4e9Sflorian int rc = 0; 592de579d12Sflorian long lc; 593de579d12Sflorian 5947b00f4e9Sflorian dodbg("%s: certificate", uri); 595de579d12Sflorian 5967b00f4e9Sflorian if ((lc = sreq(c, uri, 1, "", NULL)) < 0) 5977b00f4e9Sflorian warnx("%s: bad comm", uri); 5987b00f4e9Sflorian else if (lc != 200) 5997b00f4e9Sflorian warnx("%s: bad HTTP: %ld", uri, lc); 6007b00f4e9Sflorian else if (c->buf.sz == 0 || c->buf.buf == NULL) 6017b00f4e9Sflorian warnx("%s: empty response", uri); 6027b00f4e9Sflorian else 6037b00f4e9Sflorian rc = 1; 604de579d12Sflorian 6057b00f4e9Sflorian if (rc == 0 || verbose > 1) 6067b00f4e9Sflorian buf_dump(&c->buf); 6077b00f4e9Sflorian 6087b00f4e9Sflorian return rc; 609de579d12Sflorian } 610de579d12Sflorian 611de579d12Sflorian static int 612de579d12Sflorian dorevoke(struct conn *c, const char *addr, const char *cert) 613de579d12Sflorian { 614de579d12Sflorian char *req; 6157bce6888Sderaadt int rc = 0; 6167bce6888Sderaadt long lc = 0; 617de579d12Sflorian 618de579d12Sflorian dodbg("%s: revocation", addr); 619de579d12Sflorian 6207cd8f039Sjsing if ((req = json_fmt_revokecert(cert)) == NULL) 621de579d12Sflorian warnx("json_fmt_revokecert"); 6227b00f4e9Sflorian else if ((lc = sreq(c, addr, 1, req, NULL)) < 0) 623de579d12Sflorian warnx("%s: bad comm", addr); 6247cd8f039Sjsing else if (lc != 200 && lc != 201 && lc != 409) 625de579d12Sflorian warnx("%s: bad HTTP: %ld", addr, lc); 626de579d12Sflorian else 627de579d12Sflorian rc = 1; 628de579d12Sflorian 6297cd8f039Sjsing if (lc == 409) 630de579d12Sflorian warnx("%s: already revoked", addr); 631de579d12Sflorian 6327cd8f039Sjsing if (rc == 0 || verbose > 1) 633de579d12Sflorian buf_dump(&c->buf); 634de579d12Sflorian free(req); 63534335c11Sjsing return rc; 636de579d12Sflorian } 637de579d12Sflorian 638de579d12Sflorian /* 639de579d12Sflorian * Look up directories from the certificate authority. 640de579d12Sflorian */ 641de579d12Sflorian static int 642de579d12Sflorian dodirs(struct conn *c, const char *addr, struct capaths *paths) 643de579d12Sflorian { 6447bce6888Sderaadt struct jsmnn *j = NULL; 645de579d12Sflorian long lc; 6467bce6888Sderaadt int rc = 0; 647de579d12Sflorian 648de579d12Sflorian dodbg("%s: directories", addr); 649de579d12Sflorian 650de579d12Sflorian if ((lc = nreq(c, addr)) < 0) 651de579d12Sflorian warnx("%s: bad comm", addr); 6527cd8f039Sjsing else if (lc != 200 && lc != 201) 653de579d12Sflorian warnx("%s: bad HTTP: %ld", addr, lc); 6547cd8f039Sjsing else if ((j = json_parse(c->buf.buf, c->buf.sz)) == NULL) 655de579d12Sflorian warnx("json_parse"); 656de579d12Sflorian else if (!json_parse_capaths(j, paths)) 657de579d12Sflorian warnx("%s: bad CA paths", addr); 658de579d12Sflorian else 659de579d12Sflorian rc = 1; 660de579d12Sflorian 6617cd8f039Sjsing if (rc == 0 || verbose > 1) 662de579d12Sflorian buf_dump(&c->buf); 663de579d12Sflorian json_free(j); 66434335c11Sjsing return rc; 665de579d12Sflorian } 666de579d12Sflorian 667de579d12Sflorian /* 6688a38e73eSbenno * Communicate with the ACME server. 6698a38e73eSbenno * We need the certificate we want to upload and our account key information. 670de579d12Sflorian */ 671de579d12Sflorian int 672de579d12Sflorian netproc(int kfd, int afd, int Cfd, int cfd, int dfd, int rfd, 6737b00f4e9Sflorian int revocate, struct authority_c *authority, 6740b28b247Sflorian const char *const *alts, size_t altsz) 675de579d12Sflorian { 6767bce6888Sderaadt int rc = 0; 6777b00f4e9Sflorian size_t i; 678c5ad7108Stb char *cert = NULL, *thumb = NULL, *error = NULL; 679de579d12Sflorian struct conn c; 680de579d12Sflorian struct capaths paths; 6817b00f4e9Sflorian struct order order; 6827bce6888Sderaadt struct chng *chngs = NULL; 683de579d12Sflorian long lval; 684de579d12Sflorian 685de579d12Sflorian memset(&paths, 0, sizeof(struct capaths)); 686de579d12Sflorian memset(&c, 0, sizeof(struct conn)); 687de579d12Sflorian 688fb0a89eeStedu if (unveil(tls_default_ca_cert_file(), "r") == -1) { 689bc5a8259Sbeck warn("unveil %s", tls_default_ca_cert_file()); 69082b65d8aSderaadt goto out; 69182b65d8aSderaadt } 69282b65d8aSderaadt 69325ca385bSjsing if (pledge("stdio inet rpath", NULL) == -1) { 69425ca385bSjsing warn("pledge"); 69525ca385bSjsing goto out; 69625ca385bSjsing } 69725ca385bSjsing 69825ca385bSjsing if (http_init() == -1) { 69925ca385bSjsing warn("http_init"); 70025ca385bSjsing goto out; 70125ca385bSjsing } 70225ca385bSjsing 703ec0d8c8bSderaadt if (pledge("stdio inet", NULL) == -1) { 704ec0d8c8bSderaadt warn("pledge"); 705de579d12Sflorian goto out; 706ec0d8c8bSderaadt } 707de579d12Sflorian 708de579d12Sflorian /* 7098a38e73eSbenno * Wait until the acctproc, keyproc, and revokeproc have started up and 7108a38e73eSbenno * are ready to serve us data. 7118a38e73eSbenno * Then check whether revokeproc indicates that the certificate on file 7128a38e73eSbenno * (if any) can be updated. 713de579d12Sflorian */ 7147cd8f039Sjsing if ((lval = readop(afd, COMM_ACCT_STAT)) == 0) { 715de579d12Sflorian rc = 1; 716de579d12Sflorian goto out; 7177cd8f039Sjsing } else if (lval != ACCT_READY) { 718de579d12Sflorian warnx("unknown operation from acctproc"); 719de579d12Sflorian goto out; 720de579d12Sflorian } 721de579d12Sflorian 7227cd8f039Sjsing if ((lval = readop(kfd, COMM_KEY_STAT)) == 0) { 723de579d12Sflorian rc = 1; 724de579d12Sflorian goto out; 7257cd8f039Sjsing } else if (lval != KEY_READY) { 726de579d12Sflorian warnx("unknown operation from keyproc"); 727de579d12Sflorian goto out; 728de579d12Sflorian } 729de579d12Sflorian 7307cd8f039Sjsing if ((lval = readop(rfd, COMM_REVOKE_RESP)) == 0) { 731de579d12Sflorian rc = 1; 732de579d12Sflorian goto out; 7337cd8f039Sjsing } else if (lval != REVOKE_EXP && lval != REVOKE_OK) { 734de579d12Sflorian warnx("unknown operation from revokeproc"); 735de579d12Sflorian goto out; 736de579d12Sflorian } 737de579d12Sflorian 738de579d12Sflorian /* If our certificate is up-to-date, return now. */ 7397cd8f039Sjsing if (lval == REVOKE_OK) { 740de579d12Sflorian rc = 1; 741de579d12Sflorian goto out; 742de579d12Sflorian } 743de579d12Sflorian 744de579d12Sflorian c.dfd = dfd; 745de579d12Sflorian c.fd = afd; 746de579d12Sflorian 747de579d12Sflorian /* 7488a38e73eSbenno * Look up the API urls of the ACME server. 749de579d12Sflorian */ 7507b00f4e9Sflorian if (!dodirs(&c, authority->api, &paths)) 7517b00f4e9Sflorian goto out; 7527b00f4e9Sflorian 7537b00f4e9Sflorian c.newnonce = paths.newnonce; 7547b00f4e9Sflorian 7557b00f4e9Sflorian /* Check if our account already exists or create it. */ 7566736ff2bSflorian if (!dochkacc(&c, &paths, authority->contact)) 757de579d12Sflorian goto out; 758de579d12Sflorian 759de579d12Sflorian /* 760de579d12Sflorian * If we're meant to revoke, then wait for revokeproc to send us 761de579d12Sflorian * the certificate (if it's found at all). 762de579d12Sflorian * Following that, submit the request to the CA then notify the 763de579d12Sflorian * certproc, which will in turn notify the fileproc. 7647b00f4e9Sflorian * XXX currently we can only sign with the account key, the RFC 7657b00f4e9Sflorian * also mentions signing with the privat key of the cert itself. 766de579d12Sflorian */ 767ee27a5e1Sderaadt if (revocate) { 7687cd8f039Sjsing if ((cert = readstr(rfd, COMM_CSR)) == NULL) 769de579d12Sflorian goto out; 770de579d12Sflorian if (!dorevoke(&c, paths.revokecert, cert)) 771de579d12Sflorian goto out; 772de579d12Sflorian else if (writeop(cfd, COMM_CSR_OP, CERT_REVOKE) > 0) 773de579d12Sflorian rc = 1; 774de579d12Sflorian goto out; 775de579d12Sflorian } 776de579d12Sflorian 7777b00f4e9Sflorian memset(&order, 0, sizeof(order)); 7787b00f4e9Sflorian 7797b00f4e9Sflorian if (!doneworder(&c, alts, altsz, &order, &paths)) 780de579d12Sflorian goto out; 781de579d12Sflorian 7827b00f4e9Sflorian chngs = calloc(order.authsz, sizeof(struct chng)); 7837b00f4e9Sflorian if (chngs == NULL) { 7847b00f4e9Sflorian warn("calloc"); 785de579d12Sflorian goto out; 7867b00f4e9Sflorian } 787de579d12Sflorian 788de579d12Sflorian /* 7897b00f4e9Sflorian * Get thumbprint from acctproc. We will need it to construct 7907b00f4e9Sflorian * a response to the challenge 791de579d12Sflorian */ 792de579d12Sflorian if (writeop(afd, COMM_ACCT, ACCT_THUMBPRINT) <= 0) 793de579d12Sflorian goto out; 7947cd8f039Sjsing else if ((thumb = readstr(afd, COMM_THUMB)) == NULL) 795de579d12Sflorian goto out; 796de579d12Sflorian 7977b00f4e9Sflorian while(order.status != ORDER_VALID && order.status != ORDER_INVALID) { 7987b00f4e9Sflorian switch (order.status) { 7997b00f4e9Sflorian case ORDER_INVALID: 8007b00f4e9Sflorian warnx("order invalid"); 8017b00f4e9Sflorian goto out; 8027b00f4e9Sflorian case ORDER_VALID: 8037b00f4e9Sflorian rc = 1; 8047b00f4e9Sflorian continue; 8057b00f4e9Sflorian case ORDER_PENDING: 8067b00f4e9Sflorian if (order.authsz < 1) { 8077b00f4e9Sflorian warnx("order is in state pending but no " 8087b00f4e9Sflorian "authorizations know"); 8097b00f4e9Sflorian goto out; 8107b00f4e9Sflorian } 8117b00f4e9Sflorian for (i = 0; i < order.authsz; i++) { 8127b00f4e9Sflorian if (!dochngreq(&c, order.auths[i], &chngs[i])) 8137b00f4e9Sflorian goto out; 8147b00f4e9Sflorian 8157b00f4e9Sflorian dodbg("challenge, token: %s, uri: %s, status: " 8167b00f4e9Sflorian "%d", chngs[i].token, chngs[i].uri, 8177b00f4e9Sflorian chngs[i].status); 8187b00f4e9Sflorian 81942c2cc51Sflorian if (chngs[i].status == CHNG_VALID || 82042c2cc51Sflorian chngs[i].status == CHNG_INVALID) 8217b00f4e9Sflorian continue; 8227b00f4e9Sflorian 8237b00f4e9Sflorian if (chngs[i].retry++ >= RETRY_MAX) { 8247b00f4e9Sflorian warnx("%s: too many tries", 8257b00f4e9Sflorian chngs[i].uri); 8267b00f4e9Sflorian goto out; 8277b00f4e9Sflorian } 8287b00f4e9Sflorian 829de579d12Sflorian if (writeop(Cfd, COMM_CHNG_OP, CHNG_SYN) <= 0) 830de579d12Sflorian goto out; 831de579d12Sflorian else if (writestr(Cfd, COMM_THUMB, thumb) <= 0) 832de579d12Sflorian goto out; 8337b00f4e9Sflorian else if (writestr(Cfd, COMM_TOK, 8347b00f4e9Sflorian chngs[i].token) <= 0) 835de579d12Sflorian goto out; 836de579d12Sflorian 837de579d12Sflorian /* Read that the challenge has been made. */ 8387cd8f039Sjsing if (readop(Cfd, COMM_CHNG_ACK) != CHNG_ACK) 839de579d12Sflorian goto out; 840de579d12Sflorian 841e1b5cdf1Sflorian } 842de579d12Sflorian /* Write to the CA that it's ready. */ 843e1b5cdf1Sflorian for (i = 0; i < order.authsz; i++) { 844e1b5cdf1Sflorian if (chngs[i].status == CHNG_VALID || 845e1b5cdf1Sflorian chngs[i].status == CHNG_INVALID) 846e1b5cdf1Sflorian continue; 8477b00f4e9Sflorian if (!dochngresp(&c, &chngs[i])) 848de579d12Sflorian goto out; 849de579d12Sflorian } 850e26d00f9Sbenno break; 8517b00f4e9Sflorian case ORDER_READY: 852de579d12Sflorian /* 8537b00f4e9Sflorian * Write our acknowledgement that the challenges are 8547b00f4e9Sflorian * over. 855de579d12Sflorian * The challenge process will remove all of the files. 856de579d12Sflorian */ 857de579d12Sflorian if (writeop(Cfd, COMM_CHNG_OP, CHNG_STOP) <= 0) 858de579d12Sflorian goto out; 859de579d12Sflorian 860de579d12Sflorian /* Wait to receive the certificate itself. */ 8617cd8f039Sjsing if ((cert = readstr(kfd, COMM_CERT)) == NULL) 862de579d12Sflorian goto out; 8637b00f4e9Sflorian if (!docert(&c, order.finalize, cert)) 8647b00f4e9Sflorian goto out; 8657b00f4e9Sflorian break; 8667b00f4e9Sflorian default: 8677b00f4e9Sflorian warnx("unhandled status: %d", order.status); 8687b00f4e9Sflorian goto out; 8697b00f4e9Sflorian } 8707b00f4e9Sflorian if (!doupdorder(&c, &order)) 8717b00f4e9Sflorian goto out; 872de579d12Sflorian 8737b00f4e9Sflorian dodbg("order.status %d", order.status); 8747b00f4e9Sflorian if (order.status == ORDER_PENDING) 8757b00f4e9Sflorian sleep(RETRY_DELAY); 8767b00f4e9Sflorian } 8777b00f4e9Sflorian 87842c2cc51Sflorian if (order.status != ORDER_VALID) { 87942c2cc51Sflorian for (i = 0; i < order.authsz; i++) { 88042c2cc51Sflorian dochngreq(&c, order.auths[i], &chngs[i]); 88142c2cc51Sflorian if (chngs[i].error != NULL) { 88242c2cc51Sflorian if (stravis(&error, chngs[i].error, VIS_SAFE) 88342c2cc51Sflorian != -1) { 88442c2cc51Sflorian warnx("%s", error); 88542c2cc51Sflorian free(error); 88642c2cc51Sflorian error = NULL; 88742c2cc51Sflorian } 88842c2cc51Sflorian } 88942c2cc51Sflorian } 8907b00f4e9Sflorian goto out; 89142c2cc51Sflorian } 8927b00f4e9Sflorian 8937b00f4e9Sflorian if (order.certificate == NULL) { 8947b00f4e9Sflorian warnx("no certificate url received"); 8957b00f4e9Sflorian goto out; 8967b00f4e9Sflorian } 8977b00f4e9Sflorian 8987b00f4e9Sflorian if (!dogetcert(&c, order.certificate)) 899de579d12Sflorian goto out; 900de579d12Sflorian else if (writeop(cfd, COMM_CSR_OP, CERT_UPDATE) <= 0) 901de579d12Sflorian goto out; 902de579d12Sflorian else if (writebuf(cfd, COMM_CSR, c.buf.buf, c.buf.sz) <= 0) 903de579d12Sflorian goto out; 904de579d12Sflorian rc = 1; 905de579d12Sflorian out: 906de579d12Sflorian close(cfd); 907de579d12Sflorian close(kfd); 908de579d12Sflorian close(afd); 909de579d12Sflorian close(Cfd); 910de579d12Sflorian close(dfd); 911de579d12Sflorian close(rfd); 912de579d12Sflorian free(cert); 913de579d12Sflorian free(thumb); 9147b00f4e9Sflorian free(c.kid); 915de579d12Sflorian free(c.buf.buf); 9167cd8f039Sjsing if (chngs != NULL) 9174e6b438aSflorian for (i = 0; i < order.authsz; i++) 918de579d12Sflorian json_free_challenge(&chngs[i]); 919de579d12Sflorian free(chngs); 920de579d12Sflorian json_free_capaths(&paths); 92134335c11Sjsing return rc; 922de579d12Sflorian } 923