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 17:13:01 Functions: 100.0 % 5 5 3 1 1 3
Baseline: 15 Line coverage date bins:
Baseline Date: 2023-04-08 15:09:40 (60,120] days: 100.0 % 2 2 2
Legend: Lines: hit not hit (240..) days: 74.0 % 77 57 2 8 10 1 29 27 9 26
Function coverage date bins:
(240..) days: 62.5 % 8 5 3 1 1 3

 Age         Owner                  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 *
  453 michael                    37 CBC          60 : get_role_password(const char *role, const char **logdetail)
                                 38                 : {
 4971 tgl                        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 */
 4802 rhaas                      46              60 :     roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(role));
 4971 tgl                        47              60 :     if (!HeapTupleIsValid(roleTup))
                                 48                 :     {
 2649 tgl                        49 UBC           0 :         *logdetail = psprintf(_("Role \"%s\" does not exist."),
                                 50                 :                               role);
 2153 bruce                      51               0 :         return NULL;            /* no such user */
                                 52                 :     }
                                 53                 : 
 4971 tgl                        54 CBC          60 :     datum = SysCacheGetAttr(AUTHNAME, roleTup,
                                 55                 :                             Anum_pg_authid_rolpassword, &isnull);
                                 56              60 :     if (isnull)
                                 57                 :     {
 4971 tgl                        58 UBC           0 :         ReleaseSysCache(roleTup);
 3359                            59               0 :         *logdetail = psprintf(_("User \"%s\" has no password assigned."),
                                 60                 :                               role);
 2153 bruce                      61               0 :         return NULL;            /* user has no password */
                                 62                 :     }
 2207 heikki.linnakangas         63 CBC          60 :     shadow_pass = TextDatumGetCString(datum);
                                 64                 : 
 4971 tgl                        65              60 :     datum = SysCacheGetAttr(AUTHNAME, roleTup,
                                 66                 :                             Anum_pg_authid_rolvaliduntil, &isnull);
                                 67              60 :     if (!isnull)
 4971 tgl                        68 UBC           0 :         vuntil = DatumGetTimestampTz(datum);
                                 69                 : 
 4971 tgl                        70 CBC          60 :     ReleaseSysCache(roleTup);
                                 71                 : 
                                 72                 :     /*
                                 73                 :      * Password OK, but check to be sure we are not past rolvaliduntil
                                 74                 :      */
 2207 heikki.linnakangas         75              60 :     if (!isnull && vuntil < GetCurrentTimestamp())
                                 76                 :     {
 2309 heikki.linnakangas         77 UBC           0 :         *logdetail = psprintf(_("User \"%s\" has an expired password."),
                                 78                 :                               role);
 2207                            79               0 :         return NULL;
                                 80                 :     }
                                 81                 : 
 2207 heikki.linnakangas         82 CBC          60 :     return shadow_pass;
                                 83                 : }
                                 84                 : 
                                 85                 : /*
                                 86                 :  * What kind of a password type is 'shadow_pass'?
                                 87                 :  */
                                 88                 : PasswordType
 2258                            89             250 : get_password_type(const char *shadow_pass)
                                 90                 : {
                                 91                 :     char       *encoded_salt;
                                 92                 :     int         iterations;
  110 michael                    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                 : 
 1447 michael                    98 GIC         250 :     if (strncmp(shadow_pass, "md5", 3) == 0 &&
                                 99              62 :         strlen(shadow_pass) == MD5_PASSWD_LEN &&
 1447 michael                   100 CBC          56 :         strspn(shadow_pass + 3, MD5_PASSWD_CHARSET) == MD5_PASSWD_LEN - 3)
 2258 heikki.linnakangas        101              50 :         return PASSWORD_TYPE_MD5;
  110 michael                   102 GNC         200 :     if (parse_scram_secret(shadow_pass, &iterations, &hash_type, &key_length,
                                103                 :                            &encoded_salt, stored_key, server_key))
 2182 heikki.linnakangas        104 CBC          88 :         return PASSWORD_TYPE_SCRAM_SHA_256;
 2258 heikki.linnakangas        105 GIC         112 :     return PASSWORD_TYPE_PLAINTEXT;
 2258 heikki.linnakangas        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 *
 2258 heikki.linnakangas        116 GIC          68 : encrypt_password(PasswordType target_type, const char *role,
                                117                 :                  const char *password)
 2258 heikki.linnakangas        118 ECB             : {
 2258 heikki.linnakangas        119 GIC          68 :     PasswordType guessed_type = get_password_type(password);
                                120                 :     char       *encrypted_password;
  453 michael                   121 CBC          68 :     const char *errstr = NULL;
                                122                 : 
 2162 heikki.linnakangas        123              68 :     if (guessed_type != PASSWORD_TYPE_PLAINTEXT)
                                124                 :     {
 2162 heikki.linnakangas        125 ECB             :         /*
                                126                 :          * Cannot convert an already-encrypted password from one format to
                                127                 :          * another, so return it as it is.
                                128                 :          */
 2162 heikki.linnakangas        129 GIC          14 :         return pstrdup(password);
                                130                 :     }
 2258 heikki.linnakangas        131 ECB             : 
 2162 heikki.linnakangas        132 GIC          54 :     switch (target_type)
                                133                 :     {
 2258 heikki.linnakangas        134 CBC          12 :         case PASSWORD_TYPE_MD5:
 2162 heikki.linnakangas        135 GIC          12 :             encrypted_password = palloc(MD5_PASSWD_LEN + 1);
 2258 heikki.linnakangas        136 ECB             : 
 2162 heikki.linnakangas        137 CBC          12 :             if (!pg_md5_encrypt(password, role, strlen(role),
                                138                 :                                 encrypted_password, &errstr))
  453 michael                   139 LBC           0 :                 elog(ERROR, "password encryption failed: %s", errstr);
 2162 heikki.linnakangas        140 GIC          12 :             return encrypted_password;
 2224 heikki.linnakangas        141 EUB             : 
 2182 heikki.linnakangas        142 CBC          42 :         case PASSWORD_TYPE_SCRAM_SHA_256:
 1275 peter                     143 GIC          42 :             return pg_be_scram_build_secret(password);
 2224 heikki.linnakangas        144 ECB             : 
 2162 heikki.linnakangas        145 LBC           0 :         case PASSWORD_TYPE_PLAINTEXT:
 2162 heikki.linnakangas        146 UIC           0 :             elog(ERROR, "cannot encrypt password with 'plaintext'");
 2258 heikki.linnakangas        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                 :      */
 2258 heikki.linnakangas        153 UIC           0 :     elog(ERROR, "cannot encrypt password to requested type");
                                154                 :     return NULL;                /* keep compiler quiet */
 2258 heikki.linnakangas        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
 2309 heikki.linnakangas        169 GIC           6 : md5_crypt_verify(const char *role, const char *shadow_pass,
                                170                 :                  const char *client_pass,
 2309 heikki.linnakangas        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];
  453 michael                   176 GIC           6 :     const char *errstr = NULL;
                                177                 : 
 2309 heikki.linnakangas        178 CBC           6 :     Assert(md5_salt_len > 0);
                                179                 : 
 2162                           180               6 :     if (get_password_type(shadow_pass) != PASSWORD_TYPE_MD5)
                                181                 :     {
 2162 heikki.linnakangas        182 ECB             :         /* incompatible password hash format. */
 2162 heikki.linnakangas        183 UIC           0 :         *logdetail = psprintf(_("User \"%s\" has a password that cannot be used with MD5 authentication."),
                                184                 :                               role);
 2162 heikki.linnakangas        185 UBC           0 :         return STATUS_ERROR;
                                186                 :     }
 2162 heikki.linnakangas        187 EUB             : 
                                188                 :     /*
                                189                 :      * Compute the correct answer for the MD5 challenge.
                                190                 :      */
                                191                 :     /* stored password already encrypted, only do salt */
 2162 heikki.linnakangas        192 GIC           6 :     if (!pg_md5_encrypt(shadow_pass + strlen("md5"),
                                193                 :                         md5_salt, md5_salt_len,
  453 michael                   194 ECB             :                         crypt_pwd, &errstr))
                                195                 :     {
  453 michael                   196 UIC           0 :         *logdetail = errstr;
 2162 heikki.linnakangas        197               0 :         return STATUS_ERROR;
 7907 bruce                     198 EUB             :     }
 9173                           199                 : 
 2309 heikki.linnakangas        200 GIC           6 :     if (strcmp(client_pass, crypt_pwd) == 0)
                                201               6 :         retval = STATUS_OK;
 2309 heikki.linnakangas        202 ECB             :     else
 9173 bruce                     203                 :     {
 2309 heikki.linnakangas        204 UIC           0 :         *logdetail = psprintf(_("Password does not match for user \"%s\"."),
                                205                 :                               role);
 2309 heikki.linnakangas        206 UBC           0 :         retval = STATUS_ERROR;
                                207                 :     }
 2309 heikki.linnakangas        208 EUB             : 
 2309 heikki.linnakangas        209 GIC           6 :     return retval;
                                210                 : }
 2309 heikki.linnakangas        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
 2309 heikki.linnakangas        223 GIC          86 : plain_crypt_verify(const char *role, const char *shadow_pass,
                                224                 :                    const char *client_pass,
  453 michael                   225 ECB             :                    const char **logdetail)
                                226                 : {
                                227                 :     char        crypt_client_pass[MD5_PASSWD_LEN + 1];
  453 michael                   228 GIC          86 :     const char *errstr = NULL;
                                229                 : 
 2309 heikki.linnakangas        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                 :      */
 2258 heikki.linnakangas        235 GIC          86 :     switch (get_password_type(shadow_pass))
                                236                 :     {
 2182 heikki.linnakangas        237 CBC          15 :         case PASSWORD_TYPE_SCRAM_SHA_256:
 2214 heikki.linnakangas        238 GIC          15 :             if (scram_verify_plain_password(role,
 2214 heikki.linnakangas        239 ECB             :                                             client_pass,
                                240                 :                                             shadow_pass))
                                241                 :             {
 2214 heikki.linnakangas        242 GIC           8 :                 return STATUS_OK;
                                243                 :             }
 2214 heikki.linnakangas        244 ECB             :             else
                                245                 :             {
 2214 heikki.linnakangas        246 GIC           7 :                 *logdetail = psprintf(_("Password does not match for user \"%s\"."),
                                247                 :                                       role);
 2214 heikki.linnakangas        248 CBC           7 :                 return STATUS_ERROR;
                                249                 :             }
 2214 heikki.linnakangas        250 ECB             :             break;
                                251                 : 
 2258 heikki.linnakangas        252 GIC          17 :         case PASSWORD_TYPE_MD5:
                                253              17 :             if (!pg_md5_encrypt(client_pass,
 2258 heikki.linnakangas        254 ECB             :                                 role,
                                255                 :                                 strlen(role),
                                256                 :                                 crypt_client_pass,
                                257                 :                                 &errstr))
                                258                 :             {
  453 michael                   259 UIC           0 :                 *logdetail = errstr;
 2258 heikki.linnakangas        260               0 :                 return STATUS_ERROR;
 2258 heikki.linnakangas        261 EUB             :             }
 2214 heikki.linnakangas        262 GBC          17 :             if (strcmp(crypt_client_pass, shadow_pass) == 0)
 2214 heikki.linnakangas        263 GIC           9 :                 return STATUS_OK;
 2214 heikki.linnakangas        264 ECB             :             else
                                265                 :             {
 2214 heikki.linnakangas        266 GIC           8 :                 *logdetail = psprintf(_("Password does not match for user \"%s\"."),
                                267                 :                                       role);
 2214 heikki.linnakangas        268 CBC           8 :                 return STATUS_ERROR;
                                269                 :             }
 2258 heikki.linnakangas        270 ECB             :             break;
                                271                 : 
 2258 heikki.linnakangas        272 GIC          54 :         case PASSWORD_TYPE_PLAINTEXT:
                                273                 : 
 2162 heikki.linnakangas        274 ECB             :             /*
                                275                 :              * We never store passwords in plaintext, so this shouldn't
                                276                 :              * happen.
                                277                 :              */
 2258 heikki.linnakangas        278 GIC          54 :             break;
                                279                 :     }
 2309 heikki.linnakangas        280 ECB             : 
                                281                 :     /*
                                282                 :      * This shouldn't happen.  Plain "password" authentication is possible
                                283                 :      * with any kind of stored password hash.
                                284                 :      */
 2214 heikki.linnakangas        285 GIC          54 :     *logdetail = psprintf(_("Password of user \"%s\" is in unrecognized format."),
                                286                 :                           role);
 2214 heikki.linnakangas        287 CBC          54 :     return STATUS_ERROR;
                                288                 : }
        

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