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