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 */
42static char *
43bn2string(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 */
76static char *
77op_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 */
100static char *
101op_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 */
134static int
135op_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;
178out:
179	free(thumb);
180	free(dig64);
181	return rc;
182}
183
184static int
185op_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
214static int
215op_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 */
253static int
254op_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;
429out:
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
447int
448acctproc(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;
552out:
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