postgresql/src/interfaces/libpq/fe-secure-common.c

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

308 lines
7.9 KiB
C
Raw Normal View History

/*-------------------------------------------------------------------------
*
* 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.
*
* Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* src/interfaces/libpq/fe-secure-common.c
*
*-------------------------------------------------------------------------
*/
#include "postgres_fe.h"
#include <arpa/inet.h>
#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;
char *host = conn->connhost[conn->whichhost].host;
*store_name = NULL;
if (!(host && host[0] != '\0'))
{
libpq_append_conn_error(conn, "host name must be specified");
return -1;
}
/*
* 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)
{
libpq_append_conn_error(conn, "out of memory");
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);
libpq_append_conn_error(conn, "SSL certificate's name contains embedded null");
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;
}
/*
* 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'))
{
libpq_append_conn_error(conn, "host name must be specified");
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;
}
}
/*
* 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.
*/
libpq_append_conn_error(conn, "certificate contains IP address with invalid length %zu",
iplen);
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)
{
libpq_append_conn_error(conn, "could not convert certificate's IP address to string: %s",
strerror_r(errno, sebuf, sizeof(sebuf)));
return -1;
}
*store_name = strdup(addrstr);
return match;
}
/*
* 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)
{
char *host = conn->connhost[conn->whichhost].host;
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'))
{
libpq_append_conn_error(conn, "host name must be specified for a verified SSL connection");
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,
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\"",
names_examined - 1),
first_name, names_examined - 1, host);
appendPQExpBufferChar(&conn->errorMessage, '\n');
}
else if (names_examined == 1)
{
libpq_append_conn_error(conn, "server certificate for \"%s\" does not match host name \"%s\"",
first_name, host);
}
else
{
libpq_append_conn_error(conn, "could not get server's host name from server certificate");
}
}
/* clean up */
free(first_name);
return (rc == 1);
}