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