summaryrefslogtreecommitdiff
path: root/src/backend
diff options
context:
space:
mode:
authorHeikki Linnakangas2017-02-01 11:11:37 +0000
committerHeikki Linnakangas2017-02-01 11:11:37 +0000
commitdbd69118c05d73969a1bd52ead6702c6e40b0fee (patch)
tree66d8ab158c9b8cec81b37db64bdaaa1a170aba4c /src/backend
parent7ac4a389a7dbddaa8b19deb228f0a988e79c5795 (diff)
Replace isMD5() with a more future-proof way to check if pw is encrypted.
The rule is that if pg_authid.rolpassword begins with "md5" and has the right length, it's an MD5 hash, otherwise it's a plaintext password. The idiom has been to use isMD5() to check for that, but that gets awkward, when we add new kinds of verifiers, like the verifiers for SCRAM authentication in the pending SCRAM patch set. Replace isMD5() with a new get_password_type() function, so that when new verifier types are added, we don't need to remember to modify every place that currently calls isMD5(), to also recognize the new kinds of verifiers. Also, use the new plain_crypt_verify function in passwordcheck, so that it doesn't need to know about MD5, or in the future, about other kinds of hashes or password verifiers. Reviewed by Michael Paquier and Peter Eisentraut. Discussion: https://www.postgresql.org/message-id/[email protected]
Diffstat (limited to 'src/backend')
-rw-r--r--src/backend/commands/user.c44
-rw-r--r--src/backend/libpq/crypt.c159
2 files changed, 135 insertions, 68 deletions
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 4422fadd524..f2ec3b2d0d8 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -29,7 +29,7 @@
#include "commands/dbcommands.h"
#include "commands/seclabel.h"
#include "commands/user.h"
-#include "common/md5.h"
+#include "libpq/crypt.h"
#include "miscadmin.h"
#include "storage/lmgr.h"
#include "utils/acl.h"
@@ -81,7 +81,6 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
ListCell *option;
char *password = NULL; /* user password */
int password_type = Password_encryption;
- char encrypted_password[MD5_PASSWD_LEN + 1];
bool issuper = false; /* Make the user a superuser? */
bool inherit = true; /* Auto inherit privileges? */
bool createrole = false; /* Can this user create roles? */
@@ -370,7 +369,7 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
if (check_password_hook && password)
(*check_password_hook) (stmt->role,
password,
- isMD5(password) ? PASSWORD_TYPE_MD5 : PASSWORD_TYPE_PLAINTEXT,
+ get_password_type(password),
validUntil_datum,
validUntil_null);
@@ -393,17 +392,12 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
if (password)
{
- if (password_type == PASSWORD_TYPE_PLAINTEXT || isMD5(password))
- new_record[Anum_pg_authid_rolpassword - 1] =
- CStringGetTextDatum(password);
- else
- {
- if (!pg_md5_encrypt(password, stmt->role, strlen(stmt->role),
- encrypted_password))
- elog(ERROR, "password encryption failed");
- new_record[Anum_pg_authid_rolpassword - 1] =
- CStringGetTextDatum(encrypted_password);
- }
+ /* Encrypt the password to the requested format. */
+ char *shadow_pass;
+
+ shadow_pass = encrypt_password(password_type, stmt->role, password);
+ new_record[Anum_pg_authid_rolpassword - 1] =
+ CStringGetTextDatum(shadow_pass);
}
else
new_record_nulls[Anum_pg_authid_rolpassword - 1] = true;
@@ -505,7 +499,6 @@ AlterRole(AlterRoleStmt *stmt)
char *rolename = NULL;
char *password = NULL; /* user password */
int password_type = Password_encryption;
- char encrypted_password[MD5_PASSWD_LEN + 1];
int issuper = -1; /* Make the user a superuser? */
int inherit = -1; /* Auto inherit privileges? */
int createrole = -1; /* Can this user create roles? */
@@ -744,7 +737,7 @@ AlterRole(AlterRoleStmt *stmt)
if (check_password_hook && password)
(*check_password_hook) (rolename,
password,
- isMD5(password) ? PASSWORD_TYPE_MD5 : PASSWORD_TYPE_PLAINTEXT,
+ get_password_type(password),
validUntil_datum,
validUntil_null);
@@ -803,17 +796,12 @@ AlterRole(AlterRoleStmt *stmt)
/* password */
if (password)
{
- if (password_type == PASSWORD_TYPE_PLAINTEXT || isMD5(password))
- new_record[Anum_pg_authid_rolpassword - 1] =
- CStringGetTextDatum(password);
- else
- {
- if (!pg_md5_encrypt(password, rolename, strlen(rolename),
- encrypted_password))
- elog(ERROR, "password encryption failed");
- new_record[Anum_pg_authid_rolpassword - 1] =
- CStringGetTextDatum(encrypted_password);
- }
+ /* Encrypt the password to the requested format. */
+ char *shadow_pass;
+
+ shadow_pass = encrypt_password(password_type, rolename, password);
+ new_record[Anum_pg_authid_rolpassword - 1] =
+ CStringGetTextDatum(shadow_pass);
new_record_repl[Anum_pg_authid_rolpassword - 1] = true;
}
@@ -1228,7 +1216,7 @@ RenameRole(const char *oldname, const char *newname)
datum = heap_getattr(oldtuple, Anum_pg_authid_rolpassword, dsc, &isnull);
- if (!isnull && isMD5(TextDatumGetCString(datum)))
+ if (!isnull && get_password_type(TextDatumGetCString(datum)) == PASSWORD_TYPE_MD5)
{
/* MD5 uses the username as salt, so just clear it on a rename */
repl_repl[Anum_pg_authid_rolpassword - 1] = true;
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index e1c10137d9f..893ce6e967b 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -1,10 +1,8 @@
/*-------------------------------------------------------------------------
*
* crypt.c
- * Look into the password file and check the encrypted password with
- * the one passed in from the frontend.
- *
- * Original coding by Todd A. Brandys
+ * Functions for dealing with encrypted passwords stored in
+ * pg_authid.rolpassword.
*
* Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
@@ -106,6 +104,65 @@ get_role_password(const char *role, char **shadow_pass, char **logdetail)
}
/*
+ * What kind of a password verifier is 'shadow_pass'?
+ */
+PasswordType
+get_password_type(const char *shadow_pass)
+{
+ if (strncmp(shadow_pass, "md5", 3) == 0 && strlen(shadow_pass) == MD5_PASSWD_LEN)
+ return PASSWORD_TYPE_MD5;
+ return PASSWORD_TYPE_PLAINTEXT;
+}
+
+/*
+ * Given a user-supplied password, convert it into a verifier of
+ * 'target_type' kind.
+ *
+ * If the password looks like a valid MD5 hash, it is stored as it is.
+ * We cannot reverse the hash, so even if the caller requested a plaintext
+ * plaintext password, the MD5 hash is returned.
+ */
+char *
+encrypt_password(PasswordType target_type, const char *role,
+ const char *password)
+{
+ PasswordType guessed_type = get_password_type(password);
+ char *encrypted_password;
+
+ switch (target_type)
+ {
+ case PASSWORD_TYPE_PLAINTEXT:
+
+ /*
+ * We cannot convert a hashed password back to plaintext, so just
+ * store the password as it was, whether it was hashed or not.
+ */
+ return pstrdup(password);
+
+ case PASSWORD_TYPE_MD5:
+ switch (guessed_type)
+ {
+ case PASSWORD_TYPE_PLAINTEXT:
+ 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_MD5:
+ return pstrdup(password);
+ }
+ }
+
+ /*
+ * 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");
+}
+
+/*
* Check MD5 authentication response, and return STATUS_OK or STATUS_ERROR.
*
* 'shadow_pass' is the user's correct password or password hash, as stored
@@ -135,32 +192,40 @@ md5_crypt_verify(const char *role, const char *shadow_pass,
* 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.
*/
- if (isMD5(shadow_pass))
- {
- /* 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;
- }
- }
- else
+ switch (get_password_type(shadow_pass))
{
- /* stored password is plain, double-encrypt */
- if (!pg_md5_encrypt(shadow_pass,
- role,
- strlen(role),
- crypt_pwd2))
- {
- return STATUS_ERROR;
- }
- if (!pg_md5_encrypt(crypt_pwd2 + strlen("md5"),
- md5_salt, md5_salt_len,
- crypt_pwd))
- {
+ case PASSWORD_TYPE_MD5:
+ /* 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;
+ }
+ break;
+
+ case PASSWORD_TYPE_PLAINTEXT:
+ /* stored password is plain, double-encrypt */
+ if (!pg_md5_encrypt(shadow_pass,
+ role,
+ strlen(role),
+ crypt_pwd2))
+ {
+ return STATUS_ERROR;
+ }
+ if (!pg_md5_encrypt(crypt_pwd2 + strlen("md5"),
+ md5_salt, md5_salt_len,
+ crypt_pwd))
+ {
+ return STATUS_ERROR;
+ }
+ break;
+
+ default:
+ /* unknown password hash format. */
+ *logdetail = psprintf(_("User \"%s\" has a password that cannot be used with MD5 authentication."),
+ role);
return STATUS_ERROR;
- }
}
if (strcmp(client_pass, crypt_pwd) == 0)
@@ -198,22 +263,36 @@ plain_crypt_verify(const char *role, const char *shadow_pass,
* the password the client sent, and compare the hashes. Otherwise
* compare the plaintext passwords directly.
*/
- if (isMD5(shadow_pass))
+ switch (get_password_type(shadow_pass))
{
- if (!pg_md5_encrypt(client_pass,
- role,
- strlen(role),
- crypt_client_pass))
- {
+ 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;
+ }
+ client_pass = crypt_client_pass;
+ break;
+ case PASSWORD_TYPE_PLAINTEXT:
+ break;
+
+ default:
+
/*
- * 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.
+ * This shouldn't happen. Plain "password" authentication should
+ * be possible with any kind of stored password hash.
*/
+ *logdetail = psprintf(_("Password of user \"%s\" is in unrecognized format."),
+ role);
return STATUS_ERROR;
- }
- client_pass = crypt_client_pass;
}
if (strcmp(client_pass, shadow_pass) == 0)