xref: /openbsd-src/usr.sbin/acme-client/keyproc.c (revision 24bb5fcea3ed904bc467217bdaadb5dfc618d5bf)
1 /*	$Id: keyproc.c,v 1.15 2019/06/15 16:16:31 florian 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
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
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_new");
149 		goto out;
150 	} else if (!X509_REQ_set_pubkey(x, pkey)) {
151 		warnx("X509_set_pubkey");
152 		goto out;
153 	}
154 
155 	/* Now specify the common name that we'll request. */
156 
157 	if ((name = X509_NAME_new()) == NULL) {
158 		warnx("X509_NAME_new");
159 		goto out;
160 	} else if (!X509_NAME_add_entry_by_txt(name, "CN",
161 		MBSTRING_ASC, (u_char *)alts[0], -1, -1, 0)) {
162 		warnx("X509_NAME_add_entry_by_txt: CN=%s", alts[0]);
163 		goto out;
164 	} else if (!X509_REQ_set_subject_name(x, name)) {
165 		warnx("X509_req_set_issuer_name");
166 		goto out;
167 	}
168 
169 	/*
170 	 * Now add the SAN extensions.
171 	 * This was lifted more or less directly from demos/x509/mkreq.c
172 	 * of the OpenSSL source code.
173 	 * (The zeroth altname is the domain name.)
174 	 * TODO: is this the best way of doing this?
175 	 */
176 
177 	if (altsz > 1) {
178 		nid = NID_subject_alt_name;
179 		if ((exts = sk_X509_EXTENSION_new_null()) == NULL) {
180 			warnx("sk_X509_EXTENSION_new_null");
181 			goto out;
182 		}
183 		/* Initialise to empty string. */
184 		if ((sans = strdup("")) == NULL) {
185 			warn("strdup");
186 			goto out;
187 		}
188 		sansz = strlen(sans) + 1;
189 
190 		/*
191 		 * For each SAN entry, append it to the string.
192 		 * We need a single SAN entry for all of the SAN
193 		 * domains: NOT an entry per domain!
194 		 */
195 
196 		for (i = 1; i < altsz; i++) {
197 			cc = asprintf(&san, "%sDNS:%s",
198 			    i > 1 ? "," : "", alts[i]);
199 			if (cc == -1) {
200 				warn("asprintf");
201 				goto out;
202 			}
203 			pp = recallocarray(sans, sansz, sansz + strlen(san), 1);
204 			if (pp == NULL) {
205 				warn("recallocarray");
206 				goto out;
207 			}
208 			sans = pp;
209 			sansz += strlen(san);
210 			strlcat(sans, san, sansz);
211 			free(san);
212 			san = NULL;
213 		}
214 
215 		if (!add_ext(exts, nid, sans)) {
216 			warnx("add_ext");
217 			goto out;
218 		} else if (!X509_REQ_add_extensions(x, exts)) {
219 			warnx("X509_REQ_add_extensions");
220 			goto out;
221 		}
222 		sk_X509_EXTENSION_pop_free(exts, X509_EXTENSION_free);
223 	}
224 
225 	/* Sign the X509 request using SHA256. */
226 
227 	if (!X509_REQ_sign(x, pkey, EVP_sha256())) {
228 		warnx("X509_sign");
229 		goto out;
230 	}
231 
232 	/* Now, serialise to DER, then base64. */
233 
234 	if ((len = i2d_X509_REQ(x, NULL)) < 0) {
235 		warnx("i2d_X509");
236 		goto out;
237 	} else if ((der = dercp = malloc(len)) == NULL) {
238 		warn("malloc");
239 		goto out;
240 	} else if (len != i2d_X509_REQ(x, (u_char **)&dercp)) {
241 		warnx("i2d_X509");
242 		goto out;
243 	} else if ((der64 = base64buf_url(der, len)) == NULL) {
244 		warnx("base64buf_url");
245 		goto out;
246 	}
247 
248 	/*
249 	 * Write that we're ready, then write.
250 	 * We ignore reader-closed failure, as we're just going to roll
251 	 * into the exit case anyway.
252 	 */
253 
254 	if (writeop(netsock, COMM_KEY_STAT, KEY_READY) < 0)
255 		goto out;
256 	if (writestr(netsock, COMM_CERT, der64) < 0)
257 		goto out;
258 
259 	rc = 1;
260 out:
261 	close(netsock);
262 	if (f != NULL)
263 		fclose(f);
264 	free(der);
265 	free(der64);
266 	free(sans);
267 	free(san);
268 	X509_REQ_free(x);
269 	X509_NAME_free(name);
270 	EVP_PKEY_free(pkey);
271 	ERR_print_errors_fp(stderr);
272 	ERR_free_strings();
273 	return rc;
274 }
275