diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:15:05 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:15:05 +0000 |
commit | 46651ce6fe013220ed397add242004d764fc0153 (patch) | |
tree | 6e5299f990f88e60174a1d3ae6e48eedd2688b2b /src/backend/libpq/crypt.c | |
parent | Initial commit. (diff) | |
download | postgresql-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.c | 290 |
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; +} |