PostgreSQL Source Code  git master
fe-secure-common.c
Go to the documentation of this file.
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 wildcard_certificate_match(const char *pattern, const char *string)
46 {
47  int lenpat = strlen(pattern);
48  int lenstr = strlen(string);
49 
50  /* If we don't start with a wildcard, it's not a match (rule 1 & 2) */
51  if (lenpat < 3 ||
52  pattern[0] != '*' ||
53  pattern[1] != '.')
54  return false;
55 
56  /* If pattern is longer than the string, we can never match */
57  if (lenpat > lenstr)
58  return false;
59 
60  /*
61  * If string does not end in pattern (minus the wildcard), we don't match
62  */
63  if (pg_strcasecmp(pattern + 1, string + lenstr - lenpat + 1) != 0)
64  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  if (strchr(string, '.') < string + lenstr - lenpat)
71  return false;
72 
73  /* String ended with pattern, and didn't have a dot before, so we match */
74  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
88  const char *namedata, size_t namelen,
89  char **store_name)
90 {
91  char *name;
92  int result;
93  char *host = conn->connhost[conn->whichhost].host;
94 
95  *store_name = NULL;
96 
97  if (!(host && host[0] != '\0'))
98  {
99  libpq_append_conn_error(conn, "host name must be specified");
100  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  name = malloc(namelen + 1);
108  if (name == NULL)
109  {
110  libpq_append_conn_error(conn, "out of memory");
111  return -1;
112  }
113  memcpy(name, namedata, namelen);
114  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  if (namelen != strlen(name))
121  {
122  free(name);
123  libpq_append_conn_error(conn, "SSL certificate's name contains embedded null");
124  return -1;
125  }
126 
127  if (pg_strcasecmp(name, host) == 0)
128  {
129  /* Exact name match */
130  result = 1;
131  }
132  else if (wildcard_certificate_match(name, host))
133  {
134  /* Matched wildcard name */
135  result = 1;
136  }
137  else
138  {
139  result = 0;
140  }
141 
142  *store_name = name;
143  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
158  const unsigned char *ipdata,
159  size_t iplen,
160  char **store_name)
161 {
162  char *addrstr;
163  int match = 0;
164  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  *store_name = NULL;
170 
171  if (!(host && host[0] != '\0'))
172  {
173  libpq_append_conn_error(conn, "host name must be specified");
174  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  if (iplen == 4)
185  {
186  /* IPv4 */
187  struct in_addr addr;
188 
189  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  if (inet_aton(host, &addr))
197  {
198  if (memcmp(ipdata, &addr.s_addr, iplen) == 0)
199  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  else if (iplen == 16)
209  {
210  /* IPv6 */
211  struct in6_addr addr;
212 
213  family = AF_INET6;
214 
215  if (inet_pton(AF_INET6, host, &addr) == 1)
216  {
217  if (memcmp(ipdata, &addr.s6_addr, iplen) == 0)
218  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  libpq_append_conn_error(conn, "certificate contains IP address with invalid length %zu",
229  iplen);
230  return -1;
231  }
232 
233  /* Generate a human-readable representation of the certificate's IP. */
234  addrstr = pg_inet_net_ntop(family, ipdata, 8 * iplen, tmp, sizeof(tmp));
235  if (!addrstr)
236  {
237  libpq_append_conn_error(conn, "could not convert certificate's IP address to string: %s",
238  strerror_r(errno, sebuf, sizeof(sebuf)));
239  return -1;
240  }
241 
242  *store_name = strdup(addrstr);
243  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
253 {
254  char *host = conn->connhost[conn->whichhost].host;
255  int rc;
256  int names_examined = 0;
257  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  if (strcmp(conn->sslmode, "verify-full") != 0)
264  return true;
265 
266  /* Check that we have a hostname to compare with. */
267  if (!(host && host[0] != '\0'))
268  {
269  libpq_append_conn_error(conn, "host name must be specified for a verified SSL connection");
270  return false;
271  }
272 
273  rc = pgtls_verify_peer_name_matches_certificate_guts(conn, &names_examined, &first_name);
274 
275  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  if (names_examined > 1)
284  {
286  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  names_examined - 1),
289  first_name, names_examined - 1, host);
291  }
292  else if (names_examined == 1)
293  {
294  libpq_append_conn_error(conn, "server certificate for \"%s\" does not match host name \"%s\"",
295  first_name, host);
296  }
297  else
298  {
299  libpq_append_conn_error(conn, "could not get server's host name from server certificate");
300  }
301  }
302 
303  /* clean up */
304  free(first_name);
305 
306  return (rc == 1);
307 }
void libpq_append_conn_error(PGconn *conn, const char *fmt,...)
Definition: fe-misc.c:1372
int pq_verify_peer_name_matches_certificate_name(PGconn *conn, const char *namedata, size_t namelen, char **store_name)
static bool wildcard_certificate_match(const char *pattern, const char *string)
int pq_verify_peer_name_matches_certificate_ip(PGconn *conn, const unsigned char *ipdata, size_t iplen, char **store_name)
bool pq_verify_peer_name_matches_certificate(PGconn *conn)
int pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn, int *names_examined, char **first_name)
#define free(a)
Definition: header.h:65
#define malloc(a)
Definition: header.h:50
#define libpq_ngettext(s, p, n)
Definition: libpq-int.h:907
#define PG_STRERROR_R_BUFLEN
Definition: port.h:256
int pg_strcasecmp(const char *s1, const char *s2)
Definition: pgstrcasecmp.c:36
char * pg_inet_net_ntop(int af, const void *src, int bits, char *dst, size_t size)
Definition: inet_net_ntop.c:77
int inet_aton(const char *cp, struct in_addr *addr)
Definition: inet_aton.c:56
#define strerror_r
Definition: port.h:255
void appendPQExpBuffer(PQExpBuffer str, const char *fmt,...)
Definition: pqexpbuffer.c:265
void appendPQExpBufferChar(PQExpBuffer str, char ch)
Definition: pqexpbuffer.c:378
PGconn * conn
Definition: streamutil.c:53
char * host
Definition: libpq-int.h:364
char * sslmode
Definition: libpq-int.h:408
PQExpBufferData errorMessage
Definition: libpq-int.h:643
int whichhost
Definition: libpq-int.h:467
pg_conn_host * connhost
Definition: libpq-int.h:468
const char * name