aboutsummaryrefslogtreecommitdiff
path: root/src/interfaces/libpq/fe-secure-common.c
diff options
context:
space:
mode:
authorPeter Eisentraut <peter@eisentraut.org>2022-04-01 15:41:44 +0200
committerPeter Eisentraut <peter@eisentraut.org>2022-04-01 15:51:23 +0200
commitc1932e542863f0f646f005b3492452acc57c7e66 (patch)
tree5b5b5235d68749d804f8fdf0cb7d47a7fd3fd032 /src/interfaces/libpq/fe-secure-common.c
parentfa25bebb827a8cc4d62f15d564b0093f40b9d44d (diff)
downloadpostgresql-c1932e542863f0f646f005b3492452acc57c7e66.tar.gz
postgresql-c1932e542863f0f646f005b3492452acc57c7e66.zip
libpq: Allow IP address SANs in server certificates
The current implementation supports exactly one IP address in a server certificate's Common Name, which is brittle (the strings must match exactly). This patch adds support for IPv4 and IPv6 addresses in a server's Subject Alternative Names. Per discussion on-list: - If the client's expected host is an IP address, we allow fallback to the Subject Common Name if an iPAddress SAN is not present, even if a dNSName is present. This matches the behavior of NSS, in violation of the relevant RFCs. - We also, counter-intuitively, match IP addresses embedded in dNSName SANs. From inspection this appears to have been the behavior since the SAN matching feature was introduced in acd08d76. - Unlike NSS, we don't map IPv4 to IPv6 addresses, or vice-versa. Author: Jacob Champion <pchampion@vmware.com> Co-authored-by: Kyotaro Horiguchi <horikyota.ntt@gmail.com> Co-authored-by: Daniel Gustafsson <daniel@yesql.se> Discussion: https://www.postgresql.org/message-id/flat/9f5f20974cd3a4091a788cf7f00ab663d5fcdffe.camel@vmware.com
Diffstat (limited to 'src/interfaces/libpq/fe-secure-common.c')
-rw-r--r--src/interfaces/libpq/fe-secure-common.c104
1 files changed, 104 insertions, 0 deletions
diff --git a/src/interfaces/libpq/fe-secure-common.c b/src/interfaces/libpq/fe-secure-common.c
index bd46f08faee..165a6ed9b7b 100644
--- a/src/interfaces/libpq/fe-secure-common.c
+++ b/src/interfaces/libpq/fe-secure-common.c
@@ -19,6 +19,8 @@
#include "postgres_fe.h"
+#include <arpa/inet.h>
+
#include "fe-secure-common.h"
#include "libpq-int.h"
@@ -145,6 +147,108 @@ pq_verify_peer_name_matches_certificate_name(PGconn *conn,
}
/*
+ * 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'))
+ {
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("host name must be specified\n"));
+ 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.
+ */
+ appendPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("certificate contains IP address with invalid length %lu\n"),
+ (unsigned long) 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)
+ {
+ appendPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not convert certificate's IP address to string: %s\n"),
+ 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.