/* * Verify a plaintext password against a SCRAM verifier. This is used when * performing plaintext password authentication for a user that has a SCRAM * verifier stored in pg_authid. */ bool scram_verify_plain_password(const char *username, const char *password, const char *verifier) { char *encoded_salt; char *salt; int saltlen; int iterations; uint8 salted_password[SCRAM_KEY_LEN]; uint8 stored_key[SCRAM_KEY_LEN]; uint8 server_key[SCRAM_KEY_LEN]; uint8 computed_key[SCRAM_KEY_LEN]; char *prep_password = NULL; pg_saslprep_rc rc; if (!parse_scram_verifier(verifier, &iterations, &encoded_salt, stored_key, server_key)) { /* * The password looked like a SCRAM verifier, but could not be parsed. */ ereport(LOG, (errmsg("invalid SCRAM verifier for user \"%s\"", username))); return false; } salt = palloc(pg_b64_dec_len(strlen(encoded_salt))); saltlen = pg_b64_decode(encoded_salt, strlen(encoded_salt), salt); if (saltlen == -1) { ereport(LOG, (errmsg("invalid SCRAM verifier for user \"%s\"", username))); return false; } /* Normalize the password */ rc = pg_saslprep(password, &prep_password); if (rc == SASLPREP_SUCCESS) password = prep_password; /* Compute Server Key based on the user-supplied plaintext password */ scram_SaltedPassword(password, salt, saltlen, iterations, salted_password); scram_ServerKey(salted_password, computed_key); if (prep_password) pfree(prep_password); /* * Compare the verifier's Server Key with the one computed from the * user-supplied password. */ return memcmp(computed_key, server_key, SCRAM_KEY_LEN) == 0; }
/* * pg_be_scram_init * * Initialize a new SCRAM authentication exchange status tracker. This * needs to be called before doing any exchange. It will be filled later * after the beginning of the exchange with verifier data. * * 'username' is the username provided by the client in the startup message. * 'shadow_pass' is the role's password verifier, from pg_authid.rolpassword. * If 'shadow_pass' is NULL, we still perform an authentication exchange, but * it will fail, as if an incorrect password was given. */ void * pg_be_scram_init(const char *username, const char *shadow_pass) { scram_state *state; bool got_verifier; state = (scram_state *) palloc0(sizeof(scram_state)); state->state = SCRAM_AUTH_INIT; state->username = username; /* * Parse the stored password verifier. */ if (shadow_pass) { int password_type = get_password_type(shadow_pass); if (password_type == PASSWORD_TYPE_SCRAM_SHA_256) { if (parse_scram_verifier(shadow_pass, &state->iterations, &state->salt, state->StoredKey, state->ServerKey)) got_verifier = true; else { /* * The password looked like a SCRAM verifier, but could not be * parsed. */ ereport(LOG, (errmsg("invalid SCRAM verifier for user \"%s\"", username))); got_verifier = false; } } else { /* * The user doesn't have SCRAM verifier. (You cannot do SCRAM * authentication with an MD5 hash.) */ state->logdetail = psprintf(_("User \"%s\" does not have a valid SCRAM verifier."), state->username); got_verifier = false; } } else { /* * The caller requested us to perform a dummy authentication. This is * considered normal, since the caller requested it, so don't set log * detail. */ got_verifier = false; } /* * If the user did not have a valid SCRAM verifier, we still go through * the motions with a mock one, and fail as if the client supplied an * incorrect password. This is to avoid revealing information to an * attacker. */ if (!got_verifier) { mock_scram_verifier(username, &state->iterations, &state->salt, state->StoredKey, state->ServerKey); state->doomed = true; } return state; }
/* * pg_be_scram_init * * Initialize a new SCRAM authentication exchange status tracker. This * needs to be called before doing any exchange. It will be filled later * after the beginning of the exchange with verifier data. * * 'selected_mech' identifies the SASL mechanism that the client selected. * It should be one of the mechanisms that we support, as returned by * pg_be_scram_get_mechanisms(). * * 'shadow_pass' is the role's password verifier, from pg_authid.rolpassword. * The username was provided by the client in the startup message, and is * available in port->user_name. If 'shadow_pass' is NULL, we still perform * an authentication exchange, but it will fail, as if an incorrect password * was given. */ void * pg_be_scram_init(Port *port, const char *selected_mech, const char *shadow_pass) { scram_state *state; bool got_verifier; state = (scram_state *) palloc0(sizeof(scram_state)); state->port = port; state->state = SCRAM_AUTH_INIT; /* * Parse the selected mechanism. * * Note that if we don't support channel binding, either because the SSL * implementation doesn't support it or we're not using SSL at all, we * would not have advertised the PLUS variant in the first place. If the * client nevertheless tries to select it, it's a protocol violation like * selecting any other SASL mechanism we don't support. */ #ifdef HAVE_BE_TLS_GET_CERTIFICATE_HASH if (strcmp(selected_mech, SCRAM_SHA_256_PLUS_NAME) == 0 && port->ssl_in_use) state->channel_binding_in_use = true; else #endif if (strcmp(selected_mech, SCRAM_SHA_256_NAME) == 0) state->channel_binding_in_use = false; else ereport(ERROR, (errcode(ERRCODE_PROTOCOL_VIOLATION), errmsg("client selected an invalid SASL authentication mechanism"))); /* * Parse the stored password verifier. */ if (shadow_pass) { int password_type = get_password_type(shadow_pass); if (password_type == PASSWORD_TYPE_SCRAM_SHA_256) { if (parse_scram_verifier(shadow_pass, &state->iterations, &state->salt, state->StoredKey, state->ServerKey)) got_verifier = true; else { /* * The password looked like a SCRAM verifier, but could not be * parsed. */ ereport(LOG, (errmsg("invalid SCRAM verifier for user \"%s\"", state->port->user_name))); got_verifier = false; } } else { /* * The user doesn't have SCRAM verifier. (You cannot do SCRAM * authentication with an MD5 hash.) */ state->logdetail = psprintf(_("User \"%s\" does not have a valid SCRAM verifier."), state->port->user_name); got_verifier = false; } } else { /* * The caller requested us to perform a dummy authentication. This is * considered normal, since the caller requested it, so don't set log * detail. */ got_verifier = false; } /* * If the user did not have a valid SCRAM verifier, we still go through * the motions with a mock one, and fail as if the client supplied an * incorrect password. This is to avoid revealing information to an * attacker. */ if (!got_verifier) { mock_scram_verifier(state->port->user_name, &state->iterations, &state->salt, state->StoredKey, state->ServerKey); state->doomed = true; } return state; }