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