1 /* $Id: revokeproc.c,v 1.12 2017/01/24 13:32:55 jsing Exp $ */ 2 /* 3 * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv> 4 * 5 * Permission to use, copy, modify, and distribute this software for any 6 * purpose with or without fee is hereby granted, provided that the above 7 * copyright notice and this permission notice appear in all copies. 8 * 9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES 10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR 12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 */ 17 18 #include <assert.h> 19 #include <ctype.h> 20 #include <err.h> 21 #include <errno.h> 22 #include <stdio.h> 23 #include <stdlib.h> 24 #include <string.h> 25 #include <unistd.h> 26 27 #include <openssl/pem.h> 28 #include <openssl/x509.h> 29 #include <openssl/x509v3.h> 30 #include <openssl/err.h> 31 32 #include "extern.h" 33 34 #define RENEW_ALLOW (30 * 24 * 60 * 60) 35 36 /* 37 * Convert the X509's expiration time (which is in ASN1_TIME format) 38 * into a time_t value. 39 * There are lots of suggestions on the Internet on how to do this and 40 * they're really, really unsafe. 41 * Adapt those poor solutions to a safe one. 42 */ 43 static time_t 44 X509expires(X509 *x) 45 { 46 ASN1_TIME *atim; 47 struct tm t; 48 unsigned char *str; 49 size_t i = 0; 50 51 atim = X509_get_notAfter(x); 52 str = atim->data; 53 memset(&t, 0, sizeof(t)); 54 55 /* Account for 2 and 4-digit time. */ 56 57 if (atim->type == V_ASN1_UTCTIME) { 58 if (atim->length <= 2) { 59 warnx("invalid ASN1_TIME"); 60 return (time_t)-1; 61 } 62 t.tm_year = (str[0] - '0') * 10 + (str[1] - '0'); 63 if (t.tm_year < 70) 64 t.tm_year += 100; 65 i = 2; 66 } else if (atim->type == V_ASN1_GENERALIZEDTIME) { 67 if (atim->length <= 4) { 68 warnx("invalid ASN1_TIME"); 69 return (time_t)-1; 70 } 71 t.tm_year = (str[0] - '0') * 1000 + (str[1] - '0') * 100 + 72 (str[2] - '0') * 10 + (str[3] - '0'); 73 t.tm_year -= 1900; 74 i = 4; 75 } 76 77 /* Now the post-year parts. */ 78 79 if (atim->length <= (int)i + 10) { 80 warnx("invalid ASN1_TIME"); 81 return (time_t)-1; 82 } 83 84 t.tm_mon = ((str[i + 0] - '0') * 10 + (str[i + 1] - '0')) - 1; 85 t.tm_mday = (str[i + 2] - '0') * 10 + (str[i + 3] - '0'); 86 t.tm_hour = (str[i + 4] - '0') * 10 + (str[i + 5] - '0'); 87 t.tm_min = (str[i + 6] - '0') * 10 + (str[i + 7] - '0'); 88 t.tm_sec = (str[i + 8] - '0') * 10 + (str[i + 9] - '0'); 89 90 return mktime(&t); 91 } 92 93 int 94 revokeproc(int fd, const char *certdir, const char *certfile, int force, 95 int revocate, const char *const *alts, size_t altsz) 96 { 97 char *path = NULL, *der = NULL, *dercp, *der64 = NULL; 98 char *san = NULL, *str, *tok; 99 int rc = 0, cc, i, extsz, ssz, len; 100 size_t *found = NULL; 101 BIO *bio = NULL; 102 FILE *f = NULL; 103 X509 *x = NULL; 104 long lval; 105 enum revokeop op, rop; 106 time_t t; 107 X509_EXTENSION *ex; 108 ASN1_OBJECT *obj; 109 size_t j; 110 111 /* 112 * First try to open the certificate before we drop privileges 113 * and jail ourselves. 114 * We allow "f" to be NULL IFF the cert doesn't exist yet. 115 */ 116 117 if (asprintf(&path, "%s/%s", certdir, certfile) == -1) { 118 warn("asprintf"); 119 goto out; 120 } else if ((f = fopen(path, "r")) == NULL && errno != ENOENT) { 121 warn("%s", path); 122 goto out; 123 } 124 125 /* File-system and sandbox jailing. */ 126 127 ERR_load_crypto_strings(); 128 129 if (pledge("stdio", NULL) == -1) { 130 warn("pledge"); 131 goto out; 132 } 133 134 /* 135 * If we couldn't open the certificate, it doesn't exist so we 136 * haven't submitted it yet, so obviously we can mark that it 137 * has expired and we should renew it. 138 * If we're revoking, however, then that's an error! 139 * Ignore if the reader isn't reading in either case. 140 */ 141 142 if (f == NULL && revocate) { 143 warnx("%s/%s: no certificate found", certdir, certfile); 144 (void)writeop(fd, COMM_REVOKE_RESP, REVOKE_OK); 145 goto out; 146 } else if (f == NULL && !revocate) { 147 if (writeop(fd, COMM_REVOKE_RESP, REVOKE_EXP) >= 0) 148 rc = 1; 149 goto out; 150 } 151 152 if ((x = PEM_read_X509(f, NULL, NULL, NULL)) == NULL) { 153 warnx("PEM_read_X509"); 154 goto out; 155 } 156 157 /* Read out the expiration date. */ 158 159 if ((t = X509expires(x)) == (time_t)-1) { 160 warnx("X509expires"); 161 goto out; 162 } 163 164 /* 165 * Next, the long process to make sure that the SAN entries 166 * listed with the certificate fully cover those passed on the 167 * comamnd line. 168 */ 169 170 extsz = x->cert_info->extensions != NULL ? 171 sk_X509_EXTENSION_num(x->cert_info->extensions) : 0; 172 173 /* Scan til we find the SAN NID. */ 174 175 for (i = 0; i < extsz; i++) { 176 ex = sk_X509_EXTENSION_value(x->cert_info->extensions, i); 177 assert(ex != NULL); 178 obj = X509_EXTENSION_get_object(ex); 179 assert(obj != NULL); 180 if (NID_subject_alt_name != OBJ_obj2nid(obj)) 181 continue; 182 183 if (san != NULL) { 184 warnx("%s/%s: two SAN entries", certdir, certfile); 185 goto out; 186 } 187 188 bio = BIO_new(BIO_s_mem()); 189 if (bio == NULL) { 190 warnx("BIO_new"); 191 goto out; 192 } else if (!X509V3_EXT_print(bio, ex, 0, 0)) { 193 warnx("X509V3_EXT_print"); 194 goto out; 195 } else if ((san = calloc(1, bio->num_write + 1)) == NULL) { 196 warn("calloc"); 197 goto out; 198 } 199 ssz = BIO_read(bio, san, bio->num_write); 200 if (ssz < 0 || (unsigned)ssz != bio->num_write) { 201 warnx("BIO_read"); 202 goto out; 203 } 204 } 205 206 if (san == NULL) { 207 warnx("%s/%s: does not have a SAN entry", certdir, certfile); 208 goto out; 209 } 210 211 /* An array of buckets: the number of entries found. */ 212 213 if ((found = calloc(altsz, sizeof(size_t))) == NULL) { 214 warn("calloc"); 215 goto out; 216 } 217 218 /* 219 * Parse the SAN line. 220 * Make sure that all of the domains are represented only once. 221 */ 222 223 str = san; 224 while ((tok = strsep(&str, ",")) != NULL) { 225 if (*tok == '\0') 226 continue; 227 while (isspace((int)*tok)) 228 tok++; 229 if (strncmp(tok, "DNS:", 4)) 230 continue; 231 tok += 4; 232 for (j = 0; j < altsz; j++) 233 if (strcmp(tok, alts[j]) == 0) 234 break; 235 if (j == altsz) { 236 warnx("%s/%s: unknown SAN entry: %s", 237 certdir, certfile, tok); 238 goto out; 239 } 240 if (found[j]++) { 241 warnx("%s/%s: duplicate SAN entry: %s", 242 certdir, certfile, tok); 243 goto out; 244 } 245 } 246 247 for (j = 0; j < altsz; j++) { 248 if (found[j]) 249 continue; 250 warnx("%s/%s: domain not listed: %s", 251 certdir, certfile, alts[j]); 252 goto out; 253 } 254 255 /* 256 * If we're going to revoke, write the certificate to the 257 * netproc in DER and base64-encoded format. 258 * Then exit: we have nothing left to do. 259 */ 260 261 if (revocate) { 262 dodbg("%s/%s: revocation", certdir, certfile); 263 264 /* 265 * First, tell netproc we're online. 266 * If they're down, then just exit without warning. 267 */ 268 269 cc = writeop(fd, COMM_REVOKE_RESP, REVOKE_EXP); 270 if (cc == 0) 271 rc = 1; 272 if (cc <= 0) 273 goto out; 274 275 if ((len = i2d_X509(x, NULL)) < 0) { 276 warnx("i2d_X509"); 277 goto out; 278 } else if ((der = dercp = malloc(len)) == NULL) { 279 warn("malloc"); 280 goto out; 281 } else if (len != i2d_X509(x, (u_char **)&dercp)) { 282 warnx("i2d_X509"); 283 goto out; 284 } else if ((der64 = base64buf_url(der, len)) == NULL) { 285 warnx("base64buf_url"); 286 goto out; 287 } else if (writestr(fd, COMM_CSR, der64) >= 0) 288 rc = 1; 289 290 goto out; 291 } 292 293 rop = time(NULL) >= (t - RENEW_ALLOW) ? REVOKE_EXP : REVOKE_OK; 294 295 if (rop == REVOKE_EXP) 296 dodbg("%s/%s: certificate renewable: %lld days left", 297 certdir, certfile, 298 (long long)(t - time(NULL)) / 24 / 60 / 60); 299 else 300 dodbg("%s/%s: certificate valid: %lld days left", 301 certdir, certfile, 302 (long long)(t - time(NULL)) / 24 / 60 / 60); 303 304 if (rop == REVOKE_OK && force) { 305 warnx("%s/%s: forcing renewal", certdir, certfile); 306 rop = REVOKE_EXP; 307 } 308 309 /* 310 * We can re-submit it given RENEW_ALLOW time before. 311 * If netproc is down, just exit. 312 */ 313 314 if ((cc = writeop(fd, COMM_REVOKE_RESP, rop)) == 0) 315 rc = 1; 316 if (cc <= 0) 317 goto out; 318 319 op = REVOKE__MAX; 320 if ((lval = readop(fd, COMM_REVOKE_OP)) == 0) 321 op = REVOKE_STOP; 322 else if (lval == REVOKE_CHECK) 323 op = lval; 324 325 if (op == REVOKE__MAX) { 326 warnx("unknown operation from netproc"); 327 goto out; 328 } else if (op == REVOKE_STOP) { 329 rc = 1; 330 goto out; 331 } 332 333 rc = 1; 334 out: 335 close(fd); 336 if (f != NULL) 337 fclose(f); 338 if (x != NULL) 339 X509_free(x); 340 if (bio != NULL) 341 BIO_free(bio); 342 free(san); 343 free(path); 344 free(der); 345 free(found); 346 free(der64); 347 ERR_print_errors_fp(stderr); 348 ERR_free_strings(); 349 return rc; 350 } 351