From f8909979c17d64fb14f4074b1b811052d67aba4e Mon Sep 17 00:00:00 2001 From: Gurjeet Singh Date: Mon, 9 Oct 2023 11:48:11 -0700 Subject: [PATCH v3 2/3] Update password verification infrastructure to handle two passwords After the addition of rolsecondpassword and rolsecondvaliduntil columns to pg_authid, this commit adds the ability to honor the second password as well, if any, to authenticate the roles. The get_role_passwords() function retrieves and returns all valid passwords for a role. It does so by inspecting rolpassword and rolsecondpassword column values, and their respective rol*validuntil column values. get_salt(), a local function in user.c helps to extract the salt, needed for generating hash of new passwords, from currently stored password hashes, if any. For md5 it simply uses the role name, and for scram-sha-256 it extracts and returns the salt from the stored hash. The salt provided by get_salt() is used by CreateRole() and AlterRole() to hash the new passwords of a role. The following functions used to accept and peruse just one password / secret. They are now updated to accept and use two passwords, along with the data-structure changes needed for state management needed by these functions. CheckMD5Auth(), CheckPasswordAuth(), CheckSASLAuth(), scram_init(), verify_client_proof(), pg_be_scram_build_secret() now uses the passed-in salt to generate the hash. If one is not provided, then it generates a new random salt, like before. Similarly, +encrypt_password(), pg_md5_encrypt(), and pg_be_scram_build_secret() now accept a salt for password hashing. --- src/backend/commands/user.c | 112 ++++++++++++++++-- src/backend/libpq/auth-sasl.c | 4 +- src/backend/libpq/auth-scram.c | 210 ++++++++++++++++++++------------- src/backend/libpq/auth.c | 114 +++++++++++------- src/backend/libpq/crypt.c | 78 +++++++++--- src/include/libpq/crypt.h | 2 +- src/include/libpq/sasl.h | 4 +- src/include/libpq/scram.h | 2 +- 8 files changed, 374 insertions(+), 152 deletions(-) diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c index 0afaf74865..9ad02e4092 100644 --- a/src/backend/commands/user.c +++ b/src/backend/commands/user.c @@ -30,7 +30,9 @@ #include "commands/defrem.h" #include "commands/seclabel.h" #include "commands/user.h" +#include "common/scram-common.h" #include "libpq/crypt.h" +#include "libpq/scram.h" #include "miscadmin.h" #include "storage/lmgr.h" #include "utils/acl.h" @@ -125,6 +127,86 @@ have_createrole_privilege(void) return has_createrole_privilege(GetUserId()); } +/* + * Inspect the current passwords of a role, and return the salt that can be used + * for hashing of newer passwords. + * + * Returns success on error, and false otherwise. On error the reason is stored in + * logdetail. On success, salt may be null which indicates that the caller is + * free to generate a new salt. + */ +static bool +get_salt(char *rolename, char **salt, const char **logdetail) +{ + char **current_secrets; + int i, num_secrets; + char *salt1, *salt2 = NULL; + PasswordType passtype; + + if (Password_encryption == PASSWORD_TYPE_MD5) + { + *salt = rolename; /* md5 always uses role name, no need to look through the passwords */ + return true; + } + else if (Password_encryption == PASSWORD_TYPE_PLAINTEXT) + { + *salt = NULL; /* Plaintext does not have a salt */ + return true; + } + + current_secrets = get_role_passwords(rolename, logdetail, &num_secrets); + if (num_secrets == 0) + { + *salt = NULL; /* No existing passwords, allow salt to be generated */ + return true; + } + + for (i = 0; i < num_secrets; i++) + { + passtype = get_password_type(current_secrets[i]); + + if (passtype == PASSWORD_TYPE_MD5 || passtype == PASSWORD_TYPE_PLAINTEXT) + continue; /* md5 uses rolename as salt so it is always the same, and plaintext has no salt */ + else if (passtype == PASSWORD_TYPE_SCRAM_SHA_256) + { + int iterations; + int key_length = 0; + pg_cryptohash_type hash_type; + uint8 stored_key[SCRAM_MAX_KEY_LEN]; + uint8 server_key[SCRAM_MAX_KEY_LEN]; + + if (!parse_scram_secret(current_secrets[i], &iterations, &hash_type, &key_length, + &salt1, stored_key, server_key)) + { + *logdetail = psprintf(_("could not parse SCRAM secret")); + *salt = NULL; + return false; + } + + if (salt2 != NULL) + { + if (strcmp(salt1, salt2)) + { + *logdetail = psprintf(_("inconsistent salts, clearing password")); // TODO: Better message + *salt = NULL; + return false; + } + } + else + salt2 = salt1; + } + } + + for (i = 0; i < num_secrets; i++) + pfree(current_secrets[i]); + if (current_secrets) + pfree(current_secrets); + + if (salt2) + *salt = pstrdup(salt2); + + return true; +} /* * CREATE ROLE @@ -153,8 +235,8 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt) List *addroleto = NIL; /* roles to make this a member of */ List *rolemembers = NIL; /* roles to be members of this role */ List *adminmembers = NIL; /* roles to be admins of this role */ - char *validUntil = NULL; /* time the login is valid until */ - Datum validUntil_datum; /* same, as timestamptz Datum */ + char *validUntil = NULL; /* time the password is valid until */ + Datum validUntil_datum; /* validuntil, as timestamptz Datum */ bool validUntil_null; DefElem *dpassword = NULL; DefElem *dissuper = NULL; @@ -442,11 +524,16 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt) } else { + char *salt; + + if (!get_salt(stmt->role, &salt, &logdetail)) + ereport(ERROR, + (errmsg("could not get a valid salt for password"), + errdetail("%s", logdetail))); + /* Encrypt the password to the requested format. */ - shadow_pass = encrypt_password(Password_encryption, stmt->role, - password); - new_record[Anum_pg_authid_rolpassword - 1] = - CStringGetTextDatum(shadow_pass); + shadow_pass = encrypt_password(Password_encryption, salt, password); + new_record[Anum_pg_authid_rolpassword - 1] = CStringGetTextDatum(shadow_pass); } } else @@ -772,7 +859,7 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt) "SUPERUSER", "SUPERUSER"))); /* - * Most changes to a role require that you both have CREATEROLE privileges + * Most changes to a role require that you have both CREATEROLE privileges * and also ADMIN OPTION on the role. */ if (!have_createrole_privilege() || @@ -931,9 +1018,16 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt) } else { + char *salt; + + if (!get_salt(rolename, &salt, &logdetail)) + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("could not get a valid salt for password"), + errdetail("%s", logdetail))); + /* Encrypt the password to the requested format. */ - shadow_pass = encrypt_password(Password_encryption, rolename, - password); + shadow_pass = encrypt_password(Password_encryption, salt, password); new_record[Anum_pg_authid_rolpassword - 1] = CStringGetTextDatum(shadow_pass); } diff --git a/src/backend/libpq/auth-sasl.c b/src/backend/libpq/auth-sasl.c index c535bc5383..56bd165885 100644 --- a/src/backend/libpq/auth-sasl.c +++ b/src/backend/libpq/auth-sasl.c @@ -49,7 +49,7 @@ * should just pass NULL. */ int -CheckSASLAuth(const pg_be_sasl_mech *mech, Port *port, char *shadow_pass, +CheckSASLAuth(const pg_be_sasl_mech *mech, Port *port, const char **passwords, int num_passwords, const char **logdetail) { StringInfoData sasl_mechs; @@ -136,7 +136,7 @@ CheckSASLAuth(const pg_be_sasl_mech *mech, Port *port, char *shadow_pass, * This is because we don't want to reveal to an attacker what * usernames are valid, nor which users have a valid password. */ - opaq = mech->init(port, selected_mech, shadow_pass); + opaq = mech->init(port, selected_mech, passwords, num_passwords); inputlen = pq_getmsgint(&buf, 4); if (inputlen == -1) diff --git a/src/backend/libpq/auth-scram.c b/src/backend/libpq/auth-scram.c index 118d15b1a1..a0ebfecd69 100644 --- a/src/backend/libpq/auth-scram.c +++ b/src/backend/libpq/auth-scram.c @@ -109,7 +109,7 @@ static void scram_get_mechanisms(Port *port, StringInfo buf); static void *scram_init(Port *port, const char *selected_mech, - const char *shadow_pass); + const char **secrets, const int num_secrets); static int scram_exchange(void *opaq, const char *input, int inputlen, char **output, int *outputlen, const char **logdetail); @@ -132,6 +132,12 @@ typedef enum SCRAM_AUTH_FINISHED } scram_state_enum; +typedef struct +{ + uint8 StoredKey[SCRAM_MAX_KEY_LEN]; + uint8 ServerKey[SCRAM_MAX_KEY_LEN]; +} scram_secret; + typedef struct { scram_state_enum state; @@ -145,10 +151,16 @@ typedef struct pg_cryptohash_type hash_type; int key_length; + /* + * The salt and iterations must be the same for all + * secrets since they are sent as part of the initial message + */ int iterations; char *salt; /* base64-encoded */ - uint8 StoredKey[SCRAM_MAX_KEY_LEN]; - uint8 ServerKey[SCRAM_MAX_KEY_LEN]; + /* Array of possible secrets */ + scram_secret *secrets; + int num_secrets; + int chosen_secret; /* secret chosen during final client message */ /* Fields of the first message from client */ char cbind_flag; @@ -231,17 +243,20 @@ scram_get_mechanisms(Port *port, StringInfo buf) * It should be one of the mechanisms that we support, as returned by * scram_get_mechanisms(). * - * 'shadow_pass' is the role's stored secret, from pg_authid.rolpassword. + * 'passwords' are the role's stored secrets, 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. */ static void * -scram_init(Port *port, const char *selected_mech, const char *shadow_pass) +scram_init(Port *port, const char *selected_mech, const char **secrets, const int num_secrets) { scram_state *state; - bool got_secret; + bool got_secret = false; + int i; + int iterations; + char *salt = NULL; /* base64-encoded */ state = (scram_state *) palloc0(sizeof(scram_state)); state->port = port; @@ -270,49 +285,54 @@ scram_init(Port *port, const char *selected_mech, const char *shadow_pass) /* * Parse the stored secret. */ - if (shadow_pass) + if (secrets) { - int password_type = get_password_type(shadow_pass); - - if (password_type == PASSWORD_TYPE_SCRAM_SHA_256) + state->secrets = palloc0(sizeof(scram_secret) * num_secrets); + state->num_secrets = num_secrets; + for (i = 0; i < num_secrets; i++) { - if (parse_scram_secret(shadow_pass, &state->iterations, - &state->hash_type, &state->key_length, - &state->salt, - state->StoredKey, - state->ServerKey)) - got_secret = true; - else + int password_type = get_password_type(secrets[i]); + + if (password_type == PASSWORD_TYPE_SCRAM_SHA_256) { - /* - * The password looked like a SCRAM secret, but could not be - * parsed. - */ - ereport(LOG, - (errmsg("invalid SCRAM secret for user \"%s\"", - state->port->user_name))); - got_secret = false; + if (parse_scram_secret(secrets[i], &state->iterations, + &state->hash_type, &state->key_length, + &state->salt, + state->secrets[i].StoredKey, + state->secrets[i].ServerKey)) + { + if (salt) + { + /* The stored iterations and salt must match or we cannot proceed, allow failure via mock */ + if (strcmp(salt, state->salt) || iterations != state->iterations) + { + ereport(WARNING, (errmsg("inconsistent salt or iterations for user \"%s\"", + state->port->user_name))); + got_secret = false; /* fail and allow mock creditials to be created */ + pfree(state->secrets); + state->num_secrets = 0; + break; + } + } + else + { + salt = state->salt; + iterations = state->iterations; + got_secret = true; /* We got at least one good SCRAM secret */ + } + } + else + { + /* + * The password looked like a SCRAM secret, but could not be + * parsed. + */ + ereport(LOG, + (errmsg("invalid SCRAM secret for user \"%s\"", + state->port->user_name))); + } } } - else - { - /* - * The user doesn't have SCRAM secret. (You cannot do SCRAM - * authentication with an MD5 hash.) - */ - state->logdetail = psprintf(_("User \"%s\" does not have a valid SCRAM secret."), - state->port->user_name); - got_secret = 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_secret = false; } /* @@ -323,10 +343,13 @@ scram_init(Port *port, const char *selected_mech, const char *shadow_pass) */ if (!got_secret) { + state->secrets = palloc0(sizeof(scram_secret)); + state->num_secrets = 1; + mock_scram_secret(state->port->user_name, &state->hash_type, &state->iterations, &state->key_length, &state->salt, - state->StoredKey, state->ServerKey); + state->secrets[0].StoredKey, state->secrets[0].ServerKey); state->doomed = true; } @@ -474,7 +497,7 @@ scram_exchange(void *opaq, const char *input, int inputlen, * The result is palloc'd, so caller is responsible for freeing it. */ char * -pg_be_scram_build_secret(const char *password) +pg_be_scram_build_secret(const char *password, const char *salt) { char *prep_password; pg_saslprep_rc rc; @@ -491,11 +514,20 @@ pg_be_scram_build_secret(const char *password) if (rc == SASLPREP_SUCCESS) password = (const char *) prep_password; - /* Generate random salt */ - if (!pg_strong_random(saltbuf, SCRAM_DEFAULT_SALT_LEN)) + /* Use passed-in salt, or generate random salt */ + if (!salt && !pg_strong_random(saltbuf, SCRAM_DEFAULT_SALT_LEN)) + { ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("could not generate random salt"))); + } + else if (salt) + { + if (pg_b64_decode(salt, strlen(salt), saltbuf, SCRAM_DEFAULT_SALT_LEN) == -1) + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("could not decode SCRAM salt"))); + } result = scram_build_secret(PG_SHA256, SCRAM_SHA_256_KEY_LEN, saltbuf, SCRAM_DEFAULT_SALT_LEN, @@ -1142,48 +1174,62 @@ verify_client_proof(scram_state *state) uint8 ClientSignature[SCRAM_MAX_KEY_LEN]; uint8 ClientKey[SCRAM_MAX_KEY_LEN]; uint8 client_StoredKey[SCRAM_MAX_KEY_LEN]; - pg_hmac_ctx *ctx = pg_hmac_create(state->hash_type); - int i; + pg_hmac_ctx *ctx; + int i, j; const char *errstr = NULL; - /* * Calculate ClientSignature. Note that we don't log directly a failure * here even when processing the calculations as this could involve a mock * authentication. */ - if (pg_hmac_init(ctx, state->StoredKey, state->key_length) < 0 || - pg_hmac_update(ctx, - (uint8 *) state->client_first_message_bare, - strlen(state->client_first_message_bare)) < 0 || - pg_hmac_update(ctx, (uint8 *) ",", 1) < 0 || - pg_hmac_update(ctx, - (uint8 *) state->server_first_message, - strlen(state->server_first_message)) < 0 || - pg_hmac_update(ctx, (uint8 *) ",", 1) < 0 || - pg_hmac_update(ctx, - (uint8 *) state->client_final_message_without_proof, - strlen(state->client_final_message_without_proof)) < 0 || - pg_hmac_final(ctx, ClientSignature, state->key_length) < 0) + for (j = 0; j < state->num_secrets; j++) { - elog(ERROR, "could not calculate client signature: %s", - pg_hmac_error(ctx)); + ctx = pg_hmac_create(state->hash_type); + elog(LOG, "Trying to verify password %d", j); // TODO: Convert to DEBUG2 + + if (pg_hmac_init(ctx, state->secrets[j].StoredKey, state->key_length) < 0 || + pg_hmac_update(ctx, + (uint8 *) state->client_first_message_bare, + strlen(state->client_first_message_bare)) < 0 || + pg_hmac_update(ctx, (uint8 *) ",", 1) < 0 || + pg_hmac_update(ctx, + (uint8 *) state->server_first_message, + strlen(state->server_first_message)) < 0 || + pg_hmac_update(ctx, (uint8 *) ",", 1) < 0 || + pg_hmac_update(ctx, + (uint8 *) state->client_final_message_without_proof, + strlen(state->client_final_message_without_proof)) < 0 || + pg_hmac_final(ctx, ClientSignature, state->key_length) < 0) + { + // TODO: Convert to DEBUG2 + elog(LOG, "could not calculate client signature for secret %d", j); + pg_hmac_free(ctx); + continue; + } + + // TODO: Convert to DEBUG2 + elog(LOG, "succeeded on %d password", j); + + pg_hmac_free(ctx); + + /* Extract the ClientKey that the client calculated from the proof */ + for (i = 0; i < state->key_length; i++) + ClientKey[i] = state->ClientProof[i] ^ ClientSignature[i]; + + /* Hash it one more time, and compare with StoredKey */ + if (scram_H(ClientKey, state->hash_type, state->key_length, + client_StoredKey, &errstr) < 0) + elog(ERROR, "could not hash stored key: %s", errstr); + + if (memcmp(client_StoredKey, state->secrets[j].StoredKey, state->key_length) == 0) { + // TODO: Convert to DEBUG2 + elog(LOG, "Moving forward with Password %d", j); + state->chosen_secret = j; + return true; + } } - pg_hmac_free(ctx); - - /* Extract the ClientKey that the client calculated from the proof */ - for (i = 0; i < state->key_length; i++) - ClientKey[i] = state->ClientProof[i] ^ ClientSignature[i]; - - /* Hash it one more time, and compare with StoredKey */ - if (scram_H(ClientKey, state->hash_type, state->key_length, - client_StoredKey, &errstr) < 0) - elog(ERROR, "could not hash stored key: %s", errstr); - - if (memcmp(client_StoredKey, state->StoredKey, state->key_length) != 0) - return false; - - return true; + return false; } /* @@ -1409,7 +1455,7 @@ build_server_final_message(scram_state *state) pg_hmac_ctx *ctx = pg_hmac_create(state->hash_type); /* calculate ServerSignature */ - if (pg_hmac_init(ctx, state->ServerKey, state->key_length) < 0 || + if (pg_hmac_init(ctx, state->secrets[state->chosen_secret].ServerKey, state->key_length) < 0 || pg_hmac_update(ctx, (uint8 *) state->client_first_message_bare, strlen(state->client_first_message_bare)) < 0 || diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c index 81dabb9c27..7cc9b13645 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -57,8 +57,7 @@ static void set_authn_id(Port *port, const char *id); static int CheckPasswordAuth(Port *port, const char **logdetail); static int CheckPWChallengeAuth(Port *port, const char **logdetail); -static int CheckMD5Auth(Port *port, char *shadow_pass, - const char **logdetail); +static int CheckMD5Auth(Port *port, const char **passwords, int num_passwords, const char **logdetail); /*---------------------------------------------------------------- @@ -789,8 +788,9 @@ static int CheckPasswordAuth(Port *port, const char **logdetail) { char *passwd; - int result; - char *shadow_pass; + int result = STATUS_ERROR; + int i, num_passwords; + char **passwords; sendAuthRequest(port, AUTH_REQ_PASSWORD, NULL, 0); @@ -798,17 +798,22 @@ CheckPasswordAuth(Port *port, const char **logdetail) if (passwd == NULL) return STATUS_EOF; /* client wouldn't send password */ - shadow_pass = get_role_password(port->user_name, logdetail); - if (shadow_pass) + passwords = get_role_passwords(port->user_name, logdetail, &num_passwords); + if (passwords != NULL) { - result = plain_crypt_verify(port->user_name, shadow_pass, passwd, - logdetail); - } - else - result = STATUS_ERROR; + for (i = 0; i < num_passwords; i++) + { + result = plain_crypt_verify(port->user_name, passwords[i], passwd, + logdetail); + if (result == STATUS_OK) + break; /* Found a matching password, no need to try any others */ + } + for (i = 0; i < num_passwords; i++) + pfree(passwords[i]); + + pfree(passwords); + } - if (shadow_pass) - pfree(shadow_pass); pfree(passwd); if (result == STATUS_OK) @@ -823,54 +828,81 @@ CheckPasswordAuth(Port *port, const char **logdetail) static int CheckPWChallengeAuth(Port *port, const char **logdetail) { - int auth_result; - char *shadow_pass; - PasswordType pwtype; + bool scram_pw_avail = false; + int auth_result = STATUS_ERROR; + int i, num_passwords; + char **passwords; + PasswordType pwtype; Assert(port->hba->auth_method == uaSCRAM || port->hba->auth_method == uaMD5); - /* First look up the user's password. */ - shadow_pass = get_role_password(port->user_name, logdetail); + /* First look up the user's passwords. */ + passwords = get_role_passwords(port->user_name, logdetail, &num_passwords); /* - * If the user does not exist, or has no password or it's expired, we - * still go through the motions of authentication, to avoid revealing to + * If the user does not exist, or has no passwords or they're all expired, + * we still go through the motions of authentication, to avoid revealing to * the client that the user didn't exist. If 'md5' is allowed, we choose * whether to use 'md5' or 'scram-sha-256' authentication based on current * password_encryption setting. The idea is that most genuine users * probably have a password of that type, and if we pretend that this user * had a password of that type, too, it "blends in" best. */ - if (!shadow_pass) + if (!passwords) pwtype = Password_encryption; - else - pwtype = get_password_type(shadow_pass); /* * If 'md5' authentication is allowed, decide whether to perform 'md5' or * 'scram-sha-256' authentication based on the type of password the user - * has. If it's an MD5 hash, we must do MD5 authentication, and if it's a - * SCRAM secret, we must do SCRAM authentication. + * has. If there's a SCRAM password available then we'll do SCRAM, otherwise we + * will fall back to trying to use MD5. * * If MD5 authentication is not allowed, always use SCRAM. If the user * had an MD5 password, CheckSASLAuth() with the SCRAM mechanism will * fail. */ - if (port->hba->auth_method == uaMD5 && pwtype == PASSWORD_TYPE_MD5) - auth_result = CheckMD5Auth(port, shadow_pass, logdetail); + if (passwords == NULL) + { + if (port->hba->auth_method == uaMD5 && pwtype == PASSWORD_TYPE_MD5) + auth_result = CheckMD5Auth(port, (const char **) NULL, 0, logdetail); + else + auth_result = CheckSASLAuth(&pg_be_scram_mech, port, + (const char **) NULL, 0, logdetail); + } else - auth_result = CheckSASLAuth(&pg_be_scram_mech, port, shadow_pass, - logdetail); + { + for (i = 0; i < num_passwords; i++) + { + if (get_password_type(passwords[i]) == PASSWORD_TYPE_SCRAM_SHA_256) + { + scram_pw_avail = true; + break; + } + } - if (shadow_pass) - pfree(shadow_pass); + if (port->hba->auth_method == uaMD5 && !scram_pw_avail) + auth_result = CheckMD5Auth(port, (const char **) passwords, num_passwords, logdetail); + else + auth_result = CheckSASLAuth(&pg_be_scram_mech, port, (const char **) passwords, num_passwords, + logdetail); + + for (i = 0; i < num_passwords; i++) + { + if (passwords[i] != NULL) + pfree(passwords[i]); + else + ereport(DEBUG2, + (errmsg("Password %d was null", i))); + } + pfree(passwords); + } /* - * If get_role_password() returned error, return error, even if the + * If get_role_passwords() returned error, return error, even if the * authentication succeeded. */ - if (!shadow_pass) + if (!passwords) { Assert(auth_result != STATUS_OK); return STATUS_ERROR; @@ -883,11 +915,12 @@ CheckPWChallengeAuth(Port *port, const char **logdetail) } static int -CheckMD5Auth(Port *port, char *shadow_pass, const char **logdetail) +CheckMD5Auth(Port *port, const char **passwords, int num_passwords, const char **logdetail) { char md5Salt[4]; /* Password salt */ char *passwd; - int result; + int result = STATUS_ERROR; + int i; /* include the salt to use for computing the response */ if (!pg_strong_random(md5Salt, 4)) @@ -903,12 +936,13 @@ CheckMD5Auth(Port *port, char *shadow_pass, const char **logdetail) if (passwd == NULL) return STATUS_EOF; /* client wouldn't send password */ - if (shadow_pass) - result = md5_crypt_verify(port->user_name, shadow_pass, passwd, + for (i = 0; i < num_passwords; i++) + { + result = md5_crypt_verify(port->user_name, passwords[i], passwd, md5Salt, 4, logdetail); - else - result = STATUS_ERROR; - + if (result == STATUS_OK) + break; + } pfree(passwd); return result; diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c index ef496a0bea..3b47af1269 100644 --- a/src/backend/libpq/crypt.c +++ b/src/backend/libpq/crypt.c @@ -27,20 +27,29 @@ /* - * Fetch stored password for a user, for authentication. + * Fetch valid stored passwords for a user, for authentication. * * On error, returns NULL, and stores a palloc'd string describing the reason, * for the postmaster log, in *logdetail. The error reason should *not* be * sent to the client, to avoid giving away user information! */ -char * -get_role_password(const char *role, const char **logdetail) +char ** +get_role_passwords(const char *role, const char **logdetail, int *num_passwords) { TimestampTz vuntil = 0; + TimestampTz second_vuntil = 0; + TimestampTz current_ts; HeapTuple roleTup; Datum datum; - bool isnull; + Datum second_datum; + bool vuntil_isnull; + bool second_vuntil_isnull; + bool password_isnull; + bool second_password_isnull; char *shadow_pass; + char *second_shadow_pass; + + *num_passwords = 0; /* Get role info from pg_authid */ roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(role)); @@ -52,34 +61,73 @@ get_role_password(const char *role, const char **logdetail) } datum = SysCacheGetAttr(AUTHNAME, roleTup, - Anum_pg_authid_rolpassword, &isnull); - if (isnull) + Anum_pg_authid_rolpassword, + &password_isnull); + second_datum = SysCacheGetAttr(AUTHNAME, roleTup, + Anum_pg_authid_rolsecondpassword, + &second_password_isnull); + if (password_isnull && second_password_isnull) { ReleaseSysCache(roleTup); *logdetail = psprintf(_("User \"%s\" has no password assigned."), role); return NULL; /* user has no password */ } - shadow_pass = TextDatumGetCString(datum); + + if (!password_isnull) + shadow_pass = TextDatumGetCString(datum); + if (!second_password_isnull) + second_shadow_pass = TextDatumGetCString(second_datum); datum = SysCacheGetAttr(AUTHNAME, roleTup, - Anum_pg_authid_rolvaliduntil, &isnull); - if (!isnull) + Anum_pg_authid_rolvaliduntil, &vuntil_isnull); + second_datum = SysCacheGetAttr(AUTHNAME, roleTup, + Anum_pg_authid_rolsecondvaliduntil, + &second_vuntil_isnull); + if (!vuntil_isnull) vuntil = DatumGetTimestampTz(datum); + if (!second_vuntil_isnull) + second_vuntil = DatumGetTimestampTz(second_datum); ReleaseSysCache(roleTup); /* * Password OK, but check to be sure we are not past rolvaliduntil */ - if (!isnull && vuntil < GetCurrentTimestamp()) + current_ts = GetCurrentTimestamp(); + *num_passwords = (!password_isnull && + !vuntil_isnull && + vuntil >= current_ts) + + (!second_password_isnull && + !second_vuntil_isnull && + second_vuntil >= current_ts); + + if (*num_passwords >= 1) + { + int i = 0; + char **passwords = palloc(sizeof(char *) * (*num_passwords)); + + if (!password_isnull && !vuntil_isnull && vuntil >= current_ts) + { + passwords[i] = shadow_pass; + i++; + } + + if (!second_password_isnull && !second_vuntil_isnull && + second_vuntil >= current_ts) + { + passwords[i] = second_shadow_pass; + i++; + } + + return passwords; + } + else { *logdetail = psprintf(_("User \"%s\" has an expired password."), role); return NULL; } - - return shadow_pass; } /* @@ -113,7 +161,7 @@ get_password_type(const char *shadow_pass) * hash, so it is stored as it is regardless of the requested type. */ char * -encrypt_password(PasswordType target_type, const char *role, +encrypt_password(PasswordType target_type, const char *salt, const char *password) { PasswordType guessed_type = get_password_type(password); @@ -134,13 +182,13 @@ encrypt_password(PasswordType target_type, const char *role, case PASSWORD_TYPE_MD5: encrypted_password = palloc(MD5_PASSWD_LEN + 1); - if (!pg_md5_encrypt(password, role, strlen(role), + if (!pg_md5_encrypt(password, salt, strlen(salt), encrypted_password, &errstr)) elog(ERROR, "password encryption failed: %s", errstr); return encrypted_password; case PASSWORD_TYPE_SCRAM_SHA_256: - return pg_be_scram_build_secret(password); + return pg_be_scram_build_secret(password, salt); case PASSWORD_TYPE_PLAINTEXT: elog(ERROR, "cannot encrypt password with 'plaintext'"); diff --git a/src/include/libpq/crypt.h b/src/include/libpq/crypt.h index ddcd27469a..966eb4e627 100644 --- a/src/include/libpq/crypt.h +++ b/src/include/libpq/crypt.h @@ -35,7 +35,7 @@ extern PasswordType get_password_type(const char *shadow_pass); extern char *encrypt_password(PasswordType target_type, const char *role, const char *password); -extern char *get_role_password(const char *role, const char **logdetail); +extern char **get_role_passwords(const char *role, const char **logdetail, int *num); extern int md5_crypt_verify(const char *role, const char *shadow_pass, const char *client_pass, const char *md5_salt, diff --git a/src/include/libpq/sasl.h b/src/include/libpq/sasl.h index 7a1b1ed0a0..12c5c9602b 100644 --- a/src/include/libpq/sasl.h +++ b/src/include/libpq/sasl.h @@ -77,7 +77,7 @@ typedef struct pg_be_sasl_mech * disclosing valid user names. *--------- */ - void *(*init) (Port *port, const char *mech, const char *shadow_pass); + void *(*init) (Port *port, const char *mech, const char **secrets, const int num_secrets); /*--------- * exchange() @@ -131,6 +131,6 @@ typedef struct pg_be_sasl_mech /* Common implementation for auth.c */ extern int CheckSASLAuth(const pg_be_sasl_mech *mech, Port *port, - char *shadow_pass, const char **logdetail); + const char **passwords, int num_passwords, const char **logdetail); #endif /* PG_SASL_H */ diff --git a/src/include/libpq/scram.h b/src/include/libpq/scram.h index 310bc36517..07d9cc3990 100644 --- a/src/include/libpq/scram.h +++ b/src/include/libpq/scram.h @@ -25,7 +25,7 @@ extern PGDLLIMPORT int scram_sha_256_iterations; extern PGDLLIMPORT const pg_be_sasl_mech pg_be_scram_mech; /* Routines to handle and check SCRAM-SHA-256 secret */ -extern char *pg_be_scram_build_secret(const char *password); +extern char *pg_be_scram_build_secret(const char *password, const char *salt); extern bool parse_scram_secret(const char *secret, int *iterations, pg_cryptohash_type *hash_type, -- 2.41.0