1*df5860abStb /* $Id: keyproc.c,v 1.18 2022/08/28 18:30:29 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 <sys/stat.h>
19de579d12Sflorian
20de579d12Sflorian #include <err.h>
21de579d12Sflorian #include <stdio.h>
22de579d12Sflorian #include <stdlib.h>
23de579d12Sflorian #include <string.h>
24de579d12Sflorian #include <unistd.h>
25de579d12Sflorian
26de579d12Sflorian #include <openssl/pem.h>
27de579d12Sflorian #include <openssl/err.h>
28de579d12Sflorian #include <openssl/rand.h>
29de579d12Sflorian #include <openssl/x509.h>
30de579d12Sflorian #include <openssl/x509v3.h>
31de579d12Sflorian
32de579d12Sflorian #include "extern.h"
333e86e78bSgilles #include "key.h"
34de579d12Sflorian
35de579d12Sflorian /*
36de579d12Sflorian * This was lifted more or less directly from demos/x509/mkreq.c of the
37de579d12Sflorian * OpenSSL source code.
38de579d12Sflorian */
39de579d12Sflorian static int
add_ext(STACK_OF (X509_EXTENSION)* sk,int nid,const char * value)40de579d12Sflorian add_ext(STACK_OF(X509_EXTENSION) *sk, int nid, const char *value)
41de579d12Sflorian {
42de579d12Sflorian X509_EXTENSION *ex;
43de579d12Sflorian char *cp;
44de579d12Sflorian
45de579d12Sflorian /*
46de579d12Sflorian * XXX: I don't like this at all.
47de579d12Sflorian * There's no documentation for X509V3_EXT_conf_nid, so I'm not
48de579d12Sflorian * sure if the "value" parameter is ever written to, touched,
49de579d12Sflorian * etc.
50de579d12Sflorian * The 'official' examples suggest not (they use a string
51de579d12Sflorian * literal as the input), but to be safe, I'm doing an
52de579d12Sflorian * allocation here and just letting it go.
53de579d12Sflorian * This leaks memory, but bounded to the number of SANs.
54de579d12Sflorian */
55de579d12Sflorian
564de82fa3Sderaadt if ((cp = strdup(value)) == NULL) {
57de579d12Sflorian warn("strdup");
58de579d12Sflorian return (0);
59de579d12Sflorian }
60de579d12Sflorian ex = X509V3_EXT_conf_nid(NULL, NULL, nid, cp);
614de82fa3Sderaadt if (ex == NULL) {
62de579d12Sflorian warnx("X509V3_EXT_conf_nid");
63de579d12Sflorian free(cp);
64de579d12Sflorian return (0);
65de579d12Sflorian }
66de579d12Sflorian sk_X509_EXTENSION_push(sk, ex);
67de579d12Sflorian return (1);
68de579d12Sflorian }
69de579d12Sflorian
70de579d12Sflorian /*
71de579d12Sflorian * Create an X509 certificate from the private key we have on file.
72de579d12Sflorian * To do this, we first open the key file, then jail ourselves.
73de579d12Sflorian * We then use the crypto library to create the certificate within the
74de579d12Sflorian * jail and, on success, ship it to "netsock" as an X509 request.
75de579d12Sflorian */
76de579d12Sflorian int
keyproc(int netsock,const char * keyfile,const char ** alts,size_t altsz,enum keytype keytype)7765a104faSflorian keyproc(int netsock, const char *keyfile, const char **alts, size_t altsz,
7865a104faSflorian enum keytype keytype)
79de579d12Sflorian {
807bce6888Sderaadt char *der64 = NULL, *der = NULL, *dercp;
817bce6888Sderaadt char *sans = NULL, *san = NULL;
82de579d12Sflorian FILE *f;
83de579d12Sflorian size_t i, sansz;
84de579d12Sflorian void *pp;
857bce6888Sderaadt EVP_PKEY *pkey = NULL;
867bce6888Sderaadt X509_REQ *x = NULL;
877bce6888Sderaadt X509_NAME *name = NULL;
882570ecd0Sflorian int len, rc = 0, cc, nid, newkey = 0;
89de579d12Sflorian mode_t prev;
907bce6888Sderaadt STACK_OF(X509_EXTENSION) *exts = NULL;
91de579d12Sflorian
92de579d12Sflorian /*
93de579d12Sflorian * First, open our private key file read-only or write-only if
94de579d12Sflorian * we're creating from scratch.
95de579d12Sflorian * Set our umask to be maximally restrictive.
96de579d12Sflorian */
97de579d12Sflorian
98de579d12Sflorian prev = umask((S_IWUSR | S_IXUSR) | S_IRWXG | S_IRWXO);
992570ecd0Sflorian if ((f = fopen(keyfile, "r")) == NULL && errno == ENOENT) {
1002570ecd0Sflorian f = fopen(keyfile, "wx");
1012570ecd0Sflorian newkey = 1;
1022570ecd0Sflorian }
103de579d12Sflorian umask(prev);
104de579d12Sflorian
1054de82fa3Sderaadt if (f == NULL) {
106de579d12Sflorian warn("%s", keyfile);
107de579d12Sflorian goto out;
108de579d12Sflorian }
109de579d12Sflorian
110de579d12Sflorian /* File-system, user, and sandbox jail. */
111de579d12Sflorian
112de579d12Sflorian ERR_load_crypto_strings();
113de579d12Sflorian
114ec0d8c8bSderaadt if (pledge("stdio", NULL) == -1) {
115ec0d8c8bSderaadt warn("pledge");
116de579d12Sflorian goto out;
117ec0d8c8bSderaadt }
118de579d12Sflorian
119de579d12Sflorian if (newkey) {
12065a104faSflorian switch (keytype) {
12165a104faSflorian case KT_ECDSA:
1223e86e78bSgilles if ((pkey = ec_key_create(f, keyfile)) == NULL)
1233e86e78bSgilles goto out;
1243e86e78bSgilles dodbg("%s: generated ECDSA domain key", keyfile);
12565a104faSflorian break;
12665a104faSflorian case KT_RSA:
1274de82fa3Sderaadt if ((pkey = rsa_key_create(f, keyfile)) == NULL)
128de579d12Sflorian goto out;
129de579d12Sflorian dodbg("%s: generated RSA domain key", keyfile);
13065a104faSflorian break;
1313e86e78bSgilles }
132de579d12Sflorian } else {
1333e86e78bSgilles if ((pkey = key_load(f, keyfile)) == NULL)
134de579d12Sflorian goto out;
135ffbdc72dSflorian /* XXX check if domain key type equals configured key type */
1363e86e78bSgilles doddbg("%s: loaded domain key", keyfile);
137de579d12Sflorian }
138de579d12Sflorian
139de579d12Sflorian fclose(f);
140de579d12Sflorian f = NULL;
141de579d12Sflorian
142de579d12Sflorian /*
143de579d12Sflorian * Generate our certificate from the EVP public key.
144de579d12Sflorian * Then set it as the X509 requester's key.
145de579d12Sflorian */
146de579d12Sflorian
1474de82fa3Sderaadt if ((x = X509_REQ_new()) == NULL) {
148*df5860abStb warnx("X509_REQ_new");
149de579d12Sflorian goto out;
150a4dae718Sjsing } else if (!X509_REQ_set_version(x, 0)) {
151*df5860abStb warnx("X509_REQ_set_version");
152a4dae718Sjsing goto out;
153de579d12Sflorian } else if (!X509_REQ_set_pubkey(x, pkey)) {
154*df5860abStb warnx("X509_REQ_set_pubkey");
155de579d12Sflorian goto out;
156de579d12Sflorian }
157de579d12Sflorian
158de579d12Sflorian /* Now specify the common name that we'll request. */
159de579d12Sflorian
1604de82fa3Sderaadt if ((name = X509_NAME_new()) == NULL) {
161de579d12Sflorian warnx("X509_NAME_new");
162de579d12Sflorian goto out;
163de579d12Sflorian } else if (!X509_NAME_add_entry_by_txt(name, "CN",
164de579d12Sflorian MBSTRING_ASC, (u_char *)alts[0], -1, -1, 0)) {
165de579d12Sflorian warnx("X509_NAME_add_entry_by_txt: CN=%s", alts[0]);
166de579d12Sflorian goto out;
167de579d12Sflorian } else if (!X509_REQ_set_subject_name(x, name)) {
168de579d12Sflorian warnx("X509_req_set_issuer_name");
169de579d12Sflorian goto out;
170de579d12Sflorian }
171de579d12Sflorian
172de579d12Sflorian /*
173de579d12Sflorian * Now add the SAN extensions.
174de579d12Sflorian * This was lifted more or less directly from demos/x509/mkreq.c
175de579d12Sflorian * of the OpenSSL source code.
176de579d12Sflorian * (The zeroth altname is the domain name.)
177de579d12Sflorian * TODO: is this the best way of doing this?
178de579d12Sflorian */
179de579d12Sflorian
180de579d12Sflorian nid = NID_subject_alt_name;
1814de82fa3Sderaadt if ((exts = sk_X509_EXTENSION_new_null()) == NULL) {
182de579d12Sflorian warnx("sk_X509_EXTENSION_new_null");
183de579d12Sflorian goto out;
184de579d12Sflorian }
185de579d12Sflorian /* Initialise to empty string. */
1864de82fa3Sderaadt if ((sans = strdup("")) == NULL) {
187de579d12Sflorian warn("strdup");
188de579d12Sflorian goto out;
189de579d12Sflorian }
190de579d12Sflorian sansz = strlen(sans) + 1;
191de579d12Sflorian
192de579d12Sflorian /*
193de579d12Sflorian * For each SAN entry, append it to the string.
194de579d12Sflorian * We need a single SAN entry for all of the SAN
195de579d12Sflorian * domains: NOT an entry per domain!
196de579d12Sflorian */
197de579d12Sflorian
19871c23e2aSsthen for (i = 0; i < altsz; i++) {
199de579d12Sflorian cc = asprintf(&san, "%sDNS:%s",
20071c23e2aSsthen i ? "," : "", alts[i]);
2014de82fa3Sderaadt if (cc == -1) {
202de579d12Sflorian warn("asprintf");
203de579d12Sflorian goto out;
204de579d12Sflorian }
205425fd4bbSderaadt pp = recallocarray(sans, sansz, sansz + strlen(san), 1);
2064de82fa3Sderaadt if (pp == NULL) {
207425fd4bbSderaadt warn("recallocarray");
208de579d12Sflorian goto out;
209de579d12Sflorian }
210de579d12Sflorian sans = pp;
211de579d12Sflorian sansz += strlen(san);
212de579d12Sflorian strlcat(sans, san, sansz);
213de579d12Sflorian free(san);
214de579d12Sflorian san = NULL;
215de579d12Sflorian }
216de579d12Sflorian
217de579d12Sflorian if (!add_ext(exts, nid, sans)) {
218de579d12Sflorian warnx("add_ext");
219de579d12Sflorian goto out;
220de579d12Sflorian } else if (!X509_REQ_add_extensions(x, exts)) {
221de579d12Sflorian warnx("X509_REQ_add_extensions");
222de579d12Sflorian goto out;
223de579d12Sflorian }
22455599e87Sderaadt sk_X509_EXTENSION_pop_free(exts, X509_EXTENSION_free);
225de579d12Sflorian
226de579d12Sflorian /* Sign the X509 request using SHA256. */
227de579d12Sflorian
228de579d12Sflorian if (!X509_REQ_sign(x, pkey, EVP_sha256())) {
229de579d12Sflorian warnx("X509_sign");
230de579d12Sflorian goto out;
231de579d12Sflorian }
232de579d12Sflorian
233de579d12Sflorian /* Now, serialise to DER, then base64. */
234de579d12Sflorian
235de579d12Sflorian if ((len = i2d_X509_REQ(x, NULL)) < 0) {
236*df5860abStb warnx("i2d_X509_REQ");
237de579d12Sflorian goto out;
2384de82fa3Sderaadt } else if ((der = dercp = malloc(len)) == NULL) {
239de579d12Sflorian warn("malloc");
240de579d12Sflorian goto out;
241de579d12Sflorian } else if (len != i2d_X509_REQ(x, (u_char **)&dercp)) {
242*df5860abStb warnx("i2d_X509_REQ");
243de579d12Sflorian goto out;
2444de82fa3Sderaadt } else if ((der64 = base64buf_url(der, len)) == NULL) {
245de579d12Sflorian warnx("base64buf_url");
246de579d12Sflorian goto out;
247de579d12Sflorian }
248de579d12Sflorian
249de579d12Sflorian /*
250de579d12Sflorian * Write that we're ready, then write.
251de579d12Sflorian * We ignore reader-closed failure, as we're just going to roll
252de579d12Sflorian * into the exit case anyway.
253de579d12Sflorian */
254de579d12Sflorian
255de579d12Sflorian if (writeop(netsock, COMM_KEY_STAT, KEY_READY) < 0)
256de579d12Sflorian goto out;
257de579d12Sflorian if (writestr(netsock, COMM_CERT, der64) < 0)
258de579d12Sflorian goto out;
259de579d12Sflorian
260de579d12Sflorian rc = 1;
261de579d12Sflorian out:
262de579d12Sflorian close(netsock);
2634de82fa3Sderaadt if (f != NULL)
264de579d12Sflorian fclose(f);
265de579d12Sflorian free(der);
266de579d12Sflorian free(der64);
267de579d12Sflorian free(sans);
268de579d12Sflorian free(san);
269de579d12Sflorian X509_REQ_free(x);
270de579d12Sflorian X509_NAME_free(name);
271de579d12Sflorian EVP_PKEY_free(pkey);
272de579d12Sflorian ERR_print_errors_fp(stderr);
273de579d12Sflorian ERR_free_strings();
274e1bd26f6Stb return rc;
275de579d12Sflorian }
276