On Tue, Dec 20, 2022 at 08:58:38AM +0900, Michael Paquier wrote: > Thanks! I have applied for I have here.. There are other pieces to > think about in this area.
FYI, I have spent a few hours looking at the remaining parts of the SCRAM code that could be simplified if a new hash method is added, and this b3bb7d1 has really made things easier. There are a few things that will need more thoughts. Here are my notes, assuming that SHA-512 is done: 1) HBA entries had better use a new keyword for scram-sha-512, implying a new uaSCRAM512 to combine with the existing uaSCRAM. One reason behind that it to advertise the mechanisms supported back to the client depending on the matching HBA entry. 2) If a role has a SCRAM-SHA-256 password and the HBA entry matches scram-sha-512, the SASL exchange needs to go through the mock process with SHA-512 and fail. 3) If a role has a SCRAM-SHA-512 password and the HBA entry matches scram-sha-256, the SASL exchange needs to go through the mock process with SHA-256 and fail. 4) The case of MD5 is something that looks a bit tricky at quick glance. We know that if the role has a MD5 password stored, we will fail anyway. So we could just advertise the SHA-256 mechanisms in this case and map the mock to that? 5) The mechanism choice in libpq needs to be reworked a bit based on what the backend sends. There may be no point in advertising all the SHA-256 and SHA-512 mechanisms at the same time, I guess. Attached is a WIP patch that I have played with. This shows the parts of the code that would need more thoughts if implementing such things. This works for the cases 1~3 (see the TAP tests). I've given up on the MD5 case 4 for now, but perhaps I just missed a simple trick. 5 in libpq uses dirty tricks. I have marked this CF entry as committed, and I'll come back to each relevant part on new separate threads. -- Michael
From 49a3c993a1df722da01f16839eb1d6185c205092 Mon Sep 17 00:00:00 2001 From: Michael Paquier <mich...@paquier.xyz> Date: Tue, 20 Dec 2022 16:14:41 +0900 Subject: [PATCH] WIP SCRAM-SHA-512 --- src/include/common/scram-common.h | 5 +- src/include/libpq/crypt.h | 3 +- src/include/libpq/hba.h | 3 +- src/include/libpq/scram.h | 3 +- src/backend/libpq/auth-scram.c | 143 ++++++++++++++++++---- src/backend/libpq/auth.c | 15 ++- src/backend/libpq/crypt.c | 13 +- src/backend/libpq/hba.c | 5 +- src/backend/utils/misc/guc_tables.c | 1 + src/common/scram-common.c | 13 +- src/interfaces/libpq/fe-auth-scram.c | 24 +++- src/interfaces/libpq/fe-auth.c | 52 +++++--- src/interfaces/libpq/fe-auth.h | 2 + src/test/authentication/t/001_password.pl | 21 +++- 14 files changed, 236 insertions(+), 67 deletions(-) diff --git a/src/include/common/scram-common.h b/src/include/common/scram-common.h index 953d30ac54..10996fa735 100644 --- a/src/include/common/scram-common.h +++ b/src/include/common/scram-common.h @@ -19,15 +19,18 @@ /* Name of SCRAM mechanisms per IANA */ #define SCRAM_SHA_256_NAME "SCRAM-SHA-256" #define SCRAM_SHA_256_PLUS_NAME "SCRAM-SHA-256-PLUS" /* with channel binding */ +#define SCRAM_SHA_512_NAME "SCRAM-SHA-512" +#define SCRAM_SHA_512_PLUS_NAME "SCRAM-SHA-512-PLUS" /* with channel binding */ /* Length of SCRAM keys (client and server) */ #define SCRAM_SHA_256_KEY_LEN PG_SHA256_DIGEST_LENGTH +#define SCRAM_SHA_512_KEY_LEN PG_SHA512_DIGEST_LENGTH /* * Size of buffers used internally by SCRAM routines, that should be the * maximum of SCRAM_SHA_*_KEY_LEN among the hash methods supported. */ -#define SCRAM_MAX_KEY_LEN SCRAM_SHA_256_KEY_LEN +#define SCRAM_MAX_KEY_LEN SCRAM_SHA_512_KEY_LEN /* * Size of random nonce generated in the authentication exchange. This diff --git a/src/include/libpq/crypt.h b/src/include/libpq/crypt.h index 3238cf66d3..992d4922fc 100644 --- a/src/include/libpq/crypt.h +++ b/src/include/libpq/crypt.h @@ -28,7 +28,8 @@ typedef enum PasswordType { PASSWORD_TYPE_PLAINTEXT = 0, PASSWORD_TYPE_MD5, - PASSWORD_TYPE_SCRAM_SHA_256 + PASSWORD_TYPE_SCRAM_SHA_256, + PASSWORD_TYPE_SCRAM_SHA_512 } PasswordType; extern PasswordType get_password_type(const char *shadow_pass); diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h index 90c51ad6fa..dc536225ef 100644 --- a/src/include/libpq/hba.h +++ b/src/include/libpq/hba.h @@ -30,7 +30,8 @@ typedef enum UserAuth uaIdent, uaPassword, uaMD5, - uaSCRAM, + uaSCRAM256, + uaSCRAM512, uaGSS, uaSSPI, uaPAM, diff --git a/src/include/libpq/scram.h b/src/include/libpq/scram.h index b29501ef96..f2aa5c82c2 100644 --- a/src/include/libpq/scram.h +++ b/src/include/libpq/scram.h @@ -22,7 +22,8 @@ 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, + pg_cryptohash_type hash_type); extern bool parse_scram_secret(const char *secret, int *iterations, pg_cryptohash_type *hash_type, diff --git a/src/backend/libpq/auth-scram.c b/src/backend/libpq/auth-scram.c index 126eb70974..01cf5da754 100644 --- a/src/backend/libpq/auth-scram.c +++ b/src/backend/libpq/auth-scram.c @@ -181,7 +181,9 @@ static char *build_server_first_message(scram_state *state); static char *build_server_final_message(scram_state *state); static bool verify_client_proof(scram_state *state); static bool verify_final_nonce(scram_state *state); -static void mock_scram_secret(const char *username, pg_cryptohash_type *hash_type, +static void mock_scram_secret(const char *username, + pg_cryptohash_type mock_hash, + pg_cryptohash_type *hash_type, int *iterations, int *key_length, char **salt, uint8 *stored_key, uint8 *server_key); static bool is_scram_printable(char *p); @@ -210,12 +212,28 @@ scram_get_mechanisms(Port *port, StringInfo buf) #ifdef HAVE_BE_TLS_GET_CERTIFICATE_HASH if (port->ssl_in_use) { - appendStringInfoString(buf, SCRAM_SHA_256_PLUS_NAME); - appendStringInfoChar(buf, '\0'); + if (port->hba->auth_method == uaSCRAM512) + { + appendStringInfoString(buf, SCRAM_SHA_512_PLUS_NAME); + appendStringInfoChar(buf, '\0'); + } + else + { + appendStringInfoString(buf, SCRAM_SHA_256_PLUS_NAME); + appendStringInfoChar(buf, '\0'); + } } #endif - appendStringInfoString(buf, SCRAM_SHA_256_NAME); - appendStringInfoChar(buf, '\0'); + if (port->hba->auth_method == uaSCRAM512) + { + appendStringInfoString(buf, SCRAM_SHA_512_NAME); + appendStringInfoChar(buf, '\0'); + } + else + { + appendStringInfoString(buf, SCRAM_SHA_256_NAME); + appendStringInfoChar(buf, '\0'); + } } /* @@ -238,11 +256,14 @@ scram_init(Port *port, const char *selected_mech, const char *shadow_pass) { scram_state *state; bool got_secret; + pg_cryptohash_type mock_hash; state = (scram_state *) palloc0(sizeof(scram_state)); state->port = port; state->state = SCRAM_AUTH_INIT; + mock_hash = PG_SHA256; + /* * Parse the selected mechanism. * @@ -253,17 +274,42 @@ scram_init(Port *port, const char *selected_mech, const char *shadow_pass) * 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) + if ((strcmp(selected_mech, SCRAM_SHA_256_PLUS_NAME) == 0 || + strcmp(selected_mech, SCRAM_SHA_512_PLUS_NAME) == 0) && + port->ssl_in_use) state->channel_binding_in_use = true; else #endif - if (strcmp(selected_mech, SCRAM_SHA_256_NAME) == 0) + if (strcmp(selected_mech, SCRAM_SHA_256_NAME) == 0 || + strcmp(selected_mech, SCRAM_SHA_512_NAME) == 0) state->channel_binding_in_use = false; else ereport(ERROR, (errcode(ERRCODE_PROTOCOL_VIOLATION), errmsg("client selected an invalid SASL authentication mechanism"))); + /* + * If the HBA entry does not match with the selected mechanism, the + * authentication should fail, so go through the mock process in this + * case. + */ + if (port->hba->auth_method == uaSCRAM256 && + strcmp(selected_mech, SCRAM_SHA_256_PLUS_NAME) != 0 && + strcmp(selected_mech, SCRAM_SHA_256_NAME) != 0) + { + /* the client has selected one of the SHA-512 mechanisms */ + shadow_pass = NULL; + mock_hash = PG_SHA512; + } + if (port->hba->auth_method == uaSCRAM512 && + strcmp(selected_mech, SCRAM_SHA_512_PLUS_NAME) != 0 && + strcmp(selected_mech, SCRAM_SHA_512_NAME) != 0) + { + /* the client has selected one of the SHA-256 mechanisms */ + shadow_pass = NULL; + mock_hash = PG_SHA256; + } + /* * Parse the stored secret. */ @@ -271,15 +317,37 @@ scram_init(Port *port, const char *selected_mech, const char *shadow_pass) { int password_type = get_password_type(shadow_pass); - if (password_type == PASSWORD_TYPE_SCRAM_SHA_256) + if (password_type == PASSWORD_TYPE_SCRAM_SHA_256 || + password_type == PASSWORD_TYPE_SCRAM_SHA_512) { - if (parse_scram_secret(shadow_pass, &state->iterations, - &state->hash_type, &state->key_length, - &state->salt, - state->StoredKey, - state->ServerKey)) - got_secret = true; - else + got_secret = parse_scram_secret(shadow_pass, + &state->iterations, + &state->hash_type, + &state->key_length, + &state->salt, + state->StoredKey, + state->ServerKey); + + /* + * At this point, we already know that the HBA entry maps + * with a matching mechanism. If the password type does not + * not match any of that, do a mock processing using the + * hash selected by the client's mechanism. + */ + if (port->hba->auth_method == uaSCRAM256 && + password_type != PASSWORD_TYPE_SCRAM_SHA_256) + { + mock_hash = PG_SHA256; + got_secret = false; + } + else if (port->hba->auth_method == uaSCRAM512 && + password_type != PASSWORD_TYPE_SCRAM_SHA_512) + { + mock_hash = PG_SHA512; + got_secret = false; + } + + if (!got_secret) { /* * The password looked like a SCRAM secret, but could not be @@ -288,7 +356,6 @@ scram_init(Port *port, const char *selected_mech, const char *shadow_pass) ereport(LOG, (errmsg("invalid SCRAM secret for user \"%s\"", state->port->user_name))); - got_secret = false; } } else @@ -309,6 +376,7 @@ scram_init(Port *port, const char *selected_mech, const char *shadow_pass) * considered normal, since the caller requested it, so don't set log * detail. */ + mock_hash = PG_SHA256; got_secret = false; } @@ -320,7 +388,8 @@ scram_init(Port *port, const char *selected_mech, const char *shadow_pass) */ if (!got_secret) { - mock_scram_secret(state->port->user_name, &state->hash_type, + mock_scram_secret(state->port->user_name, mock_hash, + &state->hash_type, &state->iterations, &state->key_length, &state->salt, state->StoredKey, state->ServerKey); @@ -471,7 +540,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, pg_cryptohash_type hash_type) { char *prep_password; pg_saslprep_rc rc; @@ -494,7 +563,9 @@ pg_be_scram_build_secret(const char *password) (errcode(ERRCODE_INTERNAL_ERROR), errmsg("could not generate random salt"))); - result = scram_build_secret(PG_SHA256, SCRAM_SHA_256_KEY_LEN, + result = scram_build_secret(hash_type, + (hash_type == PG_SHA512) ? + SCRAM_SHA_512_KEY_LEN : SCRAM_SHA_256_KEY_LEN, saltbuf, SCRAM_DEFAULT_SALT_LEN, SCRAM_DEFAULT_ITERATIONS, password, &errstr); @@ -622,10 +693,18 @@ parse_scram_secret(const char *secret, int *iterations, goto invalid_secret; /* Parse the fields */ - if (strcmp(scheme_str, "SCRAM-SHA-256") != 0) + if (strcmp(scheme_str, "SCRAM-SHA-256") == 0) + { + *hash_type = PG_SHA256; + *key_length = SCRAM_SHA_256_KEY_LEN; + } + else if (strcmp(scheme_str, "SCRAM-SHA-512") == 0) + { + *hash_type = PG_SHA512; + *key_length = SCRAM_SHA_512_KEY_LEN; + } + else goto invalid_secret; - *hash_type = PG_SHA256; - *key_length = SCRAM_SHA_256_KEY_LEN; errno = 0; *iterations = strtol(iterations_str, &p, 10); @@ -675,14 +754,16 @@ invalid_secret: * * In a normal authentication, these are extracted from the secret * stored in the server. This function generates values that look - * realistic, for when there is no stored secret, using SCRAM-SHA-256. + * realistic, for when there is no stored secret, using the hash method + * specified by mock_hash. * * Like in parse_scram_secret(), for 'stored_key' and 'server_key', the * caller must pass pre-allocated buffers of size SCRAM_MAX_KEY_LEN, and * the buffer for the salt is palloc'd by this function. */ static void -mock_scram_secret(const char *username, pg_cryptohash_type *hash_type, +mock_scram_secret(const char *username, pg_cryptohash_type mock_hash, + pg_cryptohash_type *hash_type, int *iterations, int *key_length, char **salt, uint8 *stored_key, uint8 *server_key) { @@ -691,8 +772,16 @@ mock_scram_secret(const char *username, pg_cryptohash_type *hash_type, int encoded_len; /* Enforce the use of SHA-256, which would be realistic enough */ - *hash_type = PG_SHA256; - *key_length = SCRAM_SHA_256_KEY_LEN; + if (mock_hash == PG_SHA256) + { + *hash_type = PG_SHA256; + *key_length = SCRAM_SHA_256_KEY_LEN; + } + else + { + *hash_type = PG_SHA512; + *key_length = SCRAM_SHA_512_KEY_LEN; + } /* * Generate deterministic salt. @@ -1477,7 +1566,7 @@ scram_mock_salt(const char *username, pg_cryptohash_type hash_type, * This may be worth refreshing if support for more hash methods is\ * added. */ - Assert(hash_type == PG_SHA256); + Assert(hash_type == PG_SHA256 || hash_type == PG_SHA512); ctx = pg_cryptohash_create(hash_type); if (pg_cryptohash_init(ctx) < 0 || diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c index e2f723d188..6109953e8b 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -276,7 +276,8 @@ auth_failed(Port *port, int status, const char *logdetail) break; case uaPassword: case uaMD5: - case uaSCRAM: + case uaSCRAM256: + case uaSCRAM512: errstr = gettext_noop("password authentication failed for user \"%s\""); /* We use it to indicate if a .pgpass password failed. */ errcode_return = ERRCODE_INVALID_PASSWORD; @@ -585,7 +586,8 @@ ClientAuthentication(Port *port) break; case uaMD5: - case uaSCRAM: + case uaSCRAM256: + case uaSCRAM512: status = CheckPWChallengeAuth(port, &logdetail); break; @@ -806,7 +808,8 @@ CheckPWChallengeAuth(Port *port, const char **logdetail) char *shadow_pass; PasswordType pwtype; - Assert(port->hba->auth_method == uaSCRAM || + Assert(port->hba->auth_method == uaSCRAM256 || + port->hba->auth_method == uaSCRAM512 || port->hba->auth_method == uaMD5); /* First look up the user's password. */ @@ -828,9 +831,9 @@ CheckPWChallengeAuth(Port *port, const char **logdetail) /* * 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. + * 'scram-sha-{256,512}' 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. * * If MD5 authentication is not allowed, always use SCRAM. If the user * had an MD5 password, CheckSASLAuth() with the SCRAM mechanism will diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c index a81af0749a..3c0f49cb05 100644 --- a/src/backend/libpq/crypt.c +++ b/src/backend/libpq/crypt.c @@ -101,7 +101,12 @@ get_password_type(const char *shadow_pass) return PASSWORD_TYPE_MD5; if (parse_scram_secret(shadow_pass, &iterations, &hash_type, &key_length, &encoded_salt, stored_key, server_key)) - return PASSWORD_TYPE_SCRAM_SHA_256; + { + if (hash_type == PG_SHA512) + return PASSWORD_TYPE_SCRAM_SHA_512; + else + return PASSWORD_TYPE_SCRAM_SHA_256; + } return PASSWORD_TYPE_PLAINTEXT; } @@ -140,7 +145,10 @@ encrypt_password(PasswordType target_type, const char *role, return encrypted_password; case PASSWORD_TYPE_SCRAM_SHA_256: - return pg_be_scram_build_secret(password); + return pg_be_scram_build_secret(password, PG_SHA256); + + case PASSWORD_TYPE_SCRAM_SHA_512: + return pg_be_scram_build_secret(password, PG_SHA512); case PASSWORD_TYPE_PLAINTEXT: elog(ERROR, "cannot encrypt password with 'plaintext'"); @@ -235,6 +243,7 @@ plain_crypt_verify(const char *role, const char *shadow_pass, switch (get_password_type(shadow_pass)) { case PASSWORD_TYPE_SCRAM_SHA_256: + case PASSWORD_TYPE_SCRAM_SHA_512: if (scram_verify_plain_password(role, client_pass, shadow_pass)) diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c index 870b907697..53fd31de72 100644 --- a/src/backend/libpq/hba.c +++ b/src/backend/libpq/hba.c @@ -117,6 +117,7 @@ static const char *const UserAuthName[] = "password", "md5", "scram-sha-256", + "scram-sha-512", "gss", "sspi", "pam", @@ -1776,7 +1777,9 @@ parse_hba_line(TokenizedAuthLine *tok_line, int elevel) parsedline->auth_method = uaMD5; } else if (strcmp(token->string, "scram-sha-256") == 0) - parsedline->auth_method = uaSCRAM; + parsedline->auth_method = uaSCRAM256; + else if (strcmp(token->string, "scram-sha-512") == 0) + parsedline->auth_method = uaSCRAM512; else if (strcmp(token->string, "pam") == 0) #ifdef USE_PAM parsedline->auth_method = uaPAM; diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c index 1bf14eec66..564173407a 100644 --- a/src/backend/utils/misc/guc_tables.c +++ b/src/backend/utils/misc/guc_tables.c @@ -383,6 +383,7 @@ static const struct config_enum_entry plan_cache_mode_options[] = { static const struct config_enum_entry password_encryption_options[] = { {"md5", PASSWORD_TYPE_MD5, false}, {"scram-sha-256", PASSWORD_TYPE_SCRAM_SHA_256, false}, + {"scram-sha-512", PASSWORD_TYPE_SCRAM_SHA_512, false}, {NULL, 0, false} }; diff --git a/src/common/scram-common.c b/src/common/scram-common.c index bffbbb4317..d06669f2ee 100644 --- a/src/common/scram-common.c +++ b/src/common/scram-common.c @@ -203,6 +203,7 @@ scram_build_secret(pg_cryptohash_type hash_type, int key_length, uint8 salted_password[SCRAM_MAX_KEY_LEN]; uint8 stored_key[SCRAM_MAX_KEY_LEN]; uint8 server_key[SCRAM_MAX_KEY_LEN]; + char *header; char *result; char *p; int maxlen; @@ -212,7 +213,8 @@ scram_build_secret(pg_cryptohash_type hash_type, int key_length, int encoded_result; /* Only this hash method is supported currently */ - Assert(hash_type == PG_SHA256); + Assert(hash_type == PG_SHA256 || + hash_type == PG_SHA512); if (iterations <= 0) iterations = SCRAM_DEFAULT_ITERATIONS; @@ -246,7 +248,12 @@ scram_build_secret(pg_cryptohash_type hash_type, int key_length, encoded_stored_len = pg_b64_enc_len(key_length); encoded_server_len = pg_b64_enc_len(key_length); - maxlen = strlen("SCRAM-SHA-256") + 1 + if (hash_type == PG_SHA256) + header = "SCRAM-SHA-256"; + else + header = "SCRAM-SHA-512"; + + maxlen = strlen(header) + 1 + 10 + 1 /* iteration count */ + encoded_salt_len + 1 /* Base64-encoded salt */ + encoded_stored_len + 1 /* Base64-encoded StoredKey */ @@ -263,7 +270,7 @@ scram_build_secret(pg_cryptohash_type hash_type, int key_length, result = palloc(maxlen); #endif - p = result + sprintf(result, "SCRAM-SHA-256$%d:", iterations); + p = result + sprintf(result, "%s$%d:", header, iterations); /* salt */ encoded_result = pg_b64_encode(salt, saltlen, p, encoded_salt_len); diff --git a/src/interfaces/libpq/fe-auth-scram.c b/src/interfaces/libpq/fe-auth-scram.c index 7410d5ba52..1f6c47a942 100644 --- a/src/interfaces/libpq/fe-auth-scram.c +++ b/src/interfaces/libpq/fe-auth-scram.c @@ -110,8 +110,17 @@ scram_init(PGconn *conn, memset(state, 0, sizeof(fe_scram_state)); state->conn = conn; state->state = FE_SCRAM_INIT; - state->key_length = SCRAM_SHA_256_KEY_LEN; - state->hash_type = PG_SHA256; + if (strcmp(sasl_mechanism, SCRAM_SHA_512_NAME) == 0 || + strcmp(sasl_mechanism, SCRAM_SHA_512_PLUS_NAME) == 0) + { + state->key_length = SCRAM_SHA_512_KEY_LEN; + state->hash_type = PG_SHA512; + } + else + { + state->key_length = SCRAM_SHA_256_KEY_LEN; + state->hash_type = PG_SHA256; + } state->sasl_mechanism = strdup(sasl_mechanism); if (!state->sasl_mechanism) @@ -165,7 +174,8 @@ scram_channel_bound(void *opaq) return false; /* channel binding mechanism not used */ - if (strcmp(state->sasl_mechanism, SCRAM_SHA_256_PLUS_NAME) != 0) + if (strcmp(state->sasl_mechanism, SCRAM_SHA_256_PLUS_NAME) != 0 && + strcmp(state->sasl_mechanism, SCRAM_SHA_512_PLUS_NAME) != 0) return false; /* all clear! */ @@ -894,7 +904,8 @@ verify_server_signature(fe_scram_state *state, bool *match, * error details. */ char * -pg_fe_scram_build_secret(const char *password, const char **errstr) +pg_fe_scram_build_secret(const char *password, pg_cryptohash_type hash_type, + const char **errstr) { char *prep_password; pg_saslprep_rc rc; @@ -924,7 +935,10 @@ pg_fe_scram_build_secret(const char *password, const char **errstr) return NULL; } - result = scram_build_secret(PG_SHA256, SCRAM_SHA_256_KEY_LEN, saltbuf, + result = scram_build_secret(hash_type, + (hash_type == PG_SHA512) ? + SCRAM_SHA_512_KEY_LEN : SCRAM_SHA_256_KEY_LEN, + saltbuf, SCRAM_DEFAULT_SALT_LEN, SCRAM_DEFAULT_ITERATIONS, password, errstr); diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c index 4a6c358bb6..6b17ebcf9d 100644 --- a/src/interfaces/libpq/fe-auth.c +++ b/src/interfaces/libpq/fe-auth.c @@ -431,8 +431,8 @@ pg_SASL_init(PGconn *conn, int payloadlen) /* * Parse the list of SASL authentication mechanisms in the * AuthenticationSASL message, and select the best mechanism that we - * support. SCRAM-SHA-256-PLUS and SCRAM-SHA-256 are the only ones - * supported at the moment, listed by order of decreasing importance. + * support. SCRAM-SHA-256[-PLUS] and SCRAM-SHA-512[-PLUS] are the only + * ones supported at the moment, listed by order of decreasing importance. */ selected_mechanism = NULL; for (;;) @@ -451,17 +451,19 @@ pg_SASL_init(PGconn *conn, int payloadlen) break; /* - * Select the mechanism to use. Pick SCRAM-SHA-256-PLUS over anything - * else if a channel binding type is set and if the client supports it - * (and did not set channel_binding=disable). Pick SCRAM-SHA-256 if - * nothing else has already been picked. If we add more mechanisms, a - * more refined priority mechanism might become necessary. + * Select the mechanism to use. Pick SCRAM-SHA-{256,512}-PLUS over + * anything else if a channel binding type is set and if the client + * supports it (and did not set channel_binding=disable). Pick + * SCRAM-SHA-{256,512} if nothing else has already been picked. If + * we add more mechanisms, a more refined priority mechanism might + * become necessary. */ - if (strcmp(mechanism_buf.data, SCRAM_SHA_256_PLUS_NAME) == 0) + if (strcmp(mechanism_buf.data, SCRAM_SHA_512_PLUS_NAME) == 0 || + strcmp(mechanism_buf.data, SCRAM_SHA_256_PLUS_NAME) == 0) { if (conn->ssl_in_use) { - /* The server has offered SCRAM-SHA-256-PLUS. */ + /* The server has offered SCRAM-SHA-{256,512}-PLUS. */ #ifdef HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH /* @@ -470,7 +472,10 @@ pg_SASL_init(PGconn *conn, int payloadlen) */ if (conn->channel_binding[0] != 'd') /* disable */ { - selected_mechanism = SCRAM_SHA_256_PLUS_NAME; + if (strcmp(mechanism_buf.data, SCRAM_SHA_512_PLUS_NAME) == 0) + selected_mechanism = SCRAM_SHA_512_PLUS_NAME; + else + selected_mechanism = SCRAM_SHA_256_PLUS_NAME; conn->sasl = &pg_scram_mech; } #else @@ -478,7 +483,7 @@ pg_SASL_init(PGconn *conn, int payloadlen) * The client does not support channel binding. If it is * required, complain immediately instead of the error below * which would be confusing as the server is publishing - * SCRAM-SHA-256-PLUS. + * SCRAM-SHA-{256,512}-PLUS. */ if (conn->channel_binding[0] == 'r') /* require */ { @@ -490,18 +495,25 @@ pg_SASL_init(PGconn *conn, int payloadlen) else { /* - * The server offered SCRAM-SHA-256-PLUS, but the connection - * is not SSL-encrypted. That's not sane. Perhaps SSL was - * stripped by a proxy? There's no point in continuing, + * The server offered SCRAM-SHA-{256,512}-PLUS, but the + * connection is not SSL-encrypted. That's not sane. Perhaps + * SSL was stripped by a proxy? There's no point in continuing, * because the server will reject the connection anyway if we * try authenticate without channel binding even though both * the client and server supported it. The SCRAM exchange * checks for that, to prevent downgrade attacks. */ - libpq_append_conn_error(conn, "server offered SCRAM-SHA-256-PLUS authentication over a non-SSL connection"); + libpq_append_conn_error(conn, "server offered %s authentication over a non-SSL connection", + mechanism_buf.data); goto error; } } + else if (strcmp(mechanism_buf.data, SCRAM_SHA_512_NAME) == 0 && + !selected_mechanism) + { + selected_mechanism = SCRAM_SHA_512_NAME; + conn->sasl = &pg_scram_mech; + } else if (strcmp(mechanism_buf.data, SCRAM_SHA_256_NAME) == 0 && !selected_mechanism) { @@ -517,7 +529,8 @@ pg_SASL_init(PGconn *conn, int payloadlen) } if (conn->channel_binding[0] == 'r' && /* require */ - strcmp(selected_mechanism, SCRAM_SHA_256_PLUS_NAME) != 0) + strcmp(selected_mechanism, SCRAM_SHA_256_PLUS_NAME) != 0 && + strcmp(selected_mechanism, SCRAM_SHA_512_PLUS_NAME) != 0) { libpq_append_conn_error(conn, "channel binding is required, but server did not offer an authentication method that supports channel binding"); goto error; @@ -1249,11 +1262,14 @@ PQencryptPasswordConn(PGconn *conn, const char *passwd, const char *user, /* * Ok, now we know what algorithm to use */ - if (strcmp(algorithm, "scram-sha-256") == 0) + if (strcmp(algorithm, "scram-sha-256") == 0 || + strcmp(algorithm, "scram-sha-512") == 0) { const char *errstr = NULL; + pg_cryptohash_type hash_type = + (strcmp(algorithm, "scram-sha-512") == 0) ? PG_SHA512 : PG_SHA256; - crypt_pwd = pg_fe_scram_build_secret(passwd, &errstr); + crypt_pwd = pg_fe_scram_build_secret(passwd, hash_type, &errstr); if (!crypt_pwd) libpq_append_conn_error(conn, "could not encrypt password: %s", errstr); } diff --git a/src/interfaces/libpq/fe-auth.h b/src/interfaces/libpq/fe-auth.h index 049a8bb1a1..1f6dd52987 100644 --- a/src/interfaces/libpq/fe-auth.h +++ b/src/interfaces/libpq/fe-auth.h @@ -17,6 +17,7 @@ #include "libpq-fe.h" #include "libpq-int.h" +#include "common/cryptohash.h" /* Prototypes for functions in fe-auth.c */ extern int pg_fe_sendauth(AuthRequest areq, int payloadlen, PGconn *conn); @@ -26,6 +27,7 @@ extern char *pg_fe_getauthname(PQExpBuffer errorMessage); /* Mechanisms in fe-auth-scram.c */ extern const pg_fe_sasl_mech pg_scram_mech; extern char *pg_fe_scram_build_secret(const char *password, + pg_cryptohash_type hash_type, const char **errstr); #endif /* FE_AUTH_H */ diff --git a/src/test/authentication/t/001_password.pl b/src/test/authentication/t/001_password.pl index 42d3d4c79b..12026dd222 100644 --- a/src/test/authentication/t/001_password.pl +++ b/src/test/authentication/t/001_password.pl @@ -66,11 +66,14 @@ $node->init; $node->append_conf('postgresql.conf', "log_connections = on\n"); $node->start; -# Create 3 roles with different password methods for each one. The same +# Create 4 roles with different password methods for each one. The same # password is used for all of them. $node->safe_psql('postgres', "SET password_encryption='scram-sha-256'; CREATE ROLE scram_role LOGIN PASSWORD 'pass';" ); +$node->safe_psql('postgres', + "SET password_encryption='scram-sha-512'; CREATE ROLE scram_role_512 LOGIN PASSWORD 'pass';" +); $node->safe_psql('postgres', "SET password_encryption='md5'; CREATE ROLE md5_role LOGIN PASSWORD 'pass';" ); @@ -134,6 +137,8 @@ test_conn( log_like => [ qr/connection authenticated: identity="scram_role" method=scram-sha-256/ ]); +test_conn($node, 'user=scram_role_512', 'scram-sha-256', 2, + log_unlike => [qr/connection authenticated:/]); test_conn($node, 'user=md5_role', 'scram-sha-256', 2, log_unlike => [qr/connection authenticated:/]); @@ -143,6 +148,20 @@ test_conn($node, 'user=scram_role', 'scram-sha-256', 2, log_unlike => [qr/connection authenticated:/]); $ENV{"PGPASSWORD"} = 'pass'; +# For "scram-sha-512" method, user "scram_role_512" should be able to connect. +reset_pg_hba($node, 'all', 'all', 'scram-sha-512'); +test_conn( + $node, + 'user=scram_role_512', + 'scram-sha-512', + 0, + log_like => [ + qr/connection authenticated: identity="scram_role_512" method=scram-sha-512/ + ]); +test_conn($node, 'user=scram_role_256', 'scram-sha-256', 2, + log_unlike => [qr/connection authenticated:/]); + + # For "md5" method, all users should be able to connect (SCRAM # authentication will be performed for the user with a SCRAM secret.) reset_pg_hba($node, 'all', 'all', 'md5'); -- 2.39.0
signature.asc
Description: PGP signature