LCOV - differential code coverage report
Current view: top level - src/backend/libpq - crypt.c (source / functions) Coverage Total Hit LBC UIC UBC GBC GIC GNC CBC EUB ECB DCB
Current: Differential Code Coverage HEAD vs 15 Lines: 74.7 % 79 59 2 8 10 1 29 2 27 9 27 3
Current Date: 2023-04-08 15:15:32 Functions: 100.0 % 5 5 3 1 1 3
Baseline: 15
Baseline Date: 2023-04-08 15:09:40
Legend: Lines: hit not hit

           TLA  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-2023, 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 "miscadmin.h"
      24                 : #include "utils/builtins.h"
      25                 : #include "utils/syscache.h"
      26                 : #include "utils/timestamp.h"
      27                 : 
      28                 : 
      29                 : /*
      30                 :  * Fetch stored password for a user, for authentication.
      31                 :  *
      32                 :  * On error, returns NULL, and stores a palloc'd string describing the reason,
      33                 :  * for the postmaster log, in *logdetail.  The error reason should *not* be
      34                 :  * sent to the client, to avoid giving away user information!
      35                 :  */
      36                 : char *
      37 CBC          60 : get_role_password(const char *role, const char **logdetail)
      38                 : {
      39              60 :     TimestampTz vuntil = 0;
      40                 :     HeapTuple   roleTup;
      41                 :     Datum       datum;
      42                 :     bool        isnull;
      43                 :     char       *shadow_pass;
      44                 : 
      45                 :     /* Get role info from pg_authid */
      46              60 :     roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(role));
      47              60 :     if (!HeapTupleIsValid(roleTup))
      48                 :     {
      49 UBC           0 :         *logdetail = psprintf(_("Role \"%s\" does not exist."),
      50                 :                               role);
      51               0 :         return NULL;            /* no such user */
      52                 :     }
      53                 : 
      54 CBC          60 :     datum = SysCacheGetAttr(AUTHNAME, roleTup,
      55                 :                             Anum_pg_authid_rolpassword, &isnull);
      56              60 :     if (isnull)
      57                 :     {
      58 UBC           0 :         ReleaseSysCache(roleTup);
      59               0 :         *logdetail = psprintf(_("User \"%s\" has no password assigned."),
      60                 :                               role);
      61               0 :         return NULL;            /* user has no password */
      62                 :     }
      63 CBC          60 :     shadow_pass = TextDatumGetCString(datum);
      64                 : 
      65              60 :     datum = SysCacheGetAttr(AUTHNAME, roleTup,
      66                 :                             Anum_pg_authid_rolvaliduntil, &isnull);
      67              60 :     if (!isnull)
      68 UBC           0 :         vuntil = DatumGetTimestampTz(datum);
      69                 : 
      70 CBC          60 :     ReleaseSysCache(roleTup);
      71                 : 
      72                 :     /*
      73                 :      * Password OK, but check to be sure we are not past rolvaliduntil
      74                 :      */
      75              60 :     if (!isnull && vuntil < GetCurrentTimestamp())
      76                 :     {
      77 UBC           0 :         *logdetail = psprintf(_("User \"%s\" has an expired password."),
      78                 :                               role);
      79               0 :         return NULL;
      80                 :     }
      81                 : 
      82 CBC          60 :     return shadow_pass;
      83                 : }
      84                 : 
      85                 : /*
      86                 :  * What kind of a password type is 'shadow_pass'?
      87                 :  */
      88                 : PasswordType
      89             250 : get_password_type(const char *shadow_pass)
      90                 : {
      91                 :     char       *encoded_salt;
      92                 :     int         iterations;
      93 GNC         250 :     int         key_length = 0;
      94                 :     pg_cryptohash_type hash_type;
      95                 :     uint8       stored_key[SCRAM_MAX_KEY_LEN];
      96                 :     uint8       server_key[SCRAM_MAX_KEY_LEN];
      97                 : 
      98 GIC         250 :     if (strncmp(shadow_pass, "md5", 3) == 0 &&
      99              62 :         strlen(shadow_pass) == MD5_PASSWD_LEN &&
     100 CBC          56 :         strspn(shadow_pass + 3, MD5_PASSWD_CHARSET) == MD5_PASSWD_LEN - 3)
     101              50 :         return PASSWORD_TYPE_MD5;
     102 GNC         200 :     if (parse_scram_secret(shadow_pass, &iterations, &hash_type, &key_length,
     103                 :                            &encoded_salt, stored_key, server_key))
     104 CBC          88 :         return PASSWORD_TYPE_SCRAM_SHA_256;
     105 GIC         112 :     return PASSWORD_TYPE_PLAINTEXT;
     106 ECB             : }
     107                 : 
     108                 : /*
     109                 :  * Given a user-supplied password, convert it into a secret of
     110                 :  * 'target_type' kind.
     111                 :  *
     112                 :  * If the password is already in encrypted form, we cannot reverse the
     113                 :  * hash, so it is stored as it is regardless of the requested type.
     114                 :  */
     115                 : char *
     116 GIC          68 : encrypt_password(PasswordType target_type, const char *role,
     117                 :                  const char *password)
     118 ECB             : {
     119 GIC          68 :     PasswordType guessed_type = get_password_type(password);
     120                 :     char       *encrypted_password;
     121 CBC          68 :     const char *errstr = NULL;
     122                 : 
     123              68 :     if (guessed_type != PASSWORD_TYPE_PLAINTEXT)
     124                 :     {
     125 ECB             :         /*
     126                 :          * Cannot convert an already-encrypted password from one format to
     127                 :          * another, so return it as it is.
     128                 :          */
     129 GIC          14 :         return pstrdup(password);
     130                 :     }
     131 ECB             : 
     132 GIC          54 :     switch (target_type)
     133                 :     {
     134 CBC          12 :         case PASSWORD_TYPE_MD5:
     135 GIC          12 :             encrypted_password = palloc(MD5_PASSWD_LEN + 1);
     136 ECB             : 
     137 CBC          12 :             if (!pg_md5_encrypt(password, role, strlen(role),
     138                 :                                 encrypted_password, &errstr))
     139 LBC           0 :                 elog(ERROR, "password encryption failed: %s", errstr);
     140 GIC          12 :             return encrypted_password;
     141 EUB             : 
     142 CBC          42 :         case PASSWORD_TYPE_SCRAM_SHA_256:
     143 GIC          42 :             return pg_be_scram_build_secret(password);
     144 ECB             : 
     145 LBC           0 :         case PASSWORD_TYPE_PLAINTEXT:
     146 UIC           0 :             elog(ERROR, "cannot encrypt password with 'plaintext'");
     147 EUB             :     }
     148                 : 
     149                 :     /*
     150                 :      * This shouldn't happen, because the above switch statements should
     151                 :      * handle every combination of source and target password types.
     152                 :      */
     153 UIC           0 :     elog(ERROR, "cannot encrypt password to requested type");
     154                 :     return NULL;                /* keep compiler quiet */
     155 EUB             : }
     156                 : 
     157                 : /*
     158                 :  * Check MD5 authentication response, and return STATUS_OK or STATUS_ERROR.
     159                 :  *
     160                 :  * 'shadow_pass' is the user's correct password or password hash, as stored
     161                 :  * in pg_authid.rolpassword.
     162                 :  * 'client_pass' is the response given by the remote user to the MD5 challenge.
     163                 :  * 'md5_salt' is the salt used in the MD5 authentication challenge.
     164                 :  *
     165                 :  * In the error case, save a string at *logdetail that will be sent to the
     166                 :  * postmaster log (but not the client).
     167                 :  */
     168                 : int
     169 GIC           6 : md5_crypt_verify(const char *role, const char *shadow_pass,
     170                 :                  const char *client_pass,
     171 ECB             :                  const char *md5_salt, int md5_salt_len,
     172                 :                  const char **logdetail)
     173                 : {
     174                 :     int         retval;
     175                 :     char        crypt_pwd[MD5_PASSWD_LEN + 1];
     176 GIC           6 :     const char *errstr = NULL;
     177                 : 
     178 CBC           6 :     Assert(md5_salt_len > 0);
     179                 : 
     180               6 :     if (get_password_type(shadow_pass) != PASSWORD_TYPE_MD5)
     181                 :     {
     182 ECB             :         /* incompatible password hash format. */
     183 UIC           0 :         *logdetail = psprintf(_("User \"%s\" has a password that cannot be used with MD5 authentication."),
     184                 :                               role);
     185 UBC           0 :         return STATUS_ERROR;
     186                 :     }
     187 EUB             : 
     188                 :     /*
     189                 :      * Compute the correct answer for the MD5 challenge.
     190                 :      */
     191                 :     /* stored password already encrypted, only do salt */
     192 GIC           6 :     if (!pg_md5_encrypt(shadow_pass + strlen("md5"),
     193                 :                         md5_salt, md5_salt_len,
     194 ECB             :                         crypt_pwd, &errstr))
     195                 :     {
     196 UIC           0 :         *logdetail = errstr;
     197               0 :         return STATUS_ERROR;
     198 EUB             :     }
     199                 : 
     200 GIC           6 :     if (strcmp(client_pass, crypt_pwd) == 0)
     201               6 :         retval = STATUS_OK;
     202 ECB             :     else
     203                 :     {
     204 UIC           0 :         *logdetail = psprintf(_("Password does not match for user \"%s\"."),
     205                 :                               role);
     206 UBC           0 :         retval = STATUS_ERROR;
     207                 :     }
     208 EUB             : 
     209 GIC           6 :     return retval;
     210                 : }
     211 ECB             : 
     212                 : /*
     213                 :  * Check given password for given user, and return STATUS_OK or STATUS_ERROR.
     214                 :  *
     215                 :  * 'shadow_pass' is the user's correct password hash, as stored in
     216                 :  * pg_authid.rolpassword.
     217                 :  * 'client_pass' is the password given by the remote user.
     218                 :  *
     219                 :  * In the error case, store a string at *logdetail that will be sent to the
     220                 :  * postmaster log (but not the client).
     221                 :  */
     222                 : int
     223 GIC          86 : plain_crypt_verify(const char *role, const char *shadow_pass,
     224                 :                    const char *client_pass,
     225 ECB             :                    const char **logdetail)
     226                 : {
     227                 :     char        crypt_client_pass[MD5_PASSWD_LEN + 1];
     228 GIC          86 :     const char *errstr = NULL;
     229                 : 
     230 ECB             :     /*
     231                 :      * Client sent password in plaintext.  If we have an MD5 hash stored, hash
     232                 :      * the password the client sent, and compare the hashes.  Otherwise
     233                 :      * compare the plaintext passwords directly.
     234                 :      */
     235 GIC          86 :     switch (get_password_type(shadow_pass))
     236                 :     {
     237 CBC          15 :         case PASSWORD_TYPE_SCRAM_SHA_256:
     238 GIC          15 :             if (scram_verify_plain_password(role,
     239 ECB             :                                             client_pass,
     240                 :                                             shadow_pass))
     241                 :             {
     242 GIC           8 :                 return STATUS_OK;
     243                 :             }
     244 ECB             :             else
     245                 :             {
     246 GIC           7 :                 *logdetail = psprintf(_("Password does not match for user \"%s\"."),
     247                 :                                       role);
     248 CBC           7 :                 return STATUS_ERROR;
     249                 :             }
     250 ECB             :             break;
     251                 : 
     252 GIC          17 :         case PASSWORD_TYPE_MD5:
     253              17 :             if (!pg_md5_encrypt(client_pass,
     254 ECB             :                                 role,
     255                 :                                 strlen(role),
     256                 :                                 crypt_client_pass,
     257                 :                                 &errstr))
     258                 :             {
     259 UIC           0 :                 *logdetail = errstr;
     260               0 :                 return STATUS_ERROR;
     261 EUB             :             }
     262 GBC          17 :             if (strcmp(crypt_client_pass, shadow_pass) == 0)
     263 GIC           9 :                 return STATUS_OK;
     264 ECB             :             else
     265                 :             {
     266 GIC           8 :                 *logdetail = psprintf(_("Password does not match for user \"%s\"."),
     267                 :                                       role);
     268 CBC           8 :                 return STATUS_ERROR;
     269                 :             }
     270 ECB             :             break;
     271                 : 
     272 GIC          54 :         case PASSWORD_TYPE_PLAINTEXT:
     273                 : 
     274 ECB             :             /*
     275                 :              * We never store passwords in plaintext, so this shouldn't
     276                 :              * happen.
     277                 :              */
     278 GIC          54 :             break;
     279                 :     }
     280 ECB             : 
     281                 :     /*
     282                 :      * This shouldn't happen.  Plain "password" authentication is possible
     283                 :      * with any kind of stored password hash.
     284                 :      */
     285 GIC          54 :     *logdetail = psprintf(_("Password of user \"%s\" is in unrecognized format."),
     286                 :                           role);
     287 CBC          54 :     return STATUS_ERROR;
     288                 : }
        

Generated by: LCOV version v1.16-55-g56c0a2a