LCOV - code coverage report
Current view: top level - src/backend/libpq - crypt.c (source / functions) Hit Total Coverage
Test: PostgreSQL 17beta1 Lines: 58 78 74.4 %
Date: 2024-06-17 23:11:01 Functions: 5 5 100.0 %
Legend: Lines: hit not hit

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

Generated by: LCOV version 1.14