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