LCOV - code coverage report
Current view: top level - src/backend/libpq - crypt.c (source / functions) Hit Total Coverage
Test: PostgreSQL 13devel Lines: 43 66 65.2 %
Date: 2019-09-22 08:06:49 Functions: 4 5 80.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-2019, 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          34 : get_role_password(const char *role, char **logdetail)
      38             : {
      39          34 :     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          34 :     roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(role));
      47          34 :     if (!HeapTupleIsValid(roleTup))
      48             :     {
      49           0 :         *logdetail = psprintf(_("Role \"%s\" does not exist."),
      50             :                               role);
      51           0 :         return NULL;            /* no such user */
      52             :     }
      53             : 
      54          34 :     datum = SysCacheGetAttr(AUTHNAME, roleTup,
      55             :                             Anum_pg_authid_rolpassword, &isnull);
      56          34 :     if (isnull)
      57             :     {
      58           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          34 :     shadow_pass = TextDatumGetCString(datum);
      64             : 
      65          34 :     datum = SysCacheGetAttr(AUTHNAME, roleTup,
      66             :                             Anum_pg_authid_rolvaliduntil, &isnull);
      67          34 :     if (!isnull)
      68           0 :         vuntil = DatumGetTimestampTz(datum);
      69             : 
      70          34 :     ReleaseSysCache(roleTup);
      71             : 
      72             :     /*
      73             :      * Password OK, but check to be sure we are not past rolvaliduntil
      74             :      */
      75          34 :     if (!isnull && vuntil < GetCurrentTimestamp())
      76             :     {
      77           0 :         *logdetail = psprintf(_("User \"%s\" has an expired password."),
      78             :                               role);
      79           0 :         return NULL;
      80             :     }
      81             : 
      82          34 :     return shadow_pass;
      83             : }
      84             : 
      85             : /*
      86             :  * What kind of a password verifier is 'shadow_pass'?
      87             :  */
      88             : PasswordType
      89         268 : get_password_type(const char *shadow_pass)
      90             : {
      91             :     char       *encoded_salt;
      92             :     int         iterations;
      93             :     uint8       stored_key[SCRAM_KEY_LEN];
      94             :     uint8       server_key[SCRAM_KEY_LEN];
      95             : 
      96         326 :     if (strncmp(shadow_pass, "md5", 3) == 0 &&
      97         108 :         strlen(shadow_pass) == MD5_PASSWD_LEN &&
      98          50 :         strspn(shadow_pass + 3, MD5_PASSWD_CHARSET) == MD5_PASSWD_LEN - 3)
      99          42 :         return PASSWORD_TYPE_MD5;
     100         226 :     if (parse_scram_verifier(shadow_pass, &iterations, &encoded_salt,
     101             :                              stored_key, server_key))
     102          78 :         return PASSWORD_TYPE_SCRAM_SHA_256;
     103         148 :     return PASSWORD_TYPE_PLAINTEXT;
     104             : }
     105             : 
     106             : /*
     107             :  * Given a user-supplied password, convert it into a verifier of
     108             :  * 'target_type' kind.
     109             :  *
     110             :  * If the password is already in encrypted form, we cannot reverse the
     111             :  * hash, so it is stored as it is regardless of the requested type.
     112             :  */
     113             : char *
     114          88 : encrypt_password(PasswordType target_type, const char *role,
     115             :                  const char *password)
     116             : {
     117          88 :     PasswordType guessed_type = get_password_type(password);
     118             :     char       *encrypted_password;
     119             : 
     120          88 :     if (guessed_type != PASSWORD_TYPE_PLAINTEXT)
     121             :     {
     122             :         /*
     123             :          * Cannot convert an already-encrypted password from one format to
     124             :          * another, so return it as it is.
     125             :          */
     126          18 :         return pstrdup(password);
     127             :     }
     128             : 
     129          70 :     switch (target_type)
     130             :     {
     131             :         case PASSWORD_TYPE_MD5:
     132          26 :             encrypted_password = palloc(MD5_PASSWD_LEN + 1);
     133             : 
     134          26 :             if (!pg_md5_encrypt(password, role, strlen(role),
     135             :                                 encrypted_password))
     136           0 :                 elog(ERROR, "password encryption failed");
     137          26 :             return encrypted_password;
     138             : 
     139             :         case PASSWORD_TYPE_SCRAM_SHA_256:
     140          44 :             return pg_be_scram_build_verifier(password);
     141             : 
     142             :         case PASSWORD_TYPE_PLAINTEXT:
     143           0 :             elog(ERROR, "cannot encrypt password with 'plaintext'");
     144             :     }
     145             : 
     146             :     /*
     147             :      * This shouldn't happen, because the above switch statements should
     148             :      * handle every combination of source and target password types.
     149             :      */
     150           0 :     elog(ERROR, "cannot encrypt password to requested type");
     151             :     return NULL;                /* keep compiler quiet */
     152             : }
     153             : 
     154             : /*
     155             :  * Check MD5 authentication response, and return STATUS_OK or STATUS_ERROR.
     156             :  *
     157             :  * 'shadow_pass' is the user's correct password or password hash, as stored
     158             :  * in pg_authid.rolpassword.
     159             :  * 'client_pass' is the response given by the remote user to the MD5 challenge.
     160             :  * 'md5_salt' is the salt used in the MD5 authentication challenge.
     161             :  *
     162             :  * In the error case, optionally store a palloc'd string at *logdetail
     163             :  * that will be sent to the postmaster log (but not the client).
     164             :  */
     165             : int
     166           0 : md5_crypt_verify(const char *role, const char *shadow_pass,
     167             :                  const char *client_pass,
     168             :                  const char *md5_salt, int md5_salt_len,
     169             :                  char **logdetail)
     170             : {
     171             :     int         retval;
     172             :     char        crypt_pwd[MD5_PASSWD_LEN + 1];
     173             : 
     174             :     Assert(md5_salt_len > 0);
     175             : 
     176           0 :     if (get_password_type(shadow_pass) != PASSWORD_TYPE_MD5)
     177             :     {
     178             :         /* incompatible password hash format. */
     179           0 :         *logdetail = psprintf(_("User \"%s\" has a password that cannot be used with MD5 authentication."),
     180             :                               role);
     181           0 :         return STATUS_ERROR;
     182             :     }
     183             : 
     184             :     /*
     185             :      * Compute the correct answer for the MD5 challenge.
     186             :      *
     187             :      * We do not bother setting logdetail for any pg_md5_encrypt failure
     188             :      * below: the only possible error is out-of-memory, which is unlikely, and
     189             :      * if it did happen adding a psprintf call would only make things worse.
     190             :      */
     191             :     /* stored password already encrypted, only do salt */
     192           0 :     if (!pg_md5_encrypt(shadow_pass + strlen("md5"),
     193             :                         md5_salt, md5_salt_len,
     194             :                         crypt_pwd))
     195             :     {
     196           0 :         return STATUS_ERROR;
     197             :     }
     198             : 
     199           0 :     if (strcmp(client_pass, crypt_pwd) == 0)
     200           0 :         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           0 :     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, optionally store a palloc'd string at *logdetail
     219             :  * that will be sent to the postmaster log (but not the client).
     220             :  */
     221             : int
     222         104 : plain_crypt_verify(const char *role, const char *shadow_pass,
     223             :                    const char *client_pass,
     224             :                    char **logdetail)
     225             : {
     226             :     char        crypt_client_pass[MD5_PASSWD_LEN + 1];
     227             : 
     228             :     /*
     229             :      * Client sent password in plaintext.  If we have an MD5 hash stored, hash
     230             :      * the password the client sent, and compare the hashes.  Otherwise
     231             :      * compare the plaintext passwords directly.
     232             :      */
     233         104 :     switch (get_password_type(shadow_pass))
     234             :     {
     235             :         case PASSWORD_TYPE_SCRAM_SHA_256:
     236          14 :             if (scram_verify_plain_password(role,
     237             :                                             client_pass,
     238             :                                             shadow_pass))
     239             :             {
     240           6 :                 return STATUS_OK;
     241             :             }
     242             :             else
     243             :             {
     244           8 :                 *logdetail = psprintf(_("Password does not match for user \"%s\"."),
     245             :                                       role);
     246           8 :                 return STATUS_ERROR;
     247             :             }
     248             :             break;
     249             : 
     250             :         case PASSWORD_TYPE_MD5:
     251          20 :             if (!pg_md5_encrypt(client_pass,
     252             :                                 role,
     253             :                                 strlen(role),
     254             :                                 crypt_client_pass))
     255             :             {
     256             :                 /*
     257             :                  * We do not bother setting logdetail for pg_md5_encrypt
     258             :                  * failure: the only possible error is out-of-memory, which is
     259             :                  * unlikely, and if it did happen adding a psprintf call would
     260             :                  * only make things worse.
     261             :                  */
     262           0 :                 return STATUS_ERROR;
     263             :             }
     264          20 :             if (strcmp(crypt_client_pass, shadow_pass) == 0)
     265           8 :                 return STATUS_OK;
     266             :             else
     267             :             {
     268          12 :                 *logdetail = psprintf(_("Password does not match for user \"%s\"."),
     269             :                                       role);
     270          12 :                 return STATUS_ERROR;
     271             :             }
     272             :             break;
     273             : 
     274             :         case PASSWORD_TYPE_PLAINTEXT:
     275             : 
     276             :             /*
     277             :              * We never store passwords in plaintext, so this shouldn't
     278             :              * happen.
     279             :              */
     280          70 :             break;
     281             :     }
     282             : 
     283             :     /*
     284             :      * This shouldn't happen.  Plain "password" authentication is possible
     285             :      * with any kind of stored password hash.
     286             :      */
     287          70 :     *logdetail = psprintf(_("Password of user \"%s\" is in unrecognized format."),
     288             :                           role);
     289          70 :     return STATUS_ERROR;
     290             : }

Generated by: LCOV version 1.13