LCOV - code coverage report
Current view: top level - src/backend/libpq - crypt.c (source / functions) Hit Total Coverage
Test: PostgreSQL 12beta2 Lines: 43 66 65.2 %
Date: 2019-06-18 07:06:57 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             : #ifdef HAVE_CRYPT_H
      18             : #include <crypt.h>
      19             : #endif
      20             : 
      21             : #include "catalog/pg_authid.h"
      22             : #include "common/md5.h"
      23             : #include "common/scram-common.h"
      24             : #include "libpq/crypt.h"
      25             : #include "libpq/scram.h"
      26             : #include "miscadmin.h"
      27             : #include "utils/builtins.h"
      28             : #include "utils/syscache.h"
      29             : #include "utils/timestamp.h"
      30             : 
      31             : 
      32             : /*
      33             :  * Fetch stored password for a user, for authentication.
      34             :  *
      35             :  * On error, returns NULL, and stores a palloc'd string describing the reason,
      36             :  * for the postmaster log, in *logdetail.  The error reason should *not* be
      37             :  * sent to the client, to avoid giving away user information!
      38             :  */
      39             : char *
      40          36 : get_role_password(const char *role, char **logdetail)
      41             : {
      42          36 :     TimestampTz vuntil = 0;
      43             :     HeapTuple   roleTup;
      44             :     Datum       datum;
      45             :     bool        isnull;
      46             :     char       *shadow_pass;
      47             : 
      48             :     /* Get role info from pg_authid */
      49          36 :     roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(role));
      50          36 :     if (!HeapTupleIsValid(roleTup))
      51             :     {
      52           0 :         *logdetail = psprintf(_("Role \"%s\" does not exist."),
      53             :                               role);
      54           0 :         return NULL;            /* no such user */
      55             :     }
      56             : 
      57          36 :     datum = SysCacheGetAttr(AUTHNAME, roleTup,
      58             :                             Anum_pg_authid_rolpassword, &isnull);
      59          36 :     if (isnull)
      60             :     {
      61           0 :         ReleaseSysCache(roleTup);
      62           0 :         *logdetail = psprintf(_("User \"%s\" has no password assigned."),
      63             :                               role);
      64           0 :         return NULL;            /* user has no password */
      65             :     }
      66          36 :     shadow_pass = TextDatumGetCString(datum);
      67             : 
      68          36 :     datum = SysCacheGetAttr(AUTHNAME, roleTup,
      69             :                             Anum_pg_authid_rolvaliduntil, &isnull);
      70          36 :     if (!isnull)
      71           0 :         vuntil = DatumGetTimestampTz(datum);
      72             : 
      73          36 :     ReleaseSysCache(roleTup);
      74             : 
      75             :     /*
      76             :      * Password OK, but check to be sure we are not past rolvaliduntil
      77             :      */
      78          36 :     if (!isnull && vuntil < GetCurrentTimestamp())
      79             :     {
      80           0 :         *logdetail = psprintf(_("User \"%s\" has an expired password."),
      81             :                               role);
      82           0 :         return NULL;
      83             :     }
      84             : 
      85          36 :     return shadow_pass;
      86             : }
      87             : 
      88             : /*
      89             :  * What kind of a password verifier is 'shadow_pass'?
      90             :  */
      91             : PasswordType
      92         272 : get_password_type(const char *shadow_pass)
      93             : {
      94             :     char       *encoded_salt;
      95             :     int         iterations;
      96             :     uint8       stored_key[SCRAM_KEY_LEN];
      97             :     uint8       server_key[SCRAM_KEY_LEN];
      98             : 
      99         330 :     if (strncmp(shadow_pass, "md5", 3) == 0 &&
     100         108 :         strlen(shadow_pass) == MD5_PASSWD_LEN &&
     101          50 :         strspn(shadow_pass + 3, MD5_PASSWD_CHARSET) == MD5_PASSWD_LEN - 3)
     102          42 :         return PASSWORD_TYPE_MD5;
     103         230 :     if (parse_scram_verifier(shadow_pass, &iterations, &encoded_salt,
     104             :                              stored_key, server_key))
     105          82 :         return PASSWORD_TYPE_SCRAM_SHA_256;
     106         148 :     return PASSWORD_TYPE_PLAINTEXT;
     107             : }
     108             : 
     109             : /*
     110             :  * Given a user-supplied password, convert it into a verifier 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          88 : encrypt_password(PasswordType target_type, const char *role,
     118             :                  const char *password)
     119             : {
     120          88 :     PasswordType guessed_type = get_password_type(password);
     121             :     char       *encrypted_password;
     122             : 
     123          88 :     if (guessed_type != PASSWORD_TYPE_PLAINTEXT)
     124             :     {
     125             :         /*
     126             :          * Cannot convert an already-encrypted password from one format to
     127             :          * another, so return it as it is.
     128             :          */
     129          18 :         return pstrdup(password);
     130             :     }
     131             : 
     132          70 :     switch (target_type)
     133             :     {
     134             :         case PASSWORD_TYPE_MD5:
     135          26 :             encrypted_password = palloc(MD5_PASSWD_LEN + 1);
     136             : 
     137          26 :             if (!pg_md5_encrypt(password, role, strlen(role),
     138             :                                 encrypted_password))
     139           0 :                 elog(ERROR, "password encryption failed");
     140          26 :             return encrypted_password;
     141             : 
     142             :         case PASSWORD_TYPE_SCRAM_SHA_256:
     143          44 :             return pg_be_scram_build_verifier(password);
     144             : 
     145             :         case PASSWORD_TYPE_PLAINTEXT:
     146           0 :             elog(ERROR, "cannot encrypt password with 'plaintext'");
     147             :     }
     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           0 :     elog(ERROR, "cannot encrypt password to requested type");
     154             :     return NULL;                /* keep compiler quiet */
     155             : }
     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, optionally store a palloc'd string at *logdetail
     166             :  * that will be sent to the postmaster log (but not the client).
     167             :  */
     168             : int
     169           0 : md5_crypt_verify(const char *role, const char *shadow_pass,
     170             :                  const char *client_pass,
     171             :                  const char *md5_salt, int md5_salt_len,
     172             :                  char **logdetail)
     173             : {
     174             :     int         retval;
     175             :     char        crypt_pwd[MD5_PASSWD_LEN + 1];
     176             : 
     177             :     Assert(md5_salt_len > 0);
     178             : 
     179           0 :     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             :      * We do not bother setting logdetail for any pg_md5_encrypt failure
     191             :      * below: the only possible error is out-of-memory, which is unlikely, and
     192             :      * if it did happen adding a psprintf call would only make things worse.
     193             :      */
     194             :     /* stored password already encrypted, only do salt */
     195           0 :     if (!pg_md5_encrypt(shadow_pass + strlen("md5"),
     196             :                         md5_salt, md5_salt_len,
     197             :                         crypt_pwd))
     198             :     {
     199           0 :         return STATUS_ERROR;
     200             :     }
     201             : 
     202           0 :     if (strcmp(client_pass, crypt_pwd) == 0)
     203           0 :         retval = STATUS_OK;
     204             :     else
     205             :     {
     206           0 :         *logdetail = psprintf(_("Password does not match for user \"%s\"."),
     207             :                               role);
     208           0 :         retval = STATUS_ERROR;
     209             :     }
     210             : 
     211           0 :     return retval;
     212             : }
     213             : 
     214             : /*
     215             :  * Check given password for given user, and return STATUS_OK or STATUS_ERROR.
     216             :  *
     217             :  * 'shadow_pass' is the user's correct password hash, as stored in
     218             :  * pg_authid.rolpassword.
     219             :  * 'client_pass' is the password given by the remote user.
     220             :  *
     221             :  * In the error case, optionally store a palloc'd string at *logdetail
     222             :  * that will be sent to the postmaster log (but not the client).
     223             :  */
     224             : int
     225         104 : plain_crypt_verify(const char *role, const char *shadow_pass,
     226             :                    const char *client_pass,
     227             :                    char **logdetail)
     228             : {
     229             :     char        crypt_client_pass[MD5_PASSWD_LEN + 1];
     230             : 
     231             :     /*
     232             :      * Client sent password in plaintext.  If we have an MD5 hash stored, hash
     233             :      * the password the client sent, and compare the hashes.  Otherwise
     234             :      * compare the plaintext passwords directly.
     235             :      */
     236         104 :     switch (get_password_type(shadow_pass))
     237             :     {
     238             :         case PASSWORD_TYPE_SCRAM_SHA_256:
     239          14 :             if (scram_verify_plain_password(role,
     240             :                                             client_pass,
     241             :                                             shadow_pass))
     242             :             {
     243           6 :                 return STATUS_OK;
     244             :             }
     245             :             else
     246             :             {
     247           8 :                 *logdetail = psprintf(_("Password does not match for user \"%s\"."),
     248             :                                       role);
     249           8 :                 return STATUS_ERROR;
     250             :             }
     251             :             break;
     252             : 
     253             :         case PASSWORD_TYPE_MD5:
     254          20 :             if (!pg_md5_encrypt(client_pass,
     255             :                                 role,
     256             :                                 strlen(role),
     257             :                                 crypt_client_pass))
     258             :             {
     259             :                 /*
     260             :                  * We do not bother setting logdetail for pg_md5_encrypt
     261             :                  * failure: the only possible error is out-of-memory, which is
     262             :                  * unlikely, and if it did happen adding a psprintf call would
     263             :                  * only make things worse.
     264             :                  */
     265           0 :                 return STATUS_ERROR;
     266             :             }
     267          20 :             if (strcmp(crypt_client_pass, shadow_pass) == 0)
     268           8 :                 return STATUS_OK;
     269             :             else
     270             :             {
     271          12 :                 *logdetail = psprintf(_("Password does not match for user \"%s\"."),
     272             :                                       role);
     273          12 :                 return STATUS_ERROR;
     274             :             }
     275             :             break;
     276             : 
     277             :         case PASSWORD_TYPE_PLAINTEXT:
     278             : 
     279             :             /*
     280             :              * We never store passwords in plaintext, so this shouldn't
     281             :              * happen.
     282             :              */
     283          70 :             break;
     284             :     }
     285             : 
     286             :     /*
     287             :      * This shouldn't happen.  Plain "password" authentication is possible
     288             :      * with any kind of stored password hash.
     289             :      */
     290          70 :     *logdetail = psprintf(_("Password of user \"%s\" is in unrecognized format."),
     291             :                           role);
     292          70 :     return STATUS_ERROR;
     293             : }

Generated by: LCOV version 1.13