xref: /openbsd-src/usr.sbin/acme-client/certproc.c (revision d1df930ffab53da22f3324c32bed7ac5709915e6)
1 /*	$Id: certproc.c,v 1.11 2018/07/28 15:25:23 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 <err.h>
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <unistd.h>
23 
24 #include <openssl/pem.h>
25 #include <openssl/x509.h>
26 #include <openssl/x509v3.h>
27 #include <openssl/err.h>
28 
29 #include "extern.h"
30 
31 #define MARKER "-----BEGIN CERTIFICATE-----"
32 
33 /*
34  * Convert an X509 certificate to a buffer of "sz".
35  * We don't guarantee that it's NUL-terminated.
36  * Returns NULL on failure.
37  */
38 static char *
39 x509buf(X509 *x, size_t *sz)
40 {
41 	BIO	*bio;
42 	char	*p;
43 	int	 ssz;
44 
45 	/* Convert X509 to PEM in BIO. */
46 
47 	if ((bio = BIO_new(BIO_s_mem())) == NULL) {
48 		warnx("BIO_new");
49 		return NULL;
50 	} else if (!PEM_write_bio_X509(bio, x)) {
51 		warnx("PEM_write_bio_X509");
52 		BIO_free(bio);
53 		return NULL;
54 	}
55 
56 	/*
57 	 * Now convert bio to string.
58 	 * Make into NUL-terminated, just in case.
59 	 */
60 
61 	if ((p = calloc(1, bio->num_write + 1)) == NULL) {
62 		warn("calloc");
63 		BIO_free(bio);
64 		return NULL;
65 	}
66 
67 	ssz = BIO_read(bio, p, bio->num_write);
68 	if (ssz < 0 || (unsigned)ssz != bio->num_write) {
69 		warnx("BIO_read");
70 		BIO_free(bio);
71 		return NULL;
72 	}
73 
74 	*sz = ssz;
75 	BIO_free(bio);
76 	return p;
77 }
78 
79 int
80 certproc(int netsock, int filesock)
81 {
82 	char		*csr = NULL, *chain = NULL, *url = NULL;
83 	unsigned char	*csrcp, *chaincp;
84 	size_t		 csrsz, chainsz;
85 	int		 i, rc = 0, idx = -1, cc;
86 	enum certop	 op;
87 	long		 lval;
88 	X509		*x = NULL, *chainx = NULL;
89 	X509_EXTENSION	*ext = NULL;
90 	X509V3_EXT_METHOD *method = NULL;
91 	void		*entries;
92 	STACK_OF(CONF_VALUE) *val;
93 	CONF_VALUE	*nval;
94 
95 	/* File-system and sandbox jailing. */
96 
97 	ERR_load_crypto_strings();
98 
99 	if (pledge("stdio", NULL) == -1) {
100 		warn("pledge");
101 		goto out;
102 	}
103 
104 	/* Read what the netproc wants us to do. */
105 
106 	op = CERT__MAX;
107 	if ((lval = readop(netsock, COMM_CSR_OP)) == 0)
108 		op = CERT_STOP;
109 	else if (lval == CERT_REVOKE || lval == CERT_UPDATE)
110 		op = lval;
111 
112 	if (CERT_STOP == op) {
113 		rc = 1;
114 		goto out;
115 	} else if (CERT__MAX == op) {
116 		warnx("unknown operation from netproc");
117 		goto out;
118 	}
119 
120 	/*
121 	 * Pass revocation right through to fileproc.
122 	 * If the reader is terminated, ignore it.
123 	 */
124 
125 	if (CERT_REVOKE == op) {
126 		if (writeop(filesock, COMM_CHAIN_OP, FILE_REMOVE) >= 0)
127 			rc = 1;
128 		goto out;
129 	}
130 
131 	/*
132 	 * Wait until we receive the DER encoded (signed) certificate
133 	 * from the network process.
134 	 * Then convert the DER encoding into an X509 certificate.
135 	 */
136 
137 	if ((csr = readbuf(netsock, COMM_CSR, &csrsz)) == NULL)
138 		goto out;
139 
140 	csrcp = (u_char *)csr;
141 	x = d2i_X509(NULL, (const u_char **)&csrcp, csrsz);
142 	if (x == NULL) {
143 		warnx("d2i_X509");
144 		goto out;
145 	}
146 
147 	/*
148 	 * Extract the CA Issuers from its NID.
149 	 * TODO: I have no idea what I'm doing.
150 	 */
151 
152 	idx = X509_get_ext_by_NID(x, NID_info_access, idx);
153 	if (idx >= 0 && (ext = X509_get_ext(x, idx)) != NULL)
154 		method = (X509V3_EXT_METHOD *)X509V3_EXT_get(ext);
155 
156 	entries = X509_get_ext_d2i(x, NID_info_access, 0, 0);
157 	if (method != NULL && entries != NULL) {
158 		val = method->i2v(method, entries, 0);
159 		for (i = 0; i < sk_CONF_VALUE_num(val); i++) {
160 			nval = sk_CONF_VALUE_value(val, i);
161 			if (strcmp(nval->name, "CA Issuers - URI"))
162 				continue;
163 			url = strdup(nval->value);
164 			if (url == NULL) {
165 				warn("strdup");
166 				goto out;
167 			}
168 			break;
169 		}
170 	}
171 
172 	if (url == NULL) {
173 		warnx("no CA issuer registered with certificate");
174 		goto out;
175 	}
176 
177 	/* Write the CA issuer to the netsock. */
178 
179 	if (writestr(netsock, COMM_ISSUER, url) <= 0)
180 		goto out;
181 
182 	/* Read the full-chain back from the netsock. */
183 
184 	if ((chain = readbuf(netsock, COMM_CHAIN, &chainsz)) == NULL)
185 		goto out;
186 
187 	/*
188 	 * Then check if the chain is PEM-encoded by looking to see if
189 	 * it begins with the PEM marker.
190 	 * If so, ship it as-is; otherwise, convert to a PEM encoded
191 	 * buffer and ship that.
192 	 * FIXME: if PEM, re-parse it.
193 	 */
194 
195 	if (chainsz <= strlen(MARKER) ||
196 	    strncmp(chain, MARKER, strlen(MARKER))) {
197 		chaincp = (u_char *)chain;
198 		chainx = d2i_X509(NULL, (const u_char **)&chaincp, chainsz);
199 		if (chainx == NULL) {
200 			warnx("d2i_X509");
201 			goto out;
202 		}
203 		free(chain);
204 		if ((chain = x509buf(chainx, &chainsz)) == NULL)
205 			goto out;
206 	}
207 
208 	/* Allow reader termination to just push us out. */
209 
210 	if ((cc = writeop(filesock, COMM_CHAIN_OP, FILE_CREATE)) == 0)
211 		rc = 1;
212 	if (cc <= 0)
213 		goto out;
214 	if ((cc = writebuf(filesock, COMM_CHAIN, chain, chainsz)) == 0)
215 		rc = 1;
216 	if (cc <= 0)
217 		goto out;
218 
219 	/*
220 	 * Next, convert the X509 to a buffer and send that.
221 	 * Reader failure doesn't change anything.
222 	 */
223 
224 	free(chain);
225 	if ((chain = x509buf(x, &chainsz)) == NULL)
226 		goto out;
227 	if (writebuf(filesock, COMM_CSR, chain, chainsz) < 0)
228 		goto out;
229 
230 	rc = 1;
231 out:
232 	close(netsock);
233 	close(filesock);
234 	X509_free(x);
235 	X509_free(chainx);
236 	free(csr);
237 	free(url);
238 	free(chain);
239 	ERR_print_errors_fp(stderr);
240 	ERR_free_strings();
241 	return rc;
242 }
243