PostgreSQL Source Code git master
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
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-2025, 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 */
44static bool
45wildcard_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 */
86int
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 */
156int
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 */
251bool
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:1381
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:939
#define PG_STRERROR_R_BUFLEN
Definition: port.h:257
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:256
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:52
char * host
Definition: libpq-int.h:358
char * sslmode
Definition: libpq-int.h:403
PQExpBufferData errorMessage
Definition: libpq-int.h:671
int whichhost
Definition: libpq-int.h:478
pg_conn_host * connhost
Definition: libpq-int.h:479
const char * name