xref: /openbsd-src/usr.sbin/acme-client/acctproc.c (revision 3374c67d44f9b75b98444cbf63020f777792342e)
1 /*	$Id: acctproc.c,v 1.31 2022/12/19 11:16:52 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 <errno.h>
22 #include <limits.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <unistd.h>
27 
28 #include <openssl/bn.h>
29 #include <openssl/ec.h>
30 #include <openssl/ecdsa.h>
31 #include <openssl/evp.h>
32 #include <openssl/rsa.h>
33 #include <openssl/err.h>
34 
35 #include "extern.h"
36 #include "key.h"
37 
38 /*
39  * Converts a BIGNUM to the form used in JWK.
40  * This is essentially a base64-encoded big-endian binary string
41  * representation of the number.
42  */
43 static char *
44 bn2string(const BIGNUM *bn)
45 {
46 	int	 len;
47 	char	*buf, *bbuf;
48 
49 	/* Extract big-endian representation of BIGNUM. */
50 
51 	len = BN_num_bytes(bn);
52 	if ((buf = malloc(len)) == NULL) {
53 		warn("malloc");
54 		return NULL;
55 	} else if (len != BN_bn2bin(bn, (unsigned char *)buf)) {
56 		warnx("BN_bn2bin");
57 		free(buf);
58 		return NULL;
59 	}
60 
61 	/* Convert to base64url. */
62 
63 	if ((bbuf = base64buf_url(buf, len)) == NULL) {
64 		warnx("base64buf_url");
65 		free(buf);
66 		return NULL;
67 	}
68 
69 	free(buf);
70 	return bbuf;
71 }
72 
73 /*
74  * Extract the relevant RSA components from the key and create the JSON
75  * thumbprint from them.
76  */
77 static char *
78 op_thumb_rsa(EVP_PKEY *pkey)
79 {
80 	char	*exp = NULL, *mod = NULL, *json = NULL;
81 	RSA	*r;
82 
83 	if ((r = EVP_PKEY_get0_RSA(pkey)) == NULL)
84 		warnx("EVP_PKEY_get0_RSA");
85 	else if ((mod = bn2string(RSA_get0_n(r))) == NULL)
86 		warnx("bn2string");
87 	else if ((exp = bn2string(RSA_get0_e(r))) == NULL)
88 		warnx("bn2string");
89 	else if ((json = json_fmt_thumb_rsa(exp, mod)) == NULL)
90 		warnx("json_fmt_thumb_rsa");
91 
92 	free(exp);
93 	free(mod);
94 	return json;
95 }
96 
97 /*
98  * Extract the relevant EC components from the key and create the JSON
99  * thumbprint from them.
100  */
101 static char *
102 op_thumb_ec(EVP_PKEY *pkey)
103 {
104 	BIGNUM	*X = NULL, *Y = NULL;
105 	EC_KEY	*ec = NULL;
106 	char	*x = NULL, *y = NULL;
107 	char	*json = NULL;
108 
109 	if ((ec = EVP_PKEY_get0_EC_KEY(pkey)) == NULL)
110 		warnx("EVP_PKEY_get0_EC_KEY");
111 	else if ((X = BN_new()) == NULL)
112 		warnx("BN_new");
113 	else if ((Y = BN_new()) == NULL)
114 		warnx("BN_new");
115 	else if (!EC_POINT_get_affine_coordinates(EC_KEY_get0_group(ec),
116 	    EC_KEY_get0_public_key(ec), X, Y, NULL))
117 		warnx("EC_POINT_get_affine_coordinates");
118 	else if ((x = bn2string(X)) == NULL)
119 		warnx("bn2string");
120 	else if ((y = bn2string(Y)) == NULL)
121 		warnx("bn2string");
122 	else if ((json = json_fmt_thumb_ec(x, y)) == NULL)
123 		warnx("json_fmt_thumb_ec");
124 
125 	BN_free(X);
126 	BN_free(Y);
127 	free(x);
128 	free(y);
129 	return json;
130 }
131 
132 /*
133  * The thumbprint operation is used for the challenge sequence.
134  */
135 static int
136 op_thumbprint(int fd, EVP_PKEY *pkey)
137 {
138 	char		*thumb = NULL, *dig64 = NULL;
139 	unsigned char	 dig[EVP_MAX_MD_SIZE];
140 	unsigned int	 digsz;
141 	int		 rc = 0;
142 
143 	/* Construct the thumbprint input itself. */
144 
145 	switch (EVP_PKEY_base_id(pkey)) {
146 	case EVP_PKEY_RSA:
147 		if ((thumb = op_thumb_rsa(pkey)) != NULL)
148 			break;
149 		goto out;
150 	case EVP_PKEY_EC:
151 		if ((thumb = op_thumb_ec(pkey)) != NULL)
152 			break;
153 		goto out;
154 	default:
155 		warnx("EVP_PKEY_base_id: unknown key type");
156 		goto out;
157 	}
158 
159 	/*
160 	 * Compute the SHA256 digest of the thumbprint then
161 	 * base64-encode the digest itself.
162 	 * If the reader is closed when we write, ignore it (we'll pick
163 	 * it up in the read loop).
164 	 */
165 
166 	if (!EVP_Digest(thumb, strlen(thumb), dig, &digsz, EVP_sha256(),
167 	    NULL)) {
168 		warnx("EVP_Digest");
169 		goto out;
170 	}
171 	if ((dig64 = base64buf_url((char *)dig, digsz)) == NULL) {
172 		warnx("base64buf_url");
173 		goto out;
174 	}
175 	if (writestr(fd, COMM_THUMB, dig64) < 0)
176 		goto out;
177 
178 	rc = 1;
179 out:
180 	free(thumb);
181 	free(dig64);
182 	return rc;
183 }
184 
185 static int
186 op_sign_rsa(char **prot, EVP_PKEY *pkey, const char *nonce, const char *url)
187 {
188 	char	*exp = NULL, *mod = NULL;
189 	int	rc = 0;
190 	RSA	*r;
191 
192 	*prot = NULL;
193 
194 	/*
195 	 * First, extract relevant portions of our private key.
196 	 * Finally, format the header combined with the nonce.
197 	 */
198 
199 	if ((r = EVP_PKEY_get0_RSA(pkey)) == NULL)
200 		warnx("EVP_PKEY_get0_RSA");
201 	else if ((mod = bn2string(RSA_get0_n(r))) == NULL)
202 		warnx("bn2string");
203 	else if ((exp = bn2string(RSA_get0_e(r))) == NULL)
204 		warnx("bn2string");
205 	else if ((*prot = json_fmt_protected_rsa(exp, mod, nonce, url)) == NULL)
206 		warnx("json_fmt_protected_rsa");
207 	else
208 		rc = 1;
209 
210 	free(exp);
211 	free(mod);
212 	return rc;
213 }
214 
215 static int
216 op_sign_ec(char **prot, EVP_PKEY *pkey, const char *nonce, const char *url)
217 {
218 	BIGNUM	*X = NULL, *Y = NULL;
219 	EC_KEY	*ec = NULL;
220 	char	*x = NULL, *y = NULL;
221 	int	rc = 0;
222 
223 	*prot = NULL;
224 
225 	if ((ec = EVP_PKEY_get0_EC_KEY(pkey)) == NULL)
226 		warnx("EVP_PKEY_get0_EC_KEY");
227 	else if ((X = BN_new()) == NULL)
228 		warnx("BN_new");
229 	else if ((Y = BN_new()) == NULL)
230 		warnx("BN_new");
231 	else if (!EC_POINT_get_affine_coordinates(EC_KEY_get0_group(ec),
232 	    EC_KEY_get0_public_key(ec), X, Y, NULL))
233 		warnx("EC_POINT_get_affine_coordinates");
234 	else if ((x = bn2string(X)) == NULL)
235 		warnx("bn2string");
236 	else if ((y = bn2string(Y)) == NULL)
237 		warnx("bn2string");
238 	else if ((*prot = json_fmt_protected_ec(x, y, nonce, url)) == NULL)
239 		warnx("json_fmt_protected_ec");
240 	else
241 		rc = 1;
242 
243 	BN_free(X);
244 	BN_free(Y);
245 	free(x);
246 	free(y);
247 	return rc;
248 }
249 
250 /*
251  * Operation to sign a message with the account key.
252  * This requires the sender ("fd") to provide the payload and a nonce.
253  */
254 static int
255 op_sign(int fd, EVP_PKEY *pkey, enum acctop op)
256 {
257 	EVP_MD_CTX		*ctx = NULL;
258 	const EVP_MD		*evp_md = NULL;
259 	ECDSA_SIG		*ec_sig = NULL;
260 	const BIGNUM		*ec_sig_r = NULL, *ec_sig_s = NULL;
261 	int			 bn_len, sign_len, rc = 0;
262 	char			*nonce = NULL, *pay = NULL, *pay64 = NULL;
263 	char			*prot = NULL, *prot64 = NULL;
264 	char			*sign = NULL, *dig64 = NULL, *fin = NULL;
265 	char			*url = NULL, *kid = NULL, *alg = NULL;
266 	const unsigned char	*digp;
267 	unsigned char		*dig = NULL, *buf = NULL;
268 	size_t			 digsz;
269 
270 	/* Read our payload and nonce from the requestor. */
271 
272 	if ((pay = readstr(fd, COMM_PAY)) == NULL)
273 		goto out;
274 	else if ((nonce = readstr(fd, COMM_NONCE)) == NULL)
275 		goto out;
276 	else if ((url = readstr(fd, COMM_URL)) == NULL)
277 		goto out;
278 
279 	if (op == ACCT_KID_SIGN)
280 		if ((kid = readstr(fd, COMM_KID)) == NULL)
281 			goto out;
282 
283 	/* Base64-encode the payload. */
284 
285 	if ((pay64 = base64buf_url(pay, strlen(pay))) == NULL) {
286 		warnx("base64buf_url");
287 		goto out;
288 	}
289 
290 	switch (EVP_PKEY_base_id(pkey)) {
291 	case EVP_PKEY_RSA:
292 		alg = "RS256";
293 		evp_md = EVP_sha256();
294 		break;
295 	case EVP_PKEY_EC:
296 		alg = "ES384";
297 		evp_md = EVP_sha384();
298 		break;
299 	default:
300 		warnx("unknown account key type");
301 		goto out;
302 	}
303 
304 	if (op == ACCT_KID_SIGN) {
305 		if ((prot = json_fmt_protected_kid(alg, kid, nonce, url)) ==
306 		    NULL) {
307 			warnx("json_fmt_protected_kid");
308 			goto out;
309 		}
310 	} else {
311 		switch (EVP_PKEY_base_id(pkey)) {
312 		case EVP_PKEY_RSA:
313 			if (!op_sign_rsa(&prot, pkey, nonce, url))
314 				goto out;
315 			break;
316 		case EVP_PKEY_EC:
317 			if (!op_sign_ec(&prot, pkey, nonce, url))
318 				goto out;
319 			break;
320 		default:
321 			warnx("EVP_PKEY_base_id");
322 			goto out;
323 		}
324 	}
325 
326 	/* The header combined with the nonce, base64. */
327 
328 	if ((prot64 = base64buf_url(prot, strlen(prot))) == NULL) {
329 		warnx("base64buf_url");
330 		goto out;
331 	}
332 
333 	/* Now the signature material. */
334 
335 	sign_len = asprintf(&sign, "%s.%s", prot64, pay64);
336 	if (sign_len == -1) {
337 		warn("asprintf");
338 		sign = NULL;
339 		goto out;
340 	}
341 
342 	/* Sign the message. */
343 
344 	if ((ctx = EVP_MD_CTX_new()) == NULL) {
345 		warnx("EVP_MD_CTX_new");
346 		goto out;
347 	}
348 	if (!EVP_DigestSignInit(ctx, NULL, evp_md, NULL, pkey)) {
349 		warnx("EVP_DigestSignInit");
350 		goto out;
351 	}
352 	if (!EVP_DigestSign(ctx, NULL, &digsz, sign, sign_len)) {
353 		warnx("EVP_DigestSign");
354 		goto out;
355 	}
356 	if ((dig = malloc(digsz)) == NULL) {
357 		warn("malloc");
358 		goto out;
359 	}
360 	if (!EVP_DigestSign(ctx, dig, &digsz, sign, sign_len)) {
361 		warnx("EVP_DigestSign");
362 		goto out;
363 	}
364 
365 	switch (EVP_PKEY_base_id(pkey)) {
366 	case EVP_PKEY_RSA:
367 		if ((dig64 = base64buf_url((char *)dig, digsz)) == NULL) {
368 			warnx("base64buf_url");
369 			goto out;
370 		}
371 		break;
372 	case EVP_PKEY_EC:
373 		if (digsz > LONG_MAX) {
374 			warnx("EC signature too long");
375 			goto out;
376 		}
377 
378 		digp = dig;
379 		if ((ec_sig = d2i_ECDSA_SIG(NULL, &digp, digsz)) == NULL) {
380 			warnx("d2i_ECDSA_SIG");
381 			goto out;
382 		}
383 
384 		if ((ec_sig_r = ECDSA_SIG_get0_r(ec_sig)) == NULL ||
385 		    (ec_sig_s = ECDSA_SIG_get0_s(ec_sig)) == NULL) {
386 			warnx("ECDSA_SIG_get0");
387 			goto out;
388 		}
389 
390 		if ((bn_len = (EVP_PKEY_bits(pkey) + 7) / 8) <= 0) {
391 			warnx("EVP_PKEY_bits");
392 			goto out;
393 		}
394 
395 		if ((buf = calloc(2, bn_len)) == NULL) {
396 			warnx("calloc");
397 			goto out;
398 		}
399 
400 		if (BN_bn2binpad(ec_sig_r, buf, bn_len) != bn_len ||
401 		    BN_bn2binpad(ec_sig_s, buf + bn_len, bn_len) != bn_len) {
402 			warnx("BN_bn2binpad");
403 			goto out;
404 		}
405 
406 		if ((dig64 = base64buf_url((char *)buf, 2 * bn_len)) == NULL) {
407 			warnx("base64buf_url");
408 			goto out;
409 		}
410 
411 		break;
412 	default:
413 		warnx("EVP_PKEY_base_id");
414 		goto out;
415 	}
416 
417 	/*
418 	 * Write back in the correct JSON format.
419 	 * If the reader is closed, just ignore it (we'll pick it up
420 	 * when we next enter the read loop).
421 	 */
422 
423 	if ((fin = json_fmt_signed(prot64, pay64, dig64)) == NULL) {
424 		warnx("json_fmt_signed");
425 		goto out;
426 	} else if (writestr(fd, COMM_REQ, fin) < 0)
427 		goto out;
428 
429 	rc = 1;
430 out:
431 	ECDSA_SIG_free(ec_sig);
432 	EVP_MD_CTX_free(ctx);
433 	free(pay);
434 	free(sign);
435 	free(pay64);
436 	free(url);
437 	free(nonce);
438 	free(kid);
439 	free(prot);
440 	free(prot64);
441 	free(dig);
442 	free(dig64);
443 	free(fin);
444 	free(buf);
445 	return rc;
446 }
447 
448 int
449 acctproc(int netsock, const char *acctkey, enum keytype keytype)
450 {
451 	FILE		*f = NULL;
452 	EVP_PKEY	*pkey = NULL;
453 	long		 lval;
454 	enum acctop	 op;
455 	int		 rc = 0, cc, newacct = 0;
456 	mode_t		 prev;
457 
458 	/*
459 	 * First, open our private key file read-only or write-only if
460 	 * we're creating from scratch.
461 	 * Set our umask to be maximally restrictive.
462 	 */
463 
464 	prev = umask((S_IWUSR | S_IXUSR) | S_IRWXG | S_IRWXO);
465 	if ((f = fopen(acctkey, "r")) == NULL && errno == ENOENT) {
466 		f = fopen(acctkey, "wx");
467 		newacct = 1;
468 	}
469 	umask(prev);
470 
471 	if (f == NULL) {
472 		warn("%s", acctkey);
473 		goto out;
474 	}
475 
476 	/* File-system, user, and sandbox jailing. */
477 
478 	ERR_load_crypto_strings();
479 
480 	if (pledge("stdio", NULL) == -1) {
481 		warn("pledge");
482 		goto out;
483 	}
484 
485 	if (newacct) {
486 		switch (keytype) {
487 		case KT_ECDSA:
488 			if ((pkey = ec_key_create(f, acctkey)) == NULL)
489 				goto out;
490 			dodbg("%s: generated ECDSA account key", acctkey);
491 			break;
492 		case KT_RSA:
493 			if ((pkey = rsa_key_create(f, acctkey)) == NULL)
494 				goto out;
495 			dodbg("%s: generated RSA account key", acctkey);
496 			break;
497 		}
498 	} else {
499 		if ((pkey = key_load(f, acctkey)) == NULL)
500 			goto out;
501 		/* XXX check if account key type equals configured key type */
502 		doddbg("%s: loaded account key", acctkey);
503 	}
504 
505 	fclose(f);
506 	f = NULL;
507 
508 	/* Notify the netproc that we've started up. */
509 
510 	if ((cc = writeop(netsock, COMM_ACCT_STAT, ACCT_READY)) == 0)
511 		rc = 1;
512 	if (cc <= 0)
513 		goto out;
514 
515 	/*
516 	 * Now we wait for requests from the network-facing process.
517 	 * It might ask us for our thumbprint, for example, or for us to
518 	 * sign a message.
519 	 */
520 
521 	for (;;) {
522 		op = ACCT__MAX;
523 		if ((lval = readop(netsock, COMM_ACCT)) == 0)
524 			op = ACCT_STOP;
525 		else if (lval == ACCT_SIGN || lval == ACCT_KID_SIGN ||
526 		    lval == ACCT_THUMBPRINT)
527 			op = lval;
528 
529 		if (ACCT__MAX == op) {
530 			warnx("unknown operation from netproc");
531 			goto out;
532 		} else if (ACCT_STOP == op)
533 			break;
534 
535 		switch (op) {
536 		case ACCT_SIGN:
537 		case ACCT_KID_SIGN:
538 			if (op_sign(netsock, pkey, op))
539 				break;
540 			warnx("op_sign");
541 			goto out;
542 		case ACCT_THUMBPRINT:
543 			if (op_thumbprint(netsock, pkey))
544 				break;
545 			warnx("op_thumbprint");
546 			goto out;
547 		default:
548 			abort();
549 		}
550 	}
551 
552 	rc = 1;
553 out:
554 	close(netsock);
555 	if (f != NULL)
556 		fclose(f);
557 	EVP_PKEY_free(pkey);
558 	ERR_print_errors_fp(stderr);
559 	ERR_free_strings();
560 	return rc;
561 }
562