LCOV - code coverage report
Current view: top level - src/interfaces/libpq - fe-secure-common.c (source / functions) Hit Total Coverage
Test: PostgreSQL 17devel Lines: 67 84 79.8 %
Date: 2024-04-18 16:11:09 Functions: 4 4 100.0 %
Legend: Lines: hit not hit

          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
      45          42 : wildcard_certificate_match(const char *pattern, const char *string)
      46             : {
      47          42 :     int         lenpat = strlen(pattern);
      48          42 :     int         lenstr = strlen(string);
      49             : 
      50             :     /* If we don't start with a wildcard, it's not a match (rule 1 & 2) */
      51          42 :     if (lenpat < 3 ||
      52          42 :         pattern[0] != '*' ||
      53           6 :         pattern[1] != '.')
      54          36 :         return false;
      55             : 
      56             :     /* If pattern is longer than the string, we can never match */
      57           6 :     if (lenpat > lenstr)
      58           0 :         return false;
      59             : 
      60             :     /*
      61             :      * If string does not end in pattern (minus the wildcard), we don't match
      62             :      */
      63           6 :     if (pg_strcasecmp(pattern + 1, string + lenstr - lenpat + 1) != 0)
      64           2 :         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           4 :     if (strchr(string, '.') < string + lenstr - lenpat)
      71           2 :         return false;
      72             : 
      73             :     /* String ended with pattern, and didn't have a dot before, so we match */
      74           2 :     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          68 : 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;
      93          68 :     char       *host = conn->connhost[conn->whichhost].host;
      94             : 
      95          68 :     *store_name = NULL;
      96             : 
      97          68 :     if (!(host && host[0] != '\0'))
      98             :     {
      99           0 :         libpq_append_conn_error(conn, "host name must be specified");
     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             :      */
     107          68 :     name = malloc(namelen + 1);
     108          68 :     if (name == NULL)
     109             :     {
     110           0 :         libpq_append_conn_error(conn, "out of memory");
     111           0 :         return -1;
     112             :     }
     113          68 :     memcpy(name, namedata, namelen);
     114          68 :     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          68 :     if (namelen != strlen(name))
     121             :     {
     122           0 :         free(name);
     123           0 :         libpq_append_conn_error(conn, "SSL certificate's name contains embedded null");
     124           0 :         return -1;
     125             :     }
     126             : 
     127          68 :     if (pg_strcasecmp(name, host) == 0)
     128             :     {
     129             :         /* Exact name match */
     130          26 :         result = 1;
     131             :     }
     132          42 :     else if (wildcard_certificate_match(name, host))
     133             :     {
     134             :         /* Matched wildcard name */
     135           2 :         result = 1;
     136             :     }
     137             :     else
     138             :     {
     139          40 :         result = 0;
     140             :     }
     141             : 
     142          68 :     *store_name = name;
     143          68 :     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
     157          48 : 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          48 :     int         match = 0;
     164          48 :     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          48 :     *store_name = NULL;
     170             : 
     171          48 :     if (!(host && host[0] != '\0'))
     172             :     {
     173           0 :         libpq_append_conn_error(conn, "host name must be specified");
     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             :      */
     184          48 :     if (iplen == 4)
     185             :     {
     186             :         /* IPv4 */
     187             :         struct in_addr addr;
     188             : 
     189          28 :         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          28 :         if (inet_aton(host, &addr))
     197             :         {
     198          12 :             if (memcmp(ipdata, &addr.s_addr, iplen) == 0)
     199           8 :                 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          20 :     else if (iplen == 16)
     209             :     {
     210             :         /* IPv6 */
     211             :         struct in6_addr addr;
     212             : 
     213          20 :         family = AF_INET6;
     214             : 
     215          20 :         if (inet_pton(AF_INET6, host, &addr) == 1)
     216             :         {
     217          12 :             if (memcmp(ipdata, &addr.s6_addr, iplen) == 0)
     218          10 :                 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             :          */
     228           0 :         libpq_append_conn_error(conn, "certificate contains IP address with invalid length %zu",
     229             :                                 iplen);
     230           0 :         return -1;
     231             :     }
     232             : 
     233             :     /* Generate a human-readable representation of the certificate's IP. */
     234          48 :     addrstr = pg_inet_net_ntop(family, ipdata, 8 * iplen, tmp, sizeof(tmp));
     235          48 :     if (!addrstr)
     236             :     {
     237           0 :         libpq_append_conn_error(conn, "could not convert certificate's IP address to string: %s",
     238           0 :                                 strerror_r(errno, sebuf, sizeof(sebuf)));
     239           0 :         return -1;
     240             :     }
     241             : 
     242          48 :     *store_name = strdup(addrstr);
     243          48 :     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
     252         206 : pq_verify_peer_name_matches_certificate(PGconn *conn)
     253             : {
     254         206 :     char       *host = conn->connhost[conn->whichhost].host;
     255             :     int         rc;
     256         206 :     int         names_examined = 0;
     257         206 :     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         206 :     if (strcmp(conn->sslmode, "verify-full") != 0)
     264         134 :         return true;
     265             : 
     266             :     /* Check that we have a hostname to compare with. */
     267          72 :     if (!(host && host[0] != '\0'))
     268             :     {
     269           0 :         libpq_append_conn_error(conn, "host name must be specified for a verified SSL connection");
     270           0 :         return false;
     271             :     }
     272             : 
     273          72 :     rc = pgtls_verify_peer_name_matches_certificate_guts(conn, &names_examined, &first_name);
     274             : 
     275          72 :     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          26 :         if (names_examined > 1)
     284             :         {
     285          14 :             appendPQExpBuffer(&conn->errorMessage,
     286          14 :                               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\"",
     288          14 :                                              names_examined - 1),
     289             :                               first_name, names_examined - 1, host);
     290          14 :             appendPQExpBufferChar(&conn->errorMessage, '\n');
     291             :         }
     292          12 :         else if (names_examined == 1)
     293             :         {
     294          10 :             libpq_append_conn_error(conn, "server certificate for \"%s\" does not match host name \"%s\"",
     295             :                                     first_name, host);
     296             :         }
     297             :         else
     298             :         {
     299           2 :             libpq_append_conn_error(conn, "could not get server's host name from server certificate");
     300             :         }
     301             :     }
     302             : 
     303             :     /* clean up */
     304          72 :     free(first_name);
     305             : 
     306          72 :     return (rc == 1);
     307             : }

Generated by: LCOV version 1.14