xref: /openbsd/usr.sbin/acme-client/keyproc.c (revision fc61954a)
1 /*	$Id: keyproc.c,v 1.7 2016/09/13 17:13:37 deraadt 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 "rsa.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 (NULL == (cp = strdup(value))) {
57 		warn("strdup");
58 		return (0);
59 	}
60 	ex = X509V3_EXT_conf_nid(NULL, NULL, nid, cp);
61 	if (NULL == ex) {
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,
78     const char **alts, size_t altsz, int newkey)
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;
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 	f = fopen(keyfile, newkey ? "wx" : "r");
100 	umask(prev);
101 
102 	if (NULL == f) {
103 		warn("%s", keyfile);
104 		goto out;
105 	}
106 
107 	/* File-system, user, and sandbox jail. */
108 
109 	ERR_load_crypto_strings();
110 
111 	if (pledge("stdio", NULL) == -1) {
112 		warn("pledge");
113 		goto out;
114 	}
115 
116 	if (newkey) {
117 		if (NULL == (pkey = rsa_key_create(f, keyfile)))
118 			goto out;
119 		dodbg("%s: generated RSA domain key", keyfile);
120 	} else {
121 		if (NULL == (pkey = rsa_key_load(f, keyfile)))
122 			goto out;
123 		doddbg("%s: loaded RSA domain key", keyfile);
124 	}
125 
126 	fclose(f);
127 	f = NULL;
128 
129 	/*
130 	 * Generate our certificate from the EVP public key.
131 	 * Then set it as the X509 requester's key.
132 	 */
133 
134 	if (NULL == (x = X509_REQ_new())) {
135 		warnx("X509_new");
136 		goto out;
137 	} else if (!X509_REQ_set_pubkey(x, pkey)) {
138 		warnx("X509_set_pubkey");
139 		goto out;
140 	}
141 
142 	/* Now specify the common name that we'll request. */
143 
144 	if (NULL == (name = X509_NAME_new())) {
145 		warnx("X509_NAME_new");
146 		goto out;
147 	} else if (!X509_NAME_add_entry_by_txt(name, "CN",
148 		MBSTRING_ASC, (u_char *)alts[0], -1, -1, 0)) {
149 		warnx("X509_NAME_add_entry_by_txt: CN=%s", alts[0]);
150 		goto out;
151 	} else if (!X509_REQ_set_subject_name(x, name)) {
152 		warnx("X509_req_set_issuer_name");
153 		goto out;
154 	}
155 
156 	/*
157 	 * Now add the SAN extensions.
158 	 * This was lifted more or less directly from demos/x509/mkreq.c
159 	 * of the OpenSSL source code.
160 	 * (The zeroth altname is the domain name.)
161 	 * TODO: is this the best way of doing this?
162 	 */
163 
164 	if (altsz > 1) {
165 		nid = NID_subject_alt_name;
166 		if (NULL == (exts = sk_X509_EXTENSION_new_null())) {
167 			warnx("sk_X509_EXTENSION_new_null");
168 			goto out;
169 		}
170 		/* Initialise to empty string. */
171 		if (NULL == (sans = strdup(""))) {
172 			warn("strdup");
173 			goto out;
174 		}
175 		sansz = strlen(sans) + 1;
176 
177 		/*
178 		 * For each SAN entry, append it to the string.
179 		 * We need a single SAN entry for all of the SAN
180 		 * domains: NOT an entry per domain!
181 		 */
182 
183 		for (i = 1; i < altsz; i++) {
184 			cc = asprintf(&san, "%sDNS:%s",
185 			    i > 1 ? "," : "", alts[i]);
186 			if (-1 == cc) {
187 				warn("asprintf");
188 				goto out;
189 			}
190 			pp = realloc(sans, sansz + strlen(san));
191 			if (NULL == pp) {
192 				warn("realloc");
193 				goto out;
194 			}
195 			sans = pp;
196 			sansz += strlen(san);
197 			strlcat(sans, san, sansz);
198 			free(san);
199 			san = NULL;
200 		}
201 
202 		if (!add_ext(exts, nid, sans)) {
203 			warnx("add_ext");
204 			goto out;
205 		} else if (!X509_REQ_add_extensions(x, exts)) {
206 			warnx("X509_REQ_add_extensions");
207 			goto out;
208 		}
209 		sk_X509_EXTENSION_pop_free(exts, X509_EXTENSION_free);
210 	}
211 
212 	/* Sign the X509 request using SHA256. */
213 
214 	if (!X509_REQ_sign(x, pkey, EVP_sha256())) {
215 		warnx("X509_sign");
216 		goto out;
217 	}
218 
219 	/* Now, serialise to DER, then base64. */
220 
221 	if ((len = i2d_X509_REQ(x, NULL)) < 0) {
222 		warnx("i2d_X509");
223 		goto out;
224 	} else if (NULL == (der = dercp = malloc(len))) {
225 		warn("malloc");
226 		goto out;
227 	} else if (len != i2d_X509_REQ(x, (u_char **)&dercp)) {
228 		warnx("i2d_X509");
229 		goto out;
230 	} else if (NULL == (der64 = base64buf_url(der, len))) {
231 		warnx("base64buf_url");
232 		goto out;
233 	}
234 
235 	/*
236 	 * Write that we're ready, then write.
237 	 * We ignore reader-closed failure, as we're just going to roll
238 	 * into the exit case anyway.
239 	 */
240 
241 	if (writeop(netsock, COMM_KEY_STAT, KEY_READY) < 0)
242 		goto out;
243 	if (writestr(netsock, COMM_CERT, der64) < 0)
244 		goto out;
245 
246 	rc = 1;
247 out:
248 	close(netsock);
249 	if (NULL != f)
250 		fclose(f);
251 	free(der);
252 	free(der64);
253 	free(sans);
254 	free(san);
255 	if (NULL != x)
256 		X509_REQ_free(x);
257 	if (NULL != name)
258 		X509_NAME_free(name);
259 	if (NULL != pkey)
260 		EVP_PKEY_free(pkey);
261 	ERR_print_errors_fp(stderr);
262 	ERR_free_strings();
263 	return (rc);
264 }
265