Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit f75a959

Browse files
committed
Refactor client-side SSL certificate checking code
Separate the parts specific to the SSL library from the general logic. The previous code structure was open_client_SSL() calls verify_peer_name_matches_certificate() calls verify_peer_name_matches_certificate_name() calls wildcard_certificate_match() and was completely in fe-secure-openssl.c. The new structure is open_client_SSL() [openssl] calls pq_verify_peer_name_matches_certificate() [generic] calls pgtls_verify_peer_name_matches_certificate_guts() [openssl] calls openssl_verify_peer_name_matches_certificate_name() [openssl] calls pq_verify_peer_name_matches_certificate_name() [generic] calls wildcard_certificate_match() [generic] Move the generic functions into a new file fe-secure-common.c, so the calls generally go fe-connect.c -> fe-secure.c -> fe-secure-${impl}.c -> fe-secure-common.c, although there is a bit of back-and-forth between the last two. Reviewed-by: Michael Paquier <michael.paquier@gmail.com>
1 parent 38d485f commit f75a959

File tree

7 files changed

+271
-187
lines changed

7 files changed

+271
-187
lines changed

src/interfaces/libpq/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ OBJS += encnames.o wchar.o
5252
OBJS += base64.o ip.o md5.o scram-common.o saslprep.o unicode_norm.o
5353

5454
ifeq ($(with_openssl),yes)
55-
OBJS += fe-secure-openssl.o sha2_openssl.o
55+
OBJS += fe-secure-openssl.o fe-secure-common.o sha2_openssl.o
5656
else
5757
OBJS += sha2.o
5858
endif
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
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-2018, 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 "fe-secure-common.h"
23+
24+
#include "libpq-int.h"
25+
#include "pqexpbuffer.h"
26+
27+
/*
28+
* Check if a wildcard certificate matches the server hostname.
29+
*
30+
* The rule for this is:
31+
* 1. We only match the '*' character as wildcard
32+
* 2. We match only wildcards at the start of the string
33+
* 3. The '*' character does *not* match '.', meaning that we match only
34+
* a single pathname component.
35+
* 4. We don't support more than one '*' in a single pattern.
36+
*
37+
* This is roughly in line with RFC2818, but contrary to what most browsers
38+
* appear to be implementing (point 3 being the difference)
39+
*
40+
* Matching is always case-insensitive, since DNS is case insensitive.
41+
*/
42+
static bool
43+
wildcard_certificate_match(const char *pattern, const char *string)
44+
{
45+
int lenpat = strlen(pattern);
46+
int lenstr = strlen(string);
47+
48+
/* If we don't start with a wildcard, it's not a match (rule 1 & 2) */
49+
if (lenpat < 3 ||
50+
pattern[0] != '*' ||
51+
pattern[1] != '.')
52+
return false;
53+
54+
/* If pattern is longer than the string, we can never match */
55+
if (lenpat > lenstr)
56+
return false;
57+
58+
/*
59+
* If string does not end in pattern (minus the wildcard), we don't match
60+
*/
61+
if (pg_strcasecmp(pattern + 1, string + lenstr - lenpat + 1) != 0)
62+
return false;
63+
64+
/*
65+
* If there is a dot left of where the pattern started to match, we don't
66+
* match (rule 3)
67+
*/
68+
if (strchr(string, '.') < string + lenstr - lenpat)
69+
return false;
70+
71+
/* String ended with pattern, and didn't have a dot before, so we match */
72+
return true;
73+
}
74+
75+
/*
76+
* Check if a name from a server's certificate matches the peer's hostname.
77+
*
78+
* Returns 1 if the name matches, and 0 if it does not. On error, returns
79+
* -1, and sets the libpq error message.
80+
*
81+
* The name extracted from the certificate is returned in *store_name. The
82+
* caller is responsible for freeing it.
83+
*/
84+
int
85+
pq_verify_peer_name_matches_certificate_name(PGconn *conn,
86+
const char *namedata, size_t namelen,
87+
char **store_name)
88+
{
89+
char *name;
90+
int result;
91+
char *host = PQhost(conn);
92+
93+
*store_name = NULL;
94+
95+
/*
96+
* There is no guarantee the string returned from the certificate is
97+
* NULL-terminated, so make a copy that is.
98+
*/
99+
name = malloc(namelen + 1);
100+
if (name == NULL)
101+
{
102+
printfPQExpBuffer(&conn->errorMessage,
103+
libpq_gettext("out of memory\n"));
104+
return -1;
105+
}
106+
memcpy(name, namedata, namelen);
107+
name[namelen] = '\0';
108+
109+
/*
110+
* Reject embedded NULLs in certificate common or alternative name to
111+
* prevent attacks like CVE-2009-4034.
112+
*/
113+
if (namelen != strlen(name))
114+
{
115+
free(name);
116+
printfPQExpBuffer(&conn->errorMessage,
117+
libpq_gettext("SSL certificate's name contains embedded null\n"));
118+
return -1;
119+
}
120+
121+
if (pg_strcasecmp(name, host) == 0)
122+
{
123+
/* Exact name match */
124+
result = 1;
125+
}
126+
else if (wildcard_certificate_match(name, host))
127+
{
128+
/* Matched wildcard name */
129+
result = 1;
130+
}
131+
else
132+
{
133+
result = 0;
134+
}
135+
136+
*store_name = name;
137+
return result;
138+
}
139+
140+
/*
141+
* Verify that the server certificate matches the hostname we connected to.
142+
*
143+
* The certificate's Common Name and Subject Alternative Names are considered.
144+
*/
145+
bool
146+
pq_verify_peer_name_matches_certificate(PGconn *conn)
147+
{
148+
char *host = PQhost(conn);
149+
int rc;
150+
int names_examined = 0;
151+
char *first_name = NULL;
152+
153+
/*
154+
* If told not to verify the peer name, don't do it. Return true
155+
* indicating that the verification was successful.
156+
*/
157+
if (strcmp(conn->sslmode, "verify-full") != 0)
158+
return true;
159+
160+
/* Check that we have a hostname to compare with. */
161+
if (!(host && host[0] != '\0'))
162+
{
163+
printfPQExpBuffer(&conn->errorMessage,
164+
libpq_gettext("host name must be specified for a verified SSL connection\n"));
165+
return false;
166+
}
167+
168+
rc = pgtls_verify_peer_name_matches_certificate_guts(conn, &names_examined, &first_name);
169+
170+
if (rc == 0)
171+
{
172+
/*
173+
* No match. Include the name from the server certificate in the error
174+
* message, to aid debugging broken configurations. If there are
175+
* multiple names, only print the first one to avoid an overly long
176+
* error message.
177+
*/
178+
if (names_examined > 1)
179+
{
180+
printfPQExpBuffer(&conn->errorMessage,
181+
libpq_ngettext("server certificate for \"%s\" (and %d other name) does not match host name \"%s\"\n",
182+
"server certificate for \"%s\" (and %d other names) does not match host name \"%s\"\n",
183+
names_examined - 1),
184+
first_name, names_examined - 1, host);
185+
}
186+
else if (names_examined == 1)
187+
{
188+
printfPQExpBuffer(&conn->errorMessage,
189+
libpq_gettext("server certificate for \"%s\" does not match host name \"%s\"\n"),
190+
first_name, host);
191+
}
192+
else
193+
{
194+
printfPQExpBuffer(&conn->errorMessage,
195+
libpq_gettext("could not get server's host name from server certificate\n"));
196+
}
197+
}
198+
199+
/* clean up */
200+
if (first_name)
201+
free(first_name);
202+
203+
return (rc == 1);
204+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*-------------------------------------------------------------------------
2+
*
3+
* fe-secure-common.h
4+
*
5+
* common implementation-independent SSL support code
6+
*
7+
* Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
8+
* Portions Copyright (c) 1994, Regents of the University of California
9+
*
10+
* IDENTIFICATION
11+
* src/interfaces/libpq/fe-secure-common.h
12+
*
13+
*-------------------------------------------------------------------------
14+
*/
15+
16+
#ifndef FE_SECURE_COMMON_H
17+
#define FE_SECURE_COMMON_H
18+
19+
#include "libpq-fe.h"
20+
21+
extern int pq_verify_peer_name_matches_certificate_name(PGconn *conn,
22+
const char *namedata, size_t namelen,
23+
char **store_name);
24+
extern bool pq_verify_peer_name_matches_certificate(PGconn *conn);
25+
26+
#endif /* FE_SECURE_COMMON_H */

0 commit comments

Comments
 (0)