xref: /openbsd/usr.sbin/acme-client/keyproc.c (revision 5dea098c)
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
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_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