#include <sys/stat.h>
#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <openssl/pem.h>
#include <openssl/err.h>
#include <openssl/rand.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include "extern.h"
#include "key.h"
static int
add_ext(STACK_OF(X509_EXTENSION) *sk, int nid, const char *value)
{
X509_EXTENSION *ex;
char *cp;
if ((cp = strdup(value)) == NULL) {
warn("strdup");
return (0);
}
ex = X509V3_EXT_conf_nid(NULL, NULL, nid, cp);
if (ex == NULL) {
warnx("X509V3_EXT_conf_nid");
free(cp);
return (0);
}
sk_X509_EXTENSION_push(sk, ex);
return (1);
}
int
keyproc(int netsock, struct domain_c *domain)
{
char *der64 = NULL, *der = NULL, *dercp;
char *sans = NULL, *san = NULL;
FILE *f;
size_t sansz;
void *pp;
EVP_PKEY *pkey = NULL;
X509_REQ *x = NULL;
int len, rc = 0, cc, nid, newkey = 0, first;
mode_t prev;
STACK_OF(X509_EXTENSION) *exts = NULL;
struct altname_c *ac;
const char *keyfile = domain->key;
prev = umask((S_IWUSR | S_IXUSR) | S_IRWXG | S_IRWXO);
if ((f = fopen(keyfile, "r")) == NULL && errno == ENOENT) {
f = fopen(keyfile, "wx");
newkey = 1;
}
umask(prev);
if (f == NULL) {
warn("%s", keyfile);
goto out;
}
ERR_load_crypto_strings();
if (pledge("stdio", NULL) == -1) {
warn("pledge");
goto out;
}
if (newkey) {
switch (domain->keytype) {
case KT_ECDSA:
if ((pkey = ec_key_create(f, keyfile)) == NULL)
goto out;
dodbg("%s: generated ECDSA domain key", keyfile);
break;
case KT_RSA:
if ((pkey = rsa_key_create(f, keyfile)) == NULL)
goto out;
dodbg("%s: generated RSA domain key", keyfile);
break;
}
} else {
if ((pkey = key_load(f, keyfile)) == NULL)
goto out;
doddbg("%s: loaded domain key", keyfile);
}
fclose(f);
f = NULL;
if ((x = X509_REQ_new()) == NULL) {
warnx("X509_REQ_new");
goto out;
} else if (!X509_REQ_set_version(x, 0)) {
warnx("X509_REQ_set_version");
goto out;
} else if (!X509_REQ_set_pubkey(x, pkey)) {
warnx("X509_REQ_set_pubkey");
goto out;
}
nid = NID_subject_alt_name;
if ((exts = sk_X509_EXTENSION_new_null()) == NULL) {
warnx("sk_X509_EXTENSION_new_null");
goto out;
}
if ((sans = strdup("")) == NULL) {
warn("strdup");
goto out;
}
sansz = strlen(sans) + 1;
first = 1;
TAILQ_FOREACH(ac, &domain->altname_list, entry) {
switch (ac->idtype) {
case ID_DNS:
cc = asprintf(&san, "%sDNS:%s", first ? "" : ",",
ac->domain);
break;
case ID_IP:
cc = asprintf(&san, "%sIP:%s", first ? "" : ",",
ac->domain);
break;
}
first = 0;
if (cc == -1) {
warn("asprintf");
goto out;
}
pp = recallocarray(sans, sansz, sansz + strlen(san), 1);
if (pp == NULL) {
warn("recallocarray");
goto out;
}
sans = pp;
sansz += strlen(san);
strlcat(sans, san, sansz);
free(san);
san = NULL;
}
if (!add_ext(exts, nid, sans)) {
warnx("add_ext");
goto out;
} else if (!X509_REQ_add_extensions(x, exts)) {
warnx("X509_REQ_add_extensions");
goto out;
}
sk_X509_EXTENSION_pop_free(exts, X509_EXTENSION_free);
if (!X509_REQ_sign(x, pkey, EVP_sha256())) {
warnx("X509_sign");
goto out;
}
if ((len = i2d_X509_REQ(x, NULL)) < 0) {
warnx("i2d_X509_REQ");
goto out;
} else if ((der = dercp = malloc(len)) == NULL) {
warn("malloc");
goto out;
} else if (len != i2d_X509_REQ(x, (u_char **)&dercp)) {
warnx("i2d_X509_REQ");
goto out;
} else if ((der64 = base64buf_url(der, len)) == NULL) {
warnx("base64buf_url");
goto out;
}
if (writeop(netsock, COMM_KEY_STAT, KEY_READY) < 0)
goto out;
if (writestr(netsock, COMM_CERT, der64) < 0)
goto out;
rc = 1;
out:
close(netsock);
if (f != NULL)
fclose(f);
free(der);
free(der64);
free(sans);
free(san);
X509_REQ_free(x);
EVP_PKEY_free(pkey);
ERR_print_errors_fp(stderr);
ERR_free_strings();
return rc;
}