#include <sys/socket.h>
#include <arpa/inet.h>
#include <assert.h>
#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <vis.h>
#include <openssl/pem.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/err.h>
#include "extern.h"
static time_t
X509notafter(X509 *x)
{
ASN1_TIME *atim;
struct tm t;
if ((atim = X509_getm_notAfter(x)) == NULL)
return -1;
memset(&t, 0, sizeof(t));
if (!ASN1_TIME_to_tm(atim, &t))
return -1;
return timegm(&t);
}
static time_t
X509notbefore(X509 *x)
{
ASN1_TIME *atim;
struct tm t;
if ((atim = X509_getm_notBefore(x)) == NULL)
return -1;
memset(&t, 0, sizeof(t));
if (!ASN1_TIME_to_tm(atim, &t))
return -1;
return timegm(&t);
}
int
revokeproc(int fd, const char *certfile, int force,
int revocate, struct domain_c *domain)
{
GENERAL_NAMES *sans = NULL;
char *der = NULL, *dercp, *der64 = NULL;
int rc = 0, cc, sanidx, len, j, k;
int *found_altnames = NULL;
FILE *f = NULL;
X509 *x = NULL;
long lval;
enum revokeop op, rop;
time_t notafter, notbefore, cert_validity;
time_t remaining_validity, renew_allow;
if ((f = fopen(certfile, "r")) == NULL && errno != ENOENT) {
warn("%s", certfile);
goto out;
}
ERR_load_crypto_strings();
if (pledge("stdio", NULL) == -1) {
warn("pledge");
goto out;
}
if (f == NULL && revocate) {
warnx("%s: no certificate found", certfile);
(void)writeop(fd, COMM_REVOKE_RESP, REVOKE_OK);
goto out;
} else if (f == NULL && !revocate) {
if (writeop(fd, COMM_REVOKE_RESP, REVOKE_EXP) >= 0)
rc = 1;
goto out;
}
if ((x = PEM_read_X509(f, NULL, NULL, NULL)) == NULL) {
warnx("PEM_read_X509");
goto out;
}
if (X509_check_purpose(x, -1, -1) <= 0) {
warnx("%s: invalid X509v3 extensions", certfile);
goto out;
}
if ((notafter = X509notafter(x)) == -1) {
warnx("X509notafter");
goto out;
}
if ((notbefore = X509notbefore(x)) == -1) {
warnx("X509notbefore");
goto out;
}
sans = X509_get_ext_d2i(x, NID_subject_alt_name, NULL, NULL);
if (sans == NULL) {
warnx("%s: does not have a SAN entry", certfile);
if (revocate)
goto out;
force = 2;
}
if ((found_altnames = (int *)calloc(domain->altname_count,
sizeof(int))) == NULL) {
warn("calloc");
goto out;
}
for (sanidx = 0; sanidx < sk_GENERAL_NAME_num(sans); sanidx++) {
GENERAL_NAME *gen_name;
char *name_buf = NULL;
int name_len;
struct altname_c *ac;
gen_name = sk_GENERAL_NAME_value(sans, sanidx);
assert(gen_name != NULL);
if (gen_name->type == GEN_IPADD) {
char ip_buf[INET6_ADDRSTRLEN];
const char *ip;
name_len = ASN1_STRING_length(gen_name->d.iPAddress);
switch (name_len) {
case 4:
ip = inet_ntop(AF_INET,
ASN1_STRING_get0_data(gen_name->d.iPAddress),
ip_buf, INET6_ADDRSTRLEN);
break;
case 16:
ip = inet_ntop(AF_INET6,
ASN1_STRING_get0_data(gen_name->d.iPAddress),
ip_buf, INET6_ADDRSTRLEN);
break;
default:
ip = NULL;
break;
}
if (ip == NULL) {
warnx("invalid IP address");
continue;
}
name_len = asprintf(&name_buf, "%s", ip);
} else if (gen_name->type == GEN_DNS) {
name_len = ASN1_STRING_length(gen_name->d.dNSName);
name_len = asprintf(&name_buf, "%.*s",
name_len,
ASN1_STRING_get0_data(gen_name->d.dNSName));
} else
continue;
if (name_len == -1) {
warn("asprintf");
continue;
}
j = 0;
TAILQ_FOREACH(ac, &domain->altname_list, entry) {
if (strcmp(name_buf, ac->domain) == 0) {
found_altnames[j]++;
break;
}
j++;
}
if (j >= domain->altname_count) {
if (revocate) {
char *visbuf;
visbuf = calloc(4, name_len + 1);
if (visbuf == NULL) {
warn("%s: unexpected SAN in "
"certificate", certfile);
free(name_buf);
goto out;
}
strvisx(visbuf, name_buf, name_len, VIS_SAFE);
warnx("%s: unexpected SAN entry in "
"certificate: %s", certfile, visbuf);
free(visbuf);
free(name_buf);
goto out;
}
force = 2;
continue;
}
if (found_altnames[j] > 1) {
if (revocate) {
char *visbuf;
visbuf = calloc(4, name_len + 1);
if (visbuf == NULL) {
warn("%s: duplicate SAN in "
"certificate", certfile);
free(name_buf);
goto out;
}
warnx("%s: duplicate SAN entry in "
"certificate: %s", certfile, visbuf);
free(name_buf);
free(visbuf);
goto out;
}
force = 2;
}
free(name_buf);
}
for (j = 0; j < domain->altname_count; j++) {
struct altname_c *ac;
if (found_altnames[j])
continue;
if (revocate) {
k = 0;
TAILQ_FOREACH(ac, &domain->altname_list, entry) {
if (j == k)
break;
k++;
}
warnx("%s: domain not listed: %s", certfile,
ac->domain);
goto out;
}
force = 2;
}
if (revocate) {
dodbg("%s: revocation", certfile);
cc = writeop(fd, COMM_REVOKE_RESP, REVOKE_EXP);
if (cc == 0)
rc = 1;
if (cc <= 0)
goto out;
if ((len = i2d_X509(x, NULL)) < 0) {
warnx("i2d_X509");
goto out;
} else if ((der = dercp = malloc(len)) == NULL) {
warn("malloc");
goto out;
} else if (len != i2d_X509(x, (u_char **)&dercp)) {
warnx("i2d_X509");
goto out;
} else if ((der64 = base64buf_url(der, len)) == NULL) {
warnx("base64buf_url");
goto out;
} else if (writestr(fd, COMM_CSR, der64) >= 0)
rc = 1;
goto out;
}
cert_validity = notafter - notbefore;
if (cert_validity < 0) {
warnx("Invalid cert, expire time before inception time");
rc = -1;
goto out;
}
if (cert_validity > 10 * 24 * 60 * 60)
renew_allow = cert_validity / 3;
else
renew_allow = cert_validity / 2;
if (renew_allow < 3 * 24 * 60 * 60)
renew_allow = 3 * 24 * 60 * 60;
remaining_validity = notafter - time(NULL);
if (remaining_validity < renew_allow)
rop = REVOKE_EXP;
else
rop = REVOKE_OK;
if (rop == REVOKE_EXP)
dodbg("%s: certificate renewable: %lld days left",
certfile, (long long)(remaining_validity / 24 / 60 / 60));
else
dodbg("%s: certificate valid: %lld days left",
certfile, (long long)(remaining_validity / 24 / 60 / 60));
if (rop == REVOKE_OK && force) {
warnx("%s: %sforcing renewal", certfile,
force == 2 ? "domain list changed, " : "");
rop = REVOKE_EXP;
}
if ((cc = writeop(fd, COMM_REVOKE_RESP, rop)) == 0)
rc = 1;
if (cc <= 0)
goto out;
op = REVOKE__MAX;
if ((lval = readop(fd, COMM_REVOKE_OP)) == 0)
op = REVOKE_STOP;
else if (lval == REVOKE_CHECK)
op = lval;
if (op == REVOKE__MAX) {
warnx("unknown operation from netproc");
goto out;
} else if (op == REVOKE_STOP) {
rc = 1;
goto out;
}
rc = 1;
out:
close(fd);
if (f != NULL)
fclose(f);
X509_free(x);
GENERAL_NAMES_free(sans);
free(der);
free(found_altnames);
free(der64);
ERR_print_errors_fp(stderr);
ERR_free_strings();
return rc;
}