1*0b60193dStb /* $Id: revokeproc.c,v 1.25 2022/12/18 12:04:55 tb 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>
22de579d12Sflorian #include <stdio.h>
23de579d12Sflorian #include <stdlib.h>
24de579d12Sflorian #include <string.h>
25de579d12Sflorian #include <unistd.h>
26*0b60193dStb #include <vis.h>
27de579d12Sflorian
28de579d12Sflorian #include <openssl/pem.h>
29de579d12Sflorian #include <openssl/x509.h>
30de579d12Sflorian #include <openssl/x509v3.h>
31de579d12Sflorian #include <openssl/err.h>
32de579d12Sflorian
33de579d12Sflorian #include "extern.h"
34de579d12Sflorian
35de579d12Sflorian #define RENEW_ALLOW (30 * 24 * 60 * 60)
36de579d12Sflorian
37de579d12Sflorian /*
38760a94e2Stb * Convert the X509's expiration time into a time_t value.
39de579d12Sflorian */
40de579d12Sflorian static time_t
X509expires(X509 * x)41de579d12Sflorian X509expires(X509 *x)
42de579d12Sflorian {
434128f05dSderaadt ASN1_TIME *atim;
44de579d12Sflorian struct tm t;
45de579d12Sflorian
46760a94e2Stb if ((atim = X509_getm_notAfter(x)) == NULL) {
47760a94e2Stb warnx("missing notAfter");
48760a94e2Stb return -1;
49760a94e2Stb }
50760a94e2Stb
51de579d12Sflorian memset(&t, 0, sizeof(t));
52de579d12Sflorian
53760a94e2Stb if (!ASN1_TIME_to_tm(atim, &t)) {
54de579d12Sflorian warnx("invalid ASN1_TIME");
55760a94e2Stb return -1;
56de579d12Sflorian }
57de579d12Sflorian
584cf348cfStb return timegm(&t);
59de579d12Sflorian }
60de579d12Sflorian
61de579d12Sflorian int
revokeproc(int fd,const char * certfile,int force,int revocate,const char * const * alts,size_t altsz)6261075b4cSflorian revokeproc(int fd, const char *certfile, int force,
6362492c74Sflorian int revocate, const char *const *alts, size_t altsz)
64de579d12Sflorian {
650cffdb45Stb GENERAL_NAMES *sans = NULL;
6661075b4cSflorian char *der = NULL, *dercp, *der64 = NULL;
670cffdb45Stb int rc = 0, cc, i, len;
687bce6888Sderaadt size_t *found = NULL;
697bce6888Sderaadt FILE *f = NULL;
707bce6888Sderaadt X509 *x = NULL;
71de579d12Sflorian long lval;
72de579d12Sflorian enum revokeop op, rop;
73de579d12Sflorian time_t t;
74de579d12Sflorian size_t j;
75de579d12Sflorian
76de579d12Sflorian /*
77de579d12Sflorian * First try to open the certificate before we drop privileges
78de579d12Sflorian * and jail ourselves.
79de579d12Sflorian * We allow "f" to be NULL IFF the cert doesn't exist yet.
80de579d12Sflorian */
81de579d12Sflorian
8261075b4cSflorian if ((f = fopen(certfile, "r")) == NULL && errno != ENOENT) {
8361075b4cSflorian warn("%s", certfile);
84de579d12Sflorian goto out;
85de579d12Sflorian }
86de579d12Sflorian
87de579d12Sflorian /* File-system and sandbox jailing. */
88de579d12Sflorian
89de579d12Sflorian ERR_load_crypto_strings();
90de579d12Sflorian
91ec0d8c8bSderaadt if (pledge("stdio", NULL) == -1) {
92ec0d8c8bSderaadt warn("pledge");
93de579d12Sflorian goto out;
94ec0d8c8bSderaadt }
95de579d12Sflorian
96de579d12Sflorian /*
97de579d12Sflorian * If we couldn't open the certificate, it doesn't exist so we
98de579d12Sflorian * haven't submitted it yet, so obviously we can mark that it
99de579d12Sflorian * has expired and we should renew it.
100de579d12Sflorian * If we're revoking, however, then that's an error!
101de579d12Sflorian * Ignore if the reader isn't reading in either case.
102de579d12Sflorian */
103de579d12Sflorian
1047cd8f039Sjsing if (f == NULL && revocate) {
10561075b4cSflorian warnx("%s: no certificate found", certfile);
106de579d12Sflorian (void)writeop(fd, COMM_REVOKE_RESP, REVOKE_OK);
107de579d12Sflorian goto out;
1087cd8f039Sjsing } else if (f == NULL && !revocate) {
109de579d12Sflorian if (writeop(fd, COMM_REVOKE_RESP, REVOKE_EXP) >= 0)
110de579d12Sflorian rc = 1;
111de579d12Sflorian goto out;
112de579d12Sflorian }
113de579d12Sflorian
1147cd8f039Sjsing if ((x = PEM_read_X509(f, NULL, NULL, NULL)) == NULL) {
115de579d12Sflorian warnx("PEM_read_X509");
116de579d12Sflorian goto out;
117de579d12Sflorian }
118de579d12Sflorian
1190cffdb45Stb /* Cache and sanity check X509v3 extensions. */
1200cffdb45Stb
1210cffdb45Stb if (X509_check_purpose(x, -1, -1) <= 0) {
1220cffdb45Stb warnx("%s: invalid X509v3 extensions", certfile);
1230cffdb45Stb goto out;
1240cffdb45Stb }
1250cffdb45Stb
126de579d12Sflorian /* Read out the expiration date. */
127de579d12Sflorian
128760a94e2Stb if ((t = X509expires(x)) == -1) {
129de579d12Sflorian warnx("X509expires");
130de579d12Sflorian goto out;
131de579d12Sflorian }
132de579d12Sflorian
1330cffdb45Stb /* Extract list of SAN entries from the certificate. */
134de579d12Sflorian
1350cffdb45Stb sans = X509_get_ext_d2i(x, NID_subject_alt_name, NULL, NULL);
1360cffdb45Stb if (sans == NULL) {
13761075b4cSflorian warnx("%s: does not have a SAN entry", certfile);
138f0c83cb3Ssthen if (revocate)
139de579d12Sflorian goto out;
140f0c83cb3Ssthen force = 2;
141de579d12Sflorian }
142de579d12Sflorian
143de579d12Sflorian /* An array of buckets: the number of entries found. */
144de579d12Sflorian
1457cd8f039Sjsing if ((found = calloc(altsz, sizeof(size_t))) == NULL) {
146de579d12Sflorian warn("calloc");
147de579d12Sflorian goto out;
148de579d12Sflorian }
149de579d12Sflorian
150de579d12Sflorian /*
1510cffdb45Stb * Ensure the certificate's SAN entries fully cover those from the
1520cffdb45Stb * configuration file and that all domains are represented only once.
153de579d12Sflorian */
154de579d12Sflorian
1550cffdb45Stb for (i = 0; i < sk_GENERAL_NAME_num(sans); i++) {
1560cffdb45Stb GENERAL_NAME *gen_name;
1570cffdb45Stb const ASN1_IA5STRING *name;
1580cffdb45Stb const unsigned char *name_buf;
1590cffdb45Stb int name_len;
1600cffdb45Stb int name_type;
1610cffdb45Stb
1620cffdb45Stb gen_name = sk_GENERAL_NAME_value(sans, i);
1630cffdb45Stb assert(gen_name != NULL);
1640cffdb45Stb
1650cffdb45Stb name = GENERAL_NAME_get0_value(gen_name, &name_type);
1660cffdb45Stb if (name_type != GEN_DNS)
167de579d12Sflorian continue;
1680cffdb45Stb
1690cffdb45Stb /* name_buf isn't a C string and could contain embedded NULs. */
1700cffdb45Stb name_buf = ASN1_STRING_get0_data(name);
1710cffdb45Stb name_len = ASN1_STRING_length(name);
1720cffdb45Stb
1730cffdb45Stb for (j = 0; j < altsz; j++) {
1740cffdb45Stb if ((size_t)name_len != strlen(alts[j]))
175de579d12Sflorian continue;
1760cffdb45Stb if (memcmp(name_buf, alts[j], name_len) == 0)
177de579d12Sflorian break;
1780cffdb45Stb }
179de579d12Sflorian if (j == altsz) {
180f0c83cb3Ssthen if (revocate) {
181*0b60193dStb char *visbuf;
182*0b60193dStb
183*0b60193dStb visbuf = calloc(4, name_len + 1);
184*0b60193dStb if (visbuf == NULL) {
185*0b60193dStb warn("%s: unexpected SAN", certfile);
186*0b60193dStb goto out;
187*0b60193dStb }
188*0b60193dStb strvisx(visbuf, name_buf, name_len, VIS_SAFE);
189*0b60193dStb warnx("%s: unexpected SAN entry: %s",
190*0b60193dStb certfile, visbuf);
191*0b60193dStb free(visbuf);
192de579d12Sflorian goto out;
193de579d12Sflorian }
194f0c83cb3Ssthen force = 2;
19576a7f400Sotto continue;
196f0c83cb3Ssthen }
197de579d12Sflorian if (found[j]++) {
198f0c83cb3Ssthen if (revocate) {
1990cffdb45Stb warnx("%s: duplicate SAN entry: %.*s",
2000cffdb45Stb certfile, name_len, name_buf);
201de579d12Sflorian goto out;
202de579d12Sflorian }
203f0c83cb3Ssthen force = 2;
204f0c83cb3Ssthen }
205de579d12Sflorian }
206de579d12Sflorian
207f0c83cb3Ssthen for (j = 0; j < altsz; j++) {
208de579d12Sflorian if (found[j])
209de579d12Sflorian continue;
210f0c83cb3Ssthen if (revocate) {
21161075b4cSflorian warnx("%s: domain not listed: %s", certfile, alts[j]);
212de579d12Sflorian goto out;
213de579d12Sflorian }
214f0c83cb3Ssthen force = 2;
215f0c83cb3Ssthen }
216de579d12Sflorian
217de579d12Sflorian /*
218de579d12Sflorian * If we're going to revoke, write the certificate to the
219de579d12Sflorian * netproc in DER and base64-encoded format.
220de579d12Sflorian * Then exit: we have nothing left to do.
221de579d12Sflorian */
222de579d12Sflorian
223ee27a5e1Sderaadt if (revocate) {
22461075b4cSflorian dodbg("%s: revocation", certfile);
225de579d12Sflorian
226de579d12Sflorian /*
227de579d12Sflorian * First, tell netproc we're online.
228de579d12Sflorian * If they're down, then just exit without warning.
229de579d12Sflorian */
230de579d12Sflorian
231de579d12Sflorian cc = writeop(fd, COMM_REVOKE_RESP, REVOKE_EXP);
2327cd8f039Sjsing if (cc == 0)
233de579d12Sflorian rc = 1;
234de579d12Sflorian if (cc <= 0)
235de579d12Sflorian goto out;
236de579d12Sflorian
237de579d12Sflorian if ((len = i2d_X509(x, NULL)) < 0) {
238de579d12Sflorian warnx("i2d_X509");
239de579d12Sflorian goto out;
2407cd8f039Sjsing } else if ((der = dercp = malloc(len)) == NULL) {
241de579d12Sflorian warn("malloc");
242de579d12Sflorian goto out;
243de579d12Sflorian } else if (len != i2d_X509(x, (u_char **)&dercp)) {
244de579d12Sflorian warnx("i2d_X509");
245de579d12Sflorian goto out;
2467cd8f039Sjsing } else if ((der64 = base64buf_url(der, len)) == NULL) {
247de579d12Sflorian warnx("base64buf_url");
248de579d12Sflorian goto out;
249de579d12Sflorian } else if (writestr(fd, COMM_CSR, der64) >= 0)
250de579d12Sflorian rc = 1;
251de579d12Sflorian
252de579d12Sflorian goto out;
253de579d12Sflorian }
254de579d12Sflorian
255de579d12Sflorian rop = time(NULL) >= (t - RENEW_ALLOW) ? REVOKE_EXP : REVOKE_OK;
256de579d12Sflorian
2574de82fa3Sderaadt if (rop == REVOKE_EXP)
25861075b4cSflorian dodbg("%s: certificate renewable: %lld days left",
25961075b4cSflorian certfile, (long long)(t - time(NULL)) / 24 / 60 / 60);
260de579d12Sflorian else
26161075b4cSflorian dodbg("%s: certificate valid: %lld days left",
26261075b4cSflorian certfile, (long long)(t - time(NULL)) / 24 / 60 / 60);
263de579d12Sflorian
2644de82fa3Sderaadt if (rop == REVOKE_OK && force) {
265f0c83cb3Ssthen warnx("%s: %sforcing renewal", certfile,
266f0c83cb3Ssthen force == 2 ? "domain list changed, " : "");
267de579d12Sflorian rop = REVOKE_EXP;
268de579d12Sflorian }
269de579d12Sflorian
270de579d12Sflorian /*
271de579d12Sflorian * We can re-submit it given RENEW_ALLOW time before.
272de579d12Sflorian * If netproc is down, just exit.
273de579d12Sflorian */
274de579d12Sflorian
2757cd8f039Sjsing if ((cc = writeop(fd, COMM_REVOKE_RESP, rop)) == 0)
276de579d12Sflorian rc = 1;
277de579d12Sflorian if (cc <= 0)
278de579d12Sflorian goto out;
279de579d12Sflorian
280de579d12Sflorian op = REVOKE__MAX;
2817cd8f039Sjsing if ((lval = readop(fd, COMM_REVOKE_OP)) == 0)
282de579d12Sflorian op = REVOKE_STOP;
2837cd8f039Sjsing else if (lval == REVOKE_CHECK)
284de579d12Sflorian op = lval;
285de579d12Sflorian
2864de82fa3Sderaadt if (op == REVOKE__MAX) {
287de579d12Sflorian warnx("unknown operation from netproc");
288de579d12Sflorian goto out;
2894de82fa3Sderaadt } else if (op == REVOKE_STOP) {
290de579d12Sflorian rc = 1;
291de579d12Sflorian goto out;
292de579d12Sflorian }
293de579d12Sflorian
294de579d12Sflorian rc = 1;
295de579d12Sflorian out:
296de579d12Sflorian close(fd);
2977cd8f039Sjsing if (f != NULL)
298de579d12Sflorian fclose(f);
299de579d12Sflorian X509_free(x);
3000cffdb45Stb GENERAL_NAMES_free(sans);
301de579d12Sflorian free(der);
302de579d12Sflorian free(found);
303de579d12Sflorian free(der64);
304de579d12Sflorian ERR_print_errors_fp(stderr);
305de579d12Sflorian ERR_free_strings();
30634335c11Sjsing return rc;
307de579d12Sflorian }
308