2018-01-27 19:47:52 +01:00
|
|
|
/*-------------------------------------------------------------------------
|
|
|
|
*
|
|
|
|
* fe-secure-common.c
|
|
|
|
*
|
|
|
|
* common implementation-independent SSL support code
|
|
|
|
*
|
|
|
|
* While fe-secure.c contains the interfaces that the rest of libpq call, this
|
|
|
|
* file contains support routines that are used by the library-specific
|
|
|
|
* implementations such as fe-secure-openssl.c.
|
|
|
|
*
|
2022-01-08 01:04:57 +01:00
|
|
|
* Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
|
2018-01-27 19:47:52 +01:00
|
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
|
|
*
|
|
|
|
* IDENTIFICATION
|
|
|
|
* src/interfaces/libpq/fe-secure-common.c
|
|
|
|
*
|
|
|
|
*-------------------------------------------------------------------------
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "postgres_fe.h"
|
|
|
|
|
2022-04-01 15:41:44 +02:00
|
|
|
#include <arpa/inet.h>
|
|
|
|
|
2018-01-27 19:47:52 +01:00
|
|
|
#include "fe-secure-common.h"
|
|
|
|
|
|
|
|
#include "libpq-int.h"
|
|
|
|
#include "pqexpbuffer.h"
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Check if a wildcard certificate matches the server hostname.
|
|
|
|
*
|
|
|
|
* The rule for this is:
|
|
|
|
* 1. We only match the '*' character as wildcard
|
|
|
|
* 2. We match only wildcards at the start of the string
|
|
|
|
* 3. The '*' character does *not* match '.', meaning that we match only
|
|
|
|
* a single pathname component.
|
|
|
|
* 4. We don't support more than one '*' in a single pattern.
|
|
|
|
*
|
|
|
|
* This is roughly in line with RFC2818, but contrary to what most browsers
|
|
|
|
* appear to be implementing (point 3 being the difference)
|
|
|
|
*
|
|
|
|
* Matching is always case-insensitive, since DNS is case insensitive.
|
|
|
|
*/
|
|
|
|
static bool
|
|
|
|
wildcard_certificate_match(const char *pattern, const char *string)
|
|
|
|
{
|
|
|
|
int lenpat = strlen(pattern);
|
|
|
|
int lenstr = strlen(string);
|
|
|
|
|
|
|
|
/* If we don't start with a wildcard, it's not a match (rule 1 & 2) */
|
|
|
|
if (lenpat < 3 ||
|
|
|
|
pattern[0] != '*' ||
|
|
|
|
pattern[1] != '.')
|
|
|
|
return false;
|
|
|
|
|
|
|
|
/* If pattern is longer than the string, we can never match */
|
|
|
|
if (lenpat > lenstr)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If string does not end in pattern (minus the wildcard), we don't match
|
|
|
|
*/
|
|
|
|
if (pg_strcasecmp(pattern + 1, string + lenstr - lenpat + 1) != 0)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If there is a dot left of where the pattern started to match, we don't
|
|
|
|
* match (rule 3)
|
|
|
|
*/
|
|
|
|
if (strchr(string, '.') < string + lenstr - lenpat)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
/* String ended with pattern, and didn't have a dot before, so we match */
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Check if a name from a server's certificate matches the peer's hostname.
|
|
|
|
*
|
|
|
|
* Returns 1 if the name matches, and 0 if it does not. On error, returns
|
|
|
|
* -1, and sets the libpq error message.
|
|
|
|
*
|
|
|
|
* The name extracted from the certificate is returned in *store_name. The
|
|
|
|
* caller is responsible for freeing it.
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
pq_verify_peer_name_matches_certificate_name(PGconn *conn,
|
|
|
|
const char *namedata, size_t namelen,
|
|
|
|
char **store_name)
|
|
|
|
{
|
|
|
|
char *name;
|
|
|
|
int result;
|
Change libpq's internal uses of PQhost() to inspect host field directly.
Commit 1944cdc98 changed PQhost() to return the hostaddr value when that
is specified and host isn't. This is a good idea in general, but
fe-auth.c and related files contain PQhost() calls for which it isn't.
Specifically, when we compare SSL certificates or other server identity
information to the host field, we do not want to use hostaddr instead;
that's not what's documented, that's not what happened pre-v10, and
it doesn't seem like a good idea.
Instead, we can just look at connhost[].host directly. This does what
we want in v10 and up; in particular, if neither host nor hostaddr
were given, the host field will be replaced with the default host name.
That seems useful, and it's likely the reason that these places were
coded to call PQhost() originally (since pre-v10, the stored field was
not replaced with the default).
Back-patch to v10, as 1944cdc98 (just) was.
Discussion: https://postgr.es/m/23287.1533227021@sss.pgh.pa.us
2018-08-03 18:12:10 +02:00
|
|
|
char *host = conn->connhost[conn->whichhost].host;
|
2018-01-27 19:47:52 +01:00
|
|
|
|
|
|
|
*store_name = NULL;
|
|
|
|
|
Change libpq's internal uses of PQhost() to inspect host field directly.
Commit 1944cdc98 changed PQhost() to return the hostaddr value when that
is specified and host isn't. This is a good idea in general, but
fe-auth.c and related files contain PQhost() calls for which it isn't.
Specifically, when we compare SSL certificates or other server identity
information to the host field, we do not want to use hostaddr instead;
that's not what's documented, that's not what happened pre-v10, and
it doesn't seem like a good idea.
Instead, we can just look at connhost[].host directly. This does what
we want in v10 and up; in particular, if neither host nor hostaddr
were given, the host field will be replaced with the default host name.
That seems useful, and it's likely the reason that these places were
coded to call PQhost() originally (since pre-v10, the stored field was
not replaced with the default).
Back-patch to v10, as 1944cdc98 (just) was.
Discussion: https://postgr.es/m/23287.1533227021@sss.pgh.pa.us
2018-08-03 18:12:10 +02:00
|
|
|
if (!(host && host[0] != '\0'))
|
|
|
|
{
|
2022-11-15 11:50:04 +01:00
|
|
|
libpq_append_conn_error(conn, "host name must be specified");
|
Change libpq's internal uses of PQhost() to inspect host field directly.
Commit 1944cdc98 changed PQhost() to return the hostaddr value when that
is specified and host isn't. This is a good idea in general, but
fe-auth.c and related files contain PQhost() calls for which it isn't.
Specifically, when we compare SSL certificates or other server identity
information to the host field, we do not want to use hostaddr instead;
that's not what's documented, that's not what happened pre-v10, and
it doesn't seem like a good idea.
Instead, we can just look at connhost[].host directly. This does what
we want in v10 and up; in particular, if neither host nor hostaddr
were given, the host field will be replaced with the default host name.
That seems useful, and it's likely the reason that these places were
coded to call PQhost() originally (since pre-v10, the stored field was
not replaced with the default).
Back-patch to v10, as 1944cdc98 (just) was.
Discussion: https://postgr.es/m/23287.1533227021@sss.pgh.pa.us
2018-08-03 18:12:10 +02:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2018-01-27 19:47:52 +01:00
|
|
|
/*
|
|
|
|
* There is no guarantee the string returned from the certificate is
|
|
|
|
* NULL-terminated, so make a copy that is.
|
|
|
|
*/
|
|
|
|
name = malloc(namelen + 1);
|
|
|
|
if (name == NULL)
|
|
|
|
{
|
2022-11-15 11:50:04 +01:00
|
|
|
libpq_append_conn_error(conn, "out of memory");
|
2018-01-27 19:47:52 +01:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
memcpy(name, namedata, namelen);
|
|
|
|
name[namelen] = '\0';
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Reject embedded NULLs in certificate common or alternative name to
|
|
|
|
* prevent attacks like CVE-2009-4034.
|
|
|
|
*/
|
|
|
|
if (namelen != strlen(name))
|
|
|
|
{
|
|
|
|
free(name);
|
2022-11-15 11:50:04 +01:00
|
|
|
libpq_append_conn_error(conn, "SSL certificate's name contains embedded null");
|
2018-01-27 19:47:52 +01:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pg_strcasecmp(name, host) == 0)
|
|
|
|
{
|
|
|
|
/* Exact name match */
|
|
|
|
result = 1;
|
|
|
|
}
|
|
|
|
else if (wildcard_certificate_match(name, host))
|
|
|
|
{
|
|
|
|
/* Matched wildcard name */
|
|
|
|
result = 1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
result = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
*store_name = name;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2022-04-01 15:41:44 +02:00
|
|
|
/*
|
|
|
|
* Check if an IP address from a server's certificate matches the peer's
|
|
|
|
* hostname (which must itself be an IPv4/6 address).
|
|
|
|
*
|
|
|
|
* Returns 1 if the address matches, and 0 if it does not. On error, returns
|
|
|
|
* -1, and sets the libpq error message.
|
|
|
|
*
|
|
|
|
* A string representation of the certificate's IP address is returned in
|
|
|
|
* *store_name. The caller is responsible for freeing it.
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
pq_verify_peer_name_matches_certificate_ip(PGconn *conn,
|
|
|
|
const unsigned char *ipdata,
|
|
|
|
size_t iplen,
|
|
|
|
char **store_name)
|
|
|
|
{
|
|
|
|
char *addrstr;
|
|
|
|
int match = 0;
|
|
|
|
char *host = conn->connhost[conn->whichhost].host;
|
|
|
|
int family;
|
|
|
|
char tmp[sizeof "ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255"];
|
|
|
|
char sebuf[PG_STRERROR_R_BUFLEN];
|
|
|
|
|
|
|
|
*store_name = NULL;
|
|
|
|
|
|
|
|
if (!(host && host[0] != '\0'))
|
|
|
|
{
|
2022-11-15 11:50:04 +01:00
|
|
|
libpq_append_conn_error(conn, "host name must be specified");
|
2022-04-01 15:41:44 +02:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The data from the certificate is in network byte order. Convert our
|
|
|
|
* host string to network-ordered bytes as well, for comparison. (The host
|
|
|
|
* string isn't guaranteed to actually be an IP address, so if this
|
|
|
|
* conversion fails we need to consider it a mismatch rather than an
|
|
|
|
* error.)
|
|
|
|
*/
|
|
|
|
if (iplen == 4)
|
|
|
|
{
|
|
|
|
/* IPv4 */
|
|
|
|
struct in_addr addr;
|
|
|
|
|
|
|
|
family = AF_INET;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The use of inet_aton() is deliberate; we accept alternative IPv4
|
|
|
|
* address notations that are accepted by inet_aton() but not
|
|
|
|
* inet_pton() as server addresses.
|
|
|
|
*/
|
|
|
|
if (inet_aton(host, &addr))
|
|
|
|
{
|
|
|
|
if (memcmp(ipdata, &addr.s_addr, iplen) == 0)
|
|
|
|
match = 1;
|
|
|
|
}
|
|
|
|
}
|
2022-05-12 21:17:30 +02:00
|
|
|
|
2022-04-01 15:41:44 +02:00
|
|
|
/*
|
|
|
|
* If they don't have inet_pton(), skip this. Then, an IPv6 address in a
|
|
|
|
* certificate will cause an error.
|
|
|
|
*/
|
|
|
|
#ifdef HAVE_INET_PTON
|
|
|
|
else if (iplen == 16)
|
|
|
|
{
|
|
|
|
/* IPv6 */
|
|
|
|
struct in6_addr addr;
|
|
|
|
|
|
|
|
family = AF_INET6;
|
|
|
|
|
|
|
|
if (inet_pton(AF_INET6, host, &addr) == 1)
|
|
|
|
{
|
|
|
|
if (memcmp(ipdata, &addr.s6_addr, iplen) == 0)
|
|
|
|
match = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* Not IPv4 or IPv6. We could ignore the field, but leniency seems
|
|
|
|
* wrong given the subject matter.
|
|
|
|
*/
|
2022-11-15 11:50:04 +01:00
|
|
|
libpq_append_conn_error(conn, "certificate contains IP address with invalid length %zu",
|
2022-11-13 21:09:05 +01:00
|
|
|
iplen);
|
2022-04-01 15:41:44 +02:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Generate a human-readable representation of the certificate's IP. */
|
|
|
|
addrstr = pg_inet_net_ntop(family, ipdata, 8 * iplen, tmp, sizeof(tmp));
|
|
|
|
if (!addrstr)
|
|
|
|
{
|
2022-11-15 11:50:04 +01:00
|
|
|
libpq_append_conn_error(conn, "could not convert certificate's IP address to string: %s",
|
2022-04-01 15:41:44 +02:00
|
|
|
strerror_r(errno, sebuf, sizeof(sebuf)));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
*store_name = strdup(addrstr);
|
|
|
|
return match;
|
|
|
|
}
|
|
|
|
|
2018-01-27 19:47:52 +01:00
|
|
|
/*
|
|
|
|
* Verify that the server certificate matches the hostname we connected to.
|
|
|
|
*
|
|
|
|
* The certificate's Common Name and Subject Alternative Names are considered.
|
|
|
|
*/
|
|
|
|
bool
|
|
|
|
pq_verify_peer_name_matches_certificate(PGconn *conn)
|
|
|
|
{
|
Change libpq's internal uses of PQhost() to inspect host field directly.
Commit 1944cdc98 changed PQhost() to return the hostaddr value when that
is specified and host isn't. This is a good idea in general, but
fe-auth.c and related files contain PQhost() calls for which it isn't.
Specifically, when we compare SSL certificates or other server identity
information to the host field, we do not want to use hostaddr instead;
that's not what's documented, that's not what happened pre-v10, and
it doesn't seem like a good idea.
Instead, we can just look at connhost[].host directly. This does what
we want in v10 and up; in particular, if neither host nor hostaddr
were given, the host field will be replaced with the default host name.
That seems useful, and it's likely the reason that these places were
coded to call PQhost() originally (since pre-v10, the stored field was
not replaced with the default).
Back-patch to v10, as 1944cdc98 (just) was.
Discussion: https://postgr.es/m/23287.1533227021@sss.pgh.pa.us
2018-08-03 18:12:10 +02:00
|
|
|
char *host = conn->connhost[conn->whichhost].host;
|
2018-01-27 19:47:52 +01:00
|
|
|
int rc;
|
|
|
|
int names_examined = 0;
|
|
|
|
char *first_name = NULL;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If told not to verify the peer name, don't do it. Return true
|
|
|
|
* indicating that the verification was successful.
|
|
|
|
*/
|
|
|
|
if (strcmp(conn->sslmode, "verify-full") != 0)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
/* Check that we have a hostname to compare with. */
|
|
|
|
if (!(host && host[0] != '\0'))
|
|
|
|
{
|
2022-11-15 11:50:04 +01:00
|
|
|
libpq_append_conn_error(conn, "host name must be specified for a verified SSL connection");
|
2018-01-27 19:47:52 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = pgtls_verify_peer_name_matches_certificate_guts(conn, &names_examined, &first_name);
|
|
|
|
|
|
|
|
if (rc == 0)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* No match. Include the name from the server certificate in the error
|
|
|
|
* message, to aid debugging broken configurations. If there are
|
|
|
|
* multiple names, only print the first one to avoid an overly long
|
|
|
|
* error message.
|
|
|
|
*/
|
|
|
|
if (names_examined > 1)
|
|
|
|
{
|
In libpq, always append new error messages to conn->errorMessage.
Previously, we had an undisciplined mish-mash of printfPQExpBuffer and
appendPQExpBuffer calls to report errors within libpq. This commit
establishes a uniform rule that appendPQExpBuffer[Str] should be used.
conn->errorMessage is reset only at the start of an application request,
and then accumulates messages till we're done. We can remove no less
than three different ad-hoc mechanisms that were used to get the effect
of concatenation of error messages within a sequence of operations.
Although this makes things quite a bit cleaner conceptually, the main
reason to do it is to make the world safer for the multiple-target-host
feature that was added awhile back. Previously, there were many cases
in which an error occurring during an individual host connection attempt
would wipe out the record of what had happened during previous attempts.
(The reporting is still inadequate, in that it can be hard to tell which
host got the failure, but that seems like a matter for a separate commit.)
Currently, lo_import and lo_export contain exceptions to the "never
use printfPQExpBuffer" rule. If we changed them, we'd risk reporting
an incidental lo_close failure before the actual read or write
failure, which would be confusing, not least because lo_close happened
after the main failure. We could improve this by inventing an
internal version of lo_close that doesn't reset the errorMessage; but
we'd also need a version of PQfn() that does that, and it didn't quite
seem worth the trouble for now.
Discussion: https://postgr.es/m/BN6PR05MB3492948E4FD76C156E747E8BC9160@BN6PR05MB3492.namprd05.prod.outlook.com
2021-01-11 19:12:09 +01:00
|
|
|
appendPQExpBuffer(&conn->errorMessage,
|
2022-11-15 11:50:04 +01:00
|
|
|
libpq_ngettext("server certificate for \"%s\" (and %d other name) does not match host name \"%s\"",
|
|
|
|
"server certificate for \"%s\" (and %d other names) does not match host name \"%s\"",
|
2018-01-27 19:47:52 +01:00
|
|
|
names_examined - 1),
|
|
|
|
first_name, names_examined - 1, host);
|
2022-11-15 11:50:04 +01:00
|
|
|
appendPQExpBufferChar(&conn->errorMessage, '\n');
|
2018-01-27 19:47:52 +01:00
|
|
|
}
|
|
|
|
else if (names_examined == 1)
|
|
|
|
{
|
2022-11-15 11:50:04 +01:00
|
|
|
libpq_append_conn_error(conn, "server certificate for \"%s\" does not match host name \"%s\"",
|
2018-01-27 19:47:52 +01:00
|
|
|
first_name, host);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2022-11-15 11:50:04 +01:00
|
|
|
libpq_append_conn_error(conn, "could not get server's host name from server certificate");
|
2018-01-27 19:47:52 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* clean up */
|
2022-06-16 21:50:56 +02:00
|
|
|
free(first_name);
|
2018-01-27 19:47:52 +01:00
|
|
|
|
|
|
|
return (rc == 1);
|
|
|
|
}
|