LCOV - differential code coverage report
Current view: top level - src/interfaces/libpq - fe-secure-common.c (source / functions) Coverage Total Hit UBC CBC
Current: Differential Code Coverage 16@8cea358b128 vs 17@8cea358b128 Lines: 79.8 % 84 67 17 67
Current Date: 2024-04-14 14:21:10 Functions: 100.0 % 4 4 4
Baseline: 16@8cea358b128 Branches: 75.9 % 54 41 13 41
Baseline Date: 2024-04-14 14:21:09 Line coverage date bins:
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed (240..) days: 79.8 % 84 67 17 67
Function coverage date bins:
(240..) days: 100.0 % 4 4 4
Branch coverage date bins:
(240..) days: 75.9 % 54 41 13 41

 Age         Owner                    Branch data    TLA  Line data    Source code
                                  1                 :                : /*-------------------------------------------------------------------------
                                  2                 :                :  *
                                  3                 :                :  * fe-secure-common.c
                                  4                 :                :  *
                                  5                 :                :  * common implementation-independent SSL support code
                                  6                 :                :  *
                                  7                 :                :  * While fe-secure.c contains the interfaces that the rest of libpq call, this
                                  8                 :                :  * file contains support routines that are used by the library-specific
                                  9                 :                :  * implementations such as fe-secure-openssl.c.
                                 10                 :                :  *
                                 11                 :                :  * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
                                 12                 :                :  * Portions Copyright (c) 1994, Regents of the University of California
                                 13                 :                :  *
                                 14                 :                :  * IDENTIFICATION
                                 15                 :                :  *    src/interfaces/libpq/fe-secure-common.c
                                 16                 :                :  *
                                 17                 :                :  *-------------------------------------------------------------------------
                                 18                 :                :  */
                                 19                 :                : 
                                 20                 :                : #include "postgres_fe.h"
                                 21                 :                : 
                                 22                 :                : #include <arpa/inet.h>
                                 23                 :                : 
                                 24                 :                : #include "fe-secure-common.h"
                                 25                 :                : 
                                 26                 :                : #include "libpq-int.h"
                                 27                 :                : #include "pqexpbuffer.h"
                                 28                 :                : 
                                 29                 :                : /*
                                 30                 :                :  * Check if a wildcard certificate matches the server hostname.
                                 31                 :                :  *
                                 32                 :                :  * The rule for this is:
                                 33                 :                :  *  1. We only match the '*' character as wildcard
                                 34                 :                :  *  2. We match only wildcards at the start of the string
                                 35                 :                :  *  3. The '*' character does *not* match '.', meaning that we match only
                                 36                 :                :  *     a single pathname component.
                                 37                 :                :  *  4. We don't support more than one '*' in a single pattern.
                                 38                 :                :  *
                                 39                 :                :  * This is roughly in line with RFC2818, but contrary to what most browsers
                                 40                 :                :  * appear to be implementing (point 3 being the difference)
                                 41                 :                :  *
                                 42                 :                :  * Matching is always case-insensitive, since DNS is case insensitive.
                                 43                 :                :  */
                                 44                 :                : static bool
 2269 peter_e@gmx.net            45                 :CBC          21 : wildcard_certificate_match(const char *pattern, const char *string)
                                 46                 :                : {
                                 47                 :             21 :     int         lenpat = strlen(pattern);
                                 48                 :             21 :     int         lenstr = strlen(string);
                                 49                 :                : 
                                 50                 :                :     /* If we don't start with a wildcard, it's not a match (rule 1 & 2) */
                                 51         [ +  - ]:             21 :     if (lenpat < 3 ||
                                 52         [ +  + ]:             21 :         pattern[0] != '*' ||
                                 53         [ -  + ]:              3 :         pattern[1] != '.')
                                 54                 :             18 :         return false;
                                 55                 :                : 
                                 56                 :                :     /* If pattern is longer than the string, we can never match */
                                 57         [ -  + ]:              3 :     if (lenpat > lenstr)
 2269 peter_e@gmx.net            58                 :UBC           0 :         return false;
                                 59                 :                : 
                                 60                 :                :     /*
                                 61                 :                :      * If string does not end in pattern (minus the wildcard), we don't match
                                 62                 :                :      */
 2269 peter_e@gmx.net            63         [ +  + ]:CBC           3 :     if (pg_strcasecmp(pattern + 1, string + lenstr - lenpat + 1) != 0)
                                 64                 :              1 :         return false;
                                 65                 :                : 
                                 66                 :                :     /*
                                 67                 :                :      * If there is a dot left of where the pattern started to match, we don't
                                 68                 :                :      * match (rule 3)
                                 69                 :                :      */
                                 70         [ +  + ]:              2 :     if (strchr(string, '.') < string + lenstr - lenpat)
                                 71                 :              1 :         return false;
                                 72                 :                : 
                                 73                 :                :     /* String ended with pattern, and didn't have a dot before, so we match */
                                 74                 :              1 :     return true;
                                 75                 :                : }
                                 76                 :                : 
                                 77                 :                : /*
                                 78                 :                :  * Check if a name from a server's certificate matches the peer's hostname.
                                 79                 :                :  *
                                 80                 :                :  * Returns 1 if the name matches, and 0 if it does not. On error, returns
                                 81                 :                :  * -1, and sets the libpq error message.
                                 82                 :                :  *
                                 83                 :                :  * The name extracted from the certificate is returned in *store_name. The
                                 84                 :                :  * caller is responsible for freeing it.
                                 85                 :                :  */
                                 86                 :                : int
                                 87                 :             34 : pq_verify_peer_name_matches_certificate_name(PGconn *conn,
                                 88                 :                :                                              const char *namedata, size_t namelen,
                                 89                 :                :                                              char **store_name)
                                 90                 :                : {
                                 91                 :                :     char       *name;
                                 92                 :                :     int         result;
 2081 tgl@sss.pgh.pa.us          93                 :             34 :     char       *host = conn->connhost[conn->whichhost].host;
                                 94                 :                : 
 2269 peter_e@gmx.net            95                 :             34 :     *store_name = NULL;
                                 96                 :                : 
 2081 tgl@sss.pgh.pa.us          97   [ +  -  -  + ]:             34 :     if (!(host && host[0] != '\0'))
                                 98                 :                :     {
  516 peter@eisentraut.org       99                 :UBC           0 :         libpq_append_conn_error(conn, "host name must be specified");
 2081 tgl@sss.pgh.pa.us         100                 :              0 :         return -1;
                                101                 :                :     }
                                102                 :                : 
                                103                 :                :     /*
                                104                 :                :      * There is no guarantee the string returned from the certificate is
                                105                 :                :      * NULL-terminated, so make a copy that is.
                                106                 :                :      */
 2269 peter_e@gmx.net           107                 :CBC          34 :     name = malloc(namelen + 1);
                                108         [ -  + ]:             34 :     if (name == NULL)
                                109                 :                :     {
  516 peter@eisentraut.org      110                 :UBC           0 :         libpq_append_conn_error(conn, "out of memory");
 2269 peter_e@gmx.net           111                 :              0 :         return -1;
                                112                 :                :     }
 2269 peter_e@gmx.net           113                 :CBC          34 :     memcpy(name, namedata, namelen);
                                114                 :             34 :     name[namelen] = '\0';
                                115                 :                : 
                                116                 :                :     /*
                                117                 :                :      * Reject embedded NULLs in certificate common or alternative name to
                                118                 :                :      * prevent attacks like CVE-2009-4034.
                                119                 :                :      */
                                120         [ -  + ]:             34 :     if (namelen != strlen(name))
                                121                 :                :     {
 2269 peter_e@gmx.net           122                 :UBC           0 :         free(name);
  516 peter@eisentraut.org      123                 :              0 :         libpq_append_conn_error(conn, "SSL certificate's name contains embedded null");
 2269 peter_e@gmx.net           124                 :              0 :         return -1;
                                125                 :                :     }
                                126                 :                : 
 2269 peter_e@gmx.net           127         [ +  + ]:CBC          34 :     if (pg_strcasecmp(name, host) == 0)
                                128                 :                :     {
                                129                 :                :         /* Exact name match */
                                130                 :             13 :         result = 1;
                                131                 :                :     }
                                132         [ +  + ]:             21 :     else if (wildcard_certificate_match(name, host))
                                133                 :                :     {
                                134                 :                :         /* Matched wildcard name */
                                135                 :              1 :         result = 1;
                                136                 :                :     }
                                137                 :                :     else
                                138                 :                :     {
                                139                 :             20 :         result = 0;
                                140                 :                :     }
                                141                 :                : 
                                142                 :             34 :     *store_name = name;
                                143                 :             34 :     return result;
                                144                 :                : }
                                145                 :                : 
                                146                 :                : /*
                                147                 :                :  * Check if an IP address from a server's certificate matches the peer's
                                148                 :                :  * hostname (which must itself be an IPv4/6 address).
                                149                 :                :  *
                                150                 :                :  * Returns 1 if the address matches, and 0 if it does not. On error, returns
                                151                 :                :  * -1, and sets the libpq error message.
                                152                 :                :  *
                                153                 :                :  * A string representation of the certificate's IP address is returned in
                                154                 :                :  * *store_name. The caller is responsible for freeing it.
                                155                 :                :  */
                                156                 :                : int
  744 peter@eisentraut.org      157                 :             24 : pq_verify_peer_name_matches_certificate_ip(PGconn *conn,
                                158                 :                :                                            const unsigned char *ipdata,
                                159                 :                :                                            size_t iplen,
                                160                 :                :                                            char **store_name)
                                161                 :                : {
                                162                 :                :     char       *addrstr;
                                163                 :             24 :     int         match = 0;
                                164                 :             24 :     char       *host = conn->connhost[conn->whichhost].host;
                                165                 :                :     int         family;
                                166                 :                :     char        tmp[sizeof "ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255"];
                                167                 :                :     char        sebuf[PG_STRERROR_R_BUFLEN];
                                168                 :                : 
                                169                 :             24 :     *store_name = NULL;
                                170                 :                : 
                                171   [ +  -  -  + ]:             24 :     if (!(host && host[0] != '\0'))
                                172                 :                :     {
  516 peter@eisentraut.org      173                 :UBC           0 :         libpq_append_conn_error(conn, "host name must be specified");
  744                           174                 :              0 :         return -1;
                                175                 :                :     }
                                176                 :                : 
                                177                 :                :     /*
                                178                 :                :      * The data from the certificate is in network byte order. Convert our
                                179                 :                :      * host string to network-ordered bytes as well, for comparison. (The host
                                180                 :                :      * string isn't guaranteed to actually be an IP address, so if this
                                181                 :                :      * conversion fails we need to consider it a mismatch rather than an
                                182                 :                :      * error.)
                                183                 :                :      */
  744 peter@eisentraut.org      184         [ +  + ]:CBC          24 :     if (iplen == 4)
                                185                 :                :     {
                                186                 :                :         /* IPv4 */
                                187                 :                :         struct in_addr addr;
                                188                 :                : 
                                189                 :             14 :         family = AF_INET;
                                190                 :                : 
                                191                 :                :         /*
                                192                 :                :          * The use of inet_aton() is deliberate; we accept alternative IPv4
                                193                 :                :          * address notations that are accepted by inet_aton() but not
                                194                 :                :          * inet_pton() as server addresses.
                                195                 :                :          */
                                196         [ +  + ]:             14 :         if (inet_aton(host, &addr))
                                197                 :                :         {
                                198         [ +  + ]:              6 :             if (memcmp(ipdata, &addr.s_addr, iplen) == 0)
                                199                 :              4 :                 match = 1;
                                200                 :                :         }
                                201                 :                :     }
                                202                 :                : 
                                203                 :                :     /*
                                204                 :                :      * If they don't have inet_pton(), skip this.  Then, an IPv6 address in a
                                205                 :                :      * certificate will cause an error.
                                206                 :                :      */
                                207                 :                : #ifdef HAVE_INET_PTON
                                208         [ +  - ]:             10 :     else if (iplen == 16)
                                209                 :                :     {
                                210                 :                :         /* IPv6 */
                                211                 :                :         struct in6_addr addr;
                                212                 :                : 
                                213                 :             10 :         family = AF_INET6;
                                214                 :                : 
                                215         [ +  + ]:             10 :         if (inet_pton(AF_INET6, host, &addr) == 1)
                                216                 :                :         {
                                217         [ +  + ]:              6 :             if (memcmp(ipdata, &addr.s6_addr, iplen) == 0)
                                218                 :              5 :                 match = 1;
                                219                 :                :         }
                                220                 :                :     }
                                221                 :                : #endif
                                222                 :                :     else
                                223                 :                :     {
                                224                 :                :         /*
                                225                 :                :          * Not IPv4 or IPv6. We could ignore the field, but leniency seems
                                226                 :                :          * wrong given the subject matter.
                                227                 :                :          */
  516 peter@eisentraut.org      228                 :UBC           0 :         libpq_append_conn_error(conn, "certificate contains IP address with invalid length %zu",
                                229                 :                :                                 iplen);
  744                           230                 :              0 :         return -1;
                                231                 :                :     }
                                232                 :                : 
                                233                 :                :     /* Generate a human-readable representation of the certificate's IP. */
  744 peter@eisentraut.org      234                 :CBC          24 :     addrstr = pg_inet_net_ntop(family, ipdata, 8 * iplen, tmp, sizeof(tmp));
                                235         [ -  + ]:             24 :     if (!addrstr)
                                236                 :                :     {
  516 peter@eisentraut.org      237                 :UBC           0 :         libpq_append_conn_error(conn, "could not convert certificate's IP address to string: %s",
  331 tgl@sss.pgh.pa.us         238                 :              0 :                                 strerror_r(errno, sebuf, sizeof(sebuf)));
  744 peter@eisentraut.org      239                 :              0 :         return -1;
                                240                 :                :     }
                                241                 :                : 
  744 peter@eisentraut.org      242                 :CBC          24 :     *store_name = strdup(addrstr);
                                243                 :             24 :     return match;
                                244                 :                : }
                                245                 :                : 
                                246                 :                : /*
                                247                 :                :  * Verify that the server certificate matches the hostname we connected to.
                                248                 :                :  *
                                249                 :                :  * The certificate's Common Name and Subject Alternative Names are considered.
                                250                 :                :  */
                                251                 :                : bool
 2269 peter_e@gmx.net           252                 :            175 : pq_verify_peer_name_matches_certificate(PGconn *conn)
                                253                 :                : {
 2081 tgl@sss.pgh.pa.us         254                 :            175 :     char       *host = conn->connhost[conn->whichhost].host;
                                255                 :                :     int         rc;
 2269 peter_e@gmx.net           256                 :            175 :     int         names_examined = 0;
                                257                 :            175 :     char       *first_name = NULL;
                                258                 :                : 
                                259                 :                :     /*
                                260                 :                :      * If told not to verify the peer name, don't do it. Return true
                                261                 :                :      * indicating that the verification was successful.
                                262                 :                :      */
                                263         [ +  + ]:            175 :     if (strcmp(conn->sslmode, "verify-full") != 0)
                                264                 :            139 :         return true;
                                265                 :                : 
                                266                 :                :     /* Check that we have a hostname to compare with. */
                                267   [ +  -  -  + ]:             36 :     if (!(host && host[0] != '\0'))
                                268                 :                :     {
  516 peter@eisentraut.org      269                 :UBC           0 :         libpq_append_conn_error(conn, "host name must be specified for a verified SSL connection");
 2269 peter_e@gmx.net           270                 :              0 :         return false;
                                271                 :                :     }
                                272                 :                : 
 2269 peter_e@gmx.net           273                 :CBC          36 :     rc = pgtls_verify_peer_name_matches_certificate_guts(conn, &names_examined, &first_name);
                                274                 :                : 
                                275         [ +  + ]:             36 :     if (rc == 0)
                                276                 :                :     {
                                277                 :                :         /*
                                278                 :                :          * No match. Include the name from the server certificate in the error
                                279                 :                :          * message, to aid debugging broken configurations. If there are
                                280                 :                :          * multiple names, only print the first one to avoid an overly long
                                281                 :                :          * error message.
                                282                 :                :          */
                                283         [ +  + ]:             13 :         if (names_examined > 1)
                                284                 :                :         {
 1189 tgl@sss.pgh.pa.us         285                 :              7 :             appendPQExpBuffer(&conn->errorMessage,
  516 peter@eisentraut.org      286                 :              7 :                               libpq_ngettext("server certificate for \"%s\" (and %d other name) does not match host name \"%s\"",
                                287                 :                :                                              "server certificate for \"%s\" (and %d other names) does not match host name \"%s\"",
 2269 peter_e@gmx.net           288                 :              7 :                                              names_examined - 1),
                                289                 :                :                               first_name, names_examined - 1, host);
  516 peter@eisentraut.org      290                 :              7 :             appendPQExpBufferChar(&conn->errorMessage, '\n');
                                291                 :                :         }
 2269 peter_e@gmx.net           292         [ +  + ]:              6 :         else if (names_examined == 1)
                                293                 :                :         {
  516 peter@eisentraut.org      294                 :              5 :             libpq_append_conn_error(conn, "server certificate for \"%s\" does not match host name \"%s\"",
                                295                 :                :                                     first_name, host);
                                296                 :                :         }
                                297                 :                :         else
                                298                 :                :         {
                                299                 :              1 :             libpq_append_conn_error(conn, "could not get server's host name from server certificate");
                                300                 :                :         }
                                301                 :                :     }
                                302                 :                : 
                                303                 :                :     /* clean up */
  668                           304                 :             36 :     free(first_name);
                                305                 :                : 
 2269 peter_e@gmx.net           306                 :             36 :     return (rc == 1);
                                307                 :                : }
        

Generated by: LCOV version 2.1-beta2-3-g6141622