LCOV - code coverage report
Current view: top level - src/backend/libpq - crypt.c (source / functions) Coverage Total Hit
Test: Code coverage Lines: 53.8 % 119 64
Test Date: 2026-01-26 10:56:24 Functions: 60.0 % 5 3
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 48.1 % 81 39

             Branch data     Line data    Source code
       1                 :             : /*-------------------------------------------------------------------------
       2                 :             :  *
       3                 :             :  * crypt.c
       4                 :             :  *        Functions for dealing with encrypted passwords stored in
       5                 :             :  *        pg_authid.rolpassword.
       6                 :             :  *
       7                 :             :  * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
       8                 :             :  * Portions Copyright (c) 1994, Regents of the University of California
       9                 :             :  *
      10                 :             :  * src/backend/libpq/crypt.c
      11                 :             :  *
      12                 :             :  *-------------------------------------------------------------------------
      13                 :             :  */
      14                 :             : #include "postgres.h"
      15                 :             : 
      16                 :             : #include <unistd.h>
      17                 :             : 
      18                 :             : #include "catalog/pg_authid.h"
      19                 :             : #include "common/md5.h"
      20                 :             : #include "common/scram-common.h"
      21                 :             : #include "libpq/crypt.h"
      22                 :             : #include "libpq/scram.h"
      23                 :             : #include "utils/builtins.h"
      24                 :             : #include "utils/syscache.h"
      25                 :             : #include "utils/timestamp.h"
      26                 :             : 
      27                 :             : /* Enables deprecation warnings for MD5 passwords. */
      28                 :             : bool            md5_password_warnings = true;
      29                 :             : 
      30                 :             : /*
      31                 :             :  * Fetch stored password for a user, for authentication.
      32                 :             :  *
      33                 :             :  * On error, returns NULL, and stores a palloc'd string describing the reason,
      34                 :             :  * for the postmaster log, in *logdetail.  The error reason should *not* be
      35                 :             :  * sent to the client, to avoid giving away user information!
      36                 :             :  */
      37                 :             : char *
      38                 :           0 : get_role_password(const char *role, const char **logdetail)
      39                 :             : {
      40                 :           0 :         TimestampTz vuntil = 0;
      41                 :           0 :         HeapTuple       roleTup;
      42                 :           0 :         Datum           datum;
      43                 :           0 :         bool            isnull;
      44                 :           0 :         char       *shadow_pass;
      45                 :             : 
      46                 :             :         /* Get role info from pg_authid */
      47                 :           0 :         roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(role));
      48         [ #  # ]:           0 :         if (!HeapTupleIsValid(roleTup))
      49                 :             :         {
      50                 :           0 :                 *logdetail = psprintf(_("Role \"%s\" does not exist."),
      51                 :           0 :                                                           role);
      52                 :           0 :                 return NULL;                    /* no such user */
      53                 :             :         }
      54                 :             : 
      55                 :           0 :         datum = SysCacheGetAttr(AUTHNAME, roleTup,
      56                 :             :                                                         Anum_pg_authid_rolpassword, &isnull);
      57         [ #  # ]:           0 :         if (isnull)
      58                 :             :         {
      59                 :           0 :                 ReleaseSysCache(roleTup);
      60                 :           0 :                 *logdetail = psprintf(_("User \"%s\" has no password assigned."),
      61                 :           0 :                                                           role);
      62                 :           0 :                 return NULL;                    /* user has no password */
      63                 :             :         }
      64                 :           0 :         shadow_pass = TextDatumGetCString(datum);
      65                 :             : 
      66                 :           0 :         datum = SysCacheGetAttr(AUTHNAME, roleTup,
      67                 :             :                                                         Anum_pg_authid_rolvaliduntil, &isnull);
      68         [ #  # ]:           0 :         if (!isnull)
      69                 :           0 :                 vuntil = DatumGetTimestampTz(datum);
      70                 :             : 
      71                 :           0 :         ReleaseSysCache(roleTup);
      72                 :             : 
      73                 :             :         /*
      74                 :             :          * Password OK, but check to be sure we are not past rolvaliduntil
      75                 :             :          */
      76   [ #  #  #  # ]:           0 :         if (!isnull && vuntil < GetCurrentTimestamp())
      77                 :             :         {
      78                 :           0 :                 *logdetail = psprintf(_("User \"%s\" has an expired password."),
      79                 :           0 :                                                           role);
      80                 :           0 :                 return NULL;
      81                 :             :         }
      82                 :             : 
      83                 :           0 :         return shadow_pass;
      84                 :           0 : }
      85                 :             : 
      86                 :             : /*
      87                 :             :  * What kind of a password type is 'shadow_pass'?
      88                 :             :  */
      89                 :             : PasswordType
      90                 :          55 : get_password_type(const char *shadow_pass)
      91                 :             : {
      92                 :          55 :         char       *encoded_salt;
      93                 :          55 :         int                     iterations;
      94                 :          55 :         int                     key_length = 0;
      95                 :          55 :         pg_cryptohash_type hash_type;
      96                 :          55 :         uint8           stored_key[SCRAM_MAX_KEY_LEN];
      97                 :          55 :         uint8           server_key[SCRAM_MAX_KEY_LEN];
      98                 :             : 
      99         [ +  + ]:          55 :         if (strncmp(shadow_pass, "md5", 3) == 0 &&
     100   [ +  +  +  + ]:          15 :                 strlen(shadow_pass) == MD5_PASSWD_LEN &&
     101                 :          13 :                 strspn(shadow_pass + 3, MD5_PASSWD_CHARSET) == MD5_PASSWD_LEN - 3)
     102                 :          11 :                 return PASSWORD_TYPE_MD5;
     103   [ +  +  +  + ]:          88 :         if (parse_scram_secret(shadow_pass, &iterations, &hash_type, &key_length,
     104                 :          44 :                                                    &encoded_salt, stored_key, server_key))
     105                 :          20 :                 return PASSWORD_TYPE_SCRAM_SHA_256;
     106                 :          24 :         return PASSWORD_TYPE_PLAINTEXT;
     107                 :          55 : }
     108                 :             : 
     109                 :             : /*
     110                 :             :  * Given a user-supplied password, convert it into a secret of
     111                 :             :  * 'target_type' kind.
     112                 :             :  *
     113                 :             :  * If the password is already in encrypted form, we cannot reverse the
     114                 :             :  * hash, so it is stored as it is regardless of the requested type.
     115                 :             :  */
     116                 :             : char *
     117                 :          18 : encrypt_password(PasswordType target_type, const char *role,
     118                 :             :                                  const char *password)
     119                 :             : {
     120                 :          18 :         PasswordType guessed_type = get_password_type(password);
     121                 :          18 :         char       *encrypted_password = NULL;
     122                 :          18 :         const char *errstr = NULL;
     123                 :             : 
     124         [ +  + ]:          18 :         if (guessed_type != PASSWORD_TYPE_PLAINTEXT)
     125                 :             :         {
     126                 :             :                 /*
     127                 :             :                  * Cannot convert an already-encrypted password from one format to
     128                 :             :                  * another, so return it as it is.
     129                 :             :                  */
     130                 :           6 :                 encrypted_password = pstrdup(password);
     131                 :           6 :         }
     132                 :             :         else
     133                 :             :         {
     134   [ +  -  +  - ]:          12 :                 switch (target_type)
     135                 :             :                 {
     136                 :             :                         case PASSWORD_TYPE_MD5:
     137                 :           3 :                                 encrypted_password = palloc(MD5_PASSWD_LEN + 1);
     138                 :             : 
     139   [ +  -  +  - ]:           6 :                                 if (!pg_md5_encrypt(password, (uint8 *) role, strlen(role),
     140                 :           3 :                                                                         encrypted_password, &errstr))
     141   [ #  #  #  # ]:           0 :                                         elog(ERROR, "password encryption failed: %s", errstr);
     142                 :           3 :                                 break;
     143                 :             : 
     144                 :             :                         case PASSWORD_TYPE_SCRAM_SHA_256:
     145                 :           9 :                                 encrypted_password = pg_be_scram_build_secret(password);
     146                 :           9 :                                 break;
     147                 :             : 
     148                 :             :                         case PASSWORD_TYPE_PLAINTEXT:
     149   [ #  #  #  # ]:           0 :                                 elog(ERROR, "cannot encrypt password with 'plaintext'");
     150                 :           0 :                                 break;
     151                 :             :                 }
     152                 :             :         }
     153                 :             : 
     154         [ +  - ]:          18 :         Assert(encrypted_password);
     155                 :             : 
     156                 :             :         /*
     157                 :             :          * Valid password hashes may be very long, but we don't want to store
     158                 :             :          * anything that might need out-of-line storage, since de-TOASTing won't
     159                 :             :          * work during authentication because we haven't selected a database yet
     160                 :             :          * and cannot read pg_class. 512 bytes should be more than enough for all
     161                 :             :          * practical use, so fail for anything longer.
     162                 :             :          */
     163   [ +  -  +  + ]:          18 :         if (encrypted_password &&       /* keep compiler quiet */
     164                 :          18 :                 strlen(encrypted_password) > MAX_ENCRYPTED_PASSWORD_LEN)
     165                 :             :         {
     166                 :             :                 /*
     167                 :             :                  * We don't expect any of our own hashing routines to produce hashes
     168                 :             :                  * that are too long.
     169                 :             :                  */
     170         [ +  - ]:           2 :                 Assert(guessed_type != PASSWORD_TYPE_PLAINTEXT);
     171                 :             : 
     172   [ +  -  +  - ]:           2 :                 ereport(ERROR,
     173                 :             :                                 (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
     174                 :             :                                  errmsg("encrypted password is too long"),
     175                 :             :                                  errdetail("Encrypted passwords must be no longer than %d bytes.",
     176                 :             :                                                    MAX_ENCRYPTED_PASSWORD_LEN)));
     177                 :           0 :         }
     178                 :             : 
     179   [ +  -  +  + ]:          16 :         if (md5_password_warnings &&
     180                 :          16 :                 get_password_type(encrypted_password) == PASSWORD_TYPE_MD5)
     181   [ -  +  +  - ]:           5 :                 ereport(WARNING,
     182                 :             :                                 (errcode(ERRCODE_WARNING_DEPRECATED_FEATURE),
     183                 :             :                                  errmsg("setting an MD5-encrypted password"),
     184                 :             :                                  errdetail("MD5 password support is deprecated and will be removed in a future release of PostgreSQL."),
     185                 :             :                                  errhint("Refer to the PostgreSQL documentation for details about migrating to another password type.")));
     186                 :             : 
     187                 :          32 :         return encrypted_password;
     188                 :          16 : }
     189                 :             : 
     190                 :             : /*
     191                 :             :  * Check MD5 authentication response, and return STATUS_OK or STATUS_ERROR.
     192                 :             :  *
     193                 :             :  * 'shadow_pass' is the user's correct password or password hash, as stored
     194                 :             :  * in pg_authid.rolpassword.
     195                 :             :  * 'client_pass' is the response given by the remote user to the MD5 challenge.
     196                 :             :  * 'md5_salt' is the salt used in the MD5 authentication challenge.
     197                 :             :  *
     198                 :             :  * In the error case, save a string at *logdetail that will be sent to the
     199                 :             :  * postmaster log (but not the client).
     200                 :             :  */
     201                 :             : int
     202                 :           0 : md5_crypt_verify(const char *role, const char *shadow_pass,
     203                 :             :                                  const char *client_pass,
     204                 :             :                                  const uint8 *md5_salt, int md5_salt_len,
     205                 :             :                                  const char **logdetail)
     206                 :             : {
     207                 :           0 :         int                     retval;
     208                 :           0 :         char            crypt_pwd[MD5_PASSWD_LEN + 1];
     209                 :           0 :         const char *errstr = NULL;
     210                 :             : 
     211         [ #  # ]:           0 :         Assert(md5_salt_len > 0);
     212                 :             : 
     213         [ #  # ]:           0 :         if (get_password_type(shadow_pass) != PASSWORD_TYPE_MD5)
     214                 :             :         {
     215                 :             :                 /* incompatible password hash format. */
     216                 :           0 :                 *logdetail = psprintf(_("User \"%s\" has a password that cannot be used with MD5 authentication."),
     217                 :           0 :                                                           role);
     218                 :           0 :                 return STATUS_ERROR;
     219                 :             :         }
     220                 :             : 
     221                 :             :         /*
     222                 :             :          * Compute the correct answer for the MD5 challenge.
     223                 :             :          */
     224                 :             :         /* stored password already encrypted, only do salt */
     225   [ #  #  #  # ]:           0 :         if (!pg_md5_encrypt(shadow_pass + strlen("md5"),
     226                 :           0 :                                                 md5_salt, md5_salt_len,
     227                 :           0 :                                                 crypt_pwd, &errstr))
     228                 :             :         {
     229                 :           0 :                 *logdetail = errstr;
     230                 :           0 :                 return STATUS_ERROR;
     231                 :             :         }
     232                 :             : 
     233         [ #  # ]:           0 :         if (strcmp(client_pass, crypt_pwd) == 0)
     234                 :           0 :                 retval = STATUS_OK;
     235                 :             :         else
     236                 :             :         {
     237                 :           0 :                 *logdetail = psprintf(_("Password does not match for user \"%s\"."),
     238                 :           0 :                                                           role);
     239                 :           0 :                 retval = STATUS_ERROR;
     240                 :             :         }
     241                 :             : 
     242                 :           0 :         return retval;
     243                 :           0 : }
     244                 :             : 
     245                 :             : /*
     246                 :             :  * Check given password for given user, and return STATUS_OK or STATUS_ERROR.
     247                 :             :  *
     248                 :             :  * 'shadow_pass' is the user's correct password hash, as stored in
     249                 :             :  * pg_authid.rolpassword.
     250                 :             :  * 'client_pass' is the password given by the remote user.
     251                 :             :  *
     252                 :             :  * In the error case, store a string at *logdetail that will be sent to the
     253                 :             :  * postmaster log (but not the client).
     254                 :             :  */
     255                 :             : int
     256                 :          20 : plain_crypt_verify(const char *role, const char *shadow_pass,
     257                 :             :                                    const char *client_pass,
     258                 :             :                                    const char **logdetail)
     259                 :             : {
     260                 :          20 :         char            crypt_client_pass[MD5_PASSWD_LEN + 1];
     261                 :          20 :         const char *errstr = NULL;
     262                 :             : 
     263                 :             :         /*
     264                 :             :          * Client sent password in plaintext.  If we have an MD5 hash stored, hash
     265                 :             :          * the password the client sent, and compare the hashes.  Otherwise
     266                 :             :          * compare the plaintext passwords directly.
     267                 :             :          */
     268      [ +  +  + ]:          20 :         switch (get_password_type(shadow_pass))
     269                 :             :         {
     270                 :             :                 case PASSWORD_TYPE_SCRAM_SHA_256:
     271   [ +  +  +  + ]:          10 :                         if (scram_verify_plain_password(role,
     272                 :           5 :                                                                                         client_pass,
     273                 :           5 :                                                                                         shadow_pass))
     274                 :             :                         {
     275                 :           1 :                                 return STATUS_OK;
     276                 :             :                         }
     277                 :             :                         else
     278                 :             :                         {
     279                 :           8 :                                 *logdetail = psprintf(_("Password does not match for user \"%s\"."),
     280                 :           4 :                                                                           role);
     281                 :           4 :                                 return STATUS_ERROR;
     282                 :             :                         }
     283                 :             :                         break;
     284                 :             : 
     285                 :             :                 case PASSWORD_TYPE_MD5:
     286   [ +  -  +  - ]:           6 :                         if (!pg_md5_encrypt(client_pass,
     287                 :           3 :                                                                 (uint8 *) role,
     288                 :           3 :                                                                 strlen(role),
     289                 :           3 :                                                                 crypt_client_pass,
     290                 :             :                                                                 &errstr))
     291                 :             :                         {
     292                 :           0 :                                 *logdetail = errstr;
     293                 :           0 :                                 return STATUS_ERROR;
     294                 :             :                         }
     295         [ +  + ]:           3 :                         if (strcmp(crypt_client_pass, shadow_pass) == 0)
     296                 :           1 :                                 return STATUS_OK;
     297                 :             :                         else
     298                 :             :                         {
     299                 :           4 :                                 *logdetail = psprintf(_("Password does not match for user \"%s\"."),
     300                 :           2 :                                                                           role);
     301                 :           2 :                                 return STATUS_ERROR;
     302                 :             :                         }
     303                 :             :                         break;
     304                 :             : 
     305                 :             :                 case PASSWORD_TYPE_PLAINTEXT:
     306                 :             : 
     307                 :             :                         /*
     308                 :             :                          * We never store passwords in plaintext, so this shouldn't
     309                 :             :                          * happen.
     310                 :             :                          */
     311                 :             :                         break;
     312                 :             :         }
     313                 :             : 
     314                 :             :         /*
     315                 :             :          * This shouldn't happen.  Plain "password" authentication is possible
     316                 :             :          * with any kind of stored password hash.
     317                 :             :          */
     318                 :          24 :         *logdetail = psprintf(_("Password of user \"%s\" is in unrecognized format."),
     319                 :          12 :                                                   role);
     320                 :          12 :         return STATUS_ERROR;
     321                 :          20 : }
        

Generated by: LCOV version 2.3.2-1