/* * Check MD5 authentication response, and return STATUS_OK or STATUS_ERROR. * * 'shadow_pass' is the user's correct password or password hash, as stored * in pg_authid.rolpassword. * 'client_pass' is the response given by the remote user to the MD5 challenge. * 'md5_salt' is the salt used in the MD5 authentication challenge. * * In the error case, optionally store a palloc'd string at *logdetail * that will be sent to the postmaster log (but not the client). */ int md5_crypt_verify(const char *role, const char *shadow_pass, const char *client_pass, const char *md5_salt, int md5_salt_len, char **logdetail) { int retval; char crypt_pwd[MD5_PASSWD_LEN + 1]; Assert(md5_salt_len > 0); if (get_password_type(shadow_pass) != PASSWORD_TYPE_MD5) { /* incompatible password hash format. */ *logdetail = psprintf(_("User \"%s\" has a password that cannot be used with MD5 authentication."), role); return STATUS_ERROR; } /* * Compute the correct answer for the MD5 challenge. * * We do not bother setting logdetail for any pg_md5_encrypt failure * below: the only possible error is out-of-memory, which is unlikely, and * if it did happen adding a psprintf call would only make things worse. */ /* stored password already encrypted, only do salt */ if (!pg_md5_encrypt(shadow_pass + strlen("md5"), md5_salt, md5_salt_len, crypt_pwd)) { return STATUS_ERROR; } if (strcmp(client_pass, crypt_pwd) == 0) retval = STATUS_OK; else { *logdetail = psprintf(_("Password does not match for user \"%s\"."), role); retval = STATUS_ERROR; } return retval; }
/* * Given a user-supplied password, convert it into a verifier of * 'target_type' kind. * * If the password is already in encrypted form, we cannot reverse the * hash, so it is stored as it is regardless of the requested type. */ char * encrypt_password(PasswordType target_type, const char *role, const char *password) { PasswordType guessed_type = get_password_type(password); char *encrypted_password; if (guessed_type != PASSWORD_TYPE_PLAINTEXT) { /* * Cannot convert an already-encrypted password from one format to * another, so return it as it is. */ return pstrdup(password); } switch (target_type) { case PASSWORD_TYPE_MD5: encrypted_password = palloc(MD5_PASSWD_LEN + 1); if (!pg_md5_encrypt(password, role, strlen(role), encrypted_password)) elog(ERROR, "password encryption failed"); return encrypted_password; case PASSWORD_TYPE_SCRAM_SHA_256: return pg_be_scram_build_verifier(password); case PASSWORD_TYPE_PLAINTEXT: elog(ERROR, "cannot encrypt password with 'plaintext'"); } /* * This shouldn't happen, because the above switch statements should * handle every combination of source and target password types. */ elog(ERROR, "cannot encrypt password to requested type"); return NULL; /* keep compiler quiet */ }
/* * 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; }
/* * Check given password for given user, and return STATUS_OK or STATUS_ERROR. * * 'shadow_pass' is the user's correct password hash, as stored in * pg_authid.rolpassword. * 'client_pass' is the password given by the remote user. * * In the error case, optionally store a palloc'd string at *logdetail * that will be sent to the postmaster log (but not the client). */ int plain_crypt_verify(const char *role, const char *shadow_pass, const char *client_pass, char **logdetail) { char crypt_client_pass[MD5_PASSWD_LEN + 1]; /* * Client sent password in plaintext. If we have an MD5 hash stored, hash * the password the client sent, and compare the hashes. Otherwise * compare the plaintext passwords directly. */ switch (get_password_type(shadow_pass)) { case PASSWORD_TYPE_SCRAM_SHA_256: if (scram_verify_plain_password(role, client_pass, shadow_pass)) { return STATUS_OK; } else { *logdetail = psprintf(_("Password does not match for user \"%s\"."), role); return STATUS_ERROR; } break; case PASSWORD_TYPE_MD5: if (!pg_md5_encrypt(client_pass, role, strlen(role), crypt_client_pass)) { /* * We do not bother setting logdetail for pg_md5_encrypt * failure: the only possible error is out-of-memory, which is * unlikely, and if it did happen adding a psprintf call would * only make things worse. */ return STATUS_ERROR; } if (strcmp(crypt_client_pass, shadow_pass) == 0) return STATUS_OK; else { *logdetail = psprintf(_("Password does not match for user \"%s\"."), role); return STATUS_ERROR; } break; case PASSWORD_TYPE_PLAINTEXT: /* * We never store passwords in plaintext, so this shouldn't * happen. */ break; } /* * This shouldn't happen. Plain "password" authentication is possible * with any kind of stored password hash. */ *logdetail = psprintf(_("Password of user \"%s\" is in unrecognized format."), role); return STATUS_ERROR; }