mirror of
https://github.com/postgres/postgres.git
synced 2025-06-14 18:42:34 +03:00
Add PQencryptPasswordConn function to libpq, use it in psql and createuser.
The new function supports creating SCRAM verifiers, in addition to md5 hashes. The algorithm is chosen based on password_encryption, by default. This fixes the issue reported by Jeff Janes, that there was previously no way to create a SCRAM verifier with "\password". Michael Paquier and me Discussion: https://www.postgresql.org/message-id/CAMkU%3D1wfBgFPbfAMYZQE78p%3DVhZX7nN86aWkp0QcCp%3D%2BKxZ%3Dbg%40mail.gmail.com
This commit is contained in:
@ -171,3 +171,4 @@ PQsslAttributeNames 168
|
||||
PQsslAttribute 169
|
||||
PQsetErrorContextVisibility 170
|
||||
PQresultVerboseErrorMessage 171
|
||||
PQencryptPasswordConn 172
|
||||
|
@ -614,6 +614,41 @@ verify_server_signature(fe_scram_state *state)
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Build a new SCRAM verifier.
|
||||
*/
|
||||
char *
|
||||
pg_fe_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_OOM)
|
||||
return NULL;
|
||||
if (rc == SASLPREP_SUCCESS)
|
||||
password = (const char *) prep_password;
|
||||
|
||||
/* Generate a random salt */
|
||||
if (!pg_frontend_random(saltbuf, SCRAM_DEFAULT_SALT_LEN))
|
||||
return NULL;
|
||||
|
||||
result = scram_build_verifier(saltbuf, SCRAM_DEFAULT_SALT_LEN,
|
||||
SCRAM_DEFAULT_ITERATIONS, password);
|
||||
|
||||
if (prep_password)
|
||||
free(prep_password);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Random number generator.
|
||||
*/
|
||||
|
@ -1077,22 +1077,12 @@ pg_fe_getauthname(PQExpBuffer errorMessage)
|
||||
|
||||
|
||||
/*
|
||||
* PQencryptPassword -- exported routine to encrypt a password
|
||||
* PQencryptPassword -- exported routine to encrypt a password with MD5
|
||||
*
|
||||
* This is intended to be used by client applications that wish to send
|
||||
* commands like ALTER USER joe PASSWORD 'pwd'. The password need not
|
||||
* be sent in cleartext if it is encrypted on the client side. This is
|
||||
* good because it ensures the cleartext password won't end up in logs,
|
||||
* pg_stat displays, etc. We export the function so that clients won't
|
||||
* be dependent on low-level details like whether the encryption is MD5
|
||||
* or something else.
|
||||
*
|
||||
* Arguments are the cleartext password, and the SQL name of the user it
|
||||
* is for.
|
||||
*
|
||||
* Return value is a malloc'd string, or NULL if out-of-memory. The client
|
||||
* may assume the string doesn't contain any special characters that would
|
||||
* require escaping.
|
||||
* This function is equivalent to calling PQencryptPasswordConn with
|
||||
* "md5" as the encryption method, except that this doesn't require
|
||||
* a connection object. This function is deprecated, use
|
||||
* PQencryptPasswordConn instead.
|
||||
*/
|
||||
char *
|
||||
PQencryptPassword(const char *passwd, const char *user)
|
||||
@ -1111,3 +1101,114 @@ PQencryptPassword(const char *passwd, const char *user)
|
||||
|
||||
return crypt_pwd;
|
||||
}
|
||||
|
||||
/*
|
||||
* PQencryptPasswordConn -- exported routine to encrypt a password
|
||||
*
|
||||
* This is intended to be used by client applications that wish to send
|
||||
* commands like ALTER USER joe PASSWORD 'pwd'. The password need not
|
||||
* be sent in cleartext if it is encrypted on the client side. This is
|
||||
* good because it ensures the cleartext password won't end up in logs,
|
||||
* pg_stat displays, etc. We export the function so that clients won't
|
||||
* be dependent on low-level details like whether the encryption is MD5
|
||||
* or something else.
|
||||
*
|
||||
* Arguments are a connection object, the cleartext password, the SQL
|
||||
* name of the user it is for, and a string indicating the algorithm to
|
||||
* use for encrypting the password. If algorithm is NULL, this queries
|
||||
* the server for the current 'password_encryption' value. If you wish
|
||||
* to avoid that, e.g. to avoid blocking, you can execute
|
||||
* 'show password_encryption' yourself before calling this function, and
|
||||
* pass it as the algorithm.
|
||||
*
|
||||
* Return value is a malloc'd string. The client may assume the string
|
||||
* doesn't contain any special characters that would require escaping.
|
||||
* On error, an error message is stored in the connection object, and
|
||||
* returns NULL.
|
||||
*/
|
||||
char *
|
||||
PQencryptPasswordConn(PGconn *conn, const char *passwd, const char *user,
|
||||
const char *algorithm)
|
||||
{
|
||||
#define MAX_ALGORITHM_NAME_LEN 50
|
||||
char algobuf[MAX_ALGORITHM_NAME_LEN + 1];
|
||||
char *crypt_pwd = NULL;
|
||||
|
||||
if (!conn)
|
||||
return NULL;
|
||||
|
||||
/* If no algorithm was given, ask the server. */
|
||||
if (algorithm == NULL)
|
||||
{
|
||||
PGresult *res;
|
||||
char *val;
|
||||
|
||||
res = PQexec(conn, "show password_encryption");
|
||||
if (res == NULL)
|
||||
{
|
||||
/* PQexec() should've set conn->errorMessage already */
|
||||
return NULL;
|
||||
}
|
||||
if (PQresultStatus(res) != PGRES_TUPLES_OK)
|
||||
{
|
||||
/* PQexec() should've set conn->errorMessage already */
|
||||
PQclear(res);
|
||||
return NULL;
|
||||
}
|
||||
if (PQntuples(res) != 1 || PQnfields(res) != 1)
|
||||
{
|
||||
PQclear(res);
|
||||
printfPQExpBuffer(&conn->errorMessage,
|
||||
libpq_gettext("unexpected shape of result set returned for SHOW\n"));
|
||||
return NULL;
|
||||
}
|
||||
val = PQgetvalue(res, 0, 0);
|
||||
|
||||
if (strlen(val) > MAX_ALGORITHM_NAME_LEN)
|
||||
{
|
||||
PQclear(res);
|
||||
printfPQExpBuffer(&conn->errorMessage,
|
||||
libpq_gettext("password_encryption value too long\n"));
|
||||
return NULL;
|
||||
}
|
||||
strcpy(algobuf, val);
|
||||
PQclear(res);
|
||||
|
||||
algorithm = algobuf;
|
||||
}
|
||||
|
||||
/* Ok, now we know what algorithm to use */
|
||||
|
||||
if (strcmp(algorithm, "scram-sha-256") == 0)
|
||||
{
|
||||
crypt_pwd = pg_fe_scram_build_verifier(passwd);
|
||||
}
|
||||
else if (strcmp(algorithm, "md5") == 0)
|
||||
{
|
||||
crypt_pwd = malloc(MD5_PASSWD_LEN + 1);
|
||||
if (crypt_pwd)
|
||||
{
|
||||
if (!pg_md5_encrypt(passwd, user, strlen(user), crypt_pwd))
|
||||
{
|
||||
free(crypt_pwd);
|
||||
crypt_pwd = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (strcmp(algorithm, "plain") == 0)
|
||||
{
|
||||
crypt_pwd = strdup(passwd);
|
||||
}
|
||||
else
|
||||
{
|
||||
printfPQExpBuffer(&conn->errorMessage,
|
||||
libpq_gettext("unknown password encryption algorithm\n"));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!crypt_pwd)
|
||||
printfPQExpBuffer(&conn->errorMessage,
|
||||
libpq_gettext("out of memory\n"));
|
||||
|
||||
return crypt_pwd;
|
||||
}
|
||||
|
@ -28,5 +28,6 @@ extern void pg_fe_scram_free(void *opaq);
|
||||
extern void pg_fe_scram_exchange(void *opaq, char *input, int inputlen,
|
||||
char **output, int *outputlen,
|
||||
bool *done, bool *success, PQExpBuffer errorMessage);
|
||||
extern char *pg_fe_scram_build_verifier(const char *password);
|
||||
|
||||
#endif /* FE_AUTH_H */
|
||||
|
@ -597,6 +597,7 @@ extern int PQenv2encoding(void);
|
||||
/* === in fe-auth.c === */
|
||||
|
||||
extern char *PQencryptPassword(const char *passwd, const char *user);
|
||||
extern char *PQencryptPasswordConn(PGconn *conn, const char *passwd, const char *user, const char *algorithm);
|
||||
|
||||
/* === in encnames.c === */
|
||||
|
||||
|
Reference in New Issue
Block a user