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