/* * Construct a verifier string for SCRAM, stored in pg_authid.rolpassword. * * The result is palloc'd, so caller is responsible for freeing it. */ char * pg_be_scram_build_verifier(const char *password) { char *prep_password = NULL; pg_saslprep_rc rc; char saltbuf[SCRAM_DEFAULT_SALT_LEN]; char *result; /* * Normalize the password with SASLprep. If that doesn't work, because * the password isn't valid UTF-8 or contains prohibited characters, just * proceed with the original password. (See comments at top of file.) */ rc = pg_saslprep(password, &prep_password); if (rc == SASLPREP_SUCCESS) password = (const char *) prep_password; /* Generate random salt */ if (!pg_backend_random(saltbuf, SCRAM_DEFAULT_SALT_LEN)) ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("could not generate random salt"))); result = scram_build_verifier(saltbuf, SCRAM_DEFAULT_SALT_LEN, SCRAM_DEFAULT_ITERATIONS, password); if (prep_password) pfree(prep_password); return result; }
/* * 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; }
Datum pg_sasl_prepare(PG_FUNCTION_ARGS) { char *password = text_to_cstring(PG_GETARG_TEXT_PP(0)); char *prep_password = NULL; if (GetDatabaseEncoding() != PG_UTF8) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("Database encoding is not UTF-8"))); if (pg_saslprep(password, &prep_password) != SASLPREP_SUCCESS) ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("Error while processing SASLprep"))); PG_RETURN_TEXT_P(cstring_to_text(prep_password)); }
/* * scram_utils_verifier * * Generate a verifier for SCRAM-SHA-256 authentication and update the * related user's pg_authid entry as per RFC 7677. */ Datum scram_utils_verifier(PG_FUNCTION_ARGS) { pg_saslprep_rc rc; char *username = text_to_cstring(PG_GETARG_TEXT_PP(0)); const char *password = text_to_cstring(PG_GETARG_TEXT_PP(1)); int iterations = PG_GETARG_INT32(2); int saltlen = PG_GETARG_INT32(3); char *prep_password = NULL; char *saltbuf; char *verifier; HeapTuple oldtuple, newtuple; TupleDesc dsc; Relation rel; Datum repl_val[Natts_pg_authid]; bool repl_null[Natts_pg_authid]; bool repl_repl[Natts_pg_authid]; if (!superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), (errmsg("must be superuser to update one's SCRAM verifier")))); /* Control iteration number and salt length */ if (iterations <= 0) { ereport(WARNING, (errmsg("Incorrect iteration number, defaulting to %d", SCRAM_DEFAULT_ITERATIONS))); iterations = SCRAM_DEFAULT_ITERATIONS; } if (saltlen <= 0) { ereport(WARNING, (errmsg("Incorrect salt length number, defaulting to %d", SCRAM_DEFAULT_SALT_LEN))); saltlen = SCRAM_DEFAULT_SALT_LEN; } /* * Normalize the password with SASLprep. If that doesn't work, because * the password isn't valid UTF-8 or contains prohibited characters, just * proceed with the original password. (See comments at top of file.) */ rc = pg_saslprep(password, &prep_password); if (rc == SASLPREP_OOM) elog(ERROR, "out of memory"); if (rc == SASLPREP_SUCCESS) password = (const char *) prep_password; /* Generate a random salt */ saltbuf = palloc(sizeof(char) * saltlen); if (!pg_strong_random(saltbuf, saltlen)) elog(ERROR, "Failed to generate random salt"); /* Build verifier */ verifier = scram_build_verifier(saltbuf, saltlen, iterations, password); if (prep_password) pfree(prep_password); /* Verifier is built, so update pg_authid with it */ rel = heap_open(AuthIdRelationId, RowExclusiveLock); oldtuple = SearchSysCache1(AUTHNAME, CStringGetDatum(username)); if (!HeapTupleIsValid(oldtuple)) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("role \"%s\" does not exist", username))); /* OK, construct the modified tuple with new password */ memset(repl_repl, false, sizeof(repl_repl)); memset(repl_null, false, sizeof(repl_null)); repl_repl[Anum_pg_authid_rolpassword - 1] = true; repl_val[Anum_pg_authid_rolpassword - 1] = CStringGetTextDatum(verifier); repl_null[Anum_pg_authid_rolpassword - 1] = false; dsc = RelationGetDescr(rel); newtuple = heap_modify_tuple(oldtuple, dsc, repl_val, repl_null, repl_repl); CatalogTupleUpdate(rel, &oldtuple->t_self, newtuple); ReleaseSysCache(oldtuple); /* * Close pg_authid, but keep lock till commit. */ heap_close(rel, NoLock); PG_RETURN_NULL(); }