summaryrefslogtreecommitdiffstats
path: root/src/backend/libpq/crypt.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:15:05 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:15:05 +0000
commit46651ce6fe013220ed397add242004d764fc0153 (patch)
tree6e5299f990f88e60174a1d3ae6e48eedd2688b2b /src/backend/libpq/crypt.c
parentInitial commit. (diff)
downloadpostgresql-14-46651ce6fe013220ed397add242004d764fc0153.tar.xz
postgresql-14-46651ce6fe013220ed397add242004d764fc0153.zip
Adding upstream version 14.5.upstream/14.5upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/backend/libpq/crypt.c')
-rw-r--r--src/backend/libpq/crypt.c290
1 files changed, 290 insertions, 0 deletions
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
new file mode 100644
index 0000000..3fcad99
--- /dev/null
+++ b/src/backend/libpq/crypt.c
@@ -0,0 +1,290 @@
+/*-------------------------------------------------------------------------
+ *
+ * crypt.c
+ * Functions for dealing with encrypted passwords stored in
+ * pg_authid.rolpassword.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/backend/libpq/crypt.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <unistd.h>
+
+#include "catalog/pg_authid.h"
+#include "common/md5.h"
+#include "common/scram-common.h"
+#include "libpq/crypt.h"
+#include "libpq/scram.h"
+#include "miscadmin.h"
+#include "utils/builtins.h"
+#include "utils/syscache.h"
+#include "utils/timestamp.h"
+
+
+/*
+ * Fetch stored password 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, char **logdetail)
+{
+ TimestampTz vuntil = 0;
+ HeapTuple roleTup;
+ Datum datum;
+ bool isnull;
+ char *shadow_pass;
+
+ /* Get role info from pg_authid */
+ roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(role));
+ if (!HeapTupleIsValid(roleTup))
+ {
+ *logdetail = psprintf(_("Role \"%s\" does not exist."),
+ role);
+ return NULL; /* no such user */
+ }
+
+ datum = SysCacheGetAttr(AUTHNAME, roleTup,
+ Anum_pg_authid_rolpassword, &isnull);
+ if (isnull)
+ {
+ ReleaseSysCache(roleTup);
+ *logdetail = psprintf(_("User \"%s\" has no password assigned."),
+ role);
+ return NULL; /* user has no password */
+ }
+ shadow_pass = TextDatumGetCString(datum);
+
+ datum = SysCacheGetAttr(AUTHNAME, roleTup,
+ Anum_pg_authid_rolvaliduntil, &isnull);
+ if (!isnull)
+ vuntil = DatumGetTimestampTz(datum);
+
+ ReleaseSysCache(roleTup);
+
+ /*
+ * Password OK, but check to be sure we are not past rolvaliduntil
+ */
+ if (!isnull && vuntil < GetCurrentTimestamp())
+ {
+ *logdetail = psprintf(_("User \"%s\" has an expired password."),
+ role);
+ return NULL;
+ }
+
+ return shadow_pass;
+}
+
+/*
+ * What kind of a password type is 'shadow_pass'?
+ */
+PasswordType
+get_password_type(const char *shadow_pass)
+{
+ char *encoded_salt;
+ int iterations;
+ uint8 stored_key[SCRAM_KEY_LEN];
+ uint8 server_key[SCRAM_KEY_LEN];
+
+ if (strncmp(shadow_pass, "md5", 3) == 0 &&
+ strlen(shadow_pass) == MD5_PASSWD_LEN &&
+ strspn(shadow_pass + 3, MD5_PASSWD_CHARSET) == MD5_PASSWD_LEN - 3)
+ return PASSWORD_TYPE_MD5;
+ if (parse_scram_secret(shadow_pass, &iterations, &encoded_salt,
+ stored_key, server_key))
+ return PASSWORD_TYPE_SCRAM_SHA_256;
+ return PASSWORD_TYPE_PLAINTEXT;
+}
+
+/*
+ * Given a user-supplied password, convert it into a secret of
+ * 'target_type' kind.
+ *
+ * If the password is already in encrypted form, we cannot reverse the
+ * hash, so it is stored as it is regardless of the requested type.
+ */
+char *
+encrypt_password(PasswordType target_type, const char *role,
+ const char *password)
+{
+ PasswordType guessed_type = get_password_type(password);
+ char *encrypted_password;
+
+ if (guessed_type != PASSWORD_TYPE_PLAINTEXT)
+ {
+ /*
+ * Cannot convert an already-encrypted password from one format to
+ * another, so return it as it is.
+ */
+ return pstrdup(password);
+ }
+
+ switch (target_type)
+ {
+ case PASSWORD_TYPE_MD5:
+ 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_SCRAM_SHA_256:
+ return pg_be_scram_build_secret(password);
+
+ case PASSWORD_TYPE_PLAINTEXT:
+ elog(ERROR, "cannot encrypt password with 'plaintext'");
+ }
+
+ /*
+ * 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");
+ return NULL; /* keep compiler quiet */
+}
+
+/*
+ * Check MD5 authentication response, and return STATUS_OK or STATUS_ERROR.
+ *
+ * 'shadow_pass' is the user's correct password or password hash, as stored
+ * in pg_authid.rolpassword.
+ * 'client_pass' is the response given by the remote user to the MD5 challenge.
+ * 'md5_salt' is the salt used in the MD5 authentication challenge.
+ *
+ * In the error case, optionally store a palloc'd string at *logdetail
+ * that will be sent to the postmaster log (but not the client).
+ */
+int
+md5_crypt_verify(const char *role, const char *shadow_pass,
+ const char *client_pass,
+ const char *md5_salt, int md5_salt_len,
+ char **logdetail)
+{
+ int retval;
+ char crypt_pwd[MD5_PASSWD_LEN + 1];
+
+ Assert(md5_salt_len > 0);
+
+ if (get_password_type(shadow_pass) != PASSWORD_TYPE_MD5)
+ {
+ /* incompatible password hash format. */
+ *logdetail = psprintf(_("User \"%s\" has a password that cannot be used with MD5 authentication."),
+ role);
+ return STATUS_ERROR;
+ }
+
+ /*
+ * Compute the correct answer for the MD5 challenge.
+ *
+ * We do not bother setting logdetail for any pg_md5_encrypt failure
+ * 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.
+ */
+ /* 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;
+ }
+
+ if (strcmp(client_pass, crypt_pwd) == 0)
+ retval = STATUS_OK;
+ else
+ {
+ *logdetail = psprintf(_("Password does not match for user \"%s\"."),
+ role);
+ retval = STATUS_ERROR;
+ }
+
+ return retval;
+}
+
+/*
+ * Check given password for given user, and return STATUS_OK or STATUS_ERROR.
+ *
+ * 'shadow_pass' is the user's correct password hash, as stored in
+ * pg_authid.rolpassword.
+ * 'client_pass' is the password given by the remote user.
+ *
+ * In the error case, optionally store a palloc'd string at *logdetail
+ * that will be sent to the postmaster log (but not the client).
+ */
+int
+plain_crypt_verify(const char *role, const char *shadow_pass,
+ const char *client_pass,
+ char **logdetail)
+{
+ char crypt_client_pass[MD5_PASSWD_LEN + 1];
+
+ /*
+ * Client sent password in plaintext. If we have an MD5 hash stored, hash
+ * the password the client sent, and compare the hashes. Otherwise
+ * compare the plaintext passwords directly.
+ */
+ switch (get_password_type(shadow_pass))
+ {
+ case PASSWORD_TYPE_SCRAM_SHA_256:
+ if (scram_verify_plain_password(role,
+ client_pass,
+ shadow_pass))
+ {
+ return STATUS_OK;
+ }
+ else
+ {
+ *logdetail = psprintf(_("Password does not match for user \"%s\"."),
+ role);
+ return STATUS_ERROR;
+ }
+ break;
+
+ 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;
+ }
+ if (strcmp(crypt_client_pass, shadow_pass) == 0)
+ return STATUS_OK;
+ else
+ {
+ *logdetail = psprintf(_("Password does not match for user \"%s\"."),
+ role);
+ return STATUS_ERROR;
+ }
+ break;
+
+ case PASSWORD_TYPE_PLAINTEXT:
+
+ /*
+ * We never store passwords in plaintext, so this shouldn't
+ * happen.
+ */
+ break;
+ }
+
+ /*
+ * This shouldn't happen. Plain "password" authentication is possible
+ * with any kind of stored password hash.
+ */
+ *logdetail = psprintf(_("Password of user \"%s\" is in unrecognized format."),
+ role);
+ return STATUS_ERROR;
+}