1 /*------------------------------------------------------------------------- 2 * 3 * fe-auth-scram.c 4 * The front-end (client) implementation of SCRAM authentication. 5 * 6 * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group 7 * Portions Copyright (c) 1994, Regents of the University of California 8 * 9 * IDENTIFICATION 10 * src/interfaces/libpq/fe-auth-scram.c 11 * 12 *------------------------------------------------------------------------- 13 */ 14 15 #include "postgres_fe.h" 16 17 #include "common/base64.h" 18 #include "common/saslprep.h" 19 #include "common/scram-common.h" 20 #include "fe-auth.h" 21 22 23 /* 24 * Status of exchange messages used for SCRAM authentication via the 25 * SASL protocol. 26 */ 27 typedef enum 28 { 29 FE_SCRAM_INIT, 30 FE_SCRAM_NONCE_SENT, 31 FE_SCRAM_PROOF_SENT, 32 FE_SCRAM_FINISHED 33 } fe_scram_state_enum; 34 35 typedef struct 36 { 37 fe_scram_state_enum state; 38 39 /* These are supplied by the user */ 40 PGconn *conn; 41 char *password; 42 char *sasl_mechanism; 43 44 /* We construct these */ 45 uint8 SaltedPassword[SCRAM_KEY_LEN]; 46 char *client_nonce; 47 char *client_first_message_bare; 48 char *client_final_message_without_proof; 49 50 /* These come from the server-first message */ 51 char *server_first_message; 52 char *salt; 53 int saltlen; 54 int iterations; 55 char *nonce; 56 57 /* These come from the server-final message */ 58 char *server_final_message; 59 char ServerSignature[SCRAM_KEY_LEN]; 60 } fe_scram_state; 61 62 static bool read_server_first_message(fe_scram_state *state, char *input); 63 static bool read_server_final_message(fe_scram_state *state, char *input); 64 static char *build_client_first_message(fe_scram_state *state); 65 static char *build_client_final_message(fe_scram_state *state); 66 static bool verify_server_signature(fe_scram_state *state); 67 static void calculate_client_proof(fe_scram_state *state, 68 const char *client_final_message_without_proof, 69 uint8 *result); 70 71 /* 72 * Initialize SCRAM exchange status. 73 */ 74 void * 75 pg_fe_scram_init(PGconn *conn, 76 const char *password, 77 const char *sasl_mechanism) 78 { 79 fe_scram_state *state; 80 char *prep_password; 81 pg_saslprep_rc rc; 82 83 Assert(sasl_mechanism != NULL); 84 85 state = (fe_scram_state *) malloc(sizeof(fe_scram_state)); 86 if (!state) 87 return NULL; 88 memset(state, 0, sizeof(fe_scram_state)); 89 state->conn = conn; 90 state->state = FE_SCRAM_INIT; 91 state->sasl_mechanism = strdup(sasl_mechanism); 92 93 if (!state->sasl_mechanism) 94 { 95 free(state); 96 return NULL; 97 } 98 99 /* Normalize the password with SASLprep, if possible */ 100 rc = pg_saslprep(password, &prep_password); 101 if (rc == SASLPREP_OOM) 102 { 103 free(state->sasl_mechanism); 104 free(state); 105 return NULL; 106 } 107 if (rc != SASLPREP_SUCCESS) 108 { 109 prep_password = strdup(password); 110 if (!prep_password) 111 { 112 free(state->sasl_mechanism); 113 free(state); 114 return NULL; 115 } 116 } 117 state->password = prep_password; 118 119 return state; 120 } 121 122 /* 123 * Free SCRAM exchange status 124 */ 125 void 126 pg_fe_scram_free(void *opaq) 127 { 128 fe_scram_state *state = (fe_scram_state *) opaq; 129 130 if (state->password) 131 free(state->password); 132 if (state->sasl_mechanism) 133 free(state->sasl_mechanism); 134 135 /* client messages */ 136 if (state->client_nonce) 137 free(state->client_nonce); 138 if (state->client_first_message_bare) 139 free(state->client_first_message_bare); 140 if (state->client_final_message_without_proof) 141 free(state->client_final_message_without_proof); 142 143 /* first message from server */ 144 if (state->server_first_message) 145 free(state->server_first_message); 146 if (state->salt) 147 free(state->salt); 148 if (state->nonce) 149 free(state->nonce); 150 151 /* final message from server */ 152 if (state->server_final_message) 153 free(state->server_final_message); 154 155 free(state); 156 } 157 158 /* 159 * Exchange a SCRAM message with backend. 160 */ 161 void 162 pg_fe_scram_exchange(void *opaq, char *input, int inputlen, 163 char **output, int *outputlen, 164 bool *done, bool *success) 165 { 166 fe_scram_state *state = (fe_scram_state *) opaq; 167 PGconn *conn = state->conn; 168 169 *done = false; 170 *success = false; 171 *output = NULL; 172 *outputlen = 0; 173 174 /* 175 * Check that the input length agrees with the string length of the input. 176 * We can ignore inputlen after this. 177 */ 178 if (state->state != FE_SCRAM_INIT) 179 { 180 if (inputlen == 0) 181 { 182 printfPQExpBuffer(&conn->errorMessage, 183 libpq_gettext("malformed SCRAM message (empty message)\n")); 184 goto error; 185 } 186 if (inputlen != strlen(input)) 187 { 188 printfPQExpBuffer(&conn->errorMessage, 189 libpq_gettext("malformed SCRAM message (length mismatch)\n")); 190 goto error; 191 } 192 } 193 194 switch (state->state) 195 { 196 case FE_SCRAM_INIT: 197 /* Begin the SCRAM handshake, by sending client nonce */ 198 *output = build_client_first_message(state); 199 if (*output == NULL) 200 goto error; 201 202 *outputlen = strlen(*output); 203 *done = false; 204 state->state = FE_SCRAM_NONCE_SENT; 205 break; 206 207 case FE_SCRAM_NONCE_SENT: 208 /* Receive salt and server nonce, send response. */ 209 if (!read_server_first_message(state, input)) 210 goto error; 211 212 *output = build_client_final_message(state); 213 if (*output == NULL) 214 goto error; 215 216 *outputlen = strlen(*output); 217 *done = false; 218 state->state = FE_SCRAM_PROOF_SENT; 219 break; 220 221 case FE_SCRAM_PROOF_SENT: 222 /* Receive server signature */ 223 if (!read_server_final_message(state, input)) 224 goto error; 225 226 /* 227 * Verify server signature, to make sure we're talking to the 228 * genuine server. XXX: A fake server could simply not require 229 * authentication, though. There is currently no option in libpq 230 * to reject a connection, if SCRAM authentication did not happen. 231 */ 232 if (verify_server_signature(state)) 233 *success = true; 234 else 235 { 236 *success = false; 237 printfPQExpBuffer(&conn->errorMessage, 238 libpq_gettext("incorrect server signature\n")); 239 } 240 *done = true; 241 state->state = FE_SCRAM_FINISHED; 242 break; 243 244 default: 245 /* shouldn't happen */ 246 printfPQExpBuffer(&conn->errorMessage, 247 libpq_gettext("invalid SCRAM exchange state\n")); 248 goto error; 249 } 250 return; 251 252 error: 253 *done = true; 254 *success = false; 255 return; 256 } 257 258 /* 259 * Read value for an attribute part of a SCRAM message. 260 */ 261 static char * 262 read_attr_value(char **input, char attr, PQExpBuffer errorMessage) 263 { 264 char *begin = *input; 265 char *end; 266 267 if (*begin != attr) 268 { 269 printfPQExpBuffer(errorMessage, 270 libpq_gettext("malformed SCRAM message (attribute \"%c\" expected)\n"), 271 attr); 272 return NULL; 273 } 274 begin++; 275 276 if (*begin != '=') 277 { 278 printfPQExpBuffer(errorMessage, 279 libpq_gettext("malformed SCRAM message (expected character \"=\" for attribute \"%c\")\n"), 280 attr); 281 return NULL; 282 } 283 begin++; 284 285 end = begin; 286 while (*end && *end != ',') 287 end++; 288 289 if (*end) 290 { 291 *end = '\0'; 292 *input = end + 1; 293 } 294 else 295 *input = end; 296 297 return begin; 298 } 299 300 /* 301 * Build the first exchange message sent by the client. 302 */ 303 static char * 304 build_client_first_message(fe_scram_state *state) 305 { 306 PGconn *conn = state->conn; 307 char raw_nonce[SCRAM_RAW_NONCE_LEN + 1]; 308 char *result; 309 int channel_info_len; 310 int encoded_len; 311 PQExpBufferData buf; 312 313 /* 314 * Generate a "raw" nonce. This is converted to ASCII-printable form by 315 * base64-encoding it. 316 */ 317 if (!pg_strong_random(raw_nonce, SCRAM_RAW_NONCE_LEN)) 318 { 319 printfPQExpBuffer(&conn->errorMessage, 320 libpq_gettext("could not generate nonce\n")); 321 return NULL; 322 } 323 324 state->client_nonce = malloc(pg_b64_enc_len(SCRAM_RAW_NONCE_LEN) + 1); 325 if (state->client_nonce == NULL) 326 { 327 printfPQExpBuffer(&conn->errorMessage, 328 libpq_gettext("out of memory\n")); 329 return NULL; 330 } 331 encoded_len = pg_b64_encode(raw_nonce, SCRAM_RAW_NONCE_LEN, state->client_nonce); 332 state->client_nonce[encoded_len] = '\0'; 333 334 /* 335 * Generate message. The username is left empty as the backend uses the 336 * value provided by the startup packet. Also, as this username is not 337 * prepared with SASLprep, the message parsing would fail if it includes 338 * '=' or ',' characters. 339 */ 340 341 initPQExpBuffer(&buf); 342 343 /* 344 * First build the gs2-header with channel binding information. 345 */ 346 if (strcmp(state->sasl_mechanism, SCRAM_SHA_256_PLUS_NAME) == 0) 347 { 348 Assert(conn->ssl_in_use); 349 appendPQExpBuffer(&buf, "p=tls-server-end-point"); 350 } 351 #ifdef HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH 352 else if (conn->ssl_in_use) 353 { 354 /* 355 * Client supports channel binding, but thinks the server does not. 356 */ 357 appendPQExpBuffer(&buf, "y"); 358 } 359 #endif 360 else 361 { 362 /* 363 * Client does not support channel binding. 364 */ 365 appendPQExpBuffer(&buf, "n"); 366 } 367 368 if (PQExpBufferDataBroken(buf)) 369 goto oom_error; 370 371 channel_info_len = buf.len; 372 373 appendPQExpBuffer(&buf, ",,n=,r=%s", state->client_nonce); 374 if (PQExpBufferDataBroken(buf)) 375 goto oom_error; 376 377 /* 378 * The first message content needs to be saved without channel binding 379 * information. 380 */ 381 state->client_first_message_bare = strdup(buf.data + channel_info_len + 2); 382 if (!state->client_first_message_bare) 383 goto oom_error; 384 385 result = strdup(buf.data); 386 if (result == NULL) 387 goto oom_error; 388 389 termPQExpBuffer(&buf); 390 return result; 391 392 oom_error: 393 termPQExpBuffer(&buf); 394 printfPQExpBuffer(&conn->errorMessage, 395 libpq_gettext("out of memory\n")); 396 return NULL; 397 } 398 399 /* 400 * Build the final exchange message sent from the client. 401 */ 402 static char * 403 build_client_final_message(fe_scram_state *state) 404 { 405 PQExpBufferData buf; 406 PGconn *conn = state->conn; 407 uint8 client_proof[SCRAM_KEY_LEN]; 408 char *result; 409 410 initPQExpBuffer(&buf); 411 412 /* 413 * Construct client-final-message-without-proof. We need to remember it 414 * for verifying the server proof in the final step of authentication. 415 * 416 * The channel binding flag handling (p/y/n) must be consistent with 417 * build_client_first_message(), because the server will check that it's 418 * the same flag both times. 419 */ 420 if (strcmp(state->sasl_mechanism, SCRAM_SHA_256_PLUS_NAME) == 0) 421 { 422 #ifdef HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH 423 char *cbind_data = NULL; 424 size_t cbind_data_len = 0; 425 size_t cbind_header_len; 426 char *cbind_input; 427 size_t cbind_input_len; 428 429 /* Fetch hash data of server's SSL certificate */ 430 cbind_data = 431 pgtls_get_peer_certificate_hash(state->conn, 432 &cbind_data_len); 433 if (cbind_data == NULL) 434 { 435 /* error message is already set on error */ 436 termPQExpBuffer(&buf); 437 return NULL; 438 } 439 440 appendPQExpBuffer(&buf, "c="); 441 442 /* p=type,, */ 443 cbind_header_len = strlen("p=tls-server-end-point,,"); 444 cbind_input_len = cbind_header_len + cbind_data_len; 445 cbind_input = malloc(cbind_input_len); 446 if (!cbind_input) 447 { 448 free(cbind_data); 449 goto oom_error; 450 } 451 memcpy(cbind_input, "p=tls-server-end-point,,", cbind_header_len); 452 memcpy(cbind_input + cbind_header_len, cbind_data, cbind_data_len); 453 454 if (!enlargePQExpBuffer(&buf, pg_b64_enc_len(cbind_input_len))) 455 { 456 free(cbind_data); 457 free(cbind_input); 458 goto oom_error; 459 } 460 buf.len += pg_b64_encode(cbind_input, cbind_input_len, buf.data + buf.len); 461 buf.data[buf.len] = '\0'; 462 463 free(cbind_data); 464 free(cbind_input); 465 #else 466 /* 467 * Chose channel binding, but the SSL library doesn't support it. 468 * Shouldn't happen. 469 */ 470 termPQExpBuffer(&buf); 471 printfPQExpBuffer(&conn->errorMessage, 472 "channel binding not supported by this build\n"); 473 return NULL; 474 #endif /* HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH */ 475 } 476 #ifdef HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH 477 else if (conn->ssl_in_use) 478 appendPQExpBuffer(&buf, "c=eSws"); /* base64 of "y,," */ 479 #endif 480 else 481 appendPQExpBuffer(&buf, "c=biws"); /* base64 of "n,," */ 482 483 if (PQExpBufferDataBroken(buf)) 484 goto oom_error; 485 486 appendPQExpBuffer(&buf, ",r=%s", state->nonce); 487 if (PQExpBufferDataBroken(buf)) 488 goto oom_error; 489 490 state->client_final_message_without_proof = strdup(buf.data); 491 if (state->client_final_message_without_proof == NULL) 492 goto oom_error; 493 494 /* Append proof to it, to form client-final-message. */ 495 calculate_client_proof(state, 496 state->client_final_message_without_proof, 497 client_proof); 498 499 appendPQExpBuffer(&buf, ",p="); 500 if (!enlargePQExpBuffer(&buf, pg_b64_enc_len(SCRAM_KEY_LEN))) 501 goto oom_error; 502 buf.len += pg_b64_encode((char *) client_proof, 503 SCRAM_KEY_LEN, 504 buf.data + buf.len); 505 buf.data[buf.len] = '\0'; 506 507 result = strdup(buf.data); 508 if (result == NULL) 509 goto oom_error; 510 511 termPQExpBuffer(&buf); 512 return result; 513 514 oom_error: 515 termPQExpBuffer(&buf); 516 printfPQExpBuffer(&conn->errorMessage, 517 libpq_gettext("out of memory\n")); 518 return NULL; 519 } 520 521 /* 522 * Read the first exchange message coming from the server. 523 */ 524 static bool 525 read_server_first_message(fe_scram_state *state, char *input) 526 { 527 PGconn *conn = state->conn; 528 char *iterations_str; 529 char *endptr; 530 char *encoded_salt; 531 char *nonce; 532 533 state->server_first_message = strdup(input); 534 if (state->server_first_message == NULL) 535 { 536 printfPQExpBuffer(&conn->errorMessage, 537 libpq_gettext("out of memory\n")); 538 return false; 539 } 540 541 /* parse the message */ 542 nonce = read_attr_value(&input, 'r', 543 &conn->errorMessage); 544 if (nonce == NULL) 545 { 546 /* read_attr_value() has generated an error string */ 547 return false; 548 } 549 550 /* Verify immediately that the server used our part of the nonce */ 551 if (strlen(nonce) < strlen(state->client_nonce) || 552 memcmp(nonce, state->client_nonce, strlen(state->client_nonce)) != 0) 553 { 554 printfPQExpBuffer(&conn->errorMessage, 555 libpq_gettext("invalid SCRAM response (nonce mismatch)\n")); 556 return false; 557 } 558 559 state->nonce = strdup(nonce); 560 if (state->nonce == NULL) 561 { 562 printfPQExpBuffer(&conn->errorMessage, 563 libpq_gettext("out of memory\n")); 564 return false; 565 } 566 567 encoded_salt = read_attr_value(&input, 's', &conn->errorMessage); 568 if (encoded_salt == NULL) 569 { 570 /* read_attr_value() has generated an error string */ 571 return false; 572 } 573 state->salt = malloc(pg_b64_dec_len(strlen(encoded_salt))); 574 if (state->salt == NULL) 575 { 576 printfPQExpBuffer(&conn->errorMessage, 577 libpq_gettext("out of memory\n")); 578 return false; 579 } 580 state->saltlen = pg_b64_decode(encoded_salt, 581 strlen(encoded_salt), 582 state->salt); 583 if (state->saltlen < 0) 584 { 585 printfPQExpBuffer(&conn->errorMessage, 586 libpq_gettext("malformed SCRAM message (invalid salt)\n")); 587 return false; 588 } 589 590 iterations_str = read_attr_value(&input, 'i', &conn->errorMessage); 591 if (iterations_str == NULL) 592 { 593 /* read_attr_value() has generated an error string */ 594 return false; 595 } 596 state->iterations = strtol(iterations_str, &endptr, 10); 597 if (*endptr != '\0' || state->iterations < 1) 598 { 599 printfPQExpBuffer(&conn->errorMessage, 600 libpq_gettext("malformed SCRAM message (invalid iteration count)\n")); 601 return false; 602 } 603 604 if (*input != '\0') 605 printfPQExpBuffer(&conn->errorMessage, 606 libpq_gettext("malformed SCRAM message (garbage at end of server-first-message)\n")); 607 608 return true; 609 } 610 611 /* 612 * Read the final exchange message coming from the server. 613 */ 614 static bool 615 read_server_final_message(fe_scram_state *state, char *input) 616 { 617 PGconn *conn = state->conn; 618 char *encoded_server_signature; 619 char *decoded_server_signature; 620 int server_signature_len; 621 622 state->server_final_message = strdup(input); 623 if (!state->server_final_message) 624 { 625 printfPQExpBuffer(&conn->errorMessage, 626 libpq_gettext("out of memory\n")); 627 return false; 628 } 629 630 /* Check for error result. */ 631 if (*input == 'e') 632 { 633 char *errmsg = read_attr_value(&input, 'e', 634 &conn->errorMessage); 635 636 printfPQExpBuffer(&conn->errorMessage, 637 libpq_gettext("error received from server in SCRAM exchange: %s\n"), 638 errmsg); 639 return false; 640 } 641 642 /* Parse the message. */ 643 encoded_server_signature = read_attr_value(&input, 'v', 644 &conn->errorMessage); 645 if (encoded_server_signature == NULL) 646 { 647 /* read_attr_value() has generated an error message */ 648 return false; 649 } 650 651 if (*input != '\0') 652 printfPQExpBuffer(&conn->errorMessage, 653 libpq_gettext("malformed SCRAM message (garbage at end of server-final-message)\n")); 654 655 server_signature_len = pg_b64_dec_len(strlen(encoded_server_signature)); 656 decoded_server_signature = malloc(server_signature_len); 657 if (!decoded_server_signature) 658 { 659 printfPQExpBuffer(&conn->errorMessage, 660 libpq_gettext("out of memory\n")); 661 return false; 662 } 663 664 server_signature_len = pg_b64_decode(encoded_server_signature, 665 strlen(encoded_server_signature), 666 decoded_server_signature); 667 if (server_signature_len != SCRAM_KEY_LEN) 668 { 669 free(decoded_server_signature); 670 printfPQExpBuffer(&conn->errorMessage, 671 libpq_gettext("malformed SCRAM message (invalid server signature)\n")); 672 return false; 673 } 674 memcpy(state->ServerSignature, decoded_server_signature, SCRAM_KEY_LEN); 675 free(decoded_server_signature); 676 677 return true; 678 } 679 680 /* 681 * Calculate the client proof, part of the final exchange message sent 682 * by the client. 683 */ 684 static void 685 calculate_client_proof(fe_scram_state *state, 686 const char *client_final_message_without_proof, 687 uint8 *result) 688 { 689 uint8 StoredKey[SCRAM_KEY_LEN]; 690 uint8 ClientKey[SCRAM_KEY_LEN]; 691 uint8 ClientSignature[SCRAM_KEY_LEN]; 692 int i; 693 scram_HMAC_ctx ctx; 694 695 /* 696 * Calculate SaltedPassword, and store it in 'state' so that we can reuse 697 * it later in verify_server_signature. 698 */ 699 scram_SaltedPassword(state->password, state->salt, state->saltlen, 700 state->iterations, state->SaltedPassword); 701 702 scram_ClientKey(state->SaltedPassword, ClientKey); 703 scram_H(ClientKey, SCRAM_KEY_LEN, StoredKey); 704 705 scram_HMAC_init(&ctx, StoredKey, SCRAM_KEY_LEN); 706 scram_HMAC_update(&ctx, 707 state->client_first_message_bare, 708 strlen(state->client_first_message_bare)); 709 scram_HMAC_update(&ctx, ",", 1); 710 scram_HMAC_update(&ctx, 711 state->server_first_message, 712 strlen(state->server_first_message)); 713 scram_HMAC_update(&ctx, ",", 1); 714 scram_HMAC_update(&ctx, 715 client_final_message_without_proof, 716 strlen(client_final_message_without_proof)); 717 scram_HMAC_final(ClientSignature, &ctx); 718 719 for (i = 0; i < SCRAM_KEY_LEN; i++) 720 result[i] = ClientKey[i] ^ ClientSignature[i]; 721 } 722 723 /* 724 * Validate the server signature, received as part of the final exchange 725 * message received from the server. 726 */ 727 static bool 728 verify_server_signature(fe_scram_state *state) 729 { 730 uint8 expected_ServerSignature[SCRAM_KEY_LEN]; 731 uint8 ServerKey[SCRAM_KEY_LEN]; 732 scram_HMAC_ctx ctx; 733 734 scram_ServerKey(state->SaltedPassword, ServerKey); 735 736 /* calculate ServerSignature */ 737 scram_HMAC_init(&ctx, ServerKey, SCRAM_KEY_LEN); 738 scram_HMAC_update(&ctx, 739 state->client_first_message_bare, 740 strlen(state->client_first_message_bare)); 741 scram_HMAC_update(&ctx, ",", 1); 742 scram_HMAC_update(&ctx, 743 state->server_first_message, 744 strlen(state->server_first_message)); 745 scram_HMAC_update(&ctx, ",", 1); 746 scram_HMAC_update(&ctx, 747 state->client_final_message_without_proof, 748 strlen(state->client_final_message_without_proof)); 749 scram_HMAC_final(expected_ServerSignature, &ctx); 750 751 if (memcmp(expected_ServerSignature, state->ServerSignature, SCRAM_KEY_LEN) != 0) 752 return false; 753 754 return true; 755 } 756 757 /* 758 * Build a new SCRAM verifier. 759 */ 760 char * 761 pg_fe_scram_build_verifier(const char *password) 762 { 763 char *prep_password; 764 pg_saslprep_rc rc; 765 char saltbuf[SCRAM_DEFAULT_SALT_LEN]; 766 char *result; 767 768 /* 769 * Normalize the password with SASLprep. If that doesn't work, because 770 * the password isn't valid UTF-8 or contains prohibited characters, just 771 * proceed with the original password. (See comments at top of file.) 772 */ 773 rc = pg_saslprep(password, &prep_password); 774 if (rc == SASLPREP_OOM) 775 return NULL; 776 if (rc == SASLPREP_SUCCESS) 777 password = (const char *) prep_password; 778 779 /* Generate a random salt */ 780 if (!pg_strong_random(saltbuf, SCRAM_DEFAULT_SALT_LEN)) 781 { 782 if (prep_password) 783 free(prep_password); 784 return NULL; 785 } 786 787 result = scram_build_verifier(saltbuf, SCRAM_DEFAULT_SALT_LEN, 788 SCRAM_DEFAULT_ITERATIONS, password); 789 790 if (prep_password) 791 free(prep_password); 792 793 return result; 794 } 795