[PATCH] Accept IP addresses in server certificate SANs

Started by Jacob Championabout 4 years ago46 messages
#1Jacob Champion
pchampion@vmware.com
2 attachment(s)

Hello all,

libpq currently supports server certificates with a single IP address
in the Common Name. It's fairly brittle; as far as I can tell, the
single name you choose has to match the client's address exactly.

Attached is a patch for libpq to support IP addresses in the server's
Subject Alternative Names, which would allow admins to issue certs for
multiple IP addresses, both IPv4 and IPv6, and mix them with
alternative DNS hostnames. These addresses are compared bytewise
instead of stringwise, so the client can contact the server via
alternative spellings of the same IP address.

This patch arose because I was writing tests for the NSS implementation
that used a server cert with both DNS names and IP addresses, and then
they failed when I ran those tests against the OpenSSL implementation.
NSS supports this functionality natively. Anecdotally, I've heard from
at least one client group who is utilizing IP-based certificates in
their cloud deployments. It seems uncommon but still useful.

There are two open questions I have; they're based on NSS
implementation details that I did not port here:

- NSS allows an IPv4 SAN to match an IPv6 mapping of that same address,
and vice-versa. I chose not to implement that behavior, figuring it
is easy enough for people to issue a certificate with both addresses.
Is that okay?

- If a certificate contains only iPAddress SANs, and none of them
match, I fall back to check the certificate Common Name. OpenSSL will
not do this (its X509_check_ip considers only SANs). NSS will only do
this if the client's address is itself a DNS name. The spec says that
we can't fall back to Common Name if the SANs contain any DNS
entries, but it's silent on the subject of IP addresses. What should
the behavior be?

The patchset roadmap:

- 0001 moves inet_net_pton() to src/port, since libpq will need it.
- 0002 implements the new functionality and adds tests.

WDYT?

Thanks,
--Jacob

Attachments:

0001-Move-inet_net_pton-to-src-port.patchtext/x-patch; name=0001-Move-inet_net_pton-to-src-port.patchDownload
From db56d6593a4574c243ccee1dbb4c8c1f1c712795 Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Wed, 24 Nov 2021 14:46:11 -0800
Subject: [PATCH 1/2] Move inet_net_pton() to src/port

This will be helpful for IP address verification in libpq.
---
 src/backend/utils/adt/Makefile                  |  1 -
 src/include/port.h                              |  3 +++
 src/include/utils/builtins.h                    |  4 ----
 src/port/Makefile                               |  1 +
 src/{backend/utils/adt => port}/inet_net_pton.c | 16 +++++++++++++++-
 5 files changed, 19 insertions(+), 6 deletions(-)
 rename src/{backend/utils/adt => port}/inet_net_pton.c (96%)

diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile
index 41b486bcef..d173d52157 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -43,7 +43,6 @@ OBJS = \
 	geo_selfuncs.o \
 	geo_spgist.o \
 	inet_cidr_ntop.o \
-	inet_net_pton.o \
 	int.o \
 	int8.o \
 	json.o \
diff --git a/src/include/port.h b/src/include/port.h
index fd9c9d6f94..cca29839bc 100644
--- a/src/include/port.h
+++ b/src/include/port.h
@@ -518,6 +518,9 @@ extern int	pg_codepage_to_encoding(UINT cp);
 extern char *pg_inet_net_ntop(int af, const void *src, int bits,
 							  char *dst, size_t size);
 
+/* port/inet_net_pton.c */
+extern int	pg_inet_net_pton(int af, const char *src, void *dst, size_t size);
+
 /* port/pg_strong_random.c */
 extern void pg_strong_random_init(void);
 extern bool pg_strong_random(void *buf, size_t len);
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 40fcb0ab6d..d063a86260 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -93,10 +93,6 @@ extern int	xidComparator(const void *arg1, const void *arg2);
 extern char *pg_inet_cidr_ntop(int af, const void *src, int bits,
 							   char *dst, size_t size);
 
-/* inet_net_pton.c */
-extern int	pg_inet_net_pton(int af, const char *src,
-							 void *dst, size_t size);
-
 /* network.c */
 extern double convert_network_to_scalar(Datum value, Oid typid, bool *failure);
 extern Datum network_scan_first(Datum in);
diff --git a/src/port/Makefile b/src/port/Makefile
index b3754d8940..7191cd6633 100644
--- a/src/port/Makefile
+++ b/src/port/Makefile
@@ -43,6 +43,7 @@ OBJS = \
 	bsearch_arg.o \
 	chklocale.o \
 	inet_net_ntop.o \
+	inet_net_pton.o \
 	noblock.o \
 	path.o \
 	pg_bitutils.o \
diff --git a/src/backend/utils/adt/inet_net_pton.c b/src/port/inet_net_pton.c
similarity index 96%
rename from src/backend/utils/adt/inet_net_pton.c
rename to src/port/inet_net_pton.c
index d3221a1313..bae50ba67e 100644
--- a/src/backend/utils/adt/inet_net_pton.c
+++ b/src/port/inet_net_pton.c
@@ -14,14 +14,18 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
  * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  *
- *	  src/backend/utils/adt/inet_net_pton.c
+ *	  src/port/inet_net_pton.c
  */
 
 #if defined(LIBC_SCCS) && !defined(lint)
 static const char rcsid[] = "Id: inet_net_pton.c,v 1.4.2.3 2004/03/17 00:40:11 marka Exp $";
 #endif
 
+#ifndef FRONTEND
 #include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
 
 #include <sys/socket.h>
 #include <netinet/in.h>
@@ -29,9 +33,19 @@ static const char rcsid[] = "Id: inet_net_pton.c,v 1.4.2.3 2004/03/17 00:40:11 m
 #include <assert.h>
 #include <ctype.h>
 
+#ifndef FRONTEND
 #include "utils/builtins.h" /* pgrminclude ignore */	/* needed on some
 														 * platforms */
 #include "utils/inet.h"
+#else
+/*
+ * In a frontend build, we can't include inet.h, but we still need to have
+ * sensible definitions of these two constants.  Note that pg_inet_net_ntop()
+ * assumes that PGSQL_AF_INET is equal to AF_INET.
+ */
+#define PGSQL_AF_INET	(AF_INET + 0)
+#define PGSQL_AF_INET6	(AF_INET + 1)
+#endif
 
 
 static int	inet_net_pton_ipv4(const char *src, u_char *dst);
-- 
2.25.1

0002-libpq-allow-IP-address-SANs-in-server-certs.patchtext/x-patch; name=0002-libpq-allow-IP-address-SANs-in-server-certs.patchDownload
From ddf36f30a5b08ef2db758c32c6e6219b299a6ba9 Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Thu, 18 Nov 2021 15:36:18 -0800
Subject: [PATCH 2/2] libpq: allow IP address SANs in server certs

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.

Open questions:

- Should we fall back to Subject Common Name if an IP address SAN is
  present and the client's expected host is an IP address? See TODO in
  pgtls_verify_peer_name_matches_certificate_guts().

- We don't map IPv4 to IPv6 addresses and vice-versa. NSS does; OpenSSL
  does not. Should we?
---
 src/interfaces/libpq/fe-secure-common.c       | 110 ++++++++++++++++++
 src/interfaces/libpq/fe-secure-common.h       |   3 +
 src/interfaces/libpq/fe-secure-openssl.c      |  73 ++++++++++--
 .../conf/server-cn-and-ip-alt-names.config    |  24 ++++
 src/test/ssl/conf/server-ip-alt-names.config  |  19 +++
 src/test/ssl/conf/server-ip-cn-only.config    |  12 ++
 .../ssl/ssl/server-cn-and-ip-alt-names.crt    |  20 ++++
 .../ssl/ssl/server-cn-and-ip-alt-names.key    |  27 +++++
 src/test/ssl/ssl/server-ip-alt-names.crt      |  19 +++
 src/test/ssl/ssl/server-ip-alt-names.key      |  27 +++++
 src/test/ssl/ssl/server-ip-cn-only.crt        |  18 +++
 src/test/ssl/ssl/server-ip-cn-only.key        |  27 +++++
 src/test/ssl/sslfiles.mk                      |   3 +
 src/test/ssl/t/001_ssltests.pl                |  77 +++++++++++-
 14 files changed, 448 insertions(+), 11 deletions(-)
 create mode 100644 src/test/ssl/conf/server-cn-and-ip-alt-names.config
 create mode 100644 src/test/ssl/conf/server-ip-alt-names.config
 create mode 100644 src/test/ssl/conf/server-ip-cn-only.config
 create mode 100644 src/test/ssl/ssl/server-cn-and-ip-alt-names.crt
 create mode 100644 src/test/ssl/ssl/server-cn-and-ip-alt-names.key
 create mode 100644 src/test/ssl/ssl/server-ip-alt-names.crt
 create mode 100644 src/test/ssl/ssl/server-ip-alt-names.key
 create mode 100644 src/test/ssl/ssl/server-ip-cn-only.crt
 create mode 100644 src/test/ssl/ssl/server-ip-cn-only.key

diff --git a/src/interfaces/libpq/fe-secure-common.c b/src/interfaces/libpq/fe-secure-common.c
index afa5d133e1..38ee65abf2 100644
--- a/src/interfaces/libpq/fe-secure-common.c
+++ b/src/interfaces/libpq/fe-secure-common.c
@@ -19,11 +19,23 @@
 
 #include "postgres_fe.h"
 
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
 #include "fe-secure-common.h"
 
 #include "libpq-int.h"
+#include "port.h"
 #include "pqexpbuffer.h"
 
+/*
+ * In a frontend build, we can't include inet.h, but we still need to have
+ * sensible definitions of these two constants.  Note that pg_inet_net_ntop()
+ * assumes that PGSQL_AF_INET is equal to AF_INET.
+ */
+#define PGSQL_AF_INET	(AF_INET + 0)
+#define PGSQL_AF_INET6	(AF_INET + 1)
+
 /*
  * Check if a wildcard certificate matches the server hostname.
  *
@@ -144,6 +156,104 @@ pq_verify_peer_name_matches_certificate_name(PGconn *conn,
 	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 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 = PGSQL_AF_INET;
+
+		/*
+		 * The use of inet_aton() instead of inet_pton() is deliberate; the
+		 * latter cannot handle alternate IPv4 notations ("numbers-and-dots").
+		 */
+		if (inet_aton(host, &addr))
+		{
+			if (memcmp(ipdata, &addr.s_addr, iplen) == 0)
+				match = 1;
+		}
+	}
+	else if (iplen == 16)
+	{
+		/* IPv6 */
+		unsigned char addr[16];
+
+		family = PGSQL_AF_INET6;
+
+		/*
+		 * pg_inet_net_pton() will accept CIDR masks, which we don't want to
+		 * match, so skip the comparison if the host string contains a slash.
+		 */
+		if (!strchr(host, '/')
+			&& pg_inet_net_pton(PGSQL_AF_INET6, host, addr, -1) == 128)
+		{
+			if (memcmp(ipdata, addr, iplen) == 0)
+				match = 1;
+		}
+	}
+	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.
  *
diff --git a/src/interfaces/libpq/fe-secure-common.h b/src/interfaces/libpq/fe-secure-common.h
index 2389f6717a..a090a92f60 100644
--- a/src/interfaces/libpq/fe-secure-common.h
+++ b/src/interfaces/libpq/fe-secure-common.h
@@ -21,6 +21,9 @@
 extern int	pq_verify_peer_name_matches_certificate_name(PGconn *conn,
 														 const char *namedata, size_t namelen,
 														 char **store_name);
+extern int	pq_verify_peer_name_matches_certificate_ip(PGconn *conn,
+													   const char *addrdata, size_t addrlen,
+													   char **store_name);
 extern bool pq_verify_peer_name_matches_certificate(PGconn *conn);
 
 #endif							/* FE_SECURE_COMMON_H */
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index 33f095c12e..16c5ff9223 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -72,6 +72,9 @@ static int	verify_cb(int ok, X509_STORE_CTX *ctx);
 static int	openssl_verify_peer_name_matches_certificate_name(PGconn *conn,
 															  ASN1_STRING *name,
 															  char **store_name);
+static int	openssl_verify_peer_name_matches_certificate_ip(PGconn *conn,
+															ASN1_OCTET_STRING *addr_entry,
+															char **store_name);
 static void destroy_ssl_system(void);
 static int	initialize_SSL(PGconn *conn);
 static PostgresPollingStatusType open_client_SSL(PGconn *);
@@ -509,6 +512,42 @@ openssl_verify_peer_name_matches_certificate_name(PGconn *conn, ASN1_STRING *nam
 	return pq_verify_peer_name_matches_certificate_name(conn, (const char *) namedata, len, store_name);
 }
 
+/*
+ * OpenSSL-specific wrapper around
+ * pq_verify_peer_name_matches_certificate_ip(), converting the
+ * ASN1_OCTET_STRING into a plain C string.
+ */
+static int
+openssl_verify_peer_name_matches_certificate_ip(PGconn *conn,
+												ASN1_OCTET_STRING *addr_entry,
+												char **store_name)
+{
+	int			len;
+	const unsigned char *addrdata;
+
+	/* Should not happen... */
+	if (addr_entry == NULL)
+	{
+		appendPQExpBufferStr(&conn->errorMessage,
+							 libpq_gettext("SSL certificate's address entry is missing\n"));
+		return -1;
+	}
+
+	/*
+	 * GEN_IPADD is an OCTET STRING containing an IP address in network byte
+	 * order.
+	 */
+#ifdef HAVE_ASN1_STRING_GET0_DATA
+	addrdata = ASN1_STRING_get0_data(addr_entry);
+#else
+	addrdata = ASN1_STRING_data(addr_entry);
+#endif
+	len = ASN1_STRING_length(addr_entry);
+
+	/* OK to cast from unsigned to plain char, since it's all ASCII. */
+	return pq_verify_peer_name_matches_certificate_ip(conn, (const char *) addrdata, len, store_name);
+}
+
 /*
  *	Verify that the server certificate matches the hostname we connected to.
  *
@@ -522,6 +561,7 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 	STACK_OF(GENERAL_NAME) * peer_san;
 	int			i;
 	int			rc = 0;
+	bool		has_dnsname = false;
 
 	/*
 	 * First, get the Subject Alternative Names (SANs) from the certificate,
@@ -537,24 +577,32 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 		for (i = 0; i < san_len; i++)
 		{
 			const GENERAL_NAME *name = sk_GENERAL_NAME_value(peer_san, i);
+			char	   *alt_name = NULL;
 
 			if (name->type == GEN_DNS)
 			{
-				char	   *alt_name;
-
+				has_dnsname = true;
 				(*names_examined)++;
 				rc = openssl_verify_peer_name_matches_certificate_name(conn,
 																	   name->d.dNSName,
 																	   &alt_name);
+			}
+			else if (name->type == GEN_IPADD)
+			{
+				(*names_examined)++;
+				rc = openssl_verify_peer_name_matches_certificate_ip(conn,
+																	 name->d.iPAddress,
+																	 &alt_name);
+			}
 
-				if (alt_name)
-				{
-					if (!*first_name)
-						*first_name = alt_name;
-					else
-						free(alt_name);
-				}
+			if (alt_name)
+			{
+				if (!*first_name)
+					*first_name = alt_name;
+				else
+					free(alt_name);
 			}
+
 			if (rc != 0)
 				break;
 		}
@@ -567,8 +615,13 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 	 *
 	 * (Per RFC 2818 and RFC 6125, if the subjectAltName extension of type
 	 * dNSName is present, the CN must be ignored.)
+	 *
+	 * TODO: we fall back to checking the CN if an iPAddress exists. NSS does
+	 * not, if the client's supplied host is itself an IP address. OpenSSL's
+	 * X509_check_ip() does not, because it doesn't ever consider the CN. Should
+	 * we?
 	 */
-	if (*names_examined == 0)
+	if ((rc == 0) && !has_dnsname)
 	{
 		X509_NAME  *subject_name;
 
diff --git a/src/test/ssl/conf/server-cn-and-ip-alt-names.config b/src/test/ssl/conf/server-cn-and-ip-alt-names.config
new file mode 100644
index 0000000000..a6fa09bad3
--- /dev/null
+++ b/src/test/ssl/conf/server-cn-and-ip-alt-names.config
@@ -0,0 +1,24 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+# This certificate contains a CN and SANs for both IPv4 and IPv6.
+
+
+[ req ]
+distinguished_name     = req_distinguished_name
+req_extensions         = v3_req
+prompt                 = no
+
+[ req_distinguished_name ]
+# Note: According to RFC 2818 and 6125, the CN is ignored, when DNS names are
+# present in the SANs. But they are silent on whether the CN is checked when IP
+# addresses are present.
+CN = common-name.pg-ssltest.test
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
+subjectAltName = @alt_names
+
+[ alt_names ]
+IP.1 = 192.0.2.1
+IP.2 = 2001:DB8::1
diff --git a/src/test/ssl/conf/server-ip-alt-names.config b/src/test/ssl/conf/server-ip-alt-names.config
new file mode 100644
index 0000000000..c22f22951a
--- /dev/null
+++ b/src/test/ssl/conf/server-ip-alt-names.config
@@ -0,0 +1,19 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+# This certificate has a two IP-address SANs, and no CN.
+
+[ req ]
+distinguished_name     = req_distinguished_name
+req_extensions         = v3_req
+prompt                 = no
+
+[ req_distinguished_name ]
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
+subjectAltName = @alt_names
+
+[ alt_names ]
+IP.1 = 192.0.2.1
+IP.2 = 2001:DB8::1
diff --git a/src/test/ssl/conf/server-ip-cn-only.config b/src/test/ssl/conf/server-ip-cn-only.config
new file mode 100644
index 0000000000..585d8bdae8
--- /dev/null
+++ b/src/test/ssl/conf/server-ip-cn-only.config
@@ -0,0 +1,12 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+
+[ req ]
+distinguished_name     = req_distinguished_name
+prompt                 = no
+
+[ req_distinguished_name ]
+CN = 192.0.2.1
+OU = PostgreSQL test suite
+
+# No Subject Alternative Names
diff --git a/src/test/ssl/ssl/server-cn-and-ip-alt-names.crt b/src/test/ssl/ssl/server-cn-and-ip-alt-names.crt
new file mode 100644
index 0000000000..4e58c85ccb
--- /dev/null
+++ b/src/test/ssl/ssl/server-cn-and-ip-alt-names.crt
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDLzCCAhegAwIBAgIIICERKRE1UQAwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UE
+Aww3VGVzdCBDQSBmb3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHNl
+cnZlciBjZXJ0czAeFw0yMTExMjkxOTM1NTFaFw00OTA0MTYxOTM1NTFaMEYxHjAc
+BgNVBAsMFVBvc3RncmVTUUwgdGVzdCBzdWl0ZTEkMCIGA1UEAwwbY29tbW9uLW5h
+bWUucGctc3NsdGVzdC50ZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
+AQEA6+8IYKAFnZ7V+fDo1cyMpbGBLzCfJOQ/1o2jOGP4+GjpsZgv6S6UT2MheC8M
+iiEFrYwdsSIZyYc3jEZrluy/UuR0bCGtqU92BCqa0iBLhvHOgjR588u253eLxQtQ
+8iJn11QPrKMk35nMkmY8GfHt4sGFbvBL6+GpipHq7a6cde3Z+v4kCB5dKMYDUDtm
+3mJmviuGNAu5wOqItk2Yi5dwJs1054007KNH0Il43urxiOfnkLS0cG5kehboPf86
+vxBt3iHByrU/9/DY5IvQCfSXVNa6rb5w5/pGja9aCei6Mv1jQY/V8SMQTga+MOsA
+0WB9akxMi2NxwS2+BQ4k/McPlwIDAQABoyUwIzAhBgNVHREEGjAYhwTAAAIBhxAg
+AQ24AAAAAAAAAAAAAAABMA0GCSqGSIb3DQEBCwUAA4IBAQAQLo2RzC07dG9p+J3A
+W6C0p3Y+Os/YE2D9wfp4TIDTZxcRUQZ0S6ahF1N6sp8l9KHBJHPU1cUpRAU1oD+Y
+SqmnP/VJRRDTTj9Ytdc/Vuo2jeLpSYhVKrCqtjqIrCwYJFoYRmMoxTtJGlwA0hSd
+kwo3XYrALPUQWUErTYPvNfDNIuUwqUXNfS0CXuIOVN3LJ+shegg6Pwbh9B5T9NHx
+kH+HswajhdpdnZIgh0FYTlTCPILDrB49aOWwqLa54AUA6WXa35hPsP8SoqL9Eucq
+ifPhBYyadsjOb+70N8GbbAsDPN1jCX9L8RuNcEkxSCKCYx91cWXh7K5KMPuGlzB7
+j8xB
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-cn-and-ip-alt-names.key b/src/test/ssl/ssl/server-cn-and-ip-alt-names.key
new file mode 100644
index 0000000000..837eef996d
--- /dev/null
+++ b/src/test/ssl/ssl/server-cn-and-ip-alt-names.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA6+8IYKAFnZ7V+fDo1cyMpbGBLzCfJOQ/1o2jOGP4+GjpsZgv
+6S6UT2MheC8MiiEFrYwdsSIZyYc3jEZrluy/UuR0bCGtqU92BCqa0iBLhvHOgjR5
+88u253eLxQtQ8iJn11QPrKMk35nMkmY8GfHt4sGFbvBL6+GpipHq7a6cde3Z+v4k
+CB5dKMYDUDtm3mJmviuGNAu5wOqItk2Yi5dwJs1054007KNH0Il43urxiOfnkLS0
+cG5kehboPf86vxBt3iHByrU/9/DY5IvQCfSXVNa6rb5w5/pGja9aCei6Mv1jQY/V
+8SMQTga+MOsA0WB9akxMi2NxwS2+BQ4k/McPlwIDAQABAoIBAQCuNFKVNdKvrUYF
+RLJGmsAG3+eo9lern7TbML2ht39vu9dBwEMwA6qSa3mdCfBSVUuh9uE9lxY/TU3g
+j2aFi81A4VptNPjLGNblAKhMGnhp7UUzspeRQYuNoSFcnpxoDKtrvK/OIq/pQeBh
+AIfECHRDh+yEG32Tb44FuPQkB1eTYl8xbMEImrhNUaSjJk7tTsmydHy0DjmqHVKX
+HUj0TREfDBDOBiHtY0XV6Pu3bnqDH/TKLTfUf3UdfTuay3Yai9aEcRPWp9GrMO7G
+axsKCifTz6177gyr6Fv8HLeMZMh9rMZRn3e0zfaF6vrH1QnZZOts5jpUa0KugSCd
+//uC0iNxAoGBAPXVc3b+o3hY5gcwwpaW6JtsarDrmNRxrizqIDG7NgpqwdFXgTi6
+6q0t2pjv81ATqij69IcPkNSissyR4OEKnu/OFJWzreg8yLi75WHKi0E/6msHpwRk
+d1yP0Zgd05ots/yOjDSp593RagaPVvHBxMECZ/Tm3B+Tq55Azudd/zvLAoGBAPWw
+xf0oUEJl6NdUZD6K7eFc6jf8yrpD85dldeko6LeN8x0XlKKWvUDJ2+3oizXoQvCm
+8by6KOYEIo4MrtXuy9MmtPWfNvRBr+hsUHchIj7IgFa9bKXyK2FnJqu/8CbEymli
+eZu7hoOhelurhnFy1zSqwNO4GC+kw60Y/BO3Z1nlAoGAVOyYJtNwxXJwhKtjjYI0
+ePzLHrNE6J8c/Ick+AkkchTPP/JqwZ5Q0+KzUYITG+avMdkAAGhwMATEn8cFWLjC
+jzUyB0U7Hq9g5/CBHXdLBA+Ae9j46ZuLYH6OeW5UWz7OnsDfzpGjeA2QAxQhhQLb
+ZZHfN8tI39+zucfJskPWmGECgYEAg9guF1Fn6InJrqwR82IYj6SN6CeXHufSM392
+C/4xDDd3rDf4QlwECV2J0RzGf9I5Ae2EshNwWScE6Be0RweTh6cw2tJq6h7J6D8f
+2x4Dw49TF7klMdRIJUf2f5pLpHJccLswqTqzz7V69PCSABVxmUi8m6EiEYconp5W
+v7nfE2UCgYALrEqzncuSIX3q6TVAjnzT7gO4h8h2TUekIWdHQFldFx8R7Kncggnd
+48gQqhewchNR83UCcd7pPsCcTqu6UR1QRdq/DV5P6J3xdZ2iS/2gCM6hvWIvKZEv
+/ClnkyFCOW7zX6RKIXtRYZTV1kz3TajApi34RTIeIMTieaCarnBJbA==
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-ip-alt-names.crt b/src/test/ssl/ssl/server-ip-alt-names.crt
new file mode 100644
index 0000000000..8a1bc620bb
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-alt-names.crt
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDCTCCAfGgAwIBAgIIICERKREEUAAwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UE
+Aww3VGVzdCBDQSBmb3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHNl
+cnZlciBjZXJ0czAeFw0yMTExMjkxOTA0NTBaFw00OTA0MTYxOTA0NTBaMCAxHjAc
+BgNVBAsMFVBvc3RncmVTUUwgdGVzdCBzdWl0ZTCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBAOM8yB6aVWb17ujr3ayU62mxHQoqn4CvG9yXlJvGOGv/ursW
+Vs0UYJdc96LsNZN1szdm9ayNzCIw3eja+ULsjxCi6+3LM4pO76IORL/XFamlTPYb
+BZ4pHdZVB0nnZAAnWCZPyXdnjOKQ5+8unVXkfibkjj8UELBJ2snehsOa+CTkOBez
+zxYMqxAgbywLIYsW448brun7UXpWmqbGK+SsdGaIZ5Sb7Zezc5lt6CrLemTZTHHK
+7l4WZFCCEi4t3sgO8o1vDELD/IE5G8lyXvIdgJg6t8ssper7iCw6S8x+okhjiSjT
+vDLU2g4AanqZRZB49aPwTo0QUcJA2BCJxL9xLy8CAwEAAaMlMCMwIQYDVR0RBBow
+GIcEwAACAYcQIAENuAAAAAAAAAAAAAAAATANBgkqhkiG9w0BAQsFAAOCAQEAwZJ+
+8KpABTlMEgKnHIYb35ItGhtFiTLQta9RkXx7vaeDwpOdPP/IvuvpjpQZkobRgBsk
+bNM0KuJpd2mSTphQAt6eKQIdcPrkzvc/Yh9OK3YNLUAbu/ZhBUnBvFnUL4wn2f1U
+mfO+m8P/LxybwqKx7r1mbaB+tP3RTxxLcIMvm9ECPQEoBntfEL325Wdoj+WuQH5Y
+IvcM6FaCTkQsNIPbaBD5l5MhMLHRULZujbDjXqGSvRMQfns6np/biMjNdQA8NZ5z
+STeUFvkQbCxoA0YYLgoSHL5KhZjXrg2g+T+2TUyCTR/91xf9OoOjBZdixR0S0DzJ
+B1+5vnUjZaCfnSEA7A==
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-ip-alt-names.key b/src/test/ssl/ssl/server-ip-alt-names.key
new file mode 100644
index 0000000000..b210b3a991
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-alt-names.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEA4zzIHppVZvXu6OvdrJTrabEdCiqfgK8b3JeUm8Y4a/+6uxZW
+zRRgl1z3ouw1k3WzN2b1rI3MIjDd6Nr5QuyPEKLr7cszik7vog5Ev9cVqaVM9hsF
+nikd1lUHSedkACdYJk/Jd2eM4pDn7y6dVeR+JuSOPxQQsEnayd6Gw5r4JOQ4F7PP
+FgyrECBvLAshixbjjxuu6ftRelaapsYr5Kx0ZohnlJvtl7NzmW3oKst6ZNlMccru
+XhZkUIISLi3eyA7yjW8MQsP8gTkbyXJe8h2AmDq3yyyl6vuILDpLzH6iSGOJKNO8
+MtTaDgBqeplFkHj1o/BOjRBRwkDYEInEv3EvLwIDAQABAoIBACp3uY6+mSdc3wF4
+0zzlt/lQuHSl8plCIJrhWUyjhvfoGyXLzv0Uydh/72frbTfZz1yTSWauOXBKYa6a
+/eqb+0DIsf8G8uLuTaqjsAWKVOoXkoKMGkistn7P9UTCkdXVhIvkbWp7V8EgA7iX
+pZ/fzBPIsyzmuxe3NcR0ags0cxuxkNuu+YXDv1oTedmT2wS3CZq1d/T1Y/EOVIf8
+Iznd2aOverlsnt6iiQ3ZWdG/W5F8FhnrR/rrBdYsdCv6TH/KUYexnDOUYpayjDbu
+oAKnifPp6UqiOM4SuBL83OAz19jptp5vpF370BEVRs3eK0q+zo/mETjv9HsXdolZ
+lfoXA0ECgYEA/7nb2azbq/2muvXCh1ZxCEbn3mt8KXoJP/xkx/v9eEc/cc5Q9e0V
+2oGfjC2hSE+bjOWMwiUMD6uU+iRjhz5A3IvUxnoSdoL7H9p0hTqLMyP7dTDkoVF5
+aEuLMaiI5YEnfAFu9L5h8ZKieoQTBoscT06wnGjh9pBV9bthfTKA7ksCgYEA43sb
+55m9WL4kWCPwOAp3vdEAFyxzmZHlO26sEQOU/m5aN01pumYybBruziEXMI96yfTj
+VmXKReeYb6XUiCcs3fLSipD/+8/8CsjO4uMORtxWumXe8AbKZfysGFzL7wJlByGT
+38AGQwIG/XD8cKnaiEMX4E/3Owbcoxwixo3WZC0CgYEAovaqJ9mEU+Jc8h/TS7PG
+bGPjN1Z/1V6zrlcFUnw/Vvrwb3HvHglsN8cLCaW6df5lPjC6tq4tNX8+fPnbg0Ak
+zWc+vQzl3ygxKGdqgcyBEKIJiPETgcoN+GzL02V3d+oKY3f2YXlBqVSsvi6UgUL9
+U3zuB36/IQVyAhrbUZFxoGkCgYEAnaFAO+Nvrp/LhXwZyGuQf+rkmipGTIMpil5t
+QzjtNMV5JFszSWPpyrl7A0Ew1YiG+I0GP2c3m+sY2TzbIiGrWH0b4cMKbw63Qy3V
+FqlpyjaCrpVKv56k/7jv883RzuQk56Uf1+szK5mrCFITy2oXsVZ0pA4lbjSaDTjA
+7D968V0CgYEA+qKqXKL98+c5CMPnpf+0B1x2zgyUym1ouPfon2x5fhK84T53zDMA
+zfdUJ/SOZw6/c9vRF7RL8h+ZfFdIyoAXv4Tt6mIiZe7P+AUVg6XgJ0ce2MUSeWjI
+W8D4WdSi0jyqr99TuVBWhbTZJviMB3pHqKaHQ07hnd/lPtvzsiH12qk=
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-ip-cn-only.crt b/src/test/ssl/ssl/server-ip-cn-only.crt
new file mode 100644
index 0000000000..9bf015cf18
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-only.crt
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC8TCCAdkCCCAhESkRN1IAMA0GCSqGSIb3DQEBCwUAMEIxQDA+BgNVBAMMN1Rl
+c3QgQ0EgZm9yIFBvc3RncmVTUUwgU1NMIHJlZ3Jlc3Npb24gdGVzdCBzZXJ2ZXIg
+Y2VydHMwHhcNMjExMTI5MTkzNzUyWhcNNDkwNDE2MTkzNzUyWjA0MR4wHAYDVQQL
+DBVQb3N0Z3JlU1FMIHRlc3Qgc3VpdGUxEjAQBgNVBAMMCTE5Mi4wLjIuMTCCASIw
+DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANWs1uUL71nHYF9Zj6p+M3MpYDvx
+32iCjVdtH5a2qpSWHXTg0rR8dLX0y92cvOYvMXHRajZT1avpHr8dooPYSVaXpGMK
+NvF/Qi+WFYovRbP2vmd1yv1cgW/FggbwJFWVobizIz4seyA4d0B2j9fqoi2OFBNP
+huW664SjF0u3p21tDy+43i2LNUMAKf6dnRR5Vqenath87LEU41tSLudu6NXgbFMk
+jvfNkl4d0w7YCzeXmklmSI+uaX3PlJJ4NzQO2j8w5BvnKVhNVD0KjgrXZ6nB/8F7
+Pg3XY+d7rJlwRgXemU6resWQDJ7+UaC9u7I4EIP+9lzCR/nNBqUktpHRmHUCAwEA
+ATANBgkqhkiG9w0BAQsFAAOCAQEAos1JncV8Yf4UaKl6h1GdYtcVtzFyJvBEnhRD
+07ldL+TYnfZiX8wK2ssBtM3cg/C78y5bzdUa5XGS83ZKQJFFdhE7PSnrvyNqyIqY
+ZgNBxto3gyvir+EjO1u9BAB0NP3r3gYoHRDZS1xOPPzt4WgjuUgTLM9k82GsqAbO
+UrOTOdRnkIqC5xLpa05EnRyJPRsR1w1PRJC2XXKnHIuFjMb4v7UuPwyCcX1P5ioc
+rQszQcORy/L+k0ezCkyweORg68htjYbBHuwOuiGfok6yKKDMzrTvD3lIslls6eX7
+4sI3XWqzkPmG9Vsxm9Vu9/Ma+PRO76VyCoIwBd+Ufg5vNXhMmw==
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-ip-cn-only.key b/src/test/ssl/ssl/server-ip-cn-only.key
new file mode 100644
index 0000000000..1966530e72
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-only.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEA1azW5QvvWcdgX1mPqn4zcylgO/HfaIKNV20flraqlJYddODS
+tHx0tfTL3Zy85i8xcdFqNlPVq+kevx2ig9hJVpekYwo28X9CL5YVii9Fs/a+Z3XK
+/VyBb8WCBvAkVZWhuLMjPix7IDh3QHaP1+qiLY4UE0+G5brrhKMXS7enbW0PL7je
+LYs1QwAp/p2dFHlWp6dq2HzssRTjW1Iu527o1eBsUySO982SXh3TDtgLN5eaSWZI
+j65pfc+Ukng3NA7aPzDkG+cpWE1UPQqOCtdnqcH/wXs+Dddj53usmXBGBd6ZTqt6
+xZAMnv5RoL27sjgQg/72XMJH+c0GpSS2kdGYdQIDAQABAoIBAQDNXviU4WnF8rmQ
+K7bH+dBdqbETLKC8BG7xTrMD2sINWlMpmUUrsEtE7+paMGHnJAj0CoF5gg5m0wN4
+UXV4H5QtpEad4p14dAYbUreVP2ZRWKEdM7xM1HKcCUu2e22QzObJbXQ8N+iHyX3k
++Y+7yYrjGiH1hYR0nbnsnAyx++zyYBSQeqzpdQwf/BLY5xZmyYWNfqbckiMpEqMs
+EmZmGXnCjIipzEC0LQHoSW9PNa92Z9bvuxOKYl8iHYDDXjvMRFoZBSiMXpzHQocb
+QlQ5F4ayfW2OrOhpNbY7niYM9GN3Bk9TgMP+0BkJE6uuktLYW35LY1M78CCPWcWb
+npJNK3QBAoGBAOxkGrhAHAysSmtirIyMdvySb76wb/Ukfi+AULKz20FI5j4/GXm9
+qCb2GeT+FFSUHeSC8f0EFnosRYkdBGruqeZioI+5rUkboYFJPspAHAuvg9kgtfF+
+kvphD4O4P/foYsEZRx66FHozDbhrrR5UXc7KzqRIASc/D3FOx2UFJLb1AoGBAOdm
+WcaMvYygl9ZW+ThWAR1xG1X70AGKwrlrpF2hBkWYxSurxSMXnD0DUzC9Nb4EyCaM
+c2uSqEZOKdW+XfXtK2DnqXKfb3YCVEoGN4gVfyuW/vxii/+ZxLo3md/b3vrkZEVp
+pfkXy/HoZ71YN7bNpcDpOnhml6vvuCRCYFnI1WuBAoGAC0shB6pwbJ6Sk5zMN47C
+ZICufAK75o9OxAAyWsdC81SDQ3gKRImuDeZ2CD2nRP8qim9DFl5qoH2a+Nj9DArI
+7SvLFfK9958tURrpuAnmDRzehLIOXzI33WRjtFxKGhLtHOKTRkGHlur3fdcPF0La
+lHWV971E6NYXa8diuU3Mmj0CgYBYd+ka3/QYL83dRKNDxp3mg7fPx9ZewI5yFZVh
+to6PTTkU2Tclk4FIUl0b5TsGyw06r7fxCMENIBUegwmpXGOZSPifuhUDKSDQrE/O
+12knYTNbitG7hy6Pg3JxA77cbTVo1FuAQHjYo+IFohSq7zTP7FtObOrP8XaVZksw
+CHiQAQKBgBW4EiA9AAnZ1LOpifAvM7bs0NHg95qTwtAL52WKom2ga2H+lMhxeu6Y
+hUSytC/f9kALVcYloZhkLYpO07x1gXmy7f4parMjA4Ex+4vfu3kPd8GiNGZ+AUJD
+nnJ1OINY9ziXJZfju7FpVWpkiuPzWCh6y/o3gZ/veq5mIUxuDMVa
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/sslfiles.mk b/src/test/ssl/sslfiles.mk
index 270f55a58f..22ce3e1f97 100644
--- a/src/test/ssl/sslfiles.mk
+++ b/src/test/ssl/sslfiles.mk
@@ -22,9 +22,12 @@
 # key/certificate pair will be generated for you, signed by the appropriate CA.
 #
 SERVERS := server-cn-and-alt-names \
+	server-cn-and-ip-alt-names \
 	server-cn-only \
+	server-ip-cn-only \
 	server-single-alt-name \
 	server-multiple-alt-names \
+	server-ip-alt-names \
 	server-no-names \
 	server-revoked
 CLIENTS := client client-dn client-revoked client_ext
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 779ab66838..d88201f5e8 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -21,7 +21,7 @@ if ($ENV{with_ssl} ne 'openssl')
 }
 else
 {
-	plan tests => 110;
+	plan tests => 128;
 }
 
 #### Some configuration
@@ -253,6 +253,23 @@ $node->connect_fails(
 	  qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/
 );
 
+# Test with an IP address in the Common Name. This is a strange corner case that
+# nevertheless is supported, as long as the address string matches exactly.
+switch_server_cert($node, 'server-ip-cn-only');
+
+$common_connstr =
+  "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+
+$node->connect_ok("$common_connstr host=192.0.2.1",
+	"IP address in the Common Name");
+
+$node->connect_fails(
+	"$common_connstr host=192.000.002.001",
+	"mismatch between host name and server certificate IP address",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.1" does not match host name "192.000.002.001"\E/
+);
+
 # Test Subject Alternative Names.
 switch_server_cert($node, 'server-multiple-alt-names');
 
@@ -305,6 +322,53 @@ $node->connect_fails(
 	  qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/
 );
 
+# Test certificate with IP addresses in the SANs.
+switch_server_cert($node, 'server-ip-alt-names');
+
+$node->connect_ok(
+	"$common_connstr host=192.0.2.1",
+	"host matching an IPv4 address (Subject Alternative Name 1)");
+
+$node->connect_ok(
+	"$common_connstr host=192.000.002.001",
+	"host matching an IPv4 address in alternate form (Subject Alternative Name 1)");
+
+$node->connect_fails(
+	"$common_connstr host=192.0.2.2",
+	"host not matching an IPv4 address (Subject Alternative Name 1)",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.1" (and 1 other name) does not match host name "192.0.2.2"\E/
+);
+
+$node->connect_fails(
+	"$common_connstr host=192.0.2.1/32",
+	"IPv4 host with CIDR mask does not match",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.1" (and 1 other name) does not match host name "192.0.2.1\/32"\E/
+);
+
+$node->connect_ok(
+	"$common_connstr host=2001:DB8::1",
+	"host matching an IPv6 address (Subject Alternative Name 2)");
+
+$node->connect_ok(
+	"$common_connstr host=2001:db8:0:0:0:0:0:1",
+	"host matching an IPv6 address in alternate form (Subject Alternative Name 2)");
+
+$node->connect_fails(
+	"$common_connstr host=::1",
+	"host not matching an IPv6 address (Subject Alternative Name 2)",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.1" (and 1 other name) does not match host name "::1"\E/
+);
+
+$node->connect_fails(
+	"$common_connstr host=2001:DB8::1/128",
+	"IPv6 host with CIDR mask does not match",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.1" (and 1 other name) does not match host name "2001:DB8::1\/128"\E/
+);
+
 # Test server certificate with a CN and SANs. Per RFCs 2818 and 6125, the CN
 # should be ignored when the certificate has both.
 switch_server_cert($node, 'server-cn-and-alt-names');
@@ -323,6 +387,17 @@ $node->connect_fails(
 	  qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/
 );
 
+# But we will fall back to check the CN if the SANs contain only IP addresses.
+# TODO: should we?
+switch_server_cert($node, 'server-cn-and-ip-alt-names');
+
+$node->connect_ok("$common_connstr host=common-name.pg-ssltest.test",
+	"certificate with both a CN and IP SANs matches CN");
+$node->connect_ok("$common_connstr host=192.0.2.1",
+	"certificate with both a CN and IP SANs matches SAN 1");
+$node->connect_ok("$common_connstr host=2001:db8::1",
+	"certificate with both a CN and IP SANs matches SAN 2");
+
 # Finally, test a server certificate that has no CN or SANs. Of course, that's
 # not a very sensible certificate, but libpq should handle it gracefully.
 switch_server_cert($node, 'server-no-names');
-- 
2.25.1

#2Kyotaro Horiguchi
horikyota.ntt@gmail.com
In reply to: Jacob Champion (#1)
Re: [PATCH] Accept IP addresses in server certificate SANs

At Thu, 16 Dec 2021 01:13:57 +0000, Jacob Champion <pchampion@vmware.com> wrote in

This patch arose because I was writing tests for the NSS implementation
that used a server cert with both DNS names and IP addresses, and then
they failed when I ran those tests against the OpenSSL implementation.
NSS supports this functionality natively. Anecdotally, I've heard from
at least one client group who is utilizing IP-based certificates in
their cloud deployments. It seems uncommon but still useful.

There are two open questions I have; they're based on NSS
implementation details that I did not port here:

- NSS allows an IPv4 SAN to match an IPv6 mapping of that same address,
and vice-versa. I chose not to implement that behavior, figuring it
is easy enough for people to issue a certificate with both addresses.
Is that okay?

- If a certificate contains only iPAddress SANs, and none of them
match, I fall back to check the certificate Common Name. OpenSSL will
not do this (its X509_check_ip considers only SANs). NSS will only do
this if the client's address is itself a DNS name. The spec says that
we can't fall back to Common Name if the SANs contain any DNS
entries, but it's silent on the subject of IP addresses. What should
the behavior be?

The patchset roadmap:

- 0001 moves inet_net_pton() to src/port, since libpq will need it.
- 0002 implements the new functionality and adds tests.

WDYT?

In RFC2828 and 6125,

In some cases, the URI is specified as an IP address rather than a
hostname. In this case, the iPAddress subjectAltName must be present
in the certificate and must exactly match the IP in the URI.

It seems like saying that we must search for iPAddress and mustn't use
CN nor dNSName if the client connected using IP address. Otherwise, if
the host name is a domain name, we use only dNSName if present, and
use CN otherwise. That behavior seems agreeing to what you wrote as
NSS's behavior. That being said it seems to me we should preserve
that behavior at least for OpenSSL as an established behavior.

In short, I think the current behavior of the patch is the direction
we would go but some documentation is may be needed.

I'm not sure about ipv4 comptible addresses. However, I think we can
identify ipv4 compatible address easily.

+		 * pg_inet_net_pton() will accept CIDR masks, which we don't want to
+		 * match, so skip the comparison if the host string contains a slash.
+		 */
+		if (!strchr(host, '/')
+			&& pg_inet_net_pton(PGSQL_AF_INET6, host, addr, -1) == 128)

If a cidr is given, pg_inet_net_pton returns a number less than 128 so
we don't need to check '/' explicity? (I'm not sure '/128' is
sensible but doesn't harm..)

regards.

--
Kyotaro Horiguchi
NTT Open Source Software Center

#3Andrew Dunstan
andrew@dunslane.net
In reply to: Jacob Champion (#1)
Re: [PATCH] Accept IP addresses in server certificate SANs

On 12/15/21 20:13, Jacob Champion wrote:

Hello all,

libpq currently supports server certificates with a single IP address
in the Common Name. It's fairly brittle; as far as I can tell, the
single name you choose has to match the client's address exactly.

Attached is a patch for libpq to support IP addresses in the server's
Subject Alternative Names, which would allow admins to issue certs for
multiple IP addresses, both IPv4 and IPv6, and mix them with
alternative DNS hostnames. These addresses are compared bytewise
instead of stringwise, so the client can contact the server via
alternative spellings of the same IP address.

Good job, this is certainly going to be useful.

This patch arose because I was writing tests for the NSS implementation
that used a server cert with both DNS names and IP addresses, and then
they failed when I ran those tests against the OpenSSL implementation.
NSS supports this functionality natively. Anecdotally, I've heard from
at least one client group who is utilizing IP-based certificates in
their cloud deployments. It seems uncommon but still useful.

There are two open questions I have; they're based on NSS
implementation details that I did not port here:

- NSS allows an IPv4 SAN to match an IPv6 mapping of that same address,
and vice-versa. I chose not to implement that behavior, figuring it
is easy enough for people to issue a certificate with both addresses.
Is that okay?

Sure.

- If a certificate contains only iPAddress SANs, and none of them
match, I fall back to check the certificate Common Name. OpenSSL will
not do this (its X509_check_ip considers only SANs). NSS will only do
this if the client's address is itself a DNS name. The spec says that
we can't fall back to Common Name if the SANs contain any DNS
entries, but it's silent on the subject of IP addresses. What should
the behavior be?

I don't think we should fall back on the CN. It would seem quite odd to
do so for IP addresses but not for DNS names.

cheers

andrew

--
Andrew Dunstan
EDB: https://www.enterprisedb.com

#4Jacob Champion
pchampion@vmware.com
In reply to: Kyotaro Horiguchi (#2)
Re: [PATCH] Accept IP addresses in server certificate SANs

On Thu, 2021-12-16 at 14:54 +0900, Kyotaro Horiguchi wrote:

In RFC2828 and 6125,

In some cases, the URI is specified as an IP address rather than a
hostname. In this case, the iPAddress subjectAltName must be present
in the certificate and must exactly match the IP in the URI.

Ah, right, I misremembered. Disregard my statement that the spec is
"silent on the subject", sorry.

It seems like saying that we must search for iPAddress and mustn't use
CN nor dNSName if the client connected using IP address. Otherwise, if
the host name is a domain name, we use only dNSName if present, and
use CN otherwise. That behavior seems agreeing to what you wrote as
NSS's behavior.

NSS departs slightly from the spec and will additionally try to match
an IP address against the CN, but only if there are no iPAddresses in
the SAN. It roughly matches the logic for DNS names.

Here's the description of the NSS behavior and some of the reasoning
behind it, quoted from a developer on Bugzilla [1]https://bugzilla.mozilla.org/show_bug.cgi?id=103752:

Elsewhere in RFC 2818, it says

If a subjectAltName extension of type dNSName is present, that MUST
be used as the identity. Otherwise, the (most specific) Common Name
field in the Subject field of the certificate MUST be used.

Notice that this section is not conditioned upon the URI being a hostname
and not an IP address. So this statement conflicts with the one cited
above.

I implemented this policy:

if the URI contains a host name
if the subject alt name is present and has one or more DNS names
use the DNS names in that extension as the server identity
else
use the subject common name as the server identity
else if the URI contains an IP address
if the subject alt name is present and has one or more IP addresses
use the IP addresses in that extension as the server identity
else
compare the URI IP address string with the subject common name.

It sounds like both you and Andrew might be comfortable with that same
behavior? I think it looks like a sane solution, so I'll implement that
and we can see what it looks like. (My work on this will be paused over
the end-of-year holidays.)

That being said it seems to me we should preserve
that behavior at least for OpenSSL as an established behavior.

That part is interesting. I'll talk more about that in my reply to
Andrew.

In short, I think the current behavior of the patch is the direction
we would go but some documentation is may be needed.

Great!

I'm not sure about ipv4 comptible addresses. However, I think we can
identify ipv4 compatible address easily.

Yeah, it would probably not be a difficult feature to add later.

+                * pg_inet_net_pton() will accept CIDR masks, which we don't want to
+                * match, so skip the comparison if the host string contains a slash.
+                */
+               if (!strchr(host, '/')
+                       && pg_inet_net_pton(PGSQL_AF_INET6, host, addr, -1) == 128)

If a cidr is given, pg_inet_net_pton returns a number less than 128 so
we don't need to check '/' explicity? (I'm not sure '/128' is
sensible but doesn't harm..)

Personally I think that, if someone wants your libpq to connect to a
server with a hostname of "some:ipv6::address/128", then they are
trying to pull something (evading a poorly coded blocklist, perhaps?)
and we should not allow that to match an IP. Thoughts?

Thanks for the review!
--Jacob

[1]: https://bugzilla.mozilla.org/show_bug.cgi?id=103752

#5Jacob Champion
pchampion@vmware.com
In reply to: Andrew Dunstan (#3)
Re: [PATCH] Accept IP addresses in server certificate SANs

On Thu, 2021-12-16 at 10:50 -0500, Andrew Dunstan wrote:

Good job, this is certainly going to be useful.

Thanks!

I don't think we should fall back on the CN. It would seem quite odd to
do so for IP addresses but not for DNS names.

So there's at least one compatibility concern with disabling the
fallback, in that there could be existing users that are happily using
a certificate with an IP address CN, and libpq is just ignoring any
iPAddress SANs that the certificate has. Once libpq becomes aware of
those, it will stop accepting the CN and the certificate might stop
working.

Personally I think that's acceptable, but it would probably warrant a
release note or some such.

I will work on implementing behavior that's modeled off of the NSS
matching logic (see my reply to Horiguchi-san), which will at least
make it more logically consistent, and we can see what that looks like?

Thanks for the review!
--Jacob

#6Kyotaro Horiguchi
horikyota.ntt@gmail.com
In reply to: Jacob Champion (#4)
Re: [PATCH] Accept IP addresses in server certificate SANs

At Thu, 16 Dec 2021 18:44:54 +0000, Jacob Champion <pchampion@vmware.com> wrote in

On Thu, 2021-12-16 at 14:54 +0900, Kyotaro Horiguchi wrote:

It seems like saying that we must search for iPAddress and mustn't use
CN nor dNSName if the client connected using IP address. Otherwise, if
the host name is a domain name, we use only dNSName if present, and
use CN otherwise. That behavior seems agreeing to what you wrote as
NSS's behavior.

NSS departs slightly from the spec and will additionally try to match
an IP address against the CN, but only if there are no iPAddresses in
the SAN. It roughly matches the logic for DNS names.

OpenSSL seems different. X509_check_host() tries SAN then CN iff SAN
doesn't exist. X509_check_ip() tries SAN and completely ignores
iPAdress and CN.

Here's the description of the NSS behavior and some of the reasoning
behind it, quoted from a developer on Bugzilla [1]:

Elsewhere in RFC 2818, it says

If a subjectAltName extension of type dNSName is present, that MUST
be used as the identity. Otherwise, the (most specific) Common Name
field in the Subject field of the certificate MUST be used.

Notice that this section is not conditioned upon the URI being a hostname
and not an IP address. So this statement conflicts with the one cited
above.

I implemented this policy:

if the URI contains a host name
if the subject alt name is present and has one or more DNS names
use the DNS names in that extension as the server identity
else
use the subject common name as the server identity
else if the URI contains an IP address
if the subject alt name is present and has one or more IP addresses
use the IP addresses in that extension as the server identity
else
compare the URI IP address string with the subject common name.

(Wow. The article is 20-years old.)

*I* am fine with it.

It sounds like both you and Andrew might be comfortable with that same
behavior? I think it looks like a sane solution, so I'll implement that
and we can see what it looks like. (My work on this will be paused over
the end-of-year holidays.)

I'm not sure about ipv4 comptible addresses. However, I think we can
identify ipv4 compatible address easily.

Yeah, it would probably not be a difficult feature to add later.

I agree.

+                * pg_inet_net_pton() will accept CIDR masks, which we don't want to
+                * match, so skip the comparison if the host string contains a slash.
+                */
+               if (!strchr(host, '/')
+                       && pg_inet_net_pton(PGSQL_AF_INET6, host, addr, -1) == 128)

If a cidr is given, pg_inet_net_pton returns a number less than 128 so
we don't need to check '/' explicity? (I'm not sure '/128' is
sensible but doesn't harm..)

Personally I think that, if someone wants your libpq to connect to a
server with a hostname of "some:ipv6::address/128", then they are
trying to pull something (evading a poorly coded blocklist, perhaps?)
and we should not allow that to match an IP. Thoughts?

If the client could connect to the network-address, it could be said
that we can assume that address is the name:p Just kidding.

As the name suggests, the function reads a network address. And the
only user is network_in(). I think we should provide pg_inet_pton()
instead of abusing pg_inet_net_pton(). inet_net_pton_*() functions
can be modified to reject /cidr part without regression so we are able
to have pg_inet_pton() with a small amount of change.

- inet_net_pton_ipv4(const char *src, u_char *dst)
+ inet_net_pton_ipv4_internal(const char *src, u_char *dst, bool netaddr)
+ inet_net_pton_ipv4(const char *src, u_char *dst)
 (calls inet_net_pton_ipv4_internal(src, dst, true))
+ inet_pton_ipv4(const char *src, u_char *dst)
 (calls inet_net_pton_ipv4_internal(src, dst, false))

Thanks for the review!
--Jacob

[1] https://bugzilla.mozilla.org/show_bug.cgi?id=103752

--
Kyotaro Horiguchi
NTT Open Source Software Center

#7Kyotaro Horiguchi
horikyota.ntt@gmail.com
In reply to: Kyotaro Horiguchi (#6)
Re: [PATCH] Accept IP addresses in server certificate SANs

Sorry for the silly mistake.

At Fri, 17 Dec 2021 15:40:10 +0900 (JST), Kyotaro Horiguchi <horikyota.ntt@gmail.com> wrote in

NSS departs slightly from the spec and will additionally try to match
an IP address against the CN, but only if there are no iPAddresses in
the SAN. It roughly matches the logic for DNS names.

OpenSSL seems different. X509_check_host() tries SAN then CN iff SAN
doesn't exist. X509_check_ip() tries SAN and completely ignores
iPAdress and CN.

OpenSSL seems different. X509_check_host() tries SAN then CN iff SAN
doesn't exist. X509_check_ip() tries iPAddress and completely ignores
CN.

regards.

--
Kyotaro Horiguchi
NTT Open Source Software Center

#8Andres Freund
andres@anarazel.de
In reply to: Jacob Champion (#1)
Re: [PATCH] Accept IP addresses in server certificate SANs

Hi,

On 2021-12-16 01:13:57 +0000, Jacob Champion wrote:

Attached is a patch for libpq to support IP addresses in the server's
Subject Alternative Names, which would allow admins to issue certs for
multiple IP addresses, both IPv4 and IPv6, and mix them with
alternative DNS hostnames. These addresses are compared bytewise
instead of stringwise, so the client can contact the server via
alternative spellings of the same IP address.

This fails to build on windows:
https://cirrus-ci.com/task/6734650927218688?logs=build#L1029

[14:33:28.277] network.obj : error LNK2019: unresolved external symbol pg_inet_net_pton referenced in function network_in [c:\cirrus\postgres.vcxproj]

Greetings,

Andres Freund

#9Jacob Champion
pchampion@vmware.com
In reply to: Kyotaro Horiguchi (#7)
Re: [PATCH] Accept IP addresses in server certificate SANs

On Fri, 2021-12-17 at 16:54 +0900, Kyotaro Horiguchi wrote:

Sorry for the silly mistake.

At Fri, 17 Dec 2021 15:40:10 +0900 (JST), Kyotaro Horiguchi <horikyota.ntt@gmail.com> wrote in

NSS departs slightly from the spec and will additionally try to match
an IP address against the CN, but only if there are no iPAddresses in
the SAN. It roughly matches the logic for DNS names.

OpenSSL seems different. X509_check_host() tries SAN then CN iff SAN
doesn't exist. X509_check_ip() tries SAN and completely ignores
iPAdress and CN.

OpenSSL seems different. X509_check_host() tries SAN then CN iff SAN
doesn't exist. X509_check_ip() tries iPAddress and completely ignores
CN.

Right.

On Fri, 2021-12-17 at 15:40 +0900, Kyotaro Horiguchi wrote:

+ * pg_inet_net_pton() will accept CIDR masks, which we don't want to

+                * match, so skip the comparison if the host string contains a slash.
+                */
+               if (!strchr(host, '/')
+                       && pg_inet_net_pton(PGSQL_AF_INET6, host, addr, -1) == 128)

If a cidr is given, pg_inet_net_pton returns a number less than 128 so
we don't need to check '/' explicity? (I'm not sure '/128' is
sensible but doesn't harm..)

Personally I think that, if someone wants your libpq to connect to a
server with a hostname of "some:ipv6::address/128", then they are
trying to pull something (evading a poorly coded blocklist, perhaps?)
and we should not allow that to match an IP. Thoughts?

If the client could connect to the network-address, it could be said
that we can assume that address is the name:p Just kidding.

As the name suggests, the function reads a network address. And the
only user is network_in(). I think we should provide pg_inet_pton()
instead of abusing pg_inet_net_pton(). inet_net_pton_*() functions
can be modified to reject /cidr part without regression so we are able
to have pg_inet_pton() with a small amount of change.

- inet_net_pton_ipv4(const char *src, u_char *dst)
+ inet_net_pton_ipv4_internal(const char *src, u_char *dst, bool netaddr)
+ inet_net_pton_ipv4(const char *src, u_char *dst)
(calls inet_net_pton_ipv4_internal(src, dst, true))
+ inet_pton_ipv4(const char *src, u_char *dst)
(calls inet_net_pton_ipv4_internal(src, dst, false))

Sounds good, I will make that change. Thanks for the feedback!

--Jacob

#10Jacob Champion
pchampion@vmware.com
In reply to: Andres Freund (#8)
Re: [PATCH] Accept IP addresses in server certificate SANs

On Sun, 2022-01-02 at 13:29 -0800, Andres Freund wrote:

Hi,

On 2021-12-16 01:13:57 +0000, Jacob Champion wrote:

Attached is a patch for libpq to support IP addresses in the server's
Subject Alternative Names, which would allow admins to issue certs for
multiple IP addresses, both IPv4 and IPv6, and mix them with
alternative DNS hostnames. These addresses are compared bytewise
instead of stringwise, so the client can contact the server via
alternative spellings of the same IP address.

This fails to build on windows:
https://nam04.safelinks.protection.outlook.com/?url=https%3A%2F%2Fcirrus-ci.com%2Ftask%2F6734650927218688%3Flogs%3Dbuild%23L1029&amp;amp;data=04%7C01%7Cpchampion%40vmware.com%7C2b2171168f3c4935e89f08d9ce36f790%7Cb39138ca3cee4b4aa4d6cd83d9dd62f0%7C0%7C0%7C637767557770534489%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000&amp;amp;sdata=JtfsPtershSljU1oDGrkL8bQiHYB3iMfUgTqlh%2B4wbs%3D&amp;amp;reserved=0

[14:33:28.277] network.obj : error LNK2019: unresolved external symbol pg_inet_net_pton referenced in function network_in [c:\cirrus\postgres.vcxproj]

Thanks for the heads up; I'll fix that while I'm implementing the
internal API.

--Jacob

#11Jacob Champion
pchampion@vmware.com
In reply to: Jacob Champion (#4)
3 attachment(s)
Re: [PATCH] Accept IP addresses in server certificate SANs

On Thu, 2021-12-16 at 18:44 +0000, Jacob Champion wrote:

It sounds like both you and Andrew might be comfortable with that same
behavior? I think it looks like a sane solution, so I'll implement that
and we can see what it looks like. (My work on this will be paused over
the end-of-year holidays.)

v2 implements the discussed CN/SAN fallback behavior and should fix the
build on Windows. Still TODO is the internal pg_inet_pton() refactoring
that you asked for; I'm still deciding how best to approach it.

Changes only in since-v1.diff.txt.

Thanks,
--Jacob

Attachments:

since-v1.diff.txttext/plain; name=since-v1.diff.txtDownload
diff --git a/src/interfaces/libpq/fe-secure-common.c b/src/interfaces/libpq/fe-secure-common.c
index 38ee65abf2..796b78e348 100644
--- a/src/interfaces/libpq/fe-secure-common.c
+++ b/src/interfaces/libpq/fe-secure-common.c
@@ -28,14 +28,6 @@
 #include "port.h"
 #include "pqexpbuffer.h"
 
-/*
- * In a frontend build, we can't include inet.h, but we still need to have
- * sensible definitions of these two constants.  Note that pg_inet_net_ntop()
- * assumes that PGSQL_AF_INET is equal to AF_INET.
- */
-#define PGSQL_AF_INET	(AF_INET + 0)
-#define PGSQL_AF_INET6	(AF_INET + 1)
-
 /*
  * Check if a wildcard certificate matches the server hostname.
  *
diff --git a/src/interfaces/libpq/fe-secure-common.h b/src/interfaces/libpq/fe-secure-common.h
index a090a92f60..29d4f54230 100644
--- a/src/interfaces/libpq/fe-secure-common.h
+++ b/src/interfaces/libpq/fe-secure-common.h
@@ -18,6 +18,14 @@
 
 #include "libpq-fe.h"
 
+/*
+ * In a frontend build, we can't include inet.h, but we still need to have
+ * sensible definitions of these two constants.  Note that pg_inet_net_ntop()
+ * assumes that PGSQL_AF_INET is equal to AF_INET.
+ */
+#define PGSQL_AF_INET	(AF_INET + 0)
+#define PGSQL_AF_INET6	(AF_INET + 1)
+
 extern int	pq_verify_peer_name_matches_certificate_name(PGconn *conn,
 														 const char *namedata, size_t namelen,
 														 char **store_name);
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index 16c5ff9223..479f63197a 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -548,6 +548,16 @@ openssl_verify_peer_name_matches_certificate_ip(PGconn *conn,
 	return pq_verify_peer_name_matches_certificate_ip(conn, (const char *) addrdata, len, store_name);
 }
 
+static bool
+is_ip_address(const char *host)
+{
+	struct in_addr	dummy4;
+	unsigned char	dummy6[16];
+
+	return inet_aton(host, &dummy4)
+		|| (pg_inet_net_pton(PGSQL_AF_INET6, host, dummy6, -1) == 128);
+}
+
 /*
  *	Verify that the server certificate matches the hostname we connected to.
  *
@@ -561,7 +571,36 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 	STACK_OF(GENERAL_NAME) * peer_san;
 	int			i;
 	int			rc = 0;
-	bool		has_dnsname = false;
+	char	   *host = conn->connhost[conn->whichhost].host;
+	int			host_type;
+	bool		check_cn = true;
+
+	Assert(host && host[0]); /* should be guaranteed by caller */
+
+	/*
+	 * We try to match the NSS behavior here, which is a slight departure from
+	 * the spec but seems to make more intuitive sense:
+	 *
+	 * If connhost contains a DNS name, and the certificate's SANs contain any
+	 * dNSName entries, then we'll ignore the Subject Common Name entirely;
+	 * otherwise, we fall back to checking the CN. (This behavior matches the
+	 * RFC.)
+	 *
+	 * If connhost contains an IP address, and the SANs contain iPAddress
+	 * entries, we again ignore the CN. Otherwise, we allow the CN to match,
+	 * EVEN IF there is a dNSName in the SANs. (RFC 6125 prohibits this: "A
+	 * client MUST NOT seek a match for a reference identifier of CN-ID if the
+	 * presented identifiers include a DNS-ID, SRV-ID, URI-ID, or any
+	 * application-specific identifier types supported by the client.")
+	 *
+	 * NOTE: Prior versions of libpq did not consider iPAddress entries at all,
+	 * so this new behavior might break a certificate that has different IP
+	 * addresses in the Subject CN and the SANs.
+	 */
+	if (is_ip_address(host))
+		host_type = GEN_IPADD;
+	else
+		host_type = GEN_DNS;
 
 	/*
 	 * First, get the Subject Alternative Names (SANs) from the certificate,
@@ -579,9 +618,11 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 			const GENERAL_NAME *name = sk_GENERAL_NAME_value(peer_san, i);
 			char	   *alt_name = NULL;
 
+			if (name->type == host_type)
+				check_cn = false;
+
 			if (name->type == GEN_DNS)
 			{
-				has_dnsname = true;
 				(*names_examined)++;
 				rc = openssl_verify_peer_name_matches_certificate_name(conn,
 																	   name->d.dNSName,
@@ -610,18 +651,14 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 	}
 
 	/*
-	 * If there is no subjectAltName extension of type dNSName, check the
+	 * If there is no subjectAltName extension of the matching type, check the
 	 * Common Name.
 	 *
 	 * (Per RFC 2818 and RFC 6125, if the subjectAltName extension of type
-	 * dNSName is present, the CN must be ignored.)
-	 *
-	 * TODO: we fall back to checking the CN if an iPAddress exists. NSS does
-	 * not, if the client's supplied host is itself an IP address. OpenSSL's
-	 * X509_check_ip() does not, because it doesn't ever consider the CN. Should
-	 * we?
+	 * dNSName is present, the CN must be ignored. We break this rule if host is
+	 * an IP address; see the comment above.)
 	 */
-	if ((rc == 0) && !has_dnsname)
+	if ((rc == 0) && check_cn)
 	{
 		X509_NAME  *subject_name;
 
diff --git a/src/test/ssl/conf/server-ip-cn-and-alt-names.config b/src/test/ssl/conf/server-ip-cn-and-alt-names.config
new file mode 100644
index 0000000000..a4087f0a18
--- /dev/null
+++ b/src/test/ssl/conf/server-ip-cn-and-alt-names.config
@@ -0,0 +1,21 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+# This certificate contains both a CN and SANs in IP address format.
+
+
+[ req ]
+distinguished_name     = req_distinguished_name
+req_extensions         = v3_req
+prompt                 = no
+
+[ req_distinguished_name ]
+CN = 192.0.2.1
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
+subjectAltName = @alt_names
+
+[ alt_names ]
+IP.1 = 192.0.2.2
+IP.2 = 2001:DB8::1
diff --git a/src/test/ssl/conf/server-ip-cn-and-dns-alt-names.config b/src/test/ssl/conf/server-ip-cn-and-dns-alt-names.config
new file mode 100644
index 0000000000..7121803b49
--- /dev/null
+++ b/src/test/ssl/conf/server-ip-cn-and-dns-alt-names.config
@@ -0,0 +1,21 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+# This certificate contains both a CN and SANs in IP address format.
+
+
+[ req ]
+distinguished_name     = req_distinguished_name
+req_extensions         = v3_req
+prompt                 = no
+
+[ req_distinguished_name ]
+CN = 192.0.2.1
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
+subjectAltName = @alt_names
+
+[ alt_names ]
+DNS.1 = dns1.alt-name.pg-ssltest.test
+DNS.2 = dns2.alt-name.pg-ssltest.test
diff --git a/src/test/ssl/ssl/server-ip-cn-and-alt-names.crt b/src/test/ssl/ssl/server-ip-cn-and-alt-names.crt
new file mode 100644
index 0000000000..2be02feb03
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-and-alt-names.crt
@@ -0,0 +1,71 @@
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 2315414275452449024 (0x2022010414363100)
+        Signature Algorithm: sha256WithRSAEncryption
+        Issuer: CN = Test CA for PostgreSQL SSL regression test server certs
+        Validity
+            Not Before: Jan  4 22:36:31 2022 GMT
+            Not After : May 22 22:36:31 2049 GMT
+        Subject: OU = PostgreSQL test suite, CN = 192.0.2.1
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                RSA Public-Key: (2048 bit)
+                Modulus:
+                    00:c2:6a:93:75:02:5f:b3:64:e2:f6:d3:e2:b5:8a:
+                    76:db:b2:34:1e:f2:ff:90:d4:a0:03:a7:a0:16:bd:
+                    1f:a1:1a:34:06:8a:f0:24:d2:33:75:b5:74:f8:49:
+                    ec:7e:20:4d:4d:62:f9:22:dd:ba:82:a3:bb:50:fd:
+                    e6:b3:cb:76:bc:70:f9:82:45:df:4f:e7:fa:b6:cd:
+                    25:54:91:1c:20:e9:14:0f:ff:30:b3:82:1c:f5:8e:
+                    2e:3e:a6:f8:80:df:a9:50:aa:9c:c6:d9:8b:5b:54:
+                    d8:93:ce:0c:04:ae:7d:5f:3e:32:3c:f4:ba:37:e1:
+                    bf:0c:c3:1e:14:74:cd:90:cf:44:43:09:ff:f8:30:
+                    b7:7c:3b:16:74:66:0c:d8:64:56:0d:32:46:83:50:
+                    39:b5:25:1c:17:eb:1e:bb:b8:b5:56:0e:d7:6a:30:
+                    5f:b2:f8:00:50:0b:2b:03:15:7e:5f:fb:0b:66:1f:
+                    78:1d:8f:a9:68:3e:b0:7d:a2:3d:f6:66:36:39:75:
+                    5c:fd:75:bf:cf:5a:fd:59:0c:e7:a2:be:b9:66:4a:
+                    27:34:26:8f:7d:ab:ea:3c:6e:6f:aa:76:9b:f4:0c:
+                    90:72:a3:ea:99:7f:21:7f:fc:6b:9e:20:12:04:09:
+                    2a:34:27:2d:6c:04:ab:14:22:18:bc:22:5f:19:f9:
+                    97:11
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Subject Alternative Name: 
+                IP Address:192.0.2.2, IP Address:2001:DB8:0:0:0:0:0:1
+    Signature Algorithm: sha256WithRSAEncryption
+         5f:ee:49:98:7d:16:1f:9d:69:35:39:47:d8:dc:dd:64:68:d8:
+         3d:a6:20:41:94:5a:fd:83:e3:90:9f:d2:94:ce:2a:e4:37:bb:
+         34:65:06:c2:1b:15:75:b8:94:01:29:2e:7c:37:21:38:d7:85:
+         68:ad:ab:bb:ef:7e:fd:7a:68:18:0d:c0:81:a4:32:18:a6:42:
+         e2:36:e8:d5:ac:83:ab:82:04:45:2a:c1:51:83:1b:b8:fe:6c:
+         34:05:28:02:71:5f:11:3d:ef:52:58:76:7d:d0:ca:2c:ed:33:
+         02:9d:6f:cf:77:13:82:0f:5c:03:d1:83:24:72:cd:2b:c0:1d:
+         e5:d0:ac:dc:ee:30:e7:7c:e1:2f:9a:0c:3f:62:c9:bb:bf:ae:
+         e9:b3:ed:39:52:ae:55:b2:44:3a:5a:ba:52:03:0e:a4:3c:3f:
+         d0:32:eb:81:01:7f:00:4c:f7:33:21:aa:31:cc:03:32:9d:ca:
+         b5:22:24:88:c0:6f:77:7f:71:28:41:04:e1:75:0e:f4:0b:a1:
+         bd:bc:b7:2e:f5:3b:4b:e8:90:2c:10:c1:44:cd:1f:7d:82:dd:
+         56:b2:ab:e6:82:5f:56:91:2b:73:8f:5c:a3:21:67:a8:e6:02:
+         2c:6b:82:60:72:47:b5:95:9b:e2:5b:2a:d3:c4:70:df:ca:e9:
+         d8:13:98:b9
+-----BEGIN CERTIFICATE-----
+MIIDHTCCAgWgAwIBAgIIICIBBBQ2MQAwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UE
+Aww3VGVzdCBDQSBmb3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHNl
+cnZlciBjZXJ0czAeFw0yMjAxMDQyMjM2MzFaFw00OTA1MjIyMjM2MzFaMDQxHjAc
+BgNVBAsMFVBvc3RncmVTUUwgdGVzdCBzdWl0ZTESMBAGA1UEAwwJMTkyLjAuMi4x
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwmqTdQJfs2Ti9tPitYp2
+27I0HvL/kNSgA6egFr0foRo0BorwJNIzdbV0+EnsfiBNTWL5It26gqO7UP3ms8t2
+vHD5gkXfT+f6ts0lVJEcIOkUD/8ws4Ic9Y4uPqb4gN+pUKqcxtmLW1TYk84MBK59
+Xz4yPPS6N+G/DMMeFHTNkM9EQwn/+DC3fDsWdGYM2GRWDTJGg1A5tSUcF+seu7i1
+Vg7XajBfsvgAUAsrAxV+X/sLZh94HY+paD6wfaI99mY2OXVc/XW/z1r9WQznor65
+ZkonNCaPfavqPG5vqnab9AyQcqPqmX8hf/xrniASBAkqNCctbASrFCIYvCJfGfmX
+EQIDAQABoyUwIzAhBgNVHREEGjAYhwTAAAIChxAgAQ24AAAAAAAAAAAAAAABMA0G
+CSqGSIb3DQEBCwUAA4IBAQBf7kmYfRYfnWk1OUfY3N1kaNg9piBBlFr9g+OQn9KU
+zirkN7s0ZQbCGxV1uJQBKS58NyE414Vorau77379emgYDcCBpDIYpkLiNujVrIOr
+ggRFKsFRgxu4/mw0BSgCcV8RPe9SWHZ90Mos7TMCnW/PdxOCD1wD0YMkcs0rwB3l
+0Kzc7jDnfOEvmgw/Ysm7v67ps+05Uq5VskQ6WrpSAw6kPD/QMuuBAX8ATPczIaox
+zAMyncq1IiSIwG93f3EoQQThdQ70C6G9vLcu9TtL6JAsEMFEzR99gt1Wsqvmgl9W
+kStzj1yjIWeo5gIsa4Jgcke1lZviWyrTxHDfyunYE5i5
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-ip-cn-and-alt-names.key b/src/test/ssl/ssl/server-ip-cn-and-alt-names.key
new file mode 100644
index 0000000000..54fe80fc68
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-and-alt-names.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAwmqTdQJfs2Ti9tPitYp227I0HvL/kNSgA6egFr0foRo0Borw
+JNIzdbV0+EnsfiBNTWL5It26gqO7UP3ms8t2vHD5gkXfT+f6ts0lVJEcIOkUD/8w
+s4Ic9Y4uPqb4gN+pUKqcxtmLW1TYk84MBK59Xz4yPPS6N+G/DMMeFHTNkM9EQwn/
++DC3fDsWdGYM2GRWDTJGg1A5tSUcF+seu7i1Vg7XajBfsvgAUAsrAxV+X/sLZh94
+HY+paD6wfaI99mY2OXVc/XW/z1r9WQznor65ZkonNCaPfavqPG5vqnab9AyQcqPq
+mX8hf/xrniASBAkqNCctbASrFCIYvCJfGfmXEQIDAQABAoIBAB6GgVST5NbT9lbu
++d+rN/JSzqA1Yy8oU19/iEFJvJec96I3WnFNl8rZjN4XLUy4YarO6XMyAUDV2Gll
+FD4Sqjf4PRTZR7DSKaleGIhoqFP6hK3mUY091rIves9XhBkoBPunbipCqgDTF5ZN
+edGaXBECQP0VJ8/yX/7u++AWXthnjDis9X0taZfFg/PYbV7SCJ1Hg1O/wEsgXlnC
+7mbL6wkCW0f6700B0x1kKbZqJY95xRqp6Ipq2lIQbJDdGywoj0WzKqNltf9cer+r
+cXl8WjeiMvvvpl4uGhckAbzUifUzxN6A3f1fu/XKtOmabMi9t7J4MRfgOgedgtQB
+0jaZGSkCgYEA+lBLnNY6M48HX2mdtr86+n41gh69v8Z7oNikJFDZkodrvI8uqE0i
+0XwnYPFddt8NbmuUhhuzI2M8RKhGLgdlbKpkSSVafnMfcxRmX2EAtWQgdvX1Iult
+752LWdBgSuw2vlzvy3T/GYnjMrXSCGput4amqojMEbvUGvIdSUMdHGMCgYEAxtU1
+WixKPL6aEnYy1f4bybzcNgGtl8PBRz9xw+P46g+ijOPoaG9O73Tr7An11AO003Ot
+DHhMW+b8yHLyxoKwS2sU2cN/lKB8xNQYZc1D61RNJlzgnHMXnA0lcH0I3M35fqKr
+/71pD1ZP40SSJS+od/KEjW80XzuOdyiXg8q81vsCgYEAnUPLbbsuj+whzrFVlFZr
+IKwgxCK6Rn3WeIUEA4kEWUpZxvsSbk0gPgtJ1l9uwFt9Xc2bX/KRRv93Aw/SH+Mn
+tvEK1uXwCBgePzgm5W/VeSFyQCthm1CbcHtD7Oa9SPVFo65SPjrAd3QpWVfgoMb1
+zrp7hhMyW0XuCgvpmHjhFk8CgYEAxq/thXM2p+bLLWGhwQcRG5G299zLbBl4PUsf
+0uEvLi17gJCKADoiRdSvoAn/9eHSQ26XYRuhKkDzHxcGlOmpY2PYzRa3mXyZ0VIk
+Iy5wDWwLQCeVZ6D22cClRfgb8BF/nFTPzVmn72SPpgoyhChQj7PvUynpyrRH07jj
+VxYziBsCgYAFr37Xbl0VnXVK+XU+vMwUZjcF4jpoCr7SFZqgRbW2GbYSUoMuPXns
+RnJh+Fvi1NUei+E5s1H4P1pVq4p0jFxP4GvH/qvNjnIn/Er3bbqvpox6dWUJXprq
+qTQSDIeoDC/V8cyRoIfqPvTVqY8Rgew6GEkv0bAImdxhoSng7vIseg==
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.crt b/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.crt
new file mode 100644
index 0000000000..23c06da01c
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.crt
@@ -0,0 +1,72 @@
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 2315414275452449025 (0x2022010414363101)
+        Signature Algorithm: sha256WithRSAEncryption
+        Issuer: CN = Test CA for PostgreSQL SSL regression test server certs
+        Validity
+            Not Before: Jan  4 22:36:31 2022 GMT
+            Not After : May 22 22:36:31 2049 GMT
+        Subject: OU = PostgreSQL test suite, CN = 192.0.2.1
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                RSA Public-Key: (2048 bit)
+                Modulus:
+                    00:f3:17:5d:6e:8f:f1:d9:33:92:21:af:db:af:c0:
+                    4d:a3:f5:11:75:3a:fd:fa:5d:91:e5:8a:23:89:92:
+                    83:b8:bc:62:41:59:20:0b:7d:0f:27:6f:c2:34:52:
+                    88:87:69:c7:85:1a:f3:92:72:3a:b0:44:d5:b6:bc:
+                    59:fb:d5:76:a9:17:35:c9:28:55:bb:8e:b6:bb:40:
+                    c7:3d:13:08:66:76:48:39:98:37:86:53:41:d1:c4:
+                    56:6c:e8:25:50:a9:6d:b4:80:11:34:44:14:71:35:
+                    32:3c:eb:72:a3:8f:ef:fa:ed:04:8f:93:53:35:c1:
+                    c5:6c:54:f4:d6:f7:43:f4:c8:d0:88:23:b7:8c:a7:
+                    40:c0:f2:1b:d7:88:d3:83:80:bb:d4:4a:59:f8:bb:
+                    9e:94:3a:38:0f:31:76:fd:78:06:5a:a5:13:ae:00:
+                    ff:5e:70:54:ff:ad:0f:53:d2:32:dc:6d:27:56:9c:
+                    76:d6:ae:a9:a6:7f:46:ed:af:51:fa:2f:05:8c:17:
+                    30:5b:54:fe:71:fb:01:0d:68:40:bf:e6:e2:46:64:
+                    80:90:43:5f:f0:bf:13:c0:e9:43:41:4c:11:3a:47:
+                    f3:dc:7c:f7:ea:fb:89:8d:d9:2b:79:02:6c:8a:a2:
+                    f4:1e:7a:e7:1f:94:f9:1b:d5:33:24:ef:3a:16:f6:
+                    50:e7
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Subject Alternative Name: 
+                DNS:dns1.alt-name.pg-ssltest.test, DNS:dns2.alt-name.pg-ssltest.test
+    Signature Algorithm: sha256WithRSAEncryption
+         5f:a6:7d:ac:3a:88:13:f3:a4:28:2a:ab:cd:f4:a4:74:5a:dd:
+         8c:e6:d5:a7:b3:b7:c4:a4:79:1a:21:51:81:cd:bf:fc:82:9c:
+         34:91:6a:ab:63:c5:b1:0d:8a:1d:e5:24:c4:93:04:bd:e0:d3:
+         0b:53:38:77:c7:f1:58:82:fe:1f:a5:bd:81:0b:1f:35:ca:89:
+         ce:e0:92:35:7b:1f:95:c4:4c:a1:ab:d1:9b:23:04:14:f6:e1:
+         c6:50:29:b0:fd:7b:4c:74:1e:b1:0d:5c:c6:20:1a:90:5f:ef:
+         8e:ea:2f:ab:93:39:52:5d:1b:4b:35:e6:1c:29:2e:ae:3b:6d:
+         8e:56:52:35:da:b6:17:a3:6e:9c:56:a6:b9:85:f0:5f:b1:68:
+         2e:76:37:d3:ff:d5:59:ea:82:e4:01:f7:4f:45:e3:1d:69:c8:
+         e2:bb:0f:bb:63:16:ef:98:f0:97:bb:3c:72:9b:6f:a5:98:e4:
+         ae:b1:02:fe:ad:47:5e:c2:63:4e:f8:f7:f1:68:b6:5a:61:aa:
+         e1:25:51:1d:9b:b3:96:73:bf:96:2b:8d:41:cf:4d:c6:f0:2a:
+         f7:07:e5:c7:4d:8b:dd:ff:bb:a3:49:b7:6c:4d:02:86:1f:69:
+         91:11:85:7e:53:03:d0:d8:93:88:02:f0:e6:34:c8:66:98:08:
+         88:f7:0d:de
+-----BEGIN CERTIFICATE-----
+MIIDQzCCAiugAwIBAgIIICIBBBQ2MQEwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UE
+Aww3VGVzdCBDQSBmb3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHNl
+cnZlciBjZXJ0czAeFw0yMjAxMDQyMjM2MzFaFw00OTA1MjIyMjM2MzFaMDQxHjAc
+BgNVBAsMFVBvc3RncmVTUUwgdGVzdCBzdWl0ZTESMBAGA1UEAwwJMTkyLjAuMi4x
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8xddbo/x2TOSIa/br8BN
+o/URdTr9+l2R5YojiZKDuLxiQVkgC30PJ2/CNFKIh2nHhRrzknI6sETVtrxZ+9V2
+qRc1yShVu462u0DHPRMIZnZIOZg3hlNB0cRWbOglUKlttIARNEQUcTUyPOtyo4/v
++u0Ej5NTNcHFbFT01vdD9MjQiCO3jKdAwPIb14jTg4C71EpZ+LuelDo4DzF2/XgG
+WqUTrgD/XnBU/60PU9Iy3G0nVpx21q6ppn9G7a9R+i8FjBcwW1T+cfsBDWhAv+bi
+RmSAkENf8L8TwOlDQUwROkfz3Hz36vuJjdkreQJsiqL0HnrnH5T5G9UzJO86FvZQ
+5wIDAQABo0swSTBHBgNVHREEQDA+gh1kbnMxLmFsdC1uYW1lLnBnLXNzbHRlc3Qu
+dGVzdIIdZG5zMi5hbHQtbmFtZS5wZy1zc2x0ZXN0LnRlc3QwDQYJKoZIhvcNAQEL
+BQADggEBAF+mfaw6iBPzpCgqq830pHRa3Yzm1aezt8SkeRohUYHNv/yCnDSRaqtj
+xbENih3lJMSTBL3g0wtTOHfH8ViC/h+lvYELHzXKic7gkjV7H5XETKGr0ZsjBBT2
+4cZQKbD9e0x0HrENXMYgGpBf747qL6uTOVJdG0s15hwpLq47bY5WUjXathejbpxW
+prmF8F+xaC52N9P/1VnqguQB909F4x1pyOK7D7tjFu+Y8Je7PHKbb6WY5K6xAv6t
+R17CY0749/FotlphquElUR2bs5Zzv5YrjUHPTcbwKvcH5cdNi93/u6NJt2xNAoYf
+aZERhX5TA9DYk4gC8OY0yGaYCIj3Dd4=
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.key b/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.key
new file mode 100644
index 0000000000..0ace41e0a1
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEogIBAAKCAQEA8xddbo/x2TOSIa/br8BNo/URdTr9+l2R5YojiZKDuLxiQVkg
+C30PJ2/CNFKIh2nHhRrzknI6sETVtrxZ+9V2qRc1yShVu462u0DHPRMIZnZIOZg3
+hlNB0cRWbOglUKlttIARNEQUcTUyPOtyo4/v+u0Ej5NTNcHFbFT01vdD9MjQiCO3
+jKdAwPIb14jTg4C71EpZ+LuelDo4DzF2/XgGWqUTrgD/XnBU/60PU9Iy3G0nVpx2
+1q6ppn9G7a9R+i8FjBcwW1T+cfsBDWhAv+biRmSAkENf8L8TwOlDQUwROkfz3Hz3
+6vuJjdkreQJsiqL0HnrnH5T5G9UzJO86FvZQ5wIDAQABAoIBAGv0BFoFMrHyZQLw
+xe7Wx6P4QTh+aiu1QgVdw0pk9nojrr62hbSUZRZuWyBBRsBcCW7i+Sgf8lA1QXNV
+UeC0e228EPa0It6YEi42JkTJHwHhpVFud7n/X0t4lajnryqTE1UFSp6bXTipFxZW
+uSJJ2ZjliRD5rApDcxkY4WJVjKg3aEt7P/DiM8iKGfyE6stq72VjEbJjdViMEcOP
+BNf0TiREZz5Mp7jAVWhpen0ebbLOBVWV4/ONNcL+yqR4mCEDUSFGewrTVX4zHL0A
+hYk198C5F8sFvEDnFkPco9sXMVanmLoI8sbhP4IIz9g4+GU6kFuj7fUKp11Azqv+
+3WQDKYECgYEA/XG4mmG/g8FG44y42mfZpUXWi1pwU4CQIrhkoU5j7EPQrvRboOOE
+Rv95jSwyZu4vCqjyI5FN1jCGTdhmt++R1e//zH6Hqa9Smo+jw7DtAFrCYd1JnCf1
+ToOwsYPHv4P7A8q8kc5vCNIv+AQSlP/wqdVNo3grdf7cGXkMtEY4F9UCgYEA9Yrq
+zWdnNGPATuSBqL6TSjQ37oR+dBD6WnGsiDenQkOzyDPFZ3CT1DjJghjEtxc8EfNf
+Oo8dMMR2q+5FZQo7WuqONEgyzKePiNR8RK2gOYpgdjN9bih1sAhHR10D26cpwlDJ
+bx7D5ZzENLbdZmfEiWwKswnaIhN4yMalgE0mP8sCgYAhzJy12ftUct4lUosEdX0N
+EXc/NlxshmSyfKzO5kllJNYbvvLJTg5B+agYL6C5IWKcpVNFcwdSXT5L+2QXe5eT
+VGJkvysQchUuD6HjYyD4PyJVMtGyRZHtWpqh0dU9sTg0lUD4oPMl1gIXrVNdE5Tg
+0VV9S3VgUxC/ROlw0TyB0QKBgGsVE0NS9hJF8mc1hko2GnwA++d8Rr2NbfElo+2f
+/8SJTA1ibpOm6AFkZpTjAl8qtdrKPVyHb16GP47Jkd/3r1z979hjKCxSYul0aWF2
+KusNKvZBjFEPOgv0AEniCb2wUCjbHI3mZ95qGLM4kKOJW4/m21+rS0MTJNjCsQic
+HLMzAoGAeCsY09d3m8xGeU+DuTPC6GH7Sgy/NBYqS5VaVNjb2jnuZlW2SSW2oiID
+4tXTi4ruKmHC898BfyFxhSMqub+tg3pVqIYADC71rnJLrVyc1SzoWzL7yMT3qFj7
+C7ZYZYmfG9agcZb5NkqKPTfCxkBhWbdgTTgBKVO/xQst8EUgko8=
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/sslfiles.mk b/src/test/ssl/sslfiles.mk
index 22ce3e1f97..6185cf75aa 100644
--- a/src/test/ssl/sslfiles.mk
+++ b/src/test/ssl/sslfiles.mk
@@ -25,6 +25,8 @@ SERVERS := server-cn-and-alt-names \
 	server-cn-and-ip-alt-names \
 	server-cn-only \
 	server-ip-cn-only \
+	server-ip-cn-and-alt-names \
+	server-ip-cn-and-dns-alt-names \
 	server-single-alt-name \
 	server-multiple-alt-names \
 	server-ip-alt-names \
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index d88201f5e8..a7cce770ed 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -21,7 +21,7 @@ if ($ENV{with_ssl} ne 'openssl')
 }
 else
 {
-	plan tests => 128;
+	plan tests => 135;
 }
 
 #### Some configuration
@@ -369,7 +369,7 @@ $node->connect_fails(
 	  qr/\Qserver certificate for "192.0.2.1" (and 1 other name) does not match host name "2001:DB8::1\/128"\E/
 );
 
-# Test server certificate with a CN and SANs. Per RFCs 2818 and 6125, the CN
+# Test server certificate with a CN and DNS SANs. Per RFCs 2818 and 6125, the CN
 # should be ignored when the certificate has both.
 switch_server_cert($node, 'server-cn-and-alt-names');
 
@@ -388,7 +388,6 @@ $node->connect_fails(
 );
 
 # But we will fall back to check the CN if the SANs contain only IP addresses.
-# TODO: should we?
 switch_server_cert($node, 'server-cn-and-ip-alt-names');
 
 $node->connect_ok("$common_connstr host=common-name.pg-ssltest.test",
@@ -398,6 +397,29 @@ $node->connect_ok("$common_connstr host=192.0.2.1",
 $node->connect_ok("$common_connstr host=2001:db8::1",
 	"certificate with both a CN and IP SANs matches SAN 2");
 
+# And now the same tests, but with IP addresses and DNS names swapped.
+switch_server_cert($node, 'server-ip-cn-and-alt-names');
+
+$node->connect_ok("$common_connstr host=192.0.2.2",
+	"certificate with both an IP CN and IP SANs 1");
+$node->connect_ok("$common_connstr host=2001:db8::1",
+	"certificate with both an IP CN and IP SANs 2");
+$node->connect_fails(
+	"$common_connstr host=192.0.2.1",
+	"certificate with both an IP CN and IP SANs ignores CN",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.2" (and 1 other name) does not match host name "192.0.2.1"\E/
+);
+
+switch_server_cert($node, 'server-ip-cn-and-dns-alt-names');
+
+$node->connect_ok("$common_connstr host=192.0.2.1",
+	"certificate with both an IP CN and DNS SANs matches CN");
+$node->connect_ok("$common_connstr host=dns1.alt-name.pg-ssltest.test",
+	"certificate with both an IP CN and DNS SANs matches SAN 1");
+$node->connect_ok("$common_connstr host=dns2.alt-name.pg-ssltest.test",
+	"certificate with both an IP CN and DNS SANs matches SAN 2");
+
 # Finally, test a server certificate that has no CN or SANs. Of course, that's
 # not a very sensible certificate, but libpq should handle it gracefully.
 switch_server_cert($node, 'server-no-names');
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 404c45a6f3..fa87511e04 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -100,7 +100,7 @@ sub mkvcbuild
 
 	our @pgportfiles = qw(
 	  chklocale.c explicit_bzero.c fls.c getpeereid.c getrusage.c inet_aton.c
-	  getaddrinfo.c gettimeofday.c inet_net_ntop.c kill.c open.c
+	  getaddrinfo.c gettimeofday.c inet_net_ntop.c inet_net_pton.c kill.c open.c
 	  snprintf.c strlcat.c strlcpy.c dirmod.c noblock.c path.c
 	  dirent.c dlopen.c getopt.c getopt_long.c link.c
 	  pread.c preadv.c pwrite.c pwritev.c pg_bitutils.c
v2-0001-Move-inet_net_pton-to-src-port.patchtext/x-patch; name=v2-0001-Move-inet_net_pton-to-src-port.patchDownload
From 3081b1e0f7af60241978c77bba71e806ba5c25f6 Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Wed, 24 Nov 2021 14:46:11 -0800
Subject: [PATCH v2 1/2] Move inet_net_pton() to src/port

This will be helpful for IP address verification in libpq.
---
 src/backend/utils/adt/Makefile                  |  1 -
 src/include/port.h                              |  3 +++
 src/include/utils/builtins.h                    |  4 ----
 src/port/Makefile                               |  1 +
 src/{backend/utils/adt => port}/inet_net_pton.c | 16 +++++++++++++++-
 src/tools/msvc/Mkvcbuild.pm                     |  2 +-
 6 files changed, 20 insertions(+), 7 deletions(-)
 rename src/{backend/utils/adt => port}/inet_net_pton.c (96%)

diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile
index 41b486bcef..d173d52157 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -43,7 +43,6 @@ OBJS = \
 	geo_selfuncs.o \
 	geo_spgist.o \
 	inet_cidr_ntop.o \
-	inet_net_pton.o \
 	int.o \
 	int8.o \
 	json.o \
diff --git a/src/include/port.h b/src/include/port.h
index fd9c9d6f94..cca29839bc 100644
--- a/src/include/port.h
+++ b/src/include/port.h
@@ -518,6 +518,9 @@ extern int	pg_codepage_to_encoding(UINT cp);
 extern char *pg_inet_net_ntop(int af, const void *src, int bits,
 							  char *dst, size_t size);
 
+/* port/inet_net_pton.c */
+extern int	pg_inet_net_pton(int af, const char *src, void *dst, size_t size);
+
 /* port/pg_strong_random.c */
 extern void pg_strong_random_init(void);
 extern bool pg_strong_random(void *buf, size_t len);
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index b07eefaf1e..a5a18e62ba 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -92,10 +92,6 @@ extern int	xidComparator(const void *arg1, const void *arg2);
 extern char *pg_inet_cidr_ntop(int af, const void *src, int bits,
 							   char *dst, size_t size);
 
-/* inet_net_pton.c */
-extern int	pg_inet_net_pton(int af, const char *src,
-							 void *dst, size_t size);
-
 /* network.c */
 extern double convert_network_to_scalar(Datum value, Oid typid, bool *failure);
 extern Datum network_scan_first(Datum in);
diff --git a/src/port/Makefile b/src/port/Makefile
index b3754d8940..7191cd6633 100644
--- a/src/port/Makefile
+++ b/src/port/Makefile
@@ -43,6 +43,7 @@ OBJS = \
 	bsearch_arg.o \
 	chklocale.o \
 	inet_net_ntop.o \
+	inet_net_pton.o \
 	noblock.o \
 	path.o \
 	pg_bitutils.o \
diff --git a/src/backend/utils/adt/inet_net_pton.c b/src/port/inet_net_pton.c
similarity index 96%
rename from src/backend/utils/adt/inet_net_pton.c
rename to src/port/inet_net_pton.c
index d3221a1313..bae50ba67e 100644
--- a/src/backend/utils/adt/inet_net_pton.c
+++ b/src/port/inet_net_pton.c
@@ -14,14 +14,18 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
  * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  *
- *	  src/backend/utils/adt/inet_net_pton.c
+ *	  src/port/inet_net_pton.c
  */
 
 #if defined(LIBC_SCCS) && !defined(lint)
 static const char rcsid[] = "Id: inet_net_pton.c,v 1.4.2.3 2004/03/17 00:40:11 marka Exp $";
 #endif
 
+#ifndef FRONTEND
 #include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
 
 #include <sys/socket.h>
 #include <netinet/in.h>
@@ -29,9 +33,19 @@ static const char rcsid[] = "Id: inet_net_pton.c,v 1.4.2.3 2004/03/17 00:40:11 m
 #include <assert.h>
 #include <ctype.h>
 
+#ifndef FRONTEND
 #include "utils/builtins.h" /* pgrminclude ignore */	/* needed on some
 														 * platforms */
 #include "utils/inet.h"
+#else
+/*
+ * In a frontend build, we can't include inet.h, but we still need to have
+ * sensible definitions of these two constants.  Note that pg_inet_net_ntop()
+ * assumes that PGSQL_AF_INET is equal to AF_INET.
+ */
+#define PGSQL_AF_INET	(AF_INET + 0)
+#define PGSQL_AF_INET6	(AF_INET + 1)
+#endif
 
 
 static int	inet_net_pton_ipv4(const char *src, u_char *dst);
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 404c45a6f3..fa87511e04 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -100,7 +100,7 @@ sub mkvcbuild
 
 	our @pgportfiles = qw(
 	  chklocale.c explicit_bzero.c fls.c getpeereid.c getrusage.c inet_aton.c
-	  getaddrinfo.c gettimeofday.c inet_net_ntop.c kill.c open.c
+	  getaddrinfo.c gettimeofday.c inet_net_ntop.c inet_net_pton.c kill.c open.c
 	  snprintf.c strlcat.c strlcpy.c dirmod.c noblock.c path.c
 	  dirent.c dlopen.c getopt.c getopt_long.c link.c
 	  pread.c preadv.c pwrite.c pwritev.c pg_bitutils.c
-- 
2.25.1

v2-0002-libpq-allow-IP-address-SANs-in-server-certs.patchtext/x-patch; name=v2-0002-libpq-allow-IP-address-SANs-in-server-certs.patchDownload
From 84987001f0fc7b5a6018c3f54684b780fee204cd Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Thu, 18 Nov 2021 15:36:18 -0800
Subject: [PATCH v2 2/2] libpq: allow IP address SANs in server certs

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.

- Unlike NSS, we don't map IPv4 to IPv6 addresses, or vice-versa.
---
 src/interfaces/libpq/fe-secure-common.c       | 102 ++++++++++++++++
 src/interfaces/libpq/fe-secure-common.h       |  11 ++
 src/interfaces/libpq/fe-secure-openssl.c      | 114 ++++++++++++++++--
 .../conf/server-cn-and-ip-alt-names.config    |  24 ++++
 src/test/ssl/conf/server-ip-alt-names.config  |  19 +++
 .../conf/server-ip-cn-and-alt-names.config    |  21 ++++
 .../server-ip-cn-and-dns-alt-names.config     |  21 ++++
 src/test/ssl/conf/server-ip-cn-only.config    |  12 ++
 .../ssl/ssl/server-cn-and-ip-alt-names.crt    |  20 +++
 .../ssl/ssl/server-cn-and-ip-alt-names.key    |  27 +++++
 src/test/ssl/ssl/server-ip-alt-names.crt      |  19 +++
 src/test/ssl/ssl/server-ip-alt-names.key      |  27 +++++
 .../ssl/ssl/server-ip-cn-and-alt-names.crt    |  19 +++
 .../ssl/ssl/server-ip-cn-and-alt-names.key    |  27 +++++
 .../ssl/server-ip-cn-and-dns-alt-names.crt    |  20 +++
 .../ssl/server-ip-cn-and-dns-alt-names.key    |  27 +++++
 src/test/ssl/ssl/server-ip-cn-only.crt        |  18 +++
 src/test/ssl/ssl/server-ip-cn-only.key        |  27 +++++
 src/test/ssl/sslfiles.mk                      |   5 +
 src/test/ssl/t/001_ssltests.pl                | 101 +++++++++++++++-
 20 files changed, 647 insertions(+), 14 deletions(-)
 create mode 100644 src/test/ssl/conf/server-cn-and-ip-alt-names.config
 create mode 100644 src/test/ssl/conf/server-ip-alt-names.config
 create mode 100644 src/test/ssl/conf/server-ip-cn-and-alt-names.config
 create mode 100644 src/test/ssl/conf/server-ip-cn-and-dns-alt-names.config
 create mode 100644 src/test/ssl/conf/server-ip-cn-only.config
 create mode 100644 src/test/ssl/ssl/server-cn-and-ip-alt-names.crt
 create mode 100644 src/test/ssl/ssl/server-cn-and-ip-alt-names.key
 create mode 100644 src/test/ssl/ssl/server-ip-alt-names.crt
 create mode 100644 src/test/ssl/ssl/server-ip-alt-names.key
 create mode 100644 src/test/ssl/ssl/server-ip-cn-and-alt-names.crt
 create mode 100644 src/test/ssl/ssl/server-ip-cn-and-alt-names.key
 create mode 100644 src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.crt
 create mode 100644 src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.key
 create mode 100644 src/test/ssl/ssl/server-ip-cn-only.crt
 create mode 100644 src/test/ssl/ssl/server-ip-cn-only.key

diff --git a/src/interfaces/libpq/fe-secure-common.c b/src/interfaces/libpq/fe-secure-common.c
index afa5d133e1..796b78e348 100644
--- a/src/interfaces/libpq/fe-secure-common.c
+++ b/src/interfaces/libpq/fe-secure-common.c
@@ -19,9 +19,13 @@
 
 #include "postgres_fe.h"
 
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
 #include "fe-secure-common.h"
 
 #include "libpq-int.h"
+#include "port.h"
 #include "pqexpbuffer.h"
 
 /*
@@ -144,6 +148,104 @@ pq_verify_peer_name_matches_certificate_name(PGconn *conn,
 	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 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 = PGSQL_AF_INET;
+
+		/*
+		 * The use of inet_aton() instead of inet_pton() is deliberate; the
+		 * latter cannot handle alternate IPv4 notations ("numbers-and-dots").
+		 */
+		if (inet_aton(host, &addr))
+		{
+			if (memcmp(ipdata, &addr.s_addr, iplen) == 0)
+				match = 1;
+		}
+	}
+	else if (iplen == 16)
+	{
+		/* IPv6 */
+		unsigned char addr[16];
+
+		family = PGSQL_AF_INET6;
+
+		/*
+		 * pg_inet_net_pton() will accept CIDR masks, which we don't want to
+		 * match, so skip the comparison if the host string contains a slash.
+		 */
+		if (!strchr(host, '/')
+			&& pg_inet_net_pton(PGSQL_AF_INET6, host, addr, -1) == 128)
+		{
+			if (memcmp(ipdata, addr, iplen) == 0)
+				match = 1;
+		}
+	}
+	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.
  *
diff --git a/src/interfaces/libpq/fe-secure-common.h b/src/interfaces/libpq/fe-secure-common.h
index 2389f6717a..29d4f54230 100644
--- a/src/interfaces/libpq/fe-secure-common.h
+++ b/src/interfaces/libpq/fe-secure-common.h
@@ -18,9 +18,20 @@
 
 #include "libpq-fe.h"
 
+/*
+ * In a frontend build, we can't include inet.h, but we still need to have
+ * sensible definitions of these two constants.  Note that pg_inet_net_ntop()
+ * assumes that PGSQL_AF_INET is equal to AF_INET.
+ */
+#define PGSQL_AF_INET	(AF_INET + 0)
+#define PGSQL_AF_INET6	(AF_INET + 1)
+
 extern int	pq_verify_peer_name_matches_certificate_name(PGconn *conn,
 														 const char *namedata, size_t namelen,
 														 char **store_name);
+extern int	pq_verify_peer_name_matches_certificate_ip(PGconn *conn,
+													   const char *addrdata, size_t addrlen,
+													   char **store_name);
 extern bool pq_verify_peer_name_matches_certificate(PGconn *conn);
 
 #endif							/* FE_SECURE_COMMON_H */
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index 33f095c12e..479f63197a 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -72,6 +72,9 @@ static int	verify_cb(int ok, X509_STORE_CTX *ctx);
 static int	openssl_verify_peer_name_matches_certificate_name(PGconn *conn,
 															  ASN1_STRING *name,
 															  char **store_name);
+static int	openssl_verify_peer_name_matches_certificate_ip(PGconn *conn,
+															ASN1_OCTET_STRING *addr_entry,
+															char **store_name);
 static void destroy_ssl_system(void);
 static int	initialize_SSL(PGconn *conn);
 static PostgresPollingStatusType open_client_SSL(PGconn *);
@@ -509,6 +512,52 @@ openssl_verify_peer_name_matches_certificate_name(PGconn *conn, ASN1_STRING *nam
 	return pq_verify_peer_name_matches_certificate_name(conn, (const char *) namedata, len, store_name);
 }
 
+/*
+ * OpenSSL-specific wrapper around
+ * pq_verify_peer_name_matches_certificate_ip(), converting the
+ * ASN1_OCTET_STRING into a plain C string.
+ */
+static int
+openssl_verify_peer_name_matches_certificate_ip(PGconn *conn,
+												ASN1_OCTET_STRING *addr_entry,
+												char **store_name)
+{
+	int			len;
+	const unsigned char *addrdata;
+
+	/* Should not happen... */
+	if (addr_entry == NULL)
+	{
+		appendPQExpBufferStr(&conn->errorMessage,
+							 libpq_gettext("SSL certificate's address entry is missing\n"));
+		return -1;
+	}
+
+	/*
+	 * GEN_IPADD is an OCTET STRING containing an IP address in network byte
+	 * order.
+	 */
+#ifdef HAVE_ASN1_STRING_GET0_DATA
+	addrdata = ASN1_STRING_get0_data(addr_entry);
+#else
+	addrdata = ASN1_STRING_data(addr_entry);
+#endif
+	len = ASN1_STRING_length(addr_entry);
+
+	/* OK to cast from unsigned to plain char, since it's all ASCII. */
+	return pq_verify_peer_name_matches_certificate_ip(conn, (const char *) addrdata, len, store_name);
+}
+
+static bool
+is_ip_address(const char *host)
+{
+	struct in_addr	dummy4;
+	unsigned char	dummy6[16];
+
+	return inet_aton(host, &dummy4)
+		|| (pg_inet_net_pton(PGSQL_AF_INET6, host, dummy6, -1) == 128);
+}
+
 /*
  *	Verify that the server certificate matches the hostname we connected to.
  *
@@ -522,6 +571,36 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 	STACK_OF(GENERAL_NAME) * peer_san;
 	int			i;
 	int			rc = 0;
+	char	   *host = conn->connhost[conn->whichhost].host;
+	int			host_type;
+	bool		check_cn = true;
+
+	Assert(host && host[0]); /* should be guaranteed by caller */
+
+	/*
+	 * We try to match the NSS behavior here, which is a slight departure from
+	 * the spec but seems to make more intuitive sense:
+	 *
+	 * If connhost contains a DNS name, and the certificate's SANs contain any
+	 * dNSName entries, then we'll ignore the Subject Common Name entirely;
+	 * otherwise, we fall back to checking the CN. (This behavior matches the
+	 * RFC.)
+	 *
+	 * If connhost contains an IP address, and the SANs contain iPAddress
+	 * entries, we again ignore the CN. Otherwise, we allow the CN to match,
+	 * EVEN IF there is a dNSName in the SANs. (RFC 6125 prohibits this: "A
+	 * client MUST NOT seek a match for a reference identifier of CN-ID if the
+	 * presented identifiers include a DNS-ID, SRV-ID, URI-ID, or any
+	 * application-specific identifier types supported by the client.")
+	 *
+	 * NOTE: Prior versions of libpq did not consider iPAddress entries at all,
+	 * so this new behavior might break a certificate that has different IP
+	 * addresses in the Subject CN and the SANs.
+	 */
+	if (is_ip_address(host))
+		host_type = GEN_IPADD;
+	else
+		host_type = GEN_DNS;
 
 	/*
 	 * First, get the Subject Alternative Names (SANs) from the certificate,
@@ -537,24 +616,34 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 		for (i = 0; i < san_len; i++)
 		{
 			const GENERAL_NAME *name = sk_GENERAL_NAME_value(peer_san, i);
+			char	   *alt_name = NULL;
+
+			if (name->type == host_type)
+				check_cn = false;
 
 			if (name->type == GEN_DNS)
 			{
-				char	   *alt_name;
-
 				(*names_examined)++;
 				rc = openssl_verify_peer_name_matches_certificate_name(conn,
 																	   name->d.dNSName,
 																	   &alt_name);
+			}
+			else if (name->type == GEN_IPADD)
+			{
+				(*names_examined)++;
+				rc = openssl_verify_peer_name_matches_certificate_ip(conn,
+																	 name->d.iPAddress,
+																	 &alt_name);
+			}
 
-				if (alt_name)
-				{
-					if (!*first_name)
-						*first_name = alt_name;
-					else
-						free(alt_name);
-				}
+			if (alt_name)
+			{
+				if (!*first_name)
+					*first_name = alt_name;
+				else
+					free(alt_name);
 			}
+
 			if (rc != 0)
 				break;
 		}
@@ -562,13 +651,14 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 	}
 
 	/*
-	 * If there is no subjectAltName extension of type dNSName, check the
+	 * If there is no subjectAltName extension of the matching type, check the
 	 * Common Name.
 	 *
 	 * (Per RFC 2818 and RFC 6125, if the subjectAltName extension of type
-	 * dNSName is present, the CN must be ignored.)
+	 * dNSName is present, the CN must be ignored. We break this rule if host is
+	 * an IP address; see the comment above.)
 	 */
-	if (*names_examined == 0)
+	if ((rc == 0) && check_cn)
 	{
 		X509_NAME  *subject_name;
 
diff --git a/src/test/ssl/conf/server-cn-and-ip-alt-names.config b/src/test/ssl/conf/server-cn-and-ip-alt-names.config
new file mode 100644
index 0000000000..a6fa09bad3
--- /dev/null
+++ b/src/test/ssl/conf/server-cn-and-ip-alt-names.config
@@ -0,0 +1,24 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+# This certificate contains a CN and SANs for both IPv4 and IPv6.
+
+
+[ req ]
+distinguished_name     = req_distinguished_name
+req_extensions         = v3_req
+prompt                 = no
+
+[ req_distinguished_name ]
+# Note: According to RFC 2818 and 6125, the CN is ignored, when DNS names are
+# present in the SANs. But they are silent on whether the CN is checked when IP
+# addresses are present.
+CN = common-name.pg-ssltest.test
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
+subjectAltName = @alt_names
+
+[ alt_names ]
+IP.1 = 192.0.2.1
+IP.2 = 2001:DB8::1
diff --git a/src/test/ssl/conf/server-ip-alt-names.config b/src/test/ssl/conf/server-ip-alt-names.config
new file mode 100644
index 0000000000..c22f22951a
--- /dev/null
+++ b/src/test/ssl/conf/server-ip-alt-names.config
@@ -0,0 +1,19 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+# This certificate has a two IP-address SANs, and no CN.
+
+[ req ]
+distinguished_name     = req_distinguished_name
+req_extensions         = v3_req
+prompt                 = no
+
+[ req_distinguished_name ]
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
+subjectAltName = @alt_names
+
+[ alt_names ]
+IP.1 = 192.0.2.1
+IP.2 = 2001:DB8::1
diff --git a/src/test/ssl/conf/server-ip-cn-and-alt-names.config b/src/test/ssl/conf/server-ip-cn-and-alt-names.config
new file mode 100644
index 0000000000..a4087f0a18
--- /dev/null
+++ b/src/test/ssl/conf/server-ip-cn-and-alt-names.config
@@ -0,0 +1,21 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+# This certificate contains both a CN and SANs in IP address format.
+
+
+[ req ]
+distinguished_name     = req_distinguished_name
+req_extensions         = v3_req
+prompt                 = no
+
+[ req_distinguished_name ]
+CN = 192.0.2.1
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
+subjectAltName = @alt_names
+
+[ alt_names ]
+IP.1 = 192.0.2.2
+IP.2 = 2001:DB8::1
diff --git a/src/test/ssl/conf/server-ip-cn-and-dns-alt-names.config b/src/test/ssl/conf/server-ip-cn-and-dns-alt-names.config
new file mode 100644
index 0000000000..7121803b49
--- /dev/null
+++ b/src/test/ssl/conf/server-ip-cn-and-dns-alt-names.config
@@ -0,0 +1,21 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+# This certificate contains both a CN and SANs in IP address format.
+
+
+[ req ]
+distinguished_name     = req_distinguished_name
+req_extensions         = v3_req
+prompt                 = no
+
+[ req_distinguished_name ]
+CN = 192.0.2.1
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
+subjectAltName = @alt_names
+
+[ alt_names ]
+DNS.1 = dns1.alt-name.pg-ssltest.test
+DNS.2 = dns2.alt-name.pg-ssltest.test
diff --git a/src/test/ssl/conf/server-ip-cn-only.config b/src/test/ssl/conf/server-ip-cn-only.config
new file mode 100644
index 0000000000..585d8bdae8
--- /dev/null
+++ b/src/test/ssl/conf/server-ip-cn-only.config
@@ -0,0 +1,12 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+
+[ req ]
+distinguished_name     = req_distinguished_name
+prompt                 = no
+
+[ req_distinguished_name ]
+CN = 192.0.2.1
+OU = PostgreSQL test suite
+
+# No Subject Alternative Names
diff --git a/src/test/ssl/ssl/server-cn-and-ip-alt-names.crt b/src/test/ssl/ssl/server-cn-and-ip-alt-names.crt
new file mode 100644
index 0000000000..4e58c85ccb
--- /dev/null
+++ b/src/test/ssl/ssl/server-cn-and-ip-alt-names.crt
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDLzCCAhegAwIBAgIIICERKRE1UQAwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UE
+Aww3VGVzdCBDQSBmb3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHNl
+cnZlciBjZXJ0czAeFw0yMTExMjkxOTM1NTFaFw00OTA0MTYxOTM1NTFaMEYxHjAc
+BgNVBAsMFVBvc3RncmVTUUwgdGVzdCBzdWl0ZTEkMCIGA1UEAwwbY29tbW9uLW5h
+bWUucGctc3NsdGVzdC50ZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
+AQEA6+8IYKAFnZ7V+fDo1cyMpbGBLzCfJOQ/1o2jOGP4+GjpsZgv6S6UT2MheC8M
+iiEFrYwdsSIZyYc3jEZrluy/UuR0bCGtqU92BCqa0iBLhvHOgjR588u253eLxQtQ
+8iJn11QPrKMk35nMkmY8GfHt4sGFbvBL6+GpipHq7a6cde3Z+v4kCB5dKMYDUDtm
+3mJmviuGNAu5wOqItk2Yi5dwJs1054007KNH0Il43urxiOfnkLS0cG5kehboPf86
+vxBt3iHByrU/9/DY5IvQCfSXVNa6rb5w5/pGja9aCei6Mv1jQY/V8SMQTga+MOsA
+0WB9akxMi2NxwS2+BQ4k/McPlwIDAQABoyUwIzAhBgNVHREEGjAYhwTAAAIBhxAg
+AQ24AAAAAAAAAAAAAAABMA0GCSqGSIb3DQEBCwUAA4IBAQAQLo2RzC07dG9p+J3A
+W6C0p3Y+Os/YE2D9wfp4TIDTZxcRUQZ0S6ahF1N6sp8l9KHBJHPU1cUpRAU1oD+Y
+SqmnP/VJRRDTTj9Ytdc/Vuo2jeLpSYhVKrCqtjqIrCwYJFoYRmMoxTtJGlwA0hSd
+kwo3XYrALPUQWUErTYPvNfDNIuUwqUXNfS0CXuIOVN3LJ+shegg6Pwbh9B5T9NHx
+kH+HswajhdpdnZIgh0FYTlTCPILDrB49aOWwqLa54AUA6WXa35hPsP8SoqL9Eucq
+ifPhBYyadsjOb+70N8GbbAsDPN1jCX9L8RuNcEkxSCKCYx91cWXh7K5KMPuGlzB7
+j8xB
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-cn-and-ip-alt-names.key b/src/test/ssl/ssl/server-cn-and-ip-alt-names.key
new file mode 100644
index 0000000000..837eef996d
--- /dev/null
+++ b/src/test/ssl/ssl/server-cn-and-ip-alt-names.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA6+8IYKAFnZ7V+fDo1cyMpbGBLzCfJOQ/1o2jOGP4+GjpsZgv
+6S6UT2MheC8MiiEFrYwdsSIZyYc3jEZrluy/UuR0bCGtqU92BCqa0iBLhvHOgjR5
+88u253eLxQtQ8iJn11QPrKMk35nMkmY8GfHt4sGFbvBL6+GpipHq7a6cde3Z+v4k
+CB5dKMYDUDtm3mJmviuGNAu5wOqItk2Yi5dwJs1054007KNH0Il43urxiOfnkLS0
+cG5kehboPf86vxBt3iHByrU/9/DY5IvQCfSXVNa6rb5w5/pGja9aCei6Mv1jQY/V
+8SMQTga+MOsA0WB9akxMi2NxwS2+BQ4k/McPlwIDAQABAoIBAQCuNFKVNdKvrUYF
+RLJGmsAG3+eo9lern7TbML2ht39vu9dBwEMwA6qSa3mdCfBSVUuh9uE9lxY/TU3g
+j2aFi81A4VptNPjLGNblAKhMGnhp7UUzspeRQYuNoSFcnpxoDKtrvK/OIq/pQeBh
+AIfECHRDh+yEG32Tb44FuPQkB1eTYl8xbMEImrhNUaSjJk7tTsmydHy0DjmqHVKX
+HUj0TREfDBDOBiHtY0XV6Pu3bnqDH/TKLTfUf3UdfTuay3Yai9aEcRPWp9GrMO7G
+axsKCifTz6177gyr6Fv8HLeMZMh9rMZRn3e0zfaF6vrH1QnZZOts5jpUa0KugSCd
+//uC0iNxAoGBAPXVc3b+o3hY5gcwwpaW6JtsarDrmNRxrizqIDG7NgpqwdFXgTi6
+6q0t2pjv81ATqij69IcPkNSissyR4OEKnu/OFJWzreg8yLi75WHKi0E/6msHpwRk
+d1yP0Zgd05ots/yOjDSp593RagaPVvHBxMECZ/Tm3B+Tq55Azudd/zvLAoGBAPWw
+xf0oUEJl6NdUZD6K7eFc6jf8yrpD85dldeko6LeN8x0XlKKWvUDJ2+3oizXoQvCm
+8by6KOYEIo4MrtXuy9MmtPWfNvRBr+hsUHchIj7IgFa9bKXyK2FnJqu/8CbEymli
+eZu7hoOhelurhnFy1zSqwNO4GC+kw60Y/BO3Z1nlAoGAVOyYJtNwxXJwhKtjjYI0
+ePzLHrNE6J8c/Ick+AkkchTPP/JqwZ5Q0+KzUYITG+avMdkAAGhwMATEn8cFWLjC
+jzUyB0U7Hq9g5/CBHXdLBA+Ae9j46ZuLYH6OeW5UWz7OnsDfzpGjeA2QAxQhhQLb
+ZZHfN8tI39+zucfJskPWmGECgYEAg9guF1Fn6InJrqwR82IYj6SN6CeXHufSM392
+C/4xDDd3rDf4QlwECV2J0RzGf9I5Ae2EshNwWScE6Be0RweTh6cw2tJq6h7J6D8f
+2x4Dw49TF7klMdRIJUf2f5pLpHJccLswqTqzz7V69PCSABVxmUi8m6EiEYconp5W
+v7nfE2UCgYALrEqzncuSIX3q6TVAjnzT7gO4h8h2TUekIWdHQFldFx8R7Kncggnd
+48gQqhewchNR83UCcd7pPsCcTqu6UR1QRdq/DV5P6J3xdZ2iS/2gCM6hvWIvKZEv
+/ClnkyFCOW7zX6RKIXtRYZTV1kz3TajApi34RTIeIMTieaCarnBJbA==
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-ip-alt-names.crt b/src/test/ssl/ssl/server-ip-alt-names.crt
new file mode 100644
index 0000000000..8a1bc620bb
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-alt-names.crt
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDCTCCAfGgAwIBAgIIICERKREEUAAwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UE
+Aww3VGVzdCBDQSBmb3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHNl
+cnZlciBjZXJ0czAeFw0yMTExMjkxOTA0NTBaFw00OTA0MTYxOTA0NTBaMCAxHjAc
+BgNVBAsMFVBvc3RncmVTUUwgdGVzdCBzdWl0ZTCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBAOM8yB6aVWb17ujr3ayU62mxHQoqn4CvG9yXlJvGOGv/ursW
+Vs0UYJdc96LsNZN1szdm9ayNzCIw3eja+ULsjxCi6+3LM4pO76IORL/XFamlTPYb
+BZ4pHdZVB0nnZAAnWCZPyXdnjOKQ5+8unVXkfibkjj8UELBJ2snehsOa+CTkOBez
+zxYMqxAgbywLIYsW448brun7UXpWmqbGK+SsdGaIZ5Sb7Zezc5lt6CrLemTZTHHK
+7l4WZFCCEi4t3sgO8o1vDELD/IE5G8lyXvIdgJg6t8ssper7iCw6S8x+okhjiSjT
+vDLU2g4AanqZRZB49aPwTo0QUcJA2BCJxL9xLy8CAwEAAaMlMCMwIQYDVR0RBBow
+GIcEwAACAYcQIAENuAAAAAAAAAAAAAAAATANBgkqhkiG9w0BAQsFAAOCAQEAwZJ+
+8KpABTlMEgKnHIYb35ItGhtFiTLQta9RkXx7vaeDwpOdPP/IvuvpjpQZkobRgBsk
+bNM0KuJpd2mSTphQAt6eKQIdcPrkzvc/Yh9OK3YNLUAbu/ZhBUnBvFnUL4wn2f1U
+mfO+m8P/LxybwqKx7r1mbaB+tP3RTxxLcIMvm9ECPQEoBntfEL325Wdoj+WuQH5Y
+IvcM6FaCTkQsNIPbaBD5l5MhMLHRULZujbDjXqGSvRMQfns6np/biMjNdQA8NZ5z
+STeUFvkQbCxoA0YYLgoSHL5KhZjXrg2g+T+2TUyCTR/91xf9OoOjBZdixR0S0DzJ
+B1+5vnUjZaCfnSEA7A==
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-ip-alt-names.key b/src/test/ssl/ssl/server-ip-alt-names.key
new file mode 100644
index 0000000000..b210b3a991
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-alt-names.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEA4zzIHppVZvXu6OvdrJTrabEdCiqfgK8b3JeUm8Y4a/+6uxZW
+zRRgl1z3ouw1k3WzN2b1rI3MIjDd6Nr5QuyPEKLr7cszik7vog5Ev9cVqaVM9hsF
+nikd1lUHSedkACdYJk/Jd2eM4pDn7y6dVeR+JuSOPxQQsEnayd6Gw5r4JOQ4F7PP
+FgyrECBvLAshixbjjxuu6ftRelaapsYr5Kx0ZohnlJvtl7NzmW3oKst6ZNlMccru
+XhZkUIISLi3eyA7yjW8MQsP8gTkbyXJe8h2AmDq3yyyl6vuILDpLzH6iSGOJKNO8
+MtTaDgBqeplFkHj1o/BOjRBRwkDYEInEv3EvLwIDAQABAoIBACp3uY6+mSdc3wF4
+0zzlt/lQuHSl8plCIJrhWUyjhvfoGyXLzv0Uydh/72frbTfZz1yTSWauOXBKYa6a
+/eqb+0DIsf8G8uLuTaqjsAWKVOoXkoKMGkistn7P9UTCkdXVhIvkbWp7V8EgA7iX
+pZ/fzBPIsyzmuxe3NcR0ags0cxuxkNuu+YXDv1oTedmT2wS3CZq1d/T1Y/EOVIf8
+Iznd2aOverlsnt6iiQ3ZWdG/W5F8FhnrR/rrBdYsdCv6TH/KUYexnDOUYpayjDbu
+oAKnifPp6UqiOM4SuBL83OAz19jptp5vpF370BEVRs3eK0q+zo/mETjv9HsXdolZ
+lfoXA0ECgYEA/7nb2azbq/2muvXCh1ZxCEbn3mt8KXoJP/xkx/v9eEc/cc5Q9e0V
+2oGfjC2hSE+bjOWMwiUMD6uU+iRjhz5A3IvUxnoSdoL7H9p0hTqLMyP7dTDkoVF5
+aEuLMaiI5YEnfAFu9L5h8ZKieoQTBoscT06wnGjh9pBV9bthfTKA7ksCgYEA43sb
+55m9WL4kWCPwOAp3vdEAFyxzmZHlO26sEQOU/m5aN01pumYybBruziEXMI96yfTj
+VmXKReeYb6XUiCcs3fLSipD/+8/8CsjO4uMORtxWumXe8AbKZfysGFzL7wJlByGT
+38AGQwIG/XD8cKnaiEMX4E/3Owbcoxwixo3WZC0CgYEAovaqJ9mEU+Jc8h/TS7PG
+bGPjN1Z/1V6zrlcFUnw/Vvrwb3HvHglsN8cLCaW6df5lPjC6tq4tNX8+fPnbg0Ak
+zWc+vQzl3ygxKGdqgcyBEKIJiPETgcoN+GzL02V3d+oKY3f2YXlBqVSsvi6UgUL9
+U3zuB36/IQVyAhrbUZFxoGkCgYEAnaFAO+Nvrp/LhXwZyGuQf+rkmipGTIMpil5t
+QzjtNMV5JFszSWPpyrl7A0Ew1YiG+I0GP2c3m+sY2TzbIiGrWH0b4cMKbw63Qy3V
+FqlpyjaCrpVKv56k/7jv883RzuQk56Uf1+szK5mrCFITy2oXsVZ0pA4lbjSaDTjA
+7D968V0CgYEA+qKqXKL98+c5CMPnpf+0B1x2zgyUym1ouPfon2x5fhK84T53zDMA
+zfdUJ/SOZw6/c9vRF7RL8h+ZfFdIyoAXv4Tt6mIiZe7P+AUVg6XgJ0ce2MUSeWjI
+W8D4WdSi0jyqr99TuVBWhbTZJviMB3pHqKaHQ07hnd/lPtvzsiH12qk=
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-ip-cn-and-alt-names.crt b/src/test/ssl/ssl/server-ip-cn-and-alt-names.crt
new file mode 100644
index 0000000000..2be02feb03
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-and-alt-names.crt
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDHTCCAgWgAwIBAgIIICIBBBQ2MQAwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UE
+Aww3VGVzdCBDQSBmb3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHNl
+cnZlciBjZXJ0czAeFw0yMjAxMDQyMjM2MzFaFw00OTA1MjIyMjM2MzFaMDQxHjAc
+BgNVBAsMFVBvc3RncmVTUUwgdGVzdCBzdWl0ZTESMBAGA1UEAwwJMTkyLjAuMi4x
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwmqTdQJfs2Ti9tPitYp2
+27I0HvL/kNSgA6egFr0foRo0BorwJNIzdbV0+EnsfiBNTWL5It26gqO7UP3ms8t2
+vHD5gkXfT+f6ts0lVJEcIOkUD/8ws4Ic9Y4uPqb4gN+pUKqcxtmLW1TYk84MBK59
+Xz4yPPS6N+G/DMMeFHTNkM9EQwn/+DC3fDsWdGYM2GRWDTJGg1A5tSUcF+seu7i1
+Vg7XajBfsvgAUAsrAxV+X/sLZh94HY+paD6wfaI99mY2OXVc/XW/z1r9WQznor65
+ZkonNCaPfavqPG5vqnab9AyQcqPqmX8hf/xrniASBAkqNCctbASrFCIYvCJfGfmX
+EQIDAQABoyUwIzAhBgNVHREEGjAYhwTAAAIChxAgAQ24AAAAAAAAAAAAAAABMA0G
+CSqGSIb3DQEBCwUAA4IBAQBf7kmYfRYfnWk1OUfY3N1kaNg9piBBlFr9g+OQn9KU
+zirkN7s0ZQbCGxV1uJQBKS58NyE414Vorau77379emgYDcCBpDIYpkLiNujVrIOr
+ggRFKsFRgxu4/mw0BSgCcV8RPe9SWHZ90Mos7TMCnW/PdxOCD1wD0YMkcs0rwB3l
+0Kzc7jDnfOEvmgw/Ysm7v67ps+05Uq5VskQ6WrpSAw6kPD/QMuuBAX8ATPczIaox
+zAMyncq1IiSIwG93f3EoQQThdQ70C6G9vLcu9TtL6JAsEMFEzR99gt1Wsqvmgl9W
+kStzj1yjIWeo5gIsa4Jgcke1lZviWyrTxHDfyunYE5i5
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-ip-cn-and-alt-names.key b/src/test/ssl/ssl/server-ip-cn-and-alt-names.key
new file mode 100644
index 0000000000..54fe80fc68
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-and-alt-names.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAwmqTdQJfs2Ti9tPitYp227I0HvL/kNSgA6egFr0foRo0Borw
+JNIzdbV0+EnsfiBNTWL5It26gqO7UP3ms8t2vHD5gkXfT+f6ts0lVJEcIOkUD/8w
+s4Ic9Y4uPqb4gN+pUKqcxtmLW1TYk84MBK59Xz4yPPS6N+G/DMMeFHTNkM9EQwn/
++DC3fDsWdGYM2GRWDTJGg1A5tSUcF+seu7i1Vg7XajBfsvgAUAsrAxV+X/sLZh94
+HY+paD6wfaI99mY2OXVc/XW/z1r9WQznor65ZkonNCaPfavqPG5vqnab9AyQcqPq
+mX8hf/xrniASBAkqNCctbASrFCIYvCJfGfmXEQIDAQABAoIBAB6GgVST5NbT9lbu
++d+rN/JSzqA1Yy8oU19/iEFJvJec96I3WnFNl8rZjN4XLUy4YarO6XMyAUDV2Gll
+FD4Sqjf4PRTZR7DSKaleGIhoqFP6hK3mUY091rIves9XhBkoBPunbipCqgDTF5ZN
+edGaXBECQP0VJ8/yX/7u++AWXthnjDis9X0taZfFg/PYbV7SCJ1Hg1O/wEsgXlnC
+7mbL6wkCW0f6700B0x1kKbZqJY95xRqp6Ipq2lIQbJDdGywoj0WzKqNltf9cer+r
+cXl8WjeiMvvvpl4uGhckAbzUifUzxN6A3f1fu/XKtOmabMi9t7J4MRfgOgedgtQB
+0jaZGSkCgYEA+lBLnNY6M48HX2mdtr86+n41gh69v8Z7oNikJFDZkodrvI8uqE0i
+0XwnYPFddt8NbmuUhhuzI2M8RKhGLgdlbKpkSSVafnMfcxRmX2EAtWQgdvX1Iult
+752LWdBgSuw2vlzvy3T/GYnjMrXSCGput4amqojMEbvUGvIdSUMdHGMCgYEAxtU1
+WixKPL6aEnYy1f4bybzcNgGtl8PBRz9xw+P46g+ijOPoaG9O73Tr7An11AO003Ot
+DHhMW+b8yHLyxoKwS2sU2cN/lKB8xNQYZc1D61RNJlzgnHMXnA0lcH0I3M35fqKr
+/71pD1ZP40SSJS+od/KEjW80XzuOdyiXg8q81vsCgYEAnUPLbbsuj+whzrFVlFZr
+IKwgxCK6Rn3WeIUEA4kEWUpZxvsSbk0gPgtJ1l9uwFt9Xc2bX/KRRv93Aw/SH+Mn
+tvEK1uXwCBgePzgm5W/VeSFyQCthm1CbcHtD7Oa9SPVFo65SPjrAd3QpWVfgoMb1
+zrp7hhMyW0XuCgvpmHjhFk8CgYEAxq/thXM2p+bLLWGhwQcRG5G299zLbBl4PUsf
+0uEvLi17gJCKADoiRdSvoAn/9eHSQ26XYRuhKkDzHxcGlOmpY2PYzRa3mXyZ0VIk
+Iy5wDWwLQCeVZ6D22cClRfgb8BF/nFTPzVmn72SPpgoyhChQj7PvUynpyrRH07jj
+VxYziBsCgYAFr37Xbl0VnXVK+XU+vMwUZjcF4jpoCr7SFZqgRbW2GbYSUoMuPXns
+RnJh+Fvi1NUei+E5s1H4P1pVq4p0jFxP4GvH/qvNjnIn/Er3bbqvpox6dWUJXprq
+qTQSDIeoDC/V8cyRoIfqPvTVqY8Rgew6GEkv0bAImdxhoSng7vIseg==
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.crt b/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.crt
new file mode 100644
index 0000000000..23c06da01c
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.crt
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDQzCCAiugAwIBAgIIICIBBBQ2MQEwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UE
+Aww3VGVzdCBDQSBmb3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHNl
+cnZlciBjZXJ0czAeFw0yMjAxMDQyMjM2MzFaFw00OTA1MjIyMjM2MzFaMDQxHjAc
+BgNVBAsMFVBvc3RncmVTUUwgdGVzdCBzdWl0ZTESMBAGA1UEAwwJMTkyLjAuMi4x
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8xddbo/x2TOSIa/br8BN
+o/URdTr9+l2R5YojiZKDuLxiQVkgC30PJ2/CNFKIh2nHhRrzknI6sETVtrxZ+9V2
+qRc1yShVu462u0DHPRMIZnZIOZg3hlNB0cRWbOglUKlttIARNEQUcTUyPOtyo4/v
++u0Ej5NTNcHFbFT01vdD9MjQiCO3jKdAwPIb14jTg4C71EpZ+LuelDo4DzF2/XgG
+WqUTrgD/XnBU/60PU9Iy3G0nVpx21q6ppn9G7a9R+i8FjBcwW1T+cfsBDWhAv+bi
+RmSAkENf8L8TwOlDQUwROkfz3Hz36vuJjdkreQJsiqL0HnrnH5T5G9UzJO86FvZQ
+5wIDAQABo0swSTBHBgNVHREEQDA+gh1kbnMxLmFsdC1uYW1lLnBnLXNzbHRlc3Qu
+dGVzdIIdZG5zMi5hbHQtbmFtZS5wZy1zc2x0ZXN0LnRlc3QwDQYJKoZIhvcNAQEL
+BQADggEBAF+mfaw6iBPzpCgqq830pHRa3Yzm1aezt8SkeRohUYHNv/yCnDSRaqtj
+xbENih3lJMSTBL3g0wtTOHfH8ViC/h+lvYELHzXKic7gkjV7H5XETKGr0ZsjBBT2
+4cZQKbD9e0x0HrENXMYgGpBf747qL6uTOVJdG0s15hwpLq47bY5WUjXathejbpxW
+prmF8F+xaC52N9P/1VnqguQB909F4x1pyOK7D7tjFu+Y8Je7PHKbb6WY5K6xAv6t
+R17CY0749/FotlphquElUR2bs5Zzv5YrjUHPTcbwKvcH5cdNi93/u6NJt2xNAoYf
+aZERhX5TA9DYk4gC8OY0yGaYCIj3Dd4=
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.key b/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.key
new file mode 100644
index 0000000000..0ace41e0a1
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEogIBAAKCAQEA8xddbo/x2TOSIa/br8BNo/URdTr9+l2R5YojiZKDuLxiQVkg
+C30PJ2/CNFKIh2nHhRrzknI6sETVtrxZ+9V2qRc1yShVu462u0DHPRMIZnZIOZg3
+hlNB0cRWbOglUKlttIARNEQUcTUyPOtyo4/v+u0Ej5NTNcHFbFT01vdD9MjQiCO3
+jKdAwPIb14jTg4C71EpZ+LuelDo4DzF2/XgGWqUTrgD/XnBU/60PU9Iy3G0nVpx2
+1q6ppn9G7a9R+i8FjBcwW1T+cfsBDWhAv+biRmSAkENf8L8TwOlDQUwROkfz3Hz3
+6vuJjdkreQJsiqL0HnrnH5T5G9UzJO86FvZQ5wIDAQABAoIBAGv0BFoFMrHyZQLw
+xe7Wx6P4QTh+aiu1QgVdw0pk9nojrr62hbSUZRZuWyBBRsBcCW7i+Sgf8lA1QXNV
+UeC0e228EPa0It6YEi42JkTJHwHhpVFud7n/X0t4lajnryqTE1UFSp6bXTipFxZW
+uSJJ2ZjliRD5rApDcxkY4WJVjKg3aEt7P/DiM8iKGfyE6stq72VjEbJjdViMEcOP
+BNf0TiREZz5Mp7jAVWhpen0ebbLOBVWV4/ONNcL+yqR4mCEDUSFGewrTVX4zHL0A
+hYk198C5F8sFvEDnFkPco9sXMVanmLoI8sbhP4IIz9g4+GU6kFuj7fUKp11Azqv+
+3WQDKYECgYEA/XG4mmG/g8FG44y42mfZpUXWi1pwU4CQIrhkoU5j7EPQrvRboOOE
+Rv95jSwyZu4vCqjyI5FN1jCGTdhmt++R1e//zH6Hqa9Smo+jw7DtAFrCYd1JnCf1
+ToOwsYPHv4P7A8q8kc5vCNIv+AQSlP/wqdVNo3grdf7cGXkMtEY4F9UCgYEA9Yrq
+zWdnNGPATuSBqL6TSjQ37oR+dBD6WnGsiDenQkOzyDPFZ3CT1DjJghjEtxc8EfNf
+Oo8dMMR2q+5FZQo7WuqONEgyzKePiNR8RK2gOYpgdjN9bih1sAhHR10D26cpwlDJ
+bx7D5ZzENLbdZmfEiWwKswnaIhN4yMalgE0mP8sCgYAhzJy12ftUct4lUosEdX0N
+EXc/NlxshmSyfKzO5kllJNYbvvLJTg5B+agYL6C5IWKcpVNFcwdSXT5L+2QXe5eT
+VGJkvysQchUuD6HjYyD4PyJVMtGyRZHtWpqh0dU9sTg0lUD4oPMl1gIXrVNdE5Tg
+0VV9S3VgUxC/ROlw0TyB0QKBgGsVE0NS9hJF8mc1hko2GnwA++d8Rr2NbfElo+2f
+/8SJTA1ibpOm6AFkZpTjAl8qtdrKPVyHb16GP47Jkd/3r1z979hjKCxSYul0aWF2
+KusNKvZBjFEPOgv0AEniCb2wUCjbHI3mZ95qGLM4kKOJW4/m21+rS0MTJNjCsQic
+HLMzAoGAeCsY09d3m8xGeU+DuTPC6GH7Sgy/NBYqS5VaVNjb2jnuZlW2SSW2oiID
+4tXTi4ruKmHC898BfyFxhSMqub+tg3pVqIYADC71rnJLrVyc1SzoWzL7yMT3qFj7
+C7ZYZYmfG9agcZb5NkqKPTfCxkBhWbdgTTgBKVO/xQst8EUgko8=
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-ip-cn-only.crt b/src/test/ssl/ssl/server-ip-cn-only.crt
new file mode 100644
index 0000000000..9bf015cf18
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-only.crt
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC8TCCAdkCCCAhESkRN1IAMA0GCSqGSIb3DQEBCwUAMEIxQDA+BgNVBAMMN1Rl
+c3QgQ0EgZm9yIFBvc3RncmVTUUwgU1NMIHJlZ3Jlc3Npb24gdGVzdCBzZXJ2ZXIg
+Y2VydHMwHhcNMjExMTI5MTkzNzUyWhcNNDkwNDE2MTkzNzUyWjA0MR4wHAYDVQQL
+DBVQb3N0Z3JlU1FMIHRlc3Qgc3VpdGUxEjAQBgNVBAMMCTE5Mi4wLjIuMTCCASIw
+DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANWs1uUL71nHYF9Zj6p+M3MpYDvx
+32iCjVdtH5a2qpSWHXTg0rR8dLX0y92cvOYvMXHRajZT1avpHr8dooPYSVaXpGMK
+NvF/Qi+WFYovRbP2vmd1yv1cgW/FggbwJFWVobizIz4seyA4d0B2j9fqoi2OFBNP
+huW664SjF0u3p21tDy+43i2LNUMAKf6dnRR5Vqenath87LEU41tSLudu6NXgbFMk
+jvfNkl4d0w7YCzeXmklmSI+uaX3PlJJ4NzQO2j8w5BvnKVhNVD0KjgrXZ6nB/8F7
+Pg3XY+d7rJlwRgXemU6resWQDJ7+UaC9u7I4EIP+9lzCR/nNBqUktpHRmHUCAwEA
+ATANBgkqhkiG9w0BAQsFAAOCAQEAos1JncV8Yf4UaKl6h1GdYtcVtzFyJvBEnhRD
+07ldL+TYnfZiX8wK2ssBtM3cg/C78y5bzdUa5XGS83ZKQJFFdhE7PSnrvyNqyIqY
+ZgNBxto3gyvir+EjO1u9BAB0NP3r3gYoHRDZS1xOPPzt4WgjuUgTLM9k82GsqAbO
+UrOTOdRnkIqC5xLpa05EnRyJPRsR1w1PRJC2XXKnHIuFjMb4v7UuPwyCcX1P5ioc
+rQszQcORy/L+k0ezCkyweORg68htjYbBHuwOuiGfok6yKKDMzrTvD3lIslls6eX7
+4sI3XWqzkPmG9Vsxm9Vu9/Ma+PRO76VyCoIwBd+Ufg5vNXhMmw==
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-ip-cn-only.key b/src/test/ssl/ssl/server-ip-cn-only.key
new file mode 100644
index 0000000000..1966530e72
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-only.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEA1azW5QvvWcdgX1mPqn4zcylgO/HfaIKNV20flraqlJYddODS
+tHx0tfTL3Zy85i8xcdFqNlPVq+kevx2ig9hJVpekYwo28X9CL5YVii9Fs/a+Z3XK
+/VyBb8WCBvAkVZWhuLMjPix7IDh3QHaP1+qiLY4UE0+G5brrhKMXS7enbW0PL7je
+LYs1QwAp/p2dFHlWp6dq2HzssRTjW1Iu527o1eBsUySO982SXh3TDtgLN5eaSWZI
+j65pfc+Ukng3NA7aPzDkG+cpWE1UPQqOCtdnqcH/wXs+Dddj53usmXBGBd6ZTqt6
+xZAMnv5RoL27sjgQg/72XMJH+c0GpSS2kdGYdQIDAQABAoIBAQDNXviU4WnF8rmQ
+K7bH+dBdqbETLKC8BG7xTrMD2sINWlMpmUUrsEtE7+paMGHnJAj0CoF5gg5m0wN4
+UXV4H5QtpEad4p14dAYbUreVP2ZRWKEdM7xM1HKcCUu2e22QzObJbXQ8N+iHyX3k
++Y+7yYrjGiH1hYR0nbnsnAyx++zyYBSQeqzpdQwf/BLY5xZmyYWNfqbckiMpEqMs
+EmZmGXnCjIipzEC0LQHoSW9PNa92Z9bvuxOKYl8iHYDDXjvMRFoZBSiMXpzHQocb
+QlQ5F4ayfW2OrOhpNbY7niYM9GN3Bk9TgMP+0BkJE6uuktLYW35LY1M78CCPWcWb
+npJNK3QBAoGBAOxkGrhAHAysSmtirIyMdvySb76wb/Ukfi+AULKz20FI5j4/GXm9
+qCb2GeT+FFSUHeSC8f0EFnosRYkdBGruqeZioI+5rUkboYFJPspAHAuvg9kgtfF+
+kvphD4O4P/foYsEZRx66FHozDbhrrR5UXc7KzqRIASc/D3FOx2UFJLb1AoGBAOdm
+WcaMvYygl9ZW+ThWAR1xG1X70AGKwrlrpF2hBkWYxSurxSMXnD0DUzC9Nb4EyCaM
+c2uSqEZOKdW+XfXtK2DnqXKfb3YCVEoGN4gVfyuW/vxii/+ZxLo3md/b3vrkZEVp
+pfkXy/HoZ71YN7bNpcDpOnhml6vvuCRCYFnI1WuBAoGAC0shB6pwbJ6Sk5zMN47C
+ZICufAK75o9OxAAyWsdC81SDQ3gKRImuDeZ2CD2nRP8qim9DFl5qoH2a+Nj9DArI
+7SvLFfK9958tURrpuAnmDRzehLIOXzI33WRjtFxKGhLtHOKTRkGHlur3fdcPF0La
+lHWV971E6NYXa8diuU3Mmj0CgYBYd+ka3/QYL83dRKNDxp3mg7fPx9ZewI5yFZVh
+to6PTTkU2Tclk4FIUl0b5TsGyw06r7fxCMENIBUegwmpXGOZSPifuhUDKSDQrE/O
+12knYTNbitG7hy6Pg3JxA77cbTVo1FuAQHjYo+IFohSq7zTP7FtObOrP8XaVZksw
+CHiQAQKBgBW4EiA9AAnZ1LOpifAvM7bs0NHg95qTwtAL52WKom2ga2H+lMhxeu6Y
+hUSytC/f9kALVcYloZhkLYpO07x1gXmy7f4parMjA4Ex+4vfu3kPd8GiNGZ+AUJD
+nnJ1OINY9ziXJZfju7FpVWpkiuPzWCh6y/o3gZ/veq5mIUxuDMVa
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/sslfiles.mk b/src/test/ssl/sslfiles.mk
index 270f55a58f..6185cf75aa 100644
--- a/src/test/ssl/sslfiles.mk
+++ b/src/test/ssl/sslfiles.mk
@@ -22,9 +22,14 @@
 # key/certificate pair will be generated for you, signed by the appropriate CA.
 #
 SERVERS := server-cn-and-alt-names \
+	server-cn-and-ip-alt-names \
 	server-cn-only \
+	server-ip-cn-only \
+	server-ip-cn-and-alt-names \
+	server-ip-cn-and-dns-alt-names \
 	server-single-alt-name \
 	server-multiple-alt-names \
+	server-ip-alt-names \
 	server-no-names \
 	server-revoked
 CLIENTS := client client-dn client-revoked client_ext
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 779ab66838..a7cce770ed 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -21,7 +21,7 @@ if ($ENV{with_ssl} ne 'openssl')
 }
 else
 {
-	plan tests => 110;
+	plan tests => 135;
 }
 
 #### Some configuration
@@ -253,6 +253,23 @@ $node->connect_fails(
 	  qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/
 );
 
+# Test with an IP address in the Common Name. This is a strange corner case that
+# nevertheless is supported, as long as the address string matches exactly.
+switch_server_cert($node, 'server-ip-cn-only');
+
+$common_connstr =
+  "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+
+$node->connect_ok("$common_connstr host=192.0.2.1",
+	"IP address in the Common Name");
+
+$node->connect_fails(
+	"$common_connstr host=192.000.002.001",
+	"mismatch between host name and server certificate IP address",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.1" does not match host name "192.000.002.001"\E/
+);
+
 # Test Subject Alternative Names.
 switch_server_cert($node, 'server-multiple-alt-names');
 
@@ -305,7 +322,54 @@ $node->connect_fails(
 	  qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/
 );
 
-# Test server certificate with a CN and SANs. Per RFCs 2818 and 6125, the CN
+# Test certificate with IP addresses in the SANs.
+switch_server_cert($node, 'server-ip-alt-names');
+
+$node->connect_ok(
+	"$common_connstr host=192.0.2.1",
+	"host matching an IPv4 address (Subject Alternative Name 1)");
+
+$node->connect_ok(
+	"$common_connstr host=192.000.002.001",
+	"host matching an IPv4 address in alternate form (Subject Alternative Name 1)");
+
+$node->connect_fails(
+	"$common_connstr host=192.0.2.2",
+	"host not matching an IPv4 address (Subject Alternative Name 1)",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.1" (and 1 other name) does not match host name "192.0.2.2"\E/
+);
+
+$node->connect_fails(
+	"$common_connstr host=192.0.2.1/32",
+	"IPv4 host with CIDR mask does not match",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.1" (and 1 other name) does not match host name "192.0.2.1\/32"\E/
+);
+
+$node->connect_ok(
+	"$common_connstr host=2001:DB8::1",
+	"host matching an IPv6 address (Subject Alternative Name 2)");
+
+$node->connect_ok(
+	"$common_connstr host=2001:db8:0:0:0:0:0:1",
+	"host matching an IPv6 address in alternate form (Subject Alternative Name 2)");
+
+$node->connect_fails(
+	"$common_connstr host=::1",
+	"host not matching an IPv6 address (Subject Alternative Name 2)",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.1" (and 1 other name) does not match host name "::1"\E/
+);
+
+$node->connect_fails(
+	"$common_connstr host=2001:DB8::1/128",
+	"IPv6 host with CIDR mask does not match",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.1" (and 1 other name) does not match host name "2001:DB8::1\/128"\E/
+);
+
+# Test server certificate with a CN and DNS SANs. Per RFCs 2818 and 6125, the CN
 # should be ignored when the certificate has both.
 switch_server_cert($node, 'server-cn-and-alt-names');
 
@@ -323,6 +387,39 @@ $node->connect_fails(
 	  qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/
 );
 
+# But we will fall back to check the CN if the SANs contain only IP addresses.
+switch_server_cert($node, 'server-cn-and-ip-alt-names');
+
+$node->connect_ok("$common_connstr host=common-name.pg-ssltest.test",
+	"certificate with both a CN and IP SANs matches CN");
+$node->connect_ok("$common_connstr host=192.0.2.1",
+	"certificate with both a CN and IP SANs matches SAN 1");
+$node->connect_ok("$common_connstr host=2001:db8::1",
+	"certificate with both a CN and IP SANs matches SAN 2");
+
+# And now the same tests, but with IP addresses and DNS names swapped.
+switch_server_cert($node, 'server-ip-cn-and-alt-names');
+
+$node->connect_ok("$common_connstr host=192.0.2.2",
+	"certificate with both an IP CN and IP SANs 1");
+$node->connect_ok("$common_connstr host=2001:db8::1",
+	"certificate with both an IP CN and IP SANs 2");
+$node->connect_fails(
+	"$common_connstr host=192.0.2.1",
+	"certificate with both an IP CN and IP SANs ignores CN",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.2" (and 1 other name) does not match host name "192.0.2.1"\E/
+);
+
+switch_server_cert($node, 'server-ip-cn-and-dns-alt-names');
+
+$node->connect_ok("$common_connstr host=192.0.2.1",
+	"certificate with both an IP CN and DNS SANs matches CN");
+$node->connect_ok("$common_connstr host=dns1.alt-name.pg-ssltest.test",
+	"certificate with both an IP CN and DNS SANs matches SAN 1");
+$node->connect_ok("$common_connstr host=dns2.alt-name.pg-ssltest.test",
+	"certificate with both an IP CN and DNS SANs matches SAN 2");
+
 # Finally, test a server certificate that has no CN or SANs. Of course, that's
 # not a very sensible certificate, but libpq should handle it gracefully.
 switch_server_cert($node, 'server-no-names');
-- 
2.25.1

#12Jacob Champion
pchampion@vmware.com
In reply to: Jacob Champion (#9)
3 attachment(s)
Re: [PATCH] Accept IP addresses in server certificate SANs

On Mon, 2022-01-03 at 16:19 +0000, Jacob Champion wrote:

On Fri, 2021-12-17 at 15:40 +0900, Kyotaro Horiguchi wrote:

+ inet_net_pton_ipv4(const char *src, u_char *dst)
(calls inet_net_pton_ipv4_internal(src, dst, true))
+ inet_pton_ipv4(const char *src, u_char *dst)
(calls inet_net_pton_ipv4_internal(src, dst, false))

Sounds good, I will make that change. Thanks for the feedback!

v3 implements a pg_inet_pton(), but for IPv6 instead of IPv4 as
presented above (since we only need inet_pton() for IPv6 in this case).
It's split into a separate patch (0003) for ease of review.

Thanks!
--Jacob

Attachments:

v3-0001-Move-inet_net_pton-to-src-port.patchtext/x-patch; name=v3-0001-Move-inet_net_pton-to-src-port.patchDownload
From 3081b1e0f7af60241978c77bba71e806ba5c25f6 Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Wed, 24 Nov 2021 14:46:11 -0800
Subject: [PATCH v3 1/3] Move inet_net_pton() to src/port

This will be helpful for IP address verification in libpq.
---
 src/backend/utils/adt/Makefile                  |  1 -
 src/include/port.h                              |  3 +++
 src/include/utils/builtins.h                    |  4 ----
 src/port/Makefile                               |  1 +
 src/{backend/utils/adt => port}/inet_net_pton.c | 16 +++++++++++++++-
 src/tools/msvc/Mkvcbuild.pm                     |  2 +-
 6 files changed, 20 insertions(+), 7 deletions(-)
 rename src/{backend/utils/adt => port}/inet_net_pton.c (96%)

diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile
index 41b486bcef..d173d52157 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -43,7 +43,6 @@ OBJS = \
 	geo_selfuncs.o \
 	geo_spgist.o \
 	inet_cidr_ntop.o \
-	inet_net_pton.o \
 	int.o \
 	int8.o \
 	json.o \
diff --git a/src/include/port.h b/src/include/port.h
index fd9c9d6f94..cca29839bc 100644
--- a/src/include/port.h
+++ b/src/include/port.h
@@ -518,6 +518,9 @@ extern int	pg_codepage_to_encoding(UINT cp);
 extern char *pg_inet_net_ntop(int af, const void *src, int bits,
 							  char *dst, size_t size);
 
+/* port/inet_net_pton.c */
+extern int	pg_inet_net_pton(int af, const char *src, void *dst, size_t size);
+
 /* port/pg_strong_random.c */
 extern void pg_strong_random_init(void);
 extern bool pg_strong_random(void *buf, size_t len);
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index b07eefaf1e..a5a18e62ba 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -92,10 +92,6 @@ extern int	xidComparator(const void *arg1, const void *arg2);
 extern char *pg_inet_cidr_ntop(int af, const void *src, int bits,
 							   char *dst, size_t size);
 
-/* inet_net_pton.c */
-extern int	pg_inet_net_pton(int af, const char *src,
-							 void *dst, size_t size);
-
 /* network.c */
 extern double convert_network_to_scalar(Datum value, Oid typid, bool *failure);
 extern Datum network_scan_first(Datum in);
diff --git a/src/port/Makefile b/src/port/Makefile
index b3754d8940..7191cd6633 100644
--- a/src/port/Makefile
+++ b/src/port/Makefile
@@ -43,6 +43,7 @@ OBJS = \
 	bsearch_arg.o \
 	chklocale.o \
 	inet_net_ntop.o \
+	inet_net_pton.o \
 	noblock.o \
 	path.o \
 	pg_bitutils.o \
diff --git a/src/backend/utils/adt/inet_net_pton.c b/src/port/inet_net_pton.c
similarity index 96%
rename from src/backend/utils/adt/inet_net_pton.c
rename to src/port/inet_net_pton.c
index d3221a1313..bae50ba67e 100644
--- a/src/backend/utils/adt/inet_net_pton.c
+++ b/src/port/inet_net_pton.c
@@ -14,14 +14,18 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
  * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  *
- *	  src/backend/utils/adt/inet_net_pton.c
+ *	  src/port/inet_net_pton.c
  */
 
 #if defined(LIBC_SCCS) && !defined(lint)
 static const char rcsid[] = "Id: inet_net_pton.c,v 1.4.2.3 2004/03/17 00:40:11 marka Exp $";
 #endif
 
+#ifndef FRONTEND
 #include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
 
 #include <sys/socket.h>
 #include <netinet/in.h>
@@ -29,9 +33,19 @@ static const char rcsid[] = "Id: inet_net_pton.c,v 1.4.2.3 2004/03/17 00:40:11 m
 #include <assert.h>
 #include <ctype.h>
 
+#ifndef FRONTEND
 #include "utils/builtins.h" /* pgrminclude ignore */	/* needed on some
 														 * platforms */
 #include "utils/inet.h"
+#else
+/*
+ * In a frontend build, we can't include inet.h, but we still need to have
+ * sensible definitions of these two constants.  Note that pg_inet_net_ntop()
+ * assumes that PGSQL_AF_INET is equal to AF_INET.
+ */
+#define PGSQL_AF_INET	(AF_INET + 0)
+#define PGSQL_AF_INET6	(AF_INET + 1)
+#endif
 
 
 static int	inet_net_pton_ipv4(const char *src, u_char *dst);
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 404c45a6f3..fa87511e04 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -100,7 +100,7 @@ sub mkvcbuild
 
 	our @pgportfiles = qw(
 	  chklocale.c explicit_bzero.c fls.c getpeereid.c getrusage.c inet_aton.c
-	  getaddrinfo.c gettimeofday.c inet_net_ntop.c kill.c open.c
+	  getaddrinfo.c gettimeofday.c inet_net_ntop.c inet_net_pton.c kill.c open.c
 	  snprintf.c strlcat.c strlcpy.c dirmod.c noblock.c path.c
 	  dirent.c dlopen.c getopt.c getopt_long.c link.c
 	  pread.c preadv.c pwrite.c pwritev.c pg_bitutils.c
-- 
2.25.1

v3-0002-libpq-allow-IP-address-SANs-in-server-certs.patchtext/x-patch; name=v3-0002-libpq-allow-IP-address-SANs-in-server-certs.patchDownload
From 84987001f0fc7b5a6018c3f54684b780fee204cd Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Thu, 18 Nov 2021 15:36:18 -0800
Subject: [PATCH v3 2/3] libpq: allow IP address SANs in server certs

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.

- Unlike NSS, we don't map IPv4 to IPv6 addresses, or vice-versa.
---
 src/interfaces/libpq/fe-secure-common.c       | 102 ++++++++++++++++
 src/interfaces/libpq/fe-secure-common.h       |  11 ++
 src/interfaces/libpq/fe-secure-openssl.c      | 114 ++++++++++++++++--
 .../conf/server-cn-and-ip-alt-names.config    |  24 ++++
 src/test/ssl/conf/server-ip-alt-names.config  |  19 +++
 .../conf/server-ip-cn-and-alt-names.config    |  21 ++++
 .../server-ip-cn-and-dns-alt-names.config     |  21 ++++
 src/test/ssl/conf/server-ip-cn-only.config    |  12 ++
 .../ssl/ssl/server-cn-and-ip-alt-names.crt    |  20 +++
 .../ssl/ssl/server-cn-and-ip-alt-names.key    |  27 +++++
 src/test/ssl/ssl/server-ip-alt-names.crt      |  19 +++
 src/test/ssl/ssl/server-ip-alt-names.key      |  27 +++++
 .../ssl/ssl/server-ip-cn-and-alt-names.crt    |  19 +++
 .../ssl/ssl/server-ip-cn-and-alt-names.key    |  27 +++++
 .../ssl/server-ip-cn-and-dns-alt-names.crt    |  20 +++
 .../ssl/server-ip-cn-and-dns-alt-names.key    |  27 +++++
 src/test/ssl/ssl/server-ip-cn-only.crt        |  18 +++
 src/test/ssl/ssl/server-ip-cn-only.key        |  27 +++++
 src/test/ssl/sslfiles.mk                      |   5 +
 src/test/ssl/t/001_ssltests.pl                | 101 +++++++++++++++-
 20 files changed, 647 insertions(+), 14 deletions(-)
 create mode 100644 src/test/ssl/conf/server-cn-and-ip-alt-names.config
 create mode 100644 src/test/ssl/conf/server-ip-alt-names.config
 create mode 100644 src/test/ssl/conf/server-ip-cn-and-alt-names.config
 create mode 100644 src/test/ssl/conf/server-ip-cn-and-dns-alt-names.config
 create mode 100644 src/test/ssl/conf/server-ip-cn-only.config
 create mode 100644 src/test/ssl/ssl/server-cn-and-ip-alt-names.crt
 create mode 100644 src/test/ssl/ssl/server-cn-and-ip-alt-names.key
 create mode 100644 src/test/ssl/ssl/server-ip-alt-names.crt
 create mode 100644 src/test/ssl/ssl/server-ip-alt-names.key
 create mode 100644 src/test/ssl/ssl/server-ip-cn-and-alt-names.crt
 create mode 100644 src/test/ssl/ssl/server-ip-cn-and-alt-names.key
 create mode 100644 src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.crt
 create mode 100644 src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.key
 create mode 100644 src/test/ssl/ssl/server-ip-cn-only.crt
 create mode 100644 src/test/ssl/ssl/server-ip-cn-only.key

diff --git a/src/interfaces/libpq/fe-secure-common.c b/src/interfaces/libpq/fe-secure-common.c
index afa5d133e1..796b78e348 100644
--- a/src/interfaces/libpq/fe-secure-common.c
+++ b/src/interfaces/libpq/fe-secure-common.c
@@ -19,9 +19,13 @@
 
 #include "postgres_fe.h"
 
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
 #include "fe-secure-common.h"
 
 #include "libpq-int.h"
+#include "port.h"
 #include "pqexpbuffer.h"
 
 /*
@@ -144,6 +148,104 @@ pq_verify_peer_name_matches_certificate_name(PGconn *conn,
 	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 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 = PGSQL_AF_INET;
+
+		/*
+		 * The use of inet_aton() instead of inet_pton() is deliberate; the
+		 * latter cannot handle alternate IPv4 notations ("numbers-and-dots").
+		 */
+		if (inet_aton(host, &addr))
+		{
+			if (memcmp(ipdata, &addr.s_addr, iplen) == 0)
+				match = 1;
+		}
+	}
+	else if (iplen == 16)
+	{
+		/* IPv6 */
+		unsigned char addr[16];
+
+		family = PGSQL_AF_INET6;
+
+		/*
+		 * pg_inet_net_pton() will accept CIDR masks, which we don't want to
+		 * match, so skip the comparison if the host string contains a slash.
+		 */
+		if (!strchr(host, '/')
+			&& pg_inet_net_pton(PGSQL_AF_INET6, host, addr, -1) == 128)
+		{
+			if (memcmp(ipdata, addr, iplen) == 0)
+				match = 1;
+		}
+	}
+	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.
  *
diff --git a/src/interfaces/libpq/fe-secure-common.h b/src/interfaces/libpq/fe-secure-common.h
index 2389f6717a..29d4f54230 100644
--- a/src/interfaces/libpq/fe-secure-common.h
+++ b/src/interfaces/libpq/fe-secure-common.h
@@ -18,9 +18,20 @@
 
 #include "libpq-fe.h"
 
+/*
+ * In a frontend build, we can't include inet.h, but we still need to have
+ * sensible definitions of these two constants.  Note that pg_inet_net_ntop()
+ * assumes that PGSQL_AF_INET is equal to AF_INET.
+ */
+#define PGSQL_AF_INET	(AF_INET + 0)
+#define PGSQL_AF_INET6	(AF_INET + 1)
+
 extern int	pq_verify_peer_name_matches_certificate_name(PGconn *conn,
 														 const char *namedata, size_t namelen,
 														 char **store_name);
+extern int	pq_verify_peer_name_matches_certificate_ip(PGconn *conn,
+													   const char *addrdata, size_t addrlen,
+													   char **store_name);
 extern bool pq_verify_peer_name_matches_certificate(PGconn *conn);
 
 #endif							/* FE_SECURE_COMMON_H */
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index 33f095c12e..479f63197a 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -72,6 +72,9 @@ static int	verify_cb(int ok, X509_STORE_CTX *ctx);
 static int	openssl_verify_peer_name_matches_certificate_name(PGconn *conn,
 															  ASN1_STRING *name,
 															  char **store_name);
+static int	openssl_verify_peer_name_matches_certificate_ip(PGconn *conn,
+															ASN1_OCTET_STRING *addr_entry,
+															char **store_name);
 static void destroy_ssl_system(void);
 static int	initialize_SSL(PGconn *conn);
 static PostgresPollingStatusType open_client_SSL(PGconn *);
@@ -509,6 +512,52 @@ openssl_verify_peer_name_matches_certificate_name(PGconn *conn, ASN1_STRING *nam
 	return pq_verify_peer_name_matches_certificate_name(conn, (const char *) namedata, len, store_name);
 }
 
+/*
+ * OpenSSL-specific wrapper around
+ * pq_verify_peer_name_matches_certificate_ip(), converting the
+ * ASN1_OCTET_STRING into a plain C string.
+ */
+static int
+openssl_verify_peer_name_matches_certificate_ip(PGconn *conn,
+												ASN1_OCTET_STRING *addr_entry,
+												char **store_name)
+{
+	int			len;
+	const unsigned char *addrdata;
+
+	/* Should not happen... */
+	if (addr_entry == NULL)
+	{
+		appendPQExpBufferStr(&conn->errorMessage,
+							 libpq_gettext("SSL certificate's address entry is missing\n"));
+		return -1;
+	}
+
+	/*
+	 * GEN_IPADD is an OCTET STRING containing an IP address in network byte
+	 * order.
+	 */
+#ifdef HAVE_ASN1_STRING_GET0_DATA
+	addrdata = ASN1_STRING_get0_data(addr_entry);
+#else
+	addrdata = ASN1_STRING_data(addr_entry);
+#endif
+	len = ASN1_STRING_length(addr_entry);
+
+	/* OK to cast from unsigned to plain char, since it's all ASCII. */
+	return pq_verify_peer_name_matches_certificate_ip(conn, (const char *) addrdata, len, store_name);
+}
+
+static bool
+is_ip_address(const char *host)
+{
+	struct in_addr	dummy4;
+	unsigned char	dummy6[16];
+
+	return inet_aton(host, &dummy4)
+		|| (pg_inet_net_pton(PGSQL_AF_INET6, host, dummy6, -1) == 128);
+}
+
 /*
  *	Verify that the server certificate matches the hostname we connected to.
  *
@@ -522,6 +571,36 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 	STACK_OF(GENERAL_NAME) * peer_san;
 	int			i;
 	int			rc = 0;
+	char	   *host = conn->connhost[conn->whichhost].host;
+	int			host_type;
+	bool		check_cn = true;
+
+	Assert(host && host[0]); /* should be guaranteed by caller */
+
+	/*
+	 * We try to match the NSS behavior here, which is a slight departure from
+	 * the spec but seems to make more intuitive sense:
+	 *
+	 * If connhost contains a DNS name, and the certificate's SANs contain any
+	 * dNSName entries, then we'll ignore the Subject Common Name entirely;
+	 * otherwise, we fall back to checking the CN. (This behavior matches the
+	 * RFC.)
+	 *
+	 * If connhost contains an IP address, and the SANs contain iPAddress
+	 * entries, we again ignore the CN. Otherwise, we allow the CN to match,
+	 * EVEN IF there is a dNSName in the SANs. (RFC 6125 prohibits this: "A
+	 * client MUST NOT seek a match for a reference identifier of CN-ID if the
+	 * presented identifiers include a DNS-ID, SRV-ID, URI-ID, or any
+	 * application-specific identifier types supported by the client.")
+	 *
+	 * NOTE: Prior versions of libpq did not consider iPAddress entries at all,
+	 * so this new behavior might break a certificate that has different IP
+	 * addresses in the Subject CN and the SANs.
+	 */
+	if (is_ip_address(host))
+		host_type = GEN_IPADD;
+	else
+		host_type = GEN_DNS;
 
 	/*
 	 * First, get the Subject Alternative Names (SANs) from the certificate,
@@ -537,24 +616,34 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 		for (i = 0; i < san_len; i++)
 		{
 			const GENERAL_NAME *name = sk_GENERAL_NAME_value(peer_san, i);
+			char	   *alt_name = NULL;
+
+			if (name->type == host_type)
+				check_cn = false;
 
 			if (name->type == GEN_DNS)
 			{
-				char	   *alt_name;
-
 				(*names_examined)++;
 				rc = openssl_verify_peer_name_matches_certificate_name(conn,
 																	   name->d.dNSName,
 																	   &alt_name);
+			}
+			else if (name->type == GEN_IPADD)
+			{
+				(*names_examined)++;
+				rc = openssl_verify_peer_name_matches_certificate_ip(conn,
+																	 name->d.iPAddress,
+																	 &alt_name);
+			}
 
-				if (alt_name)
-				{
-					if (!*first_name)
-						*first_name = alt_name;
-					else
-						free(alt_name);
-				}
+			if (alt_name)
+			{
+				if (!*first_name)
+					*first_name = alt_name;
+				else
+					free(alt_name);
 			}
+
 			if (rc != 0)
 				break;
 		}
@@ -562,13 +651,14 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 	}
 
 	/*
-	 * If there is no subjectAltName extension of type dNSName, check the
+	 * If there is no subjectAltName extension of the matching type, check the
 	 * Common Name.
 	 *
 	 * (Per RFC 2818 and RFC 6125, if the subjectAltName extension of type
-	 * dNSName is present, the CN must be ignored.)
+	 * dNSName is present, the CN must be ignored. We break this rule if host is
+	 * an IP address; see the comment above.)
 	 */
-	if (*names_examined == 0)
+	if ((rc == 0) && check_cn)
 	{
 		X509_NAME  *subject_name;
 
diff --git a/src/test/ssl/conf/server-cn-and-ip-alt-names.config b/src/test/ssl/conf/server-cn-and-ip-alt-names.config
new file mode 100644
index 0000000000..a6fa09bad3
--- /dev/null
+++ b/src/test/ssl/conf/server-cn-and-ip-alt-names.config
@@ -0,0 +1,24 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+# This certificate contains a CN and SANs for both IPv4 and IPv6.
+
+
+[ req ]
+distinguished_name     = req_distinguished_name
+req_extensions         = v3_req
+prompt                 = no
+
+[ req_distinguished_name ]
+# Note: According to RFC 2818 and 6125, the CN is ignored, when DNS names are
+# present in the SANs. But they are silent on whether the CN is checked when IP
+# addresses are present.
+CN = common-name.pg-ssltest.test
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
+subjectAltName = @alt_names
+
+[ alt_names ]
+IP.1 = 192.0.2.1
+IP.2 = 2001:DB8::1
diff --git a/src/test/ssl/conf/server-ip-alt-names.config b/src/test/ssl/conf/server-ip-alt-names.config
new file mode 100644
index 0000000000..c22f22951a
--- /dev/null
+++ b/src/test/ssl/conf/server-ip-alt-names.config
@@ -0,0 +1,19 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+# This certificate has a two IP-address SANs, and no CN.
+
+[ req ]
+distinguished_name     = req_distinguished_name
+req_extensions         = v3_req
+prompt                 = no
+
+[ req_distinguished_name ]
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
+subjectAltName = @alt_names
+
+[ alt_names ]
+IP.1 = 192.0.2.1
+IP.2 = 2001:DB8::1
diff --git a/src/test/ssl/conf/server-ip-cn-and-alt-names.config b/src/test/ssl/conf/server-ip-cn-and-alt-names.config
new file mode 100644
index 0000000000..a4087f0a18
--- /dev/null
+++ b/src/test/ssl/conf/server-ip-cn-and-alt-names.config
@@ -0,0 +1,21 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+# This certificate contains both a CN and SANs in IP address format.
+
+
+[ req ]
+distinguished_name     = req_distinguished_name
+req_extensions         = v3_req
+prompt                 = no
+
+[ req_distinguished_name ]
+CN = 192.0.2.1
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
+subjectAltName = @alt_names
+
+[ alt_names ]
+IP.1 = 192.0.2.2
+IP.2 = 2001:DB8::1
diff --git a/src/test/ssl/conf/server-ip-cn-and-dns-alt-names.config b/src/test/ssl/conf/server-ip-cn-and-dns-alt-names.config
new file mode 100644
index 0000000000..7121803b49
--- /dev/null
+++ b/src/test/ssl/conf/server-ip-cn-and-dns-alt-names.config
@@ -0,0 +1,21 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+# This certificate contains both a CN and SANs in IP address format.
+
+
+[ req ]
+distinguished_name     = req_distinguished_name
+req_extensions         = v3_req
+prompt                 = no
+
+[ req_distinguished_name ]
+CN = 192.0.2.1
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
+subjectAltName = @alt_names
+
+[ alt_names ]
+DNS.1 = dns1.alt-name.pg-ssltest.test
+DNS.2 = dns2.alt-name.pg-ssltest.test
diff --git a/src/test/ssl/conf/server-ip-cn-only.config b/src/test/ssl/conf/server-ip-cn-only.config
new file mode 100644
index 0000000000..585d8bdae8
--- /dev/null
+++ b/src/test/ssl/conf/server-ip-cn-only.config
@@ -0,0 +1,12 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+
+[ req ]
+distinguished_name     = req_distinguished_name
+prompt                 = no
+
+[ req_distinguished_name ]
+CN = 192.0.2.1
+OU = PostgreSQL test suite
+
+# No Subject Alternative Names
diff --git a/src/test/ssl/ssl/server-cn-and-ip-alt-names.crt b/src/test/ssl/ssl/server-cn-and-ip-alt-names.crt
new file mode 100644
index 0000000000..4e58c85ccb
--- /dev/null
+++ b/src/test/ssl/ssl/server-cn-and-ip-alt-names.crt
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDLzCCAhegAwIBAgIIICERKRE1UQAwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UE
+Aww3VGVzdCBDQSBmb3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHNl
+cnZlciBjZXJ0czAeFw0yMTExMjkxOTM1NTFaFw00OTA0MTYxOTM1NTFaMEYxHjAc
+BgNVBAsMFVBvc3RncmVTUUwgdGVzdCBzdWl0ZTEkMCIGA1UEAwwbY29tbW9uLW5h
+bWUucGctc3NsdGVzdC50ZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
+AQEA6+8IYKAFnZ7V+fDo1cyMpbGBLzCfJOQ/1o2jOGP4+GjpsZgv6S6UT2MheC8M
+iiEFrYwdsSIZyYc3jEZrluy/UuR0bCGtqU92BCqa0iBLhvHOgjR588u253eLxQtQ
+8iJn11QPrKMk35nMkmY8GfHt4sGFbvBL6+GpipHq7a6cde3Z+v4kCB5dKMYDUDtm
+3mJmviuGNAu5wOqItk2Yi5dwJs1054007KNH0Il43urxiOfnkLS0cG5kehboPf86
+vxBt3iHByrU/9/DY5IvQCfSXVNa6rb5w5/pGja9aCei6Mv1jQY/V8SMQTga+MOsA
+0WB9akxMi2NxwS2+BQ4k/McPlwIDAQABoyUwIzAhBgNVHREEGjAYhwTAAAIBhxAg
+AQ24AAAAAAAAAAAAAAABMA0GCSqGSIb3DQEBCwUAA4IBAQAQLo2RzC07dG9p+J3A
+W6C0p3Y+Os/YE2D9wfp4TIDTZxcRUQZ0S6ahF1N6sp8l9KHBJHPU1cUpRAU1oD+Y
+SqmnP/VJRRDTTj9Ytdc/Vuo2jeLpSYhVKrCqtjqIrCwYJFoYRmMoxTtJGlwA0hSd
+kwo3XYrALPUQWUErTYPvNfDNIuUwqUXNfS0CXuIOVN3LJ+shegg6Pwbh9B5T9NHx
+kH+HswajhdpdnZIgh0FYTlTCPILDrB49aOWwqLa54AUA6WXa35hPsP8SoqL9Eucq
+ifPhBYyadsjOb+70N8GbbAsDPN1jCX9L8RuNcEkxSCKCYx91cWXh7K5KMPuGlzB7
+j8xB
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-cn-and-ip-alt-names.key b/src/test/ssl/ssl/server-cn-and-ip-alt-names.key
new file mode 100644
index 0000000000..837eef996d
--- /dev/null
+++ b/src/test/ssl/ssl/server-cn-and-ip-alt-names.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA6+8IYKAFnZ7V+fDo1cyMpbGBLzCfJOQ/1o2jOGP4+GjpsZgv
+6S6UT2MheC8MiiEFrYwdsSIZyYc3jEZrluy/UuR0bCGtqU92BCqa0iBLhvHOgjR5
+88u253eLxQtQ8iJn11QPrKMk35nMkmY8GfHt4sGFbvBL6+GpipHq7a6cde3Z+v4k
+CB5dKMYDUDtm3mJmviuGNAu5wOqItk2Yi5dwJs1054007KNH0Il43urxiOfnkLS0
+cG5kehboPf86vxBt3iHByrU/9/DY5IvQCfSXVNa6rb5w5/pGja9aCei6Mv1jQY/V
+8SMQTga+MOsA0WB9akxMi2NxwS2+BQ4k/McPlwIDAQABAoIBAQCuNFKVNdKvrUYF
+RLJGmsAG3+eo9lern7TbML2ht39vu9dBwEMwA6qSa3mdCfBSVUuh9uE9lxY/TU3g
+j2aFi81A4VptNPjLGNblAKhMGnhp7UUzspeRQYuNoSFcnpxoDKtrvK/OIq/pQeBh
+AIfECHRDh+yEG32Tb44FuPQkB1eTYl8xbMEImrhNUaSjJk7tTsmydHy0DjmqHVKX
+HUj0TREfDBDOBiHtY0XV6Pu3bnqDH/TKLTfUf3UdfTuay3Yai9aEcRPWp9GrMO7G
+axsKCifTz6177gyr6Fv8HLeMZMh9rMZRn3e0zfaF6vrH1QnZZOts5jpUa0KugSCd
+//uC0iNxAoGBAPXVc3b+o3hY5gcwwpaW6JtsarDrmNRxrizqIDG7NgpqwdFXgTi6
+6q0t2pjv81ATqij69IcPkNSissyR4OEKnu/OFJWzreg8yLi75WHKi0E/6msHpwRk
+d1yP0Zgd05ots/yOjDSp593RagaPVvHBxMECZ/Tm3B+Tq55Azudd/zvLAoGBAPWw
+xf0oUEJl6NdUZD6K7eFc6jf8yrpD85dldeko6LeN8x0XlKKWvUDJ2+3oizXoQvCm
+8by6KOYEIo4MrtXuy9MmtPWfNvRBr+hsUHchIj7IgFa9bKXyK2FnJqu/8CbEymli
+eZu7hoOhelurhnFy1zSqwNO4GC+kw60Y/BO3Z1nlAoGAVOyYJtNwxXJwhKtjjYI0
+ePzLHrNE6J8c/Ick+AkkchTPP/JqwZ5Q0+KzUYITG+avMdkAAGhwMATEn8cFWLjC
+jzUyB0U7Hq9g5/CBHXdLBA+Ae9j46ZuLYH6OeW5UWz7OnsDfzpGjeA2QAxQhhQLb
+ZZHfN8tI39+zucfJskPWmGECgYEAg9guF1Fn6InJrqwR82IYj6SN6CeXHufSM392
+C/4xDDd3rDf4QlwECV2J0RzGf9I5Ae2EshNwWScE6Be0RweTh6cw2tJq6h7J6D8f
+2x4Dw49TF7klMdRIJUf2f5pLpHJccLswqTqzz7V69PCSABVxmUi8m6EiEYconp5W
+v7nfE2UCgYALrEqzncuSIX3q6TVAjnzT7gO4h8h2TUekIWdHQFldFx8R7Kncggnd
+48gQqhewchNR83UCcd7pPsCcTqu6UR1QRdq/DV5P6J3xdZ2iS/2gCM6hvWIvKZEv
+/ClnkyFCOW7zX6RKIXtRYZTV1kz3TajApi34RTIeIMTieaCarnBJbA==
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-ip-alt-names.crt b/src/test/ssl/ssl/server-ip-alt-names.crt
new file mode 100644
index 0000000000..8a1bc620bb
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-alt-names.crt
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDCTCCAfGgAwIBAgIIICERKREEUAAwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UE
+Aww3VGVzdCBDQSBmb3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHNl
+cnZlciBjZXJ0czAeFw0yMTExMjkxOTA0NTBaFw00OTA0MTYxOTA0NTBaMCAxHjAc
+BgNVBAsMFVBvc3RncmVTUUwgdGVzdCBzdWl0ZTCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBAOM8yB6aVWb17ujr3ayU62mxHQoqn4CvG9yXlJvGOGv/ursW
+Vs0UYJdc96LsNZN1szdm9ayNzCIw3eja+ULsjxCi6+3LM4pO76IORL/XFamlTPYb
+BZ4pHdZVB0nnZAAnWCZPyXdnjOKQ5+8unVXkfibkjj8UELBJ2snehsOa+CTkOBez
+zxYMqxAgbywLIYsW448brun7UXpWmqbGK+SsdGaIZ5Sb7Zezc5lt6CrLemTZTHHK
+7l4WZFCCEi4t3sgO8o1vDELD/IE5G8lyXvIdgJg6t8ssper7iCw6S8x+okhjiSjT
+vDLU2g4AanqZRZB49aPwTo0QUcJA2BCJxL9xLy8CAwEAAaMlMCMwIQYDVR0RBBow
+GIcEwAACAYcQIAENuAAAAAAAAAAAAAAAATANBgkqhkiG9w0BAQsFAAOCAQEAwZJ+
+8KpABTlMEgKnHIYb35ItGhtFiTLQta9RkXx7vaeDwpOdPP/IvuvpjpQZkobRgBsk
+bNM0KuJpd2mSTphQAt6eKQIdcPrkzvc/Yh9OK3YNLUAbu/ZhBUnBvFnUL4wn2f1U
+mfO+m8P/LxybwqKx7r1mbaB+tP3RTxxLcIMvm9ECPQEoBntfEL325Wdoj+WuQH5Y
+IvcM6FaCTkQsNIPbaBD5l5MhMLHRULZujbDjXqGSvRMQfns6np/biMjNdQA8NZ5z
+STeUFvkQbCxoA0YYLgoSHL5KhZjXrg2g+T+2TUyCTR/91xf9OoOjBZdixR0S0DzJ
+B1+5vnUjZaCfnSEA7A==
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-ip-alt-names.key b/src/test/ssl/ssl/server-ip-alt-names.key
new file mode 100644
index 0000000000..b210b3a991
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-alt-names.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEA4zzIHppVZvXu6OvdrJTrabEdCiqfgK8b3JeUm8Y4a/+6uxZW
+zRRgl1z3ouw1k3WzN2b1rI3MIjDd6Nr5QuyPEKLr7cszik7vog5Ev9cVqaVM9hsF
+nikd1lUHSedkACdYJk/Jd2eM4pDn7y6dVeR+JuSOPxQQsEnayd6Gw5r4JOQ4F7PP
+FgyrECBvLAshixbjjxuu6ftRelaapsYr5Kx0ZohnlJvtl7NzmW3oKst6ZNlMccru
+XhZkUIISLi3eyA7yjW8MQsP8gTkbyXJe8h2AmDq3yyyl6vuILDpLzH6iSGOJKNO8
+MtTaDgBqeplFkHj1o/BOjRBRwkDYEInEv3EvLwIDAQABAoIBACp3uY6+mSdc3wF4
+0zzlt/lQuHSl8plCIJrhWUyjhvfoGyXLzv0Uydh/72frbTfZz1yTSWauOXBKYa6a
+/eqb+0DIsf8G8uLuTaqjsAWKVOoXkoKMGkistn7P9UTCkdXVhIvkbWp7V8EgA7iX
+pZ/fzBPIsyzmuxe3NcR0ags0cxuxkNuu+YXDv1oTedmT2wS3CZq1d/T1Y/EOVIf8
+Iznd2aOverlsnt6iiQ3ZWdG/W5F8FhnrR/rrBdYsdCv6TH/KUYexnDOUYpayjDbu
+oAKnifPp6UqiOM4SuBL83OAz19jptp5vpF370BEVRs3eK0q+zo/mETjv9HsXdolZ
+lfoXA0ECgYEA/7nb2azbq/2muvXCh1ZxCEbn3mt8KXoJP/xkx/v9eEc/cc5Q9e0V
+2oGfjC2hSE+bjOWMwiUMD6uU+iRjhz5A3IvUxnoSdoL7H9p0hTqLMyP7dTDkoVF5
+aEuLMaiI5YEnfAFu9L5h8ZKieoQTBoscT06wnGjh9pBV9bthfTKA7ksCgYEA43sb
+55m9WL4kWCPwOAp3vdEAFyxzmZHlO26sEQOU/m5aN01pumYybBruziEXMI96yfTj
+VmXKReeYb6XUiCcs3fLSipD/+8/8CsjO4uMORtxWumXe8AbKZfysGFzL7wJlByGT
+38AGQwIG/XD8cKnaiEMX4E/3Owbcoxwixo3WZC0CgYEAovaqJ9mEU+Jc8h/TS7PG
+bGPjN1Z/1V6zrlcFUnw/Vvrwb3HvHglsN8cLCaW6df5lPjC6tq4tNX8+fPnbg0Ak
+zWc+vQzl3ygxKGdqgcyBEKIJiPETgcoN+GzL02V3d+oKY3f2YXlBqVSsvi6UgUL9
+U3zuB36/IQVyAhrbUZFxoGkCgYEAnaFAO+Nvrp/LhXwZyGuQf+rkmipGTIMpil5t
+QzjtNMV5JFszSWPpyrl7A0Ew1YiG+I0GP2c3m+sY2TzbIiGrWH0b4cMKbw63Qy3V
+FqlpyjaCrpVKv56k/7jv883RzuQk56Uf1+szK5mrCFITy2oXsVZ0pA4lbjSaDTjA
+7D968V0CgYEA+qKqXKL98+c5CMPnpf+0B1x2zgyUym1ouPfon2x5fhK84T53zDMA
+zfdUJ/SOZw6/c9vRF7RL8h+ZfFdIyoAXv4Tt6mIiZe7P+AUVg6XgJ0ce2MUSeWjI
+W8D4WdSi0jyqr99TuVBWhbTZJviMB3pHqKaHQ07hnd/lPtvzsiH12qk=
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-ip-cn-and-alt-names.crt b/src/test/ssl/ssl/server-ip-cn-and-alt-names.crt
new file mode 100644
index 0000000000..2be02feb03
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-and-alt-names.crt
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDHTCCAgWgAwIBAgIIICIBBBQ2MQAwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UE
+Aww3VGVzdCBDQSBmb3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHNl
+cnZlciBjZXJ0czAeFw0yMjAxMDQyMjM2MzFaFw00OTA1MjIyMjM2MzFaMDQxHjAc
+BgNVBAsMFVBvc3RncmVTUUwgdGVzdCBzdWl0ZTESMBAGA1UEAwwJMTkyLjAuMi4x
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwmqTdQJfs2Ti9tPitYp2
+27I0HvL/kNSgA6egFr0foRo0BorwJNIzdbV0+EnsfiBNTWL5It26gqO7UP3ms8t2
+vHD5gkXfT+f6ts0lVJEcIOkUD/8ws4Ic9Y4uPqb4gN+pUKqcxtmLW1TYk84MBK59
+Xz4yPPS6N+G/DMMeFHTNkM9EQwn/+DC3fDsWdGYM2GRWDTJGg1A5tSUcF+seu7i1
+Vg7XajBfsvgAUAsrAxV+X/sLZh94HY+paD6wfaI99mY2OXVc/XW/z1r9WQznor65
+ZkonNCaPfavqPG5vqnab9AyQcqPqmX8hf/xrniASBAkqNCctbASrFCIYvCJfGfmX
+EQIDAQABoyUwIzAhBgNVHREEGjAYhwTAAAIChxAgAQ24AAAAAAAAAAAAAAABMA0G
+CSqGSIb3DQEBCwUAA4IBAQBf7kmYfRYfnWk1OUfY3N1kaNg9piBBlFr9g+OQn9KU
+zirkN7s0ZQbCGxV1uJQBKS58NyE414Vorau77379emgYDcCBpDIYpkLiNujVrIOr
+ggRFKsFRgxu4/mw0BSgCcV8RPe9SWHZ90Mos7TMCnW/PdxOCD1wD0YMkcs0rwB3l
+0Kzc7jDnfOEvmgw/Ysm7v67ps+05Uq5VskQ6WrpSAw6kPD/QMuuBAX8ATPczIaox
+zAMyncq1IiSIwG93f3EoQQThdQ70C6G9vLcu9TtL6JAsEMFEzR99gt1Wsqvmgl9W
+kStzj1yjIWeo5gIsa4Jgcke1lZviWyrTxHDfyunYE5i5
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-ip-cn-and-alt-names.key b/src/test/ssl/ssl/server-ip-cn-and-alt-names.key
new file mode 100644
index 0000000000..54fe80fc68
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-and-alt-names.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAwmqTdQJfs2Ti9tPitYp227I0HvL/kNSgA6egFr0foRo0Borw
+JNIzdbV0+EnsfiBNTWL5It26gqO7UP3ms8t2vHD5gkXfT+f6ts0lVJEcIOkUD/8w
+s4Ic9Y4uPqb4gN+pUKqcxtmLW1TYk84MBK59Xz4yPPS6N+G/DMMeFHTNkM9EQwn/
++DC3fDsWdGYM2GRWDTJGg1A5tSUcF+seu7i1Vg7XajBfsvgAUAsrAxV+X/sLZh94
+HY+paD6wfaI99mY2OXVc/XW/z1r9WQznor65ZkonNCaPfavqPG5vqnab9AyQcqPq
+mX8hf/xrniASBAkqNCctbASrFCIYvCJfGfmXEQIDAQABAoIBAB6GgVST5NbT9lbu
++d+rN/JSzqA1Yy8oU19/iEFJvJec96I3WnFNl8rZjN4XLUy4YarO6XMyAUDV2Gll
+FD4Sqjf4PRTZR7DSKaleGIhoqFP6hK3mUY091rIves9XhBkoBPunbipCqgDTF5ZN
+edGaXBECQP0VJ8/yX/7u++AWXthnjDis9X0taZfFg/PYbV7SCJ1Hg1O/wEsgXlnC
+7mbL6wkCW0f6700B0x1kKbZqJY95xRqp6Ipq2lIQbJDdGywoj0WzKqNltf9cer+r
+cXl8WjeiMvvvpl4uGhckAbzUifUzxN6A3f1fu/XKtOmabMi9t7J4MRfgOgedgtQB
+0jaZGSkCgYEA+lBLnNY6M48HX2mdtr86+n41gh69v8Z7oNikJFDZkodrvI8uqE0i
+0XwnYPFddt8NbmuUhhuzI2M8RKhGLgdlbKpkSSVafnMfcxRmX2EAtWQgdvX1Iult
+752LWdBgSuw2vlzvy3T/GYnjMrXSCGput4amqojMEbvUGvIdSUMdHGMCgYEAxtU1
+WixKPL6aEnYy1f4bybzcNgGtl8PBRz9xw+P46g+ijOPoaG9O73Tr7An11AO003Ot
+DHhMW+b8yHLyxoKwS2sU2cN/lKB8xNQYZc1D61RNJlzgnHMXnA0lcH0I3M35fqKr
+/71pD1ZP40SSJS+od/KEjW80XzuOdyiXg8q81vsCgYEAnUPLbbsuj+whzrFVlFZr
+IKwgxCK6Rn3WeIUEA4kEWUpZxvsSbk0gPgtJ1l9uwFt9Xc2bX/KRRv93Aw/SH+Mn
+tvEK1uXwCBgePzgm5W/VeSFyQCthm1CbcHtD7Oa9SPVFo65SPjrAd3QpWVfgoMb1
+zrp7hhMyW0XuCgvpmHjhFk8CgYEAxq/thXM2p+bLLWGhwQcRG5G299zLbBl4PUsf
+0uEvLi17gJCKADoiRdSvoAn/9eHSQ26XYRuhKkDzHxcGlOmpY2PYzRa3mXyZ0VIk
+Iy5wDWwLQCeVZ6D22cClRfgb8BF/nFTPzVmn72SPpgoyhChQj7PvUynpyrRH07jj
+VxYziBsCgYAFr37Xbl0VnXVK+XU+vMwUZjcF4jpoCr7SFZqgRbW2GbYSUoMuPXns
+RnJh+Fvi1NUei+E5s1H4P1pVq4p0jFxP4GvH/qvNjnIn/Er3bbqvpox6dWUJXprq
+qTQSDIeoDC/V8cyRoIfqPvTVqY8Rgew6GEkv0bAImdxhoSng7vIseg==
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.crt b/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.crt
new file mode 100644
index 0000000000..23c06da01c
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.crt
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDQzCCAiugAwIBAgIIICIBBBQ2MQEwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UE
+Aww3VGVzdCBDQSBmb3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHNl
+cnZlciBjZXJ0czAeFw0yMjAxMDQyMjM2MzFaFw00OTA1MjIyMjM2MzFaMDQxHjAc
+BgNVBAsMFVBvc3RncmVTUUwgdGVzdCBzdWl0ZTESMBAGA1UEAwwJMTkyLjAuMi4x
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8xddbo/x2TOSIa/br8BN
+o/URdTr9+l2R5YojiZKDuLxiQVkgC30PJ2/CNFKIh2nHhRrzknI6sETVtrxZ+9V2
+qRc1yShVu462u0DHPRMIZnZIOZg3hlNB0cRWbOglUKlttIARNEQUcTUyPOtyo4/v
++u0Ej5NTNcHFbFT01vdD9MjQiCO3jKdAwPIb14jTg4C71EpZ+LuelDo4DzF2/XgG
+WqUTrgD/XnBU/60PU9Iy3G0nVpx21q6ppn9G7a9R+i8FjBcwW1T+cfsBDWhAv+bi
+RmSAkENf8L8TwOlDQUwROkfz3Hz36vuJjdkreQJsiqL0HnrnH5T5G9UzJO86FvZQ
+5wIDAQABo0swSTBHBgNVHREEQDA+gh1kbnMxLmFsdC1uYW1lLnBnLXNzbHRlc3Qu
+dGVzdIIdZG5zMi5hbHQtbmFtZS5wZy1zc2x0ZXN0LnRlc3QwDQYJKoZIhvcNAQEL
+BQADggEBAF+mfaw6iBPzpCgqq830pHRa3Yzm1aezt8SkeRohUYHNv/yCnDSRaqtj
+xbENih3lJMSTBL3g0wtTOHfH8ViC/h+lvYELHzXKic7gkjV7H5XETKGr0ZsjBBT2
+4cZQKbD9e0x0HrENXMYgGpBf747qL6uTOVJdG0s15hwpLq47bY5WUjXathejbpxW
+prmF8F+xaC52N9P/1VnqguQB909F4x1pyOK7D7tjFu+Y8Je7PHKbb6WY5K6xAv6t
+R17CY0749/FotlphquElUR2bs5Zzv5YrjUHPTcbwKvcH5cdNi93/u6NJt2xNAoYf
+aZERhX5TA9DYk4gC8OY0yGaYCIj3Dd4=
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.key b/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.key
new file mode 100644
index 0000000000..0ace41e0a1
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEogIBAAKCAQEA8xddbo/x2TOSIa/br8BNo/URdTr9+l2R5YojiZKDuLxiQVkg
+C30PJ2/CNFKIh2nHhRrzknI6sETVtrxZ+9V2qRc1yShVu462u0DHPRMIZnZIOZg3
+hlNB0cRWbOglUKlttIARNEQUcTUyPOtyo4/v+u0Ej5NTNcHFbFT01vdD9MjQiCO3
+jKdAwPIb14jTg4C71EpZ+LuelDo4DzF2/XgGWqUTrgD/XnBU/60PU9Iy3G0nVpx2
+1q6ppn9G7a9R+i8FjBcwW1T+cfsBDWhAv+biRmSAkENf8L8TwOlDQUwROkfz3Hz3
+6vuJjdkreQJsiqL0HnrnH5T5G9UzJO86FvZQ5wIDAQABAoIBAGv0BFoFMrHyZQLw
+xe7Wx6P4QTh+aiu1QgVdw0pk9nojrr62hbSUZRZuWyBBRsBcCW7i+Sgf8lA1QXNV
+UeC0e228EPa0It6YEi42JkTJHwHhpVFud7n/X0t4lajnryqTE1UFSp6bXTipFxZW
+uSJJ2ZjliRD5rApDcxkY4WJVjKg3aEt7P/DiM8iKGfyE6stq72VjEbJjdViMEcOP
+BNf0TiREZz5Mp7jAVWhpen0ebbLOBVWV4/ONNcL+yqR4mCEDUSFGewrTVX4zHL0A
+hYk198C5F8sFvEDnFkPco9sXMVanmLoI8sbhP4IIz9g4+GU6kFuj7fUKp11Azqv+
+3WQDKYECgYEA/XG4mmG/g8FG44y42mfZpUXWi1pwU4CQIrhkoU5j7EPQrvRboOOE
+Rv95jSwyZu4vCqjyI5FN1jCGTdhmt++R1e//zH6Hqa9Smo+jw7DtAFrCYd1JnCf1
+ToOwsYPHv4P7A8q8kc5vCNIv+AQSlP/wqdVNo3grdf7cGXkMtEY4F9UCgYEA9Yrq
+zWdnNGPATuSBqL6TSjQ37oR+dBD6WnGsiDenQkOzyDPFZ3CT1DjJghjEtxc8EfNf
+Oo8dMMR2q+5FZQo7WuqONEgyzKePiNR8RK2gOYpgdjN9bih1sAhHR10D26cpwlDJ
+bx7D5ZzENLbdZmfEiWwKswnaIhN4yMalgE0mP8sCgYAhzJy12ftUct4lUosEdX0N
+EXc/NlxshmSyfKzO5kllJNYbvvLJTg5B+agYL6C5IWKcpVNFcwdSXT5L+2QXe5eT
+VGJkvysQchUuD6HjYyD4PyJVMtGyRZHtWpqh0dU9sTg0lUD4oPMl1gIXrVNdE5Tg
+0VV9S3VgUxC/ROlw0TyB0QKBgGsVE0NS9hJF8mc1hko2GnwA++d8Rr2NbfElo+2f
+/8SJTA1ibpOm6AFkZpTjAl8qtdrKPVyHb16GP47Jkd/3r1z979hjKCxSYul0aWF2
+KusNKvZBjFEPOgv0AEniCb2wUCjbHI3mZ95qGLM4kKOJW4/m21+rS0MTJNjCsQic
+HLMzAoGAeCsY09d3m8xGeU+DuTPC6GH7Sgy/NBYqS5VaVNjb2jnuZlW2SSW2oiID
+4tXTi4ruKmHC898BfyFxhSMqub+tg3pVqIYADC71rnJLrVyc1SzoWzL7yMT3qFj7
+C7ZYZYmfG9agcZb5NkqKPTfCxkBhWbdgTTgBKVO/xQst8EUgko8=
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-ip-cn-only.crt b/src/test/ssl/ssl/server-ip-cn-only.crt
new file mode 100644
index 0000000000..9bf015cf18
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-only.crt
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC8TCCAdkCCCAhESkRN1IAMA0GCSqGSIb3DQEBCwUAMEIxQDA+BgNVBAMMN1Rl
+c3QgQ0EgZm9yIFBvc3RncmVTUUwgU1NMIHJlZ3Jlc3Npb24gdGVzdCBzZXJ2ZXIg
+Y2VydHMwHhcNMjExMTI5MTkzNzUyWhcNNDkwNDE2MTkzNzUyWjA0MR4wHAYDVQQL
+DBVQb3N0Z3JlU1FMIHRlc3Qgc3VpdGUxEjAQBgNVBAMMCTE5Mi4wLjIuMTCCASIw
+DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANWs1uUL71nHYF9Zj6p+M3MpYDvx
+32iCjVdtH5a2qpSWHXTg0rR8dLX0y92cvOYvMXHRajZT1avpHr8dooPYSVaXpGMK
+NvF/Qi+WFYovRbP2vmd1yv1cgW/FggbwJFWVobizIz4seyA4d0B2j9fqoi2OFBNP
+huW664SjF0u3p21tDy+43i2LNUMAKf6dnRR5Vqenath87LEU41tSLudu6NXgbFMk
+jvfNkl4d0w7YCzeXmklmSI+uaX3PlJJ4NzQO2j8w5BvnKVhNVD0KjgrXZ6nB/8F7
+Pg3XY+d7rJlwRgXemU6resWQDJ7+UaC9u7I4EIP+9lzCR/nNBqUktpHRmHUCAwEA
+ATANBgkqhkiG9w0BAQsFAAOCAQEAos1JncV8Yf4UaKl6h1GdYtcVtzFyJvBEnhRD
+07ldL+TYnfZiX8wK2ssBtM3cg/C78y5bzdUa5XGS83ZKQJFFdhE7PSnrvyNqyIqY
+ZgNBxto3gyvir+EjO1u9BAB0NP3r3gYoHRDZS1xOPPzt4WgjuUgTLM9k82GsqAbO
+UrOTOdRnkIqC5xLpa05EnRyJPRsR1w1PRJC2XXKnHIuFjMb4v7UuPwyCcX1P5ioc
+rQszQcORy/L+k0ezCkyweORg68htjYbBHuwOuiGfok6yKKDMzrTvD3lIslls6eX7
+4sI3XWqzkPmG9Vsxm9Vu9/Ma+PRO76VyCoIwBd+Ufg5vNXhMmw==
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-ip-cn-only.key b/src/test/ssl/ssl/server-ip-cn-only.key
new file mode 100644
index 0000000000..1966530e72
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-only.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEA1azW5QvvWcdgX1mPqn4zcylgO/HfaIKNV20flraqlJYddODS
+tHx0tfTL3Zy85i8xcdFqNlPVq+kevx2ig9hJVpekYwo28X9CL5YVii9Fs/a+Z3XK
+/VyBb8WCBvAkVZWhuLMjPix7IDh3QHaP1+qiLY4UE0+G5brrhKMXS7enbW0PL7je
+LYs1QwAp/p2dFHlWp6dq2HzssRTjW1Iu527o1eBsUySO982SXh3TDtgLN5eaSWZI
+j65pfc+Ukng3NA7aPzDkG+cpWE1UPQqOCtdnqcH/wXs+Dddj53usmXBGBd6ZTqt6
+xZAMnv5RoL27sjgQg/72XMJH+c0GpSS2kdGYdQIDAQABAoIBAQDNXviU4WnF8rmQ
+K7bH+dBdqbETLKC8BG7xTrMD2sINWlMpmUUrsEtE7+paMGHnJAj0CoF5gg5m0wN4
+UXV4H5QtpEad4p14dAYbUreVP2ZRWKEdM7xM1HKcCUu2e22QzObJbXQ8N+iHyX3k
++Y+7yYrjGiH1hYR0nbnsnAyx++zyYBSQeqzpdQwf/BLY5xZmyYWNfqbckiMpEqMs
+EmZmGXnCjIipzEC0LQHoSW9PNa92Z9bvuxOKYl8iHYDDXjvMRFoZBSiMXpzHQocb
+QlQ5F4ayfW2OrOhpNbY7niYM9GN3Bk9TgMP+0BkJE6uuktLYW35LY1M78CCPWcWb
+npJNK3QBAoGBAOxkGrhAHAysSmtirIyMdvySb76wb/Ukfi+AULKz20FI5j4/GXm9
+qCb2GeT+FFSUHeSC8f0EFnosRYkdBGruqeZioI+5rUkboYFJPspAHAuvg9kgtfF+
+kvphD4O4P/foYsEZRx66FHozDbhrrR5UXc7KzqRIASc/D3FOx2UFJLb1AoGBAOdm
+WcaMvYygl9ZW+ThWAR1xG1X70AGKwrlrpF2hBkWYxSurxSMXnD0DUzC9Nb4EyCaM
+c2uSqEZOKdW+XfXtK2DnqXKfb3YCVEoGN4gVfyuW/vxii/+ZxLo3md/b3vrkZEVp
+pfkXy/HoZ71YN7bNpcDpOnhml6vvuCRCYFnI1WuBAoGAC0shB6pwbJ6Sk5zMN47C
+ZICufAK75o9OxAAyWsdC81SDQ3gKRImuDeZ2CD2nRP8qim9DFl5qoH2a+Nj9DArI
+7SvLFfK9958tURrpuAnmDRzehLIOXzI33WRjtFxKGhLtHOKTRkGHlur3fdcPF0La
+lHWV971E6NYXa8diuU3Mmj0CgYBYd+ka3/QYL83dRKNDxp3mg7fPx9ZewI5yFZVh
+to6PTTkU2Tclk4FIUl0b5TsGyw06r7fxCMENIBUegwmpXGOZSPifuhUDKSDQrE/O
+12knYTNbitG7hy6Pg3JxA77cbTVo1FuAQHjYo+IFohSq7zTP7FtObOrP8XaVZksw
+CHiQAQKBgBW4EiA9AAnZ1LOpifAvM7bs0NHg95qTwtAL52WKom2ga2H+lMhxeu6Y
+hUSytC/f9kALVcYloZhkLYpO07x1gXmy7f4parMjA4Ex+4vfu3kPd8GiNGZ+AUJD
+nnJ1OINY9ziXJZfju7FpVWpkiuPzWCh6y/o3gZ/veq5mIUxuDMVa
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/sslfiles.mk b/src/test/ssl/sslfiles.mk
index 270f55a58f..6185cf75aa 100644
--- a/src/test/ssl/sslfiles.mk
+++ b/src/test/ssl/sslfiles.mk
@@ -22,9 +22,14 @@
 # key/certificate pair will be generated for you, signed by the appropriate CA.
 #
 SERVERS := server-cn-and-alt-names \
+	server-cn-and-ip-alt-names \
 	server-cn-only \
+	server-ip-cn-only \
+	server-ip-cn-and-alt-names \
+	server-ip-cn-and-dns-alt-names \
 	server-single-alt-name \
 	server-multiple-alt-names \
+	server-ip-alt-names \
 	server-no-names \
 	server-revoked
 CLIENTS := client client-dn client-revoked client_ext
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 779ab66838..a7cce770ed 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -21,7 +21,7 @@ if ($ENV{with_ssl} ne 'openssl')
 }
 else
 {
-	plan tests => 110;
+	plan tests => 135;
 }
 
 #### Some configuration
@@ -253,6 +253,23 @@ $node->connect_fails(
 	  qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/
 );
 
+# Test with an IP address in the Common Name. This is a strange corner case that
+# nevertheless is supported, as long as the address string matches exactly.
+switch_server_cert($node, 'server-ip-cn-only');
+
+$common_connstr =
+  "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+
+$node->connect_ok("$common_connstr host=192.0.2.1",
+	"IP address in the Common Name");
+
+$node->connect_fails(
+	"$common_connstr host=192.000.002.001",
+	"mismatch between host name and server certificate IP address",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.1" does not match host name "192.000.002.001"\E/
+);
+
 # Test Subject Alternative Names.
 switch_server_cert($node, 'server-multiple-alt-names');
 
@@ -305,7 +322,54 @@ $node->connect_fails(
 	  qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/
 );
 
-# Test server certificate with a CN and SANs. Per RFCs 2818 and 6125, the CN
+# Test certificate with IP addresses in the SANs.
+switch_server_cert($node, 'server-ip-alt-names');
+
+$node->connect_ok(
+	"$common_connstr host=192.0.2.1",
+	"host matching an IPv4 address (Subject Alternative Name 1)");
+
+$node->connect_ok(
+	"$common_connstr host=192.000.002.001",
+	"host matching an IPv4 address in alternate form (Subject Alternative Name 1)");
+
+$node->connect_fails(
+	"$common_connstr host=192.0.2.2",
+	"host not matching an IPv4 address (Subject Alternative Name 1)",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.1" (and 1 other name) does not match host name "192.0.2.2"\E/
+);
+
+$node->connect_fails(
+	"$common_connstr host=192.0.2.1/32",
+	"IPv4 host with CIDR mask does not match",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.1" (and 1 other name) does not match host name "192.0.2.1\/32"\E/
+);
+
+$node->connect_ok(
+	"$common_connstr host=2001:DB8::1",
+	"host matching an IPv6 address (Subject Alternative Name 2)");
+
+$node->connect_ok(
+	"$common_connstr host=2001:db8:0:0:0:0:0:1",
+	"host matching an IPv6 address in alternate form (Subject Alternative Name 2)");
+
+$node->connect_fails(
+	"$common_connstr host=::1",
+	"host not matching an IPv6 address (Subject Alternative Name 2)",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.1" (and 1 other name) does not match host name "::1"\E/
+);
+
+$node->connect_fails(
+	"$common_connstr host=2001:DB8::1/128",
+	"IPv6 host with CIDR mask does not match",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.1" (and 1 other name) does not match host name "2001:DB8::1\/128"\E/
+);
+
+# Test server certificate with a CN and DNS SANs. Per RFCs 2818 and 6125, the CN
 # should be ignored when the certificate has both.
 switch_server_cert($node, 'server-cn-and-alt-names');
 
@@ -323,6 +387,39 @@ $node->connect_fails(
 	  qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/
 );
 
+# But we will fall back to check the CN if the SANs contain only IP addresses.
+switch_server_cert($node, 'server-cn-and-ip-alt-names');
+
+$node->connect_ok("$common_connstr host=common-name.pg-ssltest.test",
+	"certificate with both a CN and IP SANs matches CN");
+$node->connect_ok("$common_connstr host=192.0.2.1",
+	"certificate with both a CN and IP SANs matches SAN 1");
+$node->connect_ok("$common_connstr host=2001:db8::1",
+	"certificate with both a CN and IP SANs matches SAN 2");
+
+# And now the same tests, but with IP addresses and DNS names swapped.
+switch_server_cert($node, 'server-ip-cn-and-alt-names');
+
+$node->connect_ok("$common_connstr host=192.0.2.2",
+	"certificate with both an IP CN and IP SANs 1");
+$node->connect_ok("$common_connstr host=2001:db8::1",
+	"certificate with both an IP CN and IP SANs 2");
+$node->connect_fails(
+	"$common_connstr host=192.0.2.1",
+	"certificate with both an IP CN and IP SANs ignores CN",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.2" (and 1 other name) does not match host name "192.0.2.1"\E/
+);
+
+switch_server_cert($node, 'server-ip-cn-and-dns-alt-names');
+
+$node->connect_ok("$common_connstr host=192.0.2.1",
+	"certificate with both an IP CN and DNS SANs matches CN");
+$node->connect_ok("$common_connstr host=dns1.alt-name.pg-ssltest.test",
+	"certificate with both an IP CN and DNS SANs matches SAN 1");
+$node->connect_ok("$common_connstr host=dns2.alt-name.pg-ssltest.test",
+	"certificate with both an IP CN and DNS SANs matches SAN 2");
+
 # Finally, test a server certificate that has no CN or SANs. Of course, that's
 # not a very sensible certificate, but libpq should handle it gracefully.
 switch_server_cert($node, 'server-no-names');
-- 
2.25.1

v3-0003-squash-libpq-allow-IP-address-SANs-in-server-cert.patchtext/x-patch; name=v3-0003-squash-libpq-allow-IP-address-SANs-in-server-cert.patchDownload
From 399fb6125070f5c3dd7f73ebfff211260f7c2c5c Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Wed, 5 Jan 2022 15:47:03 -0800
Subject: [PATCH v3 3/3] squash! libpq: allow IP address SANs in server certs

Per review, provide a pg_inet_pton() interface for IPv6 and refactor the
internals accordingly. IPv4 is not yet implemented. The call sites that
just want standard addresses without a CIDR mask have been updated to
use the new function.

Internally, the IPv6 parser now takes an allow_cidr boolean. I've
plumbed that all the way down to getbits() in order to minimize the
amount of code touched, at the expense of an unnecessary function call
in the failing case.
---
 src/include/port.h                       |  1 +
 src/interfaces/libpq/fe-secure-common.c  |  7 +--
 src/interfaces/libpq/fe-secure-openssl.c |  2 +-
 src/port/inet_net_pton.c                 | 66 ++++++++++++++++++------
 4 files changed, 52 insertions(+), 24 deletions(-)

diff --git a/src/include/port.h b/src/include/port.h
index cca29839bc..e540dea0e4 100644
--- a/src/include/port.h
+++ b/src/include/port.h
@@ -520,6 +520,7 @@ extern char *pg_inet_net_ntop(int af, const void *src, int bits,
 
 /* port/inet_net_pton.c */
 extern int	pg_inet_net_pton(int af, const char *src, void *dst, size_t size);
+extern int	pg_inet_pton(int af, const char *src, void *dst);
 
 /* port/pg_strong_random.c */
 extern void pg_strong_random_init(void);
diff --git a/src/interfaces/libpq/fe-secure-common.c b/src/interfaces/libpq/fe-secure-common.c
index 796b78e348..ec6c1f7539 100644
--- a/src/interfaces/libpq/fe-secure-common.c
+++ b/src/interfaces/libpq/fe-secure-common.c
@@ -209,12 +209,7 @@ pq_verify_peer_name_matches_certificate_ip(PGconn *conn,
 
 		family = PGSQL_AF_INET6;
 
-		/*
-		 * pg_inet_net_pton() will accept CIDR masks, which we don't want to
-		 * match, so skip the comparison if the host string contains a slash.
-		 */
-		if (!strchr(host, '/')
-			&& pg_inet_net_pton(PGSQL_AF_INET6, host, addr, -1) == 128)
+		if (pg_inet_pton(PGSQL_AF_INET6, host, addr) == 1)
 		{
 			if (memcmp(ipdata, addr, iplen) == 0)
 				match = 1;
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index 479f63197a..07e226c6ee 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -555,7 +555,7 @@ is_ip_address(const char *host)
 	unsigned char	dummy6[16];
 
 	return inet_aton(host, &dummy4)
-		|| (pg_inet_net_pton(PGSQL_AF_INET6, host, dummy6, -1) == 128);
+		|| (pg_inet_pton(PGSQL_AF_INET6, host, dummy6) == 1);
 }
 
 /*
diff --git a/src/port/inet_net_pton.c b/src/port/inet_net_pton.c
index bae50ba67e..7c29ade709 100644
--- a/src/port/inet_net_pton.c
+++ b/src/port/inet_net_pton.c
@@ -50,8 +50,8 @@ static const char rcsid[] = "Id: inet_net_pton.c,v 1.4.2.3 2004/03/17 00:40:11 m
 
 static int	inet_net_pton_ipv4(const char *src, u_char *dst);
 static int	inet_cidr_pton_ipv4(const char *src, u_char *dst, size_t size);
-static int	inet_net_pton_ipv6(const char *src, u_char *dst);
-static int	inet_cidr_pton_ipv6(const char *src, u_char *dst, size_t size);
+static int	inet_pton_ipv6_internal(const char *src, u_char *dst, size_t size,
+									bool allow_cidr);
 
 
 /*
@@ -82,9 +82,9 @@ pg_inet_net_pton(int af, const char *src, void *dst, size_t size)
 				inet_net_pton_ipv4(src, dst) :
 				inet_cidr_pton_ipv4(src, dst, size);
 		case PGSQL_AF_INET6:
-			return size == -1 ?
-				inet_net_pton_ipv6(src, dst) :
-				inet_cidr_pton_ipv6(src, dst, size);
+			return inet_pton_ipv6_internal(src, dst,
+										   (size == -1) ? 16 : size,
+										   true /* allow CIDR */);
 		default:
 			errno = EAFNOSUPPORT;
 			return -1;
@@ -360,13 +360,22 @@ emsgsize:
 }
 
 static int
-getbits(const char *src, int *bitsp)
+getbits(const char *src, int *bitsp, bool allow_cidr)
 {
 	static const char digits[] = "0123456789";
 	int			n;
 	int			val;
 	char		ch;
 
+	if (!allow_cidr)
+	{
+		/*
+		 * If we got here, the top-level call is to pg_inet_pton() and the
+		 * caller supplied a CIDR mask, which isn't allowed.
+		 */
+		return 0;
+	}
+
 	val = 0;
 	n = 0;
 	while ((ch = *src++) != '\0')
@@ -393,7 +402,7 @@ getbits(const char *src, int *bitsp)
 }
 
 static int
-getv4(const char *src, u_char *dst, int *bitsp)
+getv4(const char *src, u_char *dst, int *bitsp, bool allow_cidr)
 {
 	static const char digits[] = "0123456789";
 	u_char	   *odst = dst;
@@ -424,7 +433,7 @@ getv4(const char *src, u_char *dst, int *bitsp)
 				return 0;
 			*dst++ = val;
 			if (ch == '/')
-				return getbits(src, bitsp);
+				return getbits(src, bitsp, allow_cidr);
 			val = 0;
 			n = 0;
 			continue;
@@ -439,18 +448,12 @@ getv4(const char *src, u_char *dst, int *bitsp)
 	return 1;
 }
 
-static int
-inet_net_pton_ipv6(const char *src, u_char *dst)
-{
-	return inet_cidr_pton_ipv6(src, dst, 16);
-}
-
 #define NS_IN6ADDRSZ 16
 #define NS_INT16SZ 2
 #define NS_INADDRSZ 4
 
 static int
-inet_cidr_pton_ipv6(const char *src, u_char *dst, size_t size)
+inet_pton_ipv6_internal(const char *src, u_char *dst, size_t size, bool allow_cidr)
 {
 	static const char xdigits_l[] = "0123456789abcdef",
 				xdigits_u[] = "0123456789ABCDEF";
@@ -518,13 +521,13 @@ inet_cidr_pton_ipv6(const char *src, u_char *dst, size_t size)
 			continue;
 		}
 		if (ch == '.' && ((tp + NS_INADDRSZ) <= endp) &&
-			getv4(curtok, tp, &bits) > 0)
+			getv4(curtok, tp, &bits, allow_cidr) > 0)
 		{
 			tp += NS_INADDRSZ;
 			saw_xdigit = 0;
 			break;				/* '\0' was seen by inet_pton4(). */
 		}
-		if (ch == '/' && getbits(src, &bits) > 0)
+		if (ch == '/' && getbits(src, &bits, allow_cidr) > 0)
 			break;
 		goto enoent;
 	}
@@ -538,6 +541,9 @@ inet_cidr_pton_ipv6(const char *src, u_char *dst, size_t size)
 	if (bits == -1)
 		bits = 128;
 
+	/* Without a CIDR mask, the above should guarantee a complete address. */
+	Assert(allow_cidr || bits == 128);
+
 	endp = tmp + 16;
 
 	if (colonp != NULL)
@@ -576,3 +582,29 @@ emsgsize:
 	errno = EMSGSIZE;
 	return -1;
 }
+
+/*
+ * Converts a network address in presentation format to network format. The main
+ * difference between this and pg_inet_net_pton() above is that CIDR notation is
+ * not allowed.
+ *
+ * The memory pointed to by dst depends on the address family:
+ *
+ *     PGSQL_AF_INET:  not implemented yet (returns -1 with EAFNOSUPPORT)
+ *     PGSQL_AF_INET6: *dst must be a u_char[16]
+ *
+ * Over and above the standard inet_pton() functionality, we also set errno on
+ * parse failure, with the same meanings as with pg_inet_net_pton().
+ */
+int
+pg_inet_pton(int af, const char *src, void *dst)
+{
+	if (af != PGSQL_AF_INET6)
+	{
+		/* Currently only IPv6 is implemented. */
+		errno = EAFNOSUPPORT;
+		return -1;
+	}
+
+	return (inet_pton_ipv6_internal(src, dst, 16, false /* no CIDR */) == 128);
+}
-- 
2.25.1

#13Kyotaro Horiguchi
horikyota.ntt@gmail.com
In reply to: Jacob Champion (#12)
Re: [PATCH] Accept IP addresses in server certificate SANs

At Thu, 6 Jan 2022 00:02:27 +0000, Jacob Champion <pchampion@vmware.com> wrote in

On Mon, 2022-01-03 at 16:19 +0000, Jacob Champion wrote:

On Fri, 2021-12-17 at 15:40 +0900, Kyotaro Horiguchi wrote:

+ inet_net_pton_ipv4(const char *src, u_char *dst)
(calls inet_net_pton_ipv4_internal(src, dst, true))
+ inet_pton_ipv4(const char *src, u_char *dst)
(calls inet_net_pton_ipv4_internal(src, dst, false))

Sounds good, I will make that change. Thanks for the feedback!

v3 implements a pg_inet_pton(), but for IPv6 instead of IPv4 as
presented above (since we only need inet_pton() for IPv6 in this case).
It's split into a separate patch (0003) for ease of review.

0001 looks fine as it is in the almost same shape withinet_net_pton
about PGSQL_AF_INET and PGSQL_AF_INET6. I'm not sure about the
difference on how to handle AF_INET6 between pg_inet_net_pton and ntop
but that's not a matter of this patch.

However, 0002,

+/*
+ * In a frontend build, we can't include inet.h, but we still need to have
+ * sensible definitions of these two constants.  Note that pg_inet_net_ntop()
+ * assumes that PGSQL_AF_INET is equal to AF_INET.
+ */
+#define PGSQL_AF_INET	(AF_INET + 0)
+#define PGSQL_AF_INET6	(AF_INET + 1)
+

Now we have the same definition thrice in frontend code. Coulnd't we
define them in, say, libpq-fe.h or inet-fe.h (nonexistent) then
include it from the three files?

+$node->connect_fails(
+	"$common_connstr host=192.0.2.2",
+	"host not matching an IPv4 address (Subject Alternative Name 1)",

It is not the real IP address of the server.

https://datatracker.ietf.org/doc/html/rfc6125

In some cases, the URI is specified as an IP address rather than a
hostname. In this case, the iPAddress subjectAltName must be
present in the certificate and must exactly match the IP in the URI.

When IP address is embedded in URI, it won't be translated to another
IP address. Concretely https://192.0.1.5/hoge cannot reach to the host
192.0.1.8. On the other hand, as done in the test, libpq allows that
when "host=192.0.1.5 hostaddr=192.0.1.8". I can't understand what we
are doing in that case. Don't we need to match the SAN IP address
with hostaddr instead of host?

regards.

--
Kyotaro Horiguchi
NTT Open Source Software Center

#14Jacob Champion
pchampion@vmware.com
In reply to: Kyotaro Horiguchi (#13)
Re: [PATCH] Accept IP addresses in server certificate SANs

On Mon, 2022-01-31 at 17:29 +0900, Kyotaro Horiguchi wrote:

However, 0002,

+/*
+ * In a frontend build, we can't include inet.h, but we still need to have
+ * sensible definitions of these two constants.  Note that pg_inet_net_ntop()
+ * assumes that PGSQL_AF_INET is equal to AF_INET.
+ */
+#define PGSQL_AF_INET  (AF_INET + 0)
+#define PGSQL_AF_INET6 (AF_INET + 1)
+

Now we have the same definition thrice in frontend code. Coulnd't we
define them in, say, libpq-fe.h or inet-fe.h (nonexistent) then
include it from the three files?

I started down the inet-fe.h route, and then realized I didn't know
where that should go. Does it need to be included in (or part of)
port.h? And should it be installed as part of the logic in
src/include/Makefile?

+$node->connect_fails(
+       "$common_connstr host=192.0.2.2",
+       "host not matching an IPv4 address (Subject Alternative Name 1)",

It is not the real IP address of the server.

https://datatracker.ietf.org/doc/html/rfc6125

In some cases, the URI is specified as an IP address rather than a
hostname. In this case, the iPAddress subjectAltName must be
present in the certificate and must exactly match the IP in the URI.

When IP address is embedded in URI, it won't be translated to another
IP address. Concretely https://192.0.1.5/hoge cannot reach to the host
192.0.1.8. On the other hand, as done in the test, libpq allows that
when "host=192.0.1.5 hostaddr=192.0.1.8". I can't understand what we
are doing in that case. Don't we need to match the SAN IP address
with hostaddr instead of host?

I thought that host, not hostaddr, was the part that corresponded to
the URI. So in a hypothetical future where postgresqls:// exists, the
two URIs

postgresqls://192.0.2.2:5432/db
postgresqls://192.0.2.2:5432/db?hostaddr=127.0.0.1

should both be expecting the same certificate. That seems to match the
libpq documentation as well.

(Specifying a host parameter is also allowed... that seems like it
could cause problems for a hypothetical postgresqls:// scheme, but it's
probably not relevant for this thread.)

--Jacob

#15Kyotaro Horiguchi
horikyota.ntt@gmail.com
In reply to: Jacob Champion (#14)
Re: [PATCH] Accept IP addresses in server certificate SANs

At Wed, 2 Feb 2022 19:46:13 +0000, Jacob Champion <pchampion@vmware.com> wrote in

On Mon, 2022-01-31 at 17:29 +0900, Kyotaro Horiguchi wrote:

+#define PGSQL_AF_INET  (AF_INET + 0)
+#define PGSQL_AF_INET6 (AF_INET + 1)
+

Now we have the same definition thrice in frontend code. Coulnd't we
define them in, say, libpq-fe.h or inet-fe.h (nonexistent) then
include it from the three files?

I started down the inet-fe.h route, and then realized I didn't know
where that should go. Does it need to be included in (or part of)
port.h? And should it be installed as part of the logic in
src/include/Makefile?

I don't think it should be a part of port.h. Though I suggested
frontend-only header file by the name, isn't it enough to separate out
the definitions from utils/inet.h to common/inet-common.h then include
the inet-common.h from inet.h?

When IP address is embedded in URI, it won't be translated to another
IP address. Concretely https://192.0.1.5/hoge cannot reach to the host
192.0.1.8. On the other hand, as done in the test, libpq allows that
when "host=192.0.1.5 hostaddr=192.0.1.8". I can't understand what we
are doing in that case. Don't we need to match the SAN IP address
with hostaddr instead of host?

I thought that host, not hostaddr, was the part that corresponded to
the URI. So in a hypothetical future where postgresqls:// exists, the
two URIs

postgresqls://192.0.2.2:5432/db
postgresqls://192.0.2.2:5432/db?hostaddr=127.0.0.1

should both be expecting the same certificate. That seems to match the
libpq documentation as well.

Hmm. Well, considering that the objective for the validation is to
check if the server is actually the client is intending to connect, it
is fine. Sorry for the noise.

(Specifying a host parameter is also allowed... that seems like it
could cause problems for a hypothetical postgresqls:// scheme, but it's
probably not relevant for this thread.)

Yeah.

regards.

--
Kyotaro Horiguchi
NTT Open Source Software Center

#16Jacob Champion
pchampion@vmware.com
In reply to: Kyotaro Horiguchi (#15)
3 attachment(s)
Re: [PATCH] Accept IP addresses in server certificate SANs

On Thu, 2022-02-03 at 16:23 +0900, Kyotaro Horiguchi wrote:

At Wed, 2 Feb 2022 19:46:13 +0000, Jacob Champion <pchampion@vmware.com> wrote in

On Mon, 2022-01-31 at 17:29 +0900, Kyotaro Horiguchi wrote:

+#define PGSQL_AF_INET  (AF_INET + 0)
+#define PGSQL_AF_INET6 (AF_INET + 1)
+

Now we have the same definition thrice in frontend code. Coulnd't we
define them in, say, libpq-fe.h or inet-fe.h (nonexistent) then
include it from the three files?

I started down the inet-fe.h route, and then realized I didn't know
where that should go. Does it need to be included in (or part of)
port.h? And should it be installed as part of the logic in
src/include/Makefile?

I don't think it should be a part of port.h. Though I suggested
frontend-only header file by the name, isn't it enough to separate out
the definitions from utils/inet.h to common/inet-common.h then include
the inet-common.h from inet.h?

That works a lot better than what I had in my head. Done that way in
v4. Thanks!

--Jacob

Attachments:

v4-0001-Move-inet_net_pton-to-src-port.patchtext/x-patch; name=v4-0001-Move-inet_net_pton-to-src-port.patchDownload
From e54b41c722d04ea6eabe4434bdf10c92fa79a192 Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Wed, 24 Nov 2021 14:46:11 -0800
Subject: [PATCH v4 1/3] Move inet_net_pton() to src/port

This will be helpful for IP address verification in libpq.
---
 src/backend/utils/adt/Makefile                  |  1 -
 src/include/port.h                              |  3 +++
 src/include/utils/builtins.h                    |  4 ----
 src/port/Makefile                               |  1 +
 src/{backend/utils/adt => port}/inet_net_pton.c | 16 +++++++++++++++-
 src/tools/msvc/Mkvcbuild.pm                     |  2 +-
 6 files changed, 20 insertions(+), 7 deletions(-)
 rename src/{backend/utils/adt => port}/inet_net_pton.c (96%)

diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile
index 41b486bcef..d173d52157 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -43,7 +43,6 @@ OBJS = \
 	geo_selfuncs.o \
 	geo_spgist.o \
 	inet_cidr_ntop.o \
-	inet_net_pton.o \
 	int.o \
 	int8.o \
 	json.o \
diff --git a/src/include/port.h b/src/include/port.h
index 3d103a2b31..2852e5b58b 100644
--- a/src/include/port.h
+++ b/src/include/port.h
@@ -515,6 +515,9 @@ extern int	pg_codepage_to_encoding(UINT cp);
 extern char *pg_inet_net_ntop(int af, const void *src, int bits,
 							  char *dst, size_t size);
 
+/* port/inet_net_pton.c */
+extern int	pg_inet_net_pton(int af, const char *src, void *dst, size_t size);
+
 /* port/pg_strong_random.c */
 extern void pg_strong_random_init(void);
 extern bool pg_strong_random(void *buf, size_t len);
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index d8f05a7df3..22ebbfda17 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -93,10 +93,6 @@ extern int	xidLogicalComparator(const void *arg1, const void *arg2);
 extern char *pg_inet_cidr_ntop(int af, const void *src, int bits,
 							   char *dst, size_t size);
 
-/* inet_net_pton.c */
-extern int	pg_inet_net_pton(int af, const char *src,
-							 void *dst, size_t size);
-
 /* network.c */
 extern double convert_network_to_scalar(Datum value, Oid typid, bool *failure);
 extern Datum network_scan_first(Datum in);
diff --git a/src/port/Makefile b/src/port/Makefile
index bfe1feb0d4..c3ae7b3d5c 100644
--- a/src/port/Makefile
+++ b/src/port/Makefile
@@ -43,6 +43,7 @@ OBJS = \
 	bsearch_arg.o \
 	chklocale.o \
 	inet_net_ntop.o \
+	inet_net_pton.o \
 	noblock.o \
 	path.o \
 	pg_bitutils.o \
diff --git a/src/backend/utils/adt/inet_net_pton.c b/src/port/inet_net_pton.c
similarity index 96%
rename from src/backend/utils/adt/inet_net_pton.c
rename to src/port/inet_net_pton.c
index d3221a1313..bae50ba67e 100644
--- a/src/backend/utils/adt/inet_net_pton.c
+++ b/src/port/inet_net_pton.c
@@ -14,14 +14,18 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
  * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  *
- *	  src/backend/utils/adt/inet_net_pton.c
+ *	  src/port/inet_net_pton.c
  */
 
 #if defined(LIBC_SCCS) && !defined(lint)
 static const char rcsid[] = "Id: inet_net_pton.c,v 1.4.2.3 2004/03/17 00:40:11 marka Exp $";
 #endif
 
+#ifndef FRONTEND
 #include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
 
 #include <sys/socket.h>
 #include <netinet/in.h>
@@ -29,9 +33,19 @@ static const char rcsid[] = "Id: inet_net_pton.c,v 1.4.2.3 2004/03/17 00:40:11 m
 #include <assert.h>
 #include <ctype.h>
 
+#ifndef FRONTEND
 #include "utils/builtins.h" /* pgrminclude ignore */	/* needed on some
 														 * platforms */
 #include "utils/inet.h"
+#else
+/*
+ * In a frontend build, we can't include inet.h, but we still need to have
+ * sensible definitions of these two constants.  Note that pg_inet_net_ntop()
+ * assumes that PGSQL_AF_INET is equal to AF_INET.
+ */
+#define PGSQL_AF_INET	(AF_INET + 0)
+#define PGSQL_AF_INET6	(AF_INET + 1)
+#endif
 
 
 static int	inet_net_pton_ipv4(const char *src, u_char *dst);
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index a310bcb28c..b566a4aed7 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -100,7 +100,7 @@ sub mkvcbuild
 
 	our @pgportfiles = qw(
 	  chklocale.c explicit_bzero.c fls.c getpeereid.c getrusage.c inet_aton.c
-	  getaddrinfo.c gettimeofday.c inet_net_ntop.c kill.c open.c
+	  getaddrinfo.c gettimeofday.c inet_net_ntop.c inet_net_pton.c kill.c open.c
 	  snprintf.c strlcat.c strlcpy.c dirmod.c noblock.c path.c
 	  dirent.c dlopen.c getopt.c getopt_long.c link.c
 	  pread.c preadv.c pwrite.c pwritev.c pg_bitutils.c
-- 
2.25.1

v4-0002-libpq-allow-IP-address-SANs-in-server-certs.patchtext/x-patch; name=v4-0002-libpq-allow-IP-address-SANs-in-server-certs.patchDownload
From c73dd51c92a7a74106ea6df8b1de9763f29327f6 Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Thu, 18 Nov 2021 15:36:18 -0800
Subject: [PATCH v4 2/3] libpq: allow IP address SANs in server certs

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.

- Unlike NSS, we don't map IPv4 to IPv6 addresses, or vice-versa.

- Move PGSQL_AF_INET* to inet-common.h to reduce copy-paste.
---
 src/include/common/inet-common.h              |  35 ++++++
 src/include/utils/inet.h                      |  13 +-
 src/interfaces/libpq/fe-secure-common.c       | 102 ++++++++++++++++
 src/interfaces/libpq/fe-secure-common.h       |   4 +
 src/interfaces/libpq/fe-secure-openssl.c      | 114 ++++++++++++++++--
 src/port/inet_net_ntop.c                      |  12 +-
 src/port/inet_net_pton.c                      |  10 +-
 .../conf/server-cn-and-ip-alt-names.config    |  24 ++++
 src/test/ssl/conf/server-ip-alt-names.config  |  19 +++
 .../conf/server-ip-cn-and-alt-names.config    |  21 ++++
 .../server-ip-cn-and-dns-alt-names.config     |  21 ++++
 src/test/ssl/conf/server-ip-cn-only.config    |  12 ++
 .../ssl/ssl/server-cn-and-ip-alt-names.crt    |  20 +++
 .../ssl/ssl/server-cn-and-ip-alt-names.key    |  27 +++++
 src/test/ssl/ssl/server-ip-alt-names.crt      |  19 +++
 src/test/ssl/ssl/server-ip-alt-names.key      |  27 +++++
 .../ssl/ssl/server-ip-cn-and-alt-names.crt    |  19 +++
 .../ssl/ssl/server-ip-cn-and-alt-names.key    |  27 +++++
 .../ssl/server-ip-cn-and-dns-alt-names.crt    |  20 +++
 .../ssl/server-ip-cn-and-dns-alt-names.key    |  27 +++++
 src/test/ssl/ssl/server-ip-cn-only.crt        |  18 +++
 src/test/ssl/ssl/server-ip-cn-only.key        |  27 +++++
 src/test/ssl/sslfiles.mk                      |   5 +
 src/test/ssl/t/001_ssltests.pl                | 101 +++++++++++++++-
 24 files changed, 678 insertions(+), 46 deletions(-)
 create mode 100644 src/include/common/inet-common.h
 create mode 100644 src/test/ssl/conf/server-cn-and-ip-alt-names.config
 create mode 100644 src/test/ssl/conf/server-ip-alt-names.config
 create mode 100644 src/test/ssl/conf/server-ip-cn-and-alt-names.config
 create mode 100644 src/test/ssl/conf/server-ip-cn-and-dns-alt-names.config
 create mode 100644 src/test/ssl/conf/server-ip-cn-only.config
 create mode 100644 src/test/ssl/ssl/server-cn-and-ip-alt-names.crt
 create mode 100644 src/test/ssl/ssl/server-cn-and-ip-alt-names.key
 create mode 100644 src/test/ssl/ssl/server-ip-alt-names.crt
 create mode 100644 src/test/ssl/ssl/server-ip-alt-names.key
 create mode 100644 src/test/ssl/ssl/server-ip-cn-and-alt-names.crt
 create mode 100644 src/test/ssl/ssl/server-ip-cn-and-alt-names.key
 create mode 100644 src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.crt
 create mode 100644 src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.key
 create mode 100644 src/test/ssl/ssl/server-ip-cn-only.crt
 create mode 100644 src/test/ssl/ssl/server-ip-cn-only.key

diff --git a/src/include/common/inet-common.h b/src/include/common/inet-common.h
new file mode 100644
index 0000000000..3ad72261b6
--- /dev/null
+++ b/src/include/common/inet-common.h
@@ -0,0 +1,35 @@
+/*-------------------------------------------------------------------------
+ *
+ * inet-common.h
+ *
+ *	  Common code for clients of the inet_* APIs.
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/common/inet-common.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef INET_COMMON_H
+#define INET_COMMON_H
+
+/*
+ * We use these values for the "family" field of inet_struct.
+ *
+ * Referencing all of the non-AF_INET types to AF_INET lets us work on
+ * machines which may not have the appropriate address family (like
+ * inet6 addresses when AF_INET6 isn't present) but doesn't cause a
+ * dump/reload requirement.  Pre-7.4 databases used AF_INET for the family
+ * type on disk.
+ *
+ * In a frontend build, we can't include inet.h, but we still need to have
+ * sensible definitions of these two constants.  (Frontend clients should
+ * include this header directly.)  Note that pg_inet_net_ntop()
+ * assumes that PGSQL_AF_INET is equal to AF_INET.
+ */
+#define PGSQL_AF_INET	(AF_INET + 0)
+#define PGSQL_AF_INET6	(AF_INET + 1)
+
+#endif							/* INET_COMMON_H */
diff --git a/src/include/utils/inet.h b/src/include/utils/inet.h
index 3073c0307e..c56c016987 100644
--- a/src/include/utils/inet.h
+++ b/src/include/utils/inet.h
@@ -14,6 +14,7 @@
 #ifndef INET_H
 #define INET_H
 
+#include "common/inet-common.h"
 #include "fmgr.h"
 
 /*
@@ -27,18 +28,6 @@ typedef struct
 	unsigned char ipaddr[16];	/* up to 128 bits of address */
 } inet_struct;
 
-/*
- * We use these values for the "family" field.
- *
- * Referencing all of the non-AF_INET types to AF_INET lets us work on
- * machines which may not have the appropriate address family (like
- * inet6 addresses when AF_INET6 isn't present) but doesn't cause a
- * dump/reload requirement.  Pre-7.4 databases used AF_INET for the family
- * type on disk.
- */
-#define PGSQL_AF_INET	(AF_INET + 0)
-#define PGSQL_AF_INET6	(AF_INET + 1)
-
 /*
  * Both INET and CIDR addresses are represented within Postgres as varlena
  * objects, ie, there is a varlena header in front of the struct type
diff --git a/src/interfaces/libpq/fe-secure-common.c b/src/interfaces/libpq/fe-secure-common.c
index bd46f08fae..e8e8878d3d 100644
--- a/src/interfaces/libpq/fe-secure-common.c
+++ b/src/interfaces/libpq/fe-secure-common.c
@@ -19,9 +19,13 @@
 
 #include "postgres_fe.h"
 
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
 #include "fe-secure-common.h"
 
 #include "libpq-int.h"
+#include "port.h"
 #include "pqexpbuffer.h"
 
 /*
@@ -144,6 +148,104 @@ pq_verify_peer_name_matches_certificate_name(PGconn *conn,
 	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 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 = PGSQL_AF_INET;
+
+		/*
+		 * The use of inet_aton() instead of inet_pton() is deliberate; the
+		 * latter cannot handle alternate IPv4 notations ("numbers-and-dots").
+		 */
+		if (inet_aton(host, &addr))
+		{
+			if (memcmp(ipdata, &addr.s_addr, iplen) == 0)
+				match = 1;
+		}
+	}
+	else if (iplen == 16)
+	{
+		/* IPv6 */
+		unsigned char addr[16];
+
+		family = PGSQL_AF_INET6;
+
+		/*
+		 * pg_inet_net_pton() will accept CIDR masks, which we don't want to
+		 * match, so skip the comparison if the host string contains a slash.
+		 */
+		if (!strchr(host, '/')
+			&& pg_inet_net_pton(PGSQL_AF_INET6, host, addr, -1) == 128)
+		{
+			if (memcmp(ipdata, addr, iplen) == 0)
+				match = 1;
+		}
+	}
+	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.
  *
diff --git a/src/interfaces/libpq/fe-secure-common.h b/src/interfaces/libpq/fe-secure-common.h
index 1cca6d785a..4316c3d1b7 100644
--- a/src/interfaces/libpq/fe-secure-common.h
+++ b/src/interfaces/libpq/fe-secure-common.h
@@ -16,11 +16,15 @@
 #ifndef FE_SECURE_COMMON_H
 #define FE_SECURE_COMMON_H
 
+#include "common/inet-common.h"
 #include "libpq-fe.h"
 
 extern int	pq_verify_peer_name_matches_certificate_name(PGconn *conn,
 														 const char *namedata, size_t namelen,
 														 char **store_name);
+extern int	pq_verify_peer_name_matches_certificate_ip(PGconn *conn,
+													   const char *addrdata, size_t addrlen,
+													   char **store_name);
 extern bool pq_verify_peer_name_matches_certificate(PGconn *conn);
 
 #endif							/* FE_SECURE_COMMON_H */
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index 9f735ba437..93e80c79d4 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -72,6 +72,9 @@ static int	verify_cb(int ok, X509_STORE_CTX *ctx);
 static int	openssl_verify_peer_name_matches_certificate_name(PGconn *conn,
 															  ASN1_STRING *name,
 															  char **store_name);
+static int	openssl_verify_peer_name_matches_certificate_ip(PGconn *conn,
+															ASN1_OCTET_STRING *addr_entry,
+															char **store_name);
 static void destroy_ssl_system(void);
 static int	initialize_SSL(PGconn *conn);
 static PostgresPollingStatusType open_client_SSL(PGconn *);
@@ -509,6 +512,52 @@ openssl_verify_peer_name_matches_certificate_name(PGconn *conn, ASN1_STRING *nam
 	return pq_verify_peer_name_matches_certificate_name(conn, (const char *) namedata, len, store_name);
 }
 
+/*
+ * OpenSSL-specific wrapper around
+ * pq_verify_peer_name_matches_certificate_ip(), converting the
+ * ASN1_OCTET_STRING into a plain C string.
+ */
+static int
+openssl_verify_peer_name_matches_certificate_ip(PGconn *conn,
+												ASN1_OCTET_STRING *addr_entry,
+												char **store_name)
+{
+	int			len;
+	const unsigned char *addrdata;
+
+	/* Should not happen... */
+	if (addr_entry == NULL)
+	{
+		appendPQExpBufferStr(&conn->errorMessage,
+							 libpq_gettext("SSL certificate's address entry is missing\n"));
+		return -1;
+	}
+
+	/*
+	 * GEN_IPADD is an OCTET STRING containing an IP address in network byte
+	 * order.
+	 */
+#ifdef HAVE_ASN1_STRING_GET0_DATA
+	addrdata = ASN1_STRING_get0_data(addr_entry);
+#else
+	addrdata = ASN1_STRING_data(addr_entry);
+#endif
+	len = ASN1_STRING_length(addr_entry);
+
+	/* OK to cast from unsigned to plain char, since it's all ASCII. */
+	return pq_verify_peer_name_matches_certificate_ip(conn, (const char *) addrdata, len, store_name);
+}
+
+static bool
+is_ip_address(const char *host)
+{
+	struct in_addr	dummy4;
+	unsigned char	dummy6[16];
+
+	return inet_aton(host, &dummy4)
+		|| (pg_inet_net_pton(PGSQL_AF_INET6, host, dummy6, -1) == 128);
+}
+
 /*
  *	Verify that the server certificate matches the hostname we connected to.
  *
@@ -522,6 +571,36 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 	STACK_OF(GENERAL_NAME) * peer_san;
 	int			i;
 	int			rc = 0;
+	char	   *host = conn->connhost[conn->whichhost].host;
+	int			host_type;
+	bool		check_cn = true;
+
+	Assert(host && host[0]); /* should be guaranteed by caller */
+
+	/*
+	 * We try to match the NSS behavior here, which is a slight departure from
+	 * the spec but seems to make more intuitive sense:
+	 *
+	 * If connhost contains a DNS name, and the certificate's SANs contain any
+	 * dNSName entries, then we'll ignore the Subject Common Name entirely;
+	 * otherwise, we fall back to checking the CN. (This behavior matches the
+	 * RFC.)
+	 *
+	 * If connhost contains an IP address, and the SANs contain iPAddress
+	 * entries, we again ignore the CN. Otherwise, we allow the CN to match,
+	 * EVEN IF there is a dNSName in the SANs. (RFC 6125 prohibits this: "A
+	 * client MUST NOT seek a match for a reference identifier of CN-ID if the
+	 * presented identifiers include a DNS-ID, SRV-ID, URI-ID, or any
+	 * application-specific identifier types supported by the client.")
+	 *
+	 * NOTE: Prior versions of libpq did not consider iPAddress entries at all,
+	 * so this new behavior might break a certificate that has different IP
+	 * addresses in the Subject CN and the SANs.
+	 */
+	if (is_ip_address(host))
+		host_type = GEN_IPADD;
+	else
+		host_type = GEN_DNS;
 
 	/*
 	 * First, get the Subject Alternative Names (SANs) from the certificate,
@@ -537,24 +616,34 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 		for (i = 0; i < san_len; i++)
 		{
 			const GENERAL_NAME *name = sk_GENERAL_NAME_value(peer_san, i);
+			char	   *alt_name = NULL;
+
+			if (name->type == host_type)
+				check_cn = false;
 
 			if (name->type == GEN_DNS)
 			{
-				char	   *alt_name;
-
 				(*names_examined)++;
 				rc = openssl_verify_peer_name_matches_certificate_name(conn,
 																	   name->d.dNSName,
 																	   &alt_name);
+			}
+			else if (name->type == GEN_IPADD)
+			{
+				(*names_examined)++;
+				rc = openssl_verify_peer_name_matches_certificate_ip(conn,
+																	 name->d.iPAddress,
+																	 &alt_name);
+			}
 
-				if (alt_name)
-				{
-					if (!*first_name)
-						*first_name = alt_name;
-					else
-						free(alt_name);
-				}
+			if (alt_name)
+			{
+				if (!*first_name)
+					*first_name = alt_name;
+				else
+					free(alt_name);
 			}
+
 			if (rc != 0)
 				break;
 		}
@@ -562,13 +651,14 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 	}
 
 	/*
-	 * If there is no subjectAltName extension of type dNSName, check the
+	 * If there is no subjectAltName extension of the matching type, check the
 	 * Common Name.
 	 *
 	 * (Per RFC 2818 and RFC 6125, if the subjectAltName extension of type
-	 * dNSName is present, the CN must be ignored.)
+	 * dNSName is present, the CN must be ignored. We break this rule if host is
+	 * an IP address; see the comment above.)
 	 */
-	if (*names_examined == 0)
+	if ((rc == 0) && check_cn)
 	{
 		X509_NAME  *subject_name;
 
diff --git a/src/port/inet_net_ntop.c b/src/port/inet_net_ntop.c
index b8ad69c390..af28056132 100644
--- a/src/port/inet_net_ntop.c
+++ b/src/port/inet_net_ntop.c
@@ -31,17 +31,7 @@ static const char rcsid[] = "Id: inet_net_ntop.c,v 1.1.2.2 2004/03/09 09:17:27 m
 #include <netinet/in.h>
 #include <arpa/inet.h>
 
-#ifndef FRONTEND
-#include "utils/inet.h"
-#else
-/*
- * In a frontend build, we can't include inet.h, but we still need to have
- * sensible definitions of these two constants.  Note that pg_inet_net_ntop()
- * assumes that PGSQL_AF_INET is equal to AF_INET.
- */
-#define PGSQL_AF_INET	(AF_INET + 0)
-#define PGSQL_AF_INET6	(AF_INET + 1)
-#endif
+#include "common/inet-common.h"
 
 
 #define NS_IN6ADDRSZ 16
diff --git a/src/port/inet_net_pton.c b/src/port/inet_net_pton.c
index bae50ba67e..5bae811c6f 100644
--- a/src/port/inet_net_pton.c
+++ b/src/port/inet_net_pton.c
@@ -33,18 +33,10 @@ static const char rcsid[] = "Id: inet_net_pton.c,v 1.4.2.3 2004/03/17 00:40:11 m
 #include <assert.h>
 #include <ctype.h>
 
+#include "common/inet-common.h"
 #ifndef FRONTEND
 #include "utils/builtins.h" /* pgrminclude ignore */	/* needed on some
 														 * platforms */
-#include "utils/inet.h"
-#else
-/*
- * In a frontend build, we can't include inet.h, but we still need to have
- * sensible definitions of these two constants.  Note that pg_inet_net_ntop()
- * assumes that PGSQL_AF_INET is equal to AF_INET.
- */
-#define PGSQL_AF_INET	(AF_INET + 0)
-#define PGSQL_AF_INET6	(AF_INET + 1)
 #endif
 
 
diff --git a/src/test/ssl/conf/server-cn-and-ip-alt-names.config b/src/test/ssl/conf/server-cn-and-ip-alt-names.config
new file mode 100644
index 0000000000..a6fa09bad3
--- /dev/null
+++ b/src/test/ssl/conf/server-cn-and-ip-alt-names.config
@@ -0,0 +1,24 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+# This certificate contains a CN and SANs for both IPv4 and IPv6.
+
+
+[ req ]
+distinguished_name     = req_distinguished_name
+req_extensions         = v3_req
+prompt                 = no
+
+[ req_distinguished_name ]
+# Note: According to RFC 2818 and 6125, the CN is ignored, when DNS names are
+# present in the SANs. But they are silent on whether the CN is checked when IP
+# addresses are present.
+CN = common-name.pg-ssltest.test
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
+subjectAltName = @alt_names
+
+[ alt_names ]
+IP.1 = 192.0.2.1
+IP.2 = 2001:DB8::1
diff --git a/src/test/ssl/conf/server-ip-alt-names.config b/src/test/ssl/conf/server-ip-alt-names.config
new file mode 100644
index 0000000000..c22f22951a
--- /dev/null
+++ b/src/test/ssl/conf/server-ip-alt-names.config
@@ -0,0 +1,19 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+# This certificate has a two IP-address SANs, and no CN.
+
+[ req ]
+distinguished_name     = req_distinguished_name
+req_extensions         = v3_req
+prompt                 = no
+
+[ req_distinguished_name ]
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
+subjectAltName = @alt_names
+
+[ alt_names ]
+IP.1 = 192.0.2.1
+IP.2 = 2001:DB8::1
diff --git a/src/test/ssl/conf/server-ip-cn-and-alt-names.config b/src/test/ssl/conf/server-ip-cn-and-alt-names.config
new file mode 100644
index 0000000000..a4087f0a18
--- /dev/null
+++ b/src/test/ssl/conf/server-ip-cn-and-alt-names.config
@@ -0,0 +1,21 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+# This certificate contains both a CN and SANs in IP address format.
+
+
+[ req ]
+distinguished_name     = req_distinguished_name
+req_extensions         = v3_req
+prompt                 = no
+
+[ req_distinguished_name ]
+CN = 192.0.2.1
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
+subjectAltName = @alt_names
+
+[ alt_names ]
+IP.1 = 192.0.2.2
+IP.2 = 2001:DB8::1
diff --git a/src/test/ssl/conf/server-ip-cn-and-dns-alt-names.config b/src/test/ssl/conf/server-ip-cn-and-dns-alt-names.config
new file mode 100644
index 0000000000..7121803b49
--- /dev/null
+++ b/src/test/ssl/conf/server-ip-cn-and-dns-alt-names.config
@@ -0,0 +1,21 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+# This certificate contains both a CN and SANs in IP address format.
+
+
+[ req ]
+distinguished_name     = req_distinguished_name
+req_extensions         = v3_req
+prompt                 = no
+
+[ req_distinguished_name ]
+CN = 192.0.2.1
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
+subjectAltName = @alt_names
+
+[ alt_names ]
+DNS.1 = dns1.alt-name.pg-ssltest.test
+DNS.2 = dns2.alt-name.pg-ssltest.test
diff --git a/src/test/ssl/conf/server-ip-cn-only.config b/src/test/ssl/conf/server-ip-cn-only.config
new file mode 100644
index 0000000000..585d8bdae8
--- /dev/null
+++ b/src/test/ssl/conf/server-ip-cn-only.config
@@ -0,0 +1,12 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+
+[ req ]
+distinguished_name     = req_distinguished_name
+prompt                 = no
+
+[ req_distinguished_name ]
+CN = 192.0.2.1
+OU = PostgreSQL test suite
+
+# No Subject Alternative Names
diff --git a/src/test/ssl/ssl/server-cn-and-ip-alt-names.crt b/src/test/ssl/ssl/server-cn-and-ip-alt-names.crt
new file mode 100644
index 0000000000..4e58c85ccb
--- /dev/null
+++ b/src/test/ssl/ssl/server-cn-and-ip-alt-names.crt
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDLzCCAhegAwIBAgIIICERKRE1UQAwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UE
+Aww3VGVzdCBDQSBmb3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHNl
+cnZlciBjZXJ0czAeFw0yMTExMjkxOTM1NTFaFw00OTA0MTYxOTM1NTFaMEYxHjAc
+BgNVBAsMFVBvc3RncmVTUUwgdGVzdCBzdWl0ZTEkMCIGA1UEAwwbY29tbW9uLW5h
+bWUucGctc3NsdGVzdC50ZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
+AQEA6+8IYKAFnZ7V+fDo1cyMpbGBLzCfJOQ/1o2jOGP4+GjpsZgv6S6UT2MheC8M
+iiEFrYwdsSIZyYc3jEZrluy/UuR0bCGtqU92BCqa0iBLhvHOgjR588u253eLxQtQ
+8iJn11QPrKMk35nMkmY8GfHt4sGFbvBL6+GpipHq7a6cde3Z+v4kCB5dKMYDUDtm
+3mJmviuGNAu5wOqItk2Yi5dwJs1054007KNH0Il43urxiOfnkLS0cG5kehboPf86
+vxBt3iHByrU/9/DY5IvQCfSXVNa6rb5w5/pGja9aCei6Mv1jQY/V8SMQTga+MOsA
+0WB9akxMi2NxwS2+BQ4k/McPlwIDAQABoyUwIzAhBgNVHREEGjAYhwTAAAIBhxAg
+AQ24AAAAAAAAAAAAAAABMA0GCSqGSIb3DQEBCwUAA4IBAQAQLo2RzC07dG9p+J3A
+W6C0p3Y+Os/YE2D9wfp4TIDTZxcRUQZ0S6ahF1N6sp8l9KHBJHPU1cUpRAU1oD+Y
+SqmnP/VJRRDTTj9Ytdc/Vuo2jeLpSYhVKrCqtjqIrCwYJFoYRmMoxTtJGlwA0hSd
+kwo3XYrALPUQWUErTYPvNfDNIuUwqUXNfS0CXuIOVN3LJ+shegg6Pwbh9B5T9NHx
+kH+HswajhdpdnZIgh0FYTlTCPILDrB49aOWwqLa54AUA6WXa35hPsP8SoqL9Eucq
+ifPhBYyadsjOb+70N8GbbAsDPN1jCX9L8RuNcEkxSCKCYx91cWXh7K5KMPuGlzB7
+j8xB
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-cn-and-ip-alt-names.key b/src/test/ssl/ssl/server-cn-and-ip-alt-names.key
new file mode 100644
index 0000000000..837eef996d
--- /dev/null
+++ b/src/test/ssl/ssl/server-cn-and-ip-alt-names.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA6+8IYKAFnZ7V+fDo1cyMpbGBLzCfJOQ/1o2jOGP4+GjpsZgv
+6S6UT2MheC8MiiEFrYwdsSIZyYc3jEZrluy/UuR0bCGtqU92BCqa0iBLhvHOgjR5
+88u253eLxQtQ8iJn11QPrKMk35nMkmY8GfHt4sGFbvBL6+GpipHq7a6cde3Z+v4k
+CB5dKMYDUDtm3mJmviuGNAu5wOqItk2Yi5dwJs1054007KNH0Il43urxiOfnkLS0
+cG5kehboPf86vxBt3iHByrU/9/DY5IvQCfSXVNa6rb5w5/pGja9aCei6Mv1jQY/V
+8SMQTga+MOsA0WB9akxMi2NxwS2+BQ4k/McPlwIDAQABAoIBAQCuNFKVNdKvrUYF
+RLJGmsAG3+eo9lern7TbML2ht39vu9dBwEMwA6qSa3mdCfBSVUuh9uE9lxY/TU3g
+j2aFi81A4VptNPjLGNblAKhMGnhp7UUzspeRQYuNoSFcnpxoDKtrvK/OIq/pQeBh
+AIfECHRDh+yEG32Tb44FuPQkB1eTYl8xbMEImrhNUaSjJk7tTsmydHy0DjmqHVKX
+HUj0TREfDBDOBiHtY0XV6Pu3bnqDH/TKLTfUf3UdfTuay3Yai9aEcRPWp9GrMO7G
+axsKCifTz6177gyr6Fv8HLeMZMh9rMZRn3e0zfaF6vrH1QnZZOts5jpUa0KugSCd
+//uC0iNxAoGBAPXVc3b+o3hY5gcwwpaW6JtsarDrmNRxrizqIDG7NgpqwdFXgTi6
+6q0t2pjv81ATqij69IcPkNSissyR4OEKnu/OFJWzreg8yLi75WHKi0E/6msHpwRk
+d1yP0Zgd05ots/yOjDSp593RagaPVvHBxMECZ/Tm3B+Tq55Azudd/zvLAoGBAPWw
+xf0oUEJl6NdUZD6K7eFc6jf8yrpD85dldeko6LeN8x0XlKKWvUDJ2+3oizXoQvCm
+8by6KOYEIo4MrtXuy9MmtPWfNvRBr+hsUHchIj7IgFa9bKXyK2FnJqu/8CbEymli
+eZu7hoOhelurhnFy1zSqwNO4GC+kw60Y/BO3Z1nlAoGAVOyYJtNwxXJwhKtjjYI0
+ePzLHrNE6J8c/Ick+AkkchTPP/JqwZ5Q0+KzUYITG+avMdkAAGhwMATEn8cFWLjC
+jzUyB0U7Hq9g5/CBHXdLBA+Ae9j46ZuLYH6OeW5UWz7OnsDfzpGjeA2QAxQhhQLb
+ZZHfN8tI39+zucfJskPWmGECgYEAg9guF1Fn6InJrqwR82IYj6SN6CeXHufSM392
+C/4xDDd3rDf4QlwECV2J0RzGf9I5Ae2EshNwWScE6Be0RweTh6cw2tJq6h7J6D8f
+2x4Dw49TF7klMdRIJUf2f5pLpHJccLswqTqzz7V69PCSABVxmUi8m6EiEYconp5W
+v7nfE2UCgYALrEqzncuSIX3q6TVAjnzT7gO4h8h2TUekIWdHQFldFx8R7Kncggnd
+48gQqhewchNR83UCcd7pPsCcTqu6UR1QRdq/DV5P6J3xdZ2iS/2gCM6hvWIvKZEv
+/ClnkyFCOW7zX6RKIXtRYZTV1kz3TajApi34RTIeIMTieaCarnBJbA==
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-ip-alt-names.crt b/src/test/ssl/ssl/server-ip-alt-names.crt
new file mode 100644
index 0000000000..8a1bc620bb
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-alt-names.crt
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDCTCCAfGgAwIBAgIIICERKREEUAAwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UE
+Aww3VGVzdCBDQSBmb3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHNl
+cnZlciBjZXJ0czAeFw0yMTExMjkxOTA0NTBaFw00OTA0MTYxOTA0NTBaMCAxHjAc
+BgNVBAsMFVBvc3RncmVTUUwgdGVzdCBzdWl0ZTCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBAOM8yB6aVWb17ujr3ayU62mxHQoqn4CvG9yXlJvGOGv/ursW
+Vs0UYJdc96LsNZN1szdm9ayNzCIw3eja+ULsjxCi6+3LM4pO76IORL/XFamlTPYb
+BZ4pHdZVB0nnZAAnWCZPyXdnjOKQ5+8unVXkfibkjj8UELBJ2snehsOa+CTkOBez
+zxYMqxAgbywLIYsW448brun7UXpWmqbGK+SsdGaIZ5Sb7Zezc5lt6CrLemTZTHHK
+7l4WZFCCEi4t3sgO8o1vDELD/IE5G8lyXvIdgJg6t8ssper7iCw6S8x+okhjiSjT
+vDLU2g4AanqZRZB49aPwTo0QUcJA2BCJxL9xLy8CAwEAAaMlMCMwIQYDVR0RBBow
+GIcEwAACAYcQIAENuAAAAAAAAAAAAAAAATANBgkqhkiG9w0BAQsFAAOCAQEAwZJ+
+8KpABTlMEgKnHIYb35ItGhtFiTLQta9RkXx7vaeDwpOdPP/IvuvpjpQZkobRgBsk
+bNM0KuJpd2mSTphQAt6eKQIdcPrkzvc/Yh9OK3YNLUAbu/ZhBUnBvFnUL4wn2f1U
+mfO+m8P/LxybwqKx7r1mbaB+tP3RTxxLcIMvm9ECPQEoBntfEL325Wdoj+WuQH5Y
+IvcM6FaCTkQsNIPbaBD5l5MhMLHRULZujbDjXqGSvRMQfns6np/biMjNdQA8NZ5z
+STeUFvkQbCxoA0YYLgoSHL5KhZjXrg2g+T+2TUyCTR/91xf9OoOjBZdixR0S0DzJ
+B1+5vnUjZaCfnSEA7A==
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-ip-alt-names.key b/src/test/ssl/ssl/server-ip-alt-names.key
new file mode 100644
index 0000000000..b210b3a991
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-alt-names.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEA4zzIHppVZvXu6OvdrJTrabEdCiqfgK8b3JeUm8Y4a/+6uxZW
+zRRgl1z3ouw1k3WzN2b1rI3MIjDd6Nr5QuyPEKLr7cszik7vog5Ev9cVqaVM9hsF
+nikd1lUHSedkACdYJk/Jd2eM4pDn7y6dVeR+JuSOPxQQsEnayd6Gw5r4JOQ4F7PP
+FgyrECBvLAshixbjjxuu6ftRelaapsYr5Kx0ZohnlJvtl7NzmW3oKst6ZNlMccru
+XhZkUIISLi3eyA7yjW8MQsP8gTkbyXJe8h2AmDq3yyyl6vuILDpLzH6iSGOJKNO8
+MtTaDgBqeplFkHj1o/BOjRBRwkDYEInEv3EvLwIDAQABAoIBACp3uY6+mSdc3wF4
+0zzlt/lQuHSl8plCIJrhWUyjhvfoGyXLzv0Uydh/72frbTfZz1yTSWauOXBKYa6a
+/eqb+0DIsf8G8uLuTaqjsAWKVOoXkoKMGkistn7P9UTCkdXVhIvkbWp7V8EgA7iX
+pZ/fzBPIsyzmuxe3NcR0ags0cxuxkNuu+YXDv1oTedmT2wS3CZq1d/T1Y/EOVIf8
+Iznd2aOverlsnt6iiQ3ZWdG/W5F8FhnrR/rrBdYsdCv6TH/KUYexnDOUYpayjDbu
+oAKnifPp6UqiOM4SuBL83OAz19jptp5vpF370BEVRs3eK0q+zo/mETjv9HsXdolZ
+lfoXA0ECgYEA/7nb2azbq/2muvXCh1ZxCEbn3mt8KXoJP/xkx/v9eEc/cc5Q9e0V
+2oGfjC2hSE+bjOWMwiUMD6uU+iRjhz5A3IvUxnoSdoL7H9p0hTqLMyP7dTDkoVF5
+aEuLMaiI5YEnfAFu9L5h8ZKieoQTBoscT06wnGjh9pBV9bthfTKA7ksCgYEA43sb
+55m9WL4kWCPwOAp3vdEAFyxzmZHlO26sEQOU/m5aN01pumYybBruziEXMI96yfTj
+VmXKReeYb6XUiCcs3fLSipD/+8/8CsjO4uMORtxWumXe8AbKZfysGFzL7wJlByGT
+38AGQwIG/XD8cKnaiEMX4E/3Owbcoxwixo3WZC0CgYEAovaqJ9mEU+Jc8h/TS7PG
+bGPjN1Z/1V6zrlcFUnw/Vvrwb3HvHglsN8cLCaW6df5lPjC6tq4tNX8+fPnbg0Ak
+zWc+vQzl3ygxKGdqgcyBEKIJiPETgcoN+GzL02V3d+oKY3f2YXlBqVSsvi6UgUL9
+U3zuB36/IQVyAhrbUZFxoGkCgYEAnaFAO+Nvrp/LhXwZyGuQf+rkmipGTIMpil5t
+QzjtNMV5JFszSWPpyrl7A0Ew1YiG+I0GP2c3m+sY2TzbIiGrWH0b4cMKbw63Qy3V
+FqlpyjaCrpVKv56k/7jv883RzuQk56Uf1+szK5mrCFITy2oXsVZ0pA4lbjSaDTjA
+7D968V0CgYEA+qKqXKL98+c5CMPnpf+0B1x2zgyUym1ouPfon2x5fhK84T53zDMA
+zfdUJ/SOZw6/c9vRF7RL8h+ZfFdIyoAXv4Tt6mIiZe7P+AUVg6XgJ0ce2MUSeWjI
+W8D4WdSi0jyqr99TuVBWhbTZJviMB3pHqKaHQ07hnd/lPtvzsiH12qk=
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-ip-cn-and-alt-names.crt b/src/test/ssl/ssl/server-ip-cn-and-alt-names.crt
new file mode 100644
index 0000000000..2be02feb03
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-and-alt-names.crt
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDHTCCAgWgAwIBAgIIICIBBBQ2MQAwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UE
+Aww3VGVzdCBDQSBmb3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHNl
+cnZlciBjZXJ0czAeFw0yMjAxMDQyMjM2MzFaFw00OTA1MjIyMjM2MzFaMDQxHjAc
+BgNVBAsMFVBvc3RncmVTUUwgdGVzdCBzdWl0ZTESMBAGA1UEAwwJMTkyLjAuMi4x
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwmqTdQJfs2Ti9tPitYp2
+27I0HvL/kNSgA6egFr0foRo0BorwJNIzdbV0+EnsfiBNTWL5It26gqO7UP3ms8t2
+vHD5gkXfT+f6ts0lVJEcIOkUD/8ws4Ic9Y4uPqb4gN+pUKqcxtmLW1TYk84MBK59
+Xz4yPPS6N+G/DMMeFHTNkM9EQwn/+DC3fDsWdGYM2GRWDTJGg1A5tSUcF+seu7i1
+Vg7XajBfsvgAUAsrAxV+X/sLZh94HY+paD6wfaI99mY2OXVc/XW/z1r9WQznor65
+ZkonNCaPfavqPG5vqnab9AyQcqPqmX8hf/xrniASBAkqNCctbASrFCIYvCJfGfmX
+EQIDAQABoyUwIzAhBgNVHREEGjAYhwTAAAIChxAgAQ24AAAAAAAAAAAAAAABMA0G
+CSqGSIb3DQEBCwUAA4IBAQBf7kmYfRYfnWk1OUfY3N1kaNg9piBBlFr9g+OQn9KU
+zirkN7s0ZQbCGxV1uJQBKS58NyE414Vorau77379emgYDcCBpDIYpkLiNujVrIOr
+ggRFKsFRgxu4/mw0BSgCcV8RPe9SWHZ90Mos7TMCnW/PdxOCD1wD0YMkcs0rwB3l
+0Kzc7jDnfOEvmgw/Ysm7v67ps+05Uq5VskQ6WrpSAw6kPD/QMuuBAX8ATPczIaox
+zAMyncq1IiSIwG93f3EoQQThdQ70C6G9vLcu9TtL6JAsEMFEzR99gt1Wsqvmgl9W
+kStzj1yjIWeo5gIsa4Jgcke1lZviWyrTxHDfyunYE5i5
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-ip-cn-and-alt-names.key b/src/test/ssl/ssl/server-ip-cn-and-alt-names.key
new file mode 100644
index 0000000000..54fe80fc68
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-and-alt-names.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAwmqTdQJfs2Ti9tPitYp227I0HvL/kNSgA6egFr0foRo0Borw
+JNIzdbV0+EnsfiBNTWL5It26gqO7UP3ms8t2vHD5gkXfT+f6ts0lVJEcIOkUD/8w
+s4Ic9Y4uPqb4gN+pUKqcxtmLW1TYk84MBK59Xz4yPPS6N+G/DMMeFHTNkM9EQwn/
++DC3fDsWdGYM2GRWDTJGg1A5tSUcF+seu7i1Vg7XajBfsvgAUAsrAxV+X/sLZh94
+HY+paD6wfaI99mY2OXVc/XW/z1r9WQznor65ZkonNCaPfavqPG5vqnab9AyQcqPq
+mX8hf/xrniASBAkqNCctbASrFCIYvCJfGfmXEQIDAQABAoIBAB6GgVST5NbT9lbu
++d+rN/JSzqA1Yy8oU19/iEFJvJec96I3WnFNl8rZjN4XLUy4YarO6XMyAUDV2Gll
+FD4Sqjf4PRTZR7DSKaleGIhoqFP6hK3mUY091rIves9XhBkoBPunbipCqgDTF5ZN
+edGaXBECQP0VJ8/yX/7u++AWXthnjDis9X0taZfFg/PYbV7SCJ1Hg1O/wEsgXlnC
+7mbL6wkCW0f6700B0x1kKbZqJY95xRqp6Ipq2lIQbJDdGywoj0WzKqNltf9cer+r
+cXl8WjeiMvvvpl4uGhckAbzUifUzxN6A3f1fu/XKtOmabMi9t7J4MRfgOgedgtQB
+0jaZGSkCgYEA+lBLnNY6M48HX2mdtr86+n41gh69v8Z7oNikJFDZkodrvI8uqE0i
+0XwnYPFddt8NbmuUhhuzI2M8RKhGLgdlbKpkSSVafnMfcxRmX2EAtWQgdvX1Iult
+752LWdBgSuw2vlzvy3T/GYnjMrXSCGput4amqojMEbvUGvIdSUMdHGMCgYEAxtU1
+WixKPL6aEnYy1f4bybzcNgGtl8PBRz9xw+P46g+ijOPoaG9O73Tr7An11AO003Ot
+DHhMW+b8yHLyxoKwS2sU2cN/lKB8xNQYZc1D61RNJlzgnHMXnA0lcH0I3M35fqKr
+/71pD1ZP40SSJS+od/KEjW80XzuOdyiXg8q81vsCgYEAnUPLbbsuj+whzrFVlFZr
+IKwgxCK6Rn3WeIUEA4kEWUpZxvsSbk0gPgtJ1l9uwFt9Xc2bX/KRRv93Aw/SH+Mn
+tvEK1uXwCBgePzgm5W/VeSFyQCthm1CbcHtD7Oa9SPVFo65SPjrAd3QpWVfgoMb1
+zrp7hhMyW0XuCgvpmHjhFk8CgYEAxq/thXM2p+bLLWGhwQcRG5G299zLbBl4PUsf
+0uEvLi17gJCKADoiRdSvoAn/9eHSQ26XYRuhKkDzHxcGlOmpY2PYzRa3mXyZ0VIk
+Iy5wDWwLQCeVZ6D22cClRfgb8BF/nFTPzVmn72SPpgoyhChQj7PvUynpyrRH07jj
+VxYziBsCgYAFr37Xbl0VnXVK+XU+vMwUZjcF4jpoCr7SFZqgRbW2GbYSUoMuPXns
+RnJh+Fvi1NUei+E5s1H4P1pVq4p0jFxP4GvH/qvNjnIn/Er3bbqvpox6dWUJXprq
+qTQSDIeoDC/V8cyRoIfqPvTVqY8Rgew6GEkv0bAImdxhoSng7vIseg==
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.crt b/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.crt
new file mode 100644
index 0000000000..23c06da01c
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.crt
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDQzCCAiugAwIBAgIIICIBBBQ2MQEwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UE
+Aww3VGVzdCBDQSBmb3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHNl
+cnZlciBjZXJ0czAeFw0yMjAxMDQyMjM2MzFaFw00OTA1MjIyMjM2MzFaMDQxHjAc
+BgNVBAsMFVBvc3RncmVTUUwgdGVzdCBzdWl0ZTESMBAGA1UEAwwJMTkyLjAuMi4x
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8xddbo/x2TOSIa/br8BN
+o/URdTr9+l2R5YojiZKDuLxiQVkgC30PJ2/CNFKIh2nHhRrzknI6sETVtrxZ+9V2
+qRc1yShVu462u0DHPRMIZnZIOZg3hlNB0cRWbOglUKlttIARNEQUcTUyPOtyo4/v
++u0Ej5NTNcHFbFT01vdD9MjQiCO3jKdAwPIb14jTg4C71EpZ+LuelDo4DzF2/XgG
+WqUTrgD/XnBU/60PU9Iy3G0nVpx21q6ppn9G7a9R+i8FjBcwW1T+cfsBDWhAv+bi
+RmSAkENf8L8TwOlDQUwROkfz3Hz36vuJjdkreQJsiqL0HnrnH5T5G9UzJO86FvZQ
+5wIDAQABo0swSTBHBgNVHREEQDA+gh1kbnMxLmFsdC1uYW1lLnBnLXNzbHRlc3Qu
+dGVzdIIdZG5zMi5hbHQtbmFtZS5wZy1zc2x0ZXN0LnRlc3QwDQYJKoZIhvcNAQEL
+BQADggEBAF+mfaw6iBPzpCgqq830pHRa3Yzm1aezt8SkeRohUYHNv/yCnDSRaqtj
+xbENih3lJMSTBL3g0wtTOHfH8ViC/h+lvYELHzXKic7gkjV7H5XETKGr0ZsjBBT2
+4cZQKbD9e0x0HrENXMYgGpBf747qL6uTOVJdG0s15hwpLq47bY5WUjXathejbpxW
+prmF8F+xaC52N9P/1VnqguQB909F4x1pyOK7D7tjFu+Y8Je7PHKbb6WY5K6xAv6t
+R17CY0749/FotlphquElUR2bs5Zzv5YrjUHPTcbwKvcH5cdNi93/u6NJt2xNAoYf
+aZERhX5TA9DYk4gC8OY0yGaYCIj3Dd4=
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.key b/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.key
new file mode 100644
index 0000000000..0ace41e0a1
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEogIBAAKCAQEA8xddbo/x2TOSIa/br8BNo/URdTr9+l2R5YojiZKDuLxiQVkg
+C30PJ2/CNFKIh2nHhRrzknI6sETVtrxZ+9V2qRc1yShVu462u0DHPRMIZnZIOZg3
+hlNB0cRWbOglUKlttIARNEQUcTUyPOtyo4/v+u0Ej5NTNcHFbFT01vdD9MjQiCO3
+jKdAwPIb14jTg4C71EpZ+LuelDo4DzF2/XgGWqUTrgD/XnBU/60PU9Iy3G0nVpx2
+1q6ppn9G7a9R+i8FjBcwW1T+cfsBDWhAv+biRmSAkENf8L8TwOlDQUwROkfz3Hz3
+6vuJjdkreQJsiqL0HnrnH5T5G9UzJO86FvZQ5wIDAQABAoIBAGv0BFoFMrHyZQLw
+xe7Wx6P4QTh+aiu1QgVdw0pk9nojrr62hbSUZRZuWyBBRsBcCW7i+Sgf8lA1QXNV
+UeC0e228EPa0It6YEi42JkTJHwHhpVFud7n/X0t4lajnryqTE1UFSp6bXTipFxZW
+uSJJ2ZjliRD5rApDcxkY4WJVjKg3aEt7P/DiM8iKGfyE6stq72VjEbJjdViMEcOP
+BNf0TiREZz5Mp7jAVWhpen0ebbLOBVWV4/ONNcL+yqR4mCEDUSFGewrTVX4zHL0A
+hYk198C5F8sFvEDnFkPco9sXMVanmLoI8sbhP4IIz9g4+GU6kFuj7fUKp11Azqv+
+3WQDKYECgYEA/XG4mmG/g8FG44y42mfZpUXWi1pwU4CQIrhkoU5j7EPQrvRboOOE
+Rv95jSwyZu4vCqjyI5FN1jCGTdhmt++R1e//zH6Hqa9Smo+jw7DtAFrCYd1JnCf1
+ToOwsYPHv4P7A8q8kc5vCNIv+AQSlP/wqdVNo3grdf7cGXkMtEY4F9UCgYEA9Yrq
+zWdnNGPATuSBqL6TSjQ37oR+dBD6WnGsiDenQkOzyDPFZ3CT1DjJghjEtxc8EfNf
+Oo8dMMR2q+5FZQo7WuqONEgyzKePiNR8RK2gOYpgdjN9bih1sAhHR10D26cpwlDJ
+bx7D5ZzENLbdZmfEiWwKswnaIhN4yMalgE0mP8sCgYAhzJy12ftUct4lUosEdX0N
+EXc/NlxshmSyfKzO5kllJNYbvvLJTg5B+agYL6C5IWKcpVNFcwdSXT5L+2QXe5eT
+VGJkvysQchUuD6HjYyD4PyJVMtGyRZHtWpqh0dU9sTg0lUD4oPMl1gIXrVNdE5Tg
+0VV9S3VgUxC/ROlw0TyB0QKBgGsVE0NS9hJF8mc1hko2GnwA++d8Rr2NbfElo+2f
+/8SJTA1ibpOm6AFkZpTjAl8qtdrKPVyHb16GP47Jkd/3r1z979hjKCxSYul0aWF2
+KusNKvZBjFEPOgv0AEniCb2wUCjbHI3mZ95qGLM4kKOJW4/m21+rS0MTJNjCsQic
+HLMzAoGAeCsY09d3m8xGeU+DuTPC6GH7Sgy/NBYqS5VaVNjb2jnuZlW2SSW2oiID
+4tXTi4ruKmHC898BfyFxhSMqub+tg3pVqIYADC71rnJLrVyc1SzoWzL7yMT3qFj7
+C7ZYZYmfG9agcZb5NkqKPTfCxkBhWbdgTTgBKVO/xQst8EUgko8=
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-ip-cn-only.crt b/src/test/ssl/ssl/server-ip-cn-only.crt
new file mode 100644
index 0000000000..9bf015cf18
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-only.crt
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC8TCCAdkCCCAhESkRN1IAMA0GCSqGSIb3DQEBCwUAMEIxQDA+BgNVBAMMN1Rl
+c3QgQ0EgZm9yIFBvc3RncmVTUUwgU1NMIHJlZ3Jlc3Npb24gdGVzdCBzZXJ2ZXIg
+Y2VydHMwHhcNMjExMTI5MTkzNzUyWhcNNDkwNDE2MTkzNzUyWjA0MR4wHAYDVQQL
+DBVQb3N0Z3JlU1FMIHRlc3Qgc3VpdGUxEjAQBgNVBAMMCTE5Mi4wLjIuMTCCASIw
+DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANWs1uUL71nHYF9Zj6p+M3MpYDvx
+32iCjVdtH5a2qpSWHXTg0rR8dLX0y92cvOYvMXHRajZT1avpHr8dooPYSVaXpGMK
+NvF/Qi+WFYovRbP2vmd1yv1cgW/FggbwJFWVobizIz4seyA4d0B2j9fqoi2OFBNP
+huW664SjF0u3p21tDy+43i2LNUMAKf6dnRR5Vqenath87LEU41tSLudu6NXgbFMk
+jvfNkl4d0w7YCzeXmklmSI+uaX3PlJJ4NzQO2j8w5BvnKVhNVD0KjgrXZ6nB/8F7
+Pg3XY+d7rJlwRgXemU6resWQDJ7+UaC9u7I4EIP+9lzCR/nNBqUktpHRmHUCAwEA
+ATANBgkqhkiG9w0BAQsFAAOCAQEAos1JncV8Yf4UaKl6h1GdYtcVtzFyJvBEnhRD
+07ldL+TYnfZiX8wK2ssBtM3cg/C78y5bzdUa5XGS83ZKQJFFdhE7PSnrvyNqyIqY
+ZgNBxto3gyvir+EjO1u9BAB0NP3r3gYoHRDZS1xOPPzt4WgjuUgTLM9k82GsqAbO
+UrOTOdRnkIqC5xLpa05EnRyJPRsR1w1PRJC2XXKnHIuFjMb4v7UuPwyCcX1P5ioc
+rQszQcORy/L+k0ezCkyweORg68htjYbBHuwOuiGfok6yKKDMzrTvD3lIslls6eX7
+4sI3XWqzkPmG9Vsxm9Vu9/Ma+PRO76VyCoIwBd+Ufg5vNXhMmw==
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-ip-cn-only.key b/src/test/ssl/ssl/server-ip-cn-only.key
new file mode 100644
index 0000000000..1966530e72
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-only.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEA1azW5QvvWcdgX1mPqn4zcylgO/HfaIKNV20flraqlJYddODS
+tHx0tfTL3Zy85i8xcdFqNlPVq+kevx2ig9hJVpekYwo28X9CL5YVii9Fs/a+Z3XK
+/VyBb8WCBvAkVZWhuLMjPix7IDh3QHaP1+qiLY4UE0+G5brrhKMXS7enbW0PL7je
+LYs1QwAp/p2dFHlWp6dq2HzssRTjW1Iu527o1eBsUySO982SXh3TDtgLN5eaSWZI
+j65pfc+Ukng3NA7aPzDkG+cpWE1UPQqOCtdnqcH/wXs+Dddj53usmXBGBd6ZTqt6
+xZAMnv5RoL27sjgQg/72XMJH+c0GpSS2kdGYdQIDAQABAoIBAQDNXviU4WnF8rmQ
+K7bH+dBdqbETLKC8BG7xTrMD2sINWlMpmUUrsEtE7+paMGHnJAj0CoF5gg5m0wN4
+UXV4H5QtpEad4p14dAYbUreVP2ZRWKEdM7xM1HKcCUu2e22QzObJbXQ8N+iHyX3k
++Y+7yYrjGiH1hYR0nbnsnAyx++zyYBSQeqzpdQwf/BLY5xZmyYWNfqbckiMpEqMs
+EmZmGXnCjIipzEC0LQHoSW9PNa92Z9bvuxOKYl8iHYDDXjvMRFoZBSiMXpzHQocb
+QlQ5F4ayfW2OrOhpNbY7niYM9GN3Bk9TgMP+0BkJE6uuktLYW35LY1M78CCPWcWb
+npJNK3QBAoGBAOxkGrhAHAysSmtirIyMdvySb76wb/Ukfi+AULKz20FI5j4/GXm9
+qCb2GeT+FFSUHeSC8f0EFnosRYkdBGruqeZioI+5rUkboYFJPspAHAuvg9kgtfF+
+kvphD4O4P/foYsEZRx66FHozDbhrrR5UXc7KzqRIASc/D3FOx2UFJLb1AoGBAOdm
+WcaMvYygl9ZW+ThWAR1xG1X70AGKwrlrpF2hBkWYxSurxSMXnD0DUzC9Nb4EyCaM
+c2uSqEZOKdW+XfXtK2DnqXKfb3YCVEoGN4gVfyuW/vxii/+ZxLo3md/b3vrkZEVp
+pfkXy/HoZ71YN7bNpcDpOnhml6vvuCRCYFnI1WuBAoGAC0shB6pwbJ6Sk5zMN47C
+ZICufAK75o9OxAAyWsdC81SDQ3gKRImuDeZ2CD2nRP8qim9DFl5qoH2a+Nj9DArI
+7SvLFfK9958tURrpuAnmDRzehLIOXzI33WRjtFxKGhLtHOKTRkGHlur3fdcPF0La
+lHWV971E6NYXa8diuU3Mmj0CgYBYd+ka3/QYL83dRKNDxp3mg7fPx9ZewI5yFZVh
+to6PTTkU2Tclk4FIUl0b5TsGyw06r7fxCMENIBUegwmpXGOZSPifuhUDKSDQrE/O
+12knYTNbitG7hy6Pg3JxA77cbTVo1FuAQHjYo+IFohSq7zTP7FtObOrP8XaVZksw
+CHiQAQKBgBW4EiA9AAnZ1LOpifAvM7bs0NHg95qTwtAL52WKom2ga2H+lMhxeu6Y
+hUSytC/f9kALVcYloZhkLYpO07x1gXmy7f4parMjA4Ex+4vfu3kPd8GiNGZ+AUJD
+nnJ1OINY9ziXJZfju7FpVWpkiuPzWCh6y/o3gZ/veq5mIUxuDMVa
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/sslfiles.mk b/src/test/ssl/sslfiles.mk
index 7ed3a30f5c..f9be5ffdba 100644
--- a/src/test/ssl/sslfiles.mk
+++ b/src/test/ssl/sslfiles.mk
@@ -22,9 +22,14 @@
 # key/certificate pair will be generated for you, signed by the appropriate CA.
 #
 SERVERS := server-cn-and-alt-names \
+	server-cn-and-ip-alt-names \
 	server-cn-only \
+	server-ip-cn-only \
+	server-ip-cn-and-alt-names \
+	server-ip-cn-and-dns-alt-names \
 	server-single-alt-name \
 	server-multiple-alt-names \
+	server-ip-alt-names \
 	server-no-names \
 	server-revoked
 CLIENTS := client client-dn client-revoked client_ext
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index b1fb15ce80..e5a8080dc3 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -21,7 +21,7 @@ if ($ENV{with_ssl} ne 'openssl')
 }
 else
 {
-	plan tests => 110;
+	plan tests => 135;
 }
 
 #### Some configuration
@@ -253,6 +253,23 @@ $node->connect_fails(
 	  qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/
 );
 
+# Test with an IP address in the Common Name. This is a strange corner case that
+# nevertheless is supported, as long as the address string matches exactly.
+switch_server_cert($node, 'server-ip-cn-only');
+
+$common_connstr =
+  "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+
+$node->connect_ok("$common_connstr host=192.0.2.1",
+	"IP address in the Common Name");
+
+$node->connect_fails(
+	"$common_connstr host=192.000.002.001",
+	"mismatch between host name and server certificate IP address",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.1" does not match host name "192.000.002.001"\E/
+);
+
 # Test Subject Alternative Names.
 switch_server_cert($node, 'server-multiple-alt-names');
 
@@ -305,7 +322,54 @@ $node->connect_fails(
 	  qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/
 );
 
-# Test server certificate with a CN and SANs. Per RFCs 2818 and 6125, the CN
+# Test certificate with IP addresses in the SANs.
+switch_server_cert($node, 'server-ip-alt-names');
+
+$node->connect_ok(
+	"$common_connstr host=192.0.2.1",
+	"host matching an IPv4 address (Subject Alternative Name 1)");
+
+$node->connect_ok(
+	"$common_connstr host=192.000.002.001",
+	"host matching an IPv4 address in alternate form (Subject Alternative Name 1)");
+
+$node->connect_fails(
+	"$common_connstr host=192.0.2.2",
+	"host not matching an IPv4 address (Subject Alternative Name 1)",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.1" (and 1 other name) does not match host name "192.0.2.2"\E/
+);
+
+$node->connect_fails(
+	"$common_connstr host=192.0.2.1/32",
+	"IPv4 host with CIDR mask does not match",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.1" (and 1 other name) does not match host name "192.0.2.1\/32"\E/
+);
+
+$node->connect_ok(
+	"$common_connstr host=2001:DB8::1",
+	"host matching an IPv6 address (Subject Alternative Name 2)");
+
+$node->connect_ok(
+	"$common_connstr host=2001:db8:0:0:0:0:0:1",
+	"host matching an IPv6 address in alternate form (Subject Alternative Name 2)");
+
+$node->connect_fails(
+	"$common_connstr host=::1",
+	"host not matching an IPv6 address (Subject Alternative Name 2)",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.1" (and 1 other name) does not match host name "::1"\E/
+);
+
+$node->connect_fails(
+	"$common_connstr host=2001:DB8::1/128",
+	"IPv6 host with CIDR mask does not match",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.1" (and 1 other name) does not match host name "2001:DB8::1\/128"\E/
+);
+
+# Test server certificate with a CN and DNS SANs. Per RFCs 2818 and 6125, the CN
 # should be ignored when the certificate has both.
 switch_server_cert($node, 'server-cn-and-alt-names');
 
@@ -323,6 +387,39 @@ $node->connect_fails(
 	  qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/
 );
 
+# But we will fall back to check the CN if the SANs contain only IP addresses.
+switch_server_cert($node, 'server-cn-and-ip-alt-names');
+
+$node->connect_ok("$common_connstr host=common-name.pg-ssltest.test",
+	"certificate with both a CN and IP SANs matches CN");
+$node->connect_ok("$common_connstr host=192.0.2.1",
+	"certificate with both a CN and IP SANs matches SAN 1");
+$node->connect_ok("$common_connstr host=2001:db8::1",
+	"certificate with both a CN and IP SANs matches SAN 2");
+
+# And now the same tests, but with IP addresses and DNS names swapped.
+switch_server_cert($node, 'server-ip-cn-and-alt-names');
+
+$node->connect_ok("$common_connstr host=192.0.2.2",
+	"certificate with both an IP CN and IP SANs 1");
+$node->connect_ok("$common_connstr host=2001:db8::1",
+	"certificate with both an IP CN and IP SANs 2");
+$node->connect_fails(
+	"$common_connstr host=192.0.2.1",
+	"certificate with both an IP CN and IP SANs ignores CN",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.2" (and 1 other name) does not match host name "192.0.2.1"\E/
+);
+
+switch_server_cert($node, 'server-ip-cn-and-dns-alt-names');
+
+$node->connect_ok("$common_connstr host=192.0.2.1",
+	"certificate with both an IP CN and DNS SANs matches CN");
+$node->connect_ok("$common_connstr host=dns1.alt-name.pg-ssltest.test",
+	"certificate with both an IP CN and DNS SANs matches SAN 1");
+$node->connect_ok("$common_connstr host=dns2.alt-name.pg-ssltest.test",
+	"certificate with both an IP CN and DNS SANs matches SAN 2");
+
 # Finally, test a server certificate that has no CN or SANs. Of course, that's
 # not a very sensible certificate, but libpq should handle it gracefully.
 switch_server_cert($node, 'server-no-names');
-- 
2.25.1

v4-0003-squash-libpq-allow-IP-address-SANs-in-server-cert.patchtext/x-patch; name=v4-0003-squash-libpq-allow-IP-address-SANs-in-server-cert.patchDownload
From b39d6df24864821521aff6dba38618cb232112bf Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Wed, 5 Jan 2022 15:47:03 -0800
Subject: [PATCH v4 3/3] squash! libpq: allow IP address SANs in server certs

Per review, provide a pg_inet_pton() interface for IPv6 and refactor the
internals accordingly. IPv4 is not yet implemented. The call sites that
just want standard addresses without a CIDR mask have been updated to
use the new function.

Internally, the IPv6 parser now takes an allow_cidr boolean. I've
plumbed that all the way down to getbits() in order to minimize the
amount of code touched, at the expense of an unnecessary function call
in the failing case.
---
 src/include/port.h                       |  1 +
 src/interfaces/libpq/fe-secure-common.c  |  7 +--
 src/interfaces/libpq/fe-secure-openssl.c |  2 +-
 src/port/inet_net_pton.c                 | 66 ++++++++++++++++++------
 4 files changed, 52 insertions(+), 24 deletions(-)

diff --git a/src/include/port.h b/src/include/port.h
index 2852e5b58b..fd046f4c24 100644
--- a/src/include/port.h
+++ b/src/include/port.h
@@ -517,6 +517,7 @@ extern char *pg_inet_net_ntop(int af, const void *src, int bits,
 
 /* port/inet_net_pton.c */
 extern int	pg_inet_net_pton(int af, const char *src, void *dst, size_t size);
+extern int	pg_inet_pton(int af, const char *src, void *dst);
 
 /* port/pg_strong_random.c */
 extern void pg_strong_random_init(void);
diff --git a/src/interfaces/libpq/fe-secure-common.c b/src/interfaces/libpq/fe-secure-common.c
index e8e8878d3d..cfdde58e67 100644
--- a/src/interfaces/libpq/fe-secure-common.c
+++ b/src/interfaces/libpq/fe-secure-common.c
@@ -209,12 +209,7 @@ pq_verify_peer_name_matches_certificate_ip(PGconn *conn,
 
 		family = PGSQL_AF_INET6;
 
-		/*
-		 * pg_inet_net_pton() will accept CIDR masks, which we don't want to
-		 * match, so skip the comparison if the host string contains a slash.
-		 */
-		if (!strchr(host, '/')
-			&& pg_inet_net_pton(PGSQL_AF_INET6, host, addr, -1) == 128)
+		if (pg_inet_pton(PGSQL_AF_INET6, host, addr) == 1)
 		{
 			if (memcmp(ipdata, addr, iplen) == 0)
 				match = 1;
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index 93e80c79d4..7173ea0d73 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -555,7 +555,7 @@ is_ip_address(const char *host)
 	unsigned char	dummy6[16];
 
 	return inet_aton(host, &dummy4)
-		|| (pg_inet_net_pton(PGSQL_AF_INET6, host, dummy6, -1) == 128);
+		|| (pg_inet_pton(PGSQL_AF_INET6, host, dummy6) == 1);
 }
 
 /*
diff --git a/src/port/inet_net_pton.c b/src/port/inet_net_pton.c
index 5bae811c6f..ae0d1fd2e3 100644
--- a/src/port/inet_net_pton.c
+++ b/src/port/inet_net_pton.c
@@ -42,8 +42,8 @@ static const char rcsid[] = "Id: inet_net_pton.c,v 1.4.2.3 2004/03/17 00:40:11 m
 
 static int	inet_net_pton_ipv4(const char *src, u_char *dst);
 static int	inet_cidr_pton_ipv4(const char *src, u_char *dst, size_t size);
-static int	inet_net_pton_ipv6(const char *src, u_char *dst);
-static int	inet_cidr_pton_ipv6(const char *src, u_char *dst, size_t size);
+static int	inet_pton_ipv6_internal(const char *src, u_char *dst, size_t size,
+									bool allow_cidr);
 
 
 /*
@@ -74,9 +74,9 @@ pg_inet_net_pton(int af, const char *src, void *dst, size_t size)
 				inet_net_pton_ipv4(src, dst) :
 				inet_cidr_pton_ipv4(src, dst, size);
 		case PGSQL_AF_INET6:
-			return size == -1 ?
-				inet_net_pton_ipv6(src, dst) :
-				inet_cidr_pton_ipv6(src, dst, size);
+			return inet_pton_ipv6_internal(src, dst,
+										   (size == -1) ? 16 : size,
+										   true /* allow CIDR */);
 		default:
 			errno = EAFNOSUPPORT;
 			return -1;
@@ -352,13 +352,22 @@ emsgsize:
 }
 
 static int
-getbits(const char *src, int *bitsp)
+getbits(const char *src, int *bitsp, bool allow_cidr)
 {
 	static const char digits[] = "0123456789";
 	int			n;
 	int			val;
 	char		ch;
 
+	if (!allow_cidr)
+	{
+		/*
+		 * If we got here, the top-level call is to pg_inet_pton() and the
+		 * caller supplied a CIDR mask, which isn't allowed.
+		 */
+		return 0;
+	}
+
 	val = 0;
 	n = 0;
 	while ((ch = *src++) != '\0')
@@ -385,7 +394,7 @@ getbits(const char *src, int *bitsp)
 }
 
 static int
-getv4(const char *src, u_char *dst, int *bitsp)
+getv4(const char *src, u_char *dst, int *bitsp, bool allow_cidr)
 {
 	static const char digits[] = "0123456789";
 	u_char	   *odst = dst;
@@ -416,7 +425,7 @@ getv4(const char *src, u_char *dst, int *bitsp)
 				return 0;
 			*dst++ = val;
 			if (ch == '/')
-				return getbits(src, bitsp);
+				return getbits(src, bitsp, allow_cidr);
 			val = 0;
 			n = 0;
 			continue;
@@ -431,18 +440,12 @@ getv4(const char *src, u_char *dst, int *bitsp)
 	return 1;
 }
 
-static int
-inet_net_pton_ipv6(const char *src, u_char *dst)
-{
-	return inet_cidr_pton_ipv6(src, dst, 16);
-}
-
 #define NS_IN6ADDRSZ 16
 #define NS_INT16SZ 2
 #define NS_INADDRSZ 4
 
 static int
-inet_cidr_pton_ipv6(const char *src, u_char *dst, size_t size)
+inet_pton_ipv6_internal(const char *src, u_char *dst, size_t size, bool allow_cidr)
 {
 	static const char xdigits_l[] = "0123456789abcdef",
 				xdigits_u[] = "0123456789ABCDEF";
@@ -510,13 +513,13 @@ inet_cidr_pton_ipv6(const char *src, u_char *dst, size_t size)
 			continue;
 		}
 		if (ch == '.' && ((tp + NS_INADDRSZ) <= endp) &&
-			getv4(curtok, tp, &bits) > 0)
+			getv4(curtok, tp, &bits, allow_cidr) > 0)
 		{
 			tp += NS_INADDRSZ;
 			saw_xdigit = 0;
 			break;				/* '\0' was seen by inet_pton4(). */
 		}
-		if (ch == '/' && getbits(src, &bits) > 0)
+		if (ch == '/' && getbits(src, &bits, allow_cidr) > 0)
 			break;
 		goto enoent;
 	}
@@ -530,6 +533,9 @@ inet_cidr_pton_ipv6(const char *src, u_char *dst, size_t size)
 	if (bits == -1)
 		bits = 128;
 
+	/* Without a CIDR mask, the above should guarantee a complete address. */
+	Assert(allow_cidr || bits == 128);
+
 	endp = tmp + 16;
 
 	if (colonp != NULL)
@@ -568,3 +574,29 @@ emsgsize:
 	errno = EMSGSIZE;
 	return -1;
 }
+
+/*
+ * Converts a network address in presentation format to network format. The main
+ * difference between this and pg_inet_net_pton() above is that CIDR notation is
+ * not allowed.
+ *
+ * The memory pointed to by dst depends on the address family:
+ *
+ *     PGSQL_AF_INET:  not implemented yet (returns -1 with EAFNOSUPPORT)
+ *     PGSQL_AF_INET6: *dst must be a u_char[16]
+ *
+ * Over and above the standard inet_pton() functionality, we also set errno on
+ * parse failure, with the same meanings as with pg_inet_net_pton().
+ */
+int
+pg_inet_pton(int af, const char *src, void *dst)
+{
+	if (af != PGSQL_AF_INET6)
+	{
+		/* Currently only IPv6 is implemented. */
+		errno = EAFNOSUPPORT;
+		return -1;
+	}
+
+	return (inet_pton_ipv6_internal(src, dst, 16, false /* no CIDR */) == 128);
+}
-- 
2.25.1

#17Kyotaro Horiguchi
horikyota.ntt@gmail.com
In reply to: Jacob Champion (#16)
Re: [PATCH] Accept IP addresses in server certificate SANs

At Fri, 4 Feb 2022 17:06:53 +0000, Jacob Champion <pchampion@vmware.com> wrote in

That works a lot better than what I had in my head. Done that way in
v4. Thanks!

Thanks!

0002:

+#define PGSQL_AF_INET	(AF_INET + 0)
+#define PGSQL_AF_INET6	(AF_INET + 1)
..
-#define PGSQL_AF_INET	(AF_INET + 0)
-#define PGSQL_AF_INET6	(AF_INET + 1)

I feel this should be a part of 0001. (But the patches will be
finally merged so maybe no need to bother moving it).

* The use of inet_aton() instead of inet_pton() is deliberate; the
* latter cannot handle alternate IPv4 notations ("numbers-and-dots").

I think we should be consistent in handling IP addresses. We have
both inet_pton and inet_aton to parse IPv4 addresses.

We use inet_pton in the inet type (network_in).
We use inet_aton in server addresses.

# Hmm. I'm surprised to see listen_addresses accepts "0x7f.1".
# I think we should accept the same by network_in but it is another
# issue.

So, inet_aton there seems to be the right choice but the comment
doesn't describe the reason for that behavior. I think we should add
an explanation about the reason for the behavior, maybe something like
this:

We accept alternative IPv4 address notations that are accepted by
inet_aton but not by inet_pton as server address.

+	 * GEN_IPADD is an OCTET STRING containing an IP address in network byte
+	 * order.
+	/* OK to cast from unsigned to plain char, since it's all ASCII. */
+	return pq_verify_peer_name_matches_certificate_ip(conn, (const char *) addrdata, len, store_name);

Aren't the two comments contradicting each other? The retruned general
name looks like an octet array, which is not a subset of ASCII
string. So pq_verify_peer_name_matches_certificate_ip should receive
addrdata as "const unsigned char *", without casting.

+ if (name->type == host_type)
+ check_cn = false;

Don't we want a concise coment for this?

-	if (*names_examined == 0)
+	if ((rc == 0) && check_cn)

To me, it seems a bit hard to understand. We can set false to
check_cn in the rc != 0 path in the loop on i, like this:

if (rc != 0)
+			{
+				/*
+				 * don't fall back to CN when we have a match or have an error
+				 */
+				check_cn = false;
break;
+			}

...

-	if ((rc == 0) && check_cn)
+	if (check_cn)

The following existing code (CN fallback)

rc = openssl_verify_peer_name_matches_certificate_name(conn,
X509_NAME_ENTRY_get_data(X509_NAME_get_entry(subject_name, cn_index)),
first_name);

is expecting that first_name has not been set when it is visited.
However, with this patch, first_name can be set when the cert has any
SAN of unmatching type (DNS/IPADD) and the already-set name leaks. We
need to avoid that memory leak since the path can be visited multiple
times from the user-program of libpq. I came up with two directions.

1. Completely ignore type-unmatching entries. first_name is not set by
such entries. Such unmatching entreis doesn't increase
*names_examined.

2. Avoid overwriting first_name there.

I like 1, but since we don't make distinction between DNS and IPADDR
in the error message emited by the caller, we would take 2?

regards.

--
Kyotaro Horiguchi
NTT Open Source Software Center

#18Jacob Champion
pchampion@vmware.com
In reply to: Kyotaro Horiguchi (#17)
4 attachment(s)
Re: [PATCH] Accept IP addresses in server certificate SANs

On Mon, 2022-02-07 at 17:29 +0900, Kyotaro Horiguchi wrote:

At Fri, 4 Feb 2022 17:06:53 +0000, Jacob Champion <pchampion@vmware.com> wrote in

That works a lot better than what I had in my head. Done that way in
v4. Thanks!

Thanks!

0002:

+#define PGSQL_AF_INET  (AF_INET + 0)
+#define PGSQL_AF_INET6 (AF_INET + 1)
..
-#define PGSQL_AF_INET  (AF_INET + 0)
-#define PGSQL_AF_INET6 (AF_INET + 1)

I feel this should be a part of 0001. (But the patches will be
finally merged so maybe no need to bother moving it).

Okay. I can move it easily if you feel like it would help review, but
for now I've kept it in 0002.

* The use of inet_aton() instead of inet_pton() is deliberate; the
* latter cannot handle alternate IPv4 notations ("numbers-and-dots").

I think we should be consistent in handling IP addresses. We have
both inet_pton and inet_aton to parse IPv4 addresses.

We use inet_pton in the inet type (network_in).
We use inet_aton in server addresses.

# Hmm. I'm surprised to see listen_addresses accepts "0x7f.1".
# I think we should accept the same by network_in but it is another
# issue.

Yeah, that's an interesting inconsistency.

So, inet_aton there seems to be the right choice but the comment
doesn't describe the reason for that behavior. I think we should add
an explanation about the reason for the behavior, maybe something like
this:

We accept alternative IPv4 address notations that are accepted by
inet_aton but not by inet_pton as server address.

I've pulled this wording into the comment in v5, attached.

+        * GEN_IPADD is an OCTET STRING containing an IP address in network byte
+        * order.
+       /* OK to cast from unsigned to plain char, since it's all ASCII. */
+       return pq_verify_peer_name_matches_certificate_ip(conn, (const char *) addrdata, len, store_name);

Aren't the two comments contradicting each other? The retruned general
name looks like an octet array, which is not a subset of ASCII
string. So pq_verify_peer_name_matches_certificate_ip should receive
addrdata as "const unsigned char *", without casting.

Bad copy-paste on my part; thanks for the catch. Fixed.

+                       if (name->type == host_type)
+                               check_cn = false;

Don't we want a concise coment for this?

Added one; see what you think.

-       if (*names_examined == 0)
+       if ((rc == 0) && check_cn)

To me, it seems a bit hard to understand. We can set false to
check_cn in the rc != 0 path in the loop on i, like this:

if (rc != 0)
+                     {
+                             /*
+                              * don't fall back to CN when we have a match or have an error
+                              */
+                             check_cn = false;
break;
+                     }

...

-     if ((rc == 0) && check_cn)
+     if (check_cn)

If I understand right, that's not quite equivalent (and the new tests
fail if I implement it that way). We have to disable fallback if the
SAN exists, whether it matches or not.

The following existing code (CN fallback)

rc = openssl_verify_peer_name_matches_certificate_name(conn,
X509_NAME_ENTRY_get_data(X509_NAME_get_entry(subject_name, cn_index)),
first_name);

is expecting that first_name has not been set when it is visited.
However, with this patch, first_name can be set when the cert has any
SAN of unmatching type (DNS/IPADD) and the already-set name leaks. We
need to avoid that memory leak since the path can be visited multiple
times from the user-program of libpq. I came up with two directions.

1. Completely ignore type-unmatching entries. first_name is not set by
such entries. Such unmatching entreis doesn't increase
*names_examined.

2. Avoid overwriting first_name there.

I like 1, but since we don't make distinction between DNS and IPADDR
in the error message emited by the caller, we would take 2?

Great catch, thanks! I implemented option 2 to start. Option 1 might
make things difficult to debug if you're connecting to a server by IP
address but its certificate only has DNS names.

Thanks!
--Jacob

Attachments:

since-v4.diff.txttext/plain; name=since-v4.diff.txtDownload
commit 8c427e3289a28cb683eff5d05b2e8770c3c07662
Author: Jacob Champion <pchampion@vmware.com>
Date:   Tue Feb 8 16:26:27 2022 -0800

    squash! libpq: allow IP address SANs in server certs

diff --git a/src/interfaces/libpq/fe-secure-common.c b/src/interfaces/libpq/fe-secure-common.c
index cfdde58e67..4d78715756 100644
--- a/src/interfaces/libpq/fe-secure-common.c
+++ b/src/interfaces/libpq/fe-secure-common.c
@@ -160,7 +160,8 @@ pq_verify_peer_name_matches_certificate_name(PGconn *conn,
  */
 int
 pq_verify_peer_name_matches_certificate_ip(PGconn *conn,
-										   const char *ipdata, size_t iplen,
+										   const unsigned char *ipdata,
+										   size_t iplen,
 										   char **store_name)
 {
 	char	   *addrstr;
@@ -193,8 +194,9 @@ pq_verify_peer_name_matches_certificate_ip(PGconn *conn,
 		family = PGSQL_AF_INET;
 
 		/*
-		 * The use of inet_aton() instead of inet_pton() is deliberate; the
-		 * latter cannot handle alternate IPv4 notations ("numbers-and-dots").
+		 * 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))
 		{
diff --git a/src/interfaces/libpq/fe-secure-common.h b/src/interfaces/libpq/fe-secure-common.h
index 4316c3d1b7..20ff9ba5db 100644
--- a/src/interfaces/libpq/fe-secure-common.h
+++ b/src/interfaces/libpq/fe-secure-common.h
@@ -23,7 +23,8 @@ extern int	pq_verify_peer_name_matches_certificate_name(PGconn *conn,
 														 const char *namedata, size_t namelen,
 														 char **store_name);
 extern int	pq_verify_peer_name_matches_certificate_ip(PGconn *conn,
-													   const char *addrdata, size_t addrlen,
+													   const unsigned char *addrdata,
+													   size_t addrlen,
 													   char **store_name);
 extern bool pq_verify_peer_name_matches_certificate(PGconn *conn);
 
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index 7173ea0d73..00e297db17 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -544,8 +544,7 @@ openssl_verify_peer_name_matches_certificate_ip(PGconn *conn,
 #endif
 	len = ASN1_STRING_length(addr_entry);
 
-	/* OK to cast from unsigned to plain char, since it's all ASCII. */
-	return pq_verify_peer_name_matches_certificate_ip(conn, (const char *) addrdata, len, store_name);
+	return pq_verify_peer_name_matches_certificate_ip(conn, addrdata, len, store_name);
 }
 
 static bool
@@ -619,7 +618,13 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 			char	   *alt_name = NULL;
 
 			if (name->type == host_type)
+			{
+				/*
+				 * This SAN is of the same type (IP or DNS) as our host name, so
+				 * don't allow a fallback check of the CN.
+				 */
 				check_cn = false;
+			}
 
 			if (name->type == GEN_DNS)
 			{
@@ -671,10 +676,20 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 												  NID_commonName, -1);
 			if (cn_index >= 0)
 			{
+				char   *common_name = NULL;
+
 				(*names_examined)++;
 				rc = openssl_verify_peer_name_matches_certificate_name(conn,
 																	   X509_NAME_ENTRY_get_data(X509_NAME_get_entry(subject_name, cn_index)),
-																	   first_name);
+																	   &common_name);
+
+				if (common_name)
+				{
+					if (!*first_name)
+						*first_name = common_name;
+					else
+						free(common_name);
+				}
 			}
 		}
 	}
v5-0001-Move-inet_net_pton-to-src-port.patchtext/x-patch; name=v5-0001-Move-inet_net_pton-to-src-port.patchDownload
From 476017589454fac0bef2eaf5f9098b2e363429f7 Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Wed, 24 Nov 2021 14:46:11 -0800
Subject: [PATCH v5 1/3] Move inet_net_pton() to src/port

This will be helpful for IP address verification in libpq.
---
 src/backend/utils/adt/Makefile                  |  1 -
 src/include/port.h                              |  3 +++
 src/include/utils/builtins.h                    |  4 ----
 src/port/Makefile                               |  1 +
 src/{backend/utils/adt => port}/inet_net_pton.c | 16 +++++++++++++++-
 src/tools/msvc/Mkvcbuild.pm                     |  2 +-
 6 files changed, 20 insertions(+), 7 deletions(-)
 rename src/{backend/utils/adt => port}/inet_net_pton.c (96%)

diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile
index 41b486bcef..d173d52157 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -43,7 +43,6 @@ OBJS = \
 	geo_selfuncs.o \
 	geo_spgist.o \
 	inet_cidr_ntop.o \
-	inet_net_pton.o \
 	int.o \
 	int8.o \
 	json.o \
diff --git a/src/include/port.h b/src/include/port.h
index 3d103a2b31..2852e5b58b 100644
--- a/src/include/port.h
+++ b/src/include/port.h
@@ -515,6 +515,9 @@ extern int	pg_codepage_to_encoding(UINT cp);
 extern char *pg_inet_net_ntop(int af, const void *src, int bits,
 							  char *dst, size_t size);
 
+/* port/inet_net_pton.c */
+extern int	pg_inet_net_pton(int af, const char *src, void *dst, size_t size);
+
 /* port/pg_strong_random.c */
 extern void pg_strong_random_init(void);
 extern bool pg_strong_random(void *buf, size_t len);
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index d8f05a7df3..22ebbfda17 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -93,10 +93,6 @@ extern int	xidLogicalComparator(const void *arg1, const void *arg2);
 extern char *pg_inet_cidr_ntop(int af, const void *src, int bits,
 							   char *dst, size_t size);
 
-/* inet_net_pton.c */
-extern int	pg_inet_net_pton(int af, const char *src,
-							 void *dst, size_t size);
-
 /* network.c */
 extern double convert_network_to_scalar(Datum value, Oid typid, bool *failure);
 extern Datum network_scan_first(Datum in);
diff --git a/src/port/Makefile b/src/port/Makefile
index bfe1feb0d4..c3ae7b3d5c 100644
--- a/src/port/Makefile
+++ b/src/port/Makefile
@@ -43,6 +43,7 @@ OBJS = \
 	bsearch_arg.o \
 	chklocale.o \
 	inet_net_ntop.o \
+	inet_net_pton.o \
 	noblock.o \
 	path.o \
 	pg_bitutils.o \
diff --git a/src/backend/utils/adt/inet_net_pton.c b/src/port/inet_net_pton.c
similarity index 96%
rename from src/backend/utils/adt/inet_net_pton.c
rename to src/port/inet_net_pton.c
index d3221a1313..bae50ba67e 100644
--- a/src/backend/utils/adt/inet_net_pton.c
+++ b/src/port/inet_net_pton.c
@@ -14,14 +14,18 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
  * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  *
- *	  src/backend/utils/adt/inet_net_pton.c
+ *	  src/port/inet_net_pton.c
  */
 
 #if defined(LIBC_SCCS) && !defined(lint)
 static const char rcsid[] = "Id: inet_net_pton.c,v 1.4.2.3 2004/03/17 00:40:11 marka Exp $";
 #endif
 
+#ifndef FRONTEND
 #include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
 
 #include <sys/socket.h>
 #include <netinet/in.h>
@@ -29,9 +33,19 @@ static const char rcsid[] = "Id: inet_net_pton.c,v 1.4.2.3 2004/03/17 00:40:11 m
 #include <assert.h>
 #include <ctype.h>
 
+#ifndef FRONTEND
 #include "utils/builtins.h" /* pgrminclude ignore */	/* needed on some
 														 * platforms */
 #include "utils/inet.h"
+#else
+/*
+ * In a frontend build, we can't include inet.h, but we still need to have
+ * sensible definitions of these two constants.  Note that pg_inet_net_ntop()
+ * assumes that PGSQL_AF_INET is equal to AF_INET.
+ */
+#define PGSQL_AF_INET	(AF_INET + 0)
+#define PGSQL_AF_INET6	(AF_INET + 1)
+#endif
 
 
 static int	inet_net_pton_ipv4(const char *src, u_char *dst);
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index a310bcb28c..b566a4aed7 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -100,7 +100,7 @@ sub mkvcbuild
 
 	our @pgportfiles = qw(
 	  chklocale.c explicit_bzero.c fls.c getpeereid.c getrusage.c inet_aton.c
-	  getaddrinfo.c gettimeofday.c inet_net_ntop.c kill.c open.c
+	  getaddrinfo.c gettimeofday.c inet_net_ntop.c inet_net_pton.c kill.c open.c
 	  snprintf.c strlcat.c strlcpy.c dirmod.c noblock.c path.c
 	  dirent.c dlopen.c getopt.c getopt_long.c link.c
 	  pread.c preadv.c pwrite.c pwritev.c pg_bitutils.c
-- 
2.25.1

v5-0002-libpq-allow-IP-address-SANs-in-server-certs.patchtext/x-patch; name=v5-0002-libpq-allow-IP-address-SANs-in-server-certs.patchDownload
From 66f543e350df166cc8acced1567e805842572f8e Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Thu, 18 Nov 2021 15:36:18 -0800
Subject: [PATCH v5 2/3] libpq: allow IP address SANs in server certs

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.

- Unlike NSS, we don't map IPv4 to IPv6 addresses, or vice-versa.

- Move PGSQL_AF_INET* to inet-common.h to reduce copy-paste.
---
 src/include/common/inet-common.h              |  35 +++++
 src/include/utils/inet.h                      |  13 +-
 src/interfaces/libpq/fe-secure-common.c       | 104 ++++++++++++++
 src/interfaces/libpq/fe-secure-common.h       |   5 +
 src/interfaces/libpq/fe-secure-openssl.c      | 131 ++++++++++++++++--
 src/port/inet_net_ntop.c                      |  12 +-
 src/port/inet_net_pton.c                      |  10 +-
 .../conf/server-cn-and-ip-alt-names.config    |  24 ++++
 src/test/ssl/conf/server-ip-alt-names.config  |  19 +++
 .../conf/server-ip-cn-and-alt-names.config    |  21 +++
 .../server-ip-cn-and-dns-alt-names.config     |  21 +++
 src/test/ssl/conf/server-ip-cn-only.config    |  12 ++
 .../ssl/ssl/server-cn-and-ip-alt-names.crt    |  20 +++
 .../ssl/ssl/server-cn-and-ip-alt-names.key    |  27 ++++
 src/test/ssl/ssl/server-ip-alt-names.crt      |  19 +++
 src/test/ssl/ssl/server-ip-alt-names.key      |  27 ++++
 .../ssl/ssl/server-ip-cn-and-alt-names.crt    |  19 +++
 .../ssl/ssl/server-ip-cn-and-alt-names.key    |  27 ++++
 .../ssl/server-ip-cn-and-dns-alt-names.crt    |  20 +++
 .../ssl/server-ip-cn-and-dns-alt-names.key    |  27 ++++
 src/test/ssl/ssl/server-ip-cn-only.crt        |  18 +++
 src/test/ssl/ssl/server-ip-cn-only.key        |  27 ++++
 src/test/ssl/sslfiles.mk                      |   5 +
 src/test/ssl/t/001_ssltests.pl                | 101 +++++++++++++-
 24 files changed, 697 insertions(+), 47 deletions(-)
 create mode 100644 src/include/common/inet-common.h
 create mode 100644 src/test/ssl/conf/server-cn-and-ip-alt-names.config
 create mode 100644 src/test/ssl/conf/server-ip-alt-names.config
 create mode 100644 src/test/ssl/conf/server-ip-cn-and-alt-names.config
 create mode 100644 src/test/ssl/conf/server-ip-cn-and-dns-alt-names.config
 create mode 100644 src/test/ssl/conf/server-ip-cn-only.config
 create mode 100644 src/test/ssl/ssl/server-cn-and-ip-alt-names.crt
 create mode 100644 src/test/ssl/ssl/server-cn-and-ip-alt-names.key
 create mode 100644 src/test/ssl/ssl/server-ip-alt-names.crt
 create mode 100644 src/test/ssl/ssl/server-ip-alt-names.key
 create mode 100644 src/test/ssl/ssl/server-ip-cn-and-alt-names.crt
 create mode 100644 src/test/ssl/ssl/server-ip-cn-and-alt-names.key
 create mode 100644 src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.crt
 create mode 100644 src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.key
 create mode 100644 src/test/ssl/ssl/server-ip-cn-only.crt
 create mode 100644 src/test/ssl/ssl/server-ip-cn-only.key

diff --git a/src/include/common/inet-common.h b/src/include/common/inet-common.h
new file mode 100644
index 0000000000..3ad72261b6
--- /dev/null
+++ b/src/include/common/inet-common.h
@@ -0,0 +1,35 @@
+/*-------------------------------------------------------------------------
+ *
+ * inet-common.h
+ *
+ *	  Common code for clients of the inet_* APIs.
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/common/inet-common.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef INET_COMMON_H
+#define INET_COMMON_H
+
+/*
+ * We use these values for the "family" field of inet_struct.
+ *
+ * Referencing all of the non-AF_INET types to AF_INET lets us work on
+ * machines which may not have the appropriate address family (like
+ * inet6 addresses when AF_INET6 isn't present) but doesn't cause a
+ * dump/reload requirement.  Pre-7.4 databases used AF_INET for the family
+ * type on disk.
+ *
+ * In a frontend build, we can't include inet.h, but we still need to have
+ * sensible definitions of these two constants.  (Frontend clients should
+ * include this header directly.)  Note that pg_inet_net_ntop()
+ * assumes that PGSQL_AF_INET is equal to AF_INET.
+ */
+#define PGSQL_AF_INET	(AF_INET + 0)
+#define PGSQL_AF_INET6	(AF_INET + 1)
+
+#endif							/* INET_COMMON_H */
diff --git a/src/include/utils/inet.h b/src/include/utils/inet.h
index 3073c0307e..c56c016987 100644
--- a/src/include/utils/inet.h
+++ b/src/include/utils/inet.h
@@ -14,6 +14,7 @@
 #ifndef INET_H
 #define INET_H
 
+#include "common/inet-common.h"
 #include "fmgr.h"
 
 /*
@@ -27,18 +28,6 @@ typedef struct
 	unsigned char ipaddr[16];	/* up to 128 bits of address */
 } inet_struct;
 
-/*
- * We use these values for the "family" field.
- *
- * Referencing all of the non-AF_INET types to AF_INET lets us work on
- * machines which may not have the appropriate address family (like
- * inet6 addresses when AF_INET6 isn't present) but doesn't cause a
- * dump/reload requirement.  Pre-7.4 databases used AF_INET for the family
- * type on disk.
- */
-#define PGSQL_AF_INET	(AF_INET + 0)
-#define PGSQL_AF_INET6	(AF_INET + 1)
-
 /*
  * Both INET and CIDR addresses are represented within Postgres as varlena
  * objects, ie, there is a varlena header in front of the struct type
diff --git a/src/interfaces/libpq/fe-secure-common.c b/src/interfaces/libpq/fe-secure-common.c
index bd46f08fae..2b8f2d6b24 100644
--- a/src/interfaces/libpq/fe-secure-common.c
+++ b/src/interfaces/libpq/fe-secure-common.c
@@ -19,9 +19,13 @@
 
 #include "postgres_fe.h"
 
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
 #include "fe-secure-common.h"
 
 #include "libpq-int.h"
+#include "port.h"
 #include "pqexpbuffer.h"
 
 /*
@@ -144,6 +148,106 @@ pq_verify_peer_name_matches_certificate_name(PGconn *conn,
 	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'))
+	{
+		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 = PGSQL_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;
+		}
+	}
+	else if (iplen == 16)
+	{
+		/* IPv6 */
+		unsigned char addr[16];
+
+		family = PGSQL_AF_INET6;
+
+		/*
+		 * pg_inet_net_pton() will accept CIDR masks, which we don't want to
+		 * match, so skip the comparison if the host string contains a slash.
+		 */
+		if (!strchr(host, '/')
+			&& pg_inet_net_pton(PGSQL_AF_INET6, host, addr, -1) == 128)
+		{
+			if (memcmp(ipdata, addr, iplen) == 0)
+				match = 1;
+		}
+	}
+	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.
  *
diff --git a/src/interfaces/libpq/fe-secure-common.h b/src/interfaces/libpq/fe-secure-common.h
index 1cca6d785a..20ff9ba5db 100644
--- a/src/interfaces/libpq/fe-secure-common.h
+++ b/src/interfaces/libpq/fe-secure-common.h
@@ -16,11 +16,16 @@
 #ifndef FE_SECURE_COMMON_H
 #define FE_SECURE_COMMON_H
 
+#include "common/inet-common.h"
 #include "libpq-fe.h"
 
 extern int	pq_verify_peer_name_matches_certificate_name(PGconn *conn,
 														 const char *namedata, size_t namelen,
 														 char **store_name);
+extern int	pq_verify_peer_name_matches_certificate_ip(PGconn *conn,
+													   const unsigned char *addrdata,
+													   size_t addrlen,
+													   char **store_name);
 extern bool pq_verify_peer_name_matches_certificate(PGconn *conn);
 
 #endif							/* FE_SECURE_COMMON_H */
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index 9f735ba437..55bd55ae6e 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -72,6 +72,9 @@ static int	verify_cb(int ok, X509_STORE_CTX *ctx);
 static int	openssl_verify_peer_name_matches_certificate_name(PGconn *conn,
 															  ASN1_STRING *name,
 															  char **store_name);
+static int	openssl_verify_peer_name_matches_certificate_ip(PGconn *conn,
+															ASN1_OCTET_STRING *addr_entry,
+															char **store_name);
 static void destroy_ssl_system(void);
 static int	initialize_SSL(PGconn *conn);
 static PostgresPollingStatusType open_client_SSL(PGconn *);
@@ -509,6 +512,51 @@ openssl_verify_peer_name_matches_certificate_name(PGconn *conn, ASN1_STRING *nam
 	return pq_verify_peer_name_matches_certificate_name(conn, (const char *) namedata, len, store_name);
 }
 
+/*
+ * OpenSSL-specific wrapper around
+ * pq_verify_peer_name_matches_certificate_ip(), converting the
+ * ASN1_OCTET_STRING into a plain C string.
+ */
+static int
+openssl_verify_peer_name_matches_certificate_ip(PGconn *conn,
+												ASN1_OCTET_STRING *addr_entry,
+												char **store_name)
+{
+	int			len;
+	const unsigned char *addrdata;
+
+	/* Should not happen... */
+	if (addr_entry == NULL)
+	{
+		appendPQExpBufferStr(&conn->errorMessage,
+							 libpq_gettext("SSL certificate's address entry is missing\n"));
+		return -1;
+	}
+
+	/*
+	 * GEN_IPADD is an OCTET STRING containing an IP address in network byte
+	 * order.
+	 */
+#ifdef HAVE_ASN1_STRING_GET0_DATA
+	addrdata = ASN1_STRING_get0_data(addr_entry);
+#else
+	addrdata = ASN1_STRING_data(addr_entry);
+#endif
+	len = ASN1_STRING_length(addr_entry);
+
+	return pq_verify_peer_name_matches_certificate_ip(conn, addrdata, len, store_name);
+}
+
+static bool
+is_ip_address(const char *host)
+{
+	struct in_addr	dummy4;
+	unsigned char	dummy6[16];
+
+	return inet_aton(host, &dummy4)
+		|| (pg_inet_net_pton(PGSQL_AF_INET6, host, dummy6, -1) == 128);
+}
+
 /*
  *	Verify that the server certificate matches the hostname we connected to.
  *
@@ -522,6 +570,36 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 	STACK_OF(GENERAL_NAME) * peer_san;
 	int			i;
 	int			rc = 0;
+	char	   *host = conn->connhost[conn->whichhost].host;
+	int			host_type;
+	bool		check_cn = true;
+
+	Assert(host && host[0]); /* should be guaranteed by caller */
+
+	/*
+	 * We try to match the NSS behavior here, which is a slight departure from
+	 * the spec but seems to make more intuitive sense:
+	 *
+	 * If connhost contains a DNS name, and the certificate's SANs contain any
+	 * dNSName entries, then we'll ignore the Subject Common Name entirely;
+	 * otherwise, we fall back to checking the CN. (This behavior matches the
+	 * RFC.)
+	 *
+	 * If connhost contains an IP address, and the SANs contain iPAddress
+	 * entries, we again ignore the CN. Otherwise, we allow the CN to match,
+	 * EVEN IF there is a dNSName in the SANs. (RFC 6125 prohibits this: "A
+	 * client MUST NOT seek a match for a reference identifier of CN-ID if the
+	 * presented identifiers include a DNS-ID, SRV-ID, URI-ID, or any
+	 * application-specific identifier types supported by the client.")
+	 *
+	 * NOTE: Prior versions of libpq did not consider iPAddress entries at all,
+	 * so this new behavior might break a certificate that has different IP
+	 * addresses in the Subject CN and the SANs.
+	 */
+	if (is_ip_address(host))
+		host_type = GEN_IPADD;
+	else
+		host_type = GEN_DNS;
 
 	/*
 	 * First, get the Subject Alternative Names (SANs) from the certificate,
@@ -537,24 +615,40 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 		for (i = 0; i < san_len; i++)
 		{
 			const GENERAL_NAME *name = sk_GENERAL_NAME_value(peer_san, i);
+			char	   *alt_name = NULL;
 
-			if (name->type == GEN_DNS)
+			if (name->type == host_type)
 			{
-				char	   *alt_name;
+				/*
+				 * This SAN is of the same type (IP or DNS) as our host name, so
+				 * don't allow a fallback check of the CN.
+				 */
+				check_cn = false;
+			}
 
+			if (name->type == GEN_DNS)
+			{
 				(*names_examined)++;
 				rc = openssl_verify_peer_name_matches_certificate_name(conn,
 																	   name->d.dNSName,
 																	   &alt_name);
+			}
+			else if (name->type == GEN_IPADD)
+			{
+				(*names_examined)++;
+				rc = openssl_verify_peer_name_matches_certificate_ip(conn,
+																	 name->d.iPAddress,
+																	 &alt_name);
+			}
 
-				if (alt_name)
-				{
-					if (!*first_name)
-						*first_name = alt_name;
-					else
-						free(alt_name);
-				}
+			if (alt_name)
+			{
+				if (!*first_name)
+					*first_name = alt_name;
+				else
+					free(alt_name);
 			}
+
 			if (rc != 0)
 				break;
 		}
@@ -562,13 +656,14 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 	}
 
 	/*
-	 * If there is no subjectAltName extension of type dNSName, check the
+	 * If there is no subjectAltName extension of the matching type, check the
 	 * Common Name.
 	 *
 	 * (Per RFC 2818 and RFC 6125, if the subjectAltName extension of type
-	 * dNSName is present, the CN must be ignored.)
+	 * dNSName is present, the CN must be ignored. We break this rule if host is
+	 * an IP address; see the comment above.)
 	 */
-	if (*names_examined == 0)
+	if ((rc == 0) && check_cn)
 	{
 		X509_NAME  *subject_name;
 
@@ -581,10 +676,20 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 												  NID_commonName, -1);
 			if (cn_index >= 0)
 			{
+				char   *common_name = NULL;
+
 				(*names_examined)++;
 				rc = openssl_verify_peer_name_matches_certificate_name(conn,
 																	   X509_NAME_ENTRY_get_data(X509_NAME_get_entry(subject_name, cn_index)),
-																	   first_name);
+																	   &common_name);
+
+				if (common_name)
+				{
+					if (!*first_name)
+						*first_name = common_name;
+					else
+						free(common_name);
+				}
 			}
 		}
 	}
diff --git a/src/port/inet_net_ntop.c b/src/port/inet_net_ntop.c
index b8ad69c390..af28056132 100644
--- a/src/port/inet_net_ntop.c
+++ b/src/port/inet_net_ntop.c
@@ -31,17 +31,7 @@ static const char rcsid[] = "Id: inet_net_ntop.c,v 1.1.2.2 2004/03/09 09:17:27 m
 #include <netinet/in.h>
 #include <arpa/inet.h>
 
-#ifndef FRONTEND
-#include "utils/inet.h"
-#else
-/*
- * In a frontend build, we can't include inet.h, but we still need to have
- * sensible definitions of these two constants.  Note that pg_inet_net_ntop()
- * assumes that PGSQL_AF_INET is equal to AF_INET.
- */
-#define PGSQL_AF_INET	(AF_INET + 0)
-#define PGSQL_AF_INET6	(AF_INET + 1)
-#endif
+#include "common/inet-common.h"
 
 
 #define NS_IN6ADDRSZ 16
diff --git a/src/port/inet_net_pton.c b/src/port/inet_net_pton.c
index bae50ba67e..5bae811c6f 100644
--- a/src/port/inet_net_pton.c
+++ b/src/port/inet_net_pton.c
@@ -33,18 +33,10 @@ static const char rcsid[] = "Id: inet_net_pton.c,v 1.4.2.3 2004/03/17 00:40:11 m
 #include <assert.h>
 #include <ctype.h>
 
+#include "common/inet-common.h"
 #ifndef FRONTEND
 #include "utils/builtins.h" /* pgrminclude ignore */	/* needed on some
 														 * platforms */
-#include "utils/inet.h"
-#else
-/*
- * In a frontend build, we can't include inet.h, but we still need to have
- * sensible definitions of these two constants.  Note that pg_inet_net_ntop()
- * assumes that PGSQL_AF_INET is equal to AF_INET.
- */
-#define PGSQL_AF_INET	(AF_INET + 0)
-#define PGSQL_AF_INET6	(AF_INET + 1)
 #endif
 
 
diff --git a/src/test/ssl/conf/server-cn-and-ip-alt-names.config b/src/test/ssl/conf/server-cn-and-ip-alt-names.config
new file mode 100644
index 0000000000..a6fa09bad3
--- /dev/null
+++ b/src/test/ssl/conf/server-cn-and-ip-alt-names.config
@@ -0,0 +1,24 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+# This certificate contains a CN and SANs for both IPv4 and IPv6.
+
+
+[ req ]
+distinguished_name     = req_distinguished_name
+req_extensions         = v3_req
+prompt                 = no
+
+[ req_distinguished_name ]
+# Note: According to RFC 2818 and 6125, the CN is ignored, when DNS names are
+# present in the SANs. But they are silent on whether the CN is checked when IP
+# addresses are present.
+CN = common-name.pg-ssltest.test
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
+subjectAltName = @alt_names
+
+[ alt_names ]
+IP.1 = 192.0.2.1
+IP.2 = 2001:DB8::1
diff --git a/src/test/ssl/conf/server-ip-alt-names.config b/src/test/ssl/conf/server-ip-alt-names.config
new file mode 100644
index 0000000000..c22f22951a
--- /dev/null
+++ b/src/test/ssl/conf/server-ip-alt-names.config
@@ -0,0 +1,19 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+# This certificate has a two IP-address SANs, and no CN.
+
+[ req ]
+distinguished_name     = req_distinguished_name
+req_extensions         = v3_req
+prompt                 = no
+
+[ req_distinguished_name ]
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
+subjectAltName = @alt_names
+
+[ alt_names ]
+IP.1 = 192.0.2.1
+IP.2 = 2001:DB8::1
diff --git a/src/test/ssl/conf/server-ip-cn-and-alt-names.config b/src/test/ssl/conf/server-ip-cn-and-alt-names.config
new file mode 100644
index 0000000000..a4087f0a18
--- /dev/null
+++ b/src/test/ssl/conf/server-ip-cn-and-alt-names.config
@@ -0,0 +1,21 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+# This certificate contains both a CN and SANs in IP address format.
+
+
+[ req ]
+distinguished_name     = req_distinguished_name
+req_extensions         = v3_req
+prompt                 = no
+
+[ req_distinguished_name ]
+CN = 192.0.2.1
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
+subjectAltName = @alt_names
+
+[ alt_names ]
+IP.1 = 192.0.2.2
+IP.2 = 2001:DB8::1
diff --git a/src/test/ssl/conf/server-ip-cn-and-dns-alt-names.config b/src/test/ssl/conf/server-ip-cn-and-dns-alt-names.config
new file mode 100644
index 0000000000..7121803b49
--- /dev/null
+++ b/src/test/ssl/conf/server-ip-cn-and-dns-alt-names.config
@@ -0,0 +1,21 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+# This certificate contains both a CN and SANs in IP address format.
+
+
+[ req ]
+distinguished_name     = req_distinguished_name
+req_extensions         = v3_req
+prompt                 = no
+
+[ req_distinguished_name ]
+CN = 192.0.2.1
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
+subjectAltName = @alt_names
+
+[ alt_names ]
+DNS.1 = dns1.alt-name.pg-ssltest.test
+DNS.2 = dns2.alt-name.pg-ssltest.test
diff --git a/src/test/ssl/conf/server-ip-cn-only.config b/src/test/ssl/conf/server-ip-cn-only.config
new file mode 100644
index 0000000000..585d8bdae8
--- /dev/null
+++ b/src/test/ssl/conf/server-ip-cn-only.config
@@ -0,0 +1,12 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+
+[ req ]
+distinguished_name     = req_distinguished_name
+prompt                 = no
+
+[ req_distinguished_name ]
+CN = 192.0.2.1
+OU = PostgreSQL test suite
+
+# No Subject Alternative Names
diff --git a/src/test/ssl/ssl/server-cn-and-ip-alt-names.crt b/src/test/ssl/ssl/server-cn-and-ip-alt-names.crt
new file mode 100644
index 0000000000..4e58c85ccb
--- /dev/null
+++ b/src/test/ssl/ssl/server-cn-and-ip-alt-names.crt
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDLzCCAhegAwIBAgIIICERKRE1UQAwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UE
+Aww3VGVzdCBDQSBmb3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHNl
+cnZlciBjZXJ0czAeFw0yMTExMjkxOTM1NTFaFw00OTA0MTYxOTM1NTFaMEYxHjAc
+BgNVBAsMFVBvc3RncmVTUUwgdGVzdCBzdWl0ZTEkMCIGA1UEAwwbY29tbW9uLW5h
+bWUucGctc3NsdGVzdC50ZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
+AQEA6+8IYKAFnZ7V+fDo1cyMpbGBLzCfJOQ/1o2jOGP4+GjpsZgv6S6UT2MheC8M
+iiEFrYwdsSIZyYc3jEZrluy/UuR0bCGtqU92BCqa0iBLhvHOgjR588u253eLxQtQ
+8iJn11QPrKMk35nMkmY8GfHt4sGFbvBL6+GpipHq7a6cde3Z+v4kCB5dKMYDUDtm
+3mJmviuGNAu5wOqItk2Yi5dwJs1054007KNH0Il43urxiOfnkLS0cG5kehboPf86
+vxBt3iHByrU/9/DY5IvQCfSXVNa6rb5w5/pGja9aCei6Mv1jQY/V8SMQTga+MOsA
+0WB9akxMi2NxwS2+BQ4k/McPlwIDAQABoyUwIzAhBgNVHREEGjAYhwTAAAIBhxAg
+AQ24AAAAAAAAAAAAAAABMA0GCSqGSIb3DQEBCwUAA4IBAQAQLo2RzC07dG9p+J3A
+W6C0p3Y+Os/YE2D9wfp4TIDTZxcRUQZ0S6ahF1N6sp8l9KHBJHPU1cUpRAU1oD+Y
+SqmnP/VJRRDTTj9Ytdc/Vuo2jeLpSYhVKrCqtjqIrCwYJFoYRmMoxTtJGlwA0hSd
+kwo3XYrALPUQWUErTYPvNfDNIuUwqUXNfS0CXuIOVN3LJ+shegg6Pwbh9B5T9NHx
+kH+HswajhdpdnZIgh0FYTlTCPILDrB49aOWwqLa54AUA6WXa35hPsP8SoqL9Eucq
+ifPhBYyadsjOb+70N8GbbAsDPN1jCX9L8RuNcEkxSCKCYx91cWXh7K5KMPuGlzB7
+j8xB
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-cn-and-ip-alt-names.key b/src/test/ssl/ssl/server-cn-and-ip-alt-names.key
new file mode 100644
index 0000000000..837eef996d
--- /dev/null
+++ b/src/test/ssl/ssl/server-cn-and-ip-alt-names.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA6+8IYKAFnZ7V+fDo1cyMpbGBLzCfJOQ/1o2jOGP4+GjpsZgv
+6S6UT2MheC8MiiEFrYwdsSIZyYc3jEZrluy/UuR0bCGtqU92BCqa0iBLhvHOgjR5
+88u253eLxQtQ8iJn11QPrKMk35nMkmY8GfHt4sGFbvBL6+GpipHq7a6cde3Z+v4k
+CB5dKMYDUDtm3mJmviuGNAu5wOqItk2Yi5dwJs1054007KNH0Il43urxiOfnkLS0
+cG5kehboPf86vxBt3iHByrU/9/DY5IvQCfSXVNa6rb5w5/pGja9aCei6Mv1jQY/V
+8SMQTga+MOsA0WB9akxMi2NxwS2+BQ4k/McPlwIDAQABAoIBAQCuNFKVNdKvrUYF
+RLJGmsAG3+eo9lern7TbML2ht39vu9dBwEMwA6qSa3mdCfBSVUuh9uE9lxY/TU3g
+j2aFi81A4VptNPjLGNblAKhMGnhp7UUzspeRQYuNoSFcnpxoDKtrvK/OIq/pQeBh
+AIfECHRDh+yEG32Tb44FuPQkB1eTYl8xbMEImrhNUaSjJk7tTsmydHy0DjmqHVKX
+HUj0TREfDBDOBiHtY0XV6Pu3bnqDH/TKLTfUf3UdfTuay3Yai9aEcRPWp9GrMO7G
+axsKCifTz6177gyr6Fv8HLeMZMh9rMZRn3e0zfaF6vrH1QnZZOts5jpUa0KugSCd
+//uC0iNxAoGBAPXVc3b+o3hY5gcwwpaW6JtsarDrmNRxrizqIDG7NgpqwdFXgTi6
+6q0t2pjv81ATqij69IcPkNSissyR4OEKnu/OFJWzreg8yLi75WHKi0E/6msHpwRk
+d1yP0Zgd05ots/yOjDSp593RagaPVvHBxMECZ/Tm3B+Tq55Azudd/zvLAoGBAPWw
+xf0oUEJl6NdUZD6K7eFc6jf8yrpD85dldeko6LeN8x0XlKKWvUDJ2+3oizXoQvCm
+8by6KOYEIo4MrtXuy9MmtPWfNvRBr+hsUHchIj7IgFa9bKXyK2FnJqu/8CbEymli
+eZu7hoOhelurhnFy1zSqwNO4GC+kw60Y/BO3Z1nlAoGAVOyYJtNwxXJwhKtjjYI0
+ePzLHrNE6J8c/Ick+AkkchTPP/JqwZ5Q0+KzUYITG+avMdkAAGhwMATEn8cFWLjC
+jzUyB0U7Hq9g5/CBHXdLBA+Ae9j46ZuLYH6OeW5UWz7OnsDfzpGjeA2QAxQhhQLb
+ZZHfN8tI39+zucfJskPWmGECgYEAg9guF1Fn6InJrqwR82IYj6SN6CeXHufSM392
+C/4xDDd3rDf4QlwECV2J0RzGf9I5Ae2EshNwWScE6Be0RweTh6cw2tJq6h7J6D8f
+2x4Dw49TF7klMdRIJUf2f5pLpHJccLswqTqzz7V69PCSABVxmUi8m6EiEYconp5W
+v7nfE2UCgYALrEqzncuSIX3q6TVAjnzT7gO4h8h2TUekIWdHQFldFx8R7Kncggnd
+48gQqhewchNR83UCcd7pPsCcTqu6UR1QRdq/DV5P6J3xdZ2iS/2gCM6hvWIvKZEv
+/ClnkyFCOW7zX6RKIXtRYZTV1kz3TajApi34RTIeIMTieaCarnBJbA==
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-ip-alt-names.crt b/src/test/ssl/ssl/server-ip-alt-names.crt
new file mode 100644
index 0000000000..8a1bc620bb
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-alt-names.crt
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDCTCCAfGgAwIBAgIIICERKREEUAAwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UE
+Aww3VGVzdCBDQSBmb3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHNl
+cnZlciBjZXJ0czAeFw0yMTExMjkxOTA0NTBaFw00OTA0MTYxOTA0NTBaMCAxHjAc
+BgNVBAsMFVBvc3RncmVTUUwgdGVzdCBzdWl0ZTCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBAOM8yB6aVWb17ujr3ayU62mxHQoqn4CvG9yXlJvGOGv/ursW
+Vs0UYJdc96LsNZN1szdm9ayNzCIw3eja+ULsjxCi6+3LM4pO76IORL/XFamlTPYb
+BZ4pHdZVB0nnZAAnWCZPyXdnjOKQ5+8unVXkfibkjj8UELBJ2snehsOa+CTkOBez
+zxYMqxAgbywLIYsW448brun7UXpWmqbGK+SsdGaIZ5Sb7Zezc5lt6CrLemTZTHHK
+7l4WZFCCEi4t3sgO8o1vDELD/IE5G8lyXvIdgJg6t8ssper7iCw6S8x+okhjiSjT
+vDLU2g4AanqZRZB49aPwTo0QUcJA2BCJxL9xLy8CAwEAAaMlMCMwIQYDVR0RBBow
+GIcEwAACAYcQIAENuAAAAAAAAAAAAAAAATANBgkqhkiG9w0BAQsFAAOCAQEAwZJ+
+8KpABTlMEgKnHIYb35ItGhtFiTLQta9RkXx7vaeDwpOdPP/IvuvpjpQZkobRgBsk
+bNM0KuJpd2mSTphQAt6eKQIdcPrkzvc/Yh9OK3YNLUAbu/ZhBUnBvFnUL4wn2f1U
+mfO+m8P/LxybwqKx7r1mbaB+tP3RTxxLcIMvm9ECPQEoBntfEL325Wdoj+WuQH5Y
+IvcM6FaCTkQsNIPbaBD5l5MhMLHRULZujbDjXqGSvRMQfns6np/biMjNdQA8NZ5z
+STeUFvkQbCxoA0YYLgoSHL5KhZjXrg2g+T+2TUyCTR/91xf9OoOjBZdixR0S0DzJ
+B1+5vnUjZaCfnSEA7A==
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-ip-alt-names.key b/src/test/ssl/ssl/server-ip-alt-names.key
new file mode 100644
index 0000000000..b210b3a991
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-alt-names.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEA4zzIHppVZvXu6OvdrJTrabEdCiqfgK8b3JeUm8Y4a/+6uxZW
+zRRgl1z3ouw1k3WzN2b1rI3MIjDd6Nr5QuyPEKLr7cszik7vog5Ev9cVqaVM9hsF
+nikd1lUHSedkACdYJk/Jd2eM4pDn7y6dVeR+JuSOPxQQsEnayd6Gw5r4JOQ4F7PP
+FgyrECBvLAshixbjjxuu6ftRelaapsYr5Kx0ZohnlJvtl7NzmW3oKst6ZNlMccru
+XhZkUIISLi3eyA7yjW8MQsP8gTkbyXJe8h2AmDq3yyyl6vuILDpLzH6iSGOJKNO8
+MtTaDgBqeplFkHj1o/BOjRBRwkDYEInEv3EvLwIDAQABAoIBACp3uY6+mSdc3wF4
+0zzlt/lQuHSl8plCIJrhWUyjhvfoGyXLzv0Uydh/72frbTfZz1yTSWauOXBKYa6a
+/eqb+0DIsf8G8uLuTaqjsAWKVOoXkoKMGkistn7P9UTCkdXVhIvkbWp7V8EgA7iX
+pZ/fzBPIsyzmuxe3NcR0ags0cxuxkNuu+YXDv1oTedmT2wS3CZq1d/T1Y/EOVIf8
+Iznd2aOverlsnt6iiQ3ZWdG/W5F8FhnrR/rrBdYsdCv6TH/KUYexnDOUYpayjDbu
+oAKnifPp6UqiOM4SuBL83OAz19jptp5vpF370BEVRs3eK0q+zo/mETjv9HsXdolZ
+lfoXA0ECgYEA/7nb2azbq/2muvXCh1ZxCEbn3mt8KXoJP/xkx/v9eEc/cc5Q9e0V
+2oGfjC2hSE+bjOWMwiUMD6uU+iRjhz5A3IvUxnoSdoL7H9p0hTqLMyP7dTDkoVF5
+aEuLMaiI5YEnfAFu9L5h8ZKieoQTBoscT06wnGjh9pBV9bthfTKA7ksCgYEA43sb
+55m9WL4kWCPwOAp3vdEAFyxzmZHlO26sEQOU/m5aN01pumYybBruziEXMI96yfTj
+VmXKReeYb6XUiCcs3fLSipD/+8/8CsjO4uMORtxWumXe8AbKZfysGFzL7wJlByGT
+38AGQwIG/XD8cKnaiEMX4E/3Owbcoxwixo3WZC0CgYEAovaqJ9mEU+Jc8h/TS7PG
+bGPjN1Z/1V6zrlcFUnw/Vvrwb3HvHglsN8cLCaW6df5lPjC6tq4tNX8+fPnbg0Ak
+zWc+vQzl3ygxKGdqgcyBEKIJiPETgcoN+GzL02V3d+oKY3f2YXlBqVSsvi6UgUL9
+U3zuB36/IQVyAhrbUZFxoGkCgYEAnaFAO+Nvrp/LhXwZyGuQf+rkmipGTIMpil5t
+QzjtNMV5JFszSWPpyrl7A0Ew1YiG+I0GP2c3m+sY2TzbIiGrWH0b4cMKbw63Qy3V
+FqlpyjaCrpVKv56k/7jv883RzuQk56Uf1+szK5mrCFITy2oXsVZ0pA4lbjSaDTjA
+7D968V0CgYEA+qKqXKL98+c5CMPnpf+0B1x2zgyUym1ouPfon2x5fhK84T53zDMA
+zfdUJ/SOZw6/c9vRF7RL8h+ZfFdIyoAXv4Tt6mIiZe7P+AUVg6XgJ0ce2MUSeWjI
+W8D4WdSi0jyqr99TuVBWhbTZJviMB3pHqKaHQ07hnd/lPtvzsiH12qk=
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-ip-cn-and-alt-names.crt b/src/test/ssl/ssl/server-ip-cn-and-alt-names.crt
new file mode 100644
index 0000000000..2be02feb03
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-and-alt-names.crt
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDHTCCAgWgAwIBAgIIICIBBBQ2MQAwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UE
+Aww3VGVzdCBDQSBmb3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHNl
+cnZlciBjZXJ0czAeFw0yMjAxMDQyMjM2MzFaFw00OTA1MjIyMjM2MzFaMDQxHjAc
+BgNVBAsMFVBvc3RncmVTUUwgdGVzdCBzdWl0ZTESMBAGA1UEAwwJMTkyLjAuMi4x
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwmqTdQJfs2Ti9tPitYp2
+27I0HvL/kNSgA6egFr0foRo0BorwJNIzdbV0+EnsfiBNTWL5It26gqO7UP3ms8t2
+vHD5gkXfT+f6ts0lVJEcIOkUD/8ws4Ic9Y4uPqb4gN+pUKqcxtmLW1TYk84MBK59
+Xz4yPPS6N+G/DMMeFHTNkM9EQwn/+DC3fDsWdGYM2GRWDTJGg1A5tSUcF+seu7i1
+Vg7XajBfsvgAUAsrAxV+X/sLZh94HY+paD6wfaI99mY2OXVc/XW/z1r9WQznor65
+ZkonNCaPfavqPG5vqnab9AyQcqPqmX8hf/xrniASBAkqNCctbASrFCIYvCJfGfmX
+EQIDAQABoyUwIzAhBgNVHREEGjAYhwTAAAIChxAgAQ24AAAAAAAAAAAAAAABMA0G
+CSqGSIb3DQEBCwUAA4IBAQBf7kmYfRYfnWk1OUfY3N1kaNg9piBBlFr9g+OQn9KU
+zirkN7s0ZQbCGxV1uJQBKS58NyE414Vorau77379emgYDcCBpDIYpkLiNujVrIOr
+ggRFKsFRgxu4/mw0BSgCcV8RPe9SWHZ90Mos7TMCnW/PdxOCD1wD0YMkcs0rwB3l
+0Kzc7jDnfOEvmgw/Ysm7v67ps+05Uq5VskQ6WrpSAw6kPD/QMuuBAX8ATPczIaox
+zAMyncq1IiSIwG93f3EoQQThdQ70C6G9vLcu9TtL6JAsEMFEzR99gt1Wsqvmgl9W
+kStzj1yjIWeo5gIsa4Jgcke1lZviWyrTxHDfyunYE5i5
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-ip-cn-and-alt-names.key b/src/test/ssl/ssl/server-ip-cn-and-alt-names.key
new file mode 100644
index 0000000000..54fe80fc68
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-and-alt-names.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAwmqTdQJfs2Ti9tPitYp227I0HvL/kNSgA6egFr0foRo0Borw
+JNIzdbV0+EnsfiBNTWL5It26gqO7UP3ms8t2vHD5gkXfT+f6ts0lVJEcIOkUD/8w
+s4Ic9Y4uPqb4gN+pUKqcxtmLW1TYk84MBK59Xz4yPPS6N+G/DMMeFHTNkM9EQwn/
++DC3fDsWdGYM2GRWDTJGg1A5tSUcF+seu7i1Vg7XajBfsvgAUAsrAxV+X/sLZh94
+HY+paD6wfaI99mY2OXVc/XW/z1r9WQznor65ZkonNCaPfavqPG5vqnab9AyQcqPq
+mX8hf/xrniASBAkqNCctbASrFCIYvCJfGfmXEQIDAQABAoIBAB6GgVST5NbT9lbu
++d+rN/JSzqA1Yy8oU19/iEFJvJec96I3WnFNl8rZjN4XLUy4YarO6XMyAUDV2Gll
+FD4Sqjf4PRTZR7DSKaleGIhoqFP6hK3mUY091rIves9XhBkoBPunbipCqgDTF5ZN
+edGaXBECQP0VJ8/yX/7u++AWXthnjDis9X0taZfFg/PYbV7SCJ1Hg1O/wEsgXlnC
+7mbL6wkCW0f6700B0x1kKbZqJY95xRqp6Ipq2lIQbJDdGywoj0WzKqNltf9cer+r
+cXl8WjeiMvvvpl4uGhckAbzUifUzxN6A3f1fu/XKtOmabMi9t7J4MRfgOgedgtQB
+0jaZGSkCgYEA+lBLnNY6M48HX2mdtr86+n41gh69v8Z7oNikJFDZkodrvI8uqE0i
+0XwnYPFddt8NbmuUhhuzI2M8RKhGLgdlbKpkSSVafnMfcxRmX2EAtWQgdvX1Iult
+752LWdBgSuw2vlzvy3T/GYnjMrXSCGput4amqojMEbvUGvIdSUMdHGMCgYEAxtU1
+WixKPL6aEnYy1f4bybzcNgGtl8PBRz9xw+P46g+ijOPoaG9O73Tr7An11AO003Ot
+DHhMW+b8yHLyxoKwS2sU2cN/lKB8xNQYZc1D61RNJlzgnHMXnA0lcH0I3M35fqKr
+/71pD1ZP40SSJS+od/KEjW80XzuOdyiXg8q81vsCgYEAnUPLbbsuj+whzrFVlFZr
+IKwgxCK6Rn3WeIUEA4kEWUpZxvsSbk0gPgtJ1l9uwFt9Xc2bX/KRRv93Aw/SH+Mn
+tvEK1uXwCBgePzgm5W/VeSFyQCthm1CbcHtD7Oa9SPVFo65SPjrAd3QpWVfgoMb1
+zrp7hhMyW0XuCgvpmHjhFk8CgYEAxq/thXM2p+bLLWGhwQcRG5G299zLbBl4PUsf
+0uEvLi17gJCKADoiRdSvoAn/9eHSQ26XYRuhKkDzHxcGlOmpY2PYzRa3mXyZ0VIk
+Iy5wDWwLQCeVZ6D22cClRfgb8BF/nFTPzVmn72SPpgoyhChQj7PvUynpyrRH07jj
+VxYziBsCgYAFr37Xbl0VnXVK+XU+vMwUZjcF4jpoCr7SFZqgRbW2GbYSUoMuPXns
+RnJh+Fvi1NUei+E5s1H4P1pVq4p0jFxP4GvH/qvNjnIn/Er3bbqvpox6dWUJXprq
+qTQSDIeoDC/V8cyRoIfqPvTVqY8Rgew6GEkv0bAImdxhoSng7vIseg==
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.crt b/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.crt
new file mode 100644
index 0000000000..23c06da01c
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.crt
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDQzCCAiugAwIBAgIIICIBBBQ2MQEwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UE
+Aww3VGVzdCBDQSBmb3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHNl
+cnZlciBjZXJ0czAeFw0yMjAxMDQyMjM2MzFaFw00OTA1MjIyMjM2MzFaMDQxHjAc
+BgNVBAsMFVBvc3RncmVTUUwgdGVzdCBzdWl0ZTESMBAGA1UEAwwJMTkyLjAuMi4x
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8xddbo/x2TOSIa/br8BN
+o/URdTr9+l2R5YojiZKDuLxiQVkgC30PJ2/CNFKIh2nHhRrzknI6sETVtrxZ+9V2
+qRc1yShVu462u0DHPRMIZnZIOZg3hlNB0cRWbOglUKlttIARNEQUcTUyPOtyo4/v
++u0Ej5NTNcHFbFT01vdD9MjQiCO3jKdAwPIb14jTg4C71EpZ+LuelDo4DzF2/XgG
+WqUTrgD/XnBU/60PU9Iy3G0nVpx21q6ppn9G7a9R+i8FjBcwW1T+cfsBDWhAv+bi
+RmSAkENf8L8TwOlDQUwROkfz3Hz36vuJjdkreQJsiqL0HnrnH5T5G9UzJO86FvZQ
+5wIDAQABo0swSTBHBgNVHREEQDA+gh1kbnMxLmFsdC1uYW1lLnBnLXNzbHRlc3Qu
+dGVzdIIdZG5zMi5hbHQtbmFtZS5wZy1zc2x0ZXN0LnRlc3QwDQYJKoZIhvcNAQEL
+BQADggEBAF+mfaw6iBPzpCgqq830pHRa3Yzm1aezt8SkeRohUYHNv/yCnDSRaqtj
+xbENih3lJMSTBL3g0wtTOHfH8ViC/h+lvYELHzXKic7gkjV7H5XETKGr0ZsjBBT2
+4cZQKbD9e0x0HrENXMYgGpBf747qL6uTOVJdG0s15hwpLq47bY5WUjXathejbpxW
+prmF8F+xaC52N9P/1VnqguQB909F4x1pyOK7D7tjFu+Y8Je7PHKbb6WY5K6xAv6t
+R17CY0749/FotlphquElUR2bs5Zzv5YrjUHPTcbwKvcH5cdNi93/u6NJt2xNAoYf
+aZERhX5TA9DYk4gC8OY0yGaYCIj3Dd4=
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.key b/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.key
new file mode 100644
index 0000000000..0ace41e0a1
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEogIBAAKCAQEA8xddbo/x2TOSIa/br8BNo/URdTr9+l2R5YojiZKDuLxiQVkg
+C30PJ2/CNFKIh2nHhRrzknI6sETVtrxZ+9V2qRc1yShVu462u0DHPRMIZnZIOZg3
+hlNB0cRWbOglUKlttIARNEQUcTUyPOtyo4/v+u0Ej5NTNcHFbFT01vdD9MjQiCO3
+jKdAwPIb14jTg4C71EpZ+LuelDo4DzF2/XgGWqUTrgD/XnBU/60PU9Iy3G0nVpx2
+1q6ppn9G7a9R+i8FjBcwW1T+cfsBDWhAv+biRmSAkENf8L8TwOlDQUwROkfz3Hz3
+6vuJjdkreQJsiqL0HnrnH5T5G9UzJO86FvZQ5wIDAQABAoIBAGv0BFoFMrHyZQLw
+xe7Wx6P4QTh+aiu1QgVdw0pk9nojrr62hbSUZRZuWyBBRsBcCW7i+Sgf8lA1QXNV
+UeC0e228EPa0It6YEi42JkTJHwHhpVFud7n/X0t4lajnryqTE1UFSp6bXTipFxZW
+uSJJ2ZjliRD5rApDcxkY4WJVjKg3aEt7P/DiM8iKGfyE6stq72VjEbJjdViMEcOP
+BNf0TiREZz5Mp7jAVWhpen0ebbLOBVWV4/ONNcL+yqR4mCEDUSFGewrTVX4zHL0A
+hYk198C5F8sFvEDnFkPco9sXMVanmLoI8sbhP4IIz9g4+GU6kFuj7fUKp11Azqv+
+3WQDKYECgYEA/XG4mmG/g8FG44y42mfZpUXWi1pwU4CQIrhkoU5j7EPQrvRboOOE
+Rv95jSwyZu4vCqjyI5FN1jCGTdhmt++R1e//zH6Hqa9Smo+jw7DtAFrCYd1JnCf1
+ToOwsYPHv4P7A8q8kc5vCNIv+AQSlP/wqdVNo3grdf7cGXkMtEY4F9UCgYEA9Yrq
+zWdnNGPATuSBqL6TSjQ37oR+dBD6WnGsiDenQkOzyDPFZ3CT1DjJghjEtxc8EfNf
+Oo8dMMR2q+5FZQo7WuqONEgyzKePiNR8RK2gOYpgdjN9bih1sAhHR10D26cpwlDJ
+bx7D5ZzENLbdZmfEiWwKswnaIhN4yMalgE0mP8sCgYAhzJy12ftUct4lUosEdX0N
+EXc/NlxshmSyfKzO5kllJNYbvvLJTg5B+agYL6C5IWKcpVNFcwdSXT5L+2QXe5eT
+VGJkvysQchUuD6HjYyD4PyJVMtGyRZHtWpqh0dU9sTg0lUD4oPMl1gIXrVNdE5Tg
+0VV9S3VgUxC/ROlw0TyB0QKBgGsVE0NS9hJF8mc1hko2GnwA++d8Rr2NbfElo+2f
+/8SJTA1ibpOm6AFkZpTjAl8qtdrKPVyHb16GP47Jkd/3r1z979hjKCxSYul0aWF2
+KusNKvZBjFEPOgv0AEniCb2wUCjbHI3mZ95qGLM4kKOJW4/m21+rS0MTJNjCsQic
+HLMzAoGAeCsY09d3m8xGeU+DuTPC6GH7Sgy/NBYqS5VaVNjb2jnuZlW2SSW2oiID
+4tXTi4ruKmHC898BfyFxhSMqub+tg3pVqIYADC71rnJLrVyc1SzoWzL7yMT3qFj7
+C7ZYZYmfG9agcZb5NkqKPTfCxkBhWbdgTTgBKVO/xQst8EUgko8=
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-ip-cn-only.crt b/src/test/ssl/ssl/server-ip-cn-only.crt
new file mode 100644
index 0000000000..9bf015cf18
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-only.crt
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC8TCCAdkCCCAhESkRN1IAMA0GCSqGSIb3DQEBCwUAMEIxQDA+BgNVBAMMN1Rl
+c3QgQ0EgZm9yIFBvc3RncmVTUUwgU1NMIHJlZ3Jlc3Npb24gdGVzdCBzZXJ2ZXIg
+Y2VydHMwHhcNMjExMTI5MTkzNzUyWhcNNDkwNDE2MTkzNzUyWjA0MR4wHAYDVQQL
+DBVQb3N0Z3JlU1FMIHRlc3Qgc3VpdGUxEjAQBgNVBAMMCTE5Mi4wLjIuMTCCASIw
+DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANWs1uUL71nHYF9Zj6p+M3MpYDvx
+32iCjVdtH5a2qpSWHXTg0rR8dLX0y92cvOYvMXHRajZT1avpHr8dooPYSVaXpGMK
+NvF/Qi+WFYovRbP2vmd1yv1cgW/FggbwJFWVobizIz4seyA4d0B2j9fqoi2OFBNP
+huW664SjF0u3p21tDy+43i2LNUMAKf6dnRR5Vqenath87LEU41tSLudu6NXgbFMk
+jvfNkl4d0w7YCzeXmklmSI+uaX3PlJJ4NzQO2j8w5BvnKVhNVD0KjgrXZ6nB/8F7
+Pg3XY+d7rJlwRgXemU6resWQDJ7+UaC9u7I4EIP+9lzCR/nNBqUktpHRmHUCAwEA
+ATANBgkqhkiG9w0BAQsFAAOCAQEAos1JncV8Yf4UaKl6h1GdYtcVtzFyJvBEnhRD
+07ldL+TYnfZiX8wK2ssBtM3cg/C78y5bzdUa5XGS83ZKQJFFdhE7PSnrvyNqyIqY
+ZgNBxto3gyvir+EjO1u9BAB0NP3r3gYoHRDZS1xOPPzt4WgjuUgTLM9k82GsqAbO
+UrOTOdRnkIqC5xLpa05EnRyJPRsR1w1PRJC2XXKnHIuFjMb4v7UuPwyCcX1P5ioc
+rQszQcORy/L+k0ezCkyweORg68htjYbBHuwOuiGfok6yKKDMzrTvD3lIslls6eX7
+4sI3XWqzkPmG9Vsxm9Vu9/Ma+PRO76VyCoIwBd+Ufg5vNXhMmw==
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-ip-cn-only.key b/src/test/ssl/ssl/server-ip-cn-only.key
new file mode 100644
index 0000000000..1966530e72
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-only.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEA1azW5QvvWcdgX1mPqn4zcylgO/HfaIKNV20flraqlJYddODS
+tHx0tfTL3Zy85i8xcdFqNlPVq+kevx2ig9hJVpekYwo28X9CL5YVii9Fs/a+Z3XK
+/VyBb8WCBvAkVZWhuLMjPix7IDh3QHaP1+qiLY4UE0+G5brrhKMXS7enbW0PL7je
+LYs1QwAp/p2dFHlWp6dq2HzssRTjW1Iu527o1eBsUySO982SXh3TDtgLN5eaSWZI
+j65pfc+Ukng3NA7aPzDkG+cpWE1UPQqOCtdnqcH/wXs+Dddj53usmXBGBd6ZTqt6
+xZAMnv5RoL27sjgQg/72XMJH+c0GpSS2kdGYdQIDAQABAoIBAQDNXviU4WnF8rmQ
+K7bH+dBdqbETLKC8BG7xTrMD2sINWlMpmUUrsEtE7+paMGHnJAj0CoF5gg5m0wN4
+UXV4H5QtpEad4p14dAYbUreVP2ZRWKEdM7xM1HKcCUu2e22QzObJbXQ8N+iHyX3k
++Y+7yYrjGiH1hYR0nbnsnAyx++zyYBSQeqzpdQwf/BLY5xZmyYWNfqbckiMpEqMs
+EmZmGXnCjIipzEC0LQHoSW9PNa92Z9bvuxOKYl8iHYDDXjvMRFoZBSiMXpzHQocb
+QlQ5F4ayfW2OrOhpNbY7niYM9GN3Bk9TgMP+0BkJE6uuktLYW35LY1M78CCPWcWb
+npJNK3QBAoGBAOxkGrhAHAysSmtirIyMdvySb76wb/Ukfi+AULKz20FI5j4/GXm9
+qCb2GeT+FFSUHeSC8f0EFnosRYkdBGruqeZioI+5rUkboYFJPspAHAuvg9kgtfF+
+kvphD4O4P/foYsEZRx66FHozDbhrrR5UXc7KzqRIASc/D3FOx2UFJLb1AoGBAOdm
+WcaMvYygl9ZW+ThWAR1xG1X70AGKwrlrpF2hBkWYxSurxSMXnD0DUzC9Nb4EyCaM
+c2uSqEZOKdW+XfXtK2DnqXKfb3YCVEoGN4gVfyuW/vxii/+ZxLo3md/b3vrkZEVp
+pfkXy/HoZ71YN7bNpcDpOnhml6vvuCRCYFnI1WuBAoGAC0shB6pwbJ6Sk5zMN47C
+ZICufAK75o9OxAAyWsdC81SDQ3gKRImuDeZ2CD2nRP8qim9DFl5qoH2a+Nj9DArI
+7SvLFfK9958tURrpuAnmDRzehLIOXzI33WRjtFxKGhLtHOKTRkGHlur3fdcPF0La
+lHWV971E6NYXa8diuU3Mmj0CgYBYd+ka3/QYL83dRKNDxp3mg7fPx9ZewI5yFZVh
+to6PTTkU2Tclk4FIUl0b5TsGyw06r7fxCMENIBUegwmpXGOZSPifuhUDKSDQrE/O
+12knYTNbitG7hy6Pg3JxA77cbTVo1FuAQHjYo+IFohSq7zTP7FtObOrP8XaVZksw
+CHiQAQKBgBW4EiA9AAnZ1LOpifAvM7bs0NHg95qTwtAL52WKom2ga2H+lMhxeu6Y
+hUSytC/f9kALVcYloZhkLYpO07x1gXmy7f4parMjA4Ex+4vfu3kPd8GiNGZ+AUJD
+nnJ1OINY9ziXJZfju7FpVWpkiuPzWCh6y/o3gZ/veq5mIUxuDMVa
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/sslfiles.mk b/src/test/ssl/sslfiles.mk
index 7ed3a30f5c..f9be5ffdba 100644
--- a/src/test/ssl/sslfiles.mk
+++ b/src/test/ssl/sslfiles.mk
@@ -22,9 +22,14 @@
 # key/certificate pair will be generated for you, signed by the appropriate CA.
 #
 SERVERS := server-cn-and-alt-names \
+	server-cn-and-ip-alt-names \
 	server-cn-only \
+	server-ip-cn-only \
+	server-ip-cn-and-alt-names \
+	server-ip-cn-and-dns-alt-names \
 	server-single-alt-name \
 	server-multiple-alt-names \
+	server-ip-alt-names \
 	server-no-names \
 	server-revoked
 CLIENTS := client client-dn client-revoked client_ext
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index b1fb15ce80..e5a8080dc3 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -21,7 +21,7 @@ if ($ENV{with_ssl} ne 'openssl')
 }
 else
 {
-	plan tests => 110;
+	plan tests => 135;
 }
 
 #### Some configuration
@@ -253,6 +253,23 @@ $node->connect_fails(
 	  qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/
 );
 
+# Test with an IP address in the Common Name. This is a strange corner case that
+# nevertheless is supported, as long as the address string matches exactly.
+switch_server_cert($node, 'server-ip-cn-only');
+
+$common_connstr =
+  "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+
+$node->connect_ok("$common_connstr host=192.0.2.1",
+	"IP address in the Common Name");
+
+$node->connect_fails(
+	"$common_connstr host=192.000.002.001",
+	"mismatch between host name and server certificate IP address",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.1" does not match host name "192.000.002.001"\E/
+);
+
 # Test Subject Alternative Names.
 switch_server_cert($node, 'server-multiple-alt-names');
 
@@ -305,7 +322,54 @@ $node->connect_fails(
 	  qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/
 );
 
-# Test server certificate with a CN and SANs. Per RFCs 2818 and 6125, the CN
+# Test certificate with IP addresses in the SANs.
+switch_server_cert($node, 'server-ip-alt-names');
+
+$node->connect_ok(
+	"$common_connstr host=192.0.2.1",
+	"host matching an IPv4 address (Subject Alternative Name 1)");
+
+$node->connect_ok(
+	"$common_connstr host=192.000.002.001",
+	"host matching an IPv4 address in alternate form (Subject Alternative Name 1)");
+
+$node->connect_fails(
+	"$common_connstr host=192.0.2.2",
+	"host not matching an IPv4 address (Subject Alternative Name 1)",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.1" (and 1 other name) does not match host name "192.0.2.2"\E/
+);
+
+$node->connect_fails(
+	"$common_connstr host=192.0.2.1/32",
+	"IPv4 host with CIDR mask does not match",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.1" (and 1 other name) does not match host name "192.0.2.1\/32"\E/
+);
+
+$node->connect_ok(
+	"$common_connstr host=2001:DB8::1",
+	"host matching an IPv6 address (Subject Alternative Name 2)");
+
+$node->connect_ok(
+	"$common_connstr host=2001:db8:0:0:0:0:0:1",
+	"host matching an IPv6 address in alternate form (Subject Alternative Name 2)");
+
+$node->connect_fails(
+	"$common_connstr host=::1",
+	"host not matching an IPv6 address (Subject Alternative Name 2)",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.1" (and 1 other name) does not match host name "::1"\E/
+);
+
+$node->connect_fails(
+	"$common_connstr host=2001:DB8::1/128",
+	"IPv6 host with CIDR mask does not match",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.1" (and 1 other name) does not match host name "2001:DB8::1\/128"\E/
+);
+
+# Test server certificate with a CN and DNS SANs. Per RFCs 2818 and 6125, the CN
 # should be ignored when the certificate has both.
 switch_server_cert($node, 'server-cn-and-alt-names');
 
@@ -323,6 +387,39 @@ $node->connect_fails(
 	  qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/
 );
 
+# But we will fall back to check the CN if the SANs contain only IP addresses.
+switch_server_cert($node, 'server-cn-and-ip-alt-names');
+
+$node->connect_ok("$common_connstr host=common-name.pg-ssltest.test",
+	"certificate with both a CN and IP SANs matches CN");
+$node->connect_ok("$common_connstr host=192.0.2.1",
+	"certificate with both a CN and IP SANs matches SAN 1");
+$node->connect_ok("$common_connstr host=2001:db8::1",
+	"certificate with both a CN and IP SANs matches SAN 2");
+
+# And now the same tests, but with IP addresses and DNS names swapped.
+switch_server_cert($node, 'server-ip-cn-and-alt-names');
+
+$node->connect_ok("$common_connstr host=192.0.2.2",
+	"certificate with both an IP CN and IP SANs 1");
+$node->connect_ok("$common_connstr host=2001:db8::1",
+	"certificate with both an IP CN and IP SANs 2");
+$node->connect_fails(
+	"$common_connstr host=192.0.2.1",
+	"certificate with both an IP CN and IP SANs ignores CN",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.2" (and 1 other name) does not match host name "192.0.2.1"\E/
+);
+
+switch_server_cert($node, 'server-ip-cn-and-dns-alt-names');
+
+$node->connect_ok("$common_connstr host=192.0.2.1",
+	"certificate with both an IP CN and DNS SANs matches CN");
+$node->connect_ok("$common_connstr host=dns1.alt-name.pg-ssltest.test",
+	"certificate with both an IP CN and DNS SANs matches SAN 1");
+$node->connect_ok("$common_connstr host=dns2.alt-name.pg-ssltest.test",
+	"certificate with both an IP CN and DNS SANs matches SAN 2");
+
 # Finally, test a server certificate that has no CN or SANs. Of course, that's
 # not a very sensible certificate, but libpq should handle it gracefully.
 switch_server_cert($node, 'server-no-names');
-- 
2.25.1

v5-0003-squash-libpq-allow-IP-address-SANs-in-server-cert.patchtext/x-patch; name=v5-0003-squash-libpq-allow-IP-address-SANs-in-server-cert.patchDownload
From 6b4080e0db528a863feacf9c3cb89ca81948ef9c Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Wed, 5 Jan 2022 15:47:03 -0800
Subject: [PATCH v5 3/3] squash! libpq: allow IP address SANs in server certs

Per review, provide a pg_inet_pton() interface for IPv6 and refactor the
internals accordingly. IPv4 is not yet implemented. The call sites that
just want standard addresses without a CIDR mask have been updated to
use the new function.

Internally, the IPv6 parser now takes an allow_cidr boolean. I've
plumbed that all the way down to getbits() in order to minimize the
amount of code touched, at the expense of an unnecessary function call
in the failing case.
---
 src/include/port.h                       |  1 +
 src/interfaces/libpq/fe-secure-common.c  |  7 +--
 src/interfaces/libpq/fe-secure-openssl.c |  2 +-
 src/port/inet_net_pton.c                 | 66 ++++++++++++++++++------
 4 files changed, 52 insertions(+), 24 deletions(-)

diff --git a/src/include/port.h b/src/include/port.h
index 2852e5b58b..fd046f4c24 100644
--- a/src/include/port.h
+++ b/src/include/port.h
@@ -517,6 +517,7 @@ extern char *pg_inet_net_ntop(int af, const void *src, int bits,
 
 /* port/inet_net_pton.c */
 extern int	pg_inet_net_pton(int af, const char *src, void *dst, size_t size);
+extern int	pg_inet_pton(int af, const char *src, void *dst);
 
 /* port/pg_strong_random.c */
 extern void pg_strong_random_init(void);
diff --git a/src/interfaces/libpq/fe-secure-common.c b/src/interfaces/libpq/fe-secure-common.c
index 2b8f2d6b24..4d78715756 100644
--- a/src/interfaces/libpq/fe-secure-common.c
+++ b/src/interfaces/libpq/fe-secure-common.c
@@ -211,12 +211,7 @@ pq_verify_peer_name_matches_certificate_ip(PGconn *conn,
 
 		family = PGSQL_AF_INET6;
 
-		/*
-		 * pg_inet_net_pton() will accept CIDR masks, which we don't want to
-		 * match, so skip the comparison if the host string contains a slash.
-		 */
-		if (!strchr(host, '/')
-			&& pg_inet_net_pton(PGSQL_AF_INET6, host, addr, -1) == 128)
+		if (pg_inet_pton(PGSQL_AF_INET6, host, addr) == 1)
 		{
 			if (memcmp(ipdata, addr, iplen) == 0)
 				match = 1;
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index 55bd55ae6e..00e297db17 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -554,7 +554,7 @@ is_ip_address(const char *host)
 	unsigned char	dummy6[16];
 
 	return inet_aton(host, &dummy4)
-		|| (pg_inet_net_pton(PGSQL_AF_INET6, host, dummy6, -1) == 128);
+		|| (pg_inet_pton(PGSQL_AF_INET6, host, dummy6) == 1);
 }
 
 /*
diff --git a/src/port/inet_net_pton.c b/src/port/inet_net_pton.c
index 5bae811c6f..ae0d1fd2e3 100644
--- a/src/port/inet_net_pton.c
+++ b/src/port/inet_net_pton.c
@@ -42,8 +42,8 @@ static const char rcsid[] = "Id: inet_net_pton.c,v 1.4.2.3 2004/03/17 00:40:11 m
 
 static int	inet_net_pton_ipv4(const char *src, u_char *dst);
 static int	inet_cidr_pton_ipv4(const char *src, u_char *dst, size_t size);
-static int	inet_net_pton_ipv6(const char *src, u_char *dst);
-static int	inet_cidr_pton_ipv6(const char *src, u_char *dst, size_t size);
+static int	inet_pton_ipv6_internal(const char *src, u_char *dst, size_t size,
+									bool allow_cidr);
 
 
 /*
@@ -74,9 +74,9 @@ pg_inet_net_pton(int af, const char *src, void *dst, size_t size)
 				inet_net_pton_ipv4(src, dst) :
 				inet_cidr_pton_ipv4(src, dst, size);
 		case PGSQL_AF_INET6:
-			return size == -1 ?
-				inet_net_pton_ipv6(src, dst) :
-				inet_cidr_pton_ipv6(src, dst, size);
+			return inet_pton_ipv6_internal(src, dst,
+										   (size == -1) ? 16 : size,
+										   true /* allow CIDR */);
 		default:
 			errno = EAFNOSUPPORT;
 			return -1;
@@ -352,13 +352,22 @@ emsgsize:
 }
 
 static int
-getbits(const char *src, int *bitsp)
+getbits(const char *src, int *bitsp, bool allow_cidr)
 {
 	static const char digits[] = "0123456789";
 	int			n;
 	int			val;
 	char		ch;
 
+	if (!allow_cidr)
+	{
+		/*
+		 * If we got here, the top-level call is to pg_inet_pton() and the
+		 * caller supplied a CIDR mask, which isn't allowed.
+		 */
+		return 0;
+	}
+
 	val = 0;
 	n = 0;
 	while ((ch = *src++) != '\0')
@@ -385,7 +394,7 @@ getbits(const char *src, int *bitsp)
 }
 
 static int
-getv4(const char *src, u_char *dst, int *bitsp)
+getv4(const char *src, u_char *dst, int *bitsp, bool allow_cidr)
 {
 	static const char digits[] = "0123456789";
 	u_char	   *odst = dst;
@@ -416,7 +425,7 @@ getv4(const char *src, u_char *dst, int *bitsp)
 				return 0;
 			*dst++ = val;
 			if (ch == '/')
-				return getbits(src, bitsp);
+				return getbits(src, bitsp, allow_cidr);
 			val = 0;
 			n = 0;
 			continue;
@@ -431,18 +440,12 @@ getv4(const char *src, u_char *dst, int *bitsp)
 	return 1;
 }
 
-static int
-inet_net_pton_ipv6(const char *src, u_char *dst)
-{
-	return inet_cidr_pton_ipv6(src, dst, 16);
-}
-
 #define NS_IN6ADDRSZ 16
 #define NS_INT16SZ 2
 #define NS_INADDRSZ 4
 
 static int
-inet_cidr_pton_ipv6(const char *src, u_char *dst, size_t size)
+inet_pton_ipv6_internal(const char *src, u_char *dst, size_t size, bool allow_cidr)
 {
 	static const char xdigits_l[] = "0123456789abcdef",
 				xdigits_u[] = "0123456789ABCDEF";
@@ -510,13 +513,13 @@ inet_cidr_pton_ipv6(const char *src, u_char *dst, size_t size)
 			continue;
 		}
 		if (ch == '.' && ((tp + NS_INADDRSZ) <= endp) &&
-			getv4(curtok, tp, &bits) > 0)
+			getv4(curtok, tp, &bits, allow_cidr) > 0)
 		{
 			tp += NS_INADDRSZ;
 			saw_xdigit = 0;
 			break;				/* '\0' was seen by inet_pton4(). */
 		}
-		if (ch == '/' && getbits(src, &bits) > 0)
+		if (ch == '/' && getbits(src, &bits, allow_cidr) > 0)
 			break;
 		goto enoent;
 	}
@@ -530,6 +533,9 @@ inet_cidr_pton_ipv6(const char *src, u_char *dst, size_t size)
 	if (bits == -1)
 		bits = 128;
 
+	/* Without a CIDR mask, the above should guarantee a complete address. */
+	Assert(allow_cidr || bits == 128);
+
 	endp = tmp + 16;
 
 	if (colonp != NULL)
@@ -568,3 +574,29 @@ emsgsize:
 	errno = EMSGSIZE;
 	return -1;
 }
+
+/*
+ * Converts a network address in presentation format to network format. The main
+ * difference between this and pg_inet_net_pton() above is that CIDR notation is
+ * not allowed.
+ *
+ * The memory pointed to by dst depends on the address family:
+ *
+ *     PGSQL_AF_INET:  not implemented yet (returns -1 with EAFNOSUPPORT)
+ *     PGSQL_AF_INET6: *dst must be a u_char[16]
+ *
+ * Over and above the standard inet_pton() functionality, we also set errno on
+ * parse failure, with the same meanings as with pg_inet_net_pton().
+ */
+int
+pg_inet_pton(int af, const char *src, void *dst)
+{
+	if (af != PGSQL_AF_INET6)
+	{
+		/* Currently only IPv6 is implemented. */
+		errno = EAFNOSUPPORT;
+		return -1;
+	}
+
+	return (inet_pton_ipv6_internal(src, dst, 16, false /* no CIDR */) == 128);
+}
-- 
2.25.1

#19Kyotaro Horiguchi
horikyota.ntt@gmail.com
In reply to: Jacob Champion (#18)
Re: [PATCH] Accept IP addresses in server certificate SANs

(This needs rebasing)

At Wed, 9 Feb 2022 00:52:48 +0000, Jacob Champion <pchampion@vmware.com> wrote in

On Mon, 2022-02-07 at 17:29 +0900, Kyotaro Horiguchi wrote:

I feel this should be a part of 0001. (But the patches will be
finally merged so maybe no need to bother moving it).

Okay. I can move it easily if you feel like it would help review, but
for now I've kept it in 0002.

Thanks.

So, inet_aton there seems to be the right choice but the comment
doesn't describe the reason for that behavior. I think we should add
an explanation about the reason for the behavior, maybe something like
this:

We accept alternative IPv4 address notations that are accepted by
inet_aton but not by inet_pton as server address.

I've pulled this wording into the comment in v5, attached.

+                       if (name->type == host_type)
+                               check_cn = false;

Don't we want a concise coment for this?

Added one; see what you think.

That's fine with me.

if (rc != 0)
+                     {
+                             /*
+                              * don't fall back to CN when we have a match or have an error
+                              */
+                             check_cn = false;
break;
+                     }

...

-     if ((rc == 0) && check_cn)
+     if (check_cn)

If I understand right, that's not quite equivalent (and the new tests
fail if I implement it that way). We have to disable fallback if the
SAN exists, whether it matches or not.

# I forgot to mention that, the test fails for me even without the
# change. I didn't checked what is wrong there, though.

Mmm. after the end of the loop, rc is non-zero only when the loop has
exited by the break and otherwise rc is zero. Why isn't it equivalent
to setting check_cn to false at the break?

Anyway, apart from that detail, I reconfirmed the spec the patch is
going to implement.

* If connhost contains a DNS name, and the certificate's SANs contain any
* dNSName entries, then we'll ignore the Subject Common Name entirely;
* otherwise, we fall back to checking the CN. (This behavior matches the
* RFC.)

Sure.

* If connhost contains an IP address, and the SANs contain iPAddress
* entries, we again ignore the CN. Otherwise, we allow the CN to match,
* EVEN IF there is a dNSName in the SANs. (RFC 6125 prohibits this: "A
* client MUST NOT seek a match for a reference identifier of CN-ID if the
* presented identifiers include a DNS-ID, SRV-ID, URI-ID, or any
* application-specific identifier types supported by the client.")

Actually the patch searches for a match of IP address connhost from
dNSName SANs even if iPAddress SANs exist. I think we've not
explicitly defined thebehavior in that case. I supposed that we only
be deviant in the case "IP address connhost and no SANs of any type
exists". What do you think about it?

- For the certificate that have only dNSNames or no SANs presented, we
serach for a match from all dNSNames if any or otherwise try CN
regardless of the type of connhost.

- Otherwise (the cert has at least one iPAddress SANs) we follow the RFCs.

- For IP-addr connhost, we search only the iPAddress SANs.

- For DNSName connhost, we search only dNSName SANs if any or
otherwise try CN.

Honestly I didn't consider to that detail. On second thought, with
this specification we cannot decide the behavior unless we scanned all
SANs. Maybe we can find an elegant implement but I don't think people
here would welcome even that level of complexity needed only for that
dubious existing use case.

What do you think about this? And I'd like to hear from others.

Great catch, thanks! I implemented option 2 to start. Option 1 might
make things difficult to debug if you're connecting to a server by IP
address but its certificate only has DNS names.

Looks fine. Thanks!

regards.

--
Kyotaro Horiguchi
NTT Open Source Software Center

#20Jacob Champion
pchampion@vmware.com
In reply to: Kyotaro Horiguchi (#19)
3 attachment(s)
Re: [PATCH] Accept IP addresses in server certificate SANs

On Tue, 2022-02-15 at 15:16 +0900, Kyotaro Horiguchi wrote:

(This needs rebasing)

Done in v6, attached.

# I forgot to mention that, the test fails for me even without the
# change. I didn't checked what is wrong there, though.

Ah. We should probably figure that out, then -- what failures do you
see?

Mmm. after the end of the loop, rc is non-zero only when the loop has
exited by the break and otherwise rc is zero. Why isn't it equivalent
to setting check_cn to false at the break?

check_cn can be false if rc is zero, too; it means that we found a SAN
of the correct type but it didn't match.

Anyway, apart from that detail, I reconfirmed the spec the patch is
going to implement.

* If connhost contains a DNS name, and the certificate's SANs contain any
* dNSName entries, then we'll ignore the Subject Common Name entirely;
* otherwise, we fall back to checking the CN. (This behavior matches the
* RFC.)

Sure.

* If connhost contains an IP address, and the SANs contain iPAddress
* entries, we again ignore the CN. Otherwise, we allow the CN to match,
* EVEN IF there is a dNSName in the SANs. (RFC 6125 prohibits this: "A
* client MUST NOT seek a match for a reference identifier of CN-ID if the
* presented identifiers include a DNS-ID, SRV-ID, URI-ID, or any
* application-specific identifier types supported by the client.")

Actually the patch searches for a match of IP address connhost from
dNSName SANs even if iPAddress SANs exist. I think we've not
explicitly defined thebehavior in that case.

That's a good point; I didn't change the prior behavior. I feel more
comfortable leaving that check, since it is technically possible to
push something that looks like an IP address into a dNSName SAN. We
should probably make an explicit decision on that, as you say.

But I don't think that contradicts the code comment, does it? The
comment is just talking about CN fallback scenarios. If you find a
match in a dNSName, there's no reason to fall back to the CN.

I supposed that we only
be deviant in the case "IP address connhost and no SANs of any type
exists". What do you think about it?

We fall back in the case of "IP address connhost and dNSName SANs
exist", which is prohibited by that part of RFC 6125. I don't think we
deviate in the case you described; can you explain further?

- For the certificate that have only dNSNames or no SANs presented, we
serach for a match from all dNSNames if any or otherwise try CN
regardless of the type of connhost.

Correct. (I don't find that way of dividing up the cases very
intuitive, though.)

- Otherwise (the cert has at least one iPAddress SANs) we follow the RFCs.

- For IP-addr connhost, we search only the iPAddress SANs.

We search the dNSNames as well, as you pointed out above. But we don't
fall back to the CN.

- For DNSName connhost, we search only dNSName SANs if any or
otherwise try CN.

Effectively, yes. (We call the IP address verification functions too,
to get alt_name, but they can't match. If that's too confusing, we'd
need to pull the alt_name handling up out of the verification layer.)

Honestly I didn't consider to that detail. On second thought, with
this specification we cannot decide the behavior unless we scanned all
SANs.

Right.

Maybe we can find an elegant implement but I don't think people
here would welcome even that level of complexity needed only for that
dubious existing use case.

Which use case do you mean?

What do you think about this? And I'd like to hear from others.

I think we need to decide whether or not to keep the current "IP
address connhost can match a dNSName SAN" behavior, and if so I need to
add it to the test cases. (And we need to figure out why the tests are
failing in your build, of course.)

Thanks!
--Jacob

Attachments:

v6-0001-Move-inet_net_pton-to-src-port.patchtext/x-patch; name=v6-0001-Move-inet_net_pton-to-src-port.patchDownload
From fa93bc48654164d36fc163ee84d8f9b78fe29e96 Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Wed, 24 Nov 2021 14:46:11 -0800
Subject: [PATCH v6 1/3] Move inet_net_pton() to src/port

This will be helpful for IP address verification in libpq.
---
 src/backend/utils/adt/Makefile                  |  1 -
 src/include/port.h                              |  3 +++
 src/include/utils/builtins.h                    |  4 ----
 src/port/Makefile                               |  1 +
 src/{backend/utils/adt => port}/inet_net_pton.c | 16 +++++++++++++++-
 src/tools/msvc/Mkvcbuild.pm                     |  2 +-
 6 files changed, 20 insertions(+), 7 deletions(-)
 rename src/{backend/utils/adt => port}/inet_net_pton.c (96%)

diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile
index 41b486bcef..d173d52157 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -43,7 +43,6 @@ OBJS = \
 	geo_selfuncs.o \
 	geo_spgist.o \
 	inet_cidr_ntop.o \
-	inet_net_pton.o \
 	int.o \
 	int8.o \
 	json.o \
diff --git a/src/include/port.h b/src/include/port.h
index 3d103a2b31..2852e5b58b 100644
--- a/src/include/port.h
+++ b/src/include/port.h
@@ -515,6 +515,9 @@ extern int	pg_codepage_to_encoding(UINT cp);
 extern char *pg_inet_net_ntop(int af, const void *src, int bits,
 							  char *dst, size_t size);
 
+/* port/inet_net_pton.c */
+extern int	pg_inet_net_pton(int af, const char *src, void *dst, size_t size);
+
 /* port/pg_strong_random.c */
 extern void pg_strong_random_init(void);
 extern bool pg_strong_random(void *buf, size_t len);
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 666e545496..67b9326dc8 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -93,10 +93,6 @@ extern int	xidLogicalComparator(const void *arg1, const void *arg2);
 extern char *pg_inet_cidr_ntop(int af, const void *src, int bits,
 							   char *dst, size_t size);
 
-/* inet_net_pton.c */
-extern int	pg_inet_net_pton(int af, const char *src,
-							 void *dst, size_t size);
-
 /* network.c */
 extern double convert_network_to_scalar(Datum value, Oid typid, bool *failure);
 extern Datum network_scan_first(Datum in);
diff --git a/src/port/Makefile b/src/port/Makefile
index bfe1feb0d4..c3ae7b3d5c 100644
--- a/src/port/Makefile
+++ b/src/port/Makefile
@@ -43,6 +43,7 @@ OBJS = \
 	bsearch_arg.o \
 	chklocale.o \
 	inet_net_ntop.o \
+	inet_net_pton.o \
 	noblock.o \
 	path.o \
 	pg_bitutils.o \
diff --git a/src/backend/utils/adt/inet_net_pton.c b/src/port/inet_net_pton.c
similarity index 96%
rename from src/backend/utils/adt/inet_net_pton.c
rename to src/port/inet_net_pton.c
index d3221a1313..bae50ba67e 100644
--- a/src/backend/utils/adt/inet_net_pton.c
+++ b/src/port/inet_net_pton.c
@@ -14,14 +14,18 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
  * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  *
- *	  src/backend/utils/adt/inet_net_pton.c
+ *	  src/port/inet_net_pton.c
  */
 
 #if defined(LIBC_SCCS) && !defined(lint)
 static const char rcsid[] = "Id: inet_net_pton.c,v 1.4.2.3 2004/03/17 00:40:11 marka Exp $";
 #endif
 
+#ifndef FRONTEND
 #include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
 
 #include <sys/socket.h>
 #include <netinet/in.h>
@@ -29,9 +33,19 @@ static const char rcsid[] = "Id: inet_net_pton.c,v 1.4.2.3 2004/03/17 00:40:11 m
 #include <assert.h>
 #include <ctype.h>
 
+#ifndef FRONTEND
 #include "utils/builtins.h" /* pgrminclude ignore */	/* needed on some
 														 * platforms */
 #include "utils/inet.h"
+#else
+/*
+ * In a frontend build, we can't include inet.h, but we still need to have
+ * sensible definitions of these two constants.  Note that pg_inet_net_ntop()
+ * assumes that PGSQL_AF_INET is equal to AF_INET.
+ */
+#define PGSQL_AF_INET	(AF_INET + 0)
+#define PGSQL_AF_INET6	(AF_INET + 1)
+#endif
 
 
 static int	inet_net_pton_ipv4(const char *src, u_char *dst);
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 105f5c72a2..db2f7afd24 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -100,7 +100,7 @@ sub mkvcbuild
 
 	our @pgportfiles = qw(
 	  chklocale.c explicit_bzero.c fls.c getpeereid.c getrusage.c inet_aton.c
-	  getaddrinfo.c gettimeofday.c inet_net_ntop.c kill.c open.c
+	  getaddrinfo.c gettimeofday.c inet_net_ntop.c inet_net_pton.c kill.c open.c
 	  snprintf.c strlcat.c strlcpy.c dirmod.c noblock.c path.c
 	  dirent.c dlopen.c getopt.c getopt_long.c link.c
 	  pread.c preadv.c pwrite.c pwritev.c pg_bitutils.c
-- 
2.25.1

v6-0002-libpq-allow-IP-address-SANs-in-server-certs.patchtext/x-patch; name=v6-0002-libpq-allow-IP-address-SANs-in-server-certs.patchDownload
From dc5682d7cff0fe5d8eff6ff936ed4738f33e251f Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Thu, 18 Nov 2021 15:36:18 -0800
Subject: [PATCH v6 2/3] libpq: allow IP address SANs in server certs

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.

- Unlike NSS, we don't map IPv4 to IPv6 addresses, or vice-versa.

- Move PGSQL_AF_INET* to inet-common.h to reduce copy-paste.
---
 src/include/common/inet-common.h              |  35 +++++
 src/include/utils/inet.h                      |  13 +-
 src/interfaces/libpq/fe-secure-common.c       | 104 ++++++++++++++
 src/interfaces/libpq/fe-secure-common.h       |   5 +
 src/interfaces/libpq/fe-secure-openssl.c      | 131 ++++++++++++++++--
 src/port/inet_net_ntop.c                      |  12 +-
 src/port/inet_net_pton.c                      |  10 +-
 .../conf/server-cn-and-ip-alt-names.config    |  24 ++++
 src/test/ssl/conf/server-ip-alt-names.config  |  19 +++
 .../conf/server-ip-cn-and-alt-names.config    |  21 +++
 .../server-ip-cn-and-dns-alt-names.config     |  21 +++
 src/test/ssl/conf/server-ip-cn-only.config    |  12 ++
 .../ssl/ssl/server-cn-and-ip-alt-names.crt    |  20 +++
 .../ssl/ssl/server-cn-and-ip-alt-names.key    |  27 ++++
 src/test/ssl/ssl/server-ip-alt-names.crt      |  19 +++
 src/test/ssl/ssl/server-ip-alt-names.key      |  27 ++++
 .../ssl/ssl/server-ip-cn-and-alt-names.crt    |  19 +++
 .../ssl/ssl/server-ip-cn-and-alt-names.key    |  27 ++++
 .../ssl/server-ip-cn-and-dns-alt-names.crt    |  20 +++
 .../ssl/server-ip-cn-and-dns-alt-names.key    |  27 ++++
 src/test/ssl/ssl/server-ip-cn-only.crt        |  18 +++
 src/test/ssl/ssl/server-ip-cn-only.key        |  27 ++++
 src/test/ssl/sslfiles.mk                      |   5 +
 src/test/ssl/t/001_ssltests.pl                |  99 ++++++++++++-
 24 files changed, 696 insertions(+), 46 deletions(-)
 create mode 100644 src/include/common/inet-common.h
 create mode 100644 src/test/ssl/conf/server-cn-and-ip-alt-names.config
 create mode 100644 src/test/ssl/conf/server-ip-alt-names.config
 create mode 100644 src/test/ssl/conf/server-ip-cn-and-alt-names.config
 create mode 100644 src/test/ssl/conf/server-ip-cn-and-dns-alt-names.config
 create mode 100644 src/test/ssl/conf/server-ip-cn-only.config
 create mode 100644 src/test/ssl/ssl/server-cn-and-ip-alt-names.crt
 create mode 100644 src/test/ssl/ssl/server-cn-and-ip-alt-names.key
 create mode 100644 src/test/ssl/ssl/server-ip-alt-names.crt
 create mode 100644 src/test/ssl/ssl/server-ip-alt-names.key
 create mode 100644 src/test/ssl/ssl/server-ip-cn-and-alt-names.crt
 create mode 100644 src/test/ssl/ssl/server-ip-cn-and-alt-names.key
 create mode 100644 src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.crt
 create mode 100644 src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.key
 create mode 100644 src/test/ssl/ssl/server-ip-cn-only.crt
 create mode 100644 src/test/ssl/ssl/server-ip-cn-only.key

diff --git a/src/include/common/inet-common.h b/src/include/common/inet-common.h
new file mode 100644
index 0000000000..3ad72261b6
--- /dev/null
+++ b/src/include/common/inet-common.h
@@ -0,0 +1,35 @@
+/*-------------------------------------------------------------------------
+ *
+ * inet-common.h
+ *
+ *	  Common code for clients of the inet_* APIs.
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/common/inet-common.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef INET_COMMON_H
+#define INET_COMMON_H
+
+/*
+ * We use these values for the "family" field of inet_struct.
+ *
+ * Referencing all of the non-AF_INET types to AF_INET lets us work on
+ * machines which may not have the appropriate address family (like
+ * inet6 addresses when AF_INET6 isn't present) but doesn't cause a
+ * dump/reload requirement.  Pre-7.4 databases used AF_INET for the family
+ * type on disk.
+ *
+ * In a frontend build, we can't include inet.h, but we still need to have
+ * sensible definitions of these two constants.  (Frontend clients should
+ * include this header directly.)  Note that pg_inet_net_ntop()
+ * assumes that PGSQL_AF_INET is equal to AF_INET.
+ */
+#define PGSQL_AF_INET	(AF_INET + 0)
+#define PGSQL_AF_INET6	(AF_INET + 1)
+
+#endif							/* INET_COMMON_H */
diff --git a/src/include/utils/inet.h b/src/include/utils/inet.h
index 3073c0307e..c56c016987 100644
--- a/src/include/utils/inet.h
+++ b/src/include/utils/inet.h
@@ -14,6 +14,7 @@
 #ifndef INET_H
 #define INET_H
 
+#include "common/inet-common.h"
 #include "fmgr.h"
 
 /*
@@ -27,18 +28,6 @@ typedef struct
 	unsigned char ipaddr[16];	/* up to 128 bits of address */
 } inet_struct;
 
-/*
- * We use these values for the "family" field.
- *
- * Referencing all of the non-AF_INET types to AF_INET lets us work on
- * machines which may not have the appropriate address family (like
- * inet6 addresses when AF_INET6 isn't present) but doesn't cause a
- * dump/reload requirement.  Pre-7.4 databases used AF_INET for the family
- * type on disk.
- */
-#define PGSQL_AF_INET	(AF_INET + 0)
-#define PGSQL_AF_INET6	(AF_INET + 1)
-
 /*
  * Both INET and CIDR addresses are represented within Postgres as varlena
  * objects, ie, there is a varlena header in front of the struct type
diff --git a/src/interfaces/libpq/fe-secure-common.c b/src/interfaces/libpq/fe-secure-common.c
index bd46f08fae..2b8f2d6b24 100644
--- a/src/interfaces/libpq/fe-secure-common.c
+++ b/src/interfaces/libpq/fe-secure-common.c
@@ -19,9 +19,13 @@
 
 #include "postgres_fe.h"
 
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
 #include "fe-secure-common.h"
 
 #include "libpq-int.h"
+#include "port.h"
 #include "pqexpbuffer.h"
 
 /*
@@ -144,6 +148,106 @@ pq_verify_peer_name_matches_certificate_name(PGconn *conn,
 	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'))
+	{
+		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 = PGSQL_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;
+		}
+	}
+	else if (iplen == 16)
+	{
+		/* IPv6 */
+		unsigned char addr[16];
+
+		family = PGSQL_AF_INET6;
+
+		/*
+		 * pg_inet_net_pton() will accept CIDR masks, which we don't want to
+		 * match, so skip the comparison if the host string contains a slash.
+		 */
+		if (!strchr(host, '/')
+			&& pg_inet_net_pton(PGSQL_AF_INET6, host, addr, -1) == 128)
+		{
+			if (memcmp(ipdata, addr, iplen) == 0)
+				match = 1;
+		}
+	}
+	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.
  *
diff --git a/src/interfaces/libpq/fe-secure-common.h b/src/interfaces/libpq/fe-secure-common.h
index 1cca6d785a..20ff9ba5db 100644
--- a/src/interfaces/libpq/fe-secure-common.h
+++ b/src/interfaces/libpq/fe-secure-common.h
@@ -16,11 +16,16 @@
 #ifndef FE_SECURE_COMMON_H
 #define FE_SECURE_COMMON_H
 
+#include "common/inet-common.h"
 #include "libpq-fe.h"
 
 extern int	pq_verify_peer_name_matches_certificate_name(PGconn *conn,
 														 const char *namedata, size_t namelen,
 														 char **store_name);
+extern int	pq_verify_peer_name_matches_certificate_ip(PGconn *conn,
+													   const unsigned char *addrdata,
+													   size_t addrlen,
+													   char **store_name);
 extern bool pq_verify_peer_name_matches_certificate(PGconn *conn);
 
 #endif							/* FE_SECURE_COMMON_H */
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index f6e563a2e5..108def3c1b 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -72,6 +72,9 @@ static int	verify_cb(int ok, X509_STORE_CTX *ctx);
 static int	openssl_verify_peer_name_matches_certificate_name(PGconn *conn,
 															  ASN1_STRING *name,
 															  char **store_name);
+static int	openssl_verify_peer_name_matches_certificate_ip(PGconn *conn,
+															ASN1_OCTET_STRING *addr_entry,
+															char **store_name);
 static void destroy_ssl_system(void);
 static int	initialize_SSL(PGconn *conn);
 static PostgresPollingStatusType open_client_SSL(PGconn *);
@@ -509,6 +512,51 @@ openssl_verify_peer_name_matches_certificate_name(PGconn *conn, ASN1_STRING *nam
 	return pq_verify_peer_name_matches_certificate_name(conn, (const char *) namedata, len, store_name);
 }
 
+/*
+ * OpenSSL-specific wrapper around
+ * pq_verify_peer_name_matches_certificate_ip(), converting the
+ * ASN1_OCTET_STRING into a plain C string.
+ */
+static int
+openssl_verify_peer_name_matches_certificate_ip(PGconn *conn,
+												ASN1_OCTET_STRING *addr_entry,
+												char **store_name)
+{
+	int			len;
+	const unsigned char *addrdata;
+
+	/* Should not happen... */
+	if (addr_entry == NULL)
+	{
+		appendPQExpBufferStr(&conn->errorMessage,
+							 libpq_gettext("SSL certificate's address entry is missing\n"));
+		return -1;
+	}
+
+	/*
+	 * GEN_IPADD is an OCTET STRING containing an IP address in network byte
+	 * order.
+	 */
+#ifdef HAVE_ASN1_STRING_GET0_DATA
+	addrdata = ASN1_STRING_get0_data(addr_entry);
+#else
+	addrdata = ASN1_STRING_data(addr_entry);
+#endif
+	len = ASN1_STRING_length(addr_entry);
+
+	return pq_verify_peer_name_matches_certificate_ip(conn, addrdata, len, store_name);
+}
+
+static bool
+is_ip_address(const char *host)
+{
+	struct in_addr	dummy4;
+	unsigned char	dummy6[16];
+
+	return inet_aton(host, &dummy4)
+		|| (pg_inet_net_pton(PGSQL_AF_INET6, host, dummy6, -1) == 128);
+}
+
 /*
  *	Verify that the server certificate matches the hostname we connected to.
  *
@@ -522,6 +570,36 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 	STACK_OF(GENERAL_NAME) * peer_san;
 	int			i;
 	int			rc = 0;
+	char	   *host = conn->connhost[conn->whichhost].host;
+	int			host_type;
+	bool		check_cn = true;
+
+	Assert(host && host[0]); /* should be guaranteed by caller */
+
+	/*
+	 * We try to match the NSS behavior here, which is a slight departure from
+	 * the spec but seems to make more intuitive sense:
+	 *
+	 * If connhost contains a DNS name, and the certificate's SANs contain any
+	 * dNSName entries, then we'll ignore the Subject Common Name entirely;
+	 * otherwise, we fall back to checking the CN. (This behavior matches the
+	 * RFC.)
+	 *
+	 * If connhost contains an IP address, and the SANs contain iPAddress
+	 * entries, we again ignore the CN. Otherwise, we allow the CN to match,
+	 * EVEN IF there is a dNSName in the SANs. (RFC 6125 prohibits this: "A
+	 * client MUST NOT seek a match for a reference identifier of CN-ID if the
+	 * presented identifiers include a DNS-ID, SRV-ID, URI-ID, or any
+	 * application-specific identifier types supported by the client.")
+	 *
+	 * NOTE: Prior versions of libpq did not consider iPAddress entries at all,
+	 * so this new behavior might break a certificate that has different IP
+	 * addresses in the Subject CN and the SANs.
+	 */
+	if (is_ip_address(host))
+		host_type = GEN_IPADD;
+	else
+		host_type = GEN_DNS;
 
 	/*
 	 * First, get the Subject Alternative Names (SANs) from the certificate,
@@ -537,24 +615,40 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 		for (i = 0; i < san_len; i++)
 		{
 			const GENERAL_NAME *name = sk_GENERAL_NAME_value(peer_san, i);
+			char	   *alt_name = NULL;
 
-			if (name->type == GEN_DNS)
+			if (name->type == host_type)
 			{
-				char	   *alt_name;
+				/*
+				 * This SAN is of the same type (IP or DNS) as our host name, so
+				 * don't allow a fallback check of the CN.
+				 */
+				check_cn = false;
+			}
 
+			if (name->type == GEN_DNS)
+			{
 				(*names_examined)++;
 				rc = openssl_verify_peer_name_matches_certificate_name(conn,
 																	   name->d.dNSName,
 																	   &alt_name);
+			}
+			else if (name->type == GEN_IPADD)
+			{
+				(*names_examined)++;
+				rc = openssl_verify_peer_name_matches_certificate_ip(conn,
+																	 name->d.iPAddress,
+																	 &alt_name);
+			}
 
-				if (alt_name)
-				{
-					if (!*first_name)
-						*first_name = alt_name;
-					else
-						free(alt_name);
-				}
+			if (alt_name)
+			{
+				if (!*first_name)
+					*first_name = alt_name;
+				else
+					free(alt_name);
 			}
+
 			if (rc != 0)
 				break;
 		}
@@ -562,13 +656,14 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 	}
 
 	/*
-	 * If there is no subjectAltName extension of type dNSName, check the
+	 * If there is no subjectAltName extension of the matching type, check the
 	 * Common Name.
 	 *
 	 * (Per RFC 2818 and RFC 6125, if the subjectAltName extension of type
-	 * dNSName is present, the CN must be ignored.)
+	 * dNSName is present, the CN must be ignored. We break this rule if host is
+	 * an IP address; see the comment above.)
 	 */
-	if (*names_examined == 0)
+	if ((rc == 0) && check_cn)
 	{
 		X509_NAME  *subject_name;
 
@@ -581,10 +676,20 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 												  NID_commonName, -1);
 			if (cn_index >= 0)
 			{
+				char   *common_name = NULL;
+
 				(*names_examined)++;
 				rc = openssl_verify_peer_name_matches_certificate_name(conn,
 																	   X509_NAME_ENTRY_get_data(X509_NAME_get_entry(subject_name, cn_index)),
-																	   first_name);
+																	   &common_name);
+
+				if (common_name)
+				{
+					if (!*first_name)
+						*first_name = common_name;
+					else
+						free(common_name);
+				}
 			}
 		}
 	}
diff --git a/src/port/inet_net_ntop.c b/src/port/inet_net_ntop.c
index b8ad69c390..af28056132 100644
--- a/src/port/inet_net_ntop.c
+++ b/src/port/inet_net_ntop.c
@@ -31,17 +31,7 @@ static const char rcsid[] = "Id: inet_net_ntop.c,v 1.1.2.2 2004/03/09 09:17:27 m
 #include <netinet/in.h>
 #include <arpa/inet.h>
 
-#ifndef FRONTEND
-#include "utils/inet.h"
-#else
-/*
- * In a frontend build, we can't include inet.h, but we still need to have
- * sensible definitions of these two constants.  Note that pg_inet_net_ntop()
- * assumes that PGSQL_AF_INET is equal to AF_INET.
- */
-#define PGSQL_AF_INET	(AF_INET + 0)
-#define PGSQL_AF_INET6	(AF_INET + 1)
-#endif
+#include "common/inet-common.h"
 
 
 #define NS_IN6ADDRSZ 16
diff --git a/src/port/inet_net_pton.c b/src/port/inet_net_pton.c
index bae50ba67e..5bae811c6f 100644
--- a/src/port/inet_net_pton.c
+++ b/src/port/inet_net_pton.c
@@ -33,18 +33,10 @@ static const char rcsid[] = "Id: inet_net_pton.c,v 1.4.2.3 2004/03/17 00:40:11 m
 #include <assert.h>
 #include <ctype.h>
 
+#include "common/inet-common.h"
 #ifndef FRONTEND
 #include "utils/builtins.h" /* pgrminclude ignore */	/* needed on some
 														 * platforms */
-#include "utils/inet.h"
-#else
-/*
- * In a frontend build, we can't include inet.h, but we still need to have
- * sensible definitions of these two constants.  Note that pg_inet_net_ntop()
- * assumes that PGSQL_AF_INET is equal to AF_INET.
- */
-#define PGSQL_AF_INET	(AF_INET + 0)
-#define PGSQL_AF_INET6	(AF_INET + 1)
 #endif
 
 
diff --git a/src/test/ssl/conf/server-cn-and-ip-alt-names.config b/src/test/ssl/conf/server-cn-and-ip-alt-names.config
new file mode 100644
index 0000000000..a6fa09bad3
--- /dev/null
+++ b/src/test/ssl/conf/server-cn-and-ip-alt-names.config
@@ -0,0 +1,24 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+# This certificate contains a CN and SANs for both IPv4 and IPv6.
+
+
+[ req ]
+distinguished_name     = req_distinguished_name
+req_extensions         = v3_req
+prompt                 = no
+
+[ req_distinguished_name ]
+# Note: According to RFC 2818 and 6125, the CN is ignored, when DNS names are
+# present in the SANs. But they are silent on whether the CN is checked when IP
+# addresses are present.
+CN = common-name.pg-ssltest.test
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
+subjectAltName = @alt_names
+
+[ alt_names ]
+IP.1 = 192.0.2.1
+IP.2 = 2001:DB8::1
diff --git a/src/test/ssl/conf/server-ip-alt-names.config b/src/test/ssl/conf/server-ip-alt-names.config
new file mode 100644
index 0000000000..c22f22951a
--- /dev/null
+++ b/src/test/ssl/conf/server-ip-alt-names.config
@@ -0,0 +1,19 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+# This certificate has a two IP-address SANs, and no CN.
+
+[ req ]
+distinguished_name     = req_distinguished_name
+req_extensions         = v3_req
+prompt                 = no
+
+[ req_distinguished_name ]
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
+subjectAltName = @alt_names
+
+[ alt_names ]
+IP.1 = 192.0.2.1
+IP.2 = 2001:DB8::1
diff --git a/src/test/ssl/conf/server-ip-cn-and-alt-names.config b/src/test/ssl/conf/server-ip-cn-and-alt-names.config
new file mode 100644
index 0000000000..a4087f0a18
--- /dev/null
+++ b/src/test/ssl/conf/server-ip-cn-and-alt-names.config
@@ -0,0 +1,21 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+# This certificate contains both a CN and SANs in IP address format.
+
+
+[ req ]
+distinguished_name     = req_distinguished_name
+req_extensions         = v3_req
+prompt                 = no
+
+[ req_distinguished_name ]
+CN = 192.0.2.1
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
+subjectAltName = @alt_names
+
+[ alt_names ]
+IP.1 = 192.0.2.2
+IP.2 = 2001:DB8::1
diff --git a/src/test/ssl/conf/server-ip-cn-and-dns-alt-names.config b/src/test/ssl/conf/server-ip-cn-and-dns-alt-names.config
new file mode 100644
index 0000000000..7121803b49
--- /dev/null
+++ b/src/test/ssl/conf/server-ip-cn-and-dns-alt-names.config
@@ -0,0 +1,21 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+# This certificate contains both a CN and SANs in IP address format.
+
+
+[ req ]
+distinguished_name     = req_distinguished_name
+req_extensions         = v3_req
+prompt                 = no
+
+[ req_distinguished_name ]
+CN = 192.0.2.1
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
+subjectAltName = @alt_names
+
+[ alt_names ]
+DNS.1 = dns1.alt-name.pg-ssltest.test
+DNS.2 = dns2.alt-name.pg-ssltest.test
diff --git a/src/test/ssl/conf/server-ip-cn-only.config b/src/test/ssl/conf/server-ip-cn-only.config
new file mode 100644
index 0000000000..585d8bdae8
--- /dev/null
+++ b/src/test/ssl/conf/server-ip-cn-only.config
@@ -0,0 +1,12 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+
+[ req ]
+distinguished_name     = req_distinguished_name
+prompt                 = no
+
+[ req_distinguished_name ]
+CN = 192.0.2.1
+OU = PostgreSQL test suite
+
+# No Subject Alternative Names
diff --git a/src/test/ssl/ssl/server-cn-and-ip-alt-names.crt b/src/test/ssl/ssl/server-cn-and-ip-alt-names.crt
new file mode 100644
index 0000000000..4e58c85ccb
--- /dev/null
+++ b/src/test/ssl/ssl/server-cn-and-ip-alt-names.crt
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDLzCCAhegAwIBAgIIICERKRE1UQAwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UE
+Aww3VGVzdCBDQSBmb3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHNl
+cnZlciBjZXJ0czAeFw0yMTExMjkxOTM1NTFaFw00OTA0MTYxOTM1NTFaMEYxHjAc
+BgNVBAsMFVBvc3RncmVTUUwgdGVzdCBzdWl0ZTEkMCIGA1UEAwwbY29tbW9uLW5h
+bWUucGctc3NsdGVzdC50ZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
+AQEA6+8IYKAFnZ7V+fDo1cyMpbGBLzCfJOQ/1o2jOGP4+GjpsZgv6S6UT2MheC8M
+iiEFrYwdsSIZyYc3jEZrluy/UuR0bCGtqU92BCqa0iBLhvHOgjR588u253eLxQtQ
+8iJn11QPrKMk35nMkmY8GfHt4sGFbvBL6+GpipHq7a6cde3Z+v4kCB5dKMYDUDtm
+3mJmviuGNAu5wOqItk2Yi5dwJs1054007KNH0Il43urxiOfnkLS0cG5kehboPf86
+vxBt3iHByrU/9/DY5IvQCfSXVNa6rb5w5/pGja9aCei6Mv1jQY/V8SMQTga+MOsA
+0WB9akxMi2NxwS2+BQ4k/McPlwIDAQABoyUwIzAhBgNVHREEGjAYhwTAAAIBhxAg
+AQ24AAAAAAAAAAAAAAABMA0GCSqGSIb3DQEBCwUAA4IBAQAQLo2RzC07dG9p+J3A
+W6C0p3Y+Os/YE2D9wfp4TIDTZxcRUQZ0S6ahF1N6sp8l9KHBJHPU1cUpRAU1oD+Y
+SqmnP/VJRRDTTj9Ytdc/Vuo2jeLpSYhVKrCqtjqIrCwYJFoYRmMoxTtJGlwA0hSd
+kwo3XYrALPUQWUErTYPvNfDNIuUwqUXNfS0CXuIOVN3LJ+shegg6Pwbh9B5T9NHx
+kH+HswajhdpdnZIgh0FYTlTCPILDrB49aOWwqLa54AUA6WXa35hPsP8SoqL9Eucq
+ifPhBYyadsjOb+70N8GbbAsDPN1jCX9L8RuNcEkxSCKCYx91cWXh7K5KMPuGlzB7
+j8xB
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-cn-and-ip-alt-names.key b/src/test/ssl/ssl/server-cn-and-ip-alt-names.key
new file mode 100644
index 0000000000..837eef996d
--- /dev/null
+++ b/src/test/ssl/ssl/server-cn-and-ip-alt-names.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA6+8IYKAFnZ7V+fDo1cyMpbGBLzCfJOQ/1o2jOGP4+GjpsZgv
+6S6UT2MheC8MiiEFrYwdsSIZyYc3jEZrluy/UuR0bCGtqU92BCqa0iBLhvHOgjR5
+88u253eLxQtQ8iJn11QPrKMk35nMkmY8GfHt4sGFbvBL6+GpipHq7a6cde3Z+v4k
+CB5dKMYDUDtm3mJmviuGNAu5wOqItk2Yi5dwJs1054007KNH0Il43urxiOfnkLS0
+cG5kehboPf86vxBt3iHByrU/9/DY5IvQCfSXVNa6rb5w5/pGja9aCei6Mv1jQY/V
+8SMQTga+MOsA0WB9akxMi2NxwS2+BQ4k/McPlwIDAQABAoIBAQCuNFKVNdKvrUYF
+RLJGmsAG3+eo9lern7TbML2ht39vu9dBwEMwA6qSa3mdCfBSVUuh9uE9lxY/TU3g
+j2aFi81A4VptNPjLGNblAKhMGnhp7UUzspeRQYuNoSFcnpxoDKtrvK/OIq/pQeBh
+AIfECHRDh+yEG32Tb44FuPQkB1eTYl8xbMEImrhNUaSjJk7tTsmydHy0DjmqHVKX
+HUj0TREfDBDOBiHtY0XV6Pu3bnqDH/TKLTfUf3UdfTuay3Yai9aEcRPWp9GrMO7G
+axsKCifTz6177gyr6Fv8HLeMZMh9rMZRn3e0zfaF6vrH1QnZZOts5jpUa0KugSCd
+//uC0iNxAoGBAPXVc3b+o3hY5gcwwpaW6JtsarDrmNRxrizqIDG7NgpqwdFXgTi6
+6q0t2pjv81ATqij69IcPkNSissyR4OEKnu/OFJWzreg8yLi75WHKi0E/6msHpwRk
+d1yP0Zgd05ots/yOjDSp593RagaPVvHBxMECZ/Tm3B+Tq55Azudd/zvLAoGBAPWw
+xf0oUEJl6NdUZD6K7eFc6jf8yrpD85dldeko6LeN8x0XlKKWvUDJ2+3oizXoQvCm
+8by6KOYEIo4MrtXuy9MmtPWfNvRBr+hsUHchIj7IgFa9bKXyK2FnJqu/8CbEymli
+eZu7hoOhelurhnFy1zSqwNO4GC+kw60Y/BO3Z1nlAoGAVOyYJtNwxXJwhKtjjYI0
+ePzLHrNE6J8c/Ick+AkkchTPP/JqwZ5Q0+KzUYITG+avMdkAAGhwMATEn8cFWLjC
+jzUyB0U7Hq9g5/CBHXdLBA+Ae9j46ZuLYH6OeW5UWz7OnsDfzpGjeA2QAxQhhQLb
+ZZHfN8tI39+zucfJskPWmGECgYEAg9guF1Fn6InJrqwR82IYj6SN6CeXHufSM392
+C/4xDDd3rDf4QlwECV2J0RzGf9I5Ae2EshNwWScE6Be0RweTh6cw2tJq6h7J6D8f
+2x4Dw49TF7klMdRIJUf2f5pLpHJccLswqTqzz7V69PCSABVxmUi8m6EiEYconp5W
+v7nfE2UCgYALrEqzncuSIX3q6TVAjnzT7gO4h8h2TUekIWdHQFldFx8R7Kncggnd
+48gQqhewchNR83UCcd7pPsCcTqu6UR1QRdq/DV5P6J3xdZ2iS/2gCM6hvWIvKZEv
+/ClnkyFCOW7zX6RKIXtRYZTV1kz3TajApi34RTIeIMTieaCarnBJbA==
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-ip-alt-names.crt b/src/test/ssl/ssl/server-ip-alt-names.crt
new file mode 100644
index 0000000000..8a1bc620bb
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-alt-names.crt
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDCTCCAfGgAwIBAgIIICERKREEUAAwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UE
+Aww3VGVzdCBDQSBmb3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHNl
+cnZlciBjZXJ0czAeFw0yMTExMjkxOTA0NTBaFw00OTA0MTYxOTA0NTBaMCAxHjAc
+BgNVBAsMFVBvc3RncmVTUUwgdGVzdCBzdWl0ZTCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBAOM8yB6aVWb17ujr3ayU62mxHQoqn4CvG9yXlJvGOGv/ursW
+Vs0UYJdc96LsNZN1szdm9ayNzCIw3eja+ULsjxCi6+3LM4pO76IORL/XFamlTPYb
+BZ4pHdZVB0nnZAAnWCZPyXdnjOKQ5+8unVXkfibkjj8UELBJ2snehsOa+CTkOBez
+zxYMqxAgbywLIYsW448brun7UXpWmqbGK+SsdGaIZ5Sb7Zezc5lt6CrLemTZTHHK
+7l4WZFCCEi4t3sgO8o1vDELD/IE5G8lyXvIdgJg6t8ssper7iCw6S8x+okhjiSjT
+vDLU2g4AanqZRZB49aPwTo0QUcJA2BCJxL9xLy8CAwEAAaMlMCMwIQYDVR0RBBow
+GIcEwAACAYcQIAENuAAAAAAAAAAAAAAAATANBgkqhkiG9w0BAQsFAAOCAQEAwZJ+
+8KpABTlMEgKnHIYb35ItGhtFiTLQta9RkXx7vaeDwpOdPP/IvuvpjpQZkobRgBsk
+bNM0KuJpd2mSTphQAt6eKQIdcPrkzvc/Yh9OK3YNLUAbu/ZhBUnBvFnUL4wn2f1U
+mfO+m8P/LxybwqKx7r1mbaB+tP3RTxxLcIMvm9ECPQEoBntfEL325Wdoj+WuQH5Y
+IvcM6FaCTkQsNIPbaBD5l5MhMLHRULZujbDjXqGSvRMQfns6np/biMjNdQA8NZ5z
+STeUFvkQbCxoA0YYLgoSHL5KhZjXrg2g+T+2TUyCTR/91xf9OoOjBZdixR0S0DzJ
+B1+5vnUjZaCfnSEA7A==
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-ip-alt-names.key b/src/test/ssl/ssl/server-ip-alt-names.key
new file mode 100644
index 0000000000..b210b3a991
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-alt-names.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEA4zzIHppVZvXu6OvdrJTrabEdCiqfgK8b3JeUm8Y4a/+6uxZW
+zRRgl1z3ouw1k3WzN2b1rI3MIjDd6Nr5QuyPEKLr7cszik7vog5Ev9cVqaVM9hsF
+nikd1lUHSedkACdYJk/Jd2eM4pDn7y6dVeR+JuSOPxQQsEnayd6Gw5r4JOQ4F7PP
+FgyrECBvLAshixbjjxuu6ftRelaapsYr5Kx0ZohnlJvtl7NzmW3oKst6ZNlMccru
+XhZkUIISLi3eyA7yjW8MQsP8gTkbyXJe8h2AmDq3yyyl6vuILDpLzH6iSGOJKNO8
+MtTaDgBqeplFkHj1o/BOjRBRwkDYEInEv3EvLwIDAQABAoIBACp3uY6+mSdc3wF4
+0zzlt/lQuHSl8plCIJrhWUyjhvfoGyXLzv0Uydh/72frbTfZz1yTSWauOXBKYa6a
+/eqb+0DIsf8G8uLuTaqjsAWKVOoXkoKMGkistn7P9UTCkdXVhIvkbWp7V8EgA7iX
+pZ/fzBPIsyzmuxe3NcR0ags0cxuxkNuu+YXDv1oTedmT2wS3CZq1d/T1Y/EOVIf8
+Iznd2aOverlsnt6iiQ3ZWdG/W5F8FhnrR/rrBdYsdCv6TH/KUYexnDOUYpayjDbu
+oAKnifPp6UqiOM4SuBL83OAz19jptp5vpF370BEVRs3eK0q+zo/mETjv9HsXdolZ
+lfoXA0ECgYEA/7nb2azbq/2muvXCh1ZxCEbn3mt8KXoJP/xkx/v9eEc/cc5Q9e0V
+2oGfjC2hSE+bjOWMwiUMD6uU+iRjhz5A3IvUxnoSdoL7H9p0hTqLMyP7dTDkoVF5
+aEuLMaiI5YEnfAFu9L5h8ZKieoQTBoscT06wnGjh9pBV9bthfTKA7ksCgYEA43sb
+55m9WL4kWCPwOAp3vdEAFyxzmZHlO26sEQOU/m5aN01pumYybBruziEXMI96yfTj
+VmXKReeYb6XUiCcs3fLSipD/+8/8CsjO4uMORtxWumXe8AbKZfysGFzL7wJlByGT
+38AGQwIG/XD8cKnaiEMX4E/3Owbcoxwixo3WZC0CgYEAovaqJ9mEU+Jc8h/TS7PG
+bGPjN1Z/1V6zrlcFUnw/Vvrwb3HvHglsN8cLCaW6df5lPjC6tq4tNX8+fPnbg0Ak
+zWc+vQzl3ygxKGdqgcyBEKIJiPETgcoN+GzL02V3d+oKY3f2YXlBqVSsvi6UgUL9
+U3zuB36/IQVyAhrbUZFxoGkCgYEAnaFAO+Nvrp/LhXwZyGuQf+rkmipGTIMpil5t
+QzjtNMV5JFszSWPpyrl7A0Ew1YiG+I0GP2c3m+sY2TzbIiGrWH0b4cMKbw63Qy3V
+FqlpyjaCrpVKv56k/7jv883RzuQk56Uf1+szK5mrCFITy2oXsVZ0pA4lbjSaDTjA
+7D968V0CgYEA+qKqXKL98+c5CMPnpf+0B1x2zgyUym1ouPfon2x5fhK84T53zDMA
+zfdUJ/SOZw6/c9vRF7RL8h+ZfFdIyoAXv4Tt6mIiZe7P+AUVg6XgJ0ce2MUSeWjI
+W8D4WdSi0jyqr99TuVBWhbTZJviMB3pHqKaHQ07hnd/lPtvzsiH12qk=
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-ip-cn-and-alt-names.crt b/src/test/ssl/ssl/server-ip-cn-and-alt-names.crt
new file mode 100644
index 0000000000..2be02feb03
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-and-alt-names.crt
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDHTCCAgWgAwIBAgIIICIBBBQ2MQAwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UE
+Aww3VGVzdCBDQSBmb3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHNl
+cnZlciBjZXJ0czAeFw0yMjAxMDQyMjM2MzFaFw00OTA1MjIyMjM2MzFaMDQxHjAc
+BgNVBAsMFVBvc3RncmVTUUwgdGVzdCBzdWl0ZTESMBAGA1UEAwwJMTkyLjAuMi4x
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwmqTdQJfs2Ti9tPitYp2
+27I0HvL/kNSgA6egFr0foRo0BorwJNIzdbV0+EnsfiBNTWL5It26gqO7UP3ms8t2
+vHD5gkXfT+f6ts0lVJEcIOkUD/8ws4Ic9Y4uPqb4gN+pUKqcxtmLW1TYk84MBK59
+Xz4yPPS6N+G/DMMeFHTNkM9EQwn/+DC3fDsWdGYM2GRWDTJGg1A5tSUcF+seu7i1
+Vg7XajBfsvgAUAsrAxV+X/sLZh94HY+paD6wfaI99mY2OXVc/XW/z1r9WQznor65
+ZkonNCaPfavqPG5vqnab9AyQcqPqmX8hf/xrniASBAkqNCctbASrFCIYvCJfGfmX
+EQIDAQABoyUwIzAhBgNVHREEGjAYhwTAAAIChxAgAQ24AAAAAAAAAAAAAAABMA0G
+CSqGSIb3DQEBCwUAA4IBAQBf7kmYfRYfnWk1OUfY3N1kaNg9piBBlFr9g+OQn9KU
+zirkN7s0ZQbCGxV1uJQBKS58NyE414Vorau77379emgYDcCBpDIYpkLiNujVrIOr
+ggRFKsFRgxu4/mw0BSgCcV8RPe9SWHZ90Mos7TMCnW/PdxOCD1wD0YMkcs0rwB3l
+0Kzc7jDnfOEvmgw/Ysm7v67ps+05Uq5VskQ6WrpSAw6kPD/QMuuBAX8ATPczIaox
+zAMyncq1IiSIwG93f3EoQQThdQ70C6G9vLcu9TtL6JAsEMFEzR99gt1Wsqvmgl9W
+kStzj1yjIWeo5gIsa4Jgcke1lZviWyrTxHDfyunYE5i5
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-ip-cn-and-alt-names.key b/src/test/ssl/ssl/server-ip-cn-and-alt-names.key
new file mode 100644
index 0000000000..54fe80fc68
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-and-alt-names.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAwmqTdQJfs2Ti9tPitYp227I0HvL/kNSgA6egFr0foRo0Borw
+JNIzdbV0+EnsfiBNTWL5It26gqO7UP3ms8t2vHD5gkXfT+f6ts0lVJEcIOkUD/8w
+s4Ic9Y4uPqb4gN+pUKqcxtmLW1TYk84MBK59Xz4yPPS6N+G/DMMeFHTNkM9EQwn/
++DC3fDsWdGYM2GRWDTJGg1A5tSUcF+seu7i1Vg7XajBfsvgAUAsrAxV+X/sLZh94
+HY+paD6wfaI99mY2OXVc/XW/z1r9WQznor65ZkonNCaPfavqPG5vqnab9AyQcqPq
+mX8hf/xrniASBAkqNCctbASrFCIYvCJfGfmXEQIDAQABAoIBAB6GgVST5NbT9lbu
++d+rN/JSzqA1Yy8oU19/iEFJvJec96I3WnFNl8rZjN4XLUy4YarO6XMyAUDV2Gll
+FD4Sqjf4PRTZR7DSKaleGIhoqFP6hK3mUY091rIves9XhBkoBPunbipCqgDTF5ZN
+edGaXBECQP0VJ8/yX/7u++AWXthnjDis9X0taZfFg/PYbV7SCJ1Hg1O/wEsgXlnC
+7mbL6wkCW0f6700B0x1kKbZqJY95xRqp6Ipq2lIQbJDdGywoj0WzKqNltf9cer+r
+cXl8WjeiMvvvpl4uGhckAbzUifUzxN6A3f1fu/XKtOmabMi9t7J4MRfgOgedgtQB
+0jaZGSkCgYEA+lBLnNY6M48HX2mdtr86+n41gh69v8Z7oNikJFDZkodrvI8uqE0i
+0XwnYPFddt8NbmuUhhuzI2M8RKhGLgdlbKpkSSVafnMfcxRmX2EAtWQgdvX1Iult
+752LWdBgSuw2vlzvy3T/GYnjMrXSCGput4amqojMEbvUGvIdSUMdHGMCgYEAxtU1
+WixKPL6aEnYy1f4bybzcNgGtl8PBRz9xw+P46g+ijOPoaG9O73Tr7An11AO003Ot
+DHhMW+b8yHLyxoKwS2sU2cN/lKB8xNQYZc1D61RNJlzgnHMXnA0lcH0I3M35fqKr
+/71pD1ZP40SSJS+od/KEjW80XzuOdyiXg8q81vsCgYEAnUPLbbsuj+whzrFVlFZr
+IKwgxCK6Rn3WeIUEA4kEWUpZxvsSbk0gPgtJ1l9uwFt9Xc2bX/KRRv93Aw/SH+Mn
+tvEK1uXwCBgePzgm5W/VeSFyQCthm1CbcHtD7Oa9SPVFo65SPjrAd3QpWVfgoMb1
+zrp7hhMyW0XuCgvpmHjhFk8CgYEAxq/thXM2p+bLLWGhwQcRG5G299zLbBl4PUsf
+0uEvLi17gJCKADoiRdSvoAn/9eHSQ26XYRuhKkDzHxcGlOmpY2PYzRa3mXyZ0VIk
+Iy5wDWwLQCeVZ6D22cClRfgb8BF/nFTPzVmn72SPpgoyhChQj7PvUynpyrRH07jj
+VxYziBsCgYAFr37Xbl0VnXVK+XU+vMwUZjcF4jpoCr7SFZqgRbW2GbYSUoMuPXns
+RnJh+Fvi1NUei+E5s1H4P1pVq4p0jFxP4GvH/qvNjnIn/Er3bbqvpox6dWUJXprq
+qTQSDIeoDC/V8cyRoIfqPvTVqY8Rgew6GEkv0bAImdxhoSng7vIseg==
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.crt b/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.crt
new file mode 100644
index 0000000000..23c06da01c
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.crt
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDQzCCAiugAwIBAgIIICIBBBQ2MQEwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UE
+Aww3VGVzdCBDQSBmb3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHNl
+cnZlciBjZXJ0czAeFw0yMjAxMDQyMjM2MzFaFw00OTA1MjIyMjM2MzFaMDQxHjAc
+BgNVBAsMFVBvc3RncmVTUUwgdGVzdCBzdWl0ZTESMBAGA1UEAwwJMTkyLjAuMi4x
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8xddbo/x2TOSIa/br8BN
+o/URdTr9+l2R5YojiZKDuLxiQVkgC30PJ2/CNFKIh2nHhRrzknI6sETVtrxZ+9V2
+qRc1yShVu462u0DHPRMIZnZIOZg3hlNB0cRWbOglUKlttIARNEQUcTUyPOtyo4/v
++u0Ej5NTNcHFbFT01vdD9MjQiCO3jKdAwPIb14jTg4C71EpZ+LuelDo4DzF2/XgG
+WqUTrgD/XnBU/60PU9Iy3G0nVpx21q6ppn9G7a9R+i8FjBcwW1T+cfsBDWhAv+bi
+RmSAkENf8L8TwOlDQUwROkfz3Hz36vuJjdkreQJsiqL0HnrnH5T5G9UzJO86FvZQ
+5wIDAQABo0swSTBHBgNVHREEQDA+gh1kbnMxLmFsdC1uYW1lLnBnLXNzbHRlc3Qu
+dGVzdIIdZG5zMi5hbHQtbmFtZS5wZy1zc2x0ZXN0LnRlc3QwDQYJKoZIhvcNAQEL
+BQADggEBAF+mfaw6iBPzpCgqq830pHRa3Yzm1aezt8SkeRohUYHNv/yCnDSRaqtj
+xbENih3lJMSTBL3g0wtTOHfH8ViC/h+lvYELHzXKic7gkjV7H5XETKGr0ZsjBBT2
+4cZQKbD9e0x0HrENXMYgGpBf747qL6uTOVJdG0s15hwpLq47bY5WUjXathejbpxW
+prmF8F+xaC52N9P/1VnqguQB909F4x1pyOK7D7tjFu+Y8Je7PHKbb6WY5K6xAv6t
+R17CY0749/FotlphquElUR2bs5Zzv5YrjUHPTcbwKvcH5cdNi93/u6NJt2xNAoYf
+aZERhX5TA9DYk4gC8OY0yGaYCIj3Dd4=
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.key b/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.key
new file mode 100644
index 0000000000..0ace41e0a1
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEogIBAAKCAQEA8xddbo/x2TOSIa/br8BNo/URdTr9+l2R5YojiZKDuLxiQVkg
+C30PJ2/CNFKIh2nHhRrzknI6sETVtrxZ+9V2qRc1yShVu462u0DHPRMIZnZIOZg3
+hlNB0cRWbOglUKlttIARNEQUcTUyPOtyo4/v+u0Ej5NTNcHFbFT01vdD9MjQiCO3
+jKdAwPIb14jTg4C71EpZ+LuelDo4DzF2/XgGWqUTrgD/XnBU/60PU9Iy3G0nVpx2
+1q6ppn9G7a9R+i8FjBcwW1T+cfsBDWhAv+biRmSAkENf8L8TwOlDQUwROkfz3Hz3
+6vuJjdkreQJsiqL0HnrnH5T5G9UzJO86FvZQ5wIDAQABAoIBAGv0BFoFMrHyZQLw
+xe7Wx6P4QTh+aiu1QgVdw0pk9nojrr62hbSUZRZuWyBBRsBcCW7i+Sgf8lA1QXNV
+UeC0e228EPa0It6YEi42JkTJHwHhpVFud7n/X0t4lajnryqTE1UFSp6bXTipFxZW
+uSJJ2ZjliRD5rApDcxkY4WJVjKg3aEt7P/DiM8iKGfyE6stq72VjEbJjdViMEcOP
+BNf0TiREZz5Mp7jAVWhpen0ebbLOBVWV4/ONNcL+yqR4mCEDUSFGewrTVX4zHL0A
+hYk198C5F8sFvEDnFkPco9sXMVanmLoI8sbhP4IIz9g4+GU6kFuj7fUKp11Azqv+
+3WQDKYECgYEA/XG4mmG/g8FG44y42mfZpUXWi1pwU4CQIrhkoU5j7EPQrvRboOOE
+Rv95jSwyZu4vCqjyI5FN1jCGTdhmt++R1e//zH6Hqa9Smo+jw7DtAFrCYd1JnCf1
+ToOwsYPHv4P7A8q8kc5vCNIv+AQSlP/wqdVNo3grdf7cGXkMtEY4F9UCgYEA9Yrq
+zWdnNGPATuSBqL6TSjQ37oR+dBD6WnGsiDenQkOzyDPFZ3CT1DjJghjEtxc8EfNf
+Oo8dMMR2q+5FZQo7WuqONEgyzKePiNR8RK2gOYpgdjN9bih1sAhHR10D26cpwlDJ
+bx7D5ZzENLbdZmfEiWwKswnaIhN4yMalgE0mP8sCgYAhzJy12ftUct4lUosEdX0N
+EXc/NlxshmSyfKzO5kllJNYbvvLJTg5B+agYL6C5IWKcpVNFcwdSXT5L+2QXe5eT
+VGJkvysQchUuD6HjYyD4PyJVMtGyRZHtWpqh0dU9sTg0lUD4oPMl1gIXrVNdE5Tg
+0VV9S3VgUxC/ROlw0TyB0QKBgGsVE0NS9hJF8mc1hko2GnwA++d8Rr2NbfElo+2f
+/8SJTA1ibpOm6AFkZpTjAl8qtdrKPVyHb16GP47Jkd/3r1z979hjKCxSYul0aWF2
+KusNKvZBjFEPOgv0AEniCb2wUCjbHI3mZ95qGLM4kKOJW4/m21+rS0MTJNjCsQic
+HLMzAoGAeCsY09d3m8xGeU+DuTPC6GH7Sgy/NBYqS5VaVNjb2jnuZlW2SSW2oiID
+4tXTi4ruKmHC898BfyFxhSMqub+tg3pVqIYADC71rnJLrVyc1SzoWzL7yMT3qFj7
+C7ZYZYmfG9agcZb5NkqKPTfCxkBhWbdgTTgBKVO/xQst8EUgko8=
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-ip-cn-only.crt b/src/test/ssl/ssl/server-ip-cn-only.crt
new file mode 100644
index 0000000000..9bf015cf18
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-only.crt
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC8TCCAdkCCCAhESkRN1IAMA0GCSqGSIb3DQEBCwUAMEIxQDA+BgNVBAMMN1Rl
+c3QgQ0EgZm9yIFBvc3RncmVTUUwgU1NMIHJlZ3Jlc3Npb24gdGVzdCBzZXJ2ZXIg
+Y2VydHMwHhcNMjExMTI5MTkzNzUyWhcNNDkwNDE2MTkzNzUyWjA0MR4wHAYDVQQL
+DBVQb3N0Z3JlU1FMIHRlc3Qgc3VpdGUxEjAQBgNVBAMMCTE5Mi4wLjIuMTCCASIw
+DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANWs1uUL71nHYF9Zj6p+M3MpYDvx
+32iCjVdtH5a2qpSWHXTg0rR8dLX0y92cvOYvMXHRajZT1avpHr8dooPYSVaXpGMK
+NvF/Qi+WFYovRbP2vmd1yv1cgW/FggbwJFWVobizIz4seyA4d0B2j9fqoi2OFBNP
+huW664SjF0u3p21tDy+43i2LNUMAKf6dnRR5Vqenath87LEU41tSLudu6NXgbFMk
+jvfNkl4d0w7YCzeXmklmSI+uaX3PlJJ4NzQO2j8w5BvnKVhNVD0KjgrXZ6nB/8F7
+Pg3XY+d7rJlwRgXemU6resWQDJ7+UaC9u7I4EIP+9lzCR/nNBqUktpHRmHUCAwEA
+ATANBgkqhkiG9w0BAQsFAAOCAQEAos1JncV8Yf4UaKl6h1GdYtcVtzFyJvBEnhRD
+07ldL+TYnfZiX8wK2ssBtM3cg/C78y5bzdUa5XGS83ZKQJFFdhE7PSnrvyNqyIqY
+ZgNBxto3gyvir+EjO1u9BAB0NP3r3gYoHRDZS1xOPPzt4WgjuUgTLM9k82GsqAbO
+UrOTOdRnkIqC5xLpa05EnRyJPRsR1w1PRJC2XXKnHIuFjMb4v7UuPwyCcX1P5ioc
+rQszQcORy/L+k0ezCkyweORg68htjYbBHuwOuiGfok6yKKDMzrTvD3lIslls6eX7
+4sI3XWqzkPmG9Vsxm9Vu9/Ma+PRO76VyCoIwBd+Ufg5vNXhMmw==
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-ip-cn-only.key b/src/test/ssl/ssl/server-ip-cn-only.key
new file mode 100644
index 0000000000..1966530e72
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-only.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEA1azW5QvvWcdgX1mPqn4zcylgO/HfaIKNV20flraqlJYddODS
+tHx0tfTL3Zy85i8xcdFqNlPVq+kevx2ig9hJVpekYwo28X9CL5YVii9Fs/a+Z3XK
+/VyBb8WCBvAkVZWhuLMjPix7IDh3QHaP1+qiLY4UE0+G5brrhKMXS7enbW0PL7je
+LYs1QwAp/p2dFHlWp6dq2HzssRTjW1Iu527o1eBsUySO982SXh3TDtgLN5eaSWZI
+j65pfc+Ukng3NA7aPzDkG+cpWE1UPQqOCtdnqcH/wXs+Dddj53usmXBGBd6ZTqt6
+xZAMnv5RoL27sjgQg/72XMJH+c0GpSS2kdGYdQIDAQABAoIBAQDNXviU4WnF8rmQ
+K7bH+dBdqbETLKC8BG7xTrMD2sINWlMpmUUrsEtE7+paMGHnJAj0CoF5gg5m0wN4
+UXV4H5QtpEad4p14dAYbUreVP2ZRWKEdM7xM1HKcCUu2e22QzObJbXQ8N+iHyX3k
++Y+7yYrjGiH1hYR0nbnsnAyx++zyYBSQeqzpdQwf/BLY5xZmyYWNfqbckiMpEqMs
+EmZmGXnCjIipzEC0LQHoSW9PNa92Z9bvuxOKYl8iHYDDXjvMRFoZBSiMXpzHQocb
+QlQ5F4ayfW2OrOhpNbY7niYM9GN3Bk9TgMP+0BkJE6uuktLYW35LY1M78CCPWcWb
+npJNK3QBAoGBAOxkGrhAHAysSmtirIyMdvySb76wb/Ukfi+AULKz20FI5j4/GXm9
+qCb2GeT+FFSUHeSC8f0EFnosRYkdBGruqeZioI+5rUkboYFJPspAHAuvg9kgtfF+
+kvphD4O4P/foYsEZRx66FHozDbhrrR5UXc7KzqRIASc/D3FOx2UFJLb1AoGBAOdm
+WcaMvYygl9ZW+ThWAR1xG1X70AGKwrlrpF2hBkWYxSurxSMXnD0DUzC9Nb4EyCaM
+c2uSqEZOKdW+XfXtK2DnqXKfb3YCVEoGN4gVfyuW/vxii/+ZxLo3md/b3vrkZEVp
+pfkXy/HoZ71YN7bNpcDpOnhml6vvuCRCYFnI1WuBAoGAC0shB6pwbJ6Sk5zMN47C
+ZICufAK75o9OxAAyWsdC81SDQ3gKRImuDeZ2CD2nRP8qim9DFl5qoH2a+Nj9DArI
+7SvLFfK9958tURrpuAnmDRzehLIOXzI33WRjtFxKGhLtHOKTRkGHlur3fdcPF0La
+lHWV971E6NYXa8diuU3Mmj0CgYBYd+ka3/QYL83dRKNDxp3mg7fPx9ZewI5yFZVh
+to6PTTkU2Tclk4FIUl0b5TsGyw06r7fxCMENIBUegwmpXGOZSPifuhUDKSDQrE/O
+12knYTNbitG7hy6Pg3JxA77cbTVo1FuAQHjYo+IFohSq7zTP7FtObOrP8XaVZksw
+CHiQAQKBgBW4EiA9AAnZ1LOpifAvM7bs0NHg95qTwtAL52WKom2ga2H+lMhxeu6Y
+hUSytC/f9kALVcYloZhkLYpO07x1gXmy7f4parMjA4Ex+4vfu3kPd8GiNGZ+AUJD
+nnJ1OINY9ziXJZfju7FpVWpkiuPzWCh6y/o3gZ/veq5mIUxuDMVa
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/sslfiles.mk b/src/test/ssl/sslfiles.mk
index 7ed3a30f5c..f9be5ffdba 100644
--- a/src/test/ssl/sslfiles.mk
+++ b/src/test/ssl/sslfiles.mk
@@ -22,9 +22,14 @@
 # key/certificate pair will be generated for you, signed by the appropriate CA.
 #
 SERVERS := server-cn-and-alt-names \
+	server-cn-and-ip-alt-names \
 	server-cn-only \
+	server-ip-cn-only \
+	server-ip-cn-and-alt-names \
+	server-ip-cn-and-dns-alt-names \
 	server-single-alt-name \
 	server-multiple-alt-names \
+	server-ip-alt-names \
 	server-no-names \
 	server-revoked
 CLIENTS := client client-dn client-revoked client_ext
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index b8f8b65a8f..11811d5794 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -249,6 +249,23 @@ $node->connect_fails(
 	  qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/
 );
 
+# Test with an IP address in the Common Name. This is a strange corner case that
+# nevertheless is supported, as long as the address string matches exactly.
+switch_server_cert($node, 'server-ip-cn-only');
+
+$common_connstr =
+  "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+
+$node->connect_ok("$common_connstr host=192.0.2.1",
+	"IP address in the Common Name");
+
+$node->connect_fails(
+	"$common_connstr host=192.000.002.001",
+	"mismatch between host name and server certificate IP address",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.1" does not match host name "192.000.002.001"\E/
+);
+
 # Test Subject Alternative Names.
 switch_server_cert($node, 'server-multiple-alt-names');
 
@@ -301,7 +318,54 @@ $node->connect_fails(
 	  qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/
 );
 
-# Test server certificate with a CN and SANs. Per RFCs 2818 and 6125, the CN
+# Test certificate with IP addresses in the SANs.
+switch_server_cert($node, 'server-ip-alt-names');
+
+$node->connect_ok(
+	"$common_connstr host=192.0.2.1",
+	"host matching an IPv4 address (Subject Alternative Name 1)");
+
+$node->connect_ok(
+	"$common_connstr host=192.000.002.001",
+	"host matching an IPv4 address in alternate form (Subject Alternative Name 1)");
+
+$node->connect_fails(
+	"$common_connstr host=192.0.2.2",
+	"host not matching an IPv4 address (Subject Alternative Name 1)",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.1" (and 1 other name) does not match host name "192.0.2.2"\E/
+);
+
+$node->connect_fails(
+	"$common_connstr host=192.0.2.1/32",
+	"IPv4 host with CIDR mask does not match",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.1" (and 1 other name) does not match host name "192.0.2.1\/32"\E/
+);
+
+$node->connect_ok(
+	"$common_connstr host=2001:DB8::1",
+	"host matching an IPv6 address (Subject Alternative Name 2)");
+
+$node->connect_ok(
+	"$common_connstr host=2001:db8:0:0:0:0:0:1",
+	"host matching an IPv6 address in alternate form (Subject Alternative Name 2)");
+
+$node->connect_fails(
+	"$common_connstr host=::1",
+	"host not matching an IPv6 address (Subject Alternative Name 2)",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.1" (and 1 other name) does not match host name "::1"\E/
+);
+
+$node->connect_fails(
+	"$common_connstr host=2001:DB8::1/128",
+	"IPv6 host with CIDR mask does not match",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.1" (and 1 other name) does not match host name "2001:DB8::1\/128"\E/
+);
+
+# Test server certificate with a CN and DNS SANs. Per RFCs 2818 and 6125, the CN
 # should be ignored when the certificate has both.
 switch_server_cert($node, 'server-cn-and-alt-names');
 
@@ -319,6 +383,39 @@ $node->connect_fails(
 	  qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/
 );
 
+# But we will fall back to check the CN if the SANs contain only IP addresses.
+switch_server_cert($node, 'server-cn-and-ip-alt-names');
+
+$node->connect_ok("$common_connstr host=common-name.pg-ssltest.test",
+	"certificate with both a CN and IP SANs matches CN");
+$node->connect_ok("$common_connstr host=192.0.2.1",
+	"certificate with both a CN and IP SANs matches SAN 1");
+$node->connect_ok("$common_connstr host=2001:db8::1",
+	"certificate with both a CN and IP SANs matches SAN 2");
+
+# And now the same tests, but with IP addresses and DNS names swapped.
+switch_server_cert($node, 'server-ip-cn-and-alt-names');
+
+$node->connect_ok("$common_connstr host=192.0.2.2",
+	"certificate with both an IP CN and IP SANs 1");
+$node->connect_ok("$common_connstr host=2001:db8::1",
+	"certificate with both an IP CN and IP SANs 2");
+$node->connect_fails(
+	"$common_connstr host=192.0.2.1",
+	"certificate with both an IP CN and IP SANs ignores CN",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.2" (and 1 other name) does not match host name "192.0.2.1"\E/
+);
+
+switch_server_cert($node, 'server-ip-cn-and-dns-alt-names');
+
+$node->connect_ok("$common_connstr host=192.0.2.1",
+	"certificate with both an IP CN and DNS SANs matches CN");
+$node->connect_ok("$common_connstr host=dns1.alt-name.pg-ssltest.test",
+	"certificate with both an IP CN and DNS SANs matches SAN 1");
+$node->connect_ok("$common_connstr host=dns2.alt-name.pg-ssltest.test",
+	"certificate with both an IP CN and DNS SANs matches SAN 2");
+
 # Finally, test a server certificate that has no CN or SANs. Of course, that's
 # not a very sensible certificate, but libpq should handle it gracefully.
 switch_server_cert($node, 'server-no-names');
-- 
2.25.1

v6-0003-squash-libpq-allow-IP-address-SANs-in-server-cert.patchtext/x-patch; name=v6-0003-squash-libpq-allow-IP-address-SANs-in-server-cert.patchDownload
From 334f43ac1a9af0c4d89bc40022eb50606ae1b37d Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Wed, 5 Jan 2022 15:47:03 -0800
Subject: [PATCH v6 3/3] squash! libpq: allow IP address SANs in server certs

Per review, provide a pg_inet_pton() interface for IPv6 and refactor the
internals accordingly. IPv4 is not yet implemented. The call sites that
just want standard addresses without a CIDR mask have been updated to
use the new function.

Internally, the IPv6 parser now takes an allow_cidr boolean. I've
plumbed that all the way down to getbits() in order to minimize the
amount of code touched, at the expense of an unnecessary function call
in the failing case.
---
 src/include/port.h                       |  1 +
 src/interfaces/libpq/fe-secure-common.c  |  7 +--
 src/interfaces/libpq/fe-secure-openssl.c |  2 +-
 src/port/inet_net_pton.c                 | 66 ++++++++++++++++++------
 4 files changed, 52 insertions(+), 24 deletions(-)

diff --git a/src/include/port.h b/src/include/port.h
index 2852e5b58b..fd046f4c24 100644
--- a/src/include/port.h
+++ b/src/include/port.h
@@ -517,6 +517,7 @@ extern char *pg_inet_net_ntop(int af, const void *src, int bits,
 
 /* port/inet_net_pton.c */
 extern int	pg_inet_net_pton(int af, const char *src, void *dst, size_t size);
+extern int	pg_inet_pton(int af, const char *src, void *dst);
 
 /* port/pg_strong_random.c */
 extern void pg_strong_random_init(void);
diff --git a/src/interfaces/libpq/fe-secure-common.c b/src/interfaces/libpq/fe-secure-common.c
index 2b8f2d6b24..4d78715756 100644
--- a/src/interfaces/libpq/fe-secure-common.c
+++ b/src/interfaces/libpq/fe-secure-common.c
@@ -211,12 +211,7 @@ pq_verify_peer_name_matches_certificate_ip(PGconn *conn,
 
 		family = PGSQL_AF_INET6;
 
-		/*
-		 * pg_inet_net_pton() will accept CIDR masks, which we don't want to
-		 * match, so skip the comparison if the host string contains a slash.
-		 */
-		if (!strchr(host, '/')
-			&& pg_inet_net_pton(PGSQL_AF_INET6, host, addr, -1) == 128)
+		if (pg_inet_pton(PGSQL_AF_INET6, host, addr) == 1)
 		{
 			if (memcmp(ipdata, addr, iplen) == 0)
 				match = 1;
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index 108def3c1b..6339f4540e 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -554,7 +554,7 @@ is_ip_address(const char *host)
 	unsigned char	dummy6[16];
 
 	return inet_aton(host, &dummy4)
-		|| (pg_inet_net_pton(PGSQL_AF_INET6, host, dummy6, -1) == 128);
+		|| (pg_inet_pton(PGSQL_AF_INET6, host, dummy6) == 1);
 }
 
 /*
diff --git a/src/port/inet_net_pton.c b/src/port/inet_net_pton.c
index 5bae811c6f..ae0d1fd2e3 100644
--- a/src/port/inet_net_pton.c
+++ b/src/port/inet_net_pton.c
@@ -42,8 +42,8 @@ static const char rcsid[] = "Id: inet_net_pton.c,v 1.4.2.3 2004/03/17 00:40:11 m
 
 static int	inet_net_pton_ipv4(const char *src, u_char *dst);
 static int	inet_cidr_pton_ipv4(const char *src, u_char *dst, size_t size);
-static int	inet_net_pton_ipv6(const char *src, u_char *dst);
-static int	inet_cidr_pton_ipv6(const char *src, u_char *dst, size_t size);
+static int	inet_pton_ipv6_internal(const char *src, u_char *dst, size_t size,
+									bool allow_cidr);
 
 
 /*
@@ -74,9 +74,9 @@ pg_inet_net_pton(int af, const char *src, void *dst, size_t size)
 				inet_net_pton_ipv4(src, dst) :
 				inet_cidr_pton_ipv4(src, dst, size);
 		case PGSQL_AF_INET6:
-			return size == -1 ?
-				inet_net_pton_ipv6(src, dst) :
-				inet_cidr_pton_ipv6(src, dst, size);
+			return inet_pton_ipv6_internal(src, dst,
+										   (size == -1) ? 16 : size,
+										   true /* allow CIDR */);
 		default:
 			errno = EAFNOSUPPORT;
 			return -1;
@@ -352,13 +352,22 @@ emsgsize:
 }
 
 static int
-getbits(const char *src, int *bitsp)
+getbits(const char *src, int *bitsp, bool allow_cidr)
 {
 	static const char digits[] = "0123456789";
 	int			n;
 	int			val;
 	char		ch;
 
+	if (!allow_cidr)
+	{
+		/*
+		 * If we got here, the top-level call is to pg_inet_pton() and the
+		 * caller supplied a CIDR mask, which isn't allowed.
+		 */
+		return 0;
+	}
+
 	val = 0;
 	n = 0;
 	while ((ch = *src++) != '\0')
@@ -385,7 +394,7 @@ getbits(const char *src, int *bitsp)
 }
 
 static int
-getv4(const char *src, u_char *dst, int *bitsp)
+getv4(const char *src, u_char *dst, int *bitsp, bool allow_cidr)
 {
 	static const char digits[] = "0123456789";
 	u_char	   *odst = dst;
@@ -416,7 +425,7 @@ getv4(const char *src, u_char *dst, int *bitsp)
 				return 0;
 			*dst++ = val;
 			if (ch == '/')
-				return getbits(src, bitsp);
+				return getbits(src, bitsp, allow_cidr);
 			val = 0;
 			n = 0;
 			continue;
@@ -431,18 +440,12 @@ getv4(const char *src, u_char *dst, int *bitsp)
 	return 1;
 }
 
-static int
-inet_net_pton_ipv6(const char *src, u_char *dst)
-{
-	return inet_cidr_pton_ipv6(src, dst, 16);
-}
-
 #define NS_IN6ADDRSZ 16
 #define NS_INT16SZ 2
 #define NS_INADDRSZ 4
 
 static int
-inet_cidr_pton_ipv6(const char *src, u_char *dst, size_t size)
+inet_pton_ipv6_internal(const char *src, u_char *dst, size_t size, bool allow_cidr)
 {
 	static const char xdigits_l[] = "0123456789abcdef",
 				xdigits_u[] = "0123456789ABCDEF";
@@ -510,13 +513,13 @@ inet_cidr_pton_ipv6(const char *src, u_char *dst, size_t size)
 			continue;
 		}
 		if (ch == '.' && ((tp + NS_INADDRSZ) <= endp) &&
-			getv4(curtok, tp, &bits) > 0)
+			getv4(curtok, tp, &bits, allow_cidr) > 0)
 		{
 			tp += NS_INADDRSZ;
 			saw_xdigit = 0;
 			break;				/* '\0' was seen by inet_pton4(). */
 		}
-		if (ch == '/' && getbits(src, &bits) > 0)
+		if (ch == '/' && getbits(src, &bits, allow_cidr) > 0)
 			break;
 		goto enoent;
 	}
@@ -530,6 +533,9 @@ inet_cidr_pton_ipv6(const char *src, u_char *dst, size_t size)
 	if (bits == -1)
 		bits = 128;
 
+	/* Without a CIDR mask, the above should guarantee a complete address. */
+	Assert(allow_cidr || bits == 128);
+
 	endp = tmp + 16;
 
 	if (colonp != NULL)
@@ -568,3 +574,29 @@ emsgsize:
 	errno = EMSGSIZE;
 	return -1;
 }
+
+/*
+ * Converts a network address in presentation format to network format. The main
+ * difference between this and pg_inet_net_pton() above is that CIDR notation is
+ * not allowed.
+ *
+ * The memory pointed to by dst depends on the address family:
+ *
+ *     PGSQL_AF_INET:  not implemented yet (returns -1 with EAFNOSUPPORT)
+ *     PGSQL_AF_INET6: *dst must be a u_char[16]
+ *
+ * Over and above the standard inet_pton() functionality, we also set errno on
+ * parse failure, with the same meanings as with pg_inet_net_pton().
+ */
+int
+pg_inet_pton(int af, const char *src, void *dst)
+{
+	if (af != PGSQL_AF_INET6)
+	{
+		/* Currently only IPv6 is implemented. */
+		errno = EAFNOSUPPORT;
+		return -1;
+	}
+
+	return (inet_pton_ipv6_internal(src, dst, 16, false /* no CIDR */) == 128);
+}
-- 
2.25.1

#21Kyotaro Horiguchi
horikyota.ntt@gmail.com
In reply to: Jacob Champion (#20)
Re: [PATCH] Accept IP addresses in server certificate SANs

At Thu, 17 Feb 2022 17:29:15 +0000, Jacob Champion <pchampion@vmware.com> wrote in

On Tue, 2022-02-15 at 15:16 +0900, Kyotaro Horiguchi wrote:

(This needs rebasing)

Done in v6, attached.

Thanks!

# I forgot to mention that, the test fails for me even without the
# change. I didn't checked what is wrong there, though.

Ah. We should probably figure that out, then -- what failures do you
see?

I forgot the detail but v6 still fails for me. I think it is that.

t/003_sslinfo.pl ... 1/? # Tests were run but no plan was declared and done_testing() was not seen.
# Looks like your test exited with 29 just after 6.
t/003_sslinfo.pl ... Dubious, test returned 29 (wstat 7424, 0x1d00)
All 6 subtests passed
...
Result: FAIL

The script complains like this:

ok 6 - ssl_client_cert_present() for connection with cert
connection error: 'psql: error: connection to server at "127.0.0.1", port 62656 failed: SSL error: tlsv1 alert unknown ca'
while running 'psql -XAtq -d sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=trustdb hostaddr=127.0.0.1 user=ssltestuser host=localhost -f - -v ON_ERROR_STOP=1' at /home/horiguti/work/worktrees/ipsan/src/test/ssl/../../../src/test/perl/PostgreSQL/Test/Cluster.pm line 1873.

So, psql looks like disliking the ca certificate. I also will dig
into that.

Mmm. after the end of the loop, rc is non-zero only when the loop has
exited by the break and otherwise rc is zero. Why isn't it equivalent
to setting check_cn to false at the break?

check_cn can be false if rc is zero, too; it means that we found a SAN
of the correct type but it didn't match.

Don't we count unmatched certs as "existed"? In that case I think we
don't go to CN.

Anyway, apart from that detail, I reconfirmed the spec the patch is
going to implement.

* If connhost contains a DNS name, and the certificate's SANs contain any
* dNSName entries, then we'll ignore the Subject Common Name entirely;
* otherwise, we fall back to checking the CN. (This behavior matches the
* RFC.)

Sure.

* If connhost contains an IP address, and the SANs contain iPAddress
* entries, we again ignore the CN. Otherwise, we allow the CN to match,
* EVEN IF there is a dNSName in the SANs. (RFC 6125 prohibits this: "A
* client MUST NOT seek a match for a reference identifier of CN-ID if the
* presented identifiers include a DNS-ID, SRV-ID, URI-ID, or any
* application-specific identifier types supported by the client.")

Actually the patch searches for a match of IP address connhost from
dNSName SANs even if iPAddress SANs exist. I think we've not
explicitly defined thebehavior in that case.

That's a good point; I didn't change the prior behavior. I feel more
comfortable leaving that check, since it is technically possible to
push something that looks like an IP address into a dNSName SAN. We
should probably make an explicit decision on that, as you say.

But I don't think that contradicts the code comment, does it? The
comment is just talking about CN fallback scenarios. If you find a
match in a dNSName, there's no reason to fall back to the CN.

The comment explains the spec correctly. From a practical view, the
behavior above doesn't seem to make things insecure. So I don't have
a strong opinion on the choice of the behaviors.

The only thing I'm concerned here is the possibility that the decision
corners us to some uncomfortable state between the RFC and our spec in
future. On the other hand, changing the behavior can immediately make
someone uncomfortable.

So, I'd like to leave it to committers:p

I supposed that we only
be deviant in the case "IP address connhost and no SANs of any type
exists". What do you think about it?

We fall back in the case of "IP address connhost and dNSName SANs
exist", which is prohibited by that part of RFC 6125. I don't think we
deviate in the case you described; can you explain further?

In that case, i.e., connhost is IP address and no SANs exist at all,
we go to CN. On the other hand in RFC6125:

rfc6125> In some cases, the URI is specified as an IP address rather
rfc6125> than a hostname. In this case, the iPAddress subjectAltName
rfc6125> must be present in the certificate and must exactly match the
rfc6125> IP in the URI.

It (seems to me) denies that behavior. Regardless of the existence of
other types of SANs, iPAddress is required if connname is an IP
address. (That is, it doesn't seem to me that there's any context
like "if any SANs exists", but I'm not so sure I read it perfectly.)

- For the certificate that have only dNSNames or no SANs presented, we
serach for a match from all dNSNames if any or otherwise try CN
regardless of the type of connhost.

Correct. (I don't find that way of dividing up the cases very
intuitive, though.)

Yeah, it's the same decision to the above. It doesn't matter in the
security view (if the cert issuer is sane) but we could be cornerd in
future.

- Otherwise (the cert has at least one iPAddress SANs) we follow the RFCs.

- For IP-addr connhost, we search only the iPAddress SANs.

We search the dNSNames as well, as you pointed out above. But we don't
fall back to the CN.

Ur. Yes.

- For DNSName connhost, we search only dNSName SANs if any or
otherwise try CN.

Effectively, yes. (We call the IP address verification functions too,
to get alt_name, but they can't match. If that's too confusing, we'd
need to pull the alt_name handling up out of the verification layer.)

Yes, I meant that.

Honestly I didn't consider to that detail. On second thought, with
this specification we cannot decide the behavior unless we scanned all
SANs.

Right.

Maybe we can find an elegant implement but I don't think people
here would welcome even that level of complexity needed only for that
dubious existing use case.

Which use case do you mean?

"dubious". So I meant that the use case where dNSNames is expected to
match with an IP address.

What do you think about this? And I'd like to hear from others.

I think we need to decide whether or not to keep the current "IP
address connhost can match a dNSName SAN" behavior, and if so I need to
add it to the test cases. (And we need to figure out why the tests are
failing in your build, of course.)

Thanks. All behaviors and theier reasons is now clear. So....

Let's leave them for committers for now.

regards.

--
Kyotaro Horiguchi
NTT Open Source Software Center

#22Jacob Champion
pchampion@vmware.com
In reply to: Kyotaro Horiguchi (#21)
Re: [PATCH] Accept IP addresses in server certificate SANs

On Mon, 2022-03-14 at 15:30 +0900, Kyotaro Horiguchi wrote:

t/003_sslinfo.pl ... 1/? # Tests were run but no plan was declared and done_testing() was not seen.
# Looks like your test exited with 29 just after 6.
t/003_sslinfo.pl ... Dubious, test returned 29 (wstat 7424, 0x1d00)
All 6 subtests passed
...
Result: FAIL

The script complains like this:

ok 6 - ssl_client_cert_present() for connection with cert
connection error: 'psql: error: connection to server at "127.0.0.1", port 62656 failed: SSL error: tlsv1 alert unknown ca'
while running 'psql -XAtq -d sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=trustdb hostaddr=127.0.0.1 user=ssltestuser host=localhost -f - -v ON_ERROR_STOP=1' at /home/horiguti/work/worktrees/ipsan/src/test/ssl/../../../src/test/perl/PostgreSQL/Test/Cluster.pm line 1873.

So, psql looks like disliking the ca certificate. I also will dig
into that.

Hmm, the sslinfo tests are failing? I wouldn't have expected that based
on the patch changes. Just to confirm -- they pass for you without the
patch?

Mmm. after the end of the loop, rc is non-zero only when the loop has
exited by the break and otherwise rc is zero. Why isn't it equivalent
to setting check_cn to false at the break?

check_cn can be false if rc is zero, too; it means that we found a SAN
of the correct type but it didn't match.

Don't we count unmatched certs as "existed"? In that case I think we
don't go to CN.

Unmatched names, you mean? I'm not sure I understand.

If it helps, the two tests that will fail if check_cn is unset only at
the break are

- certificate with both a CN and SANs ignores CN
- certificate with both an IP CN and IP SANs ignores CN

because none of the SANs would match in that case.

Actually the patch searches for a match of IP address connhost from
dNSName SANs even if iPAddress SANs exist. I think we've not
explicitly defined thebehavior in that case.

That's a good point; I didn't change the prior behavior. I feel more
comfortable leaving that check, since it is technically possible to
push something that looks like an IP address into a dNSName SAN. We
should probably make an explicit decision on that, as you say.

But I don't think that contradicts the code comment, does it? The
comment is just talking about CN fallback scenarios. If you find a
match in a dNSName, there's no reason to fall back to the CN.

The comment explains the spec correctly. From a practical view, the
behavior above doesn't seem to make things insecure. So I don't have
a strong opinion on the choice of the behaviors.

The only thing I'm concerned here is the possibility that the decision
corners us to some uncomfortable state between the RFC and our spec in
future. On the other hand, changing the behavior can immediately make
someone uncomfortable.

So, I'd like to leave it to committers:p

Sounds good. I'll work on adding tests for the current behavior, and if
the committers don't like it, we can change it.

I supposed that we only
be deviant in the case "IP address connhost and no SANs of any type
exists". What do you think about it?

We fall back in the case of "IP address connhost and dNSName SANs
exist", which is prohibited by that part of RFC 6125. I don't think we
deviate in the case you described; can you explain further?

In that case, i.e., connhost is IP address and no SANs exist at all,
we go to CN. On the other hand in RFC6125:

rfc6125> In some cases, the URI is specified as an IP address rather
rfc6125> than a hostname. In this case, the iPAddress subjectAltName
rfc6125> must be present in the certificate and must exactly match the
rfc6125> IP in the URI.

It (seems to me) denies that behavior. Regardless of the existence of
other types of SANs, iPAddress is required if connname is an IP
address. (That is, it doesn't seem to me that there's any context
like "if any SANs exists", but I'm not so sure I read it perfectly.)

I see what you mean now. Yes, we deviate there as well (and have done
so for a while now). I think breaking compatibility there would
probably not go over well.

Thanks. All behaviors and theier reasons is now clear. So....

Let's leave them for committers for now.

Thank you for the review!

--Jacob

#23Jacob Champion
pchampion@vmware.com
In reply to: Jacob Champion (#22)
4 attachment(s)
Re: [PATCH] Accept IP addresses in server certificate SANs

On Tue, 2022-03-15 at 21:41 +0000, Jacob Champion wrote:

Sounds good. I'll work on adding tests for the current behavior, and if
the committers don't like it, we can change it.

Done in v7, attached.

--Jacob

Attachments:

since-v6.diff.txttext/plain; name=since-v6.diff.txtDownload
commit 51052e322f4d4e4f75d3cf3d000a363244696013
Author: Jacob Champion <pchampion@vmware.com>
Date:   Tue Mar 15 16:02:15 2022 -0700

    squash! libpq: allow IP address SANs in server certs
    
    Per discussion on list, add tests for the (counter-intuitive) standing
    behavior that matches IP addresses embedded in dNSName SANs.

diff --git a/src/test/ssl/conf/server-ip-in-dnsname.config b/src/test/ssl/conf/server-ip-in-dnsname.config
new file mode 100644
index 0000000000..b15649aef7
--- /dev/null
+++ b/src/test/ssl/conf/server-ip-in-dnsname.config
@@ -0,0 +1,18 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+
+[ req ]
+distinguished_name     = req_distinguished_name
+req_extensions         = v3_req
+prompt                 = no
+
+[ req_distinguished_name ]
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
+subjectAltName = @alt_names
+
+# Normally IP addresses should not go into a dNSName.
+[ alt_names ]
+DNS.1 = 192.0.2.1
diff --git a/src/test/ssl/ssl/server-ip-in-dnsname.crt b/src/test/ssl/ssl/server-ip-in-dnsname.crt
new file mode 100644
index 0000000000..78ad8d99c8
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-in-dnsname.crt
@@ -0,0 +1,70 @@
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 2315416547509162496 (0x2022031515585200)
+        Signature Algorithm: sha256WithRSAEncryption
+        Issuer: CN = Test CA for PostgreSQL SSL regression test server certs
+        Validity
+            Not Before: Mar 15 22:58:52 2022 GMT
+            Not After : Jul 31 22:58:52 2049 GMT
+        Subject: OU = PostgreSQL test suite
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                RSA Public-Key: (2048 bit)
+                Modulus:
+                    00:ca:67:e5:b3:f5:fc:e7:c1:41:1f:f2:bc:e9:0e:
+                    07:3c:40:ac:4d:63:d5:84:a1:55:ad:a9:72:3f:3d:
+                    eb:e0:95:0c:48:15:37:91:e5:bb:3e:ca:1d:4f:c9:
+                    63:33:59:08:01:db:e5:60:07:ba:f5:f1:29:ea:23:
+                    e1:38:b9:6d:ed:4a:b2:a3:b4:98:dd:69:f9:13:0d:
+                    ed:59:42:a4:2a:5b:d0:05:93:cf:8c:fe:23:c4:d4:
+                    85:26:67:9a:08:21:1e:f7:d6:e1:17:dc:64:c0:9c:
+                    1e:ad:6f:7a:f5:53:0f:14:7f:70:06:c3:3d:8a:60:
+                    04:20:f9:17:f4:99:31:1c:8c:0f:0e:41:ec:82:cb:
+                    99:fb:74:7a:a0:35:9c:2a:9a:bf:2a:81:08:66:6f:
+                    c7:5a:25:f0:de:41:7d:57:6b:0d:7a:7f:ac:de:7d:
+                    f7:b9:21:05:64:11:67:c8:3c:e0:56:72:15:95:df:
+                    a7:0a:79:ed:eb:b4:38:64:03:cd:91:57:a0:42:f6:
+                    b7:83:95:95:de:bb:2b:98:dc:72:55:95:c4:76:3a:
+                    14:67:07:8c:2b:f2:aa:ce:3c:3c:23:ce:47:ce:1a:
+                    9d:9c:23:1a:ca:95:39:2e:b6:e7:4f:c3:58:a0:50:
+                    3b:82:b2:86:44:d5:7f:40:16:fc:80:86:48:c3:8a:
+                    90:09
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Subject Alternative Name: 
+                DNS:192.0.2.1
+    Signature Algorithm: sha256WithRSAEncryption
+         c8:00:01:f4:58:92:84:a5:b3:cd:d1:08:a1:37:a4:85:50:0e:
+         56:2f:88:b1:b0:10:02:a3:d3:88:00:69:12:9c:a7:8e:79:51:
+         06:e6:fb:e0:3a:76:b4:86:34:1c:39:c8:2d:23:5a:02:0c:b8:
+         54:2d:c8:c2:bb:0c:62:21:6e:b2:42:7b:4e:6b:3c:a8:d2:ca:
+         28:26:bd:21:12:6d:96:7e:2a:6f:22:94:2c:e9:6f:44:d4:e4:
+         dc:b3:ad:b8:04:05:5f:90:0c:2b:e9:64:f6:57:0a:08:00:f7:
+         10:0f:69:d1:b4:84:21:49:42:11:f4:9e:d0:e5:66:f3:b9:0c:
+         3b:a6:d8:50:84:17:d6:fd:43:d6:c3:bb:da:8d:e9:74:af:57:
+         5f:95:b7:b4:0a:0a:79:58:63:5f:a7:59:18:cb:e4:1f:5d:6f:
+         d8:8e:4b:2d:64:43:f5:28:59:be:4c:5e:7c:bb:1a:c8:0e:66:
+         1b:a1:2f:04:7a:b5:11:10:64:22:c6:28:db:23:40:1f:5e:34:
+         a1:c6:d8:b2:4b:ad:eb:6d:da:68:dc:2d:01:3e:3f:58:c7:65:
+         6d:5a:2e:14:d2:aa:1e:7f:f8:8b:27:12:41:08:bb:c4:ff:de:
+         fe:76:e3:dd:2b:1d:00:92:43:96:03:d5:6e:7a:79:16:2e:9e:
+         fd:ed:43:3c
+-----BEGIN CERTIFICATE-----
+MIIC/DCCAeSgAwIBAgIIICIDFRVYUgAwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UE
+Aww3VGVzdCBDQSBmb3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHNl
+cnZlciBjZXJ0czAeFw0yMjAzMTUyMjU4NTJaFw00OTA3MzEyMjU4NTJaMCAxHjAc
+BgNVBAsMFVBvc3RncmVTUUwgdGVzdCBzdWl0ZTCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBAMpn5bP1/OfBQR/yvOkOBzxArE1j1YShVa2pcj896+CVDEgV
+N5Hluz7KHU/JYzNZCAHb5WAHuvXxKeoj4Ti5be1KsqO0mN1p+RMN7VlCpCpb0AWT
+z4z+I8TUhSZnmgghHvfW4RfcZMCcHq1vevVTDxR/cAbDPYpgBCD5F/SZMRyMDw5B
+7ILLmft0eqA1nCqavyqBCGZvx1ol8N5BfVdrDXp/rN5997khBWQRZ8g84FZyFZXf
+pwp57eu0OGQDzZFXoEL2t4OVld67K5jcclWVxHY6FGcHjCvyqs48PCPOR84anZwj
+GsqVOS6250/DWKBQO4KyhkTVf0AW/ICGSMOKkAkCAwEAAaMYMBYwFAYDVR0RBA0w
+C4IJMTkyLjAuMi4xMA0GCSqGSIb3DQEBCwUAA4IBAQDIAAH0WJKEpbPN0QihN6SF
+UA5WL4ixsBACo9OIAGkSnKeOeVEG5vvgOna0hjQcOcgtI1oCDLhULcjCuwxiIW6y
+QntOazyo0sooJr0hEm2WfipvIpQs6W9E1OTcs624BAVfkAwr6WT2VwoIAPcQD2nR
+tIQhSUIR9J7Q5WbzuQw7pthQhBfW/UPWw7vajel0r1dflbe0Cgp5WGNfp1kYy+Qf
+XW/YjkstZEP1KFm+TF58uxrIDmYboS8EerUREGQixijbI0AfXjShxtiyS63rbdpo
+3C0BPj9Yx2VtWi4U0qoef/iLJxJBCLvE/97+duPdKx0AkkOWA9VuenkWLp797UM8
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-ip-in-dnsname.key b/src/test/ssl/ssl/server-ip-in-dnsname.key
new file mode 100644
index 0000000000..ba319b001e
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-in-dnsname.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAymfls/X858FBH/K86Q4HPECsTWPVhKFVralyPz3r4JUMSBU3
+keW7PsodT8ljM1kIAdvlYAe69fEp6iPhOLlt7Uqyo7SY3Wn5Ew3tWUKkKlvQBZPP
+jP4jxNSFJmeaCCEe99bhF9xkwJwerW969VMPFH9wBsM9imAEIPkX9JkxHIwPDkHs
+gsuZ+3R6oDWcKpq/KoEIZm/HWiXw3kF9V2sNen+s3n33uSEFZBFnyDzgVnIVld+n
+Cnnt67Q4ZAPNkVegQva3g5WV3rsrmNxyVZXEdjoUZweMK/Kqzjw8I85HzhqdnCMa
+ypU5LrbnT8NYoFA7grKGRNV/QBb8gIZIw4qQCQIDAQABAoIBAA2kPP4JCTeRddMy
+Z/sJIAG2liZNITnkKcMflXyfrsMfKIm/LFSf+CO+OYWEHDR8vqZpbKcxPi+PRnTq
+YCaTkM4aZ7nS1S6vEsNu/90xOaFFONr3YFivVDfS3vp8pwv/N3gaumcCSqQUoZis
+18urAmwuPp2mEQK/f+e9AhlRLdcvlqDyKm+zMrVixK77Hj5JiEkh3rfZ3onHHKGE
+B7T2XRRqnZ4FCN9qLH2pMGUknZ4MGC9SlCyoerXFodb4DhKWQhJDRLjb8qP96r/E
+FGSg5WUiAERU/OgODoqZNTeIwIDB/f9NK45dEY3Hw6BsSFfU2VChrlNoVlzFUx2k
+yaH5Y4ECgYEA8rht3crh3GTy0jBJjNqB2iul8fkG/uiaiSvERWT/+KZnmV1+JGAW
+h2/wvd5apagOJjqKY0bCHMei/qYF9r4yJnkIy4qNper3QUz7TMCjsWduCm8S834A
+Z+Vwi3RBGJiQQH9Dfexko5sDjo+w5g4RsH52INCeReInNdxHOv06jZECgYEA1XrR
+QNwZlxHt3H93YKmKDZXikqW12Cuq6RSwf5VVdeuzV+pUN+/JaSgEuYsBilW7Q5p2
+gPROi0l8/eUPsBJb+dh1BcGzSjI2Kkzf66QOTG83S7tCPwQhwJUAylFuADvURjPQ
+qvqNjbQUomdm2QjBzyWtiFbolqxBgM3dnE6R/vkCgYBYGqQexx83LhmKPGbmTwal
+mARzkg59BxfZRN7IxcG4k0a1v98i+xISdYqwkP7cdOU18Tf8k1mwsrKytrcheqaf
+mn2bzJ5gJKs9s+DgWmjQ45dpCCqb4hfpnro8lKVwdSifkNKB6gYZ8RHYdMYkq+S1
+6SGeBbv95/qNrXjZq8POUQKBgHyaDwD4dsdCY79LdvYofrenQHOv3Q+rjTo2JT6S
+fysww6EQ2M89WiXSgc96Xw/LMl4nDfv+nMmXvyjCRgHS9XRC7yrJAEjSPeM6s4fq
+XZ4nW/ML/YKiesDZN3jfRoFEaoX/QFBLpcuLzG9uQw1ymwy5RSxK7b7kE+eGQU82
+XOihAoGBAI3xvT9fG3jRsSuw/8OQBlmDUFZcT0fRPRZ3pg8XlSreAam4b607d2WY
+u/bBHIclG3CLJ2EFqBtxl9AQeM0OTweF0KmV3dbtdBmaTbnhbK8/NLYnl5+aosEJ
+YrFKD8k8z6z+mYQs+7bAnfRa53TjfC7f24BpgEQyEfKL2fa3PF+J
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/sslfiles.mk b/src/test/ssl/sslfiles.mk
index f9be5ffdba..d6a3870079 100644
--- a/src/test/ssl/sslfiles.mk
+++ b/src/test/ssl/sslfiles.mk
@@ -27,6 +27,7 @@ SERVERS := server-cn-and-alt-names \
 	server-ip-cn-only \
 	server-ip-cn-and-alt-names \
 	server-ip-cn-and-dns-alt-names \
+	server-ip-in-dnsname \
 	server-single-alt-name \
 	server-multiple-alt-names \
 	server-ip-alt-names \
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 6c73c0f9ea..07e5b71d8c 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -266,6 +266,13 @@ $node->connect_fails(
 	  qr/\Qserver certificate for "192.0.2.1" does not match host name "192.000.002.001"\E/
 );
 
+# Similarly, we'll also match an IP address in a dNSName SAN. (This is
+# long-standing behavior.)
+switch_server_cert($node, 'server-ip-in-dnsname');
+
+$node->connect_ok("$common_connstr host=192.0.2.1",
+	"IP address in a dNSName");
+
 # Test Subject Alternative Names.
 switch_server_cert($node, 'server-multiple-alt-names');
 
v7-0001-Move-inet_net_pton-to-src-port.patchtext/x-patch; name=v7-0001-Move-inet_net_pton-to-src-port.patchDownload
From 699fe51818da474c0bcabf4f6930e04ecf8a6a1b Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Wed, 24 Nov 2021 14:46:11 -0800
Subject: [PATCH v7 1/3] Move inet_net_pton() to src/port

This will be helpful for IP address verification in libpq.
---
 src/backend/utils/adt/Makefile                  |  1 -
 src/include/port.h                              |  3 +++
 src/include/utils/builtins.h                    |  4 ----
 src/port/Makefile                               |  1 +
 src/{backend/utils/adt => port}/inet_net_pton.c | 16 +++++++++++++++-
 src/tools/msvc/Mkvcbuild.pm                     |  2 +-
 6 files changed, 20 insertions(+), 7 deletions(-)
 rename src/{backend/utils/adt => port}/inet_net_pton.c (96%)

diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile
index 41b486bcef..d173d52157 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -43,7 +43,6 @@ OBJS = \
 	geo_selfuncs.o \
 	geo_spgist.o \
 	inet_cidr_ntop.o \
-	inet_net_pton.o \
 	int.o \
 	int8.o \
 	json.o \
diff --git a/src/include/port.h b/src/include/port.h
index 3d103a2b31..2852e5b58b 100644
--- a/src/include/port.h
+++ b/src/include/port.h
@@ -515,6 +515,9 @@ extern int	pg_codepage_to_encoding(UINT cp);
 extern char *pg_inet_net_ntop(int af, const void *src, int bits,
 							  char *dst, size_t size);
 
+/* port/inet_net_pton.c */
+extern int	pg_inet_net_pton(int af, const char *src, void *dst, size_t size);
+
 /* port/pg_strong_random.c */
 extern void pg_strong_random_init(void);
 extern bool pg_strong_random(void *buf, size_t len);
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 666e545496..67b9326dc8 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -93,10 +93,6 @@ extern int	xidLogicalComparator(const void *arg1, const void *arg2);
 extern char *pg_inet_cidr_ntop(int af, const void *src, int bits,
 							   char *dst, size_t size);
 
-/* inet_net_pton.c */
-extern int	pg_inet_net_pton(int af, const char *src,
-							 void *dst, size_t size);
-
 /* network.c */
 extern double convert_network_to_scalar(Datum value, Oid typid, bool *failure);
 extern Datum network_scan_first(Datum in);
diff --git a/src/port/Makefile b/src/port/Makefile
index bfe1feb0d4..c3ae7b3d5c 100644
--- a/src/port/Makefile
+++ b/src/port/Makefile
@@ -43,6 +43,7 @@ OBJS = \
 	bsearch_arg.o \
 	chklocale.o \
 	inet_net_ntop.o \
+	inet_net_pton.o \
 	noblock.o \
 	path.o \
 	pg_bitutils.o \
diff --git a/src/backend/utils/adt/inet_net_pton.c b/src/port/inet_net_pton.c
similarity index 96%
rename from src/backend/utils/adt/inet_net_pton.c
rename to src/port/inet_net_pton.c
index d3221a1313..bae50ba67e 100644
--- a/src/backend/utils/adt/inet_net_pton.c
+++ b/src/port/inet_net_pton.c
@@ -14,14 +14,18 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
  * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  *
- *	  src/backend/utils/adt/inet_net_pton.c
+ *	  src/port/inet_net_pton.c
  */
 
 #if defined(LIBC_SCCS) && !defined(lint)
 static const char rcsid[] = "Id: inet_net_pton.c,v 1.4.2.3 2004/03/17 00:40:11 marka Exp $";
 #endif
 
+#ifndef FRONTEND
 #include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
 
 #include <sys/socket.h>
 #include <netinet/in.h>
@@ -29,9 +33,19 @@ static const char rcsid[] = "Id: inet_net_pton.c,v 1.4.2.3 2004/03/17 00:40:11 m
 #include <assert.h>
 #include <ctype.h>
 
+#ifndef FRONTEND
 #include "utils/builtins.h" /* pgrminclude ignore */	/* needed on some
 														 * platforms */
 #include "utils/inet.h"
+#else
+/*
+ * In a frontend build, we can't include inet.h, but we still need to have
+ * sensible definitions of these two constants.  Note that pg_inet_net_ntop()
+ * assumes that PGSQL_AF_INET is equal to AF_INET.
+ */
+#define PGSQL_AF_INET	(AF_INET + 0)
+#define PGSQL_AF_INET6	(AF_INET + 1)
+#endif
 
 
 static int	inet_net_pton_ipv4(const char *src, u_char *dst);
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 441d6ae6bf..667b173ec3 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -100,7 +100,7 @@ sub mkvcbuild
 
 	our @pgportfiles = qw(
 	  chklocale.c explicit_bzero.c fls.c getpeereid.c getrusage.c inet_aton.c
-	  getaddrinfo.c gettimeofday.c inet_net_ntop.c kill.c open.c
+	  getaddrinfo.c gettimeofday.c inet_net_ntop.c inet_net_pton.c kill.c open.c
 	  snprintf.c strlcat.c strlcpy.c dirmod.c noblock.c path.c
 	  dirent.c dlopen.c getopt.c getopt_long.c link.c
 	  pread.c preadv.c pwrite.c pwritev.c pg_bitutils.c
-- 
2.25.1

v7-0002-libpq-allow-IP-address-SANs-in-server-certs.patchtext/x-patch; name=v7-0002-libpq-allow-IP-address-SANs-in-server-certs.patchDownload
From fbebfd8a56c1e74a20188fc626226706c533d5e9 Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Thu, 18 Nov 2021 15:36:18 -0800
Subject: [PATCH v7 2/3] libpq: allow IP address SANs in server certs

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.

- Move PGSQL_AF_INET* to inet-common.h to reduce copy-paste.
---
 src/include/common/inet-common.h              |  35 +++++
 src/include/utils/inet.h                      |  13 +-
 src/interfaces/libpq/fe-secure-common.c       | 104 ++++++++++++++
 src/interfaces/libpq/fe-secure-common.h       |   5 +
 src/interfaces/libpq/fe-secure-openssl.c      | 131 ++++++++++++++++--
 src/port/inet_net_ntop.c                      |  12 +-
 src/port/inet_net_pton.c                      |  10 +-
 .../conf/server-cn-and-ip-alt-names.config    |  24 ++++
 src/test/ssl/conf/server-ip-alt-names.config  |  19 +++
 .../conf/server-ip-cn-and-alt-names.config    |  21 +++
 .../server-ip-cn-and-dns-alt-names.config     |  21 +++
 src/test/ssl/conf/server-ip-cn-only.config    |  12 ++
 src/test/ssl/conf/server-ip-in-dnsname.config |  18 +++
 .../ssl/ssl/server-cn-and-ip-alt-names.crt    |  20 +++
 .../ssl/ssl/server-cn-and-ip-alt-names.key    |  27 ++++
 src/test/ssl/ssl/server-ip-alt-names.crt      |  19 +++
 src/test/ssl/ssl/server-ip-alt-names.key      |  27 ++++
 .../ssl/ssl/server-ip-cn-and-alt-names.crt    |  19 +++
 .../ssl/ssl/server-ip-cn-and-alt-names.key    |  27 ++++
 .../ssl/server-ip-cn-and-dns-alt-names.crt    |  20 +++
 .../ssl/server-ip-cn-and-dns-alt-names.key    |  27 ++++
 src/test/ssl/ssl/server-ip-cn-only.crt        |  18 +++
 src/test/ssl/ssl/server-ip-cn-only.key        |  27 ++++
 src/test/ssl/ssl/server-ip-in-dnsname.crt     |  18 +++
 src/test/ssl/ssl/server-ip-in-dnsname.key     |  27 ++++
 src/test/ssl/sslfiles.mk                      |   6 +
 src/test/ssl/t/001_ssltests.pl                | 106 +++++++++++++-
 27 files changed, 767 insertions(+), 46 deletions(-)
 create mode 100644 src/include/common/inet-common.h
 create mode 100644 src/test/ssl/conf/server-cn-and-ip-alt-names.config
 create mode 100644 src/test/ssl/conf/server-ip-alt-names.config
 create mode 100644 src/test/ssl/conf/server-ip-cn-and-alt-names.config
 create mode 100644 src/test/ssl/conf/server-ip-cn-and-dns-alt-names.config
 create mode 100644 src/test/ssl/conf/server-ip-cn-only.config
 create mode 100644 src/test/ssl/conf/server-ip-in-dnsname.config
 create mode 100644 src/test/ssl/ssl/server-cn-and-ip-alt-names.crt
 create mode 100644 src/test/ssl/ssl/server-cn-and-ip-alt-names.key
 create mode 100644 src/test/ssl/ssl/server-ip-alt-names.crt
 create mode 100644 src/test/ssl/ssl/server-ip-alt-names.key
 create mode 100644 src/test/ssl/ssl/server-ip-cn-and-alt-names.crt
 create mode 100644 src/test/ssl/ssl/server-ip-cn-and-alt-names.key
 create mode 100644 src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.crt
 create mode 100644 src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.key
 create mode 100644 src/test/ssl/ssl/server-ip-cn-only.crt
 create mode 100644 src/test/ssl/ssl/server-ip-cn-only.key
 create mode 100644 src/test/ssl/ssl/server-ip-in-dnsname.crt
 create mode 100644 src/test/ssl/ssl/server-ip-in-dnsname.key

diff --git a/src/include/common/inet-common.h b/src/include/common/inet-common.h
new file mode 100644
index 0000000000..3ad72261b6
--- /dev/null
+++ b/src/include/common/inet-common.h
@@ -0,0 +1,35 @@
+/*-------------------------------------------------------------------------
+ *
+ * inet-common.h
+ *
+ *	  Common code for clients of the inet_* APIs.
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/common/inet-common.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef INET_COMMON_H
+#define INET_COMMON_H
+
+/*
+ * We use these values for the "family" field of inet_struct.
+ *
+ * Referencing all of the non-AF_INET types to AF_INET lets us work on
+ * machines which may not have the appropriate address family (like
+ * inet6 addresses when AF_INET6 isn't present) but doesn't cause a
+ * dump/reload requirement.  Pre-7.4 databases used AF_INET for the family
+ * type on disk.
+ *
+ * In a frontend build, we can't include inet.h, but we still need to have
+ * sensible definitions of these two constants.  (Frontend clients should
+ * include this header directly.)  Note that pg_inet_net_ntop()
+ * assumes that PGSQL_AF_INET is equal to AF_INET.
+ */
+#define PGSQL_AF_INET	(AF_INET + 0)
+#define PGSQL_AF_INET6	(AF_INET + 1)
+
+#endif							/* INET_COMMON_H */
diff --git a/src/include/utils/inet.h b/src/include/utils/inet.h
index 3073c0307e..c56c016987 100644
--- a/src/include/utils/inet.h
+++ b/src/include/utils/inet.h
@@ -14,6 +14,7 @@
 #ifndef INET_H
 #define INET_H
 
+#include "common/inet-common.h"
 #include "fmgr.h"
 
 /*
@@ -27,18 +28,6 @@ typedef struct
 	unsigned char ipaddr[16];	/* up to 128 bits of address */
 } inet_struct;
 
-/*
- * We use these values for the "family" field.
- *
- * Referencing all of the non-AF_INET types to AF_INET lets us work on
- * machines which may not have the appropriate address family (like
- * inet6 addresses when AF_INET6 isn't present) but doesn't cause a
- * dump/reload requirement.  Pre-7.4 databases used AF_INET for the family
- * type on disk.
- */
-#define PGSQL_AF_INET	(AF_INET + 0)
-#define PGSQL_AF_INET6	(AF_INET + 1)
-
 /*
  * Both INET and CIDR addresses are represented within Postgres as varlena
  * objects, ie, there is a varlena header in front of the struct type
diff --git a/src/interfaces/libpq/fe-secure-common.c b/src/interfaces/libpq/fe-secure-common.c
index bd46f08fae..2b8f2d6b24 100644
--- a/src/interfaces/libpq/fe-secure-common.c
+++ b/src/interfaces/libpq/fe-secure-common.c
@@ -19,9 +19,13 @@
 
 #include "postgres_fe.h"
 
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
 #include "fe-secure-common.h"
 
 #include "libpq-int.h"
+#include "port.h"
 #include "pqexpbuffer.h"
 
 /*
@@ -144,6 +148,106 @@ pq_verify_peer_name_matches_certificate_name(PGconn *conn,
 	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'))
+	{
+		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 = PGSQL_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;
+		}
+	}
+	else if (iplen == 16)
+	{
+		/* IPv6 */
+		unsigned char addr[16];
+
+		family = PGSQL_AF_INET6;
+
+		/*
+		 * pg_inet_net_pton() will accept CIDR masks, which we don't want to
+		 * match, so skip the comparison if the host string contains a slash.
+		 */
+		if (!strchr(host, '/')
+			&& pg_inet_net_pton(PGSQL_AF_INET6, host, addr, -1) == 128)
+		{
+			if (memcmp(ipdata, addr, iplen) == 0)
+				match = 1;
+		}
+	}
+	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.
  *
diff --git a/src/interfaces/libpq/fe-secure-common.h b/src/interfaces/libpq/fe-secure-common.h
index 1cca6d785a..20ff9ba5db 100644
--- a/src/interfaces/libpq/fe-secure-common.h
+++ b/src/interfaces/libpq/fe-secure-common.h
@@ -16,11 +16,16 @@
 #ifndef FE_SECURE_COMMON_H
 #define FE_SECURE_COMMON_H
 
+#include "common/inet-common.h"
 #include "libpq-fe.h"
 
 extern int	pq_verify_peer_name_matches_certificate_name(PGconn *conn,
 														 const char *namedata, size_t namelen,
 														 char **store_name);
+extern int	pq_verify_peer_name_matches_certificate_ip(PGconn *conn,
+													   const unsigned char *addrdata,
+													   size_t addrlen,
+													   char **store_name);
 extern bool pq_verify_peer_name_matches_certificate(PGconn *conn);
 
 #endif							/* FE_SECURE_COMMON_H */
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index d81218a4cc..0eab797821 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -72,6 +72,9 @@ static int	verify_cb(int ok, X509_STORE_CTX *ctx);
 static int	openssl_verify_peer_name_matches_certificate_name(PGconn *conn,
 															  ASN1_STRING *name,
 															  char **store_name);
+static int	openssl_verify_peer_name_matches_certificate_ip(PGconn *conn,
+															ASN1_OCTET_STRING *addr_entry,
+															char **store_name);
 static void destroy_ssl_system(void);
 static int	initialize_SSL(PGconn *conn);
 static PostgresPollingStatusType open_client_SSL(PGconn *);
@@ -509,6 +512,51 @@ openssl_verify_peer_name_matches_certificate_name(PGconn *conn, ASN1_STRING *nam
 	return pq_verify_peer_name_matches_certificate_name(conn, (const char *) namedata, len, store_name);
 }
 
+/*
+ * OpenSSL-specific wrapper around
+ * pq_verify_peer_name_matches_certificate_ip(), converting the
+ * ASN1_OCTET_STRING into a plain C string.
+ */
+static int
+openssl_verify_peer_name_matches_certificate_ip(PGconn *conn,
+												ASN1_OCTET_STRING *addr_entry,
+												char **store_name)
+{
+	int			len;
+	const unsigned char *addrdata;
+
+	/* Should not happen... */
+	if (addr_entry == NULL)
+	{
+		appendPQExpBufferStr(&conn->errorMessage,
+							 libpq_gettext("SSL certificate's address entry is missing\n"));
+		return -1;
+	}
+
+	/*
+	 * GEN_IPADD is an OCTET STRING containing an IP address in network byte
+	 * order.
+	 */
+#ifdef HAVE_ASN1_STRING_GET0_DATA
+	addrdata = ASN1_STRING_get0_data(addr_entry);
+#else
+	addrdata = ASN1_STRING_data(addr_entry);
+#endif
+	len = ASN1_STRING_length(addr_entry);
+
+	return pq_verify_peer_name_matches_certificate_ip(conn, addrdata, len, store_name);
+}
+
+static bool
+is_ip_address(const char *host)
+{
+	struct in_addr	dummy4;
+	unsigned char	dummy6[16];
+
+	return inet_aton(host, &dummy4)
+		|| (pg_inet_net_pton(PGSQL_AF_INET6, host, dummy6, -1) == 128);
+}
+
 /*
  *	Verify that the server certificate matches the hostname we connected to.
  *
@@ -522,6 +570,36 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 	STACK_OF(GENERAL_NAME) * peer_san;
 	int			i;
 	int			rc = 0;
+	char	   *host = conn->connhost[conn->whichhost].host;
+	int			host_type;
+	bool		check_cn = true;
+
+	Assert(host && host[0]); /* should be guaranteed by caller */
+
+	/*
+	 * We try to match the NSS behavior here, which is a slight departure from
+	 * the spec but seems to make more intuitive sense:
+	 *
+	 * If connhost contains a DNS name, and the certificate's SANs contain any
+	 * dNSName entries, then we'll ignore the Subject Common Name entirely;
+	 * otherwise, we fall back to checking the CN. (This behavior matches the
+	 * RFC.)
+	 *
+	 * If connhost contains an IP address, and the SANs contain iPAddress
+	 * entries, we again ignore the CN. Otherwise, we allow the CN to match,
+	 * EVEN IF there is a dNSName in the SANs. (RFC 6125 prohibits this: "A
+	 * client MUST NOT seek a match for a reference identifier of CN-ID if the
+	 * presented identifiers include a DNS-ID, SRV-ID, URI-ID, or any
+	 * application-specific identifier types supported by the client.")
+	 *
+	 * NOTE: Prior versions of libpq did not consider iPAddress entries at all,
+	 * so this new behavior might break a certificate that has different IP
+	 * addresses in the Subject CN and the SANs.
+	 */
+	if (is_ip_address(host))
+		host_type = GEN_IPADD;
+	else
+		host_type = GEN_DNS;
 
 	/*
 	 * First, get the Subject Alternative Names (SANs) from the certificate,
@@ -537,24 +615,40 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 		for (i = 0; i < san_len; i++)
 		{
 			const GENERAL_NAME *name = sk_GENERAL_NAME_value(peer_san, i);
+			char	   *alt_name = NULL;
 
-			if (name->type == GEN_DNS)
+			if (name->type == host_type)
 			{
-				char	   *alt_name;
+				/*
+				 * This SAN is of the same type (IP or DNS) as our host name, so
+				 * don't allow a fallback check of the CN.
+				 */
+				check_cn = false;
+			}
 
+			if (name->type == GEN_DNS)
+			{
 				(*names_examined)++;
 				rc = openssl_verify_peer_name_matches_certificate_name(conn,
 																	   name->d.dNSName,
 																	   &alt_name);
+			}
+			else if (name->type == GEN_IPADD)
+			{
+				(*names_examined)++;
+				rc = openssl_verify_peer_name_matches_certificate_ip(conn,
+																	 name->d.iPAddress,
+																	 &alt_name);
+			}
 
-				if (alt_name)
-				{
-					if (!*first_name)
-						*first_name = alt_name;
-					else
-						free(alt_name);
-				}
+			if (alt_name)
+			{
+				if (!*first_name)
+					*first_name = alt_name;
+				else
+					free(alt_name);
 			}
+
 			if (rc != 0)
 				break;
 		}
@@ -562,13 +656,14 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 	}
 
 	/*
-	 * If there is no subjectAltName extension of type dNSName, check the
+	 * If there is no subjectAltName extension of the matching type, check the
 	 * Common Name.
 	 *
 	 * (Per RFC 2818 and RFC 6125, if the subjectAltName extension of type
-	 * dNSName is present, the CN must be ignored.)
+	 * dNSName is present, the CN must be ignored. We break this rule if host is
+	 * an IP address; see the comment above.)
 	 */
-	if (*names_examined == 0)
+	if ((rc == 0) && check_cn)
 	{
 		X509_NAME  *subject_name;
 
@@ -581,10 +676,20 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 												  NID_commonName, -1);
 			if (cn_index >= 0)
 			{
+				char   *common_name = NULL;
+
 				(*names_examined)++;
 				rc = openssl_verify_peer_name_matches_certificate_name(conn,
 																	   X509_NAME_ENTRY_get_data(X509_NAME_get_entry(subject_name, cn_index)),
-																	   first_name);
+																	   &common_name);
+
+				if (common_name)
+				{
+					if (!*first_name)
+						*first_name = common_name;
+					else
+						free(common_name);
+				}
 			}
 		}
 	}
diff --git a/src/port/inet_net_ntop.c b/src/port/inet_net_ntop.c
index b8ad69c390..af28056132 100644
--- a/src/port/inet_net_ntop.c
+++ b/src/port/inet_net_ntop.c
@@ -31,17 +31,7 @@ static const char rcsid[] = "Id: inet_net_ntop.c,v 1.1.2.2 2004/03/09 09:17:27 m
 #include <netinet/in.h>
 #include <arpa/inet.h>
 
-#ifndef FRONTEND
-#include "utils/inet.h"
-#else
-/*
- * In a frontend build, we can't include inet.h, but we still need to have
- * sensible definitions of these two constants.  Note that pg_inet_net_ntop()
- * assumes that PGSQL_AF_INET is equal to AF_INET.
- */
-#define PGSQL_AF_INET	(AF_INET + 0)
-#define PGSQL_AF_INET6	(AF_INET + 1)
-#endif
+#include "common/inet-common.h"
 
 
 #define NS_IN6ADDRSZ 16
diff --git a/src/port/inet_net_pton.c b/src/port/inet_net_pton.c
index bae50ba67e..5bae811c6f 100644
--- a/src/port/inet_net_pton.c
+++ b/src/port/inet_net_pton.c
@@ -33,18 +33,10 @@ static const char rcsid[] = "Id: inet_net_pton.c,v 1.4.2.3 2004/03/17 00:40:11 m
 #include <assert.h>
 #include <ctype.h>
 
+#include "common/inet-common.h"
 #ifndef FRONTEND
 #include "utils/builtins.h" /* pgrminclude ignore */	/* needed on some
 														 * platforms */
-#include "utils/inet.h"
-#else
-/*
- * In a frontend build, we can't include inet.h, but we still need to have
- * sensible definitions of these two constants.  Note that pg_inet_net_ntop()
- * assumes that PGSQL_AF_INET is equal to AF_INET.
- */
-#define PGSQL_AF_INET	(AF_INET + 0)
-#define PGSQL_AF_INET6	(AF_INET + 1)
 #endif
 
 
diff --git a/src/test/ssl/conf/server-cn-and-ip-alt-names.config b/src/test/ssl/conf/server-cn-and-ip-alt-names.config
new file mode 100644
index 0000000000..a6fa09bad3
--- /dev/null
+++ b/src/test/ssl/conf/server-cn-and-ip-alt-names.config
@@ -0,0 +1,24 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+# This certificate contains a CN and SANs for both IPv4 and IPv6.
+
+
+[ req ]
+distinguished_name     = req_distinguished_name
+req_extensions         = v3_req
+prompt                 = no
+
+[ req_distinguished_name ]
+# Note: According to RFC 2818 and 6125, the CN is ignored, when DNS names are
+# present in the SANs. But they are silent on whether the CN is checked when IP
+# addresses are present.
+CN = common-name.pg-ssltest.test
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
+subjectAltName = @alt_names
+
+[ alt_names ]
+IP.1 = 192.0.2.1
+IP.2 = 2001:DB8::1
diff --git a/src/test/ssl/conf/server-ip-alt-names.config b/src/test/ssl/conf/server-ip-alt-names.config
new file mode 100644
index 0000000000..c22f22951a
--- /dev/null
+++ b/src/test/ssl/conf/server-ip-alt-names.config
@@ -0,0 +1,19 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+# This certificate has a two IP-address SANs, and no CN.
+
+[ req ]
+distinguished_name     = req_distinguished_name
+req_extensions         = v3_req
+prompt                 = no
+
+[ req_distinguished_name ]
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
+subjectAltName = @alt_names
+
+[ alt_names ]
+IP.1 = 192.0.2.1
+IP.2 = 2001:DB8::1
diff --git a/src/test/ssl/conf/server-ip-cn-and-alt-names.config b/src/test/ssl/conf/server-ip-cn-and-alt-names.config
new file mode 100644
index 0000000000..a4087f0a18
--- /dev/null
+++ b/src/test/ssl/conf/server-ip-cn-and-alt-names.config
@@ -0,0 +1,21 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+# This certificate contains both a CN and SANs in IP address format.
+
+
+[ req ]
+distinguished_name     = req_distinguished_name
+req_extensions         = v3_req
+prompt                 = no
+
+[ req_distinguished_name ]
+CN = 192.0.2.1
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
+subjectAltName = @alt_names
+
+[ alt_names ]
+IP.1 = 192.0.2.2
+IP.2 = 2001:DB8::1
diff --git a/src/test/ssl/conf/server-ip-cn-and-dns-alt-names.config b/src/test/ssl/conf/server-ip-cn-and-dns-alt-names.config
new file mode 100644
index 0000000000..7121803b49
--- /dev/null
+++ b/src/test/ssl/conf/server-ip-cn-and-dns-alt-names.config
@@ -0,0 +1,21 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+# This certificate contains both a CN and SANs in IP address format.
+
+
+[ req ]
+distinguished_name     = req_distinguished_name
+req_extensions         = v3_req
+prompt                 = no
+
+[ req_distinguished_name ]
+CN = 192.0.2.1
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
+subjectAltName = @alt_names
+
+[ alt_names ]
+DNS.1 = dns1.alt-name.pg-ssltest.test
+DNS.2 = dns2.alt-name.pg-ssltest.test
diff --git a/src/test/ssl/conf/server-ip-cn-only.config b/src/test/ssl/conf/server-ip-cn-only.config
new file mode 100644
index 0000000000..585d8bdae8
--- /dev/null
+++ b/src/test/ssl/conf/server-ip-cn-only.config
@@ -0,0 +1,12 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+
+[ req ]
+distinguished_name     = req_distinguished_name
+prompt                 = no
+
+[ req_distinguished_name ]
+CN = 192.0.2.1
+OU = PostgreSQL test suite
+
+# No Subject Alternative Names
diff --git a/src/test/ssl/conf/server-ip-in-dnsname.config b/src/test/ssl/conf/server-ip-in-dnsname.config
new file mode 100644
index 0000000000..b15649aef7
--- /dev/null
+++ b/src/test/ssl/conf/server-ip-in-dnsname.config
@@ -0,0 +1,18 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+
+[ req ]
+distinguished_name     = req_distinguished_name
+req_extensions         = v3_req
+prompt                 = no
+
+[ req_distinguished_name ]
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
+subjectAltName = @alt_names
+
+# Normally IP addresses should not go into a dNSName.
+[ alt_names ]
+DNS.1 = 192.0.2.1
diff --git a/src/test/ssl/ssl/server-cn-and-ip-alt-names.crt b/src/test/ssl/ssl/server-cn-and-ip-alt-names.crt
new file mode 100644
index 0000000000..4e58c85ccb
--- /dev/null
+++ b/src/test/ssl/ssl/server-cn-and-ip-alt-names.crt
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDLzCCAhegAwIBAgIIICERKRE1UQAwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UE
+Aww3VGVzdCBDQSBmb3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHNl
+cnZlciBjZXJ0czAeFw0yMTExMjkxOTM1NTFaFw00OTA0MTYxOTM1NTFaMEYxHjAc
+BgNVBAsMFVBvc3RncmVTUUwgdGVzdCBzdWl0ZTEkMCIGA1UEAwwbY29tbW9uLW5h
+bWUucGctc3NsdGVzdC50ZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
+AQEA6+8IYKAFnZ7V+fDo1cyMpbGBLzCfJOQ/1o2jOGP4+GjpsZgv6S6UT2MheC8M
+iiEFrYwdsSIZyYc3jEZrluy/UuR0bCGtqU92BCqa0iBLhvHOgjR588u253eLxQtQ
+8iJn11QPrKMk35nMkmY8GfHt4sGFbvBL6+GpipHq7a6cde3Z+v4kCB5dKMYDUDtm
+3mJmviuGNAu5wOqItk2Yi5dwJs1054007KNH0Il43urxiOfnkLS0cG5kehboPf86
+vxBt3iHByrU/9/DY5IvQCfSXVNa6rb5w5/pGja9aCei6Mv1jQY/V8SMQTga+MOsA
+0WB9akxMi2NxwS2+BQ4k/McPlwIDAQABoyUwIzAhBgNVHREEGjAYhwTAAAIBhxAg
+AQ24AAAAAAAAAAAAAAABMA0GCSqGSIb3DQEBCwUAA4IBAQAQLo2RzC07dG9p+J3A
+W6C0p3Y+Os/YE2D9wfp4TIDTZxcRUQZ0S6ahF1N6sp8l9KHBJHPU1cUpRAU1oD+Y
+SqmnP/VJRRDTTj9Ytdc/Vuo2jeLpSYhVKrCqtjqIrCwYJFoYRmMoxTtJGlwA0hSd
+kwo3XYrALPUQWUErTYPvNfDNIuUwqUXNfS0CXuIOVN3LJ+shegg6Pwbh9B5T9NHx
+kH+HswajhdpdnZIgh0FYTlTCPILDrB49aOWwqLa54AUA6WXa35hPsP8SoqL9Eucq
+ifPhBYyadsjOb+70N8GbbAsDPN1jCX9L8RuNcEkxSCKCYx91cWXh7K5KMPuGlzB7
+j8xB
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-cn-and-ip-alt-names.key b/src/test/ssl/ssl/server-cn-and-ip-alt-names.key
new file mode 100644
index 0000000000..837eef996d
--- /dev/null
+++ b/src/test/ssl/ssl/server-cn-and-ip-alt-names.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA6+8IYKAFnZ7V+fDo1cyMpbGBLzCfJOQ/1o2jOGP4+GjpsZgv
+6S6UT2MheC8MiiEFrYwdsSIZyYc3jEZrluy/UuR0bCGtqU92BCqa0iBLhvHOgjR5
+88u253eLxQtQ8iJn11QPrKMk35nMkmY8GfHt4sGFbvBL6+GpipHq7a6cde3Z+v4k
+CB5dKMYDUDtm3mJmviuGNAu5wOqItk2Yi5dwJs1054007KNH0Il43urxiOfnkLS0
+cG5kehboPf86vxBt3iHByrU/9/DY5IvQCfSXVNa6rb5w5/pGja9aCei6Mv1jQY/V
+8SMQTga+MOsA0WB9akxMi2NxwS2+BQ4k/McPlwIDAQABAoIBAQCuNFKVNdKvrUYF
+RLJGmsAG3+eo9lern7TbML2ht39vu9dBwEMwA6qSa3mdCfBSVUuh9uE9lxY/TU3g
+j2aFi81A4VptNPjLGNblAKhMGnhp7UUzspeRQYuNoSFcnpxoDKtrvK/OIq/pQeBh
+AIfECHRDh+yEG32Tb44FuPQkB1eTYl8xbMEImrhNUaSjJk7tTsmydHy0DjmqHVKX
+HUj0TREfDBDOBiHtY0XV6Pu3bnqDH/TKLTfUf3UdfTuay3Yai9aEcRPWp9GrMO7G
+axsKCifTz6177gyr6Fv8HLeMZMh9rMZRn3e0zfaF6vrH1QnZZOts5jpUa0KugSCd
+//uC0iNxAoGBAPXVc3b+o3hY5gcwwpaW6JtsarDrmNRxrizqIDG7NgpqwdFXgTi6
+6q0t2pjv81ATqij69IcPkNSissyR4OEKnu/OFJWzreg8yLi75WHKi0E/6msHpwRk
+d1yP0Zgd05ots/yOjDSp593RagaPVvHBxMECZ/Tm3B+Tq55Azudd/zvLAoGBAPWw
+xf0oUEJl6NdUZD6K7eFc6jf8yrpD85dldeko6LeN8x0XlKKWvUDJ2+3oizXoQvCm
+8by6KOYEIo4MrtXuy9MmtPWfNvRBr+hsUHchIj7IgFa9bKXyK2FnJqu/8CbEymli
+eZu7hoOhelurhnFy1zSqwNO4GC+kw60Y/BO3Z1nlAoGAVOyYJtNwxXJwhKtjjYI0
+ePzLHrNE6J8c/Ick+AkkchTPP/JqwZ5Q0+KzUYITG+avMdkAAGhwMATEn8cFWLjC
+jzUyB0U7Hq9g5/CBHXdLBA+Ae9j46ZuLYH6OeW5UWz7OnsDfzpGjeA2QAxQhhQLb
+ZZHfN8tI39+zucfJskPWmGECgYEAg9guF1Fn6InJrqwR82IYj6SN6CeXHufSM392
+C/4xDDd3rDf4QlwECV2J0RzGf9I5Ae2EshNwWScE6Be0RweTh6cw2tJq6h7J6D8f
+2x4Dw49TF7klMdRIJUf2f5pLpHJccLswqTqzz7V69PCSABVxmUi8m6EiEYconp5W
+v7nfE2UCgYALrEqzncuSIX3q6TVAjnzT7gO4h8h2TUekIWdHQFldFx8R7Kncggnd
+48gQqhewchNR83UCcd7pPsCcTqu6UR1QRdq/DV5P6J3xdZ2iS/2gCM6hvWIvKZEv
+/ClnkyFCOW7zX6RKIXtRYZTV1kz3TajApi34RTIeIMTieaCarnBJbA==
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-ip-alt-names.crt b/src/test/ssl/ssl/server-ip-alt-names.crt
new file mode 100644
index 0000000000..8a1bc620bb
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-alt-names.crt
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDCTCCAfGgAwIBAgIIICERKREEUAAwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UE
+Aww3VGVzdCBDQSBmb3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHNl
+cnZlciBjZXJ0czAeFw0yMTExMjkxOTA0NTBaFw00OTA0MTYxOTA0NTBaMCAxHjAc
+BgNVBAsMFVBvc3RncmVTUUwgdGVzdCBzdWl0ZTCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBAOM8yB6aVWb17ujr3ayU62mxHQoqn4CvG9yXlJvGOGv/ursW
+Vs0UYJdc96LsNZN1szdm9ayNzCIw3eja+ULsjxCi6+3LM4pO76IORL/XFamlTPYb
+BZ4pHdZVB0nnZAAnWCZPyXdnjOKQ5+8unVXkfibkjj8UELBJ2snehsOa+CTkOBez
+zxYMqxAgbywLIYsW448brun7UXpWmqbGK+SsdGaIZ5Sb7Zezc5lt6CrLemTZTHHK
+7l4WZFCCEi4t3sgO8o1vDELD/IE5G8lyXvIdgJg6t8ssper7iCw6S8x+okhjiSjT
+vDLU2g4AanqZRZB49aPwTo0QUcJA2BCJxL9xLy8CAwEAAaMlMCMwIQYDVR0RBBow
+GIcEwAACAYcQIAENuAAAAAAAAAAAAAAAATANBgkqhkiG9w0BAQsFAAOCAQEAwZJ+
+8KpABTlMEgKnHIYb35ItGhtFiTLQta9RkXx7vaeDwpOdPP/IvuvpjpQZkobRgBsk
+bNM0KuJpd2mSTphQAt6eKQIdcPrkzvc/Yh9OK3YNLUAbu/ZhBUnBvFnUL4wn2f1U
+mfO+m8P/LxybwqKx7r1mbaB+tP3RTxxLcIMvm9ECPQEoBntfEL325Wdoj+WuQH5Y
+IvcM6FaCTkQsNIPbaBD5l5MhMLHRULZujbDjXqGSvRMQfns6np/biMjNdQA8NZ5z
+STeUFvkQbCxoA0YYLgoSHL5KhZjXrg2g+T+2TUyCTR/91xf9OoOjBZdixR0S0DzJ
+B1+5vnUjZaCfnSEA7A==
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-ip-alt-names.key b/src/test/ssl/ssl/server-ip-alt-names.key
new file mode 100644
index 0000000000..b210b3a991
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-alt-names.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEA4zzIHppVZvXu6OvdrJTrabEdCiqfgK8b3JeUm8Y4a/+6uxZW
+zRRgl1z3ouw1k3WzN2b1rI3MIjDd6Nr5QuyPEKLr7cszik7vog5Ev9cVqaVM9hsF
+nikd1lUHSedkACdYJk/Jd2eM4pDn7y6dVeR+JuSOPxQQsEnayd6Gw5r4JOQ4F7PP
+FgyrECBvLAshixbjjxuu6ftRelaapsYr5Kx0ZohnlJvtl7NzmW3oKst6ZNlMccru
+XhZkUIISLi3eyA7yjW8MQsP8gTkbyXJe8h2AmDq3yyyl6vuILDpLzH6iSGOJKNO8
+MtTaDgBqeplFkHj1o/BOjRBRwkDYEInEv3EvLwIDAQABAoIBACp3uY6+mSdc3wF4
+0zzlt/lQuHSl8plCIJrhWUyjhvfoGyXLzv0Uydh/72frbTfZz1yTSWauOXBKYa6a
+/eqb+0DIsf8G8uLuTaqjsAWKVOoXkoKMGkistn7P9UTCkdXVhIvkbWp7V8EgA7iX
+pZ/fzBPIsyzmuxe3NcR0ags0cxuxkNuu+YXDv1oTedmT2wS3CZq1d/T1Y/EOVIf8
+Iznd2aOverlsnt6iiQ3ZWdG/W5F8FhnrR/rrBdYsdCv6TH/KUYexnDOUYpayjDbu
+oAKnifPp6UqiOM4SuBL83OAz19jptp5vpF370BEVRs3eK0q+zo/mETjv9HsXdolZ
+lfoXA0ECgYEA/7nb2azbq/2muvXCh1ZxCEbn3mt8KXoJP/xkx/v9eEc/cc5Q9e0V
+2oGfjC2hSE+bjOWMwiUMD6uU+iRjhz5A3IvUxnoSdoL7H9p0hTqLMyP7dTDkoVF5
+aEuLMaiI5YEnfAFu9L5h8ZKieoQTBoscT06wnGjh9pBV9bthfTKA7ksCgYEA43sb
+55m9WL4kWCPwOAp3vdEAFyxzmZHlO26sEQOU/m5aN01pumYybBruziEXMI96yfTj
+VmXKReeYb6XUiCcs3fLSipD/+8/8CsjO4uMORtxWumXe8AbKZfysGFzL7wJlByGT
+38AGQwIG/XD8cKnaiEMX4E/3Owbcoxwixo3WZC0CgYEAovaqJ9mEU+Jc8h/TS7PG
+bGPjN1Z/1V6zrlcFUnw/Vvrwb3HvHglsN8cLCaW6df5lPjC6tq4tNX8+fPnbg0Ak
+zWc+vQzl3ygxKGdqgcyBEKIJiPETgcoN+GzL02V3d+oKY3f2YXlBqVSsvi6UgUL9
+U3zuB36/IQVyAhrbUZFxoGkCgYEAnaFAO+Nvrp/LhXwZyGuQf+rkmipGTIMpil5t
+QzjtNMV5JFszSWPpyrl7A0Ew1YiG+I0GP2c3m+sY2TzbIiGrWH0b4cMKbw63Qy3V
+FqlpyjaCrpVKv56k/7jv883RzuQk56Uf1+szK5mrCFITy2oXsVZ0pA4lbjSaDTjA
+7D968V0CgYEA+qKqXKL98+c5CMPnpf+0B1x2zgyUym1ouPfon2x5fhK84T53zDMA
+zfdUJ/SOZw6/c9vRF7RL8h+ZfFdIyoAXv4Tt6mIiZe7P+AUVg6XgJ0ce2MUSeWjI
+W8D4WdSi0jyqr99TuVBWhbTZJviMB3pHqKaHQ07hnd/lPtvzsiH12qk=
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-ip-cn-and-alt-names.crt b/src/test/ssl/ssl/server-ip-cn-and-alt-names.crt
new file mode 100644
index 0000000000..2be02feb03
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-and-alt-names.crt
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDHTCCAgWgAwIBAgIIICIBBBQ2MQAwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UE
+Aww3VGVzdCBDQSBmb3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHNl
+cnZlciBjZXJ0czAeFw0yMjAxMDQyMjM2MzFaFw00OTA1MjIyMjM2MzFaMDQxHjAc
+BgNVBAsMFVBvc3RncmVTUUwgdGVzdCBzdWl0ZTESMBAGA1UEAwwJMTkyLjAuMi4x
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwmqTdQJfs2Ti9tPitYp2
+27I0HvL/kNSgA6egFr0foRo0BorwJNIzdbV0+EnsfiBNTWL5It26gqO7UP3ms8t2
+vHD5gkXfT+f6ts0lVJEcIOkUD/8ws4Ic9Y4uPqb4gN+pUKqcxtmLW1TYk84MBK59
+Xz4yPPS6N+G/DMMeFHTNkM9EQwn/+DC3fDsWdGYM2GRWDTJGg1A5tSUcF+seu7i1
+Vg7XajBfsvgAUAsrAxV+X/sLZh94HY+paD6wfaI99mY2OXVc/XW/z1r9WQznor65
+ZkonNCaPfavqPG5vqnab9AyQcqPqmX8hf/xrniASBAkqNCctbASrFCIYvCJfGfmX
+EQIDAQABoyUwIzAhBgNVHREEGjAYhwTAAAIChxAgAQ24AAAAAAAAAAAAAAABMA0G
+CSqGSIb3DQEBCwUAA4IBAQBf7kmYfRYfnWk1OUfY3N1kaNg9piBBlFr9g+OQn9KU
+zirkN7s0ZQbCGxV1uJQBKS58NyE414Vorau77379emgYDcCBpDIYpkLiNujVrIOr
+ggRFKsFRgxu4/mw0BSgCcV8RPe9SWHZ90Mos7TMCnW/PdxOCD1wD0YMkcs0rwB3l
+0Kzc7jDnfOEvmgw/Ysm7v67ps+05Uq5VskQ6WrpSAw6kPD/QMuuBAX8ATPczIaox
+zAMyncq1IiSIwG93f3EoQQThdQ70C6G9vLcu9TtL6JAsEMFEzR99gt1Wsqvmgl9W
+kStzj1yjIWeo5gIsa4Jgcke1lZviWyrTxHDfyunYE5i5
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-ip-cn-and-alt-names.key b/src/test/ssl/ssl/server-ip-cn-and-alt-names.key
new file mode 100644
index 0000000000..54fe80fc68
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-and-alt-names.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAwmqTdQJfs2Ti9tPitYp227I0HvL/kNSgA6egFr0foRo0Borw
+JNIzdbV0+EnsfiBNTWL5It26gqO7UP3ms8t2vHD5gkXfT+f6ts0lVJEcIOkUD/8w
+s4Ic9Y4uPqb4gN+pUKqcxtmLW1TYk84MBK59Xz4yPPS6N+G/DMMeFHTNkM9EQwn/
++DC3fDsWdGYM2GRWDTJGg1A5tSUcF+seu7i1Vg7XajBfsvgAUAsrAxV+X/sLZh94
+HY+paD6wfaI99mY2OXVc/XW/z1r9WQznor65ZkonNCaPfavqPG5vqnab9AyQcqPq
+mX8hf/xrniASBAkqNCctbASrFCIYvCJfGfmXEQIDAQABAoIBAB6GgVST5NbT9lbu
++d+rN/JSzqA1Yy8oU19/iEFJvJec96I3WnFNl8rZjN4XLUy4YarO6XMyAUDV2Gll
+FD4Sqjf4PRTZR7DSKaleGIhoqFP6hK3mUY091rIves9XhBkoBPunbipCqgDTF5ZN
+edGaXBECQP0VJ8/yX/7u++AWXthnjDis9X0taZfFg/PYbV7SCJ1Hg1O/wEsgXlnC
+7mbL6wkCW0f6700B0x1kKbZqJY95xRqp6Ipq2lIQbJDdGywoj0WzKqNltf9cer+r
+cXl8WjeiMvvvpl4uGhckAbzUifUzxN6A3f1fu/XKtOmabMi9t7J4MRfgOgedgtQB
+0jaZGSkCgYEA+lBLnNY6M48HX2mdtr86+n41gh69v8Z7oNikJFDZkodrvI8uqE0i
+0XwnYPFddt8NbmuUhhuzI2M8RKhGLgdlbKpkSSVafnMfcxRmX2EAtWQgdvX1Iult
+752LWdBgSuw2vlzvy3T/GYnjMrXSCGput4amqojMEbvUGvIdSUMdHGMCgYEAxtU1
+WixKPL6aEnYy1f4bybzcNgGtl8PBRz9xw+P46g+ijOPoaG9O73Tr7An11AO003Ot
+DHhMW+b8yHLyxoKwS2sU2cN/lKB8xNQYZc1D61RNJlzgnHMXnA0lcH0I3M35fqKr
+/71pD1ZP40SSJS+od/KEjW80XzuOdyiXg8q81vsCgYEAnUPLbbsuj+whzrFVlFZr
+IKwgxCK6Rn3WeIUEA4kEWUpZxvsSbk0gPgtJ1l9uwFt9Xc2bX/KRRv93Aw/SH+Mn
+tvEK1uXwCBgePzgm5W/VeSFyQCthm1CbcHtD7Oa9SPVFo65SPjrAd3QpWVfgoMb1
+zrp7hhMyW0XuCgvpmHjhFk8CgYEAxq/thXM2p+bLLWGhwQcRG5G299zLbBl4PUsf
+0uEvLi17gJCKADoiRdSvoAn/9eHSQ26XYRuhKkDzHxcGlOmpY2PYzRa3mXyZ0VIk
+Iy5wDWwLQCeVZ6D22cClRfgb8BF/nFTPzVmn72SPpgoyhChQj7PvUynpyrRH07jj
+VxYziBsCgYAFr37Xbl0VnXVK+XU+vMwUZjcF4jpoCr7SFZqgRbW2GbYSUoMuPXns
+RnJh+Fvi1NUei+E5s1H4P1pVq4p0jFxP4GvH/qvNjnIn/Er3bbqvpox6dWUJXprq
+qTQSDIeoDC/V8cyRoIfqPvTVqY8Rgew6GEkv0bAImdxhoSng7vIseg==
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.crt b/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.crt
new file mode 100644
index 0000000000..23c06da01c
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.crt
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDQzCCAiugAwIBAgIIICIBBBQ2MQEwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UE
+Aww3VGVzdCBDQSBmb3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHNl
+cnZlciBjZXJ0czAeFw0yMjAxMDQyMjM2MzFaFw00OTA1MjIyMjM2MzFaMDQxHjAc
+BgNVBAsMFVBvc3RncmVTUUwgdGVzdCBzdWl0ZTESMBAGA1UEAwwJMTkyLjAuMi4x
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8xddbo/x2TOSIa/br8BN
+o/URdTr9+l2R5YojiZKDuLxiQVkgC30PJ2/CNFKIh2nHhRrzknI6sETVtrxZ+9V2
+qRc1yShVu462u0DHPRMIZnZIOZg3hlNB0cRWbOglUKlttIARNEQUcTUyPOtyo4/v
++u0Ej5NTNcHFbFT01vdD9MjQiCO3jKdAwPIb14jTg4C71EpZ+LuelDo4DzF2/XgG
+WqUTrgD/XnBU/60PU9Iy3G0nVpx21q6ppn9G7a9R+i8FjBcwW1T+cfsBDWhAv+bi
+RmSAkENf8L8TwOlDQUwROkfz3Hz36vuJjdkreQJsiqL0HnrnH5T5G9UzJO86FvZQ
+5wIDAQABo0swSTBHBgNVHREEQDA+gh1kbnMxLmFsdC1uYW1lLnBnLXNzbHRlc3Qu
+dGVzdIIdZG5zMi5hbHQtbmFtZS5wZy1zc2x0ZXN0LnRlc3QwDQYJKoZIhvcNAQEL
+BQADggEBAF+mfaw6iBPzpCgqq830pHRa3Yzm1aezt8SkeRohUYHNv/yCnDSRaqtj
+xbENih3lJMSTBL3g0wtTOHfH8ViC/h+lvYELHzXKic7gkjV7H5XETKGr0ZsjBBT2
+4cZQKbD9e0x0HrENXMYgGpBf747qL6uTOVJdG0s15hwpLq47bY5WUjXathejbpxW
+prmF8F+xaC52N9P/1VnqguQB909F4x1pyOK7D7tjFu+Y8Je7PHKbb6WY5K6xAv6t
+R17CY0749/FotlphquElUR2bs5Zzv5YrjUHPTcbwKvcH5cdNi93/u6NJt2xNAoYf
+aZERhX5TA9DYk4gC8OY0yGaYCIj3Dd4=
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.key b/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.key
new file mode 100644
index 0000000000..0ace41e0a1
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEogIBAAKCAQEA8xddbo/x2TOSIa/br8BNo/URdTr9+l2R5YojiZKDuLxiQVkg
+C30PJ2/CNFKIh2nHhRrzknI6sETVtrxZ+9V2qRc1yShVu462u0DHPRMIZnZIOZg3
+hlNB0cRWbOglUKlttIARNEQUcTUyPOtyo4/v+u0Ej5NTNcHFbFT01vdD9MjQiCO3
+jKdAwPIb14jTg4C71EpZ+LuelDo4DzF2/XgGWqUTrgD/XnBU/60PU9Iy3G0nVpx2
+1q6ppn9G7a9R+i8FjBcwW1T+cfsBDWhAv+biRmSAkENf8L8TwOlDQUwROkfz3Hz3
+6vuJjdkreQJsiqL0HnrnH5T5G9UzJO86FvZQ5wIDAQABAoIBAGv0BFoFMrHyZQLw
+xe7Wx6P4QTh+aiu1QgVdw0pk9nojrr62hbSUZRZuWyBBRsBcCW7i+Sgf8lA1QXNV
+UeC0e228EPa0It6YEi42JkTJHwHhpVFud7n/X0t4lajnryqTE1UFSp6bXTipFxZW
+uSJJ2ZjliRD5rApDcxkY4WJVjKg3aEt7P/DiM8iKGfyE6stq72VjEbJjdViMEcOP
+BNf0TiREZz5Mp7jAVWhpen0ebbLOBVWV4/ONNcL+yqR4mCEDUSFGewrTVX4zHL0A
+hYk198C5F8sFvEDnFkPco9sXMVanmLoI8sbhP4IIz9g4+GU6kFuj7fUKp11Azqv+
+3WQDKYECgYEA/XG4mmG/g8FG44y42mfZpUXWi1pwU4CQIrhkoU5j7EPQrvRboOOE
+Rv95jSwyZu4vCqjyI5FN1jCGTdhmt++R1e//zH6Hqa9Smo+jw7DtAFrCYd1JnCf1
+ToOwsYPHv4P7A8q8kc5vCNIv+AQSlP/wqdVNo3grdf7cGXkMtEY4F9UCgYEA9Yrq
+zWdnNGPATuSBqL6TSjQ37oR+dBD6WnGsiDenQkOzyDPFZ3CT1DjJghjEtxc8EfNf
+Oo8dMMR2q+5FZQo7WuqONEgyzKePiNR8RK2gOYpgdjN9bih1sAhHR10D26cpwlDJ
+bx7D5ZzENLbdZmfEiWwKswnaIhN4yMalgE0mP8sCgYAhzJy12ftUct4lUosEdX0N
+EXc/NlxshmSyfKzO5kllJNYbvvLJTg5B+agYL6C5IWKcpVNFcwdSXT5L+2QXe5eT
+VGJkvysQchUuD6HjYyD4PyJVMtGyRZHtWpqh0dU9sTg0lUD4oPMl1gIXrVNdE5Tg
+0VV9S3VgUxC/ROlw0TyB0QKBgGsVE0NS9hJF8mc1hko2GnwA++d8Rr2NbfElo+2f
+/8SJTA1ibpOm6AFkZpTjAl8qtdrKPVyHb16GP47Jkd/3r1z979hjKCxSYul0aWF2
+KusNKvZBjFEPOgv0AEniCb2wUCjbHI3mZ95qGLM4kKOJW4/m21+rS0MTJNjCsQic
+HLMzAoGAeCsY09d3m8xGeU+DuTPC6GH7Sgy/NBYqS5VaVNjb2jnuZlW2SSW2oiID
+4tXTi4ruKmHC898BfyFxhSMqub+tg3pVqIYADC71rnJLrVyc1SzoWzL7yMT3qFj7
+C7ZYZYmfG9agcZb5NkqKPTfCxkBhWbdgTTgBKVO/xQst8EUgko8=
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-ip-cn-only.crt b/src/test/ssl/ssl/server-ip-cn-only.crt
new file mode 100644
index 0000000000..9bf015cf18
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-only.crt
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC8TCCAdkCCCAhESkRN1IAMA0GCSqGSIb3DQEBCwUAMEIxQDA+BgNVBAMMN1Rl
+c3QgQ0EgZm9yIFBvc3RncmVTUUwgU1NMIHJlZ3Jlc3Npb24gdGVzdCBzZXJ2ZXIg
+Y2VydHMwHhcNMjExMTI5MTkzNzUyWhcNNDkwNDE2MTkzNzUyWjA0MR4wHAYDVQQL
+DBVQb3N0Z3JlU1FMIHRlc3Qgc3VpdGUxEjAQBgNVBAMMCTE5Mi4wLjIuMTCCASIw
+DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANWs1uUL71nHYF9Zj6p+M3MpYDvx
+32iCjVdtH5a2qpSWHXTg0rR8dLX0y92cvOYvMXHRajZT1avpHr8dooPYSVaXpGMK
+NvF/Qi+WFYovRbP2vmd1yv1cgW/FggbwJFWVobizIz4seyA4d0B2j9fqoi2OFBNP
+huW664SjF0u3p21tDy+43i2LNUMAKf6dnRR5Vqenath87LEU41tSLudu6NXgbFMk
+jvfNkl4d0w7YCzeXmklmSI+uaX3PlJJ4NzQO2j8w5BvnKVhNVD0KjgrXZ6nB/8F7
+Pg3XY+d7rJlwRgXemU6resWQDJ7+UaC9u7I4EIP+9lzCR/nNBqUktpHRmHUCAwEA
+ATANBgkqhkiG9w0BAQsFAAOCAQEAos1JncV8Yf4UaKl6h1GdYtcVtzFyJvBEnhRD
+07ldL+TYnfZiX8wK2ssBtM3cg/C78y5bzdUa5XGS83ZKQJFFdhE7PSnrvyNqyIqY
+ZgNBxto3gyvir+EjO1u9BAB0NP3r3gYoHRDZS1xOPPzt4WgjuUgTLM9k82GsqAbO
+UrOTOdRnkIqC5xLpa05EnRyJPRsR1w1PRJC2XXKnHIuFjMb4v7UuPwyCcX1P5ioc
+rQszQcORy/L+k0ezCkyweORg68htjYbBHuwOuiGfok6yKKDMzrTvD3lIslls6eX7
+4sI3XWqzkPmG9Vsxm9Vu9/Ma+PRO76VyCoIwBd+Ufg5vNXhMmw==
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-ip-cn-only.key b/src/test/ssl/ssl/server-ip-cn-only.key
new file mode 100644
index 0000000000..1966530e72
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-only.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEA1azW5QvvWcdgX1mPqn4zcylgO/HfaIKNV20flraqlJYddODS
+tHx0tfTL3Zy85i8xcdFqNlPVq+kevx2ig9hJVpekYwo28X9CL5YVii9Fs/a+Z3XK
+/VyBb8WCBvAkVZWhuLMjPix7IDh3QHaP1+qiLY4UE0+G5brrhKMXS7enbW0PL7je
+LYs1QwAp/p2dFHlWp6dq2HzssRTjW1Iu527o1eBsUySO982SXh3TDtgLN5eaSWZI
+j65pfc+Ukng3NA7aPzDkG+cpWE1UPQqOCtdnqcH/wXs+Dddj53usmXBGBd6ZTqt6
+xZAMnv5RoL27sjgQg/72XMJH+c0GpSS2kdGYdQIDAQABAoIBAQDNXviU4WnF8rmQ
+K7bH+dBdqbETLKC8BG7xTrMD2sINWlMpmUUrsEtE7+paMGHnJAj0CoF5gg5m0wN4
+UXV4H5QtpEad4p14dAYbUreVP2ZRWKEdM7xM1HKcCUu2e22QzObJbXQ8N+iHyX3k
++Y+7yYrjGiH1hYR0nbnsnAyx++zyYBSQeqzpdQwf/BLY5xZmyYWNfqbckiMpEqMs
+EmZmGXnCjIipzEC0LQHoSW9PNa92Z9bvuxOKYl8iHYDDXjvMRFoZBSiMXpzHQocb
+QlQ5F4ayfW2OrOhpNbY7niYM9GN3Bk9TgMP+0BkJE6uuktLYW35LY1M78CCPWcWb
+npJNK3QBAoGBAOxkGrhAHAysSmtirIyMdvySb76wb/Ukfi+AULKz20FI5j4/GXm9
+qCb2GeT+FFSUHeSC8f0EFnosRYkdBGruqeZioI+5rUkboYFJPspAHAuvg9kgtfF+
+kvphD4O4P/foYsEZRx66FHozDbhrrR5UXc7KzqRIASc/D3FOx2UFJLb1AoGBAOdm
+WcaMvYygl9ZW+ThWAR1xG1X70AGKwrlrpF2hBkWYxSurxSMXnD0DUzC9Nb4EyCaM
+c2uSqEZOKdW+XfXtK2DnqXKfb3YCVEoGN4gVfyuW/vxii/+ZxLo3md/b3vrkZEVp
+pfkXy/HoZ71YN7bNpcDpOnhml6vvuCRCYFnI1WuBAoGAC0shB6pwbJ6Sk5zMN47C
+ZICufAK75o9OxAAyWsdC81SDQ3gKRImuDeZ2CD2nRP8qim9DFl5qoH2a+Nj9DArI
+7SvLFfK9958tURrpuAnmDRzehLIOXzI33WRjtFxKGhLtHOKTRkGHlur3fdcPF0La
+lHWV971E6NYXa8diuU3Mmj0CgYBYd+ka3/QYL83dRKNDxp3mg7fPx9ZewI5yFZVh
+to6PTTkU2Tclk4FIUl0b5TsGyw06r7fxCMENIBUegwmpXGOZSPifuhUDKSDQrE/O
+12knYTNbitG7hy6Pg3JxA77cbTVo1FuAQHjYo+IFohSq7zTP7FtObOrP8XaVZksw
+CHiQAQKBgBW4EiA9AAnZ1LOpifAvM7bs0NHg95qTwtAL52WKom2ga2H+lMhxeu6Y
+hUSytC/f9kALVcYloZhkLYpO07x1gXmy7f4parMjA4Ex+4vfu3kPd8GiNGZ+AUJD
+nnJ1OINY9ziXJZfju7FpVWpkiuPzWCh6y/o3gZ/veq5mIUxuDMVa
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-ip-in-dnsname.crt b/src/test/ssl/ssl/server-ip-in-dnsname.crt
new file mode 100644
index 0000000000..78ad8d99c8
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-in-dnsname.crt
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC/DCCAeSgAwIBAgIIICIDFRVYUgAwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UE
+Aww3VGVzdCBDQSBmb3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHNl
+cnZlciBjZXJ0czAeFw0yMjAzMTUyMjU4NTJaFw00OTA3MzEyMjU4NTJaMCAxHjAc
+BgNVBAsMFVBvc3RncmVTUUwgdGVzdCBzdWl0ZTCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBAMpn5bP1/OfBQR/yvOkOBzxArE1j1YShVa2pcj896+CVDEgV
+N5Hluz7KHU/JYzNZCAHb5WAHuvXxKeoj4Ti5be1KsqO0mN1p+RMN7VlCpCpb0AWT
+z4z+I8TUhSZnmgghHvfW4RfcZMCcHq1vevVTDxR/cAbDPYpgBCD5F/SZMRyMDw5B
+7ILLmft0eqA1nCqavyqBCGZvx1ol8N5BfVdrDXp/rN5997khBWQRZ8g84FZyFZXf
+pwp57eu0OGQDzZFXoEL2t4OVld67K5jcclWVxHY6FGcHjCvyqs48PCPOR84anZwj
+GsqVOS6250/DWKBQO4KyhkTVf0AW/ICGSMOKkAkCAwEAAaMYMBYwFAYDVR0RBA0w
+C4IJMTkyLjAuMi4xMA0GCSqGSIb3DQEBCwUAA4IBAQDIAAH0WJKEpbPN0QihN6SF
+UA5WL4ixsBACo9OIAGkSnKeOeVEG5vvgOna0hjQcOcgtI1oCDLhULcjCuwxiIW6y
+QntOazyo0sooJr0hEm2WfipvIpQs6W9E1OTcs624BAVfkAwr6WT2VwoIAPcQD2nR
+tIQhSUIR9J7Q5WbzuQw7pthQhBfW/UPWw7vajel0r1dflbe0Cgp5WGNfp1kYy+Qf
+XW/YjkstZEP1KFm+TF58uxrIDmYboS8EerUREGQixijbI0AfXjShxtiyS63rbdpo
+3C0BPj9Yx2VtWi4U0qoef/iLJxJBCLvE/97+duPdKx0AkkOWA9VuenkWLp797UM8
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-ip-in-dnsname.key b/src/test/ssl/ssl/server-ip-in-dnsname.key
new file mode 100644
index 0000000000..ba319b001e
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-in-dnsname.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAymfls/X858FBH/K86Q4HPECsTWPVhKFVralyPz3r4JUMSBU3
+keW7PsodT8ljM1kIAdvlYAe69fEp6iPhOLlt7Uqyo7SY3Wn5Ew3tWUKkKlvQBZPP
+jP4jxNSFJmeaCCEe99bhF9xkwJwerW969VMPFH9wBsM9imAEIPkX9JkxHIwPDkHs
+gsuZ+3R6oDWcKpq/KoEIZm/HWiXw3kF9V2sNen+s3n33uSEFZBFnyDzgVnIVld+n
+Cnnt67Q4ZAPNkVegQva3g5WV3rsrmNxyVZXEdjoUZweMK/Kqzjw8I85HzhqdnCMa
+ypU5LrbnT8NYoFA7grKGRNV/QBb8gIZIw4qQCQIDAQABAoIBAA2kPP4JCTeRddMy
+Z/sJIAG2liZNITnkKcMflXyfrsMfKIm/LFSf+CO+OYWEHDR8vqZpbKcxPi+PRnTq
+YCaTkM4aZ7nS1S6vEsNu/90xOaFFONr3YFivVDfS3vp8pwv/N3gaumcCSqQUoZis
+18urAmwuPp2mEQK/f+e9AhlRLdcvlqDyKm+zMrVixK77Hj5JiEkh3rfZ3onHHKGE
+B7T2XRRqnZ4FCN9qLH2pMGUknZ4MGC9SlCyoerXFodb4DhKWQhJDRLjb8qP96r/E
+FGSg5WUiAERU/OgODoqZNTeIwIDB/f9NK45dEY3Hw6BsSFfU2VChrlNoVlzFUx2k
+yaH5Y4ECgYEA8rht3crh3GTy0jBJjNqB2iul8fkG/uiaiSvERWT/+KZnmV1+JGAW
+h2/wvd5apagOJjqKY0bCHMei/qYF9r4yJnkIy4qNper3QUz7TMCjsWduCm8S834A
+Z+Vwi3RBGJiQQH9Dfexko5sDjo+w5g4RsH52INCeReInNdxHOv06jZECgYEA1XrR
+QNwZlxHt3H93YKmKDZXikqW12Cuq6RSwf5VVdeuzV+pUN+/JaSgEuYsBilW7Q5p2
+gPROi0l8/eUPsBJb+dh1BcGzSjI2Kkzf66QOTG83S7tCPwQhwJUAylFuADvURjPQ
+qvqNjbQUomdm2QjBzyWtiFbolqxBgM3dnE6R/vkCgYBYGqQexx83LhmKPGbmTwal
+mARzkg59BxfZRN7IxcG4k0a1v98i+xISdYqwkP7cdOU18Tf8k1mwsrKytrcheqaf
+mn2bzJ5gJKs9s+DgWmjQ45dpCCqb4hfpnro8lKVwdSifkNKB6gYZ8RHYdMYkq+S1
+6SGeBbv95/qNrXjZq8POUQKBgHyaDwD4dsdCY79LdvYofrenQHOv3Q+rjTo2JT6S
+fysww6EQ2M89WiXSgc96Xw/LMl4nDfv+nMmXvyjCRgHS9XRC7yrJAEjSPeM6s4fq
+XZ4nW/ML/YKiesDZN3jfRoFEaoX/QFBLpcuLzG9uQw1ymwy5RSxK7b7kE+eGQU82
+XOihAoGBAI3xvT9fG3jRsSuw/8OQBlmDUFZcT0fRPRZ3pg8XlSreAam4b607d2WY
+u/bBHIclG3CLJ2EFqBtxl9AQeM0OTweF0KmV3dbtdBmaTbnhbK8/NLYnl5+aosEJ
+YrFKD8k8z6z+mYQs+7bAnfRa53TjfC7f24BpgEQyEfKL2fa3PF+J
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/sslfiles.mk b/src/test/ssl/sslfiles.mk
index 7ed3a30f5c..d6a3870079 100644
--- a/src/test/ssl/sslfiles.mk
+++ b/src/test/ssl/sslfiles.mk
@@ -22,9 +22,15 @@
 # key/certificate pair will be generated for you, signed by the appropriate CA.
 #
 SERVERS := server-cn-and-alt-names \
+	server-cn-and-ip-alt-names \
 	server-cn-only \
+	server-ip-cn-only \
+	server-ip-cn-and-alt-names \
+	server-ip-cn-and-dns-alt-names \
+	server-ip-in-dnsname \
 	server-single-alt-name \
 	server-multiple-alt-names \
+	server-ip-alt-names \
 	server-no-names \
 	server-revoked
 CLIENTS := client client-dn client-revoked client_ext
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 5c5b16fbe7..07e5b71d8c 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -249,6 +249,30 @@ $node->connect_fails(
 	  qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/
 );
 
+# Test with an IP address in the Common Name. This is a strange corner case that
+# nevertheless is supported, as long as the address string matches exactly.
+switch_server_cert($node, 'server-ip-cn-only');
+
+$common_connstr =
+  "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+
+$node->connect_ok("$common_connstr host=192.0.2.1",
+	"IP address in the Common Name");
+
+$node->connect_fails(
+	"$common_connstr host=192.000.002.001",
+	"mismatch between host name and server certificate IP address",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.1" does not match host name "192.000.002.001"\E/
+);
+
+# Similarly, we'll also match an IP address in a dNSName SAN. (This is
+# long-standing behavior.)
+switch_server_cert($node, 'server-ip-in-dnsname');
+
+$node->connect_ok("$common_connstr host=192.0.2.1",
+	"IP address in a dNSName");
+
 # Test Subject Alternative Names.
 switch_server_cert($node, 'server-multiple-alt-names');
 
@@ -301,7 +325,54 @@ $node->connect_fails(
 	  qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/
 );
 
-# Test server certificate with a CN and SANs. Per RFCs 2818 and 6125, the CN
+# Test certificate with IP addresses in the SANs.
+switch_server_cert($node, 'server-ip-alt-names');
+
+$node->connect_ok(
+	"$common_connstr host=192.0.2.1",
+	"host matching an IPv4 address (Subject Alternative Name 1)");
+
+$node->connect_ok(
+	"$common_connstr host=192.000.002.001",
+	"host matching an IPv4 address in alternate form (Subject Alternative Name 1)");
+
+$node->connect_fails(
+	"$common_connstr host=192.0.2.2",
+	"host not matching an IPv4 address (Subject Alternative Name 1)",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.1" (and 1 other name) does not match host name "192.0.2.2"\E/
+);
+
+$node->connect_fails(
+	"$common_connstr host=192.0.2.1/32",
+	"IPv4 host with CIDR mask does not match",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.1" (and 1 other name) does not match host name "192.0.2.1\/32"\E/
+);
+
+$node->connect_ok(
+	"$common_connstr host=2001:DB8::1",
+	"host matching an IPv6 address (Subject Alternative Name 2)");
+
+$node->connect_ok(
+	"$common_connstr host=2001:db8:0:0:0:0:0:1",
+	"host matching an IPv6 address in alternate form (Subject Alternative Name 2)");
+
+$node->connect_fails(
+	"$common_connstr host=::1",
+	"host not matching an IPv6 address (Subject Alternative Name 2)",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.1" (and 1 other name) does not match host name "::1"\E/
+);
+
+$node->connect_fails(
+	"$common_connstr host=2001:DB8::1/128",
+	"IPv6 host with CIDR mask does not match",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.1" (and 1 other name) does not match host name "2001:DB8::1\/128"\E/
+);
+
+# Test server certificate with a CN and DNS SANs. Per RFCs 2818 and 6125, the CN
 # should be ignored when the certificate has both.
 switch_server_cert($node, 'server-cn-and-alt-names');
 
@@ -319,6 +390,39 @@ $node->connect_fails(
 	  qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/
 );
 
+# But we will fall back to check the CN if the SANs contain only IP addresses.
+switch_server_cert($node, 'server-cn-and-ip-alt-names');
+
+$node->connect_ok("$common_connstr host=common-name.pg-ssltest.test",
+	"certificate with both a CN and IP SANs matches CN");
+$node->connect_ok("$common_connstr host=192.0.2.1",
+	"certificate with both a CN and IP SANs matches SAN 1");
+$node->connect_ok("$common_connstr host=2001:db8::1",
+	"certificate with both a CN and IP SANs matches SAN 2");
+
+# And now the same tests, but with IP addresses and DNS names swapped.
+switch_server_cert($node, 'server-ip-cn-and-alt-names');
+
+$node->connect_ok("$common_connstr host=192.0.2.2",
+	"certificate with both an IP CN and IP SANs 1");
+$node->connect_ok("$common_connstr host=2001:db8::1",
+	"certificate with both an IP CN and IP SANs 2");
+$node->connect_fails(
+	"$common_connstr host=192.0.2.1",
+	"certificate with both an IP CN and IP SANs ignores CN",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.2" (and 1 other name) does not match host name "192.0.2.1"\E/
+);
+
+switch_server_cert($node, 'server-ip-cn-and-dns-alt-names');
+
+$node->connect_ok("$common_connstr host=192.0.2.1",
+	"certificate with both an IP CN and DNS SANs matches CN");
+$node->connect_ok("$common_connstr host=dns1.alt-name.pg-ssltest.test",
+	"certificate with both an IP CN and DNS SANs matches SAN 1");
+$node->connect_ok("$common_connstr host=dns2.alt-name.pg-ssltest.test",
+	"certificate with both an IP CN and DNS SANs matches SAN 2");
+
 # Finally, test a server certificate that has no CN or SANs. Of course, that's
 # not a very sensible certificate, but libpq should handle it gracefully.
 switch_server_cert($node, 'server-no-names');
-- 
2.25.1

v7-0003-squash-libpq-allow-IP-address-SANs-in-server-cert.patchtext/x-patch; name=v7-0003-squash-libpq-allow-IP-address-SANs-in-server-cert.patchDownload
From ddbc3c20bba18ab929eb8fe656432dd177b0e6d1 Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Wed, 5 Jan 2022 15:47:03 -0800
Subject: [PATCH v7 3/3] squash! libpq: allow IP address SANs in server certs

Per review, provide a pg_inet_pton() interface for IPv6 and refactor the
internals accordingly. IPv4 is not yet implemented. The call sites that
just want standard addresses without a CIDR mask have been updated to
use the new function.

Internally, the IPv6 parser now takes an allow_cidr boolean. I've
plumbed that all the way down to getbits() in order to minimize the
amount of code touched, at the expense of an unnecessary function call
in the failing case.
---
 src/include/port.h                       |  1 +
 src/interfaces/libpq/fe-secure-common.c  |  7 +--
 src/interfaces/libpq/fe-secure-openssl.c |  2 +-
 src/port/inet_net_pton.c                 | 66 ++++++++++++++++++------
 4 files changed, 52 insertions(+), 24 deletions(-)

diff --git a/src/include/port.h b/src/include/port.h
index 2852e5b58b..fd046f4c24 100644
--- a/src/include/port.h
+++ b/src/include/port.h
@@ -517,6 +517,7 @@ extern char *pg_inet_net_ntop(int af, const void *src, int bits,
 
 /* port/inet_net_pton.c */
 extern int	pg_inet_net_pton(int af, const char *src, void *dst, size_t size);
+extern int	pg_inet_pton(int af, const char *src, void *dst);
 
 /* port/pg_strong_random.c */
 extern void pg_strong_random_init(void);
diff --git a/src/interfaces/libpq/fe-secure-common.c b/src/interfaces/libpq/fe-secure-common.c
index 2b8f2d6b24..4d78715756 100644
--- a/src/interfaces/libpq/fe-secure-common.c
+++ b/src/interfaces/libpq/fe-secure-common.c
@@ -211,12 +211,7 @@ pq_verify_peer_name_matches_certificate_ip(PGconn *conn,
 
 		family = PGSQL_AF_INET6;
 
-		/*
-		 * pg_inet_net_pton() will accept CIDR masks, which we don't want to
-		 * match, so skip the comparison if the host string contains a slash.
-		 */
-		if (!strchr(host, '/')
-			&& pg_inet_net_pton(PGSQL_AF_INET6, host, addr, -1) == 128)
+		if (pg_inet_pton(PGSQL_AF_INET6, host, addr) == 1)
 		{
 			if (memcmp(ipdata, addr, iplen) == 0)
 				match = 1;
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index 0eab797821..c6a80d30c2 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -554,7 +554,7 @@ is_ip_address(const char *host)
 	unsigned char	dummy6[16];
 
 	return inet_aton(host, &dummy4)
-		|| (pg_inet_net_pton(PGSQL_AF_INET6, host, dummy6, -1) == 128);
+		|| (pg_inet_pton(PGSQL_AF_INET6, host, dummy6) == 1);
 }
 
 /*
diff --git a/src/port/inet_net_pton.c b/src/port/inet_net_pton.c
index 5bae811c6f..ae0d1fd2e3 100644
--- a/src/port/inet_net_pton.c
+++ b/src/port/inet_net_pton.c
@@ -42,8 +42,8 @@ static const char rcsid[] = "Id: inet_net_pton.c,v 1.4.2.3 2004/03/17 00:40:11 m
 
 static int	inet_net_pton_ipv4(const char *src, u_char *dst);
 static int	inet_cidr_pton_ipv4(const char *src, u_char *dst, size_t size);
-static int	inet_net_pton_ipv6(const char *src, u_char *dst);
-static int	inet_cidr_pton_ipv6(const char *src, u_char *dst, size_t size);
+static int	inet_pton_ipv6_internal(const char *src, u_char *dst, size_t size,
+									bool allow_cidr);
 
 
 /*
@@ -74,9 +74,9 @@ pg_inet_net_pton(int af, const char *src, void *dst, size_t size)
 				inet_net_pton_ipv4(src, dst) :
 				inet_cidr_pton_ipv4(src, dst, size);
 		case PGSQL_AF_INET6:
-			return size == -1 ?
-				inet_net_pton_ipv6(src, dst) :
-				inet_cidr_pton_ipv6(src, dst, size);
+			return inet_pton_ipv6_internal(src, dst,
+										   (size == -1) ? 16 : size,
+										   true /* allow CIDR */);
 		default:
 			errno = EAFNOSUPPORT;
 			return -1;
@@ -352,13 +352,22 @@ emsgsize:
 }
 
 static int
-getbits(const char *src, int *bitsp)
+getbits(const char *src, int *bitsp, bool allow_cidr)
 {
 	static const char digits[] = "0123456789";
 	int			n;
 	int			val;
 	char		ch;
 
+	if (!allow_cidr)
+	{
+		/*
+		 * If we got here, the top-level call is to pg_inet_pton() and the
+		 * caller supplied a CIDR mask, which isn't allowed.
+		 */
+		return 0;
+	}
+
 	val = 0;
 	n = 0;
 	while ((ch = *src++) != '\0')
@@ -385,7 +394,7 @@ getbits(const char *src, int *bitsp)
 }
 
 static int
-getv4(const char *src, u_char *dst, int *bitsp)
+getv4(const char *src, u_char *dst, int *bitsp, bool allow_cidr)
 {
 	static const char digits[] = "0123456789";
 	u_char	   *odst = dst;
@@ -416,7 +425,7 @@ getv4(const char *src, u_char *dst, int *bitsp)
 				return 0;
 			*dst++ = val;
 			if (ch == '/')
-				return getbits(src, bitsp);
+				return getbits(src, bitsp, allow_cidr);
 			val = 0;
 			n = 0;
 			continue;
@@ -431,18 +440,12 @@ getv4(const char *src, u_char *dst, int *bitsp)
 	return 1;
 }
 
-static int
-inet_net_pton_ipv6(const char *src, u_char *dst)
-{
-	return inet_cidr_pton_ipv6(src, dst, 16);
-}
-
 #define NS_IN6ADDRSZ 16
 #define NS_INT16SZ 2
 #define NS_INADDRSZ 4
 
 static int
-inet_cidr_pton_ipv6(const char *src, u_char *dst, size_t size)
+inet_pton_ipv6_internal(const char *src, u_char *dst, size_t size, bool allow_cidr)
 {
 	static const char xdigits_l[] = "0123456789abcdef",
 				xdigits_u[] = "0123456789ABCDEF";
@@ -510,13 +513,13 @@ inet_cidr_pton_ipv6(const char *src, u_char *dst, size_t size)
 			continue;
 		}
 		if (ch == '.' && ((tp + NS_INADDRSZ) <= endp) &&
-			getv4(curtok, tp, &bits) > 0)
+			getv4(curtok, tp, &bits, allow_cidr) > 0)
 		{
 			tp += NS_INADDRSZ;
 			saw_xdigit = 0;
 			break;				/* '\0' was seen by inet_pton4(). */
 		}
-		if (ch == '/' && getbits(src, &bits) > 0)
+		if (ch == '/' && getbits(src, &bits, allow_cidr) > 0)
 			break;
 		goto enoent;
 	}
@@ -530,6 +533,9 @@ inet_cidr_pton_ipv6(const char *src, u_char *dst, size_t size)
 	if (bits == -1)
 		bits = 128;
 
+	/* Without a CIDR mask, the above should guarantee a complete address. */
+	Assert(allow_cidr || bits == 128);
+
 	endp = tmp + 16;
 
 	if (colonp != NULL)
@@ -568,3 +574,29 @@ emsgsize:
 	errno = EMSGSIZE;
 	return -1;
 }
+
+/*
+ * Converts a network address in presentation format to network format. The main
+ * difference between this and pg_inet_net_pton() above is that CIDR notation is
+ * not allowed.
+ *
+ * The memory pointed to by dst depends on the address family:
+ *
+ *     PGSQL_AF_INET:  not implemented yet (returns -1 with EAFNOSUPPORT)
+ *     PGSQL_AF_INET6: *dst must be a u_char[16]
+ *
+ * Over and above the standard inet_pton() functionality, we also set errno on
+ * parse failure, with the same meanings as with pg_inet_net_pton().
+ */
+int
+pg_inet_pton(int af, const char *src, void *dst)
+{
+	if (af != PGSQL_AF_INET6)
+	{
+		/* Currently only IPv6 is implemented. */
+		errno = EAFNOSUPPORT;
+		return -1;
+	}
+
+	return (inet_pton_ipv6_internal(src, dst, 16, false /* no CIDR */) == 128);
+}
-- 
2.25.1

#24Kyotaro Horiguchi
horikyota.ntt@gmail.com
In reply to: Jacob Champion (#22)
Re: [PATCH] Accept IP addresses in server certificate SANs

At Tue, 15 Mar 2022 21:41:49 +0000, Jacob Champion <pchampion@vmware.com> wrote in

On Mon, 2022-03-14 at 15:30 +0900, Kyotaro Horiguchi wrote:

t/003_sslinfo.pl ... 1/? # Tests were run but no plan was declared and done_testing() was not seen.
# Looks like your test exited with 29 just after 6.
t/003_sslinfo.pl ... Dubious, test returned 29 (wstat 7424, 0x1d00)
All 6 subtests passed
...
Result: FAIL

The script complains like this:

ok 6 - ssl_client_cert_present() for connection with cert
connection error: 'psql: error: connection to server at "127.0.0.1", port 62656 failed: SSL error: tlsv1 alert unknown ca'
while running 'psql -XAtq -d sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=trustdb hostaddr=127.0.0.1 user=ssltestuser host=localhost -f - -v ON_ERROR_STOP=1' at /home/horiguti/work/worktrees/ipsan/src/test/ssl/../../../src/test/perl/PostgreSQL/Test/Cluster.pm line 1873.

So, psql looks like disliking the ca certificate. I also will dig
into that.

Hmm, the sslinfo tests are failing? I wouldn't have expected that based
on the patch changes. Just to confirm -- they pass for you without the
patch?

Mmm.... I'm not sure how come I didn't noticed that, master also fails
for me fo the same reason. In the past that may fail when valid
clinent-certs exists in the users ~/.postgresql but I believe that has
been fixed.

Mmm. after the end of the loop, rc is non-zero only when the loop has
exited by the break and otherwise rc is zero. Why isn't it equivalent
to setting check_cn to false at the break?

check_cn can be false if rc is zero, too; it means that we found a SAN
of the correct type but it didn't match.

I'm not discussing on the meaning. Purely on the logical equivalancy
of the two ways to decide whether to visit CN.

Concretely, the equivalancy between this:

=====
check_cn = true;
rc = 0;
for (i < san_len)
{
if (type matches) check_cn = false;
if (some error happens) rc = nonzero;

if (rc != 0)
break;
}
!if ((rc == 0) && check_cn) {}
=====

and this.

=====
check_cn = true;
rc = 0;
for (i < san_len)
{
if (type matches) check_cn = false;
if (some error happens) rc = nonzero;

if (rc != 0)
{
! check_cn = false;
break;
}
}
!if (check_cn) {}
=====

The two are equivalant to me. And if it is, the latter form smees
simpler and clearner to me.

check_cn can be false if rc is zero, too; it means that we found a SAN
of the correct type but it didn't match.

Don't we count unmatched certs as "existed"? In that case I think we
don't go to CN.

Unmatched names, you mean? I'm not sure I understand.

Sorry, I was confused here. Please ignore that. I shoudl have said
something like the following instead.

check_cn can be false if rc is zero, too; it means that we found a SAN
of the correct type but it didn't match.

Yes, in that case we don't visit CN because check_cn is false even if
we don't exit by (rc != 0) and that behavior is not changed by my
proposal.

I supposed that we only
be deviant in the case "IP address connhost and no SANs of any type
exists". What do you think about it?

We fall back in the case of "IP address connhost and dNSName SANs
exist", which is prohibited by that part of RFC 6125. I don't think we
deviate in the case you described; can you explain further?

In that case, i.e., connhost is IP address and no SANs exist at all,
we go to CN. On the other hand in RFC6125:

rfc6125> In some cases, the URI is specified as an IP address rather
rfc6125> than a hostname. In this case, the iPAddress subjectAltName
rfc6125> must be present in the certificate and must exactly match the
rfc6125> IP in the URI.

It (seems to me) denies that behavior. Regardless of the existence of
other types of SANs, iPAddress is required if connname is an IP
address. (That is, it doesn't seem to me that there's any context
like "if any SANs exists", but I'm not so sure I read it perfectly.)

I see what you mean now. Yes, we deviate there as well (and have done
so for a while now). I think breaking compatibility there would
probably not go over well.

Agreed.

regards.

--
Kyotaro Horiguchi
NTT Open Source Software Center

#25Kyotaro Horiguchi
horikyota.ntt@gmail.com
In reply to: Kyotaro Horiguchi (#24)
Re: [PATCH] Accept IP addresses in server certificate SANs

At Wed, 16 Mar 2022 15:56:02 +0900 (JST), Kyotaro Horiguchi <horikyota.ntt@gmail.com> wrote in

Mmm.... I'm not sure how come I didn't noticed that, master also fails
for me fo the same reason. In the past that may fail when valid
clinent-certs exists in the users ~/.postgresql but I believe that has
been fixed.

And finally my fear found to be true.. The test doesn't fail after
removing files under ~/.postgresql, which should not happen.

I'll fix it apart from this.
Sorry for the confusion.

regards.

--
Kyotaro Horiguchi
NTT Open Source Software Center

#26Jacob Champion
pchampion@vmware.com
In reply to: Kyotaro Horiguchi (#24)
Re: [PATCH] Accept IP addresses in server certificate SANs

On Wed, 2022-03-16 at 15:56 +0900, Kyotaro Horiguchi wrote:

At Tue, 15 Mar 2022 21:41:49 +0000, Jacob Champion <pchampion@vmware.com> wrote in

Hmm, the sslinfo tests are failing? I wouldn't have expected that based
on the patch changes. Just to confirm -- they pass for you without the
patch?

Mmm.... I'm not sure how come I didn't noticed that, master also fails
for me fo the same reason. In the past that may fail when valid
clinent-certs exists in the users ~/.postgresql but I believe that has
been fixed.

Good to know; I was worried that I'd messed up something well outside
the code I'd touched.

Mmm. after the end of the loop, rc is non-zero only when the loop has
exited by the break and otherwise rc is zero. Why isn't it equivalent
to setting check_cn to false at the break?

check_cn can be false if rc is zero, too; it means that we found a SAN
of the correct type but it didn't match.

I'm not discussing on the meaning. Purely on the logical equivalancy
of the two ways to decide whether to visit CN.

Concretely, the equivalancy between this: [snip]

Thank you for the explanation -- the misunderstanding was all on my
end. I thought you were asking me to move the check_cn assignment
instead of copying it to the end. I agree that your suggestion is much
clearer, and I'll make that change tomorrow.

Thanks!
--Jacob

#27Jacob Champion
pchampion@vmware.com
In reply to: Jacob Champion (#26)
4 attachment(s)
Re: [PATCH] Accept IP addresses in server certificate SANs

On Wed, 2022-03-16 at 23:49 +0000, Jacob Champion wrote:

Thank you for the explanation -- the misunderstanding was all on my
end. I thought you were asking me to move the check_cn assignment
instead of copying it to the end. I agree that your suggestion is much
clearer, and I'll make that change tomorrow.

Done in v8. Thanks again for your suggestions (and for your
perseverance when I didn't get it)!

--Jacob

Attachments:

since-v7.diff.txttext/plain; name=since-v7.diff.txtDownload
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index c6a80d30c2..06b166b7fd 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -650,7 +650,14 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 			}
 
 			if (rc != 0)
+			{
+				/*
+				 * Either we hit an error or a match, and either way we should
+				 * not fall back to the CN.
+				 */
+				check_cn = false;
 				break;
+			}
 		}
 		sk_GENERAL_NAME_pop_free(peer_san, GENERAL_NAME_free);
 	}
@@ -663,7 +670,7 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 	 * dNSName is present, the CN must be ignored. We break this rule if host is
 	 * an IP address; see the comment above.)
 	 */
-	if ((rc == 0) && check_cn)
+	if (check_cn)
 	{
 		X509_NAME  *subject_name;
 
v8-0001-Move-inet_net_pton-to-src-port.patchtext/x-patch; name=v8-0001-Move-inet_net_pton-to-src-port.patchDownload
From 84a107da33b7d901cb318f8c2fd140644fbc5100 Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Wed, 24 Nov 2021 14:46:11 -0800
Subject: [PATCH v8 1/3] Move inet_net_pton() to src/port

This will be helpful for IP address verification in libpq.
---
 src/backend/utils/adt/Makefile                  |  1 -
 src/include/port.h                              |  3 +++
 src/include/utils/builtins.h                    |  4 ----
 src/port/Makefile                               |  1 +
 src/{backend/utils/adt => port}/inet_net_pton.c | 16 +++++++++++++++-
 src/tools/msvc/Mkvcbuild.pm                     |  2 +-
 6 files changed, 20 insertions(+), 7 deletions(-)
 rename src/{backend/utils/adt => port}/inet_net_pton.c (96%)

diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile
index 41b486bcef..d173d52157 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -43,7 +43,6 @@ OBJS = \
 	geo_selfuncs.o \
 	geo_spgist.o \
 	inet_cidr_ntop.o \
-	inet_net_pton.o \
 	int.o \
 	int8.o \
 	json.o \
diff --git a/src/include/port.h b/src/include/port.h
index 3d103a2b31..2852e5b58b 100644
--- a/src/include/port.h
+++ b/src/include/port.h
@@ -515,6 +515,9 @@ extern int	pg_codepage_to_encoding(UINT cp);
 extern char *pg_inet_net_ntop(int af, const void *src, int bits,
 							  char *dst, size_t size);
 
+/* port/inet_net_pton.c */
+extern int	pg_inet_net_pton(int af, const char *src, void *dst, size_t size);
+
 /* port/pg_strong_random.c */
 extern void pg_strong_random_init(void);
 extern bool pg_strong_random(void *buf, size_t len);
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 666e545496..67b9326dc8 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -93,10 +93,6 @@ extern int	xidLogicalComparator(const void *arg1, const void *arg2);
 extern char *pg_inet_cidr_ntop(int af, const void *src, int bits,
 							   char *dst, size_t size);
 
-/* inet_net_pton.c */
-extern int	pg_inet_net_pton(int af, const char *src,
-							 void *dst, size_t size);
-
 /* network.c */
 extern double convert_network_to_scalar(Datum value, Oid typid, bool *failure);
 extern Datum network_scan_first(Datum in);
diff --git a/src/port/Makefile b/src/port/Makefile
index bfe1feb0d4..c3ae7b3d5c 100644
--- a/src/port/Makefile
+++ b/src/port/Makefile
@@ -43,6 +43,7 @@ OBJS = \
 	bsearch_arg.o \
 	chklocale.o \
 	inet_net_ntop.o \
+	inet_net_pton.o \
 	noblock.o \
 	path.o \
 	pg_bitutils.o \
diff --git a/src/backend/utils/adt/inet_net_pton.c b/src/port/inet_net_pton.c
similarity index 96%
rename from src/backend/utils/adt/inet_net_pton.c
rename to src/port/inet_net_pton.c
index d3221a1313..bae50ba67e 100644
--- a/src/backend/utils/adt/inet_net_pton.c
+++ b/src/port/inet_net_pton.c
@@ -14,14 +14,18 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
  * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  *
- *	  src/backend/utils/adt/inet_net_pton.c
+ *	  src/port/inet_net_pton.c
  */
 
 #if defined(LIBC_SCCS) && !defined(lint)
 static const char rcsid[] = "Id: inet_net_pton.c,v 1.4.2.3 2004/03/17 00:40:11 marka Exp $";
 #endif
 
+#ifndef FRONTEND
 #include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
 
 #include <sys/socket.h>
 #include <netinet/in.h>
@@ -29,9 +33,19 @@ static const char rcsid[] = "Id: inet_net_pton.c,v 1.4.2.3 2004/03/17 00:40:11 m
 #include <assert.h>
 #include <ctype.h>
 
+#ifndef FRONTEND
 #include "utils/builtins.h" /* pgrminclude ignore */	/* needed on some
 														 * platforms */
 #include "utils/inet.h"
+#else
+/*
+ * In a frontend build, we can't include inet.h, but we still need to have
+ * sensible definitions of these two constants.  Note that pg_inet_net_ntop()
+ * assumes that PGSQL_AF_INET is equal to AF_INET.
+ */
+#define PGSQL_AF_INET	(AF_INET + 0)
+#define PGSQL_AF_INET6	(AF_INET + 1)
+#endif
 
 
 static int	inet_net_pton_ipv4(const char *src, u_char *dst);
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 441d6ae6bf..667b173ec3 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -100,7 +100,7 @@ sub mkvcbuild
 
 	our @pgportfiles = qw(
 	  chklocale.c explicit_bzero.c fls.c getpeereid.c getrusage.c inet_aton.c
-	  getaddrinfo.c gettimeofday.c inet_net_ntop.c kill.c open.c
+	  getaddrinfo.c gettimeofday.c inet_net_ntop.c inet_net_pton.c kill.c open.c
 	  snprintf.c strlcat.c strlcpy.c dirmod.c noblock.c path.c
 	  dirent.c dlopen.c getopt.c getopt_long.c link.c
 	  pread.c preadv.c pwrite.c pwritev.c pg_bitutils.c
-- 
2.25.1

v8-0002-libpq-allow-IP-address-SANs-in-server-certs.patchtext/x-patch; name=v8-0002-libpq-allow-IP-address-SANs-in-server-certs.patchDownload
From df9b9592b9e7ba959dfb3bc60796111302978f61 Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Thu, 18 Nov 2021 15:36:18 -0800
Subject: [PATCH v8 2/3] libpq: allow IP address SANs in server certs

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.

- Move PGSQL_AF_INET* to inet-common.h to reduce copy-paste.
---
 src/include/common/inet-common.h              |  35 +++++
 src/include/utils/inet.h                      |  13 +-
 src/interfaces/libpq/fe-secure-common.c       | 104 +++++++++++++
 src/interfaces/libpq/fe-secure-common.h       |   5 +
 src/interfaces/libpq/fe-secure-openssl.c      | 138 ++++++++++++++++--
 src/port/inet_net_ntop.c                      |  12 +-
 src/port/inet_net_pton.c                      |  10 +-
 .../conf/server-cn-and-ip-alt-names.config    |  24 +++
 src/test/ssl/conf/server-ip-alt-names.config  |  19 +++
 .../conf/server-ip-cn-and-alt-names.config    |  21 +++
 .../server-ip-cn-and-dns-alt-names.config     |  21 +++
 src/test/ssl/conf/server-ip-cn-only.config    |  12 ++
 src/test/ssl/conf/server-ip-in-dnsname.config |  18 +++
 .../ssl/ssl/server-cn-and-ip-alt-names.crt    |  20 +++
 .../ssl/ssl/server-cn-and-ip-alt-names.key    |  27 ++++
 src/test/ssl/ssl/server-ip-alt-names.crt      |  19 +++
 src/test/ssl/ssl/server-ip-alt-names.key      |  27 ++++
 .../ssl/ssl/server-ip-cn-and-alt-names.crt    |  19 +++
 .../ssl/ssl/server-ip-cn-and-alt-names.key    |  27 ++++
 .../ssl/server-ip-cn-and-dns-alt-names.crt    |  20 +++
 .../ssl/server-ip-cn-and-dns-alt-names.key    |  27 ++++
 src/test/ssl/ssl/server-ip-cn-only.crt        |  18 +++
 src/test/ssl/ssl/server-ip-cn-only.key        |  27 ++++
 src/test/ssl/ssl/server-ip-in-dnsname.crt     |  18 +++
 src/test/ssl/ssl/server-ip-in-dnsname.key     |  27 ++++
 src/test/ssl/sslfiles.mk                      |   6 +
 src/test/ssl/t/001_ssltests.pl                | 106 +++++++++++++-
 27 files changed, 774 insertions(+), 46 deletions(-)
 create mode 100644 src/include/common/inet-common.h
 create mode 100644 src/test/ssl/conf/server-cn-and-ip-alt-names.config
 create mode 100644 src/test/ssl/conf/server-ip-alt-names.config
 create mode 100644 src/test/ssl/conf/server-ip-cn-and-alt-names.config
 create mode 100644 src/test/ssl/conf/server-ip-cn-and-dns-alt-names.config
 create mode 100644 src/test/ssl/conf/server-ip-cn-only.config
 create mode 100644 src/test/ssl/conf/server-ip-in-dnsname.config
 create mode 100644 src/test/ssl/ssl/server-cn-and-ip-alt-names.crt
 create mode 100644 src/test/ssl/ssl/server-cn-and-ip-alt-names.key
 create mode 100644 src/test/ssl/ssl/server-ip-alt-names.crt
 create mode 100644 src/test/ssl/ssl/server-ip-alt-names.key
 create mode 100644 src/test/ssl/ssl/server-ip-cn-and-alt-names.crt
 create mode 100644 src/test/ssl/ssl/server-ip-cn-and-alt-names.key
 create mode 100644 src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.crt
 create mode 100644 src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.key
 create mode 100644 src/test/ssl/ssl/server-ip-cn-only.crt
 create mode 100644 src/test/ssl/ssl/server-ip-cn-only.key
 create mode 100644 src/test/ssl/ssl/server-ip-in-dnsname.crt
 create mode 100644 src/test/ssl/ssl/server-ip-in-dnsname.key

diff --git a/src/include/common/inet-common.h b/src/include/common/inet-common.h
new file mode 100644
index 0000000000..3ad72261b6
--- /dev/null
+++ b/src/include/common/inet-common.h
@@ -0,0 +1,35 @@
+/*-------------------------------------------------------------------------
+ *
+ * inet-common.h
+ *
+ *	  Common code for clients of the inet_* APIs.
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/common/inet-common.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef INET_COMMON_H
+#define INET_COMMON_H
+
+/*
+ * We use these values for the "family" field of inet_struct.
+ *
+ * Referencing all of the non-AF_INET types to AF_INET lets us work on
+ * machines which may not have the appropriate address family (like
+ * inet6 addresses when AF_INET6 isn't present) but doesn't cause a
+ * dump/reload requirement.  Pre-7.4 databases used AF_INET for the family
+ * type on disk.
+ *
+ * In a frontend build, we can't include inet.h, but we still need to have
+ * sensible definitions of these two constants.  (Frontend clients should
+ * include this header directly.)  Note that pg_inet_net_ntop()
+ * assumes that PGSQL_AF_INET is equal to AF_INET.
+ */
+#define PGSQL_AF_INET	(AF_INET + 0)
+#define PGSQL_AF_INET6	(AF_INET + 1)
+
+#endif							/* INET_COMMON_H */
diff --git a/src/include/utils/inet.h b/src/include/utils/inet.h
index 3073c0307e..c56c016987 100644
--- a/src/include/utils/inet.h
+++ b/src/include/utils/inet.h
@@ -14,6 +14,7 @@
 #ifndef INET_H
 #define INET_H
 
+#include "common/inet-common.h"
 #include "fmgr.h"
 
 /*
@@ -27,18 +28,6 @@ typedef struct
 	unsigned char ipaddr[16];	/* up to 128 bits of address */
 } inet_struct;
 
-/*
- * We use these values for the "family" field.
- *
- * Referencing all of the non-AF_INET types to AF_INET lets us work on
- * machines which may not have the appropriate address family (like
- * inet6 addresses when AF_INET6 isn't present) but doesn't cause a
- * dump/reload requirement.  Pre-7.4 databases used AF_INET for the family
- * type on disk.
- */
-#define PGSQL_AF_INET	(AF_INET + 0)
-#define PGSQL_AF_INET6	(AF_INET + 1)
-
 /*
  * Both INET and CIDR addresses are represented within Postgres as varlena
  * objects, ie, there is a varlena header in front of the struct type
diff --git a/src/interfaces/libpq/fe-secure-common.c b/src/interfaces/libpq/fe-secure-common.c
index bd46f08fae..2b8f2d6b24 100644
--- a/src/interfaces/libpq/fe-secure-common.c
+++ b/src/interfaces/libpq/fe-secure-common.c
@@ -19,9 +19,13 @@
 
 #include "postgres_fe.h"
 
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
 #include "fe-secure-common.h"
 
 #include "libpq-int.h"
+#include "port.h"
 #include "pqexpbuffer.h"
 
 /*
@@ -144,6 +148,106 @@ pq_verify_peer_name_matches_certificate_name(PGconn *conn,
 	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'))
+	{
+		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 = PGSQL_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;
+		}
+	}
+	else if (iplen == 16)
+	{
+		/* IPv6 */
+		unsigned char addr[16];
+
+		family = PGSQL_AF_INET6;
+
+		/*
+		 * pg_inet_net_pton() will accept CIDR masks, which we don't want to
+		 * match, so skip the comparison if the host string contains a slash.
+		 */
+		if (!strchr(host, '/')
+			&& pg_inet_net_pton(PGSQL_AF_INET6, host, addr, -1) == 128)
+		{
+			if (memcmp(ipdata, addr, iplen) == 0)
+				match = 1;
+		}
+	}
+	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.
  *
diff --git a/src/interfaces/libpq/fe-secure-common.h b/src/interfaces/libpq/fe-secure-common.h
index 1cca6d785a..20ff9ba5db 100644
--- a/src/interfaces/libpq/fe-secure-common.h
+++ b/src/interfaces/libpq/fe-secure-common.h
@@ -16,11 +16,16 @@
 #ifndef FE_SECURE_COMMON_H
 #define FE_SECURE_COMMON_H
 
+#include "common/inet-common.h"
 #include "libpq-fe.h"
 
 extern int	pq_verify_peer_name_matches_certificate_name(PGconn *conn,
 														 const char *namedata, size_t namelen,
 														 char **store_name);
+extern int	pq_verify_peer_name_matches_certificate_ip(PGconn *conn,
+													   const unsigned char *addrdata,
+													   size_t addrlen,
+													   char **store_name);
 extern bool pq_verify_peer_name_matches_certificate(PGconn *conn);
 
 #endif							/* FE_SECURE_COMMON_H */
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index d81218a4cc..6ec4c2041c 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -72,6 +72,9 @@ static int	verify_cb(int ok, X509_STORE_CTX *ctx);
 static int	openssl_verify_peer_name_matches_certificate_name(PGconn *conn,
 															  ASN1_STRING *name,
 															  char **store_name);
+static int	openssl_verify_peer_name_matches_certificate_ip(PGconn *conn,
+															ASN1_OCTET_STRING *addr_entry,
+															char **store_name);
 static void destroy_ssl_system(void);
 static int	initialize_SSL(PGconn *conn);
 static PostgresPollingStatusType open_client_SSL(PGconn *);
@@ -509,6 +512,51 @@ openssl_verify_peer_name_matches_certificate_name(PGconn *conn, ASN1_STRING *nam
 	return pq_verify_peer_name_matches_certificate_name(conn, (const char *) namedata, len, store_name);
 }
 
+/*
+ * OpenSSL-specific wrapper around
+ * pq_verify_peer_name_matches_certificate_ip(), converting the
+ * ASN1_OCTET_STRING into a plain C string.
+ */
+static int
+openssl_verify_peer_name_matches_certificate_ip(PGconn *conn,
+												ASN1_OCTET_STRING *addr_entry,
+												char **store_name)
+{
+	int			len;
+	const unsigned char *addrdata;
+
+	/* Should not happen... */
+	if (addr_entry == NULL)
+	{
+		appendPQExpBufferStr(&conn->errorMessage,
+							 libpq_gettext("SSL certificate's address entry is missing\n"));
+		return -1;
+	}
+
+	/*
+	 * GEN_IPADD is an OCTET STRING containing an IP address in network byte
+	 * order.
+	 */
+#ifdef HAVE_ASN1_STRING_GET0_DATA
+	addrdata = ASN1_STRING_get0_data(addr_entry);
+#else
+	addrdata = ASN1_STRING_data(addr_entry);
+#endif
+	len = ASN1_STRING_length(addr_entry);
+
+	return pq_verify_peer_name_matches_certificate_ip(conn, addrdata, len, store_name);
+}
+
+static bool
+is_ip_address(const char *host)
+{
+	struct in_addr	dummy4;
+	unsigned char	dummy6[16];
+
+	return inet_aton(host, &dummy4)
+		|| (pg_inet_net_pton(PGSQL_AF_INET6, host, dummy6, -1) == 128);
+}
+
 /*
  *	Verify that the server certificate matches the hostname we connected to.
  *
@@ -522,6 +570,36 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 	STACK_OF(GENERAL_NAME) * peer_san;
 	int			i;
 	int			rc = 0;
+	char	   *host = conn->connhost[conn->whichhost].host;
+	int			host_type;
+	bool		check_cn = true;
+
+	Assert(host && host[0]); /* should be guaranteed by caller */
+
+	/*
+	 * We try to match the NSS behavior here, which is a slight departure from
+	 * the spec but seems to make more intuitive sense:
+	 *
+	 * If connhost contains a DNS name, and the certificate's SANs contain any
+	 * dNSName entries, then we'll ignore the Subject Common Name entirely;
+	 * otherwise, we fall back to checking the CN. (This behavior matches the
+	 * RFC.)
+	 *
+	 * If connhost contains an IP address, and the SANs contain iPAddress
+	 * entries, we again ignore the CN. Otherwise, we allow the CN to match,
+	 * EVEN IF there is a dNSName in the SANs. (RFC 6125 prohibits this: "A
+	 * client MUST NOT seek a match for a reference identifier of CN-ID if the
+	 * presented identifiers include a DNS-ID, SRV-ID, URI-ID, or any
+	 * application-specific identifier types supported by the client.")
+	 *
+	 * NOTE: Prior versions of libpq did not consider iPAddress entries at all,
+	 * so this new behavior might break a certificate that has different IP
+	 * addresses in the Subject CN and the SANs.
+	 */
+	if (is_ip_address(host))
+		host_type = GEN_IPADD;
+	else
+		host_type = GEN_DNS;
 
 	/*
 	 * First, get the Subject Alternative Names (SANs) from the certificate,
@@ -537,38 +615,62 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 		for (i = 0; i < san_len; i++)
 		{
 			const GENERAL_NAME *name = sk_GENERAL_NAME_value(peer_san, i);
+			char	   *alt_name = NULL;
 
-			if (name->type == GEN_DNS)
+			if (name->type == host_type)
 			{
-				char	   *alt_name;
+				/*
+				 * This SAN is of the same type (IP or DNS) as our host name, so
+				 * don't allow a fallback check of the CN.
+				 */
+				check_cn = false;
+			}
 
+			if (name->type == GEN_DNS)
+			{
 				(*names_examined)++;
 				rc = openssl_verify_peer_name_matches_certificate_name(conn,
 																	   name->d.dNSName,
 																	   &alt_name);
+			}
+			else if (name->type == GEN_IPADD)
+			{
+				(*names_examined)++;
+				rc = openssl_verify_peer_name_matches_certificate_ip(conn,
+																	 name->d.iPAddress,
+																	 &alt_name);
+			}
 
-				if (alt_name)
-				{
-					if (!*first_name)
-						*first_name = alt_name;
-					else
-						free(alt_name);
-				}
+			if (alt_name)
+			{
+				if (!*first_name)
+					*first_name = alt_name;
+				else
+					free(alt_name);
 			}
+
 			if (rc != 0)
+			{
+				/*
+				 * Either we hit an error or a match, and either way we should
+				 * not fall back to the CN.
+				 */
+				check_cn = false;
 				break;
+			}
 		}
 		sk_GENERAL_NAME_pop_free(peer_san, GENERAL_NAME_free);
 	}
 
 	/*
-	 * If there is no subjectAltName extension of type dNSName, check the
+	 * If there is no subjectAltName extension of the matching type, check the
 	 * Common Name.
 	 *
 	 * (Per RFC 2818 and RFC 6125, if the subjectAltName extension of type
-	 * dNSName is present, the CN must be ignored.)
+	 * dNSName is present, the CN must be ignored. We break this rule if host is
+	 * an IP address; see the comment above.)
 	 */
-	if (*names_examined == 0)
+	if (check_cn)
 	{
 		X509_NAME  *subject_name;
 
@@ -581,10 +683,20 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 												  NID_commonName, -1);
 			if (cn_index >= 0)
 			{
+				char   *common_name = NULL;
+
 				(*names_examined)++;
 				rc = openssl_verify_peer_name_matches_certificate_name(conn,
 																	   X509_NAME_ENTRY_get_data(X509_NAME_get_entry(subject_name, cn_index)),
-																	   first_name);
+																	   &common_name);
+
+				if (common_name)
+				{
+					if (!*first_name)
+						*first_name = common_name;
+					else
+						free(common_name);
+				}
 			}
 		}
 	}
diff --git a/src/port/inet_net_ntop.c b/src/port/inet_net_ntop.c
index b8ad69c390..af28056132 100644
--- a/src/port/inet_net_ntop.c
+++ b/src/port/inet_net_ntop.c
@@ -31,17 +31,7 @@ static const char rcsid[] = "Id: inet_net_ntop.c,v 1.1.2.2 2004/03/09 09:17:27 m
 #include <netinet/in.h>
 #include <arpa/inet.h>
 
-#ifndef FRONTEND
-#include "utils/inet.h"
-#else
-/*
- * In a frontend build, we can't include inet.h, but we still need to have
- * sensible definitions of these two constants.  Note that pg_inet_net_ntop()
- * assumes that PGSQL_AF_INET is equal to AF_INET.
- */
-#define PGSQL_AF_INET	(AF_INET + 0)
-#define PGSQL_AF_INET6	(AF_INET + 1)
-#endif
+#include "common/inet-common.h"
 
 
 #define NS_IN6ADDRSZ 16
diff --git a/src/port/inet_net_pton.c b/src/port/inet_net_pton.c
index bae50ba67e..5bae811c6f 100644
--- a/src/port/inet_net_pton.c
+++ b/src/port/inet_net_pton.c
@@ -33,18 +33,10 @@ static const char rcsid[] = "Id: inet_net_pton.c,v 1.4.2.3 2004/03/17 00:40:11 m
 #include <assert.h>
 #include <ctype.h>
 
+#include "common/inet-common.h"
 #ifndef FRONTEND
 #include "utils/builtins.h" /* pgrminclude ignore */	/* needed on some
 														 * platforms */
-#include "utils/inet.h"
-#else
-/*
- * In a frontend build, we can't include inet.h, but we still need to have
- * sensible definitions of these two constants.  Note that pg_inet_net_ntop()
- * assumes that PGSQL_AF_INET is equal to AF_INET.
- */
-#define PGSQL_AF_INET	(AF_INET + 0)
-#define PGSQL_AF_INET6	(AF_INET + 1)
 #endif
 
 
diff --git a/src/test/ssl/conf/server-cn-and-ip-alt-names.config b/src/test/ssl/conf/server-cn-and-ip-alt-names.config
new file mode 100644
index 0000000000..a6fa09bad3
--- /dev/null
+++ b/src/test/ssl/conf/server-cn-and-ip-alt-names.config
@@ -0,0 +1,24 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+# This certificate contains a CN and SANs for both IPv4 and IPv6.
+
+
+[ req ]
+distinguished_name     = req_distinguished_name
+req_extensions         = v3_req
+prompt                 = no
+
+[ req_distinguished_name ]
+# Note: According to RFC 2818 and 6125, the CN is ignored, when DNS names are
+# present in the SANs. But they are silent on whether the CN is checked when IP
+# addresses are present.
+CN = common-name.pg-ssltest.test
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
+subjectAltName = @alt_names
+
+[ alt_names ]
+IP.1 = 192.0.2.1
+IP.2 = 2001:DB8::1
diff --git a/src/test/ssl/conf/server-ip-alt-names.config b/src/test/ssl/conf/server-ip-alt-names.config
new file mode 100644
index 0000000000..c22f22951a
--- /dev/null
+++ b/src/test/ssl/conf/server-ip-alt-names.config
@@ -0,0 +1,19 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+# This certificate has a two IP-address SANs, and no CN.
+
+[ req ]
+distinguished_name     = req_distinguished_name
+req_extensions         = v3_req
+prompt                 = no
+
+[ req_distinguished_name ]
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
+subjectAltName = @alt_names
+
+[ alt_names ]
+IP.1 = 192.0.2.1
+IP.2 = 2001:DB8::1
diff --git a/src/test/ssl/conf/server-ip-cn-and-alt-names.config b/src/test/ssl/conf/server-ip-cn-and-alt-names.config
new file mode 100644
index 0000000000..a4087f0a18
--- /dev/null
+++ b/src/test/ssl/conf/server-ip-cn-and-alt-names.config
@@ -0,0 +1,21 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+# This certificate contains both a CN and SANs in IP address format.
+
+
+[ req ]
+distinguished_name     = req_distinguished_name
+req_extensions         = v3_req
+prompt                 = no
+
+[ req_distinguished_name ]
+CN = 192.0.2.1
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
+subjectAltName = @alt_names
+
+[ alt_names ]
+IP.1 = 192.0.2.2
+IP.2 = 2001:DB8::1
diff --git a/src/test/ssl/conf/server-ip-cn-and-dns-alt-names.config b/src/test/ssl/conf/server-ip-cn-and-dns-alt-names.config
new file mode 100644
index 0000000000..7121803b49
--- /dev/null
+++ b/src/test/ssl/conf/server-ip-cn-and-dns-alt-names.config
@@ -0,0 +1,21 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+# This certificate contains both a CN and SANs in IP address format.
+
+
+[ req ]
+distinguished_name     = req_distinguished_name
+req_extensions         = v3_req
+prompt                 = no
+
+[ req_distinguished_name ]
+CN = 192.0.2.1
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
+subjectAltName = @alt_names
+
+[ alt_names ]
+DNS.1 = dns1.alt-name.pg-ssltest.test
+DNS.2 = dns2.alt-name.pg-ssltest.test
diff --git a/src/test/ssl/conf/server-ip-cn-only.config b/src/test/ssl/conf/server-ip-cn-only.config
new file mode 100644
index 0000000000..585d8bdae8
--- /dev/null
+++ b/src/test/ssl/conf/server-ip-cn-only.config
@@ -0,0 +1,12 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+
+[ req ]
+distinguished_name     = req_distinguished_name
+prompt                 = no
+
+[ req_distinguished_name ]
+CN = 192.0.2.1
+OU = PostgreSQL test suite
+
+# No Subject Alternative Names
diff --git a/src/test/ssl/conf/server-ip-in-dnsname.config b/src/test/ssl/conf/server-ip-in-dnsname.config
new file mode 100644
index 0000000000..b15649aef7
--- /dev/null
+++ b/src/test/ssl/conf/server-ip-in-dnsname.config
@@ -0,0 +1,18 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+
+[ req ]
+distinguished_name     = req_distinguished_name
+req_extensions         = v3_req
+prompt                 = no
+
+[ req_distinguished_name ]
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
+subjectAltName = @alt_names
+
+# Normally IP addresses should not go into a dNSName.
+[ alt_names ]
+DNS.1 = 192.0.2.1
diff --git a/src/test/ssl/ssl/server-cn-and-ip-alt-names.crt b/src/test/ssl/ssl/server-cn-and-ip-alt-names.crt
new file mode 100644
index 0000000000..4e58c85ccb
--- /dev/null
+++ b/src/test/ssl/ssl/server-cn-and-ip-alt-names.crt
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDLzCCAhegAwIBAgIIICERKRE1UQAwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UE
+Aww3VGVzdCBDQSBmb3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHNl
+cnZlciBjZXJ0czAeFw0yMTExMjkxOTM1NTFaFw00OTA0MTYxOTM1NTFaMEYxHjAc
+BgNVBAsMFVBvc3RncmVTUUwgdGVzdCBzdWl0ZTEkMCIGA1UEAwwbY29tbW9uLW5h
+bWUucGctc3NsdGVzdC50ZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
+AQEA6+8IYKAFnZ7V+fDo1cyMpbGBLzCfJOQ/1o2jOGP4+GjpsZgv6S6UT2MheC8M
+iiEFrYwdsSIZyYc3jEZrluy/UuR0bCGtqU92BCqa0iBLhvHOgjR588u253eLxQtQ
+8iJn11QPrKMk35nMkmY8GfHt4sGFbvBL6+GpipHq7a6cde3Z+v4kCB5dKMYDUDtm
+3mJmviuGNAu5wOqItk2Yi5dwJs1054007KNH0Il43urxiOfnkLS0cG5kehboPf86
+vxBt3iHByrU/9/DY5IvQCfSXVNa6rb5w5/pGja9aCei6Mv1jQY/V8SMQTga+MOsA
+0WB9akxMi2NxwS2+BQ4k/McPlwIDAQABoyUwIzAhBgNVHREEGjAYhwTAAAIBhxAg
+AQ24AAAAAAAAAAAAAAABMA0GCSqGSIb3DQEBCwUAA4IBAQAQLo2RzC07dG9p+J3A
+W6C0p3Y+Os/YE2D9wfp4TIDTZxcRUQZ0S6ahF1N6sp8l9KHBJHPU1cUpRAU1oD+Y
+SqmnP/VJRRDTTj9Ytdc/Vuo2jeLpSYhVKrCqtjqIrCwYJFoYRmMoxTtJGlwA0hSd
+kwo3XYrALPUQWUErTYPvNfDNIuUwqUXNfS0CXuIOVN3LJ+shegg6Pwbh9B5T9NHx
+kH+HswajhdpdnZIgh0FYTlTCPILDrB49aOWwqLa54AUA6WXa35hPsP8SoqL9Eucq
+ifPhBYyadsjOb+70N8GbbAsDPN1jCX9L8RuNcEkxSCKCYx91cWXh7K5KMPuGlzB7
+j8xB
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-cn-and-ip-alt-names.key b/src/test/ssl/ssl/server-cn-and-ip-alt-names.key
new file mode 100644
index 0000000000..837eef996d
--- /dev/null
+++ b/src/test/ssl/ssl/server-cn-and-ip-alt-names.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA6+8IYKAFnZ7V+fDo1cyMpbGBLzCfJOQ/1o2jOGP4+GjpsZgv
+6S6UT2MheC8MiiEFrYwdsSIZyYc3jEZrluy/UuR0bCGtqU92BCqa0iBLhvHOgjR5
+88u253eLxQtQ8iJn11QPrKMk35nMkmY8GfHt4sGFbvBL6+GpipHq7a6cde3Z+v4k
+CB5dKMYDUDtm3mJmviuGNAu5wOqItk2Yi5dwJs1054007KNH0Il43urxiOfnkLS0
+cG5kehboPf86vxBt3iHByrU/9/DY5IvQCfSXVNa6rb5w5/pGja9aCei6Mv1jQY/V
+8SMQTga+MOsA0WB9akxMi2NxwS2+BQ4k/McPlwIDAQABAoIBAQCuNFKVNdKvrUYF
+RLJGmsAG3+eo9lern7TbML2ht39vu9dBwEMwA6qSa3mdCfBSVUuh9uE9lxY/TU3g
+j2aFi81A4VptNPjLGNblAKhMGnhp7UUzspeRQYuNoSFcnpxoDKtrvK/OIq/pQeBh
+AIfECHRDh+yEG32Tb44FuPQkB1eTYl8xbMEImrhNUaSjJk7tTsmydHy0DjmqHVKX
+HUj0TREfDBDOBiHtY0XV6Pu3bnqDH/TKLTfUf3UdfTuay3Yai9aEcRPWp9GrMO7G
+axsKCifTz6177gyr6Fv8HLeMZMh9rMZRn3e0zfaF6vrH1QnZZOts5jpUa0KugSCd
+//uC0iNxAoGBAPXVc3b+o3hY5gcwwpaW6JtsarDrmNRxrizqIDG7NgpqwdFXgTi6
+6q0t2pjv81ATqij69IcPkNSissyR4OEKnu/OFJWzreg8yLi75WHKi0E/6msHpwRk
+d1yP0Zgd05ots/yOjDSp593RagaPVvHBxMECZ/Tm3B+Tq55Azudd/zvLAoGBAPWw
+xf0oUEJl6NdUZD6K7eFc6jf8yrpD85dldeko6LeN8x0XlKKWvUDJ2+3oizXoQvCm
+8by6KOYEIo4MrtXuy9MmtPWfNvRBr+hsUHchIj7IgFa9bKXyK2FnJqu/8CbEymli
+eZu7hoOhelurhnFy1zSqwNO4GC+kw60Y/BO3Z1nlAoGAVOyYJtNwxXJwhKtjjYI0
+ePzLHrNE6J8c/Ick+AkkchTPP/JqwZ5Q0+KzUYITG+avMdkAAGhwMATEn8cFWLjC
+jzUyB0U7Hq9g5/CBHXdLBA+Ae9j46ZuLYH6OeW5UWz7OnsDfzpGjeA2QAxQhhQLb
+ZZHfN8tI39+zucfJskPWmGECgYEAg9guF1Fn6InJrqwR82IYj6SN6CeXHufSM392
+C/4xDDd3rDf4QlwECV2J0RzGf9I5Ae2EshNwWScE6Be0RweTh6cw2tJq6h7J6D8f
+2x4Dw49TF7klMdRIJUf2f5pLpHJccLswqTqzz7V69PCSABVxmUi8m6EiEYconp5W
+v7nfE2UCgYALrEqzncuSIX3q6TVAjnzT7gO4h8h2TUekIWdHQFldFx8R7Kncggnd
+48gQqhewchNR83UCcd7pPsCcTqu6UR1QRdq/DV5P6J3xdZ2iS/2gCM6hvWIvKZEv
+/ClnkyFCOW7zX6RKIXtRYZTV1kz3TajApi34RTIeIMTieaCarnBJbA==
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-ip-alt-names.crt b/src/test/ssl/ssl/server-ip-alt-names.crt
new file mode 100644
index 0000000000..8a1bc620bb
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-alt-names.crt
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDCTCCAfGgAwIBAgIIICERKREEUAAwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UE
+Aww3VGVzdCBDQSBmb3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHNl
+cnZlciBjZXJ0czAeFw0yMTExMjkxOTA0NTBaFw00OTA0MTYxOTA0NTBaMCAxHjAc
+BgNVBAsMFVBvc3RncmVTUUwgdGVzdCBzdWl0ZTCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBAOM8yB6aVWb17ujr3ayU62mxHQoqn4CvG9yXlJvGOGv/ursW
+Vs0UYJdc96LsNZN1szdm9ayNzCIw3eja+ULsjxCi6+3LM4pO76IORL/XFamlTPYb
+BZ4pHdZVB0nnZAAnWCZPyXdnjOKQ5+8unVXkfibkjj8UELBJ2snehsOa+CTkOBez
+zxYMqxAgbywLIYsW448brun7UXpWmqbGK+SsdGaIZ5Sb7Zezc5lt6CrLemTZTHHK
+7l4WZFCCEi4t3sgO8o1vDELD/IE5G8lyXvIdgJg6t8ssper7iCw6S8x+okhjiSjT
+vDLU2g4AanqZRZB49aPwTo0QUcJA2BCJxL9xLy8CAwEAAaMlMCMwIQYDVR0RBBow
+GIcEwAACAYcQIAENuAAAAAAAAAAAAAAAATANBgkqhkiG9w0BAQsFAAOCAQEAwZJ+
+8KpABTlMEgKnHIYb35ItGhtFiTLQta9RkXx7vaeDwpOdPP/IvuvpjpQZkobRgBsk
+bNM0KuJpd2mSTphQAt6eKQIdcPrkzvc/Yh9OK3YNLUAbu/ZhBUnBvFnUL4wn2f1U
+mfO+m8P/LxybwqKx7r1mbaB+tP3RTxxLcIMvm9ECPQEoBntfEL325Wdoj+WuQH5Y
+IvcM6FaCTkQsNIPbaBD5l5MhMLHRULZujbDjXqGSvRMQfns6np/biMjNdQA8NZ5z
+STeUFvkQbCxoA0YYLgoSHL5KhZjXrg2g+T+2TUyCTR/91xf9OoOjBZdixR0S0DzJ
+B1+5vnUjZaCfnSEA7A==
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-ip-alt-names.key b/src/test/ssl/ssl/server-ip-alt-names.key
new file mode 100644
index 0000000000..b210b3a991
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-alt-names.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEA4zzIHppVZvXu6OvdrJTrabEdCiqfgK8b3JeUm8Y4a/+6uxZW
+zRRgl1z3ouw1k3WzN2b1rI3MIjDd6Nr5QuyPEKLr7cszik7vog5Ev9cVqaVM9hsF
+nikd1lUHSedkACdYJk/Jd2eM4pDn7y6dVeR+JuSOPxQQsEnayd6Gw5r4JOQ4F7PP
+FgyrECBvLAshixbjjxuu6ftRelaapsYr5Kx0ZohnlJvtl7NzmW3oKst6ZNlMccru
+XhZkUIISLi3eyA7yjW8MQsP8gTkbyXJe8h2AmDq3yyyl6vuILDpLzH6iSGOJKNO8
+MtTaDgBqeplFkHj1o/BOjRBRwkDYEInEv3EvLwIDAQABAoIBACp3uY6+mSdc3wF4
+0zzlt/lQuHSl8plCIJrhWUyjhvfoGyXLzv0Uydh/72frbTfZz1yTSWauOXBKYa6a
+/eqb+0DIsf8G8uLuTaqjsAWKVOoXkoKMGkistn7P9UTCkdXVhIvkbWp7V8EgA7iX
+pZ/fzBPIsyzmuxe3NcR0ags0cxuxkNuu+YXDv1oTedmT2wS3CZq1d/T1Y/EOVIf8
+Iznd2aOverlsnt6iiQ3ZWdG/W5F8FhnrR/rrBdYsdCv6TH/KUYexnDOUYpayjDbu
+oAKnifPp6UqiOM4SuBL83OAz19jptp5vpF370BEVRs3eK0q+zo/mETjv9HsXdolZ
+lfoXA0ECgYEA/7nb2azbq/2muvXCh1ZxCEbn3mt8KXoJP/xkx/v9eEc/cc5Q9e0V
+2oGfjC2hSE+bjOWMwiUMD6uU+iRjhz5A3IvUxnoSdoL7H9p0hTqLMyP7dTDkoVF5
+aEuLMaiI5YEnfAFu9L5h8ZKieoQTBoscT06wnGjh9pBV9bthfTKA7ksCgYEA43sb
+55m9WL4kWCPwOAp3vdEAFyxzmZHlO26sEQOU/m5aN01pumYybBruziEXMI96yfTj
+VmXKReeYb6XUiCcs3fLSipD/+8/8CsjO4uMORtxWumXe8AbKZfysGFzL7wJlByGT
+38AGQwIG/XD8cKnaiEMX4E/3Owbcoxwixo3WZC0CgYEAovaqJ9mEU+Jc8h/TS7PG
+bGPjN1Z/1V6zrlcFUnw/Vvrwb3HvHglsN8cLCaW6df5lPjC6tq4tNX8+fPnbg0Ak
+zWc+vQzl3ygxKGdqgcyBEKIJiPETgcoN+GzL02V3d+oKY3f2YXlBqVSsvi6UgUL9
+U3zuB36/IQVyAhrbUZFxoGkCgYEAnaFAO+Nvrp/LhXwZyGuQf+rkmipGTIMpil5t
+QzjtNMV5JFszSWPpyrl7A0Ew1YiG+I0GP2c3m+sY2TzbIiGrWH0b4cMKbw63Qy3V
+FqlpyjaCrpVKv56k/7jv883RzuQk56Uf1+szK5mrCFITy2oXsVZ0pA4lbjSaDTjA
+7D968V0CgYEA+qKqXKL98+c5CMPnpf+0B1x2zgyUym1ouPfon2x5fhK84T53zDMA
+zfdUJ/SOZw6/c9vRF7RL8h+ZfFdIyoAXv4Tt6mIiZe7P+AUVg6XgJ0ce2MUSeWjI
+W8D4WdSi0jyqr99TuVBWhbTZJviMB3pHqKaHQ07hnd/lPtvzsiH12qk=
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-ip-cn-and-alt-names.crt b/src/test/ssl/ssl/server-ip-cn-and-alt-names.crt
new file mode 100644
index 0000000000..2be02feb03
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-and-alt-names.crt
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDHTCCAgWgAwIBAgIIICIBBBQ2MQAwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UE
+Aww3VGVzdCBDQSBmb3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHNl
+cnZlciBjZXJ0czAeFw0yMjAxMDQyMjM2MzFaFw00OTA1MjIyMjM2MzFaMDQxHjAc
+BgNVBAsMFVBvc3RncmVTUUwgdGVzdCBzdWl0ZTESMBAGA1UEAwwJMTkyLjAuMi4x
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwmqTdQJfs2Ti9tPitYp2
+27I0HvL/kNSgA6egFr0foRo0BorwJNIzdbV0+EnsfiBNTWL5It26gqO7UP3ms8t2
+vHD5gkXfT+f6ts0lVJEcIOkUD/8ws4Ic9Y4uPqb4gN+pUKqcxtmLW1TYk84MBK59
+Xz4yPPS6N+G/DMMeFHTNkM9EQwn/+DC3fDsWdGYM2GRWDTJGg1A5tSUcF+seu7i1
+Vg7XajBfsvgAUAsrAxV+X/sLZh94HY+paD6wfaI99mY2OXVc/XW/z1r9WQznor65
+ZkonNCaPfavqPG5vqnab9AyQcqPqmX8hf/xrniASBAkqNCctbASrFCIYvCJfGfmX
+EQIDAQABoyUwIzAhBgNVHREEGjAYhwTAAAIChxAgAQ24AAAAAAAAAAAAAAABMA0G
+CSqGSIb3DQEBCwUAA4IBAQBf7kmYfRYfnWk1OUfY3N1kaNg9piBBlFr9g+OQn9KU
+zirkN7s0ZQbCGxV1uJQBKS58NyE414Vorau77379emgYDcCBpDIYpkLiNujVrIOr
+ggRFKsFRgxu4/mw0BSgCcV8RPe9SWHZ90Mos7TMCnW/PdxOCD1wD0YMkcs0rwB3l
+0Kzc7jDnfOEvmgw/Ysm7v67ps+05Uq5VskQ6WrpSAw6kPD/QMuuBAX8ATPczIaox
+zAMyncq1IiSIwG93f3EoQQThdQ70C6G9vLcu9TtL6JAsEMFEzR99gt1Wsqvmgl9W
+kStzj1yjIWeo5gIsa4Jgcke1lZviWyrTxHDfyunYE5i5
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-ip-cn-and-alt-names.key b/src/test/ssl/ssl/server-ip-cn-and-alt-names.key
new file mode 100644
index 0000000000..54fe80fc68
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-and-alt-names.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAwmqTdQJfs2Ti9tPitYp227I0HvL/kNSgA6egFr0foRo0Borw
+JNIzdbV0+EnsfiBNTWL5It26gqO7UP3ms8t2vHD5gkXfT+f6ts0lVJEcIOkUD/8w
+s4Ic9Y4uPqb4gN+pUKqcxtmLW1TYk84MBK59Xz4yPPS6N+G/DMMeFHTNkM9EQwn/
++DC3fDsWdGYM2GRWDTJGg1A5tSUcF+seu7i1Vg7XajBfsvgAUAsrAxV+X/sLZh94
+HY+paD6wfaI99mY2OXVc/XW/z1r9WQznor65ZkonNCaPfavqPG5vqnab9AyQcqPq
+mX8hf/xrniASBAkqNCctbASrFCIYvCJfGfmXEQIDAQABAoIBAB6GgVST5NbT9lbu
++d+rN/JSzqA1Yy8oU19/iEFJvJec96I3WnFNl8rZjN4XLUy4YarO6XMyAUDV2Gll
+FD4Sqjf4PRTZR7DSKaleGIhoqFP6hK3mUY091rIves9XhBkoBPunbipCqgDTF5ZN
+edGaXBECQP0VJ8/yX/7u++AWXthnjDis9X0taZfFg/PYbV7SCJ1Hg1O/wEsgXlnC
+7mbL6wkCW0f6700B0x1kKbZqJY95xRqp6Ipq2lIQbJDdGywoj0WzKqNltf9cer+r
+cXl8WjeiMvvvpl4uGhckAbzUifUzxN6A3f1fu/XKtOmabMi9t7J4MRfgOgedgtQB
+0jaZGSkCgYEA+lBLnNY6M48HX2mdtr86+n41gh69v8Z7oNikJFDZkodrvI8uqE0i
+0XwnYPFddt8NbmuUhhuzI2M8RKhGLgdlbKpkSSVafnMfcxRmX2EAtWQgdvX1Iult
+752LWdBgSuw2vlzvy3T/GYnjMrXSCGput4amqojMEbvUGvIdSUMdHGMCgYEAxtU1
+WixKPL6aEnYy1f4bybzcNgGtl8PBRz9xw+P46g+ijOPoaG9O73Tr7An11AO003Ot
+DHhMW+b8yHLyxoKwS2sU2cN/lKB8xNQYZc1D61RNJlzgnHMXnA0lcH0I3M35fqKr
+/71pD1ZP40SSJS+od/KEjW80XzuOdyiXg8q81vsCgYEAnUPLbbsuj+whzrFVlFZr
+IKwgxCK6Rn3WeIUEA4kEWUpZxvsSbk0gPgtJ1l9uwFt9Xc2bX/KRRv93Aw/SH+Mn
+tvEK1uXwCBgePzgm5W/VeSFyQCthm1CbcHtD7Oa9SPVFo65SPjrAd3QpWVfgoMb1
+zrp7hhMyW0XuCgvpmHjhFk8CgYEAxq/thXM2p+bLLWGhwQcRG5G299zLbBl4PUsf
+0uEvLi17gJCKADoiRdSvoAn/9eHSQ26XYRuhKkDzHxcGlOmpY2PYzRa3mXyZ0VIk
+Iy5wDWwLQCeVZ6D22cClRfgb8BF/nFTPzVmn72SPpgoyhChQj7PvUynpyrRH07jj
+VxYziBsCgYAFr37Xbl0VnXVK+XU+vMwUZjcF4jpoCr7SFZqgRbW2GbYSUoMuPXns
+RnJh+Fvi1NUei+E5s1H4P1pVq4p0jFxP4GvH/qvNjnIn/Er3bbqvpox6dWUJXprq
+qTQSDIeoDC/V8cyRoIfqPvTVqY8Rgew6GEkv0bAImdxhoSng7vIseg==
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.crt b/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.crt
new file mode 100644
index 0000000000..23c06da01c
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.crt
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDQzCCAiugAwIBAgIIICIBBBQ2MQEwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UE
+Aww3VGVzdCBDQSBmb3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHNl
+cnZlciBjZXJ0czAeFw0yMjAxMDQyMjM2MzFaFw00OTA1MjIyMjM2MzFaMDQxHjAc
+BgNVBAsMFVBvc3RncmVTUUwgdGVzdCBzdWl0ZTESMBAGA1UEAwwJMTkyLjAuMi4x
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8xddbo/x2TOSIa/br8BN
+o/URdTr9+l2R5YojiZKDuLxiQVkgC30PJ2/CNFKIh2nHhRrzknI6sETVtrxZ+9V2
+qRc1yShVu462u0DHPRMIZnZIOZg3hlNB0cRWbOglUKlttIARNEQUcTUyPOtyo4/v
++u0Ej5NTNcHFbFT01vdD9MjQiCO3jKdAwPIb14jTg4C71EpZ+LuelDo4DzF2/XgG
+WqUTrgD/XnBU/60PU9Iy3G0nVpx21q6ppn9G7a9R+i8FjBcwW1T+cfsBDWhAv+bi
+RmSAkENf8L8TwOlDQUwROkfz3Hz36vuJjdkreQJsiqL0HnrnH5T5G9UzJO86FvZQ
+5wIDAQABo0swSTBHBgNVHREEQDA+gh1kbnMxLmFsdC1uYW1lLnBnLXNzbHRlc3Qu
+dGVzdIIdZG5zMi5hbHQtbmFtZS5wZy1zc2x0ZXN0LnRlc3QwDQYJKoZIhvcNAQEL
+BQADggEBAF+mfaw6iBPzpCgqq830pHRa3Yzm1aezt8SkeRohUYHNv/yCnDSRaqtj
+xbENih3lJMSTBL3g0wtTOHfH8ViC/h+lvYELHzXKic7gkjV7H5XETKGr0ZsjBBT2
+4cZQKbD9e0x0HrENXMYgGpBf747qL6uTOVJdG0s15hwpLq47bY5WUjXathejbpxW
+prmF8F+xaC52N9P/1VnqguQB909F4x1pyOK7D7tjFu+Y8Je7PHKbb6WY5K6xAv6t
+R17CY0749/FotlphquElUR2bs5Zzv5YrjUHPTcbwKvcH5cdNi93/u6NJt2xNAoYf
+aZERhX5TA9DYk4gC8OY0yGaYCIj3Dd4=
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.key b/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.key
new file mode 100644
index 0000000000..0ace41e0a1
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEogIBAAKCAQEA8xddbo/x2TOSIa/br8BNo/URdTr9+l2R5YojiZKDuLxiQVkg
+C30PJ2/CNFKIh2nHhRrzknI6sETVtrxZ+9V2qRc1yShVu462u0DHPRMIZnZIOZg3
+hlNB0cRWbOglUKlttIARNEQUcTUyPOtyo4/v+u0Ej5NTNcHFbFT01vdD9MjQiCO3
+jKdAwPIb14jTg4C71EpZ+LuelDo4DzF2/XgGWqUTrgD/XnBU/60PU9Iy3G0nVpx2
+1q6ppn9G7a9R+i8FjBcwW1T+cfsBDWhAv+biRmSAkENf8L8TwOlDQUwROkfz3Hz3
+6vuJjdkreQJsiqL0HnrnH5T5G9UzJO86FvZQ5wIDAQABAoIBAGv0BFoFMrHyZQLw
+xe7Wx6P4QTh+aiu1QgVdw0pk9nojrr62hbSUZRZuWyBBRsBcCW7i+Sgf8lA1QXNV
+UeC0e228EPa0It6YEi42JkTJHwHhpVFud7n/X0t4lajnryqTE1UFSp6bXTipFxZW
+uSJJ2ZjliRD5rApDcxkY4WJVjKg3aEt7P/DiM8iKGfyE6stq72VjEbJjdViMEcOP
+BNf0TiREZz5Mp7jAVWhpen0ebbLOBVWV4/ONNcL+yqR4mCEDUSFGewrTVX4zHL0A
+hYk198C5F8sFvEDnFkPco9sXMVanmLoI8sbhP4IIz9g4+GU6kFuj7fUKp11Azqv+
+3WQDKYECgYEA/XG4mmG/g8FG44y42mfZpUXWi1pwU4CQIrhkoU5j7EPQrvRboOOE
+Rv95jSwyZu4vCqjyI5FN1jCGTdhmt++R1e//zH6Hqa9Smo+jw7DtAFrCYd1JnCf1
+ToOwsYPHv4P7A8q8kc5vCNIv+AQSlP/wqdVNo3grdf7cGXkMtEY4F9UCgYEA9Yrq
+zWdnNGPATuSBqL6TSjQ37oR+dBD6WnGsiDenQkOzyDPFZ3CT1DjJghjEtxc8EfNf
+Oo8dMMR2q+5FZQo7WuqONEgyzKePiNR8RK2gOYpgdjN9bih1sAhHR10D26cpwlDJ
+bx7D5ZzENLbdZmfEiWwKswnaIhN4yMalgE0mP8sCgYAhzJy12ftUct4lUosEdX0N
+EXc/NlxshmSyfKzO5kllJNYbvvLJTg5B+agYL6C5IWKcpVNFcwdSXT5L+2QXe5eT
+VGJkvysQchUuD6HjYyD4PyJVMtGyRZHtWpqh0dU9sTg0lUD4oPMl1gIXrVNdE5Tg
+0VV9S3VgUxC/ROlw0TyB0QKBgGsVE0NS9hJF8mc1hko2GnwA++d8Rr2NbfElo+2f
+/8SJTA1ibpOm6AFkZpTjAl8qtdrKPVyHb16GP47Jkd/3r1z979hjKCxSYul0aWF2
+KusNKvZBjFEPOgv0AEniCb2wUCjbHI3mZ95qGLM4kKOJW4/m21+rS0MTJNjCsQic
+HLMzAoGAeCsY09d3m8xGeU+DuTPC6GH7Sgy/NBYqS5VaVNjb2jnuZlW2SSW2oiID
+4tXTi4ruKmHC898BfyFxhSMqub+tg3pVqIYADC71rnJLrVyc1SzoWzL7yMT3qFj7
+C7ZYZYmfG9agcZb5NkqKPTfCxkBhWbdgTTgBKVO/xQst8EUgko8=
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-ip-cn-only.crt b/src/test/ssl/ssl/server-ip-cn-only.crt
new file mode 100644
index 0000000000..9bf015cf18
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-only.crt
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC8TCCAdkCCCAhESkRN1IAMA0GCSqGSIb3DQEBCwUAMEIxQDA+BgNVBAMMN1Rl
+c3QgQ0EgZm9yIFBvc3RncmVTUUwgU1NMIHJlZ3Jlc3Npb24gdGVzdCBzZXJ2ZXIg
+Y2VydHMwHhcNMjExMTI5MTkzNzUyWhcNNDkwNDE2MTkzNzUyWjA0MR4wHAYDVQQL
+DBVQb3N0Z3JlU1FMIHRlc3Qgc3VpdGUxEjAQBgNVBAMMCTE5Mi4wLjIuMTCCASIw
+DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANWs1uUL71nHYF9Zj6p+M3MpYDvx
+32iCjVdtH5a2qpSWHXTg0rR8dLX0y92cvOYvMXHRajZT1avpHr8dooPYSVaXpGMK
+NvF/Qi+WFYovRbP2vmd1yv1cgW/FggbwJFWVobizIz4seyA4d0B2j9fqoi2OFBNP
+huW664SjF0u3p21tDy+43i2LNUMAKf6dnRR5Vqenath87LEU41tSLudu6NXgbFMk
+jvfNkl4d0w7YCzeXmklmSI+uaX3PlJJ4NzQO2j8w5BvnKVhNVD0KjgrXZ6nB/8F7
+Pg3XY+d7rJlwRgXemU6resWQDJ7+UaC9u7I4EIP+9lzCR/nNBqUktpHRmHUCAwEA
+ATANBgkqhkiG9w0BAQsFAAOCAQEAos1JncV8Yf4UaKl6h1GdYtcVtzFyJvBEnhRD
+07ldL+TYnfZiX8wK2ssBtM3cg/C78y5bzdUa5XGS83ZKQJFFdhE7PSnrvyNqyIqY
+ZgNBxto3gyvir+EjO1u9BAB0NP3r3gYoHRDZS1xOPPzt4WgjuUgTLM9k82GsqAbO
+UrOTOdRnkIqC5xLpa05EnRyJPRsR1w1PRJC2XXKnHIuFjMb4v7UuPwyCcX1P5ioc
+rQszQcORy/L+k0ezCkyweORg68htjYbBHuwOuiGfok6yKKDMzrTvD3lIslls6eX7
+4sI3XWqzkPmG9Vsxm9Vu9/Ma+PRO76VyCoIwBd+Ufg5vNXhMmw==
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-ip-cn-only.key b/src/test/ssl/ssl/server-ip-cn-only.key
new file mode 100644
index 0000000000..1966530e72
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-only.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEA1azW5QvvWcdgX1mPqn4zcylgO/HfaIKNV20flraqlJYddODS
+tHx0tfTL3Zy85i8xcdFqNlPVq+kevx2ig9hJVpekYwo28X9CL5YVii9Fs/a+Z3XK
+/VyBb8WCBvAkVZWhuLMjPix7IDh3QHaP1+qiLY4UE0+G5brrhKMXS7enbW0PL7je
+LYs1QwAp/p2dFHlWp6dq2HzssRTjW1Iu527o1eBsUySO982SXh3TDtgLN5eaSWZI
+j65pfc+Ukng3NA7aPzDkG+cpWE1UPQqOCtdnqcH/wXs+Dddj53usmXBGBd6ZTqt6
+xZAMnv5RoL27sjgQg/72XMJH+c0GpSS2kdGYdQIDAQABAoIBAQDNXviU4WnF8rmQ
+K7bH+dBdqbETLKC8BG7xTrMD2sINWlMpmUUrsEtE7+paMGHnJAj0CoF5gg5m0wN4
+UXV4H5QtpEad4p14dAYbUreVP2ZRWKEdM7xM1HKcCUu2e22QzObJbXQ8N+iHyX3k
++Y+7yYrjGiH1hYR0nbnsnAyx++zyYBSQeqzpdQwf/BLY5xZmyYWNfqbckiMpEqMs
+EmZmGXnCjIipzEC0LQHoSW9PNa92Z9bvuxOKYl8iHYDDXjvMRFoZBSiMXpzHQocb
+QlQ5F4ayfW2OrOhpNbY7niYM9GN3Bk9TgMP+0BkJE6uuktLYW35LY1M78CCPWcWb
+npJNK3QBAoGBAOxkGrhAHAysSmtirIyMdvySb76wb/Ukfi+AULKz20FI5j4/GXm9
+qCb2GeT+FFSUHeSC8f0EFnosRYkdBGruqeZioI+5rUkboYFJPspAHAuvg9kgtfF+
+kvphD4O4P/foYsEZRx66FHozDbhrrR5UXc7KzqRIASc/D3FOx2UFJLb1AoGBAOdm
+WcaMvYygl9ZW+ThWAR1xG1X70AGKwrlrpF2hBkWYxSurxSMXnD0DUzC9Nb4EyCaM
+c2uSqEZOKdW+XfXtK2DnqXKfb3YCVEoGN4gVfyuW/vxii/+ZxLo3md/b3vrkZEVp
+pfkXy/HoZ71YN7bNpcDpOnhml6vvuCRCYFnI1WuBAoGAC0shB6pwbJ6Sk5zMN47C
+ZICufAK75o9OxAAyWsdC81SDQ3gKRImuDeZ2CD2nRP8qim9DFl5qoH2a+Nj9DArI
+7SvLFfK9958tURrpuAnmDRzehLIOXzI33WRjtFxKGhLtHOKTRkGHlur3fdcPF0La
+lHWV971E6NYXa8diuU3Mmj0CgYBYd+ka3/QYL83dRKNDxp3mg7fPx9ZewI5yFZVh
+to6PTTkU2Tclk4FIUl0b5TsGyw06r7fxCMENIBUegwmpXGOZSPifuhUDKSDQrE/O
+12knYTNbitG7hy6Pg3JxA77cbTVo1FuAQHjYo+IFohSq7zTP7FtObOrP8XaVZksw
+CHiQAQKBgBW4EiA9AAnZ1LOpifAvM7bs0NHg95qTwtAL52WKom2ga2H+lMhxeu6Y
+hUSytC/f9kALVcYloZhkLYpO07x1gXmy7f4parMjA4Ex+4vfu3kPd8GiNGZ+AUJD
+nnJ1OINY9ziXJZfju7FpVWpkiuPzWCh6y/o3gZ/veq5mIUxuDMVa
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-ip-in-dnsname.crt b/src/test/ssl/ssl/server-ip-in-dnsname.crt
new file mode 100644
index 0000000000..78ad8d99c8
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-in-dnsname.crt
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC/DCCAeSgAwIBAgIIICIDFRVYUgAwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UE
+Aww3VGVzdCBDQSBmb3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHNl
+cnZlciBjZXJ0czAeFw0yMjAzMTUyMjU4NTJaFw00OTA3MzEyMjU4NTJaMCAxHjAc
+BgNVBAsMFVBvc3RncmVTUUwgdGVzdCBzdWl0ZTCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBAMpn5bP1/OfBQR/yvOkOBzxArE1j1YShVa2pcj896+CVDEgV
+N5Hluz7KHU/JYzNZCAHb5WAHuvXxKeoj4Ti5be1KsqO0mN1p+RMN7VlCpCpb0AWT
+z4z+I8TUhSZnmgghHvfW4RfcZMCcHq1vevVTDxR/cAbDPYpgBCD5F/SZMRyMDw5B
+7ILLmft0eqA1nCqavyqBCGZvx1ol8N5BfVdrDXp/rN5997khBWQRZ8g84FZyFZXf
+pwp57eu0OGQDzZFXoEL2t4OVld67K5jcclWVxHY6FGcHjCvyqs48PCPOR84anZwj
+GsqVOS6250/DWKBQO4KyhkTVf0AW/ICGSMOKkAkCAwEAAaMYMBYwFAYDVR0RBA0w
+C4IJMTkyLjAuMi4xMA0GCSqGSIb3DQEBCwUAA4IBAQDIAAH0WJKEpbPN0QihN6SF
+UA5WL4ixsBACo9OIAGkSnKeOeVEG5vvgOna0hjQcOcgtI1oCDLhULcjCuwxiIW6y
+QntOazyo0sooJr0hEm2WfipvIpQs6W9E1OTcs624BAVfkAwr6WT2VwoIAPcQD2nR
+tIQhSUIR9J7Q5WbzuQw7pthQhBfW/UPWw7vajel0r1dflbe0Cgp5WGNfp1kYy+Qf
+XW/YjkstZEP1KFm+TF58uxrIDmYboS8EerUREGQixijbI0AfXjShxtiyS63rbdpo
+3C0BPj9Yx2VtWi4U0qoef/iLJxJBCLvE/97+duPdKx0AkkOWA9VuenkWLp797UM8
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-ip-in-dnsname.key b/src/test/ssl/ssl/server-ip-in-dnsname.key
new file mode 100644
index 0000000000..ba319b001e
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-in-dnsname.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAymfls/X858FBH/K86Q4HPECsTWPVhKFVralyPz3r4JUMSBU3
+keW7PsodT8ljM1kIAdvlYAe69fEp6iPhOLlt7Uqyo7SY3Wn5Ew3tWUKkKlvQBZPP
+jP4jxNSFJmeaCCEe99bhF9xkwJwerW969VMPFH9wBsM9imAEIPkX9JkxHIwPDkHs
+gsuZ+3R6oDWcKpq/KoEIZm/HWiXw3kF9V2sNen+s3n33uSEFZBFnyDzgVnIVld+n
+Cnnt67Q4ZAPNkVegQva3g5WV3rsrmNxyVZXEdjoUZweMK/Kqzjw8I85HzhqdnCMa
+ypU5LrbnT8NYoFA7grKGRNV/QBb8gIZIw4qQCQIDAQABAoIBAA2kPP4JCTeRddMy
+Z/sJIAG2liZNITnkKcMflXyfrsMfKIm/LFSf+CO+OYWEHDR8vqZpbKcxPi+PRnTq
+YCaTkM4aZ7nS1S6vEsNu/90xOaFFONr3YFivVDfS3vp8pwv/N3gaumcCSqQUoZis
+18urAmwuPp2mEQK/f+e9AhlRLdcvlqDyKm+zMrVixK77Hj5JiEkh3rfZ3onHHKGE
+B7T2XRRqnZ4FCN9qLH2pMGUknZ4MGC9SlCyoerXFodb4DhKWQhJDRLjb8qP96r/E
+FGSg5WUiAERU/OgODoqZNTeIwIDB/f9NK45dEY3Hw6BsSFfU2VChrlNoVlzFUx2k
+yaH5Y4ECgYEA8rht3crh3GTy0jBJjNqB2iul8fkG/uiaiSvERWT/+KZnmV1+JGAW
+h2/wvd5apagOJjqKY0bCHMei/qYF9r4yJnkIy4qNper3QUz7TMCjsWduCm8S834A
+Z+Vwi3RBGJiQQH9Dfexko5sDjo+w5g4RsH52INCeReInNdxHOv06jZECgYEA1XrR
+QNwZlxHt3H93YKmKDZXikqW12Cuq6RSwf5VVdeuzV+pUN+/JaSgEuYsBilW7Q5p2
+gPROi0l8/eUPsBJb+dh1BcGzSjI2Kkzf66QOTG83S7tCPwQhwJUAylFuADvURjPQ
+qvqNjbQUomdm2QjBzyWtiFbolqxBgM3dnE6R/vkCgYBYGqQexx83LhmKPGbmTwal
+mARzkg59BxfZRN7IxcG4k0a1v98i+xISdYqwkP7cdOU18Tf8k1mwsrKytrcheqaf
+mn2bzJ5gJKs9s+DgWmjQ45dpCCqb4hfpnro8lKVwdSifkNKB6gYZ8RHYdMYkq+S1
+6SGeBbv95/qNrXjZq8POUQKBgHyaDwD4dsdCY79LdvYofrenQHOv3Q+rjTo2JT6S
+fysww6EQ2M89WiXSgc96Xw/LMl4nDfv+nMmXvyjCRgHS9XRC7yrJAEjSPeM6s4fq
+XZ4nW/ML/YKiesDZN3jfRoFEaoX/QFBLpcuLzG9uQw1ymwy5RSxK7b7kE+eGQU82
+XOihAoGBAI3xvT9fG3jRsSuw/8OQBlmDUFZcT0fRPRZ3pg8XlSreAam4b607d2WY
+u/bBHIclG3CLJ2EFqBtxl9AQeM0OTweF0KmV3dbtdBmaTbnhbK8/NLYnl5+aosEJ
+YrFKD8k8z6z+mYQs+7bAnfRa53TjfC7f24BpgEQyEfKL2fa3PF+J
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/sslfiles.mk b/src/test/ssl/sslfiles.mk
index 7ed3a30f5c..d6a3870079 100644
--- a/src/test/ssl/sslfiles.mk
+++ b/src/test/ssl/sslfiles.mk
@@ -22,9 +22,15 @@
 # key/certificate pair will be generated for you, signed by the appropriate CA.
 #
 SERVERS := server-cn-and-alt-names \
+	server-cn-and-ip-alt-names \
 	server-cn-only \
+	server-ip-cn-only \
+	server-ip-cn-and-alt-names \
+	server-ip-cn-and-dns-alt-names \
+	server-ip-in-dnsname \
 	server-single-alt-name \
 	server-multiple-alt-names \
+	server-ip-alt-names \
 	server-no-names \
 	server-revoked
 CLIENTS := client client-dn client-revoked client_ext
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 5c5b16fbe7..07e5b71d8c 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -249,6 +249,30 @@ $node->connect_fails(
 	  qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/
 );
 
+# Test with an IP address in the Common Name. This is a strange corner case that
+# nevertheless is supported, as long as the address string matches exactly.
+switch_server_cert($node, 'server-ip-cn-only');
+
+$common_connstr =
+  "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+
+$node->connect_ok("$common_connstr host=192.0.2.1",
+	"IP address in the Common Name");
+
+$node->connect_fails(
+	"$common_connstr host=192.000.002.001",
+	"mismatch between host name and server certificate IP address",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.1" does not match host name "192.000.002.001"\E/
+);
+
+# Similarly, we'll also match an IP address in a dNSName SAN. (This is
+# long-standing behavior.)
+switch_server_cert($node, 'server-ip-in-dnsname');
+
+$node->connect_ok("$common_connstr host=192.0.2.1",
+	"IP address in a dNSName");
+
 # Test Subject Alternative Names.
 switch_server_cert($node, 'server-multiple-alt-names');
 
@@ -301,7 +325,54 @@ $node->connect_fails(
 	  qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/
 );
 
-# Test server certificate with a CN and SANs. Per RFCs 2818 and 6125, the CN
+# Test certificate with IP addresses in the SANs.
+switch_server_cert($node, 'server-ip-alt-names');
+
+$node->connect_ok(
+	"$common_connstr host=192.0.2.1",
+	"host matching an IPv4 address (Subject Alternative Name 1)");
+
+$node->connect_ok(
+	"$common_connstr host=192.000.002.001",
+	"host matching an IPv4 address in alternate form (Subject Alternative Name 1)");
+
+$node->connect_fails(
+	"$common_connstr host=192.0.2.2",
+	"host not matching an IPv4 address (Subject Alternative Name 1)",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.1" (and 1 other name) does not match host name "192.0.2.2"\E/
+);
+
+$node->connect_fails(
+	"$common_connstr host=192.0.2.1/32",
+	"IPv4 host with CIDR mask does not match",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.1" (and 1 other name) does not match host name "192.0.2.1\/32"\E/
+);
+
+$node->connect_ok(
+	"$common_connstr host=2001:DB8::1",
+	"host matching an IPv6 address (Subject Alternative Name 2)");
+
+$node->connect_ok(
+	"$common_connstr host=2001:db8:0:0:0:0:0:1",
+	"host matching an IPv6 address in alternate form (Subject Alternative Name 2)");
+
+$node->connect_fails(
+	"$common_connstr host=::1",
+	"host not matching an IPv6 address (Subject Alternative Name 2)",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.1" (and 1 other name) does not match host name "::1"\E/
+);
+
+$node->connect_fails(
+	"$common_connstr host=2001:DB8::1/128",
+	"IPv6 host with CIDR mask does not match",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.1" (and 1 other name) does not match host name "2001:DB8::1\/128"\E/
+);
+
+# Test server certificate with a CN and DNS SANs. Per RFCs 2818 and 6125, the CN
 # should be ignored when the certificate has both.
 switch_server_cert($node, 'server-cn-and-alt-names');
 
@@ -319,6 +390,39 @@ $node->connect_fails(
 	  qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/
 );
 
+# But we will fall back to check the CN if the SANs contain only IP addresses.
+switch_server_cert($node, 'server-cn-and-ip-alt-names');
+
+$node->connect_ok("$common_connstr host=common-name.pg-ssltest.test",
+	"certificate with both a CN and IP SANs matches CN");
+$node->connect_ok("$common_connstr host=192.0.2.1",
+	"certificate with both a CN and IP SANs matches SAN 1");
+$node->connect_ok("$common_connstr host=2001:db8::1",
+	"certificate with both a CN and IP SANs matches SAN 2");
+
+# And now the same tests, but with IP addresses and DNS names swapped.
+switch_server_cert($node, 'server-ip-cn-and-alt-names');
+
+$node->connect_ok("$common_connstr host=192.0.2.2",
+	"certificate with both an IP CN and IP SANs 1");
+$node->connect_ok("$common_connstr host=2001:db8::1",
+	"certificate with both an IP CN and IP SANs 2");
+$node->connect_fails(
+	"$common_connstr host=192.0.2.1",
+	"certificate with both an IP CN and IP SANs ignores CN",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.2" (and 1 other name) does not match host name "192.0.2.1"\E/
+);
+
+switch_server_cert($node, 'server-ip-cn-and-dns-alt-names');
+
+$node->connect_ok("$common_connstr host=192.0.2.1",
+	"certificate with both an IP CN and DNS SANs matches CN");
+$node->connect_ok("$common_connstr host=dns1.alt-name.pg-ssltest.test",
+	"certificate with both an IP CN and DNS SANs matches SAN 1");
+$node->connect_ok("$common_connstr host=dns2.alt-name.pg-ssltest.test",
+	"certificate with both an IP CN and DNS SANs matches SAN 2");
+
 # Finally, test a server certificate that has no CN or SANs. Of course, that's
 # not a very sensible certificate, but libpq should handle it gracefully.
 switch_server_cert($node, 'server-no-names');
-- 
2.25.1

v8-0003-squash-libpq-allow-IP-address-SANs-in-server-cert.patchtext/x-patch; name=v8-0003-squash-libpq-allow-IP-address-SANs-in-server-cert.patchDownload
From 276abd9da9d424b51b67c18db82a19296efe3f2c Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Wed, 5 Jan 2022 15:47:03 -0800
Subject: [PATCH v8 3/3] squash! libpq: allow IP address SANs in server certs

Per review, provide a pg_inet_pton() interface for IPv6 and refactor the
internals accordingly. IPv4 is not yet implemented. The call sites that
just want standard addresses without a CIDR mask have been updated to
use the new function.

Internally, the IPv6 parser now takes an allow_cidr boolean. I've
plumbed that all the way down to getbits() in order to minimize the
amount of code touched, at the expense of an unnecessary function call
in the failing case.
---
 src/include/port.h                       |  1 +
 src/interfaces/libpq/fe-secure-common.c  |  7 +--
 src/interfaces/libpq/fe-secure-openssl.c |  2 +-
 src/port/inet_net_pton.c                 | 66 ++++++++++++++++++------
 4 files changed, 52 insertions(+), 24 deletions(-)

diff --git a/src/include/port.h b/src/include/port.h
index 2852e5b58b..fd046f4c24 100644
--- a/src/include/port.h
+++ b/src/include/port.h
@@ -517,6 +517,7 @@ extern char *pg_inet_net_ntop(int af, const void *src, int bits,
 
 /* port/inet_net_pton.c */
 extern int	pg_inet_net_pton(int af, const char *src, void *dst, size_t size);
+extern int	pg_inet_pton(int af, const char *src, void *dst);
 
 /* port/pg_strong_random.c */
 extern void pg_strong_random_init(void);
diff --git a/src/interfaces/libpq/fe-secure-common.c b/src/interfaces/libpq/fe-secure-common.c
index 2b8f2d6b24..4d78715756 100644
--- a/src/interfaces/libpq/fe-secure-common.c
+++ b/src/interfaces/libpq/fe-secure-common.c
@@ -211,12 +211,7 @@ pq_verify_peer_name_matches_certificate_ip(PGconn *conn,
 
 		family = PGSQL_AF_INET6;
 
-		/*
-		 * pg_inet_net_pton() will accept CIDR masks, which we don't want to
-		 * match, so skip the comparison if the host string contains a slash.
-		 */
-		if (!strchr(host, '/')
-			&& pg_inet_net_pton(PGSQL_AF_INET6, host, addr, -1) == 128)
+		if (pg_inet_pton(PGSQL_AF_INET6, host, addr) == 1)
 		{
 			if (memcmp(ipdata, addr, iplen) == 0)
 				match = 1;
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index 6ec4c2041c..06b166b7fd 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -554,7 +554,7 @@ is_ip_address(const char *host)
 	unsigned char	dummy6[16];
 
 	return inet_aton(host, &dummy4)
-		|| (pg_inet_net_pton(PGSQL_AF_INET6, host, dummy6, -1) == 128);
+		|| (pg_inet_pton(PGSQL_AF_INET6, host, dummy6) == 1);
 }
 
 /*
diff --git a/src/port/inet_net_pton.c b/src/port/inet_net_pton.c
index 5bae811c6f..ae0d1fd2e3 100644
--- a/src/port/inet_net_pton.c
+++ b/src/port/inet_net_pton.c
@@ -42,8 +42,8 @@ static const char rcsid[] = "Id: inet_net_pton.c,v 1.4.2.3 2004/03/17 00:40:11 m
 
 static int	inet_net_pton_ipv4(const char *src, u_char *dst);
 static int	inet_cidr_pton_ipv4(const char *src, u_char *dst, size_t size);
-static int	inet_net_pton_ipv6(const char *src, u_char *dst);
-static int	inet_cidr_pton_ipv6(const char *src, u_char *dst, size_t size);
+static int	inet_pton_ipv6_internal(const char *src, u_char *dst, size_t size,
+									bool allow_cidr);
 
 
 /*
@@ -74,9 +74,9 @@ pg_inet_net_pton(int af, const char *src, void *dst, size_t size)
 				inet_net_pton_ipv4(src, dst) :
 				inet_cidr_pton_ipv4(src, dst, size);
 		case PGSQL_AF_INET6:
-			return size == -1 ?
-				inet_net_pton_ipv6(src, dst) :
-				inet_cidr_pton_ipv6(src, dst, size);
+			return inet_pton_ipv6_internal(src, dst,
+										   (size == -1) ? 16 : size,
+										   true /* allow CIDR */);
 		default:
 			errno = EAFNOSUPPORT;
 			return -1;
@@ -352,13 +352,22 @@ emsgsize:
 }
 
 static int
-getbits(const char *src, int *bitsp)
+getbits(const char *src, int *bitsp, bool allow_cidr)
 {
 	static const char digits[] = "0123456789";
 	int			n;
 	int			val;
 	char		ch;
 
+	if (!allow_cidr)
+	{
+		/*
+		 * If we got here, the top-level call is to pg_inet_pton() and the
+		 * caller supplied a CIDR mask, which isn't allowed.
+		 */
+		return 0;
+	}
+
 	val = 0;
 	n = 0;
 	while ((ch = *src++) != '\0')
@@ -385,7 +394,7 @@ getbits(const char *src, int *bitsp)
 }
 
 static int
-getv4(const char *src, u_char *dst, int *bitsp)
+getv4(const char *src, u_char *dst, int *bitsp, bool allow_cidr)
 {
 	static const char digits[] = "0123456789";
 	u_char	   *odst = dst;
@@ -416,7 +425,7 @@ getv4(const char *src, u_char *dst, int *bitsp)
 				return 0;
 			*dst++ = val;
 			if (ch == '/')
-				return getbits(src, bitsp);
+				return getbits(src, bitsp, allow_cidr);
 			val = 0;
 			n = 0;
 			continue;
@@ -431,18 +440,12 @@ getv4(const char *src, u_char *dst, int *bitsp)
 	return 1;
 }
 
-static int
-inet_net_pton_ipv6(const char *src, u_char *dst)
-{
-	return inet_cidr_pton_ipv6(src, dst, 16);
-}
-
 #define NS_IN6ADDRSZ 16
 #define NS_INT16SZ 2
 #define NS_INADDRSZ 4
 
 static int
-inet_cidr_pton_ipv6(const char *src, u_char *dst, size_t size)
+inet_pton_ipv6_internal(const char *src, u_char *dst, size_t size, bool allow_cidr)
 {
 	static const char xdigits_l[] = "0123456789abcdef",
 				xdigits_u[] = "0123456789ABCDEF";
@@ -510,13 +513,13 @@ inet_cidr_pton_ipv6(const char *src, u_char *dst, size_t size)
 			continue;
 		}
 		if (ch == '.' && ((tp + NS_INADDRSZ) <= endp) &&
-			getv4(curtok, tp, &bits) > 0)
+			getv4(curtok, tp, &bits, allow_cidr) > 0)
 		{
 			tp += NS_INADDRSZ;
 			saw_xdigit = 0;
 			break;				/* '\0' was seen by inet_pton4(). */
 		}
-		if (ch == '/' && getbits(src, &bits) > 0)
+		if (ch == '/' && getbits(src, &bits, allow_cidr) > 0)
 			break;
 		goto enoent;
 	}
@@ -530,6 +533,9 @@ inet_cidr_pton_ipv6(const char *src, u_char *dst, size_t size)
 	if (bits == -1)
 		bits = 128;
 
+	/* Without a CIDR mask, the above should guarantee a complete address. */
+	Assert(allow_cidr || bits == 128);
+
 	endp = tmp + 16;
 
 	if (colonp != NULL)
@@ -568,3 +574,29 @@ emsgsize:
 	errno = EMSGSIZE;
 	return -1;
 }
+
+/*
+ * Converts a network address in presentation format to network format. The main
+ * difference between this and pg_inet_net_pton() above is that CIDR notation is
+ * not allowed.
+ *
+ * The memory pointed to by dst depends on the address family:
+ *
+ *     PGSQL_AF_INET:  not implemented yet (returns -1 with EAFNOSUPPORT)
+ *     PGSQL_AF_INET6: *dst must be a u_char[16]
+ *
+ * Over and above the standard inet_pton() functionality, we also set errno on
+ * parse failure, with the same meanings as with pg_inet_net_pton().
+ */
+int
+pg_inet_pton(int af, const char *src, void *dst)
+{
+	if (af != PGSQL_AF_INET6)
+	{
+		/* Currently only IPv6 is implemented. */
+		errno = EAFNOSUPPORT;
+		return -1;
+	}
+
+	return (inet_pton_ipv6_internal(src, dst, 16, false /* no CIDR */) == 128);
+}
-- 
2.25.1

#28Kyotaro Horiguchi
horikyota.ntt@gmail.com
In reply to: Jacob Champion (#27)
Re: [PATCH] Accept IP addresses in server certificate SANs

At Thu, 17 Mar 2022 21:55:07 +0000, Jacob Champion <pchampion@vmware.com> wrote in

On Wed, 2022-03-16 at 23:49 +0000, Jacob Champion wrote:

Thank you for the explanation -- the misunderstanding was all on my
end. I thought you were asking me to move the check_cn assignment
instead of copying it to the end. I agree that your suggestion is much
clearer, and I'll make that change tomorrow.

Done in v8. Thanks again for your suggestions (and for your
perseverance when I didn't get it)!

Thanks! .. and some nitpicks..(Sorry)

fe-secure-common.c doesn't need netinet/in.h.

+++ b/src/include/utils/inet.h
.. 
+#include "common/inet-common.h"

I'm not sure about the project policy on #include practice, but I
think it is the common practice not to include headers that are not
required by the file itself. In this case, fe-secure-common.h itself
doesn't need the include. Instead, fe-secure-openssl.c and
fe-secure-common.c needs the include.

regards.

--
Kyotaro Horiguchi
NTT Open Source Software Center

#29Kyotaro Horiguchi
horikyota.ntt@gmail.com
In reply to: Kyotaro Horiguchi (#28)
Re: [PATCH] Accept IP addresses in server certificate SANs

At Fri, 18 Mar 2022 16:38:57 +0900 (JST), Kyotaro Horiguchi <horikyota.ntt@gmail.com> wrote in

At Thu, 17 Mar 2022 21:55:07 +0000, Jacob Champion <pchampion@vmware.com> wrot> Thanks! .. and some nitpicks..(Sorry)

fe-secure-common.c doesn't need netinet/in.h.

+++ b/src/include/utils/inet.h
.. 
+#include "common/inet-common.h"

I'm not sure about the project policy on #include practice, but I
think it is the common practice not to include headers that are not
required by the file itself. In this case, fe-secure-common.h itself
doesn't need the include. Instead, fe-secure-openssl.c and
fe-secure-common.c needs the include.

I noticed that this doesn't contain doc changes.

https://www.postgresql.org/docs/current/libpq-ssl.html

In verify-full mode, the host name is matched against the
certificate's Subject Alternative Name attribute(s), or against the
Common Name attribute if no Subject Alternative Name of type dNSName
is present. If the certificate's name attribute starts with an
asterisk (*), the asterisk will be treated as a wildcard, which will
match all characters except a dot (.). This means the certificate will
not match subdomains. If the connection is made using an IP address
instead of a host name, the IP address will be matched (without doing
any DNS lookups).

This refers to dNSName, so we should revise this so that it describes
the new behavior.

regards.

--
Kyotaro Horiguchi
NTT Open Source Software Center

#30Jacob Champion
pchampion@vmware.com
In reply to: Kyotaro Horiguchi (#29)
4 attachment(s)
Re: [PATCH] Accept IP addresses in server certificate SANs

On Tue, 2022-03-22 at 13:32 +0900, Kyotaro Horiguchi wrote:

At Fri, 18 Mar 2022 16:38:57 +0900 (JST), Kyotaro Horiguchi <horikyota.ntt@gmail.com> wrote in

fe-secure-common.c doesn't need netinet/in.h.

+++ b/src/include/utils/inet.h
..
+#include "common/inet-common.h"

I'm not sure about the project policy on #include practice, but I
think it is the common practice not to include headers that are not
required by the file itself. In this case, fe-secure-common.h itself
doesn't need the include. Instead, fe-secure-openssl.c and
fe-secure-common.c needs the include.

Thanks, looks like I had some old header dependencies left over from
several versions ago. Fixed in v9.

I noticed that this doesn't contain doc changes.

https://nam04.safelinks.protection.outlook.com/?url=https%3A%2F%2Fwww.postgresql.org%2Fdocs%2Fcurrent%2Flibpq-ssl.html&amp;amp;data=04%7C01%7Cpchampion%40vmware.com%7Cb25566c0f0124a30221908da0bbcec13%7Cb39138ca3cee4b4aa4d6cd83d9dd62f0%7C0%7C0%7C637835203290105956%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000&amp;amp;sdata=sZuKc9UxmW1oZQij%2F%2F91rkEF57BZiQebkXtvEt%2FdROU%3D&amp;amp;reserved=0

In verify-full mode, the host name is matched against the
certificate's Subject Alternative Name attribute(s), or against the
Common Name attribute if no Subject Alternative Name of type dNSName
is present. If the certificate's name attribute starts with an
asterisk (*), the asterisk will be treated as a wildcard, which will
match all characters except a dot (.). This means the certificate will
not match subdomains. If the connection is made using an IP address
instead of a host name, the IP address will be matched (without doing
any DNS lookups).

This refers to dNSName, so we should revise this so that it describes
the new behavior.

v9 contains the bare minimum but I don't think it's quite enough. How
much of the behavior (and edge cases) do you think we should detail
here? All of it?

Thanks,
--Jacob

Attachments:

since-v8.diff.txttext/plain; name=since-v8.diff.txtDownload
commit 6278176f7f417f0afecceb71807eb480e20769ef
Author: Jacob Champion <pchampion@vmware.com>
Date:   Tue Mar 22 13:21:48 2022 -0700

    squash! libpq: add OAUTHBEARER SASL mechanism
    
    feedback:
    - clean up header deps
    - add a mention in the docs

diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 3998b1781b..bdbfed1aa5 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -8343,7 +8343,8 @@ ldap://ldap.acme.com/cn=dbserver,cn=hosts?pgconnectinfo?base?(objectclass=*)
   <para>
    In <literal>verify-full</literal> mode, the host name is matched against the
    certificate's Subject Alternative Name attribute(s), or against the
-   Common Name attribute if no Subject Alternative Name of type <literal>dNSName</literal> is
+   Common Name attribute if no Subject Alternative Name of type <literal>dNSName</literal>
+   (or <literal>iPAddress</literal>, if the connection uses an IP address) is
    present.  If the certificate's name attribute starts with an asterisk
    (<literal>*</literal>), the asterisk will be treated as
    a wildcard, which will match all characters <emphasis>except</emphasis> a dot
diff --git a/src/interfaces/libpq/fe-secure-common.c b/src/interfaces/libpq/fe-secure-common.c
index 4d78715756..9c408df369 100644
--- a/src/interfaces/libpq/fe-secure-common.c
+++ b/src/interfaces/libpq/fe-secure-common.c
@@ -19,9 +19,9 @@
 
 #include "postgres_fe.h"
 
-#include <netinet/in.h>
 #include <arpa/inet.h>
 
+#include "common/inet-common.h"
 #include "fe-secure-common.h"
 
 #include "libpq-int.h"
diff --git a/src/interfaces/libpq/fe-secure-common.h b/src/interfaces/libpq/fe-secure-common.h
index 20ff9ba5db..d18db7138c 100644
--- a/src/interfaces/libpq/fe-secure-common.h
+++ b/src/interfaces/libpq/fe-secure-common.h
@@ -16,7 +16,6 @@
 #ifndef FE_SECURE_COMMON_H
 #define FE_SECURE_COMMON_H
 
-#include "common/inet-common.h"
 #include "libpq-fe.h"
 
 extern int	pq_verify_peer_name_matches_certificate_name(PGconn *conn,
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index 06b166b7fd..b70bd2eb19 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -26,6 +26,7 @@
 #include <fcntl.h>
 #include <ctype.h>
 
+#include "common/inet-common.h"
 #include "libpq-fe.h"
 #include "fe-auth.h"
 #include "fe-secure-common.h"
v9-0001-Move-inet_net_pton-to-src-port.patchtext/x-patch; name=v9-0001-Move-inet_net_pton-to-src-port.patchDownload
From 84a107da33b7d901cb318f8c2fd140644fbc5100 Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Wed, 24 Nov 2021 14:46:11 -0800
Subject: [PATCH v9 1/3] Move inet_net_pton() to src/port

This will be helpful for IP address verification in libpq.
---
 src/backend/utils/adt/Makefile                  |  1 -
 src/include/port.h                              |  3 +++
 src/include/utils/builtins.h                    |  4 ----
 src/port/Makefile                               |  1 +
 src/{backend/utils/adt => port}/inet_net_pton.c | 16 +++++++++++++++-
 src/tools/msvc/Mkvcbuild.pm                     |  2 +-
 6 files changed, 20 insertions(+), 7 deletions(-)
 rename src/{backend/utils/adt => port}/inet_net_pton.c (96%)

diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile
index 41b486bcef..d173d52157 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -43,7 +43,6 @@ OBJS = \
 	geo_selfuncs.o \
 	geo_spgist.o \
 	inet_cidr_ntop.o \
-	inet_net_pton.o \
 	int.o \
 	int8.o \
 	json.o \
diff --git a/src/include/port.h b/src/include/port.h
index 3d103a2b31..2852e5b58b 100644
--- a/src/include/port.h
+++ b/src/include/port.h
@@ -515,6 +515,9 @@ extern int	pg_codepage_to_encoding(UINT cp);
 extern char *pg_inet_net_ntop(int af, const void *src, int bits,
 							  char *dst, size_t size);
 
+/* port/inet_net_pton.c */
+extern int	pg_inet_net_pton(int af, const char *src, void *dst, size_t size);
+
 /* port/pg_strong_random.c */
 extern void pg_strong_random_init(void);
 extern bool pg_strong_random(void *buf, size_t len);
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 666e545496..67b9326dc8 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -93,10 +93,6 @@ extern int	xidLogicalComparator(const void *arg1, const void *arg2);
 extern char *pg_inet_cidr_ntop(int af, const void *src, int bits,
 							   char *dst, size_t size);
 
-/* inet_net_pton.c */
-extern int	pg_inet_net_pton(int af, const char *src,
-							 void *dst, size_t size);
-
 /* network.c */
 extern double convert_network_to_scalar(Datum value, Oid typid, bool *failure);
 extern Datum network_scan_first(Datum in);
diff --git a/src/port/Makefile b/src/port/Makefile
index bfe1feb0d4..c3ae7b3d5c 100644
--- a/src/port/Makefile
+++ b/src/port/Makefile
@@ -43,6 +43,7 @@ OBJS = \
 	bsearch_arg.o \
 	chklocale.o \
 	inet_net_ntop.o \
+	inet_net_pton.o \
 	noblock.o \
 	path.o \
 	pg_bitutils.o \
diff --git a/src/backend/utils/adt/inet_net_pton.c b/src/port/inet_net_pton.c
similarity index 96%
rename from src/backend/utils/adt/inet_net_pton.c
rename to src/port/inet_net_pton.c
index d3221a1313..bae50ba67e 100644
--- a/src/backend/utils/adt/inet_net_pton.c
+++ b/src/port/inet_net_pton.c
@@ -14,14 +14,18 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
  * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  *
- *	  src/backend/utils/adt/inet_net_pton.c
+ *	  src/port/inet_net_pton.c
  */
 
 #if defined(LIBC_SCCS) && !defined(lint)
 static const char rcsid[] = "Id: inet_net_pton.c,v 1.4.2.3 2004/03/17 00:40:11 marka Exp $";
 #endif
 
+#ifndef FRONTEND
 #include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
 
 #include <sys/socket.h>
 #include <netinet/in.h>
@@ -29,9 +33,19 @@ static const char rcsid[] = "Id: inet_net_pton.c,v 1.4.2.3 2004/03/17 00:40:11 m
 #include <assert.h>
 #include <ctype.h>
 
+#ifndef FRONTEND
 #include "utils/builtins.h" /* pgrminclude ignore */	/* needed on some
 														 * platforms */
 #include "utils/inet.h"
+#else
+/*
+ * In a frontend build, we can't include inet.h, but we still need to have
+ * sensible definitions of these two constants.  Note that pg_inet_net_ntop()
+ * assumes that PGSQL_AF_INET is equal to AF_INET.
+ */
+#define PGSQL_AF_INET	(AF_INET + 0)
+#define PGSQL_AF_INET6	(AF_INET + 1)
+#endif
 
 
 static int	inet_net_pton_ipv4(const char *src, u_char *dst);
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 441d6ae6bf..667b173ec3 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -100,7 +100,7 @@ sub mkvcbuild
 
 	our @pgportfiles = qw(
 	  chklocale.c explicit_bzero.c fls.c getpeereid.c getrusage.c inet_aton.c
-	  getaddrinfo.c gettimeofday.c inet_net_ntop.c kill.c open.c
+	  getaddrinfo.c gettimeofday.c inet_net_ntop.c inet_net_pton.c kill.c open.c
 	  snprintf.c strlcat.c strlcpy.c dirmod.c noblock.c path.c
 	  dirent.c dlopen.c getopt.c getopt_long.c link.c
 	  pread.c preadv.c pwrite.c pwritev.c pg_bitutils.c
-- 
2.25.1

v9-0002-libpq-allow-IP-address-SANs-in-server-certs.patchtext/x-patch; name=v9-0002-libpq-allow-IP-address-SANs-in-server-certs.patchDownload
From afc40ea744fab7eeb2c2c83c3ec8323233abbc87 Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Thu, 18 Nov 2021 15:36:18 -0800
Subject: [PATCH v9 2/3] libpq: allow IP address SANs in server certs

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.

- Move PGSQL_AF_INET* to inet-common.h to reduce copy-paste.
---
 doc/src/sgml/libpq.sgml                       |   3 +-
 src/include/common/inet-common.h              |  35 +++++
 src/include/utils/inet.h                      |  13 +-
 src/interfaces/libpq/fe-secure-common.c       | 104 +++++++++++++
 src/interfaces/libpq/fe-secure-common.h       |   4 +
 src/interfaces/libpq/fe-secure-openssl.c      | 139 ++++++++++++++++--
 src/port/inet_net_ntop.c                      |  12 +-
 src/port/inet_net_pton.c                      |  10 +-
 .../conf/server-cn-and-ip-alt-names.config    |  24 +++
 src/test/ssl/conf/server-ip-alt-names.config  |  19 +++
 .../conf/server-ip-cn-and-alt-names.config    |  21 +++
 .../server-ip-cn-and-dns-alt-names.config     |  21 +++
 src/test/ssl/conf/server-ip-cn-only.config    |  12 ++
 src/test/ssl/conf/server-ip-in-dnsname.config |  18 +++
 .../ssl/ssl/server-cn-and-ip-alt-names.crt    |  20 +++
 .../ssl/ssl/server-cn-and-ip-alt-names.key    |  27 ++++
 src/test/ssl/ssl/server-ip-alt-names.crt      |  19 +++
 src/test/ssl/ssl/server-ip-alt-names.key      |  27 ++++
 .../ssl/ssl/server-ip-cn-and-alt-names.crt    |  19 +++
 .../ssl/ssl/server-ip-cn-and-alt-names.key    |  27 ++++
 .../ssl/server-ip-cn-and-dns-alt-names.crt    |  20 +++
 .../ssl/server-ip-cn-and-dns-alt-names.key    |  27 ++++
 src/test/ssl/ssl/server-ip-cn-only.crt        |  18 +++
 src/test/ssl/ssl/server-ip-cn-only.key        |  27 ++++
 src/test/ssl/ssl/server-ip-in-dnsname.crt     |  18 +++
 src/test/ssl/ssl/server-ip-in-dnsname.key     |  27 ++++
 src/test/ssl/sslfiles.mk                      |   6 +
 src/test/ssl/t/001_ssltests.pl                | 106 ++++++++++++-
 28 files changed, 776 insertions(+), 47 deletions(-)
 create mode 100644 src/include/common/inet-common.h
 create mode 100644 src/test/ssl/conf/server-cn-and-ip-alt-names.config
 create mode 100644 src/test/ssl/conf/server-ip-alt-names.config
 create mode 100644 src/test/ssl/conf/server-ip-cn-and-alt-names.config
 create mode 100644 src/test/ssl/conf/server-ip-cn-and-dns-alt-names.config
 create mode 100644 src/test/ssl/conf/server-ip-cn-only.config
 create mode 100644 src/test/ssl/conf/server-ip-in-dnsname.config
 create mode 100644 src/test/ssl/ssl/server-cn-and-ip-alt-names.crt
 create mode 100644 src/test/ssl/ssl/server-cn-and-ip-alt-names.key
 create mode 100644 src/test/ssl/ssl/server-ip-alt-names.crt
 create mode 100644 src/test/ssl/ssl/server-ip-alt-names.key
 create mode 100644 src/test/ssl/ssl/server-ip-cn-and-alt-names.crt
 create mode 100644 src/test/ssl/ssl/server-ip-cn-and-alt-names.key
 create mode 100644 src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.crt
 create mode 100644 src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.key
 create mode 100644 src/test/ssl/ssl/server-ip-cn-only.crt
 create mode 100644 src/test/ssl/ssl/server-ip-cn-only.key
 create mode 100644 src/test/ssl/ssl/server-ip-in-dnsname.crt
 create mode 100644 src/test/ssl/ssl/server-ip-in-dnsname.key

diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 3998b1781b..bdbfed1aa5 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -8343,7 +8343,8 @@ ldap://ldap.acme.com/cn=dbserver,cn=hosts?pgconnectinfo?base?(objectclass=*)
   <para>
    In <literal>verify-full</literal> mode, the host name is matched against the
    certificate's Subject Alternative Name attribute(s), or against the
-   Common Name attribute if no Subject Alternative Name of type <literal>dNSName</literal> is
+   Common Name attribute if no Subject Alternative Name of type <literal>dNSName</literal>
+   (or <literal>iPAddress</literal>, if the connection uses an IP address) is
    present.  If the certificate's name attribute starts with an asterisk
    (<literal>*</literal>), the asterisk will be treated as
    a wildcard, which will match all characters <emphasis>except</emphasis> a dot
diff --git a/src/include/common/inet-common.h b/src/include/common/inet-common.h
new file mode 100644
index 0000000000..3ad72261b6
--- /dev/null
+++ b/src/include/common/inet-common.h
@@ -0,0 +1,35 @@
+/*-------------------------------------------------------------------------
+ *
+ * inet-common.h
+ *
+ *	  Common code for clients of the inet_* APIs.
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/common/inet-common.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef INET_COMMON_H
+#define INET_COMMON_H
+
+/*
+ * We use these values for the "family" field of inet_struct.
+ *
+ * Referencing all of the non-AF_INET types to AF_INET lets us work on
+ * machines which may not have the appropriate address family (like
+ * inet6 addresses when AF_INET6 isn't present) but doesn't cause a
+ * dump/reload requirement.  Pre-7.4 databases used AF_INET for the family
+ * type on disk.
+ *
+ * In a frontend build, we can't include inet.h, but we still need to have
+ * sensible definitions of these two constants.  (Frontend clients should
+ * include this header directly.)  Note that pg_inet_net_ntop()
+ * assumes that PGSQL_AF_INET is equal to AF_INET.
+ */
+#define PGSQL_AF_INET	(AF_INET + 0)
+#define PGSQL_AF_INET6	(AF_INET + 1)
+
+#endif							/* INET_COMMON_H */
diff --git a/src/include/utils/inet.h b/src/include/utils/inet.h
index 3073c0307e..c56c016987 100644
--- a/src/include/utils/inet.h
+++ b/src/include/utils/inet.h
@@ -14,6 +14,7 @@
 #ifndef INET_H
 #define INET_H
 
+#include "common/inet-common.h"
 #include "fmgr.h"
 
 /*
@@ -27,18 +28,6 @@ typedef struct
 	unsigned char ipaddr[16];	/* up to 128 bits of address */
 } inet_struct;
 
-/*
- * We use these values for the "family" field.
- *
- * Referencing all of the non-AF_INET types to AF_INET lets us work on
- * machines which may not have the appropriate address family (like
- * inet6 addresses when AF_INET6 isn't present) but doesn't cause a
- * dump/reload requirement.  Pre-7.4 databases used AF_INET for the family
- * type on disk.
- */
-#define PGSQL_AF_INET	(AF_INET + 0)
-#define PGSQL_AF_INET6	(AF_INET + 1)
-
 /*
  * Both INET and CIDR addresses are represented within Postgres as varlena
  * objects, ie, there is a varlena header in front of the struct type
diff --git a/src/interfaces/libpq/fe-secure-common.c b/src/interfaces/libpq/fe-secure-common.c
index bd46f08fae..2c0af62afe 100644
--- a/src/interfaces/libpq/fe-secure-common.c
+++ b/src/interfaces/libpq/fe-secure-common.c
@@ -19,9 +19,13 @@
 
 #include "postgres_fe.h"
 
+#include <arpa/inet.h>
+
+#include "common/inet-common.h"
 #include "fe-secure-common.h"
 
 #include "libpq-int.h"
+#include "port.h"
 #include "pqexpbuffer.h"
 
 /*
@@ -144,6 +148,106 @@ pq_verify_peer_name_matches_certificate_name(PGconn *conn,
 	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'))
+	{
+		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 = PGSQL_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;
+		}
+	}
+	else if (iplen == 16)
+	{
+		/* IPv6 */
+		unsigned char addr[16];
+
+		family = PGSQL_AF_INET6;
+
+		/*
+		 * pg_inet_net_pton() will accept CIDR masks, which we don't want to
+		 * match, so skip the comparison if the host string contains a slash.
+		 */
+		if (!strchr(host, '/')
+			&& pg_inet_net_pton(PGSQL_AF_INET6, host, addr, -1) == 128)
+		{
+			if (memcmp(ipdata, addr, iplen) == 0)
+				match = 1;
+		}
+	}
+	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.
  *
diff --git a/src/interfaces/libpq/fe-secure-common.h b/src/interfaces/libpq/fe-secure-common.h
index 1cca6d785a..d18db7138c 100644
--- a/src/interfaces/libpq/fe-secure-common.h
+++ b/src/interfaces/libpq/fe-secure-common.h
@@ -21,6 +21,10 @@
 extern int	pq_verify_peer_name_matches_certificate_name(PGconn *conn,
 														 const char *namedata, size_t namelen,
 														 char **store_name);
+extern int	pq_verify_peer_name_matches_certificate_ip(PGconn *conn,
+													   const unsigned char *addrdata,
+													   size_t addrlen,
+													   char **store_name);
 extern bool pq_verify_peer_name_matches_certificate(PGconn *conn);
 
 #endif							/* FE_SECURE_COMMON_H */
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index d81218a4cc..7656c3a75e 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -26,6 +26,7 @@
 #include <fcntl.h>
 #include <ctype.h>
 
+#include "common/inet-common.h"
 #include "libpq-fe.h"
 #include "fe-auth.h"
 #include "fe-secure-common.h"
@@ -72,6 +73,9 @@ static int	verify_cb(int ok, X509_STORE_CTX *ctx);
 static int	openssl_verify_peer_name_matches_certificate_name(PGconn *conn,
 															  ASN1_STRING *name,
 															  char **store_name);
+static int	openssl_verify_peer_name_matches_certificate_ip(PGconn *conn,
+															ASN1_OCTET_STRING *addr_entry,
+															char **store_name);
 static void destroy_ssl_system(void);
 static int	initialize_SSL(PGconn *conn);
 static PostgresPollingStatusType open_client_SSL(PGconn *);
@@ -509,6 +513,51 @@ openssl_verify_peer_name_matches_certificate_name(PGconn *conn, ASN1_STRING *nam
 	return pq_verify_peer_name_matches_certificate_name(conn, (const char *) namedata, len, store_name);
 }
 
+/*
+ * OpenSSL-specific wrapper around
+ * pq_verify_peer_name_matches_certificate_ip(), converting the
+ * ASN1_OCTET_STRING into a plain C string.
+ */
+static int
+openssl_verify_peer_name_matches_certificate_ip(PGconn *conn,
+												ASN1_OCTET_STRING *addr_entry,
+												char **store_name)
+{
+	int			len;
+	const unsigned char *addrdata;
+
+	/* Should not happen... */
+	if (addr_entry == NULL)
+	{
+		appendPQExpBufferStr(&conn->errorMessage,
+							 libpq_gettext("SSL certificate's address entry is missing\n"));
+		return -1;
+	}
+
+	/*
+	 * GEN_IPADD is an OCTET STRING containing an IP address in network byte
+	 * order.
+	 */
+#ifdef HAVE_ASN1_STRING_GET0_DATA
+	addrdata = ASN1_STRING_get0_data(addr_entry);
+#else
+	addrdata = ASN1_STRING_data(addr_entry);
+#endif
+	len = ASN1_STRING_length(addr_entry);
+
+	return pq_verify_peer_name_matches_certificate_ip(conn, addrdata, len, store_name);
+}
+
+static bool
+is_ip_address(const char *host)
+{
+	struct in_addr	dummy4;
+	unsigned char	dummy6[16];
+
+	return inet_aton(host, &dummy4)
+		|| (pg_inet_net_pton(PGSQL_AF_INET6, host, dummy6, -1) == 128);
+}
+
 /*
  *	Verify that the server certificate matches the hostname we connected to.
  *
@@ -522,6 +571,36 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 	STACK_OF(GENERAL_NAME) * peer_san;
 	int			i;
 	int			rc = 0;
+	char	   *host = conn->connhost[conn->whichhost].host;
+	int			host_type;
+	bool		check_cn = true;
+
+	Assert(host && host[0]); /* should be guaranteed by caller */
+
+	/*
+	 * We try to match the NSS behavior here, which is a slight departure from
+	 * the spec but seems to make more intuitive sense:
+	 *
+	 * If connhost contains a DNS name, and the certificate's SANs contain any
+	 * dNSName entries, then we'll ignore the Subject Common Name entirely;
+	 * otherwise, we fall back to checking the CN. (This behavior matches the
+	 * RFC.)
+	 *
+	 * If connhost contains an IP address, and the SANs contain iPAddress
+	 * entries, we again ignore the CN. Otherwise, we allow the CN to match,
+	 * EVEN IF there is a dNSName in the SANs. (RFC 6125 prohibits this: "A
+	 * client MUST NOT seek a match for a reference identifier of CN-ID if the
+	 * presented identifiers include a DNS-ID, SRV-ID, URI-ID, or any
+	 * application-specific identifier types supported by the client.")
+	 *
+	 * NOTE: Prior versions of libpq did not consider iPAddress entries at all,
+	 * so this new behavior might break a certificate that has different IP
+	 * addresses in the Subject CN and the SANs.
+	 */
+	if (is_ip_address(host))
+		host_type = GEN_IPADD;
+	else
+		host_type = GEN_DNS;
 
 	/*
 	 * First, get the Subject Alternative Names (SANs) from the certificate,
@@ -537,38 +616,62 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 		for (i = 0; i < san_len; i++)
 		{
 			const GENERAL_NAME *name = sk_GENERAL_NAME_value(peer_san, i);
+			char	   *alt_name = NULL;
 
-			if (name->type == GEN_DNS)
+			if (name->type == host_type)
 			{
-				char	   *alt_name;
+				/*
+				 * This SAN is of the same type (IP or DNS) as our host name, so
+				 * don't allow a fallback check of the CN.
+				 */
+				check_cn = false;
+			}
 
+			if (name->type == GEN_DNS)
+			{
 				(*names_examined)++;
 				rc = openssl_verify_peer_name_matches_certificate_name(conn,
 																	   name->d.dNSName,
 																	   &alt_name);
+			}
+			else if (name->type == GEN_IPADD)
+			{
+				(*names_examined)++;
+				rc = openssl_verify_peer_name_matches_certificate_ip(conn,
+																	 name->d.iPAddress,
+																	 &alt_name);
+			}
 
-				if (alt_name)
-				{
-					if (!*first_name)
-						*first_name = alt_name;
-					else
-						free(alt_name);
-				}
+			if (alt_name)
+			{
+				if (!*first_name)
+					*first_name = alt_name;
+				else
+					free(alt_name);
 			}
+
 			if (rc != 0)
+			{
+				/*
+				 * Either we hit an error or a match, and either way we should
+				 * not fall back to the CN.
+				 */
+				check_cn = false;
 				break;
+			}
 		}
 		sk_GENERAL_NAME_pop_free(peer_san, GENERAL_NAME_free);
 	}
 
 	/*
-	 * If there is no subjectAltName extension of type dNSName, check the
+	 * If there is no subjectAltName extension of the matching type, check the
 	 * Common Name.
 	 *
 	 * (Per RFC 2818 and RFC 6125, if the subjectAltName extension of type
-	 * dNSName is present, the CN must be ignored.)
+	 * dNSName is present, the CN must be ignored. We break this rule if host is
+	 * an IP address; see the comment above.)
 	 */
-	if (*names_examined == 0)
+	if (check_cn)
 	{
 		X509_NAME  *subject_name;
 
@@ -581,10 +684,20 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 												  NID_commonName, -1);
 			if (cn_index >= 0)
 			{
+				char   *common_name = NULL;
+
 				(*names_examined)++;
 				rc = openssl_verify_peer_name_matches_certificate_name(conn,
 																	   X509_NAME_ENTRY_get_data(X509_NAME_get_entry(subject_name, cn_index)),
-																	   first_name);
+																	   &common_name);
+
+				if (common_name)
+				{
+					if (!*first_name)
+						*first_name = common_name;
+					else
+						free(common_name);
+				}
 			}
 		}
 	}
diff --git a/src/port/inet_net_ntop.c b/src/port/inet_net_ntop.c
index b8ad69c390..af28056132 100644
--- a/src/port/inet_net_ntop.c
+++ b/src/port/inet_net_ntop.c
@@ -31,17 +31,7 @@ static const char rcsid[] = "Id: inet_net_ntop.c,v 1.1.2.2 2004/03/09 09:17:27 m
 #include <netinet/in.h>
 #include <arpa/inet.h>
 
-#ifndef FRONTEND
-#include "utils/inet.h"
-#else
-/*
- * In a frontend build, we can't include inet.h, but we still need to have
- * sensible definitions of these two constants.  Note that pg_inet_net_ntop()
- * assumes that PGSQL_AF_INET is equal to AF_INET.
- */
-#define PGSQL_AF_INET	(AF_INET + 0)
-#define PGSQL_AF_INET6	(AF_INET + 1)
-#endif
+#include "common/inet-common.h"
 
 
 #define NS_IN6ADDRSZ 16
diff --git a/src/port/inet_net_pton.c b/src/port/inet_net_pton.c
index bae50ba67e..5bae811c6f 100644
--- a/src/port/inet_net_pton.c
+++ b/src/port/inet_net_pton.c
@@ -33,18 +33,10 @@ static const char rcsid[] = "Id: inet_net_pton.c,v 1.4.2.3 2004/03/17 00:40:11 m
 #include <assert.h>
 #include <ctype.h>
 
+#include "common/inet-common.h"
 #ifndef FRONTEND
 #include "utils/builtins.h" /* pgrminclude ignore */	/* needed on some
 														 * platforms */
-#include "utils/inet.h"
-#else
-/*
- * In a frontend build, we can't include inet.h, but we still need to have
- * sensible definitions of these two constants.  Note that pg_inet_net_ntop()
- * assumes that PGSQL_AF_INET is equal to AF_INET.
- */
-#define PGSQL_AF_INET	(AF_INET + 0)
-#define PGSQL_AF_INET6	(AF_INET + 1)
 #endif
 
 
diff --git a/src/test/ssl/conf/server-cn-and-ip-alt-names.config b/src/test/ssl/conf/server-cn-and-ip-alt-names.config
new file mode 100644
index 0000000000..a6fa09bad3
--- /dev/null
+++ b/src/test/ssl/conf/server-cn-and-ip-alt-names.config
@@ -0,0 +1,24 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+# This certificate contains a CN and SANs for both IPv4 and IPv6.
+
+
+[ req ]
+distinguished_name     = req_distinguished_name
+req_extensions         = v3_req
+prompt                 = no
+
+[ req_distinguished_name ]
+# Note: According to RFC 2818 and 6125, the CN is ignored, when DNS names are
+# present in the SANs. But they are silent on whether the CN is checked when IP
+# addresses are present.
+CN = common-name.pg-ssltest.test
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
+subjectAltName = @alt_names
+
+[ alt_names ]
+IP.1 = 192.0.2.1
+IP.2 = 2001:DB8::1
diff --git a/src/test/ssl/conf/server-ip-alt-names.config b/src/test/ssl/conf/server-ip-alt-names.config
new file mode 100644
index 0000000000..c22f22951a
--- /dev/null
+++ b/src/test/ssl/conf/server-ip-alt-names.config
@@ -0,0 +1,19 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+# This certificate has a two IP-address SANs, and no CN.
+
+[ req ]
+distinguished_name     = req_distinguished_name
+req_extensions         = v3_req
+prompt                 = no
+
+[ req_distinguished_name ]
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
+subjectAltName = @alt_names
+
+[ alt_names ]
+IP.1 = 192.0.2.1
+IP.2 = 2001:DB8::1
diff --git a/src/test/ssl/conf/server-ip-cn-and-alt-names.config b/src/test/ssl/conf/server-ip-cn-and-alt-names.config
new file mode 100644
index 0000000000..a4087f0a18
--- /dev/null
+++ b/src/test/ssl/conf/server-ip-cn-and-alt-names.config
@@ -0,0 +1,21 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+# This certificate contains both a CN and SANs in IP address format.
+
+
+[ req ]
+distinguished_name     = req_distinguished_name
+req_extensions         = v3_req
+prompt                 = no
+
+[ req_distinguished_name ]
+CN = 192.0.2.1
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
+subjectAltName = @alt_names
+
+[ alt_names ]
+IP.1 = 192.0.2.2
+IP.2 = 2001:DB8::1
diff --git a/src/test/ssl/conf/server-ip-cn-and-dns-alt-names.config b/src/test/ssl/conf/server-ip-cn-and-dns-alt-names.config
new file mode 100644
index 0000000000..7121803b49
--- /dev/null
+++ b/src/test/ssl/conf/server-ip-cn-and-dns-alt-names.config
@@ -0,0 +1,21 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+# This certificate contains both a CN and SANs in IP address format.
+
+
+[ req ]
+distinguished_name     = req_distinguished_name
+req_extensions         = v3_req
+prompt                 = no
+
+[ req_distinguished_name ]
+CN = 192.0.2.1
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
+subjectAltName = @alt_names
+
+[ alt_names ]
+DNS.1 = dns1.alt-name.pg-ssltest.test
+DNS.2 = dns2.alt-name.pg-ssltest.test
diff --git a/src/test/ssl/conf/server-ip-cn-only.config b/src/test/ssl/conf/server-ip-cn-only.config
new file mode 100644
index 0000000000..585d8bdae8
--- /dev/null
+++ b/src/test/ssl/conf/server-ip-cn-only.config
@@ -0,0 +1,12 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+
+[ req ]
+distinguished_name     = req_distinguished_name
+prompt                 = no
+
+[ req_distinguished_name ]
+CN = 192.0.2.1
+OU = PostgreSQL test suite
+
+# No Subject Alternative Names
diff --git a/src/test/ssl/conf/server-ip-in-dnsname.config b/src/test/ssl/conf/server-ip-in-dnsname.config
new file mode 100644
index 0000000000..b15649aef7
--- /dev/null
+++ b/src/test/ssl/conf/server-ip-in-dnsname.config
@@ -0,0 +1,18 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+
+[ req ]
+distinguished_name     = req_distinguished_name
+req_extensions         = v3_req
+prompt                 = no
+
+[ req_distinguished_name ]
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
+subjectAltName = @alt_names
+
+# Normally IP addresses should not go into a dNSName.
+[ alt_names ]
+DNS.1 = 192.0.2.1
diff --git a/src/test/ssl/ssl/server-cn-and-ip-alt-names.crt b/src/test/ssl/ssl/server-cn-and-ip-alt-names.crt
new file mode 100644
index 0000000000..4e58c85ccb
--- /dev/null
+++ b/src/test/ssl/ssl/server-cn-and-ip-alt-names.crt
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDLzCCAhegAwIBAgIIICERKRE1UQAwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UE
+Aww3VGVzdCBDQSBmb3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHNl
+cnZlciBjZXJ0czAeFw0yMTExMjkxOTM1NTFaFw00OTA0MTYxOTM1NTFaMEYxHjAc
+BgNVBAsMFVBvc3RncmVTUUwgdGVzdCBzdWl0ZTEkMCIGA1UEAwwbY29tbW9uLW5h
+bWUucGctc3NsdGVzdC50ZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
+AQEA6+8IYKAFnZ7V+fDo1cyMpbGBLzCfJOQ/1o2jOGP4+GjpsZgv6S6UT2MheC8M
+iiEFrYwdsSIZyYc3jEZrluy/UuR0bCGtqU92BCqa0iBLhvHOgjR588u253eLxQtQ
+8iJn11QPrKMk35nMkmY8GfHt4sGFbvBL6+GpipHq7a6cde3Z+v4kCB5dKMYDUDtm
+3mJmviuGNAu5wOqItk2Yi5dwJs1054007KNH0Il43urxiOfnkLS0cG5kehboPf86
+vxBt3iHByrU/9/DY5IvQCfSXVNa6rb5w5/pGja9aCei6Mv1jQY/V8SMQTga+MOsA
+0WB9akxMi2NxwS2+BQ4k/McPlwIDAQABoyUwIzAhBgNVHREEGjAYhwTAAAIBhxAg
+AQ24AAAAAAAAAAAAAAABMA0GCSqGSIb3DQEBCwUAA4IBAQAQLo2RzC07dG9p+J3A
+W6C0p3Y+Os/YE2D9wfp4TIDTZxcRUQZ0S6ahF1N6sp8l9KHBJHPU1cUpRAU1oD+Y
+SqmnP/VJRRDTTj9Ytdc/Vuo2jeLpSYhVKrCqtjqIrCwYJFoYRmMoxTtJGlwA0hSd
+kwo3XYrALPUQWUErTYPvNfDNIuUwqUXNfS0CXuIOVN3LJ+shegg6Pwbh9B5T9NHx
+kH+HswajhdpdnZIgh0FYTlTCPILDrB49aOWwqLa54AUA6WXa35hPsP8SoqL9Eucq
+ifPhBYyadsjOb+70N8GbbAsDPN1jCX9L8RuNcEkxSCKCYx91cWXh7K5KMPuGlzB7
+j8xB
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-cn-and-ip-alt-names.key b/src/test/ssl/ssl/server-cn-and-ip-alt-names.key
new file mode 100644
index 0000000000..837eef996d
--- /dev/null
+++ b/src/test/ssl/ssl/server-cn-and-ip-alt-names.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA6+8IYKAFnZ7V+fDo1cyMpbGBLzCfJOQ/1o2jOGP4+GjpsZgv
+6S6UT2MheC8MiiEFrYwdsSIZyYc3jEZrluy/UuR0bCGtqU92BCqa0iBLhvHOgjR5
+88u253eLxQtQ8iJn11QPrKMk35nMkmY8GfHt4sGFbvBL6+GpipHq7a6cde3Z+v4k
+CB5dKMYDUDtm3mJmviuGNAu5wOqItk2Yi5dwJs1054007KNH0Il43urxiOfnkLS0
+cG5kehboPf86vxBt3iHByrU/9/DY5IvQCfSXVNa6rb5w5/pGja9aCei6Mv1jQY/V
+8SMQTga+MOsA0WB9akxMi2NxwS2+BQ4k/McPlwIDAQABAoIBAQCuNFKVNdKvrUYF
+RLJGmsAG3+eo9lern7TbML2ht39vu9dBwEMwA6qSa3mdCfBSVUuh9uE9lxY/TU3g
+j2aFi81A4VptNPjLGNblAKhMGnhp7UUzspeRQYuNoSFcnpxoDKtrvK/OIq/pQeBh
+AIfECHRDh+yEG32Tb44FuPQkB1eTYl8xbMEImrhNUaSjJk7tTsmydHy0DjmqHVKX
+HUj0TREfDBDOBiHtY0XV6Pu3bnqDH/TKLTfUf3UdfTuay3Yai9aEcRPWp9GrMO7G
+axsKCifTz6177gyr6Fv8HLeMZMh9rMZRn3e0zfaF6vrH1QnZZOts5jpUa0KugSCd
+//uC0iNxAoGBAPXVc3b+o3hY5gcwwpaW6JtsarDrmNRxrizqIDG7NgpqwdFXgTi6
+6q0t2pjv81ATqij69IcPkNSissyR4OEKnu/OFJWzreg8yLi75WHKi0E/6msHpwRk
+d1yP0Zgd05ots/yOjDSp593RagaPVvHBxMECZ/Tm3B+Tq55Azudd/zvLAoGBAPWw
+xf0oUEJl6NdUZD6K7eFc6jf8yrpD85dldeko6LeN8x0XlKKWvUDJ2+3oizXoQvCm
+8by6KOYEIo4MrtXuy9MmtPWfNvRBr+hsUHchIj7IgFa9bKXyK2FnJqu/8CbEymli
+eZu7hoOhelurhnFy1zSqwNO4GC+kw60Y/BO3Z1nlAoGAVOyYJtNwxXJwhKtjjYI0
+ePzLHrNE6J8c/Ick+AkkchTPP/JqwZ5Q0+KzUYITG+avMdkAAGhwMATEn8cFWLjC
+jzUyB0U7Hq9g5/CBHXdLBA+Ae9j46ZuLYH6OeW5UWz7OnsDfzpGjeA2QAxQhhQLb
+ZZHfN8tI39+zucfJskPWmGECgYEAg9guF1Fn6InJrqwR82IYj6SN6CeXHufSM392
+C/4xDDd3rDf4QlwECV2J0RzGf9I5Ae2EshNwWScE6Be0RweTh6cw2tJq6h7J6D8f
+2x4Dw49TF7klMdRIJUf2f5pLpHJccLswqTqzz7V69PCSABVxmUi8m6EiEYconp5W
+v7nfE2UCgYALrEqzncuSIX3q6TVAjnzT7gO4h8h2TUekIWdHQFldFx8R7Kncggnd
+48gQqhewchNR83UCcd7pPsCcTqu6UR1QRdq/DV5P6J3xdZ2iS/2gCM6hvWIvKZEv
+/ClnkyFCOW7zX6RKIXtRYZTV1kz3TajApi34RTIeIMTieaCarnBJbA==
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-ip-alt-names.crt b/src/test/ssl/ssl/server-ip-alt-names.crt
new file mode 100644
index 0000000000..8a1bc620bb
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-alt-names.crt
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDCTCCAfGgAwIBAgIIICERKREEUAAwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UE
+Aww3VGVzdCBDQSBmb3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHNl
+cnZlciBjZXJ0czAeFw0yMTExMjkxOTA0NTBaFw00OTA0MTYxOTA0NTBaMCAxHjAc
+BgNVBAsMFVBvc3RncmVTUUwgdGVzdCBzdWl0ZTCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBAOM8yB6aVWb17ujr3ayU62mxHQoqn4CvG9yXlJvGOGv/ursW
+Vs0UYJdc96LsNZN1szdm9ayNzCIw3eja+ULsjxCi6+3LM4pO76IORL/XFamlTPYb
+BZ4pHdZVB0nnZAAnWCZPyXdnjOKQ5+8unVXkfibkjj8UELBJ2snehsOa+CTkOBez
+zxYMqxAgbywLIYsW448brun7UXpWmqbGK+SsdGaIZ5Sb7Zezc5lt6CrLemTZTHHK
+7l4WZFCCEi4t3sgO8o1vDELD/IE5G8lyXvIdgJg6t8ssper7iCw6S8x+okhjiSjT
+vDLU2g4AanqZRZB49aPwTo0QUcJA2BCJxL9xLy8CAwEAAaMlMCMwIQYDVR0RBBow
+GIcEwAACAYcQIAENuAAAAAAAAAAAAAAAATANBgkqhkiG9w0BAQsFAAOCAQEAwZJ+
+8KpABTlMEgKnHIYb35ItGhtFiTLQta9RkXx7vaeDwpOdPP/IvuvpjpQZkobRgBsk
+bNM0KuJpd2mSTphQAt6eKQIdcPrkzvc/Yh9OK3YNLUAbu/ZhBUnBvFnUL4wn2f1U
+mfO+m8P/LxybwqKx7r1mbaB+tP3RTxxLcIMvm9ECPQEoBntfEL325Wdoj+WuQH5Y
+IvcM6FaCTkQsNIPbaBD5l5MhMLHRULZujbDjXqGSvRMQfns6np/biMjNdQA8NZ5z
+STeUFvkQbCxoA0YYLgoSHL5KhZjXrg2g+T+2TUyCTR/91xf9OoOjBZdixR0S0DzJ
+B1+5vnUjZaCfnSEA7A==
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-ip-alt-names.key b/src/test/ssl/ssl/server-ip-alt-names.key
new file mode 100644
index 0000000000..b210b3a991
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-alt-names.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEA4zzIHppVZvXu6OvdrJTrabEdCiqfgK8b3JeUm8Y4a/+6uxZW
+zRRgl1z3ouw1k3WzN2b1rI3MIjDd6Nr5QuyPEKLr7cszik7vog5Ev9cVqaVM9hsF
+nikd1lUHSedkACdYJk/Jd2eM4pDn7y6dVeR+JuSOPxQQsEnayd6Gw5r4JOQ4F7PP
+FgyrECBvLAshixbjjxuu6ftRelaapsYr5Kx0ZohnlJvtl7NzmW3oKst6ZNlMccru
+XhZkUIISLi3eyA7yjW8MQsP8gTkbyXJe8h2AmDq3yyyl6vuILDpLzH6iSGOJKNO8
+MtTaDgBqeplFkHj1o/BOjRBRwkDYEInEv3EvLwIDAQABAoIBACp3uY6+mSdc3wF4
+0zzlt/lQuHSl8plCIJrhWUyjhvfoGyXLzv0Uydh/72frbTfZz1yTSWauOXBKYa6a
+/eqb+0DIsf8G8uLuTaqjsAWKVOoXkoKMGkistn7P9UTCkdXVhIvkbWp7V8EgA7iX
+pZ/fzBPIsyzmuxe3NcR0ags0cxuxkNuu+YXDv1oTedmT2wS3CZq1d/T1Y/EOVIf8
+Iznd2aOverlsnt6iiQ3ZWdG/W5F8FhnrR/rrBdYsdCv6TH/KUYexnDOUYpayjDbu
+oAKnifPp6UqiOM4SuBL83OAz19jptp5vpF370BEVRs3eK0q+zo/mETjv9HsXdolZ
+lfoXA0ECgYEA/7nb2azbq/2muvXCh1ZxCEbn3mt8KXoJP/xkx/v9eEc/cc5Q9e0V
+2oGfjC2hSE+bjOWMwiUMD6uU+iRjhz5A3IvUxnoSdoL7H9p0hTqLMyP7dTDkoVF5
+aEuLMaiI5YEnfAFu9L5h8ZKieoQTBoscT06wnGjh9pBV9bthfTKA7ksCgYEA43sb
+55m9WL4kWCPwOAp3vdEAFyxzmZHlO26sEQOU/m5aN01pumYybBruziEXMI96yfTj
+VmXKReeYb6XUiCcs3fLSipD/+8/8CsjO4uMORtxWumXe8AbKZfysGFzL7wJlByGT
+38AGQwIG/XD8cKnaiEMX4E/3Owbcoxwixo3WZC0CgYEAovaqJ9mEU+Jc8h/TS7PG
+bGPjN1Z/1V6zrlcFUnw/Vvrwb3HvHglsN8cLCaW6df5lPjC6tq4tNX8+fPnbg0Ak
+zWc+vQzl3ygxKGdqgcyBEKIJiPETgcoN+GzL02V3d+oKY3f2YXlBqVSsvi6UgUL9
+U3zuB36/IQVyAhrbUZFxoGkCgYEAnaFAO+Nvrp/LhXwZyGuQf+rkmipGTIMpil5t
+QzjtNMV5JFszSWPpyrl7A0Ew1YiG+I0GP2c3m+sY2TzbIiGrWH0b4cMKbw63Qy3V
+FqlpyjaCrpVKv56k/7jv883RzuQk56Uf1+szK5mrCFITy2oXsVZ0pA4lbjSaDTjA
+7D968V0CgYEA+qKqXKL98+c5CMPnpf+0B1x2zgyUym1ouPfon2x5fhK84T53zDMA
+zfdUJ/SOZw6/c9vRF7RL8h+ZfFdIyoAXv4Tt6mIiZe7P+AUVg6XgJ0ce2MUSeWjI
+W8D4WdSi0jyqr99TuVBWhbTZJviMB3pHqKaHQ07hnd/lPtvzsiH12qk=
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-ip-cn-and-alt-names.crt b/src/test/ssl/ssl/server-ip-cn-and-alt-names.crt
new file mode 100644
index 0000000000..2be02feb03
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-and-alt-names.crt
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDHTCCAgWgAwIBAgIIICIBBBQ2MQAwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UE
+Aww3VGVzdCBDQSBmb3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHNl
+cnZlciBjZXJ0czAeFw0yMjAxMDQyMjM2MzFaFw00OTA1MjIyMjM2MzFaMDQxHjAc
+BgNVBAsMFVBvc3RncmVTUUwgdGVzdCBzdWl0ZTESMBAGA1UEAwwJMTkyLjAuMi4x
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwmqTdQJfs2Ti9tPitYp2
+27I0HvL/kNSgA6egFr0foRo0BorwJNIzdbV0+EnsfiBNTWL5It26gqO7UP3ms8t2
+vHD5gkXfT+f6ts0lVJEcIOkUD/8ws4Ic9Y4uPqb4gN+pUKqcxtmLW1TYk84MBK59
+Xz4yPPS6N+G/DMMeFHTNkM9EQwn/+DC3fDsWdGYM2GRWDTJGg1A5tSUcF+seu7i1
+Vg7XajBfsvgAUAsrAxV+X/sLZh94HY+paD6wfaI99mY2OXVc/XW/z1r9WQznor65
+ZkonNCaPfavqPG5vqnab9AyQcqPqmX8hf/xrniASBAkqNCctbASrFCIYvCJfGfmX
+EQIDAQABoyUwIzAhBgNVHREEGjAYhwTAAAIChxAgAQ24AAAAAAAAAAAAAAABMA0G
+CSqGSIb3DQEBCwUAA4IBAQBf7kmYfRYfnWk1OUfY3N1kaNg9piBBlFr9g+OQn9KU
+zirkN7s0ZQbCGxV1uJQBKS58NyE414Vorau77379emgYDcCBpDIYpkLiNujVrIOr
+ggRFKsFRgxu4/mw0BSgCcV8RPe9SWHZ90Mos7TMCnW/PdxOCD1wD0YMkcs0rwB3l
+0Kzc7jDnfOEvmgw/Ysm7v67ps+05Uq5VskQ6WrpSAw6kPD/QMuuBAX8ATPczIaox
+zAMyncq1IiSIwG93f3EoQQThdQ70C6G9vLcu9TtL6JAsEMFEzR99gt1Wsqvmgl9W
+kStzj1yjIWeo5gIsa4Jgcke1lZviWyrTxHDfyunYE5i5
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-ip-cn-and-alt-names.key b/src/test/ssl/ssl/server-ip-cn-and-alt-names.key
new file mode 100644
index 0000000000..54fe80fc68
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-and-alt-names.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAwmqTdQJfs2Ti9tPitYp227I0HvL/kNSgA6egFr0foRo0Borw
+JNIzdbV0+EnsfiBNTWL5It26gqO7UP3ms8t2vHD5gkXfT+f6ts0lVJEcIOkUD/8w
+s4Ic9Y4uPqb4gN+pUKqcxtmLW1TYk84MBK59Xz4yPPS6N+G/DMMeFHTNkM9EQwn/
++DC3fDsWdGYM2GRWDTJGg1A5tSUcF+seu7i1Vg7XajBfsvgAUAsrAxV+X/sLZh94
+HY+paD6wfaI99mY2OXVc/XW/z1r9WQznor65ZkonNCaPfavqPG5vqnab9AyQcqPq
+mX8hf/xrniASBAkqNCctbASrFCIYvCJfGfmXEQIDAQABAoIBAB6GgVST5NbT9lbu
++d+rN/JSzqA1Yy8oU19/iEFJvJec96I3WnFNl8rZjN4XLUy4YarO6XMyAUDV2Gll
+FD4Sqjf4PRTZR7DSKaleGIhoqFP6hK3mUY091rIves9XhBkoBPunbipCqgDTF5ZN
+edGaXBECQP0VJ8/yX/7u++AWXthnjDis9X0taZfFg/PYbV7SCJ1Hg1O/wEsgXlnC
+7mbL6wkCW0f6700B0x1kKbZqJY95xRqp6Ipq2lIQbJDdGywoj0WzKqNltf9cer+r
+cXl8WjeiMvvvpl4uGhckAbzUifUzxN6A3f1fu/XKtOmabMi9t7J4MRfgOgedgtQB
+0jaZGSkCgYEA+lBLnNY6M48HX2mdtr86+n41gh69v8Z7oNikJFDZkodrvI8uqE0i
+0XwnYPFddt8NbmuUhhuzI2M8RKhGLgdlbKpkSSVafnMfcxRmX2EAtWQgdvX1Iult
+752LWdBgSuw2vlzvy3T/GYnjMrXSCGput4amqojMEbvUGvIdSUMdHGMCgYEAxtU1
+WixKPL6aEnYy1f4bybzcNgGtl8PBRz9xw+P46g+ijOPoaG9O73Tr7An11AO003Ot
+DHhMW+b8yHLyxoKwS2sU2cN/lKB8xNQYZc1D61RNJlzgnHMXnA0lcH0I3M35fqKr
+/71pD1ZP40SSJS+od/KEjW80XzuOdyiXg8q81vsCgYEAnUPLbbsuj+whzrFVlFZr
+IKwgxCK6Rn3WeIUEA4kEWUpZxvsSbk0gPgtJ1l9uwFt9Xc2bX/KRRv93Aw/SH+Mn
+tvEK1uXwCBgePzgm5W/VeSFyQCthm1CbcHtD7Oa9SPVFo65SPjrAd3QpWVfgoMb1
+zrp7hhMyW0XuCgvpmHjhFk8CgYEAxq/thXM2p+bLLWGhwQcRG5G299zLbBl4PUsf
+0uEvLi17gJCKADoiRdSvoAn/9eHSQ26XYRuhKkDzHxcGlOmpY2PYzRa3mXyZ0VIk
+Iy5wDWwLQCeVZ6D22cClRfgb8BF/nFTPzVmn72SPpgoyhChQj7PvUynpyrRH07jj
+VxYziBsCgYAFr37Xbl0VnXVK+XU+vMwUZjcF4jpoCr7SFZqgRbW2GbYSUoMuPXns
+RnJh+Fvi1NUei+E5s1H4P1pVq4p0jFxP4GvH/qvNjnIn/Er3bbqvpox6dWUJXprq
+qTQSDIeoDC/V8cyRoIfqPvTVqY8Rgew6GEkv0bAImdxhoSng7vIseg==
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.crt b/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.crt
new file mode 100644
index 0000000000..23c06da01c
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.crt
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDQzCCAiugAwIBAgIIICIBBBQ2MQEwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UE
+Aww3VGVzdCBDQSBmb3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHNl
+cnZlciBjZXJ0czAeFw0yMjAxMDQyMjM2MzFaFw00OTA1MjIyMjM2MzFaMDQxHjAc
+BgNVBAsMFVBvc3RncmVTUUwgdGVzdCBzdWl0ZTESMBAGA1UEAwwJMTkyLjAuMi4x
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8xddbo/x2TOSIa/br8BN
+o/URdTr9+l2R5YojiZKDuLxiQVkgC30PJ2/CNFKIh2nHhRrzknI6sETVtrxZ+9V2
+qRc1yShVu462u0DHPRMIZnZIOZg3hlNB0cRWbOglUKlttIARNEQUcTUyPOtyo4/v
++u0Ej5NTNcHFbFT01vdD9MjQiCO3jKdAwPIb14jTg4C71EpZ+LuelDo4DzF2/XgG
+WqUTrgD/XnBU/60PU9Iy3G0nVpx21q6ppn9G7a9R+i8FjBcwW1T+cfsBDWhAv+bi
+RmSAkENf8L8TwOlDQUwROkfz3Hz36vuJjdkreQJsiqL0HnrnH5T5G9UzJO86FvZQ
+5wIDAQABo0swSTBHBgNVHREEQDA+gh1kbnMxLmFsdC1uYW1lLnBnLXNzbHRlc3Qu
+dGVzdIIdZG5zMi5hbHQtbmFtZS5wZy1zc2x0ZXN0LnRlc3QwDQYJKoZIhvcNAQEL
+BQADggEBAF+mfaw6iBPzpCgqq830pHRa3Yzm1aezt8SkeRohUYHNv/yCnDSRaqtj
+xbENih3lJMSTBL3g0wtTOHfH8ViC/h+lvYELHzXKic7gkjV7H5XETKGr0ZsjBBT2
+4cZQKbD9e0x0HrENXMYgGpBf747qL6uTOVJdG0s15hwpLq47bY5WUjXathejbpxW
+prmF8F+xaC52N9P/1VnqguQB909F4x1pyOK7D7tjFu+Y8Je7PHKbb6WY5K6xAv6t
+R17CY0749/FotlphquElUR2bs5Zzv5YrjUHPTcbwKvcH5cdNi93/u6NJt2xNAoYf
+aZERhX5TA9DYk4gC8OY0yGaYCIj3Dd4=
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.key b/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.key
new file mode 100644
index 0000000000..0ace41e0a1
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEogIBAAKCAQEA8xddbo/x2TOSIa/br8BNo/URdTr9+l2R5YojiZKDuLxiQVkg
+C30PJ2/CNFKIh2nHhRrzknI6sETVtrxZ+9V2qRc1yShVu462u0DHPRMIZnZIOZg3
+hlNB0cRWbOglUKlttIARNEQUcTUyPOtyo4/v+u0Ej5NTNcHFbFT01vdD9MjQiCO3
+jKdAwPIb14jTg4C71EpZ+LuelDo4DzF2/XgGWqUTrgD/XnBU/60PU9Iy3G0nVpx2
+1q6ppn9G7a9R+i8FjBcwW1T+cfsBDWhAv+biRmSAkENf8L8TwOlDQUwROkfz3Hz3
+6vuJjdkreQJsiqL0HnrnH5T5G9UzJO86FvZQ5wIDAQABAoIBAGv0BFoFMrHyZQLw
+xe7Wx6P4QTh+aiu1QgVdw0pk9nojrr62hbSUZRZuWyBBRsBcCW7i+Sgf8lA1QXNV
+UeC0e228EPa0It6YEi42JkTJHwHhpVFud7n/X0t4lajnryqTE1UFSp6bXTipFxZW
+uSJJ2ZjliRD5rApDcxkY4WJVjKg3aEt7P/DiM8iKGfyE6stq72VjEbJjdViMEcOP
+BNf0TiREZz5Mp7jAVWhpen0ebbLOBVWV4/ONNcL+yqR4mCEDUSFGewrTVX4zHL0A
+hYk198C5F8sFvEDnFkPco9sXMVanmLoI8sbhP4IIz9g4+GU6kFuj7fUKp11Azqv+
+3WQDKYECgYEA/XG4mmG/g8FG44y42mfZpUXWi1pwU4CQIrhkoU5j7EPQrvRboOOE
+Rv95jSwyZu4vCqjyI5FN1jCGTdhmt++R1e//zH6Hqa9Smo+jw7DtAFrCYd1JnCf1
+ToOwsYPHv4P7A8q8kc5vCNIv+AQSlP/wqdVNo3grdf7cGXkMtEY4F9UCgYEA9Yrq
+zWdnNGPATuSBqL6TSjQ37oR+dBD6WnGsiDenQkOzyDPFZ3CT1DjJghjEtxc8EfNf
+Oo8dMMR2q+5FZQo7WuqONEgyzKePiNR8RK2gOYpgdjN9bih1sAhHR10D26cpwlDJ
+bx7D5ZzENLbdZmfEiWwKswnaIhN4yMalgE0mP8sCgYAhzJy12ftUct4lUosEdX0N
+EXc/NlxshmSyfKzO5kllJNYbvvLJTg5B+agYL6C5IWKcpVNFcwdSXT5L+2QXe5eT
+VGJkvysQchUuD6HjYyD4PyJVMtGyRZHtWpqh0dU9sTg0lUD4oPMl1gIXrVNdE5Tg
+0VV9S3VgUxC/ROlw0TyB0QKBgGsVE0NS9hJF8mc1hko2GnwA++d8Rr2NbfElo+2f
+/8SJTA1ibpOm6AFkZpTjAl8qtdrKPVyHb16GP47Jkd/3r1z979hjKCxSYul0aWF2
+KusNKvZBjFEPOgv0AEniCb2wUCjbHI3mZ95qGLM4kKOJW4/m21+rS0MTJNjCsQic
+HLMzAoGAeCsY09d3m8xGeU+DuTPC6GH7Sgy/NBYqS5VaVNjb2jnuZlW2SSW2oiID
+4tXTi4ruKmHC898BfyFxhSMqub+tg3pVqIYADC71rnJLrVyc1SzoWzL7yMT3qFj7
+C7ZYZYmfG9agcZb5NkqKPTfCxkBhWbdgTTgBKVO/xQst8EUgko8=
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-ip-cn-only.crt b/src/test/ssl/ssl/server-ip-cn-only.crt
new file mode 100644
index 0000000000..9bf015cf18
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-only.crt
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC8TCCAdkCCCAhESkRN1IAMA0GCSqGSIb3DQEBCwUAMEIxQDA+BgNVBAMMN1Rl
+c3QgQ0EgZm9yIFBvc3RncmVTUUwgU1NMIHJlZ3Jlc3Npb24gdGVzdCBzZXJ2ZXIg
+Y2VydHMwHhcNMjExMTI5MTkzNzUyWhcNNDkwNDE2MTkzNzUyWjA0MR4wHAYDVQQL
+DBVQb3N0Z3JlU1FMIHRlc3Qgc3VpdGUxEjAQBgNVBAMMCTE5Mi4wLjIuMTCCASIw
+DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANWs1uUL71nHYF9Zj6p+M3MpYDvx
+32iCjVdtH5a2qpSWHXTg0rR8dLX0y92cvOYvMXHRajZT1avpHr8dooPYSVaXpGMK
+NvF/Qi+WFYovRbP2vmd1yv1cgW/FggbwJFWVobizIz4seyA4d0B2j9fqoi2OFBNP
+huW664SjF0u3p21tDy+43i2LNUMAKf6dnRR5Vqenath87LEU41tSLudu6NXgbFMk
+jvfNkl4d0w7YCzeXmklmSI+uaX3PlJJ4NzQO2j8w5BvnKVhNVD0KjgrXZ6nB/8F7
+Pg3XY+d7rJlwRgXemU6resWQDJ7+UaC9u7I4EIP+9lzCR/nNBqUktpHRmHUCAwEA
+ATANBgkqhkiG9w0BAQsFAAOCAQEAos1JncV8Yf4UaKl6h1GdYtcVtzFyJvBEnhRD
+07ldL+TYnfZiX8wK2ssBtM3cg/C78y5bzdUa5XGS83ZKQJFFdhE7PSnrvyNqyIqY
+ZgNBxto3gyvir+EjO1u9BAB0NP3r3gYoHRDZS1xOPPzt4WgjuUgTLM9k82GsqAbO
+UrOTOdRnkIqC5xLpa05EnRyJPRsR1w1PRJC2XXKnHIuFjMb4v7UuPwyCcX1P5ioc
+rQszQcORy/L+k0ezCkyweORg68htjYbBHuwOuiGfok6yKKDMzrTvD3lIslls6eX7
+4sI3XWqzkPmG9Vsxm9Vu9/Ma+PRO76VyCoIwBd+Ufg5vNXhMmw==
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-ip-cn-only.key b/src/test/ssl/ssl/server-ip-cn-only.key
new file mode 100644
index 0000000000..1966530e72
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-only.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEA1azW5QvvWcdgX1mPqn4zcylgO/HfaIKNV20flraqlJYddODS
+tHx0tfTL3Zy85i8xcdFqNlPVq+kevx2ig9hJVpekYwo28X9CL5YVii9Fs/a+Z3XK
+/VyBb8WCBvAkVZWhuLMjPix7IDh3QHaP1+qiLY4UE0+G5brrhKMXS7enbW0PL7je
+LYs1QwAp/p2dFHlWp6dq2HzssRTjW1Iu527o1eBsUySO982SXh3TDtgLN5eaSWZI
+j65pfc+Ukng3NA7aPzDkG+cpWE1UPQqOCtdnqcH/wXs+Dddj53usmXBGBd6ZTqt6
+xZAMnv5RoL27sjgQg/72XMJH+c0GpSS2kdGYdQIDAQABAoIBAQDNXviU4WnF8rmQ
+K7bH+dBdqbETLKC8BG7xTrMD2sINWlMpmUUrsEtE7+paMGHnJAj0CoF5gg5m0wN4
+UXV4H5QtpEad4p14dAYbUreVP2ZRWKEdM7xM1HKcCUu2e22QzObJbXQ8N+iHyX3k
++Y+7yYrjGiH1hYR0nbnsnAyx++zyYBSQeqzpdQwf/BLY5xZmyYWNfqbckiMpEqMs
+EmZmGXnCjIipzEC0LQHoSW9PNa92Z9bvuxOKYl8iHYDDXjvMRFoZBSiMXpzHQocb
+QlQ5F4ayfW2OrOhpNbY7niYM9GN3Bk9TgMP+0BkJE6uuktLYW35LY1M78CCPWcWb
+npJNK3QBAoGBAOxkGrhAHAysSmtirIyMdvySb76wb/Ukfi+AULKz20FI5j4/GXm9
+qCb2GeT+FFSUHeSC8f0EFnosRYkdBGruqeZioI+5rUkboYFJPspAHAuvg9kgtfF+
+kvphD4O4P/foYsEZRx66FHozDbhrrR5UXc7KzqRIASc/D3FOx2UFJLb1AoGBAOdm
+WcaMvYygl9ZW+ThWAR1xG1X70AGKwrlrpF2hBkWYxSurxSMXnD0DUzC9Nb4EyCaM
+c2uSqEZOKdW+XfXtK2DnqXKfb3YCVEoGN4gVfyuW/vxii/+ZxLo3md/b3vrkZEVp
+pfkXy/HoZ71YN7bNpcDpOnhml6vvuCRCYFnI1WuBAoGAC0shB6pwbJ6Sk5zMN47C
+ZICufAK75o9OxAAyWsdC81SDQ3gKRImuDeZ2CD2nRP8qim9DFl5qoH2a+Nj9DArI
+7SvLFfK9958tURrpuAnmDRzehLIOXzI33WRjtFxKGhLtHOKTRkGHlur3fdcPF0La
+lHWV971E6NYXa8diuU3Mmj0CgYBYd+ka3/QYL83dRKNDxp3mg7fPx9ZewI5yFZVh
+to6PTTkU2Tclk4FIUl0b5TsGyw06r7fxCMENIBUegwmpXGOZSPifuhUDKSDQrE/O
+12knYTNbitG7hy6Pg3JxA77cbTVo1FuAQHjYo+IFohSq7zTP7FtObOrP8XaVZksw
+CHiQAQKBgBW4EiA9AAnZ1LOpifAvM7bs0NHg95qTwtAL52WKom2ga2H+lMhxeu6Y
+hUSytC/f9kALVcYloZhkLYpO07x1gXmy7f4parMjA4Ex+4vfu3kPd8GiNGZ+AUJD
+nnJ1OINY9ziXJZfju7FpVWpkiuPzWCh6y/o3gZ/veq5mIUxuDMVa
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-ip-in-dnsname.crt b/src/test/ssl/ssl/server-ip-in-dnsname.crt
new file mode 100644
index 0000000000..78ad8d99c8
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-in-dnsname.crt
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC/DCCAeSgAwIBAgIIICIDFRVYUgAwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UE
+Aww3VGVzdCBDQSBmb3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHNl
+cnZlciBjZXJ0czAeFw0yMjAzMTUyMjU4NTJaFw00OTA3MzEyMjU4NTJaMCAxHjAc
+BgNVBAsMFVBvc3RncmVTUUwgdGVzdCBzdWl0ZTCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBAMpn5bP1/OfBQR/yvOkOBzxArE1j1YShVa2pcj896+CVDEgV
+N5Hluz7KHU/JYzNZCAHb5WAHuvXxKeoj4Ti5be1KsqO0mN1p+RMN7VlCpCpb0AWT
+z4z+I8TUhSZnmgghHvfW4RfcZMCcHq1vevVTDxR/cAbDPYpgBCD5F/SZMRyMDw5B
+7ILLmft0eqA1nCqavyqBCGZvx1ol8N5BfVdrDXp/rN5997khBWQRZ8g84FZyFZXf
+pwp57eu0OGQDzZFXoEL2t4OVld67K5jcclWVxHY6FGcHjCvyqs48PCPOR84anZwj
+GsqVOS6250/DWKBQO4KyhkTVf0AW/ICGSMOKkAkCAwEAAaMYMBYwFAYDVR0RBA0w
+C4IJMTkyLjAuMi4xMA0GCSqGSIb3DQEBCwUAA4IBAQDIAAH0WJKEpbPN0QihN6SF
+UA5WL4ixsBACo9OIAGkSnKeOeVEG5vvgOna0hjQcOcgtI1oCDLhULcjCuwxiIW6y
+QntOazyo0sooJr0hEm2WfipvIpQs6W9E1OTcs624BAVfkAwr6WT2VwoIAPcQD2nR
+tIQhSUIR9J7Q5WbzuQw7pthQhBfW/UPWw7vajel0r1dflbe0Cgp5WGNfp1kYy+Qf
+XW/YjkstZEP1KFm+TF58uxrIDmYboS8EerUREGQixijbI0AfXjShxtiyS63rbdpo
+3C0BPj9Yx2VtWi4U0qoef/iLJxJBCLvE/97+duPdKx0AkkOWA9VuenkWLp797UM8
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-ip-in-dnsname.key b/src/test/ssl/ssl/server-ip-in-dnsname.key
new file mode 100644
index 0000000000..ba319b001e
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-in-dnsname.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAymfls/X858FBH/K86Q4HPECsTWPVhKFVralyPz3r4JUMSBU3
+keW7PsodT8ljM1kIAdvlYAe69fEp6iPhOLlt7Uqyo7SY3Wn5Ew3tWUKkKlvQBZPP
+jP4jxNSFJmeaCCEe99bhF9xkwJwerW969VMPFH9wBsM9imAEIPkX9JkxHIwPDkHs
+gsuZ+3R6oDWcKpq/KoEIZm/HWiXw3kF9V2sNen+s3n33uSEFZBFnyDzgVnIVld+n
+Cnnt67Q4ZAPNkVegQva3g5WV3rsrmNxyVZXEdjoUZweMK/Kqzjw8I85HzhqdnCMa
+ypU5LrbnT8NYoFA7grKGRNV/QBb8gIZIw4qQCQIDAQABAoIBAA2kPP4JCTeRddMy
+Z/sJIAG2liZNITnkKcMflXyfrsMfKIm/LFSf+CO+OYWEHDR8vqZpbKcxPi+PRnTq
+YCaTkM4aZ7nS1S6vEsNu/90xOaFFONr3YFivVDfS3vp8pwv/N3gaumcCSqQUoZis
+18urAmwuPp2mEQK/f+e9AhlRLdcvlqDyKm+zMrVixK77Hj5JiEkh3rfZ3onHHKGE
+B7T2XRRqnZ4FCN9qLH2pMGUknZ4MGC9SlCyoerXFodb4DhKWQhJDRLjb8qP96r/E
+FGSg5WUiAERU/OgODoqZNTeIwIDB/f9NK45dEY3Hw6BsSFfU2VChrlNoVlzFUx2k
+yaH5Y4ECgYEA8rht3crh3GTy0jBJjNqB2iul8fkG/uiaiSvERWT/+KZnmV1+JGAW
+h2/wvd5apagOJjqKY0bCHMei/qYF9r4yJnkIy4qNper3QUz7TMCjsWduCm8S834A
+Z+Vwi3RBGJiQQH9Dfexko5sDjo+w5g4RsH52INCeReInNdxHOv06jZECgYEA1XrR
+QNwZlxHt3H93YKmKDZXikqW12Cuq6RSwf5VVdeuzV+pUN+/JaSgEuYsBilW7Q5p2
+gPROi0l8/eUPsBJb+dh1BcGzSjI2Kkzf66QOTG83S7tCPwQhwJUAylFuADvURjPQ
+qvqNjbQUomdm2QjBzyWtiFbolqxBgM3dnE6R/vkCgYBYGqQexx83LhmKPGbmTwal
+mARzkg59BxfZRN7IxcG4k0a1v98i+xISdYqwkP7cdOU18Tf8k1mwsrKytrcheqaf
+mn2bzJ5gJKs9s+DgWmjQ45dpCCqb4hfpnro8lKVwdSifkNKB6gYZ8RHYdMYkq+S1
+6SGeBbv95/qNrXjZq8POUQKBgHyaDwD4dsdCY79LdvYofrenQHOv3Q+rjTo2JT6S
+fysww6EQ2M89WiXSgc96Xw/LMl4nDfv+nMmXvyjCRgHS9XRC7yrJAEjSPeM6s4fq
+XZ4nW/ML/YKiesDZN3jfRoFEaoX/QFBLpcuLzG9uQw1ymwy5RSxK7b7kE+eGQU82
+XOihAoGBAI3xvT9fG3jRsSuw/8OQBlmDUFZcT0fRPRZ3pg8XlSreAam4b607d2WY
+u/bBHIclG3CLJ2EFqBtxl9AQeM0OTweF0KmV3dbtdBmaTbnhbK8/NLYnl5+aosEJ
+YrFKD8k8z6z+mYQs+7bAnfRa53TjfC7f24BpgEQyEfKL2fa3PF+J
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/sslfiles.mk b/src/test/ssl/sslfiles.mk
index 7ed3a30f5c..d6a3870079 100644
--- a/src/test/ssl/sslfiles.mk
+++ b/src/test/ssl/sslfiles.mk
@@ -22,9 +22,15 @@
 # key/certificate pair will be generated for you, signed by the appropriate CA.
 #
 SERVERS := server-cn-and-alt-names \
+	server-cn-and-ip-alt-names \
 	server-cn-only \
+	server-ip-cn-only \
+	server-ip-cn-and-alt-names \
+	server-ip-cn-and-dns-alt-names \
+	server-ip-in-dnsname \
 	server-single-alt-name \
 	server-multiple-alt-names \
+	server-ip-alt-names \
 	server-no-names \
 	server-revoked
 CLIENTS := client client-dn client-revoked client_ext
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 5c5b16fbe7..07e5b71d8c 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -249,6 +249,30 @@ $node->connect_fails(
 	  qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/
 );
 
+# Test with an IP address in the Common Name. This is a strange corner case that
+# nevertheless is supported, as long as the address string matches exactly.
+switch_server_cert($node, 'server-ip-cn-only');
+
+$common_connstr =
+  "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+
+$node->connect_ok("$common_connstr host=192.0.2.1",
+	"IP address in the Common Name");
+
+$node->connect_fails(
+	"$common_connstr host=192.000.002.001",
+	"mismatch between host name and server certificate IP address",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.1" does not match host name "192.000.002.001"\E/
+);
+
+# Similarly, we'll also match an IP address in a dNSName SAN. (This is
+# long-standing behavior.)
+switch_server_cert($node, 'server-ip-in-dnsname');
+
+$node->connect_ok("$common_connstr host=192.0.2.1",
+	"IP address in a dNSName");
+
 # Test Subject Alternative Names.
 switch_server_cert($node, 'server-multiple-alt-names');
 
@@ -301,7 +325,54 @@ $node->connect_fails(
 	  qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/
 );
 
-# Test server certificate with a CN and SANs. Per RFCs 2818 and 6125, the CN
+# Test certificate with IP addresses in the SANs.
+switch_server_cert($node, 'server-ip-alt-names');
+
+$node->connect_ok(
+	"$common_connstr host=192.0.2.1",
+	"host matching an IPv4 address (Subject Alternative Name 1)");
+
+$node->connect_ok(
+	"$common_connstr host=192.000.002.001",
+	"host matching an IPv4 address in alternate form (Subject Alternative Name 1)");
+
+$node->connect_fails(
+	"$common_connstr host=192.0.2.2",
+	"host not matching an IPv4 address (Subject Alternative Name 1)",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.1" (and 1 other name) does not match host name "192.0.2.2"\E/
+);
+
+$node->connect_fails(
+	"$common_connstr host=192.0.2.1/32",
+	"IPv4 host with CIDR mask does not match",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.1" (and 1 other name) does not match host name "192.0.2.1\/32"\E/
+);
+
+$node->connect_ok(
+	"$common_connstr host=2001:DB8::1",
+	"host matching an IPv6 address (Subject Alternative Name 2)");
+
+$node->connect_ok(
+	"$common_connstr host=2001:db8:0:0:0:0:0:1",
+	"host matching an IPv6 address in alternate form (Subject Alternative Name 2)");
+
+$node->connect_fails(
+	"$common_connstr host=::1",
+	"host not matching an IPv6 address (Subject Alternative Name 2)",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.1" (and 1 other name) does not match host name "::1"\E/
+);
+
+$node->connect_fails(
+	"$common_connstr host=2001:DB8::1/128",
+	"IPv6 host with CIDR mask does not match",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.1" (and 1 other name) does not match host name "2001:DB8::1\/128"\E/
+);
+
+# Test server certificate with a CN and DNS SANs. Per RFCs 2818 and 6125, the CN
 # should be ignored when the certificate has both.
 switch_server_cert($node, 'server-cn-and-alt-names');
 
@@ -319,6 +390,39 @@ $node->connect_fails(
 	  qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/
 );
 
+# But we will fall back to check the CN if the SANs contain only IP addresses.
+switch_server_cert($node, 'server-cn-and-ip-alt-names');
+
+$node->connect_ok("$common_connstr host=common-name.pg-ssltest.test",
+	"certificate with both a CN and IP SANs matches CN");
+$node->connect_ok("$common_connstr host=192.0.2.1",
+	"certificate with both a CN and IP SANs matches SAN 1");
+$node->connect_ok("$common_connstr host=2001:db8::1",
+	"certificate with both a CN and IP SANs matches SAN 2");
+
+# And now the same tests, but with IP addresses and DNS names swapped.
+switch_server_cert($node, 'server-ip-cn-and-alt-names');
+
+$node->connect_ok("$common_connstr host=192.0.2.2",
+	"certificate with both an IP CN and IP SANs 1");
+$node->connect_ok("$common_connstr host=2001:db8::1",
+	"certificate with both an IP CN and IP SANs 2");
+$node->connect_fails(
+	"$common_connstr host=192.0.2.1",
+	"certificate with both an IP CN and IP SANs ignores CN",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.2" (and 1 other name) does not match host name "192.0.2.1"\E/
+);
+
+switch_server_cert($node, 'server-ip-cn-and-dns-alt-names');
+
+$node->connect_ok("$common_connstr host=192.0.2.1",
+	"certificate with both an IP CN and DNS SANs matches CN");
+$node->connect_ok("$common_connstr host=dns1.alt-name.pg-ssltest.test",
+	"certificate with both an IP CN and DNS SANs matches SAN 1");
+$node->connect_ok("$common_connstr host=dns2.alt-name.pg-ssltest.test",
+	"certificate with both an IP CN and DNS SANs matches SAN 2");
+
 # Finally, test a server certificate that has no CN or SANs. Of course, that's
 # not a very sensible certificate, but libpq should handle it gracefully.
 switch_server_cert($node, 'server-no-names');
-- 
2.25.1

v9-0003-squash-libpq-allow-IP-address-SANs-in-server-cert.patchtext/x-patch; name=v9-0003-squash-libpq-allow-IP-address-SANs-in-server-cert.patchDownload
From 80e1d1bff2f75d1643b2f8191c5333d4a11e173a Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Wed, 5 Jan 2022 15:47:03 -0800
Subject: [PATCH v9 3/3] squash! libpq: allow IP address SANs in server certs

Per review, provide a pg_inet_pton() interface for IPv6 and refactor the
internals accordingly. IPv4 is not yet implemented. The call sites that
just want standard addresses without a CIDR mask have been updated to
use the new function.

Internally, the IPv6 parser now takes an allow_cidr boolean. I've
plumbed that all the way down to getbits() in order to minimize the
amount of code touched, at the expense of an unnecessary function call
in the failing case.
---
 src/include/port.h                       |  1 +
 src/interfaces/libpq/fe-secure-common.c  |  7 +--
 src/interfaces/libpq/fe-secure-openssl.c |  2 +-
 src/port/inet_net_pton.c                 | 66 ++++++++++++++++++------
 4 files changed, 52 insertions(+), 24 deletions(-)

diff --git a/src/include/port.h b/src/include/port.h
index 2852e5b58b..fd046f4c24 100644
--- a/src/include/port.h
+++ b/src/include/port.h
@@ -517,6 +517,7 @@ extern char *pg_inet_net_ntop(int af, const void *src, int bits,
 
 /* port/inet_net_pton.c */
 extern int	pg_inet_net_pton(int af, const char *src, void *dst, size_t size);
+extern int	pg_inet_pton(int af, const char *src, void *dst);
 
 /* port/pg_strong_random.c */
 extern void pg_strong_random_init(void);
diff --git a/src/interfaces/libpq/fe-secure-common.c b/src/interfaces/libpq/fe-secure-common.c
index 2c0af62afe..9c408df369 100644
--- a/src/interfaces/libpq/fe-secure-common.c
+++ b/src/interfaces/libpq/fe-secure-common.c
@@ -211,12 +211,7 @@ pq_verify_peer_name_matches_certificate_ip(PGconn *conn,
 
 		family = PGSQL_AF_INET6;
 
-		/*
-		 * pg_inet_net_pton() will accept CIDR masks, which we don't want to
-		 * match, so skip the comparison if the host string contains a slash.
-		 */
-		if (!strchr(host, '/')
-			&& pg_inet_net_pton(PGSQL_AF_INET6, host, addr, -1) == 128)
+		if (pg_inet_pton(PGSQL_AF_INET6, host, addr) == 1)
 		{
 			if (memcmp(ipdata, addr, iplen) == 0)
 				match = 1;
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index 7656c3a75e..b70bd2eb19 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -555,7 +555,7 @@ is_ip_address(const char *host)
 	unsigned char	dummy6[16];
 
 	return inet_aton(host, &dummy4)
-		|| (pg_inet_net_pton(PGSQL_AF_INET6, host, dummy6, -1) == 128);
+		|| (pg_inet_pton(PGSQL_AF_INET6, host, dummy6) == 1);
 }
 
 /*
diff --git a/src/port/inet_net_pton.c b/src/port/inet_net_pton.c
index 5bae811c6f..ae0d1fd2e3 100644
--- a/src/port/inet_net_pton.c
+++ b/src/port/inet_net_pton.c
@@ -42,8 +42,8 @@ static const char rcsid[] = "Id: inet_net_pton.c,v 1.4.2.3 2004/03/17 00:40:11 m
 
 static int	inet_net_pton_ipv4(const char *src, u_char *dst);
 static int	inet_cidr_pton_ipv4(const char *src, u_char *dst, size_t size);
-static int	inet_net_pton_ipv6(const char *src, u_char *dst);
-static int	inet_cidr_pton_ipv6(const char *src, u_char *dst, size_t size);
+static int	inet_pton_ipv6_internal(const char *src, u_char *dst, size_t size,
+									bool allow_cidr);
 
 
 /*
@@ -74,9 +74,9 @@ pg_inet_net_pton(int af, const char *src, void *dst, size_t size)
 				inet_net_pton_ipv4(src, dst) :
 				inet_cidr_pton_ipv4(src, dst, size);
 		case PGSQL_AF_INET6:
-			return size == -1 ?
-				inet_net_pton_ipv6(src, dst) :
-				inet_cidr_pton_ipv6(src, dst, size);
+			return inet_pton_ipv6_internal(src, dst,
+										   (size == -1) ? 16 : size,
+										   true /* allow CIDR */);
 		default:
 			errno = EAFNOSUPPORT;
 			return -1;
@@ -352,13 +352,22 @@ emsgsize:
 }
 
 static int
-getbits(const char *src, int *bitsp)
+getbits(const char *src, int *bitsp, bool allow_cidr)
 {
 	static const char digits[] = "0123456789";
 	int			n;
 	int			val;
 	char		ch;
 
+	if (!allow_cidr)
+	{
+		/*
+		 * If we got here, the top-level call is to pg_inet_pton() and the
+		 * caller supplied a CIDR mask, which isn't allowed.
+		 */
+		return 0;
+	}
+
 	val = 0;
 	n = 0;
 	while ((ch = *src++) != '\0')
@@ -385,7 +394,7 @@ getbits(const char *src, int *bitsp)
 }
 
 static int
-getv4(const char *src, u_char *dst, int *bitsp)
+getv4(const char *src, u_char *dst, int *bitsp, bool allow_cidr)
 {
 	static const char digits[] = "0123456789";
 	u_char	   *odst = dst;
@@ -416,7 +425,7 @@ getv4(const char *src, u_char *dst, int *bitsp)
 				return 0;
 			*dst++ = val;
 			if (ch == '/')
-				return getbits(src, bitsp);
+				return getbits(src, bitsp, allow_cidr);
 			val = 0;
 			n = 0;
 			continue;
@@ -431,18 +440,12 @@ getv4(const char *src, u_char *dst, int *bitsp)
 	return 1;
 }
 
-static int
-inet_net_pton_ipv6(const char *src, u_char *dst)
-{
-	return inet_cidr_pton_ipv6(src, dst, 16);
-}
-
 #define NS_IN6ADDRSZ 16
 #define NS_INT16SZ 2
 #define NS_INADDRSZ 4
 
 static int
-inet_cidr_pton_ipv6(const char *src, u_char *dst, size_t size)
+inet_pton_ipv6_internal(const char *src, u_char *dst, size_t size, bool allow_cidr)
 {
 	static const char xdigits_l[] = "0123456789abcdef",
 				xdigits_u[] = "0123456789ABCDEF";
@@ -510,13 +513,13 @@ inet_cidr_pton_ipv6(const char *src, u_char *dst, size_t size)
 			continue;
 		}
 		if (ch == '.' && ((tp + NS_INADDRSZ) <= endp) &&
-			getv4(curtok, tp, &bits) > 0)
+			getv4(curtok, tp, &bits, allow_cidr) > 0)
 		{
 			tp += NS_INADDRSZ;
 			saw_xdigit = 0;
 			break;				/* '\0' was seen by inet_pton4(). */
 		}
-		if (ch == '/' && getbits(src, &bits) > 0)
+		if (ch == '/' && getbits(src, &bits, allow_cidr) > 0)
 			break;
 		goto enoent;
 	}
@@ -530,6 +533,9 @@ inet_cidr_pton_ipv6(const char *src, u_char *dst, size_t size)
 	if (bits == -1)
 		bits = 128;
 
+	/* Without a CIDR mask, the above should guarantee a complete address. */
+	Assert(allow_cidr || bits == 128);
+
 	endp = tmp + 16;
 
 	if (colonp != NULL)
@@ -568,3 +574,29 @@ emsgsize:
 	errno = EMSGSIZE;
 	return -1;
 }
+
+/*
+ * Converts a network address in presentation format to network format. The main
+ * difference between this and pg_inet_net_pton() above is that CIDR notation is
+ * not allowed.
+ *
+ * The memory pointed to by dst depends on the address family:
+ *
+ *     PGSQL_AF_INET:  not implemented yet (returns -1 with EAFNOSUPPORT)
+ *     PGSQL_AF_INET6: *dst must be a u_char[16]
+ *
+ * Over and above the standard inet_pton() functionality, we also set errno on
+ * parse failure, with the same meanings as with pg_inet_net_pton().
+ */
+int
+pg_inet_pton(int af, const char *src, void *dst)
+{
+	if (af != PGSQL_AF_INET6)
+	{
+		/* Currently only IPv6 is implemented. */
+		errno = EAFNOSUPPORT;
+		return -1;
+	}
+
+	return (inet_pton_ipv6_internal(src, dst, 16, false /* no CIDR */) == 128);
+}
-- 
2.25.1

#31Kyotaro Horiguchi
horikyota.ntt@gmail.com
In reply to: Jacob Champion (#30)
1 attachment(s)
Re: [PATCH] Accept IP addresses in server certificate SANs

At Tue, 22 Mar 2022 20:42:37 +0000, Jacob Champion <pchampion@vmware.com> wrote in

Thanks, looks like I had some old header dependencies left over from
several versions ago. Fixed in v9.

Thanks! Looks perfect.

v9 contains the bare minimum but I don't think it's quite enough. How
much of the behavior (and edge cases) do you think we should detail
here? All of it?

I tried to write out the doc part. What do you think about it?

regards.

--
Kyotaro Horiguchi
NTT Open Source Software Center

Attachments:

ipaddr_san_doc_kh.txttext/plain; charset=us-asciiDownload
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 3998b1781b..13e3e63768 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -8342,16 +8342,31 @@ ldap://ldap.acme.com/cn=dbserver,cn=hosts?pgconnectinfo?base?(objectclass=*)
 
   <para>
    In <literal>verify-full</literal> mode, the host name is matched against the
-   certificate's Subject Alternative Name attribute(s), or against the
-   Common Name attribute if no Subject Alternative Name of type <literal>dNSName</literal> is
+   certificate's Subject Alternative Name attribute(s) (SAN), or against the
+   Common Name attribute if no SAN of type <literal>dNSName</literal> is
    present.  If the certificate's name attribute starts with an asterisk
    (<literal>*</literal>), the asterisk will be treated as
    a wildcard, which will match all characters <emphasis>except</emphasis> a dot
    (<literal>.</literal>). This means the certificate will not match subdomains.
    If the connection is made using an IP address instead of a host name, the
-   IP address will be matched (without doing any DNS lookups).
+   IP address will be matched (without doing any DNS lookups) against SANs of
+   type <literal>iPAddress</literal> or <literal>dNSName</literal>.  If no
+   <literal>ipAddress</literal> SAN is present and no
+   matching <literal>dNSName</literal> SAN is present, the host IP address is
+   matched against the Common Name attribute.
   </para>
 
+  <note>
+    <para>
+      For backward compatibility with earlier versions of PostgreSQL, the host
+      IP address is verified in a manner different
+      from <ulink url="https://tools.ietf.org/html/rfc6125">RFC 6125</ulink>.
+      The host IP address is always matched against <literal>dNSName</literal>
+      SANs as well as <literal>iPAdress</literal> SANs, and can be matched
+      against the Common Name attribute for a certain condition.
+   </para>
+  </note>
+
   <para>
    To allow server certificate verification, one or more root certificates
    must be placed in the file <filename>~/.postgresql/root.crt</filename>
#32Jacob Champion
pchampion@vmware.com
In reply to: Kyotaro Horiguchi (#31)
3 attachment(s)
Re: [PATCH] Accept IP addresses in server certificate SANs

On Wed, 2022-03-23 at 14:20 +0900, Kyotaro Horiguchi wrote:

I tried to write out the doc part. What do you think about it?

I like it, thanks! I've applied that in v10, with a tweak to two
iPAddress spellings and a short expansion of the condition in the Note,
and I've added you as a co-author to 0002.

--Jacob

Attachments:

v10-0001-Move-inet_net_pton-to-src-port.patchtext/x-patch; name=v10-0001-Move-inet_net_pton-to-src-port.patchDownload
From 84a107da33b7d901cb318f8c2fd140644fbc5100 Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Wed, 24 Nov 2021 14:46:11 -0800
Subject: [PATCH v10 1/3] Move inet_net_pton() to src/port

This will be helpful for IP address verification in libpq.
---
 src/backend/utils/adt/Makefile                  |  1 -
 src/include/port.h                              |  3 +++
 src/include/utils/builtins.h                    |  4 ----
 src/port/Makefile                               |  1 +
 src/{backend/utils/adt => port}/inet_net_pton.c | 16 +++++++++++++++-
 src/tools/msvc/Mkvcbuild.pm                     |  2 +-
 6 files changed, 20 insertions(+), 7 deletions(-)
 rename src/{backend/utils/adt => port}/inet_net_pton.c (96%)

diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile
index 41b486bcef..d173d52157 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -43,7 +43,6 @@ OBJS = \
 	geo_selfuncs.o \
 	geo_spgist.o \
 	inet_cidr_ntop.o \
-	inet_net_pton.o \
 	int.o \
 	int8.o \
 	json.o \
diff --git a/src/include/port.h b/src/include/port.h
index 3d103a2b31..2852e5b58b 100644
--- a/src/include/port.h
+++ b/src/include/port.h
@@ -515,6 +515,9 @@ extern int	pg_codepage_to_encoding(UINT cp);
 extern char *pg_inet_net_ntop(int af, const void *src, int bits,
 							  char *dst, size_t size);
 
+/* port/inet_net_pton.c */
+extern int	pg_inet_net_pton(int af, const char *src, void *dst, size_t size);
+
 /* port/pg_strong_random.c */
 extern void pg_strong_random_init(void);
 extern bool pg_strong_random(void *buf, size_t len);
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 666e545496..67b9326dc8 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -93,10 +93,6 @@ extern int	xidLogicalComparator(const void *arg1, const void *arg2);
 extern char *pg_inet_cidr_ntop(int af, const void *src, int bits,
 							   char *dst, size_t size);
 
-/* inet_net_pton.c */
-extern int	pg_inet_net_pton(int af, const char *src,
-							 void *dst, size_t size);
-
 /* network.c */
 extern double convert_network_to_scalar(Datum value, Oid typid, bool *failure);
 extern Datum network_scan_first(Datum in);
diff --git a/src/port/Makefile b/src/port/Makefile
index bfe1feb0d4..c3ae7b3d5c 100644
--- a/src/port/Makefile
+++ b/src/port/Makefile
@@ -43,6 +43,7 @@ OBJS = \
 	bsearch_arg.o \
 	chklocale.o \
 	inet_net_ntop.o \
+	inet_net_pton.o \
 	noblock.o \
 	path.o \
 	pg_bitutils.o \
diff --git a/src/backend/utils/adt/inet_net_pton.c b/src/port/inet_net_pton.c
similarity index 96%
rename from src/backend/utils/adt/inet_net_pton.c
rename to src/port/inet_net_pton.c
index d3221a1313..bae50ba67e 100644
--- a/src/backend/utils/adt/inet_net_pton.c
+++ b/src/port/inet_net_pton.c
@@ -14,14 +14,18 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
  * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  *
- *	  src/backend/utils/adt/inet_net_pton.c
+ *	  src/port/inet_net_pton.c
  */
 
 #if defined(LIBC_SCCS) && !defined(lint)
 static const char rcsid[] = "Id: inet_net_pton.c,v 1.4.2.3 2004/03/17 00:40:11 marka Exp $";
 #endif
 
+#ifndef FRONTEND
 #include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
 
 #include <sys/socket.h>
 #include <netinet/in.h>
@@ -29,9 +33,19 @@ static const char rcsid[] = "Id: inet_net_pton.c,v 1.4.2.3 2004/03/17 00:40:11 m
 #include <assert.h>
 #include <ctype.h>
 
+#ifndef FRONTEND
 #include "utils/builtins.h" /* pgrminclude ignore */	/* needed on some
 														 * platforms */
 #include "utils/inet.h"
+#else
+/*
+ * In a frontend build, we can't include inet.h, but we still need to have
+ * sensible definitions of these two constants.  Note that pg_inet_net_ntop()
+ * assumes that PGSQL_AF_INET is equal to AF_INET.
+ */
+#define PGSQL_AF_INET	(AF_INET + 0)
+#define PGSQL_AF_INET6	(AF_INET + 1)
+#endif
 
 
 static int	inet_net_pton_ipv4(const char *src, u_char *dst);
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 441d6ae6bf..667b173ec3 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -100,7 +100,7 @@ sub mkvcbuild
 
 	our @pgportfiles = qw(
 	  chklocale.c explicit_bzero.c fls.c getpeereid.c getrusage.c inet_aton.c
-	  getaddrinfo.c gettimeofday.c inet_net_ntop.c kill.c open.c
+	  getaddrinfo.c gettimeofday.c inet_net_ntop.c inet_net_pton.c kill.c open.c
 	  snprintf.c strlcat.c strlcpy.c dirmod.c noblock.c path.c
 	  dirent.c dlopen.c getopt.c getopt_long.c link.c
 	  pread.c preadv.c pwrite.c pwritev.c pg_bitutils.c
-- 
2.25.1

v10-0002-libpq-allow-IP-address-SANs-in-server-certs.patchtext/x-patch; name=v10-0002-libpq-allow-IP-address-SANs-in-server-certs.patchDownload
From 6ad0b390f11a025045be278a789d72547775fe77 Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Thu, 18 Nov 2021 15:36:18 -0800
Subject: [PATCH v10 2/3] libpq: allow IP address SANs in server certs

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.

- Move PGSQL_AF_INET* to inet-common.h to reduce copy-paste.

Co-authored-by: Kyotaro Horiguchi <horikyota.ntt@gmail.com>
---
 doc/src/sgml/libpq.sgml                       |  21 ++-
 src/include/common/inet-common.h              |  35 +++++
 src/include/utils/inet.h                      |  13 +-
 src/interfaces/libpq/fe-secure-common.c       | 104 +++++++++++++
 src/interfaces/libpq/fe-secure-common.h       |   4 +
 src/interfaces/libpq/fe-secure-openssl.c      | 139 ++++++++++++++++--
 src/port/inet_net_ntop.c                      |  12 +-
 src/port/inet_net_pton.c                      |  10 +-
 .../conf/server-cn-and-ip-alt-names.config    |  24 +++
 src/test/ssl/conf/server-ip-alt-names.config  |  19 +++
 .../conf/server-ip-cn-and-alt-names.config    |  21 +++
 .../server-ip-cn-and-dns-alt-names.config     |  21 +++
 src/test/ssl/conf/server-ip-cn-only.config    |  12 ++
 src/test/ssl/conf/server-ip-in-dnsname.config |  18 +++
 .../ssl/ssl/server-cn-and-ip-alt-names.crt    |  20 +++
 .../ssl/ssl/server-cn-and-ip-alt-names.key    |  27 ++++
 src/test/ssl/ssl/server-ip-alt-names.crt      |  19 +++
 src/test/ssl/ssl/server-ip-alt-names.key      |  27 ++++
 .../ssl/ssl/server-ip-cn-and-alt-names.crt    |  19 +++
 .../ssl/ssl/server-ip-cn-and-alt-names.key    |  27 ++++
 .../ssl/server-ip-cn-and-dns-alt-names.crt    |  20 +++
 .../ssl/server-ip-cn-and-dns-alt-names.key    |  27 ++++
 src/test/ssl/ssl/server-ip-cn-only.crt        |  18 +++
 src/test/ssl/ssl/server-ip-cn-only.key        |  27 ++++
 src/test/ssl/ssl/server-ip-in-dnsname.crt     |  18 +++
 src/test/ssl/ssl/server-ip-in-dnsname.key     |  27 ++++
 src/test/ssl/sslfiles.mk                      |   6 +
 src/test/ssl/t/001_ssltests.pl                | 106 ++++++++++++-
 28 files changed, 792 insertions(+), 49 deletions(-)
 create mode 100644 src/include/common/inet-common.h
 create mode 100644 src/test/ssl/conf/server-cn-and-ip-alt-names.config
 create mode 100644 src/test/ssl/conf/server-ip-alt-names.config
 create mode 100644 src/test/ssl/conf/server-ip-cn-and-alt-names.config
 create mode 100644 src/test/ssl/conf/server-ip-cn-and-dns-alt-names.config
 create mode 100644 src/test/ssl/conf/server-ip-cn-only.config
 create mode 100644 src/test/ssl/conf/server-ip-in-dnsname.config
 create mode 100644 src/test/ssl/ssl/server-cn-and-ip-alt-names.crt
 create mode 100644 src/test/ssl/ssl/server-cn-and-ip-alt-names.key
 create mode 100644 src/test/ssl/ssl/server-ip-alt-names.crt
 create mode 100644 src/test/ssl/ssl/server-ip-alt-names.key
 create mode 100644 src/test/ssl/ssl/server-ip-cn-and-alt-names.crt
 create mode 100644 src/test/ssl/ssl/server-ip-cn-and-alt-names.key
 create mode 100644 src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.crt
 create mode 100644 src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.key
 create mode 100644 src/test/ssl/ssl/server-ip-cn-only.crt
 create mode 100644 src/test/ssl/ssl/server-ip-cn-only.key
 create mode 100644 src/test/ssl/ssl/server-ip-in-dnsname.crt
 create mode 100644 src/test/ssl/ssl/server-ip-in-dnsname.key

diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 3998b1781b..103f4240c3 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -8342,16 +8342,31 @@ ldap://ldap.acme.com/cn=dbserver,cn=hosts?pgconnectinfo?base?(objectclass=*)
 
   <para>
    In <literal>verify-full</literal> mode, the host name is matched against the
-   certificate's Subject Alternative Name attribute(s), or against the
-   Common Name attribute if no Subject Alternative Name of type <literal>dNSName</literal> is
+   certificate's Subject Alternative Name attribute(s) (SAN), or against the
+   Common Name attribute if no SAN of type <literal>dNSName</literal> is
    present.  If the certificate's name attribute starts with an asterisk
    (<literal>*</literal>), the asterisk will be treated as
    a wildcard, which will match all characters <emphasis>except</emphasis> a dot
    (<literal>.</literal>). This means the certificate will not match subdomains.
    If the connection is made using an IP address instead of a host name, the
-   IP address will be matched (without doing any DNS lookups).
+   IP address will be matched (without doing any DNS lookups) against SANs of
+   type <literal>iPAddress</literal> or <literal>dNSName</literal>.  If no
+   <literal>iPAddress</literal> SAN is present and no
+   matching <literal>dNSName</literal> SAN is present, the host IP address is
+   matched against the Common Name attribute.
   </para>
 
+  <note>
+    <para>
+      For backward compatibility with earlier versions of PostgreSQL, the host
+      IP address is verified in a manner different
+      from <ulink url="https://tools.ietf.org/html/rfc6125">RFC 6125</ulink>.
+      The host IP address is always matched against <literal>dNSName</literal>
+      SANs as well as <literal>iPAddress</literal> SANs, and can be matched
+      against the Common Name attribute if no relevant SANs exist.
+   </para>
+  </note>
+
   <para>
    To allow server certificate verification, one or more root certificates
    must be placed in the file <filename>~/.postgresql/root.crt</filename>
diff --git a/src/include/common/inet-common.h b/src/include/common/inet-common.h
new file mode 100644
index 0000000000..3ad72261b6
--- /dev/null
+++ b/src/include/common/inet-common.h
@@ -0,0 +1,35 @@
+/*-------------------------------------------------------------------------
+ *
+ * inet-common.h
+ *
+ *	  Common code for clients of the inet_* APIs.
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/common/inet-common.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef INET_COMMON_H
+#define INET_COMMON_H
+
+/*
+ * We use these values for the "family" field of inet_struct.
+ *
+ * Referencing all of the non-AF_INET types to AF_INET lets us work on
+ * machines which may not have the appropriate address family (like
+ * inet6 addresses when AF_INET6 isn't present) but doesn't cause a
+ * dump/reload requirement.  Pre-7.4 databases used AF_INET for the family
+ * type on disk.
+ *
+ * In a frontend build, we can't include inet.h, but we still need to have
+ * sensible definitions of these two constants.  (Frontend clients should
+ * include this header directly.)  Note that pg_inet_net_ntop()
+ * assumes that PGSQL_AF_INET is equal to AF_INET.
+ */
+#define PGSQL_AF_INET	(AF_INET + 0)
+#define PGSQL_AF_INET6	(AF_INET + 1)
+
+#endif							/* INET_COMMON_H */
diff --git a/src/include/utils/inet.h b/src/include/utils/inet.h
index 3073c0307e..c56c016987 100644
--- a/src/include/utils/inet.h
+++ b/src/include/utils/inet.h
@@ -14,6 +14,7 @@
 #ifndef INET_H
 #define INET_H
 
+#include "common/inet-common.h"
 #include "fmgr.h"
 
 /*
@@ -27,18 +28,6 @@ typedef struct
 	unsigned char ipaddr[16];	/* up to 128 bits of address */
 } inet_struct;
 
-/*
- * We use these values for the "family" field.
- *
- * Referencing all of the non-AF_INET types to AF_INET lets us work on
- * machines which may not have the appropriate address family (like
- * inet6 addresses when AF_INET6 isn't present) but doesn't cause a
- * dump/reload requirement.  Pre-7.4 databases used AF_INET for the family
- * type on disk.
- */
-#define PGSQL_AF_INET	(AF_INET + 0)
-#define PGSQL_AF_INET6	(AF_INET + 1)
-
 /*
  * Both INET and CIDR addresses are represented within Postgres as varlena
  * objects, ie, there is a varlena header in front of the struct type
diff --git a/src/interfaces/libpq/fe-secure-common.c b/src/interfaces/libpq/fe-secure-common.c
index bd46f08fae..2c0af62afe 100644
--- a/src/interfaces/libpq/fe-secure-common.c
+++ b/src/interfaces/libpq/fe-secure-common.c
@@ -19,9 +19,13 @@
 
 #include "postgres_fe.h"
 
+#include <arpa/inet.h>
+
+#include "common/inet-common.h"
 #include "fe-secure-common.h"
 
 #include "libpq-int.h"
+#include "port.h"
 #include "pqexpbuffer.h"
 
 /*
@@ -144,6 +148,106 @@ pq_verify_peer_name_matches_certificate_name(PGconn *conn,
 	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'))
+	{
+		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 = PGSQL_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;
+		}
+	}
+	else if (iplen == 16)
+	{
+		/* IPv6 */
+		unsigned char addr[16];
+
+		family = PGSQL_AF_INET6;
+
+		/*
+		 * pg_inet_net_pton() will accept CIDR masks, which we don't want to
+		 * match, so skip the comparison if the host string contains a slash.
+		 */
+		if (!strchr(host, '/')
+			&& pg_inet_net_pton(PGSQL_AF_INET6, host, addr, -1) == 128)
+		{
+			if (memcmp(ipdata, addr, iplen) == 0)
+				match = 1;
+		}
+	}
+	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.
  *
diff --git a/src/interfaces/libpq/fe-secure-common.h b/src/interfaces/libpq/fe-secure-common.h
index 1cca6d785a..d18db7138c 100644
--- a/src/interfaces/libpq/fe-secure-common.h
+++ b/src/interfaces/libpq/fe-secure-common.h
@@ -21,6 +21,10 @@
 extern int	pq_verify_peer_name_matches_certificate_name(PGconn *conn,
 														 const char *namedata, size_t namelen,
 														 char **store_name);
+extern int	pq_verify_peer_name_matches_certificate_ip(PGconn *conn,
+													   const unsigned char *addrdata,
+													   size_t addrlen,
+													   char **store_name);
 extern bool pq_verify_peer_name_matches_certificate(PGconn *conn);
 
 #endif							/* FE_SECURE_COMMON_H */
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index d81218a4cc..7656c3a75e 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -26,6 +26,7 @@
 #include <fcntl.h>
 #include <ctype.h>
 
+#include "common/inet-common.h"
 #include "libpq-fe.h"
 #include "fe-auth.h"
 #include "fe-secure-common.h"
@@ -72,6 +73,9 @@ static int	verify_cb(int ok, X509_STORE_CTX *ctx);
 static int	openssl_verify_peer_name_matches_certificate_name(PGconn *conn,
 															  ASN1_STRING *name,
 															  char **store_name);
+static int	openssl_verify_peer_name_matches_certificate_ip(PGconn *conn,
+															ASN1_OCTET_STRING *addr_entry,
+															char **store_name);
 static void destroy_ssl_system(void);
 static int	initialize_SSL(PGconn *conn);
 static PostgresPollingStatusType open_client_SSL(PGconn *);
@@ -509,6 +513,51 @@ openssl_verify_peer_name_matches_certificate_name(PGconn *conn, ASN1_STRING *nam
 	return pq_verify_peer_name_matches_certificate_name(conn, (const char *) namedata, len, store_name);
 }
 
+/*
+ * OpenSSL-specific wrapper around
+ * pq_verify_peer_name_matches_certificate_ip(), converting the
+ * ASN1_OCTET_STRING into a plain C string.
+ */
+static int
+openssl_verify_peer_name_matches_certificate_ip(PGconn *conn,
+												ASN1_OCTET_STRING *addr_entry,
+												char **store_name)
+{
+	int			len;
+	const unsigned char *addrdata;
+
+	/* Should not happen... */
+	if (addr_entry == NULL)
+	{
+		appendPQExpBufferStr(&conn->errorMessage,
+							 libpq_gettext("SSL certificate's address entry is missing\n"));
+		return -1;
+	}
+
+	/*
+	 * GEN_IPADD is an OCTET STRING containing an IP address in network byte
+	 * order.
+	 */
+#ifdef HAVE_ASN1_STRING_GET0_DATA
+	addrdata = ASN1_STRING_get0_data(addr_entry);
+#else
+	addrdata = ASN1_STRING_data(addr_entry);
+#endif
+	len = ASN1_STRING_length(addr_entry);
+
+	return pq_verify_peer_name_matches_certificate_ip(conn, addrdata, len, store_name);
+}
+
+static bool
+is_ip_address(const char *host)
+{
+	struct in_addr	dummy4;
+	unsigned char	dummy6[16];
+
+	return inet_aton(host, &dummy4)
+		|| (pg_inet_net_pton(PGSQL_AF_INET6, host, dummy6, -1) == 128);
+}
+
 /*
  *	Verify that the server certificate matches the hostname we connected to.
  *
@@ -522,6 +571,36 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 	STACK_OF(GENERAL_NAME) * peer_san;
 	int			i;
 	int			rc = 0;
+	char	   *host = conn->connhost[conn->whichhost].host;
+	int			host_type;
+	bool		check_cn = true;
+
+	Assert(host && host[0]); /* should be guaranteed by caller */
+
+	/*
+	 * We try to match the NSS behavior here, which is a slight departure from
+	 * the spec but seems to make more intuitive sense:
+	 *
+	 * If connhost contains a DNS name, and the certificate's SANs contain any
+	 * dNSName entries, then we'll ignore the Subject Common Name entirely;
+	 * otherwise, we fall back to checking the CN. (This behavior matches the
+	 * RFC.)
+	 *
+	 * If connhost contains an IP address, and the SANs contain iPAddress
+	 * entries, we again ignore the CN. Otherwise, we allow the CN to match,
+	 * EVEN IF there is a dNSName in the SANs. (RFC 6125 prohibits this: "A
+	 * client MUST NOT seek a match for a reference identifier of CN-ID if the
+	 * presented identifiers include a DNS-ID, SRV-ID, URI-ID, or any
+	 * application-specific identifier types supported by the client.")
+	 *
+	 * NOTE: Prior versions of libpq did not consider iPAddress entries at all,
+	 * so this new behavior might break a certificate that has different IP
+	 * addresses in the Subject CN and the SANs.
+	 */
+	if (is_ip_address(host))
+		host_type = GEN_IPADD;
+	else
+		host_type = GEN_DNS;
 
 	/*
 	 * First, get the Subject Alternative Names (SANs) from the certificate,
@@ -537,38 +616,62 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 		for (i = 0; i < san_len; i++)
 		{
 			const GENERAL_NAME *name = sk_GENERAL_NAME_value(peer_san, i);
+			char	   *alt_name = NULL;
 
-			if (name->type == GEN_DNS)
+			if (name->type == host_type)
 			{
-				char	   *alt_name;
+				/*
+				 * This SAN is of the same type (IP or DNS) as our host name, so
+				 * don't allow a fallback check of the CN.
+				 */
+				check_cn = false;
+			}
 
+			if (name->type == GEN_DNS)
+			{
 				(*names_examined)++;
 				rc = openssl_verify_peer_name_matches_certificate_name(conn,
 																	   name->d.dNSName,
 																	   &alt_name);
+			}
+			else if (name->type == GEN_IPADD)
+			{
+				(*names_examined)++;
+				rc = openssl_verify_peer_name_matches_certificate_ip(conn,
+																	 name->d.iPAddress,
+																	 &alt_name);
+			}
 
-				if (alt_name)
-				{
-					if (!*first_name)
-						*first_name = alt_name;
-					else
-						free(alt_name);
-				}
+			if (alt_name)
+			{
+				if (!*first_name)
+					*first_name = alt_name;
+				else
+					free(alt_name);
 			}
+
 			if (rc != 0)
+			{
+				/*
+				 * Either we hit an error or a match, and either way we should
+				 * not fall back to the CN.
+				 */
+				check_cn = false;
 				break;
+			}
 		}
 		sk_GENERAL_NAME_pop_free(peer_san, GENERAL_NAME_free);
 	}
 
 	/*
-	 * If there is no subjectAltName extension of type dNSName, check the
+	 * If there is no subjectAltName extension of the matching type, check the
 	 * Common Name.
 	 *
 	 * (Per RFC 2818 and RFC 6125, if the subjectAltName extension of type
-	 * dNSName is present, the CN must be ignored.)
+	 * dNSName is present, the CN must be ignored. We break this rule if host is
+	 * an IP address; see the comment above.)
 	 */
-	if (*names_examined == 0)
+	if (check_cn)
 	{
 		X509_NAME  *subject_name;
 
@@ -581,10 +684,20 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 												  NID_commonName, -1);
 			if (cn_index >= 0)
 			{
+				char   *common_name = NULL;
+
 				(*names_examined)++;
 				rc = openssl_verify_peer_name_matches_certificate_name(conn,
 																	   X509_NAME_ENTRY_get_data(X509_NAME_get_entry(subject_name, cn_index)),
-																	   first_name);
+																	   &common_name);
+
+				if (common_name)
+				{
+					if (!*first_name)
+						*first_name = common_name;
+					else
+						free(common_name);
+				}
 			}
 		}
 	}
diff --git a/src/port/inet_net_ntop.c b/src/port/inet_net_ntop.c
index b8ad69c390..af28056132 100644
--- a/src/port/inet_net_ntop.c
+++ b/src/port/inet_net_ntop.c
@@ -31,17 +31,7 @@ static const char rcsid[] = "Id: inet_net_ntop.c,v 1.1.2.2 2004/03/09 09:17:27 m
 #include <netinet/in.h>
 #include <arpa/inet.h>
 
-#ifndef FRONTEND
-#include "utils/inet.h"
-#else
-/*
- * In a frontend build, we can't include inet.h, but we still need to have
- * sensible definitions of these two constants.  Note that pg_inet_net_ntop()
- * assumes that PGSQL_AF_INET is equal to AF_INET.
- */
-#define PGSQL_AF_INET	(AF_INET + 0)
-#define PGSQL_AF_INET6	(AF_INET + 1)
-#endif
+#include "common/inet-common.h"
 
 
 #define NS_IN6ADDRSZ 16
diff --git a/src/port/inet_net_pton.c b/src/port/inet_net_pton.c
index bae50ba67e..5bae811c6f 100644
--- a/src/port/inet_net_pton.c
+++ b/src/port/inet_net_pton.c
@@ -33,18 +33,10 @@ static const char rcsid[] = "Id: inet_net_pton.c,v 1.4.2.3 2004/03/17 00:40:11 m
 #include <assert.h>
 #include <ctype.h>
 
+#include "common/inet-common.h"
 #ifndef FRONTEND
 #include "utils/builtins.h" /* pgrminclude ignore */	/* needed on some
 														 * platforms */
-#include "utils/inet.h"
-#else
-/*
- * In a frontend build, we can't include inet.h, but we still need to have
- * sensible definitions of these two constants.  Note that pg_inet_net_ntop()
- * assumes that PGSQL_AF_INET is equal to AF_INET.
- */
-#define PGSQL_AF_INET	(AF_INET + 0)
-#define PGSQL_AF_INET6	(AF_INET + 1)
 #endif
 
 
diff --git a/src/test/ssl/conf/server-cn-and-ip-alt-names.config b/src/test/ssl/conf/server-cn-and-ip-alt-names.config
new file mode 100644
index 0000000000..a6fa09bad3
--- /dev/null
+++ b/src/test/ssl/conf/server-cn-and-ip-alt-names.config
@@ -0,0 +1,24 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+# This certificate contains a CN and SANs for both IPv4 and IPv6.
+
+
+[ req ]
+distinguished_name     = req_distinguished_name
+req_extensions         = v3_req
+prompt                 = no
+
+[ req_distinguished_name ]
+# Note: According to RFC 2818 and 6125, the CN is ignored, when DNS names are
+# present in the SANs. But they are silent on whether the CN is checked when IP
+# addresses are present.
+CN = common-name.pg-ssltest.test
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
+subjectAltName = @alt_names
+
+[ alt_names ]
+IP.1 = 192.0.2.1
+IP.2 = 2001:DB8::1
diff --git a/src/test/ssl/conf/server-ip-alt-names.config b/src/test/ssl/conf/server-ip-alt-names.config
new file mode 100644
index 0000000000..c22f22951a
--- /dev/null
+++ b/src/test/ssl/conf/server-ip-alt-names.config
@@ -0,0 +1,19 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+# This certificate has a two IP-address SANs, and no CN.
+
+[ req ]
+distinguished_name     = req_distinguished_name
+req_extensions         = v3_req
+prompt                 = no
+
+[ req_distinguished_name ]
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
+subjectAltName = @alt_names
+
+[ alt_names ]
+IP.1 = 192.0.2.1
+IP.2 = 2001:DB8::1
diff --git a/src/test/ssl/conf/server-ip-cn-and-alt-names.config b/src/test/ssl/conf/server-ip-cn-and-alt-names.config
new file mode 100644
index 0000000000..a4087f0a18
--- /dev/null
+++ b/src/test/ssl/conf/server-ip-cn-and-alt-names.config
@@ -0,0 +1,21 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+# This certificate contains both a CN and SANs in IP address format.
+
+
+[ req ]
+distinguished_name     = req_distinguished_name
+req_extensions         = v3_req
+prompt                 = no
+
+[ req_distinguished_name ]
+CN = 192.0.2.1
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
+subjectAltName = @alt_names
+
+[ alt_names ]
+IP.1 = 192.0.2.2
+IP.2 = 2001:DB8::1
diff --git a/src/test/ssl/conf/server-ip-cn-and-dns-alt-names.config b/src/test/ssl/conf/server-ip-cn-and-dns-alt-names.config
new file mode 100644
index 0000000000..7121803b49
--- /dev/null
+++ b/src/test/ssl/conf/server-ip-cn-and-dns-alt-names.config
@@ -0,0 +1,21 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+# This certificate contains both a CN and SANs in IP address format.
+
+
+[ req ]
+distinguished_name     = req_distinguished_name
+req_extensions         = v3_req
+prompt                 = no
+
+[ req_distinguished_name ]
+CN = 192.0.2.1
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
+subjectAltName = @alt_names
+
+[ alt_names ]
+DNS.1 = dns1.alt-name.pg-ssltest.test
+DNS.2 = dns2.alt-name.pg-ssltest.test
diff --git a/src/test/ssl/conf/server-ip-cn-only.config b/src/test/ssl/conf/server-ip-cn-only.config
new file mode 100644
index 0000000000..585d8bdae8
--- /dev/null
+++ b/src/test/ssl/conf/server-ip-cn-only.config
@@ -0,0 +1,12 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+
+[ req ]
+distinguished_name     = req_distinguished_name
+prompt                 = no
+
+[ req_distinguished_name ]
+CN = 192.0.2.1
+OU = PostgreSQL test suite
+
+# No Subject Alternative Names
diff --git a/src/test/ssl/conf/server-ip-in-dnsname.config b/src/test/ssl/conf/server-ip-in-dnsname.config
new file mode 100644
index 0000000000..b15649aef7
--- /dev/null
+++ b/src/test/ssl/conf/server-ip-in-dnsname.config
@@ -0,0 +1,18 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+
+[ req ]
+distinguished_name     = req_distinguished_name
+req_extensions         = v3_req
+prompt                 = no
+
+[ req_distinguished_name ]
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
+subjectAltName = @alt_names
+
+# Normally IP addresses should not go into a dNSName.
+[ alt_names ]
+DNS.1 = 192.0.2.1
diff --git a/src/test/ssl/ssl/server-cn-and-ip-alt-names.crt b/src/test/ssl/ssl/server-cn-and-ip-alt-names.crt
new file mode 100644
index 0000000000..4e58c85ccb
--- /dev/null
+++ b/src/test/ssl/ssl/server-cn-and-ip-alt-names.crt
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDLzCCAhegAwIBAgIIICERKRE1UQAwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UE
+Aww3VGVzdCBDQSBmb3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHNl
+cnZlciBjZXJ0czAeFw0yMTExMjkxOTM1NTFaFw00OTA0MTYxOTM1NTFaMEYxHjAc
+BgNVBAsMFVBvc3RncmVTUUwgdGVzdCBzdWl0ZTEkMCIGA1UEAwwbY29tbW9uLW5h
+bWUucGctc3NsdGVzdC50ZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
+AQEA6+8IYKAFnZ7V+fDo1cyMpbGBLzCfJOQ/1o2jOGP4+GjpsZgv6S6UT2MheC8M
+iiEFrYwdsSIZyYc3jEZrluy/UuR0bCGtqU92BCqa0iBLhvHOgjR588u253eLxQtQ
+8iJn11QPrKMk35nMkmY8GfHt4sGFbvBL6+GpipHq7a6cde3Z+v4kCB5dKMYDUDtm
+3mJmviuGNAu5wOqItk2Yi5dwJs1054007KNH0Il43urxiOfnkLS0cG5kehboPf86
+vxBt3iHByrU/9/DY5IvQCfSXVNa6rb5w5/pGja9aCei6Mv1jQY/V8SMQTga+MOsA
+0WB9akxMi2NxwS2+BQ4k/McPlwIDAQABoyUwIzAhBgNVHREEGjAYhwTAAAIBhxAg
+AQ24AAAAAAAAAAAAAAABMA0GCSqGSIb3DQEBCwUAA4IBAQAQLo2RzC07dG9p+J3A
+W6C0p3Y+Os/YE2D9wfp4TIDTZxcRUQZ0S6ahF1N6sp8l9KHBJHPU1cUpRAU1oD+Y
+SqmnP/VJRRDTTj9Ytdc/Vuo2jeLpSYhVKrCqtjqIrCwYJFoYRmMoxTtJGlwA0hSd
+kwo3XYrALPUQWUErTYPvNfDNIuUwqUXNfS0CXuIOVN3LJ+shegg6Pwbh9B5T9NHx
+kH+HswajhdpdnZIgh0FYTlTCPILDrB49aOWwqLa54AUA6WXa35hPsP8SoqL9Eucq
+ifPhBYyadsjOb+70N8GbbAsDPN1jCX9L8RuNcEkxSCKCYx91cWXh7K5KMPuGlzB7
+j8xB
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-cn-and-ip-alt-names.key b/src/test/ssl/ssl/server-cn-and-ip-alt-names.key
new file mode 100644
index 0000000000..837eef996d
--- /dev/null
+++ b/src/test/ssl/ssl/server-cn-and-ip-alt-names.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA6+8IYKAFnZ7V+fDo1cyMpbGBLzCfJOQ/1o2jOGP4+GjpsZgv
+6S6UT2MheC8MiiEFrYwdsSIZyYc3jEZrluy/UuR0bCGtqU92BCqa0iBLhvHOgjR5
+88u253eLxQtQ8iJn11QPrKMk35nMkmY8GfHt4sGFbvBL6+GpipHq7a6cde3Z+v4k
+CB5dKMYDUDtm3mJmviuGNAu5wOqItk2Yi5dwJs1054007KNH0Il43urxiOfnkLS0
+cG5kehboPf86vxBt3iHByrU/9/DY5IvQCfSXVNa6rb5w5/pGja9aCei6Mv1jQY/V
+8SMQTga+MOsA0WB9akxMi2NxwS2+BQ4k/McPlwIDAQABAoIBAQCuNFKVNdKvrUYF
+RLJGmsAG3+eo9lern7TbML2ht39vu9dBwEMwA6qSa3mdCfBSVUuh9uE9lxY/TU3g
+j2aFi81A4VptNPjLGNblAKhMGnhp7UUzspeRQYuNoSFcnpxoDKtrvK/OIq/pQeBh
+AIfECHRDh+yEG32Tb44FuPQkB1eTYl8xbMEImrhNUaSjJk7tTsmydHy0DjmqHVKX
+HUj0TREfDBDOBiHtY0XV6Pu3bnqDH/TKLTfUf3UdfTuay3Yai9aEcRPWp9GrMO7G
+axsKCifTz6177gyr6Fv8HLeMZMh9rMZRn3e0zfaF6vrH1QnZZOts5jpUa0KugSCd
+//uC0iNxAoGBAPXVc3b+o3hY5gcwwpaW6JtsarDrmNRxrizqIDG7NgpqwdFXgTi6
+6q0t2pjv81ATqij69IcPkNSissyR4OEKnu/OFJWzreg8yLi75WHKi0E/6msHpwRk
+d1yP0Zgd05ots/yOjDSp593RagaPVvHBxMECZ/Tm3B+Tq55Azudd/zvLAoGBAPWw
+xf0oUEJl6NdUZD6K7eFc6jf8yrpD85dldeko6LeN8x0XlKKWvUDJ2+3oizXoQvCm
+8by6KOYEIo4MrtXuy9MmtPWfNvRBr+hsUHchIj7IgFa9bKXyK2FnJqu/8CbEymli
+eZu7hoOhelurhnFy1zSqwNO4GC+kw60Y/BO3Z1nlAoGAVOyYJtNwxXJwhKtjjYI0
+ePzLHrNE6J8c/Ick+AkkchTPP/JqwZ5Q0+KzUYITG+avMdkAAGhwMATEn8cFWLjC
+jzUyB0U7Hq9g5/CBHXdLBA+Ae9j46ZuLYH6OeW5UWz7OnsDfzpGjeA2QAxQhhQLb
+ZZHfN8tI39+zucfJskPWmGECgYEAg9guF1Fn6InJrqwR82IYj6SN6CeXHufSM392
+C/4xDDd3rDf4QlwECV2J0RzGf9I5Ae2EshNwWScE6Be0RweTh6cw2tJq6h7J6D8f
+2x4Dw49TF7klMdRIJUf2f5pLpHJccLswqTqzz7V69PCSABVxmUi8m6EiEYconp5W
+v7nfE2UCgYALrEqzncuSIX3q6TVAjnzT7gO4h8h2TUekIWdHQFldFx8R7Kncggnd
+48gQqhewchNR83UCcd7pPsCcTqu6UR1QRdq/DV5P6J3xdZ2iS/2gCM6hvWIvKZEv
+/ClnkyFCOW7zX6RKIXtRYZTV1kz3TajApi34RTIeIMTieaCarnBJbA==
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-ip-alt-names.crt b/src/test/ssl/ssl/server-ip-alt-names.crt
new file mode 100644
index 0000000000..8a1bc620bb
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-alt-names.crt
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDCTCCAfGgAwIBAgIIICERKREEUAAwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UE
+Aww3VGVzdCBDQSBmb3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHNl
+cnZlciBjZXJ0czAeFw0yMTExMjkxOTA0NTBaFw00OTA0MTYxOTA0NTBaMCAxHjAc
+BgNVBAsMFVBvc3RncmVTUUwgdGVzdCBzdWl0ZTCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBAOM8yB6aVWb17ujr3ayU62mxHQoqn4CvG9yXlJvGOGv/ursW
+Vs0UYJdc96LsNZN1szdm9ayNzCIw3eja+ULsjxCi6+3LM4pO76IORL/XFamlTPYb
+BZ4pHdZVB0nnZAAnWCZPyXdnjOKQ5+8unVXkfibkjj8UELBJ2snehsOa+CTkOBez
+zxYMqxAgbywLIYsW448brun7UXpWmqbGK+SsdGaIZ5Sb7Zezc5lt6CrLemTZTHHK
+7l4WZFCCEi4t3sgO8o1vDELD/IE5G8lyXvIdgJg6t8ssper7iCw6S8x+okhjiSjT
+vDLU2g4AanqZRZB49aPwTo0QUcJA2BCJxL9xLy8CAwEAAaMlMCMwIQYDVR0RBBow
+GIcEwAACAYcQIAENuAAAAAAAAAAAAAAAATANBgkqhkiG9w0BAQsFAAOCAQEAwZJ+
+8KpABTlMEgKnHIYb35ItGhtFiTLQta9RkXx7vaeDwpOdPP/IvuvpjpQZkobRgBsk
+bNM0KuJpd2mSTphQAt6eKQIdcPrkzvc/Yh9OK3YNLUAbu/ZhBUnBvFnUL4wn2f1U
+mfO+m8P/LxybwqKx7r1mbaB+tP3RTxxLcIMvm9ECPQEoBntfEL325Wdoj+WuQH5Y
+IvcM6FaCTkQsNIPbaBD5l5MhMLHRULZujbDjXqGSvRMQfns6np/biMjNdQA8NZ5z
+STeUFvkQbCxoA0YYLgoSHL5KhZjXrg2g+T+2TUyCTR/91xf9OoOjBZdixR0S0DzJ
+B1+5vnUjZaCfnSEA7A==
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-ip-alt-names.key b/src/test/ssl/ssl/server-ip-alt-names.key
new file mode 100644
index 0000000000..b210b3a991
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-alt-names.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEA4zzIHppVZvXu6OvdrJTrabEdCiqfgK8b3JeUm8Y4a/+6uxZW
+zRRgl1z3ouw1k3WzN2b1rI3MIjDd6Nr5QuyPEKLr7cszik7vog5Ev9cVqaVM9hsF
+nikd1lUHSedkACdYJk/Jd2eM4pDn7y6dVeR+JuSOPxQQsEnayd6Gw5r4JOQ4F7PP
+FgyrECBvLAshixbjjxuu6ftRelaapsYr5Kx0ZohnlJvtl7NzmW3oKst6ZNlMccru
+XhZkUIISLi3eyA7yjW8MQsP8gTkbyXJe8h2AmDq3yyyl6vuILDpLzH6iSGOJKNO8
+MtTaDgBqeplFkHj1o/BOjRBRwkDYEInEv3EvLwIDAQABAoIBACp3uY6+mSdc3wF4
+0zzlt/lQuHSl8plCIJrhWUyjhvfoGyXLzv0Uydh/72frbTfZz1yTSWauOXBKYa6a
+/eqb+0DIsf8G8uLuTaqjsAWKVOoXkoKMGkistn7P9UTCkdXVhIvkbWp7V8EgA7iX
+pZ/fzBPIsyzmuxe3NcR0ags0cxuxkNuu+YXDv1oTedmT2wS3CZq1d/T1Y/EOVIf8
+Iznd2aOverlsnt6iiQ3ZWdG/W5F8FhnrR/rrBdYsdCv6TH/KUYexnDOUYpayjDbu
+oAKnifPp6UqiOM4SuBL83OAz19jptp5vpF370BEVRs3eK0q+zo/mETjv9HsXdolZ
+lfoXA0ECgYEA/7nb2azbq/2muvXCh1ZxCEbn3mt8KXoJP/xkx/v9eEc/cc5Q9e0V
+2oGfjC2hSE+bjOWMwiUMD6uU+iRjhz5A3IvUxnoSdoL7H9p0hTqLMyP7dTDkoVF5
+aEuLMaiI5YEnfAFu9L5h8ZKieoQTBoscT06wnGjh9pBV9bthfTKA7ksCgYEA43sb
+55m9WL4kWCPwOAp3vdEAFyxzmZHlO26sEQOU/m5aN01pumYybBruziEXMI96yfTj
+VmXKReeYb6XUiCcs3fLSipD/+8/8CsjO4uMORtxWumXe8AbKZfysGFzL7wJlByGT
+38AGQwIG/XD8cKnaiEMX4E/3Owbcoxwixo3WZC0CgYEAovaqJ9mEU+Jc8h/TS7PG
+bGPjN1Z/1V6zrlcFUnw/Vvrwb3HvHglsN8cLCaW6df5lPjC6tq4tNX8+fPnbg0Ak
+zWc+vQzl3ygxKGdqgcyBEKIJiPETgcoN+GzL02V3d+oKY3f2YXlBqVSsvi6UgUL9
+U3zuB36/IQVyAhrbUZFxoGkCgYEAnaFAO+Nvrp/LhXwZyGuQf+rkmipGTIMpil5t
+QzjtNMV5JFszSWPpyrl7A0Ew1YiG+I0GP2c3m+sY2TzbIiGrWH0b4cMKbw63Qy3V
+FqlpyjaCrpVKv56k/7jv883RzuQk56Uf1+szK5mrCFITy2oXsVZ0pA4lbjSaDTjA
+7D968V0CgYEA+qKqXKL98+c5CMPnpf+0B1x2zgyUym1ouPfon2x5fhK84T53zDMA
+zfdUJ/SOZw6/c9vRF7RL8h+ZfFdIyoAXv4Tt6mIiZe7P+AUVg6XgJ0ce2MUSeWjI
+W8D4WdSi0jyqr99TuVBWhbTZJviMB3pHqKaHQ07hnd/lPtvzsiH12qk=
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-ip-cn-and-alt-names.crt b/src/test/ssl/ssl/server-ip-cn-and-alt-names.crt
new file mode 100644
index 0000000000..2be02feb03
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-and-alt-names.crt
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDHTCCAgWgAwIBAgIIICIBBBQ2MQAwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UE
+Aww3VGVzdCBDQSBmb3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHNl
+cnZlciBjZXJ0czAeFw0yMjAxMDQyMjM2MzFaFw00OTA1MjIyMjM2MzFaMDQxHjAc
+BgNVBAsMFVBvc3RncmVTUUwgdGVzdCBzdWl0ZTESMBAGA1UEAwwJMTkyLjAuMi4x
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwmqTdQJfs2Ti9tPitYp2
+27I0HvL/kNSgA6egFr0foRo0BorwJNIzdbV0+EnsfiBNTWL5It26gqO7UP3ms8t2
+vHD5gkXfT+f6ts0lVJEcIOkUD/8ws4Ic9Y4uPqb4gN+pUKqcxtmLW1TYk84MBK59
+Xz4yPPS6N+G/DMMeFHTNkM9EQwn/+DC3fDsWdGYM2GRWDTJGg1A5tSUcF+seu7i1
+Vg7XajBfsvgAUAsrAxV+X/sLZh94HY+paD6wfaI99mY2OXVc/XW/z1r9WQznor65
+ZkonNCaPfavqPG5vqnab9AyQcqPqmX8hf/xrniASBAkqNCctbASrFCIYvCJfGfmX
+EQIDAQABoyUwIzAhBgNVHREEGjAYhwTAAAIChxAgAQ24AAAAAAAAAAAAAAABMA0G
+CSqGSIb3DQEBCwUAA4IBAQBf7kmYfRYfnWk1OUfY3N1kaNg9piBBlFr9g+OQn9KU
+zirkN7s0ZQbCGxV1uJQBKS58NyE414Vorau77379emgYDcCBpDIYpkLiNujVrIOr
+ggRFKsFRgxu4/mw0BSgCcV8RPe9SWHZ90Mos7TMCnW/PdxOCD1wD0YMkcs0rwB3l
+0Kzc7jDnfOEvmgw/Ysm7v67ps+05Uq5VskQ6WrpSAw6kPD/QMuuBAX8ATPczIaox
+zAMyncq1IiSIwG93f3EoQQThdQ70C6G9vLcu9TtL6JAsEMFEzR99gt1Wsqvmgl9W
+kStzj1yjIWeo5gIsa4Jgcke1lZviWyrTxHDfyunYE5i5
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-ip-cn-and-alt-names.key b/src/test/ssl/ssl/server-ip-cn-and-alt-names.key
new file mode 100644
index 0000000000..54fe80fc68
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-and-alt-names.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAwmqTdQJfs2Ti9tPitYp227I0HvL/kNSgA6egFr0foRo0Borw
+JNIzdbV0+EnsfiBNTWL5It26gqO7UP3ms8t2vHD5gkXfT+f6ts0lVJEcIOkUD/8w
+s4Ic9Y4uPqb4gN+pUKqcxtmLW1TYk84MBK59Xz4yPPS6N+G/DMMeFHTNkM9EQwn/
++DC3fDsWdGYM2GRWDTJGg1A5tSUcF+seu7i1Vg7XajBfsvgAUAsrAxV+X/sLZh94
+HY+paD6wfaI99mY2OXVc/XW/z1r9WQznor65ZkonNCaPfavqPG5vqnab9AyQcqPq
+mX8hf/xrniASBAkqNCctbASrFCIYvCJfGfmXEQIDAQABAoIBAB6GgVST5NbT9lbu
++d+rN/JSzqA1Yy8oU19/iEFJvJec96I3WnFNl8rZjN4XLUy4YarO6XMyAUDV2Gll
+FD4Sqjf4PRTZR7DSKaleGIhoqFP6hK3mUY091rIves9XhBkoBPunbipCqgDTF5ZN
+edGaXBECQP0VJ8/yX/7u++AWXthnjDis9X0taZfFg/PYbV7SCJ1Hg1O/wEsgXlnC
+7mbL6wkCW0f6700B0x1kKbZqJY95xRqp6Ipq2lIQbJDdGywoj0WzKqNltf9cer+r
+cXl8WjeiMvvvpl4uGhckAbzUifUzxN6A3f1fu/XKtOmabMi9t7J4MRfgOgedgtQB
+0jaZGSkCgYEA+lBLnNY6M48HX2mdtr86+n41gh69v8Z7oNikJFDZkodrvI8uqE0i
+0XwnYPFddt8NbmuUhhuzI2M8RKhGLgdlbKpkSSVafnMfcxRmX2EAtWQgdvX1Iult
+752LWdBgSuw2vlzvy3T/GYnjMrXSCGput4amqojMEbvUGvIdSUMdHGMCgYEAxtU1
+WixKPL6aEnYy1f4bybzcNgGtl8PBRz9xw+P46g+ijOPoaG9O73Tr7An11AO003Ot
+DHhMW+b8yHLyxoKwS2sU2cN/lKB8xNQYZc1D61RNJlzgnHMXnA0lcH0I3M35fqKr
+/71pD1ZP40SSJS+od/KEjW80XzuOdyiXg8q81vsCgYEAnUPLbbsuj+whzrFVlFZr
+IKwgxCK6Rn3WeIUEA4kEWUpZxvsSbk0gPgtJ1l9uwFt9Xc2bX/KRRv93Aw/SH+Mn
+tvEK1uXwCBgePzgm5W/VeSFyQCthm1CbcHtD7Oa9SPVFo65SPjrAd3QpWVfgoMb1
+zrp7hhMyW0XuCgvpmHjhFk8CgYEAxq/thXM2p+bLLWGhwQcRG5G299zLbBl4PUsf
+0uEvLi17gJCKADoiRdSvoAn/9eHSQ26XYRuhKkDzHxcGlOmpY2PYzRa3mXyZ0VIk
+Iy5wDWwLQCeVZ6D22cClRfgb8BF/nFTPzVmn72SPpgoyhChQj7PvUynpyrRH07jj
+VxYziBsCgYAFr37Xbl0VnXVK+XU+vMwUZjcF4jpoCr7SFZqgRbW2GbYSUoMuPXns
+RnJh+Fvi1NUei+E5s1H4P1pVq4p0jFxP4GvH/qvNjnIn/Er3bbqvpox6dWUJXprq
+qTQSDIeoDC/V8cyRoIfqPvTVqY8Rgew6GEkv0bAImdxhoSng7vIseg==
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.crt b/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.crt
new file mode 100644
index 0000000000..23c06da01c
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.crt
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDQzCCAiugAwIBAgIIICIBBBQ2MQEwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UE
+Aww3VGVzdCBDQSBmb3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHNl
+cnZlciBjZXJ0czAeFw0yMjAxMDQyMjM2MzFaFw00OTA1MjIyMjM2MzFaMDQxHjAc
+BgNVBAsMFVBvc3RncmVTUUwgdGVzdCBzdWl0ZTESMBAGA1UEAwwJMTkyLjAuMi4x
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8xddbo/x2TOSIa/br8BN
+o/URdTr9+l2R5YojiZKDuLxiQVkgC30PJ2/CNFKIh2nHhRrzknI6sETVtrxZ+9V2
+qRc1yShVu462u0DHPRMIZnZIOZg3hlNB0cRWbOglUKlttIARNEQUcTUyPOtyo4/v
++u0Ej5NTNcHFbFT01vdD9MjQiCO3jKdAwPIb14jTg4C71EpZ+LuelDo4DzF2/XgG
+WqUTrgD/XnBU/60PU9Iy3G0nVpx21q6ppn9G7a9R+i8FjBcwW1T+cfsBDWhAv+bi
+RmSAkENf8L8TwOlDQUwROkfz3Hz36vuJjdkreQJsiqL0HnrnH5T5G9UzJO86FvZQ
+5wIDAQABo0swSTBHBgNVHREEQDA+gh1kbnMxLmFsdC1uYW1lLnBnLXNzbHRlc3Qu
+dGVzdIIdZG5zMi5hbHQtbmFtZS5wZy1zc2x0ZXN0LnRlc3QwDQYJKoZIhvcNAQEL
+BQADggEBAF+mfaw6iBPzpCgqq830pHRa3Yzm1aezt8SkeRohUYHNv/yCnDSRaqtj
+xbENih3lJMSTBL3g0wtTOHfH8ViC/h+lvYELHzXKic7gkjV7H5XETKGr0ZsjBBT2
+4cZQKbD9e0x0HrENXMYgGpBf747qL6uTOVJdG0s15hwpLq47bY5WUjXathejbpxW
+prmF8F+xaC52N9P/1VnqguQB909F4x1pyOK7D7tjFu+Y8Je7PHKbb6WY5K6xAv6t
+R17CY0749/FotlphquElUR2bs5Zzv5YrjUHPTcbwKvcH5cdNi93/u6NJt2xNAoYf
+aZERhX5TA9DYk4gC8OY0yGaYCIj3Dd4=
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.key b/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.key
new file mode 100644
index 0000000000..0ace41e0a1
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEogIBAAKCAQEA8xddbo/x2TOSIa/br8BNo/URdTr9+l2R5YojiZKDuLxiQVkg
+C30PJ2/CNFKIh2nHhRrzknI6sETVtrxZ+9V2qRc1yShVu462u0DHPRMIZnZIOZg3
+hlNB0cRWbOglUKlttIARNEQUcTUyPOtyo4/v+u0Ej5NTNcHFbFT01vdD9MjQiCO3
+jKdAwPIb14jTg4C71EpZ+LuelDo4DzF2/XgGWqUTrgD/XnBU/60PU9Iy3G0nVpx2
+1q6ppn9G7a9R+i8FjBcwW1T+cfsBDWhAv+biRmSAkENf8L8TwOlDQUwROkfz3Hz3
+6vuJjdkreQJsiqL0HnrnH5T5G9UzJO86FvZQ5wIDAQABAoIBAGv0BFoFMrHyZQLw
+xe7Wx6P4QTh+aiu1QgVdw0pk9nojrr62hbSUZRZuWyBBRsBcCW7i+Sgf8lA1QXNV
+UeC0e228EPa0It6YEi42JkTJHwHhpVFud7n/X0t4lajnryqTE1UFSp6bXTipFxZW
+uSJJ2ZjliRD5rApDcxkY4WJVjKg3aEt7P/DiM8iKGfyE6stq72VjEbJjdViMEcOP
+BNf0TiREZz5Mp7jAVWhpen0ebbLOBVWV4/ONNcL+yqR4mCEDUSFGewrTVX4zHL0A
+hYk198C5F8sFvEDnFkPco9sXMVanmLoI8sbhP4IIz9g4+GU6kFuj7fUKp11Azqv+
+3WQDKYECgYEA/XG4mmG/g8FG44y42mfZpUXWi1pwU4CQIrhkoU5j7EPQrvRboOOE
+Rv95jSwyZu4vCqjyI5FN1jCGTdhmt++R1e//zH6Hqa9Smo+jw7DtAFrCYd1JnCf1
+ToOwsYPHv4P7A8q8kc5vCNIv+AQSlP/wqdVNo3grdf7cGXkMtEY4F9UCgYEA9Yrq
+zWdnNGPATuSBqL6TSjQ37oR+dBD6WnGsiDenQkOzyDPFZ3CT1DjJghjEtxc8EfNf
+Oo8dMMR2q+5FZQo7WuqONEgyzKePiNR8RK2gOYpgdjN9bih1sAhHR10D26cpwlDJ
+bx7D5ZzENLbdZmfEiWwKswnaIhN4yMalgE0mP8sCgYAhzJy12ftUct4lUosEdX0N
+EXc/NlxshmSyfKzO5kllJNYbvvLJTg5B+agYL6C5IWKcpVNFcwdSXT5L+2QXe5eT
+VGJkvysQchUuD6HjYyD4PyJVMtGyRZHtWpqh0dU9sTg0lUD4oPMl1gIXrVNdE5Tg
+0VV9S3VgUxC/ROlw0TyB0QKBgGsVE0NS9hJF8mc1hko2GnwA++d8Rr2NbfElo+2f
+/8SJTA1ibpOm6AFkZpTjAl8qtdrKPVyHb16GP47Jkd/3r1z979hjKCxSYul0aWF2
+KusNKvZBjFEPOgv0AEniCb2wUCjbHI3mZ95qGLM4kKOJW4/m21+rS0MTJNjCsQic
+HLMzAoGAeCsY09d3m8xGeU+DuTPC6GH7Sgy/NBYqS5VaVNjb2jnuZlW2SSW2oiID
+4tXTi4ruKmHC898BfyFxhSMqub+tg3pVqIYADC71rnJLrVyc1SzoWzL7yMT3qFj7
+C7ZYZYmfG9agcZb5NkqKPTfCxkBhWbdgTTgBKVO/xQst8EUgko8=
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-ip-cn-only.crt b/src/test/ssl/ssl/server-ip-cn-only.crt
new file mode 100644
index 0000000000..9bf015cf18
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-only.crt
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC8TCCAdkCCCAhESkRN1IAMA0GCSqGSIb3DQEBCwUAMEIxQDA+BgNVBAMMN1Rl
+c3QgQ0EgZm9yIFBvc3RncmVTUUwgU1NMIHJlZ3Jlc3Npb24gdGVzdCBzZXJ2ZXIg
+Y2VydHMwHhcNMjExMTI5MTkzNzUyWhcNNDkwNDE2MTkzNzUyWjA0MR4wHAYDVQQL
+DBVQb3N0Z3JlU1FMIHRlc3Qgc3VpdGUxEjAQBgNVBAMMCTE5Mi4wLjIuMTCCASIw
+DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANWs1uUL71nHYF9Zj6p+M3MpYDvx
+32iCjVdtH5a2qpSWHXTg0rR8dLX0y92cvOYvMXHRajZT1avpHr8dooPYSVaXpGMK
+NvF/Qi+WFYovRbP2vmd1yv1cgW/FggbwJFWVobizIz4seyA4d0B2j9fqoi2OFBNP
+huW664SjF0u3p21tDy+43i2LNUMAKf6dnRR5Vqenath87LEU41tSLudu6NXgbFMk
+jvfNkl4d0w7YCzeXmklmSI+uaX3PlJJ4NzQO2j8w5BvnKVhNVD0KjgrXZ6nB/8F7
+Pg3XY+d7rJlwRgXemU6resWQDJ7+UaC9u7I4EIP+9lzCR/nNBqUktpHRmHUCAwEA
+ATANBgkqhkiG9w0BAQsFAAOCAQEAos1JncV8Yf4UaKl6h1GdYtcVtzFyJvBEnhRD
+07ldL+TYnfZiX8wK2ssBtM3cg/C78y5bzdUa5XGS83ZKQJFFdhE7PSnrvyNqyIqY
+ZgNBxto3gyvir+EjO1u9BAB0NP3r3gYoHRDZS1xOPPzt4WgjuUgTLM9k82GsqAbO
+UrOTOdRnkIqC5xLpa05EnRyJPRsR1w1PRJC2XXKnHIuFjMb4v7UuPwyCcX1P5ioc
+rQszQcORy/L+k0ezCkyweORg68htjYbBHuwOuiGfok6yKKDMzrTvD3lIslls6eX7
+4sI3XWqzkPmG9Vsxm9Vu9/Ma+PRO76VyCoIwBd+Ufg5vNXhMmw==
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-ip-cn-only.key b/src/test/ssl/ssl/server-ip-cn-only.key
new file mode 100644
index 0000000000..1966530e72
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-only.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEA1azW5QvvWcdgX1mPqn4zcylgO/HfaIKNV20flraqlJYddODS
+tHx0tfTL3Zy85i8xcdFqNlPVq+kevx2ig9hJVpekYwo28X9CL5YVii9Fs/a+Z3XK
+/VyBb8WCBvAkVZWhuLMjPix7IDh3QHaP1+qiLY4UE0+G5brrhKMXS7enbW0PL7je
+LYs1QwAp/p2dFHlWp6dq2HzssRTjW1Iu527o1eBsUySO982SXh3TDtgLN5eaSWZI
+j65pfc+Ukng3NA7aPzDkG+cpWE1UPQqOCtdnqcH/wXs+Dddj53usmXBGBd6ZTqt6
+xZAMnv5RoL27sjgQg/72XMJH+c0GpSS2kdGYdQIDAQABAoIBAQDNXviU4WnF8rmQ
+K7bH+dBdqbETLKC8BG7xTrMD2sINWlMpmUUrsEtE7+paMGHnJAj0CoF5gg5m0wN4
+UXV4H5QtpEad4p14dAYbUreVP2ZRWKEdM7xM1HKcCUu2e22QzObJbXQ8N+iHyX3k
++Y+7yYrjGiH1hYR0nbnsnAyx++zyYBSQeqzpdQwf/BLY5xZmyYWNfqbckiMpEqMs
+EmZmGXnCjIipzEC0LQHoSW9PNa92Z9bvuxOKYl8iHYDDXjvMRFoZBSiMXpzHQocb
+QlQ5F4ayfW2OrOhpNbY7niYM9GN3Bk9TgMP+0BkJE6uuktLYW35LY1M78CCPWcWb
+npJNK3QBAoGBAOxkGrhAHAysSmtirIyMdvySb76wb/Ukfi+AULKz20FI5j4/GXm9
+qCb2GeT+FFSUHeSC8f0EFnosRYkdBGruqeZioI+5rUkboYFJPspAHAuvg9kgtfF+
+kvphD4O4P/foYsEZRx66FHozDbhrrR5UXc7KzqRIASc/D3FOx2UFJLb1AoGBAOdm
+WcaMvYygl9ZW+ThWAR1xG1X70AGKwrlrpF2hBkWYxSurxSMXnD0DUzC9Nb4EyCaM
+c2uSqEZOKdW+XfXtK2DnqXKfb3YCVEoGN4gVfyuW/vxii/+ZxLo3md/b3vrkZEVp
+pfkXy/HoZ71YN7bNpcDpOnhml6vvuCRCYFnI1WuBAoGAC0shB6pwbJ6Sk5zMN47C
+ZICufAK75o9OxAAyWsdC81SDQ3gKRImuDeZ2CD2nRP8qim9DFl5qoH2a+Nj9DArI
+7SvLFfK9958tURrpuAnmDRzehLIOXzI33WRjtFxKGhLtHOKTRkGHlur3fdcPF0La
+lHWV971E6NYXa8diuU3Mmj0CgYBYd+ka3/QYL83dRKNDxp3mg7fPx9ZewI5yFZVh
+to6PTTkU2Tclk4FIUl0b5TsGyw06r7fxCMENIBUegwmpXGOZSPifuhUDKSDQrE/O
+12knYTNbitG7hy6Pg3JxA77cbTVo1FuAQHjYo+IFohSq7zTP7FtObOrP8XaVZksw
+CHiQAQKBgBW4EiA9AAnZ1LOpifAvM7bs0NHg95qTwtAL52WKom2ga2H+lMhxeu6Y
+hUSytC/f9kALVcYloZhkLYpO07x1gXmy7f4parMjA4Ex+4vfu3kPd8GiNGZ+AUJD
+nnJ1OINY9ziXJZfju7FpVWpkiuPzWCh6y/o3gZ/veq5mIUxuDMVa
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-ip-in-dnsname.crt b/src/test/ssl/ssl/server-ip-in-dnsname.crt
new file mode 100644
index 0000000000..78ad8d99c8
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-in-dnsname.crt
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC/DCCAeSgAwIBAgIIICIDFRVYUgAwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UE
+Aww3VGVzdCBDQSBmb3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHNl
+cnZlciBjZXJ0czAeFw0yMjAzMTUyMjU4NTJaFw00OTA3MzEyMjU4NTJaMCAxHjAc
+BgNVBAsMFVBvc3RncmVTUUwgdGVzdCBzdWl0ZTCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBAMpn5bP1/OfBQR/yvOkOBzxArE1j1YShVa2pcj896+CVDEgV
+N5Hluz7KHU/JYzNZCAHb5WAHuvXxKeoj4Ti5be1KsqO0mN1p+RMN7VlCpCpb0AWT
+z4z+I8TUhSZnmgghHvfW4RfcZMCcHq1vevVTDxR/cAbDPYpgBCD5F/SZMRyMDw5B
+7ILLmft0eqA1nCqavyqBCGZvx1ol8N5BfVdrDXp/rN5997khBWQRZ8g84FZyFZXf
+pwp57eu0OGQDzZFXoEL2t4OVld67K5jcclWVxHY6FGcHjCvyqs48PCPOR84anZwj
+GsqVOS6250/DWKBQO4KyhkTVf0AW/ICGSMOKkAkCAwEAAaMYMBYwFAYDVR0RBA0w
+C4IJMTkyLjAuMi4xMA0GCSqGSIb3DQEBCwUAA4IBAQDIAAH0WJKEpbPN0QihN6SF
+UA5WL4ixsBACo9OIAGkSnKeOeVEG5vvgOna0hjQcOcgtI1oCDLhULcjCuwxiIW6y
+QntOazyo0sooJr0hEm2WfipvIpQs6W9E1OTcs624BAVfkAwr6WT2VwoIAPcQD2nR
+tIQhSUIR9J7Q5WbzuQw7pthQhBfW/UPWw7vajel0r1dflbe0Cgp5WGNfp1kYy+Qf
+XW/YjkstZEP1KFm+TF58uxrIDmYboS8EerUREGQixijbI0AfXjShxtiyS63rbdpo
+3C0BPj9Yx2VtWi4U0qoef/iLJxJBCLvE/97+duPdKx0AkkOWA9VuenkWLp797UM8
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-ip-in-dnsname.key b/src/test/ssl/ssl/server-ip-in-dnsname.key
new file mode 100644
index 0000000000..ba319b001e
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-in-dnsname.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAymfls/X858FBH/K86Q4HPECsTWPVhKFVralyPz3r4JUMSBU3
+keW7PsodT8ljM1kIAdvlYAe69fEp6iPhOLlt7Uqyo7SY3Wn5Ew3tWUKkKlvQBZPP
+jP4jxNSFJmeaCCEe99bhF9xkwJwerW969VMPFH9wBsM9imAEIPkX9JkxHIwPDkHs
+gsuZ+3R6oDWcKpq/KoEIZm/HWiXw3kF9V2sNen+s3n33uSEFZBFnyDzgVnIVld+n
+Cnnt67Q4ZAPNkVegQva3g5WV3rsrmNxyVZXEdjoUZweMK/Kqzjw8I85HzhqdnCMa
+ypU5LrbnT8NYoFA7grKGRNV/QBb8gIZIw4qQCQIDAQABAoIBAA2kPP4JCTeRddMy
+Z/sJIAG2liZNITnkKcMflXyfrsMfKIm/LFSf+CO+OYWEHDR8vqZpbKcxPi+PRnTq
+YCaTkM4aZ7nS1S6vEsNu/90xOaFFONr3YFivVDfS3vp8pwv/N3gaumcCSqQUoZis
+18urAmwuPp2mEQK/f+e9AhlRLdcvlqDyKm+zMrVixK77Hj5JiEkh3rfZ3onHHKGE
+B7T2XRRqnZ4FCN9qLH2pMGUknZ4MGC9SlCyoerXFodb4DhKWQhJDRLjb8qP96r/E
+FGSg5WUiAERU/OgODoqZNTeIwIDB/f9NK45dEY3Hw6BsSFfU2VChrlNoVlzFUx2k
+yaH5Y4ECgYEA8rht3crh3GTy0jBJjNqB2iul8fkG/uiaiSvERWT/+KZnmV1+JGAW
+h2/wvd5apagOJjqKY0bCHMei/qYF9r4yJnkIy4qNper3QUz7TMCjsWduCm8S834A
+Z+Vwi3RBGJiQQH9Dfexko5sDjo+w5g4RsH52INCeReInNdxHOv06jZECgYEA1XrR
+QNwZlxHt3H93YKmKDZXikqW12Cuq6RSwf5VVdeuzV+pUN+/JaSgEuYsBilW7Q5p2
+gPROi0l8/eUPsBJb+dh1BcGzSjI2Kkzf66QOTG83S7tCPwQhwJUAylFuADvURjPQ
+qvqNjbQUomdm2QjBzyWtiFbolqxBgM3dnE6R/vkCgYBYGqQexx83LhmKPGbmTwal
+mARzkg59BxfZRN7IxcG4k0a1v98i+xISdYqwkP7cdOU18Tf8k1mwsrKytrcheqaf
+mn2bzJ5gJKs9s+DgWmjQ45dpCCqb4hfpnro8lKVwdSifkNKB6gYZ8RHYdMYkq+S1
+6SGeBbv95/qNrXjZq8POUQKBgHyaDwD4dsdCY79LdvYofrenQHOv3Q+rjTo2JT6S
+fysww6EQ2M89WiXSgc96Xw/LMl4nDfv+nMmXvyjCRgHS9XRC7yrJAEjSPeM6s4fq
+XZ4nW/ML/YKiesDZN3jfRoFEaoX/QFBLpcuLzG9uQw1ymwy5RSxK7b7kE+eGQU82
+XOihAoGBAI3xvT9fG3jRsSuw/8OQBlmDUFZcT0fRPRZ3pg8XlSreAam4b607d2WY
+u/bBHIclG3CLJ2EFqBtxl9AQeM0OTweF0KmV3dbtdBmaTbnhbK8/NLYnl5+aosEJ
+YrFKD8k8z6z+mYQs+7bAnfRa53TjfC7f24BpgEQyEfKL2fa3PF+J
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/sslfiles.mk b/src/test/ssl/sslfiles.mk
index 7ed3a30f5c..d6a3870079 100644
--- a/src/test/ssl/sslfiles.mk
+++ b/src/test/ssl/sslfiles.mk
@@ -22,9 +22,15 @@
 # key/certificate pair will be generated for you, signed by the appropriate CA.
 #
 SERVERS := server-cn-and-alt-names \
+	server-cn-and-ip-alt-names \
 	server-cn-only \
+	server-ip-cn-only \
+	server-ip-cn-and-alt-names \
+	server-ip-cn-and-dns-alt-names \
+	server-ip-in-dnsname \
 	server-single-alt-name \
 	server-multiple-alt-names \
+	server-ip-alt-names \
 	server-no-names \
 	server-revoked
 CLIENTS := client client-dn client-revoked client_ext
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 5c5b16fbe7..07e5b71d8c 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -249,6 +249,30 @@ $node->connect_fails(
 	  qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/
 );
 
+# Test with an IP address in the Common Name. This is a strange corner case that
+# nevertheless is supported, as long as the address string matches exactly.
+switch_server_cert($node, 'server-ip-cn-only');
+
+$common_connstr =
+  "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+
+$node->connect_ok("$common_connstr host=192.0.2.1",
+	"IP address in the Common Name");
+
+$node->connect_fails(
+	"$common_connstr host=192.000.002.001",
+	"mismatch between host name and server certificate IP address",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.1" does not match host name "192.000.002.001"\E/
+);
+
+# Similarly, we'll also match an IP address in a dNSName SAN. (This is
+# long-standing behavior.)
+switch_server_cert($node, 'server-ip-in-dnsname');
+
+$node->connect_ok("$common_connstr host=192.0.2.1",
+	"IP address in a dNSName");
+
 # Test Subject Alternative Names.
 switch_server_cert($node, 'server-multiple-alt-names');
 
@@ -301,7 +325,54 @@ $node->connect_fails(
 	  qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/
 );
 
-# Test server certificate with a CN and SANs. Per RFCs 2818 and 6125, the CN
+# Test certificate with IP addresses in the SANs.
+switch_server_cert($node, 'server-ip-alt-names');
+
+$node->connect_ok(
+	"$common_connstr host=192.0.2.1",
+	"host matching an IPv4 address (Subject Alternative Name 1)");
+
+$node->connect_ok(
+	"$common_connstr host=192.000.002.001",
+	"host matching an IPv4 address in alternate form (Subject Alternative Name 1)");
+
+$node->connect_fails(
+	"$common_connstr host=192.0.2.2",
+	"host not matching an IPv4 address (Subject Alternative Name 1)",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.1" (and 1 other name) does not match host name "192.0.2.2"\E/
+);
+
+$node->connect_fails(
+	"$common_connstr host=192.0.2.1/32",
+	"IPv4 host with CIDR mask does not match",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.1" (and 1 other name) does not match host name "192.0.2.1\/32"\E/
+);
+
+$node->connect_ok(
+	"$common_connstr host=2001:DB8::1",
+	"host matching an IPv6 address (Subject Alternative Name 2)");
+
+$node->connect_ok(
+	"$common_connstr host=2001:db8:0:0:0:0:0:1",
+	"host matching an IPv6 address in alternate form (Subject Alternative Name 2)");
+
+$node->connect_fails(
+	"$common_connstr host=::1",
+	"host not matching an IPv6 address (Subject Alternative Name 2)",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.1" (and 1 other name) does not match host name "::1"\E/
+);
+
+$node->connect_fails(
+	"$common_connstr host=2001:DB8::1/128",
+	"IPv6 host with CIDR mask does not match",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.1" (and 1 other name) does not match host name "2001:DB8::1\/128"\E/
+);
+
+# Test server certificate with a CN and DNS SANs. Per RFCs 2818 and 6125, the CN
 # should be ignored when the certificate has both.
 switch_server_cert($node, 'server-cn-and-alt-names');
 
@@ -319,6 +390,39 @@ $node->connect_fails(
 	  qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/
 );
 
+# But we will fall back to check the CN if the SANs contain only IP addresses.
+switch_server_cert($node, 'server-cn-and-ip-alt-names');
+
+$node->connect_ok("$common_connstr host=common-name.pg-ssltest.test",
+	"certificate with both a CN and IP SANs matches CN");
+$node->connect_ok("$common_connstr host=192.0.2.1",
+	"certificate with both a CN and IP SANs matches SAN 1");
+$node->connect_ok("$common_connstr host=2001:db8::1",
+	"certificate with both a CN and IP SANs matches SAN 2");
+
+# And now the same tests, but with IP addresses and DNS names swapped.
+switch_server_cert($node, 'server-ip-cn-and-alt-names');
+
+$node->connect_ok("$common_connstr host=192.0.2.2",
+	"certificate with both an IP CN and IP SANs 1");
+$node->connect_ok("$common_connstr host=2001:db8::1",
+	"certificate with both an IP CN and IP SANs 2");
+$node->connect_fails(
+	"$common_connstr host=192.0.2.1",
+	"certificate with both an IP CN and IP SANs ignores CN",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.2" (and 1 other name) does not match host name "192.0.2.1"\E/
+);
+
+switch_server_cert($node, 'server-ip-cn-and-dns-alt-names');
+
+$node->connect_ok("$common_connstr host=192.0.2.1",
+	"certificate with both an IP CN and DNS SANs matches CN");
+$node->connect_ok("$common_connstr host=dns1.alt-name.pg-ssltest.test",
+	"certificate with both an IP CN and DNS SANs matches SAN 1");
+$node->connect_ok("$common_connstr host=dns2.alt-name.pg-ssltest.test",
+	"certificate with both an IP CN and DNS SANs matches SAN 2");
+
 # Finally, test a server certificate that has no CN or SANs. Of course, that's
 # not a very sensible certificate, but libpq should handle it gracefully.
 switch_server_cert($node, 'server-no-names');
-- 
2.25.1

v10-0003-squash-libpq-allow-IP-address-SANs-in-server-cer.patchtext/x-patch; name=v10-0003-squash-libpq-allow-IP-address-SANs-in-server-cer.patchDownload
From 1a070a1ac821c8eda4d3a2856e2721b2fa97aaa1 Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Wed, 5 Jan 2022 15:47:03 -0800
Subject: [PATCH v10 3/3] squash! libpq: allow IP address SANs in server certs

Per review, provide a pg_inet_pton() interface for IPv6 and refactor the
internals accordingly. IPv4 is not yet implemented. The call sites that
just want standard addresses without a CIDR mask have been updated to
use the new function.

Internally, the IPv6 parser now takes an allow_cidr boolean. I've
plumbed that all the way down to getbits() in order to minimize the
amount of code touched, at the expense of an unnecessary function call
in the failing case.
---
 src/include/port.h                       |  1 +
 src/interfaces/libpq/fe-secure-common.c  |  7 +--
 src/interfaces/libpq/fe-secure-openssl.c |  2 +-
 src/port/inet_net_pton.c                 | 66 ++++++++++++++++++------
 4 files changed, 52 insertions(+), 24 deletions(-)

diff --git a/src/include/port.h b/src/include/port.h
index 2852e5b58b..fd046f4c24 100644
--- a/src/include/port.h
+++ b/src/include/port.h
@@ -517,6 +517,7 @@ extern char *pg_inet_net_ntop(int af, const void *src, int bits,
 
 /* port/inet_net_pton.c */
 extern int	pg_inet_net_pton(int af, const char *src, void *dst, size_t size);
+extern int	pg_inet_pton(int af, const char *src, void *dst);
 
 /* port/pg_strong_random.c */
 extern void pg_strong_random_init(void);
diff --git a/src/interfaces/libpq/fe-secure-common.c b/src/interfaces/libpq/fe-secure-common.c
index 2c0af62afe..9c408df369 100644
--- a/src/interfaces/libpq/fe-secure-common.c
+++ b/src/interfaces/libpq/fe-secure-common.c
@@ -211,12 +211,7 @@ pq_verify_peer_name_matches_certificate_ip(PGconn *conn,
 
 		family = PGSQL_AF_INET6;
 
-		/*
-		 * pg_inet_net_pton() will accept CIDR masks, which we don't want to
-		 * match, so skip the comparison if the host string contains a slash.
-		 */
-		if (!strchr(host, '/')
-			&& pg_inet_net_pton(PGSQL_AF_INET6, host, addr, -1) == 128)
+		if (pg_inet_pton(PGSQL_AF_INET6, host, addr) == 1)
 		{
 			if (memcmp(ipdata, addr, iplen) == 0)
 				match = 1;
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index 7656c3a75e..b70bd2eb19 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -555,7 +555,7 @@ is_ip_address(const char *host)
 	unsigned char	dummy6[16];
 
 	return inet_aton(host, &dummy4)
-		|| (pg_inet_net_pton(PGSQL_AF_INET6, host, dummy6, -1) == 128);
+		|| (pg_inet_pton(PGSQL_AF_INET6, host, dummy6) == 1);
 }
 
 /*
diff --git a/src/port/inet_net_pton.c b/src/port/inet_net_pton.c
index 5bae811c6f..ae0d1fd2e3 100644
--- a/src/port/inet_net_pton.c
+++ b/src/port/inet_net_pton.c
@@ -42,8 +42,8 @@ static const char rcsid[] = "Id: inet_net_pton.c,v 1.4.2.3 2004/03/17 00:40:11 m
 
 static int	inet_net_pton_ipv4(const char *src, u_char *dst);
 static int	inet_cidr_pton_ipv4(const char *src, u_char *dst, size_t size);
-static int	inet_net_pton_ipv6(const char *src, u_char *dst);
-static int	inet_cidr_pton_ipv6(const char *src, u_char *dst, size_t size);
+static int	inet_pton_ipv6_internal(const char *src, u_char *dst, size_t size,
+									bool allow_cidr);
 
 
 /*
@@ -74,9 +74,9 @@ pg_inet_net_pton(int af, const char *src, void *dst, size_t size)
 				inet_net_pton_ipv4(src, dst) :
 				inet_cidr_pton_ipv4(src, dst, size);
 		case PGSQL_AF_INET6:
-			return size == -1 ?
-				inet_net_pton_ipv6(src, dst) :
-				inet_cidr_pton_ipv6(src, dst, size);
+			return inet_pton_ipv6_internal(src, dst,
+										   (size == -1) ? 16 : size,
+										   true /* allow CIDR */);
 		default:
 			errno = EAFNOSUPPORT;
 			return -1;
@@ -352,13 +352,22 @@ emsgsize:
 }
 
 static int
-getbits(const char *src, int *bitsp)
+getbits(const char *src, int *bitsp, bool allow_cidr)
 {
 	static const char digits[] = "0123456789";
 	int			n;
 	int			val;
 	char		ch;
 
+	if (!allow_cidr)
+	{
+		/*
+		 * If we got here, the top-level call is to pg_inet_pton() and the
+		 * caller supplied a CIDR mask, which isn't allowed.
+		 */
+		return 0;
+	}
+
 	val = 0;
 	n = 0;
 	while ((ch = *src++) != '\0')
@@ -385,7 +394,7 @@ getbits(const char *src, int *bitsp)
 }
 
 static int
-getv4(const char *src, u_char *dst, int *bitsp)
+getv4(const char *src, u_char *dst, int *bitsp, bool allow_cidr)
 {
 	static const char digits[] = "0123456789";
 	u_char	   *odst = dst;
@@ -416,7 +425,7 @@ getv4(const char *src, u_char *dst, int *bitsp)
 				return 0;
 			*dst++ = val;
 			if (ch == '/')
-				return getbits(src, bitsp);
+				return getbits(src, bitsp, allow_cidr);
 			val = 0;
 			n = 0;
 			continue;
@@ -431,18 +440,12 @@ getv4(const char *src, u_char *dst, int *bitsp)
 	return 1;
 }
 
-static int
-inet_net_pton_ipv6(const char *src, u_char *dst)
-{
-	return inet_cidr_pton_ipv6(src, dst, 16);
-}
-
 #define NS_IN6ADDRSZ 16
 #define NS_INT16SZ 2
 #define NS_INADDRSZ 4
 
 static int
-inet_cidr_pton_ipv6(const char *src, u_char *dst, size_t size)
+inet_pton_ipv6_internal(const char *src, u_char *dst, size_t size, bool allow_cidr)
 {
 	static const char xdigits_l[] = "0123456789abcdef",
 				xdigits_u[] = "0123456789ABCDEF";
@@ -510,13 +513,13 @@ inet_cidr_pton_ipv6(const char *src, u_char *dst, size_t size)
 			continue;
 		}
 		if (ch == '.' && ((tp + NS_INADDRSZ) <= endp) &&
-			getv4(curtok, tp, &bits) > 0)
+			getv4(curtok, tp, &bits, allow_cidr) > 0)
 		{
 			tp += NS_INADDRSZ;
 			saw_xdigit = 0;
 			break;				/* '\0' was seen by inet_pton4(). */
 		}
-		if (ch == '/' && getbits(src, &bits) > 0)
+		if (ch == '/' && getbits(src, &bits, allow_cidr) > 0)
 			break;
 		goto enoent;
 	}
@@ -530,6 +533,9 @@ inet_cidr_pton_ipv6(const char *src, u_char *dst, size_t size)
 	if (bits == -1)
 		bits = 128;
 
+	/* Without a CIDR mask, the above should guarantee a complete address. */
+	Assert(allow_cidr || bits == 128);
+
 	endp = tmp + 16;
 
 	if (colonp != NULL)
@@ -568,3 +574,29 @@ emsgsize:
 	errno = EMSGSIZE;
 	return -1;
 }
+
+/*
+ * Converts a network address in presentation format to network format. The main
+ * difference between this and pg_inet_net_pton() above is that CIDR notation is
+ * not allowed.
+ *
+ * The memory pointed to by dst depends on the address family:
+ *
+ *     PGSQL_AF_INET:  not implemented yet (returns -1 with EAFNOSUPPORT)
+ *     PGSQL_AF_INET6: *dst must be a u_char[16]
+ *
+ * Over and above the standard inet_pton() functionality, we also set errno on
+ * parse failure, with the same meanings as with pg_inet_net_pton().
+ */
+int
+pg_inet_pton(int af, const char *src, void *dst)
+{
+	if (af != PGSQL_AF_INET6)
+	{
+		/* Currently only IPv6 is implemented. */
+		errno = EAFNOSUPPORT;
+		return -1;
+	}
+
+	return (inet_pton_ipv6_internal(src, dst, 16, false /* no CIDR */) == 128);
+}
-- 
2.25.1

#33Kyotaro Horiguchi
horikyota.ntt@gmail.com
In reply to: Jacob Champion (#32)
Re: [PATCH] Accept IP addresses in server certificate SANs

At Wed, 23 Mar 2022 23:52:06 +0000, Jacob Champion <pchampion@vmware.com> wrote in

On Wed, 2022-03-23 at 14:20 +0900, Kyotaro Horiguchi wrote:

I tried to write out the doc part. What do you think about it?

I like it, thanks! I've applied that in v10, with a tweak to two
iPAddress spellings and a short expansion of the condition in the Note,
and I've added you as a co-author to 0002.

I'm fine with it. Thanks. I marked it as Ready-for-Commiter.

Note for the patch set:

0001 is preliminary patch to move inet_pton out of src/backend tree.

0002 is the main patch of this patchset

0003 is optional, which introduces pg_inet_pton() only works for IPv6
addresses. 0002 gets the same effect by the following use of
pg_inet_net_pton().

if (!strchr(host, '/')
&& pg_inet_net_pton(PGSQL_AF_INET6, host, addr, -1) == 128)

regards.

--
Kyotaro Horiguchi
NTT Open Source Software Center

#34Jacob Champion
pchampion@vmware.com
In reply to: Kyotaro Horiguchi (#33)
Re: [PATCH] Accept IP addresses in server certificate SANs

On Thu, 2022-03-24 at 17:10 +0900, Kyotaro Horiguchi wrote:

I'm fine with it. Thanks. I marked it as Ready-for-Commiter.

Thank you for the reviews and feedback!

--Jacob

#35Tom Lane
tgl@sss.pgh.pa.us
In reply to: Jacob Champion (#32)
Re: [PATCH] Accept IP addresses in server certificate SANs

Jacob Champion <pchampion@vmware.com> writes:

[ v10-0001-Move-inet_net_pton-to-src-port.patch etc ]

There is something broken about the ssl tests as modified by
this patch. The cfbot doesn't provide a lot of evidence about
why it's failing, but I applied the patchset locally and what
I see is

...
ok 47 - mismatch between host name and server certificate sslmode=verify-full: m
atches
Odd number of elements in hash assignment at /home/postgres/pgsql/src/test/ssl/t
/SSL/Server.pm line 288.
Use of uninitialized value in concatenation (.) or string at /home/postgres/pgsq
l/src/test/ssl/t/SSL/Backend/OpenSSL.pm line 178.
Use of uninitialized value in concatenation (.) or string at /home/postgres/pgsq
l/src/test/ssl/t/SSL/Backend/OpenSSL.pm line 178.
### Restarting node "primary"
# Running: pg_ctl -w -D /home/postgres/pgsql/src/test/ssl/tmp_check/t_001_ssltes
ts_primary_data/pgdata -l /home/postgres/pgsql/src/test/ssl/tmp_check/log/001_ss
ltests_primary.log restart
waiting for server to shut down.... done
server stopped
waiting for server to start.... stopped waiting
pg_ctl: could not start server

The tail end of the server log is

2022-03-27 17:13:11.482 EDT [551720] FATAL: could not load server certificate file ".crt": No such file or directory

so it seems pretty clear that something's fouling up computation of
a certificate file name. This may be caused by 9ca234bae or
4a7e964fc.

regards, tom lane

#36Daniel Gustafsson
daniel@yesql.se
In reply to: Tom Lane (#35)
Re: [PATCH] Accept IP addresses in server certificate SANs

On 27 Mar 2022, at 23:19, Tom Lane <tgl@sss.pgh.pa.us> wrote:

This may be caused by 9ca234bae or 4a7e964fc.

I'd say 4a7e964fc is the culprit here. From a quick skim the the
switch_server_cert() calls need to be changed along the lines of:

from: switch_server_cert($node, 'server-ip-in-dnsname');
to: switch_server_cert($node, certfile => 'server-ip-in-dnsname');

There migth be more changes required, that was the one that stood out. Unless
someone beats me to it I'll take a look at fixing up the test in this patch
tomorrow.

--
Daniel Gustafsson https://vmware.com/

#37Andres Freund
andres@anarazel.de
In reply to: Tom Lane (#35)
Re: [PATCH] Accept IP addresses in server certificate SANs

Hi,

On 2022-03-27 17:19:27 -0400, Tom Lane wrote:

The cfbot doesn't provide a lot of evidence about
why it's failing, but I applied the patchset locally and what
I see is

FWIW - and I agree that's not nice user interface wise - just below the cpu /
memory graphs there's a "directory browser", allowing you to navigate to the
saved log files. Navigating to log/src/test/ssl/tmp_check/log allows you to
download
https://api.cirrus-ci.com/v1/artifact/task/5261015175659520/log/src/test/ssl/tmp_check/log/regress_log_001_ssltests
https://api.cirrus-ci.com/v1/artifact/task/5261015175659520/log/src/test/ssl/tmp_check/log/001_ssltests_primary.log

Greetings,

Andres Freund

#38Daniel Gustafsson
daniel@yesql.se
In reply to: Daniel Gustafsson (#36)
4 attachment(s)
Re: [PATCH] Accept IP addresses in server certificate SANs

On 28 Mar 2022, at 00:44, Daniel Gustafsson <daniel@yesql.se> wrote:

I'll take a look at fixing up the test in this patch tomorrow.

Fixing up the switch_server_cert() calls and using default_ssl_connstr makes
the test pass for me. The required fixes are in the supplied 0004 diff, I kept
them separate to allow the original author to incorporate them without having
to dig them out to see what changed (named to match the git format-patch output
since I think the CFBot just applies the patches in alphabetical order).

--
Daniel Gustafsson https://vmware.com/

Attachments:

v10-0001-Move-inet_net_pton-to-src-port.patchapplication/octet-stream; name=v10-0001-Move-inet_net_pton-to-src-port.patch; x-unix-mode=0644Download
From 84a107da33b7d901cb318f8c2fd140644fbc5100 Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Wed, 24 Nov 2021 14:46:11 -0800
Subject: [PATCH v10 1/3] Move inet_net_pton() to src/port

This will be helpful for IP address verification in libpq.
---
 src/backend/utils/adt/Makefile                  |  1 -
 src/include/port.h                              |  3 +++
 src/include/utils/builtins.h                    |  4 ----
 src/port/Makefile                               |  1 +
 src/{backend/utils/adt => port}/inet_net_pton.c | 16 +++++++++++++++-
 src/tools/msvc/Mkvcbuild.pm                     |  2 +-
 6 files changed, 20 insertions(+), 7 deletions(-)
 rename src/{backend/utils/adt => port}/inet_net_pton.c (96%)

diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile
index 7c722ea2ce..ccaa46babf 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -44,7 +44,6 @@ OBJS = \
        geo_spgist.o \
        hbafuncs.o \
        inet_cidr_ntop.o \
-       inet_net_pton.o \
        int.o \
        int8.o \
        json.o \
diff --git a/src/include/port.h b/src/include/port.h
index 3d103a2b31..2852e5b58b 100644
--- a/src/include/port.h
+++ b/src/include/port.h
@@ -515,6 +515,9 @@ extern int	pg_codepage_to_encoding(UINT cp);
 extern char *pg_inet_net_ntop(int af, const void *src, int bits,
 							  char *dst, size_t size);
 
+/* port/inet_net_pton.c */
+extern int	pg_inet_net_pton(int af, const char *src, void *dst, size_t size);
+
 /* port/pg_strong_random.c */
 extern void pg_strong_random_init(void);
 extern bool pg_strong_random(void *buf, size_t len);
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 666e545496..67b9326dc8 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -93,10 +93,6 @@ extern int	xidLogicalComparator(const void *arg1, const void *arg2);
 extern char *pg_inet_cidr_ntop(int af, const void *src, int bits,
 							   char *dst, size_t size);
 
-/* inet_net_pton.c */
-extern int	pg_inet_net_pton(int af, const char *src,
-							 void *dst, size_t size);
-
 /* network.c */
 extern double convert_network_to_scalar(Datum value, Oid typid, bool *failure);
 extern Datum network_scan_first(Datum in);
diff --git a/src/port/Makefile b/src/port/Makefile
index bfe1feb0d4..c3ae7b3d5c 100644
--- a/src/port/Makefile
+++ b/src/port/Makefile
@@ -43,6 +43,7 @@ OBJS = \
 	bsearch_arg.o \
 	chklocale.o \
 	inet_net_ntop.o \
+	inet_net_pton.o \
 	noblock.o \
 	path.o \
 	pg_bitutils.o \
diff --git a/src/backend/utils/adt/inet_net_pton.c b/src/port/inet_net_pton.c
similarity index 96%
rename from src/backend/utils/adt/inet_net_pton.c
rename to src/port/inet_net_pton.c
index d3221a1313..bae50ba67e 100644
--- a/src/backend/utils/adt/inet_net_pton.c
+++ b/src/port/inet_net_pton.c
@@ -14,14 +14,18 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
  * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  *
- *	  src/backend/utils/adt/inet_net_pton.c
+ *	  src/port/inet_net_pton.c
  */
 
 #if defined(LIBC_SCCS) && !defined(lint)
 static const char rcsid[] = "Id: inet_net_pton.c,v 1.4.2.3 2004/03/17 00:40:11 marka Exp $";
 #endif
 
+#ifndef FRONTEND
 #include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
 
 #include <sys/socket.h>
 #include <netinet/in.h>
@@ -29,9 +33,19 @@ static const char rcsid[] = "Id: inet_net_pton.c,v 1.4.2.3 2004/03/17 00:40:11 m
 #include <assert.h>
 #include <ctype.h>
 
+#ifndef FRONTEND
 #include "utils/builtins.h" /* pgrminclude ignore */	/* needed on some
 														 * platforms */
 #include "utils/inet.h"
+#else
+/*
+ * In a frontend build, we can't include inet.h, but we still need to have
+ * sensible definitions of these two constants.  Note that pg_inet_net_ntop()
+ * assumes that PGSQL_AF_INET is equal to AF_INET.
+ */
+#define PGSQL_AF_INET	(AF_INET + 0)
+#define PGSQL_AF_INET6	(AF_INET + 1)
+#endif
 
 
 static int	inet_net_pton_ipv4(const char *src, u_char *dst);
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 441d6ae6bf..667b173ec3 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -100,7 +100,7 @@ sub mkvcbuild
 
 	our @pgportfiles = qw(
 	  chklocale.c explicit_bzero.c fls.c getpeereid.c getrusage.c inet_aton.c
-	  getaddrinfo.c gettimeofday.c inet_net_ntop.c kill.c open.c
+	  getaddrinfo.c gettimeofday.c inet_net_ntop.c inet_net_pton.c kill.c open.c
 	  snprintf.c strlcat.c strlcpy.c dirmod.c noblock.c path.c
 	  dirent.c dlopen.c getopt.c getopt_long.c link.c
 	  pread.c preadv.c pwrite.c pwritev.c pg_bitutils.c
-- 
2.25.1

v10-0003-squash-libpq-allow-IP-address-SANs-in-server-cer.patchapplication/octet-stream; name=v10-0003-squash-libpq-allow-IP-address-SANs-in-server-cer.patch; x-unix-mode=0644Download
From 1a070a1ac821c8eda4d3a2856e2721b2fa97aaa1 Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Wed, 5 Jan 2022 15:47:03 -0800
Subject: [PATCH v10 3/3] squash! libpq: allow IP address SANs in server certs

Per review, provide a pg_inet_pton() interface for IPv6 and refactor the
internals accordingly. IPv4 is not yet implemented. The call sites that
just want standard addresses without a CIDR mask have been updated to
use the new function.

Internally, the IPv6 parser now takes an allow_cidr boolean. I've
plumbed that all the way down to getbits() in order to minimize the
amount of code touched, at the expense of an unnecessary function call
in the failing case.
---
 src/include/port.h                       |  1 +
 src/interfaces/libpq/fe-secure-common.c  |  7 +--
 src/interfaces/libpq/fe-secure-openssl.c |  2 +-
 src/port/inet_net_pton.c                 | 66 ++++++++++++++++++------
 4 files changed, 52 insertions(+), 24 deletions(-)

diff --git a/src/port/inet_net_pton.c b/src/port/inet_net_pton.c
index 5bae811c6f..ae0d1fd2e3 100644
--- a/src/port/inet_net_pton.c
+++ b/src/port/inet_net_pton.c
@@ -42,8 +42,8 @@ static const char rcsid[] = "Id: inet_net_pton.c,v 1.4.2.3 2004/03/17 00:40:11 m
 
 static int	inet_net_pton_ipv4(const char *src, u_char *dst);
 static int	inet_cidr_pton_ipv4(const char *src, u_char *dst, size_t size);
-static int	inet_net_pton_ipv6(const char *src, u_char *dst);
-static int	inet_cidr_pton_ipv6(const char *src, u_char *dst, size_t size);
+static int	inet_pton_ipv6_internal(const char *src, u_char *dst, size_t size,
+									bool allow_cidr);
 
 
 /*
@@ -74,9 +74,9 @@ pg_inet_net_pton(int af, const char *src, void *dst, size_t size)
 				inet_net_pton_ipv4(src, dst) :
 				inet_cidr_pton_ipv4(src, dst, size);
 		case PGSQL_AF_INET6:
-			return size == -1 ?
-				inet_net_pton_ipv6(src, dst) :
-				inet_cidr_pton_ipv6(src, dst, size);
+			return inet_pton_ipv6_internal(src, dst,
+										   (size == -1) ? 16 : size,
+										   true /* allow CIDR */);
 		default:
 			errno = EAFNOSUPPORT;
 			return -1;
@@ -352,13 +352,22 @@ emsgsize:
 }
 
 static int
-getbits(const char *src, int *bitsp)
+getbits(const char *src, int *bitsp, bool allow_cidr)
 {
 	static const char digits[] = "0123456789";
 	int			n;
 	int			val;
 	char		ch;
 
+	if (!allow_cidr)
+	{
+		/*
+		 * If we got here, the top-level call is to pg_inet_pton() and the
+		 * caller supplied a CIDR mask, which isn't allowed.
+		 */
+		return 0;
+	}
+
 	val = 0;
 	n = 0;
 	while ((ch = *src++) != '\0')
@@ -385,7 +394,7 @@ getbits(const char *src, int *bitsp)
 }
 
 static int
-getv4(const char *src, u_char *dst, int *bitsp)
+getv4(const char *src, u_char *dst, int *bitsp, bool allow_cidr)
 {
 	static const char digits[] = "0123456789";
 	u_char	   *odst = dst;
@@ -416,7 +425,7 @@ getv4(const char *src, u_char *dst, int *bitsp)
 				return 0;
 			*dst++ = val;
 			if (ch == '/')
-				return getbits(src, bitsp);
+				return getbits(src, bitsp, allow_cidr);
 			val = 0;
 			n = 0;
 			continue;
@@ -431,18 +440,12 @@ getv4(const char *src, u_char *dst, int *bitsp)
 	return 1;
 }
 
-static int
-inet_net_pton_ipv6(const char *src, u_char *dst)
-{
-	return inet_cidr_pton_ipv6(src, dst, 16);
-}
-
 #define NS_IN6ADDRSZ 16
 #define NS_INT16SZ 2
 #define NS_INADDRSZ 4
 
 static int
-inet_cidr_pton_ipv6(const char *src, u_char *dst, size_t size)
+inet_pton_ipv6_internal(const char *src, u_char *dst, size_t size, bool allow_cidr)
 {
 	static const char xdigits_l[] = "0123456789abcdef",
 				xdigits_u[] = "0123456789ABCDEF";
@@ -510,13 +513,13 @@ inet_cidr_pton_ipv6(const char *src, u_char *dst, size_t size)
 			continue;
 		}
 		if (ch == '.' && ((tp + NS_INADDRSZ) <= endp) &&
-			getv4(curtok, tp, &bits) > 0)
+			getv4(curtok, tp, &bits, allow_cidr) > 0)
 		{
 			tp += NS_INADDRSZ;
 			saw_xdigit = 0;
 			break;				/* '\0' was seen by inet_pton4(). */
 		}
-		if (ch == '/' && getbits(src, &bits) > 0)
+		if (ch == '/' && getbits(src, &bits, allow_cidr) > 0)
 			break;
 		goto enoent;
 	}
@@ -530,6 +533,9 @@ inet_cidr_pton_ipv6(const char *src, u_char *dst, size_t size)
 	if (bits == -1)
 		bits = 128;
 
+	/* Without a CIDR mask, the above should guarantee a complete address. */
+	Assert(allow_cidr || bits == 128);
+
 	endp = tmp + 16;
 
 	if (colonp != NULL)
@@ -568,3 +574,29 @@ emsgsize:
 	errno = EMSGSIZE;
 	return -1;
 }
+
+/*
+ * Converts a network address in presentation format to network format. The main
+ * difference between this and pg_inet_net_pton() above is that CIDR notation is
+ * not allowed.
+ *
+ * The memory pointed to by dst depends on the address family:
+ *
+ *     PGSQL_AF_INET:  not implemented yet (returns -1 with EAFNOSUPPORT)
+ *     PGSQL_AF_INET6: *dst must be a u_char[16]
+ *
+ * Over and above the standard inet_pton() functionality, we also set errno on
+ * parse failure, with the same meanings as with pg_inet_net_pton().
+ */
+int
+pg_inet_pton(int af, const char *src, void *dst)
+{
+	if (af != PGSQL_AF_INET6)
+	{
+		/* Currently only IPv6 is implemented. */
+		errno = EAFNOSUPPORT;
+		return -1;
+	}
+
+	return (inet_pton_ipv6_internal(src, dst, 16, false /* no CIDR */) == 128);
+}
-- 
2.25.1

v10-0003-squash-libpq-allow-IP-address-SANs-in-server-cer-1.patchapplication/octet-stream; name=v10-0003-squash-libpq-allow-IP-address-SANs-in-server-cer-1.patch; x-unix-mode=0644Download
From 1a070a1ac821c8eda4d3a2856e2721b2fa97aaa1 Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Wed, 5 Jan 2022 15:47:03 -0800
Subject: [PATCH v10 3/3] squash! libpq: allow IP address SANs in server certs

Per review, provide a pg_inet_pton() interface for IPv6 and refactor the
internals accordingly. IPv4 is not yet implemented. The call sites that
just want standard addresses without a CIDR mask have been updated to
use the new function.

Internally, the IPv6 parser now takes an allow_cidr boolean. I've
plumbed that all the way down to getbits() in order to minimize the
amount of code touched, at the expense of an unnecessary function call
in the failing case.
---
 src/include/port.h                       |  1 +
 src/interfaces/libpq/fe-secure-common.c  |  7 +--
 src/interfaces/libpq/fe-secure-openssl.c |  2 +-
 src/port/inet_net_pton.c                 | 66 ++++++++++++++++++------
 4 files changed, 52 insertions(+), 24 deletions(-)

diff --git a/src/include/port.h b/src/include/port.h
index 2852e5b58b..fd046f4c24 100644
--- a/src/include/port.h
+++ b/src/include/port.h
@@ -517,6 +517,7 @@ extern char *pg_inet_net_ntop(int af, const void *src, int bits,
 
 /* port/inet_net_pton.c */
 extern int	pg_inet_net_pton(int af, const char *src, void *dst, size_t size);
+extern int	pg_inet_pton(int af, const char *src, void *dst);
 
 /* port/pg_strong_random.c */
 extern void pg_strong_random_init(void);
diff --git a/src/interfaces/libpq/fe-secure-common.c b/src/interfaces/libpq/fe-secure-common.c
index 2c0af62afe..9c408df369 100644
--- a/src/interfaces/libpq/fe-secure-common.c
+++ b/src/interfaces/libpq/fe-secure-common.c
@@ -211,12 +211,7 @@ pq_verify_peer_name_matches_certificate_ip(PGconn *conn,
 
 		family = PGSQL_AF_INET6;
 
-		/*
-		 * pg_inet_net_pton() will accept CIDR masks, which we don't want to
-		 * match, so skip the comparison if the host string contains a slash.
-		 */
-		if (!strchr(host, '/')
-			&& pg_inet_net_pton(PGSQL_AF_INET6, host, addr, -1) == 128)
+		if (pg_inet_pton(PGSQL_AF_INET6, host, addr) == 1)
 		{
 			if (memcmp(ipdata, addr, iplen) == 0)
 				match = 1;
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index 7656c3a75e..b70bd2eb19 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -555,7 +555,7 @@ is_ip_address(const char *host)
 	unsigned char	dummy6[16];
 
 	return inet_aton(host, &dummy4)
-		|| (pg_inet_net_pton(PGSQL_AF_INET6, host, dummy6, -1) == 128);
+		|| (pg_inet_pton(PGSQL_AF_INET6, host, dummy6) == 1);
 }
 
 /*
-- 
2.25.1

v10-0004-fix-tests.diffapplication/octet-stream; name=v10-0004-fix-tests.diff; x-unix-mode=0644Download
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 96d3536090..9cc6fed51f 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -231,10 +231,10 @@ $node->connect_fails(
 
 # Test with an IP address in the Common Name. This is a strange corner case that
 # nevertheless is supported, as long as the address string matches exactly.
-switch_server_cert($node, 'server-ip-cn-only');
+switch_server_cert($node, certfile => 'server-ip-cn-only');
 
 $common_connstr =
-  "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+  "$default_ssl_connstr user=ssltestuser dbname=trustdb sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
 
 $node->connect_ok("$common_connstr host=192.0.2.1",
 	"IP address in the Common Name");
@@ -248,7 +248,7 @@ $node->connect_fails(
 
 # Similarly, we'll also match an IP address in a dNSName SAN. (This is
 # long-standing behavior.)
-switch_server_cert($node, 'server-ip-in-dnsname');
+switch_server_cert($node, certfile => 'server-ip-in-dnsname');
 
 $node->connect_ok("$common_connstr host=192.0.2.1",
 	"IP address in a dNSName");
@@ -306,7 +306,7 @@ $node->connect_fails(
 );
 
 # Test certificate with IP addresses in the SANs.
-switch_server_cert($node, 'server-ip-alt-names');
+switch_server_cert($node, certfile => 'server-ip-alt-names');
 
 $node->connect_ok(
 	"$common_connstr host=192.0.2.1",
@@ -371,7 +371,7 @@ $node->connect_fails(
 );
 
 # But we will fall back to check the CN if the SANs contain only IP addresses.
-switch_server_cert($node, 'server-cn-and-ip-alt-names');
+switch_server_cert($node, certfile => 'server-cn-and-ip-alt-names');
 
 $node->connect_ok("$common_connstr host=common-name.pg-ssltest.test",
 	"certificate with both a CN and IP SANs matches CN");
@@ -381,7 +381,7 @@ $node->connect_ok("$common_connstr host=2001:db8::1",
 	"certificate with both a CN and IP SANs matches SAN 2");
 
 # And now the same tests, but with IP addresses and DNS names swapped.
-switch_server_cert($node, 'server-ip-cn-and-alt-names');
+switch_server_cert($node, certfile => 'server-ip-cn-and-alt-names');
 
 $node->connect_ok("$common_connstr host=192.0.2.2",
 	"certificate with both an IP CN and IP SANs 1");
@@ -394,7 +394,7 @@ $node->connect_fails(
 	  qr/\Qserver certificate for "192.0.2.2" (and 1 other name) does not match host name "192.0.2.1"\E/
 );
 
-switch_server_cert($node, 'server-ip-cn-and-dns-alt-names');
+switch_server_cert($node, certfile => 'server-ip-cn-and-dns-alt-names');
 
 $node->connect_ok("$common_connstr host=192.0.2.1",
 	"certificate with both an IP CN and DNS SANs matches CN");
#39Greg Stark
stark@mit.edu
In reply to: Daniel Gustafsson (#38)
Re: [PATCH] Accept IP addresses in server certificate SANs

On Mon, 28 Mar 2022 at 05:17, Daniel Gustafsson <daniel@yesql.se> wrote:

named to match the git format-patch output
since I think the CFBot just applies the patches in alphabetical order).

The first patch doesn't seem to actually apply though so it doesn't
get to the subsequent patches.

http://cfbot.cputube.org/patch_37_3458.log

=== Applying patches on top of PostgreSQL commit ID
e26114c817b610424010cfbe91a743f591246ff1 ===
=== applying patch ./v10-0001-Move-inet_net_pton-to-src-port.patch
patching file src/backend/utils/adt/Makefile
Hunk #1 FAILED at 44.
1 out of 1 hunk FAILED -- saving rejects to file
src/backend/utils/adt/Makefile.rej
patching file src/include/port.h
patching file src/include/utils/builtins.h
patching file src/port/Makefile
patching file src/port/inet_net_pton.c (renamed from
src/backend/utils/adt/inet_net_pton.c)
patching file src/tools/msvc/Mkvcbuild.pm

--
greg

#40Jacob Champion
pchampion@vmware.com
In reply to: Daniel Gustafsson (#38)
3 attachment(s)
Re: [PATCH] Accept IP addresses in server certificate SANs

On Mon, 2022-03-28 at 11:17 +0200, Daniel Gustafsson wrote:

Fixing up the switch_server_cert() calls and using default_ssl_connstr makes
the test pass for me. The required fixes are in the supplied 0004 diff, I kept
them separate to allow the original author to incorporate them without having
to dig them out to see what changed (named to match the git format-patch output
since I think the CFBot just applies the patches in alphabetical order).

Thanks! Those changes look good to me; I've folded them into v11. This
is rebased on a newer HEAD so it should fix the apply failures that
Greg pointed out.

--Jacob

Attachments:

v11-0001-Move-inet_net_pton-to-src-port.patchtext/x-patch; name=v11-0001-Move-inet_net_pton-to-src-port.patchDownload
From 0025dcecab2d05c30e118105ea7d461ce98a8466 Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Wed, 24 Nov 2021 14:46:11 -0800
Subject: [PATCH v11 1/3] Move inet_net_pton() to src/port

This will be helpful for IP address verification in libpq.
---
 src/backend/utils/adt/Makefile                  |  1 -
 src/include/port.h                              |  3 +++
 src/include/utils/builtins.h                    |  4 ----
 src/port/Makefile                               |  1 +
 src/{backend/utils/adt => port}/inet_net_pton.c | 16 +++++++++++++++-
 src/tools/msvc/Mkvcbuild.pm                     |  2 +-
 6 files changed, 20 insertions(+), 7 deletions(-)
 rename src/{backend/utils/adt => port}/inet_net_pton.c (96%)

diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile
index 7c722ea2ce..ccaa46babf 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -44,7 +44,6 @@ OBJS = \
 	geo_spgist.o \
 	hbafuncs.o \
 	inet_cidr_ntop.o \
-	inet_net_pton.o \
 	int.o \
 	int8.o \
 	json.o \
diff --git a/src/include/port.h b/src/include/port.h
index 3d103a2b31..2852e5b58b 100644
--- a/src/include/port.h
+++ b/src/include/port.h
@@ -515,6 +515,9 @@ extern int	pg_codepage_to_encoding(UINT cp);
 extern char *pg_inet_net_ntop(int af, const void *src, int bits,
 							  char *dst, size_t size);
 
+/* port/inet_net_pton.c */
+extern int	pg_inet_net_pton(int af, const char *src, void *dst, size_t size);
+
 /* port/pg_strong_random.c */
 extern void pg_strong_random_init(void);
 extern bool pg_strong_random(void *buf, size_t len);
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 666e545496..67b9326dc8 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -93,10 +93,6 @@ extern int	xidLogicalComparator(const void *arg1, const void *arg2);
 extern char *pg_inet_cidr_ntop(int af, const void *src, int bits,
 							   char *dst, size_t size);
 
-/* inet_net_pton.c */
-extern int	pg_inet_net_pton(int af, const char *src,
-							 void *dst, size_t size);
-
 /* network.c */
 extern double convert_network_to_scalar(Datum value, Oid typid, bool *failure);
 extern Datum network_scan_first(Datum in);
diff --git a/src/port/Makefile b/src/port/Makefile
index bfe1feb0d4..c3ae7b3d5c 100644
--- a/src/port/Makefile
+++ b/src/port/Makefile
@@ -43,6 +43,7 @@ OBJS = \
 	bsearch_arg.o \
 	chklocale.o \
 	inet_net_ntop.o \
+	inet_net_pton.o \
 	noblock.o \
 	path.o \
 	pg_bitutils.o \
diff --git a/src/backend/utils/adt/inet_net_pton.c b/src/port/inet_net_pton.c
similarity index 96%
rename from src/backend/utils/adt/inet_net_pton.c
rename to src/port/inet_net_pton.c
index d3221a1313..bae50ba67e 100644
--- a/src/backend/utils/adt/inet_net_pton.c
+++ b/src/port/inet_net_pton.c
@@ -14,14 +14,18 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
  * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  *
- *	  src/backend/utils/adt/inet_net_pton.c
+ *	  src/port/inet_net_pton.c
  */
 
 #if defined(LIBC_SCCS) && !defined(lint)
 static const char rcsid[] = "Id: inet_net_pton.c,v 1.4.2.3 2004/03/17 00:40:11 marka Exp $";
 #endif
 
+#ifndef FRONTEND
 #include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
 
 #include <sys/socket.h>
 #include <netinet/in.h>
@@ -29,9 +33,19 @@ static const char rcsid[] = "Id: inet_net_pton.c,v 1.4.2.3 2004/03/17 00:40:11 m
 #include <assert.h>
 #include <ctype.h>
 
+#ifndef FRONTEND
 #include "utils/builtins.h" /* pgrminclude ignore */	/* needed on some
 														 * platforms */
 #include "utils/inet.h"
+#else
+/*
+ * In a frontend build, we can't include inet.h, but we still need to have
+ * sensible definitions of these two constants.  Note that pg_inet_net_ntop()
+ * assumes that PGSQL_AF_INET is equal to AF_INET.
+ */
+#define PGSQL_AF_INET	(AF_INET + 0)
+#define PGSQL_AF_INET6	(AF_INET + 1)
+#endif
 
 
 static int	inet_net_pton_ipv4(const char *src, u_char *dst);
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index de8676d339..8491e31adb 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -100,7 +100,7 @@ sub mkvcbuild
 
 	our @pgportfiles = qw(
 	  chklocale.c explicit_bzero.c fls.c getpeereid.c getrusage.c inet_aton.c
-	  getaddrinfo.c gettimeofday.c inet_net_ntop.c kill.c open.c
+	  getaddrinfo.c gettimeofday.c inet_net_ntop.c inet_net_pton.c kill.c open.c
 	  snprintf.c strlcat.c strlcpy.c dirmod.c noblock.c path.c
 	  dirent.c dlopen.c getopt.c getopt_long.c link.c
 	  pread.c preadv.c pwrite.c pwritev.c pg_bitutils.c
-- 
2.25.1

v11-0002-libpq-allow-IP-address-SANs-in-server-certs.patchtext/x-patch; name=v11-0002-libpq-allow-IP-address-SANs-in-server-certs.patchDownload
From 43a0ecebf3ccabcb25b735494eadefeb2a56ffd6 Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Thu, 18 Nov 2021 15:36:18 -0800
Subject: [PATCH v11 2/3] libpq: allow IP address SANs in server certs

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.

- Move PGSQL_AF_INET* to inet-common.h to reduce copy-paste.

Co-authored-by: Kyotaro Horiguchi <horikyota.ntt@gmail.com>
Co-authored-by: Daniel Gustafsson <daniel@yesql.se>
---
 doc/src/sgml/libpq.sgml                       |  21 ++-
 src/include/common/inet-common.h              |  35 +++++
 src/include/utils/inet.h                      |  13 +-
 src/interfaces/libpq/fe-secure-common.c       | 104 +++++++++++++
 src/interfaces/libpq/fe-secure-common.h       |   4 +
 src/interfaces/libpq/fe-secure-openssl.c      | 139 ++++++++++++++++--
 src/port/inet_net_ntop.c                      |  12 +-
 src/port/inet_net_pton.c                      |  10 +-
 .../conf/server-cn-and-ip-alt-names.config    |  24 +++
 src/test/ssl/conf/server-ip-alt-names.config  |  19 +++
 .../conf/server-ip-cn-and-alt-names.config    |  21 +++
 .../server-ip-cn-and-dns-alt-names.config     |  21 +++
 src/test/ssl/conf/server-ip-cn-only.config    |  12 ++
 src/test/ssl/conf/server-ip-in-dnsname.config |  18 +++
 .../ssl/ssl/server-cn-and-ip-alt-names.crt    |  20 +++
 .../ssl/ssl/server-cn-and-ip-alt-names.key    |  27 ++++
 src/test/ssl/ssl/server-ip-alt-names.crt      |  19 +++
 src/test/ssl/ssl/server-ip-alt-names.key      |  27 ++++
 .../ssl/ssl/server-ip-cn-and-alt-names.crt    |  19 +++
 .../ssl/ssl/server-ip-cn-and-alt-names.key    |  27 ++++
 .../ssl/server-ip-cn-and-dns-alt-names.crt    |  20 +++
 .../ssl/server-ip-cn-and-dns-alt-names.key    |  27 ++++
 src/test/ssl/ssl/server-ip-cn-only.crt        |  18 +++
 src/test/ssl/ssl/server-ip-cn-only.key        |  27 ++++
 src/test/ssl/ssl/server-ip-in-dnsname.crt     |  18 +++
 src/test/ssl/ssl/server-ip-in-dnsname.key     |  27 ++++
 src/test/ssl/sslfiles.mk                      |   6 +
 src/test/ssl/t/001_ssltests.pl                | 106 ++++++++++++-
 28 files changed, 792 insertions(+), 49 deletions(-)
 create mode 100644 src/include/common/inet-common.h
 create mode 100644 src/test/ssl/conf/server-cn-and-ip-alt-names.config
 create mode 100644 src/test/ssl/conf/server-ip-alt-names.config
 create mode 100644 src/test/ssl/conf/server-ip-cn-and-alt-names.config
 create mode 100644 src/test/ssl/conf/server-ip-cn-and-dns-alt-names.config
 create mode 100644 src/test/ssl/conf/server-ip-cn-only.config
 create mode 100644 src/test/ssl/conf/server-ip-in-dnsname.config
 create mode 100644 src/test/ssl/ssl/server-cn-and-ip-alt-names.crt
 create mode 100644 src/test/ssl/ssl/server-cn-and-ip-alt-names.key
 create mode 100644 src/test/ssl/ssl/server-ip-alt-names.crt
 create mode 100644 src/test/ssl/ssl/server-ip-alt-names.key
 create mode 100644 src/test/ssl/ssl/server-ip-cn-and-alt-names.crt
 create mode 100644 src/test/ssl/ssl/server-ip-cn-and-alt-names.key
 create mode 100644 src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.crt
 create mode 100644 src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.key
 create mode 100644 src/test/ssl/ssl/server-ip-cn-only.crt
 create mode 100644 src/test/ssl/ssl/server-ip-cn-only.key
 create mode 100644 src/test/ssl/ssl/server-ip-in-dnsname.crt
 create mode 100644 src/test/ssl/ssl/server-ip-in-dnsname.key

diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 70233aa872..cf69567c21 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -8344,16 +8344,31 @@ ldap://ldap.acme.com/cn=dbserver,cn=hosts?pgconnectinfo?base?(objectclass=*)
 
   <para>
    In <literal>verify-full</literal> mode, the host name is matched against the
-   certificate's Subject Alternative Name attribute(s), or against the
-   Common Name attribute if no Subject Alternative Name of type <literal>dNSName</literal> is
+   certificate's Subject Alternative Name attribute(s) (SAN), or against the
+   Common Name attribute if no SAN of type <literal>dNSName</literal> is
    present.  If the certificate's name attribute starts with an asterisk
    (<literal>*</literal>), the asterisk will be treated as
    a wildcard, which will match all characters <emphasis>except</emphasis> a dot
    (<literal>.</literal>). This means the certificate will not match subdomains.
    If the connection is made using an IP address instead of a host name, the
-   IP address will be matched (without doing any DNS lookups).
+   IP address will be matched (without doing any DNS lookups) against SANs of
+   type <literal>iPAddress</literal> or <literal>dNSName</literal>.  If no
+   <literal>iPAddress</literal> SAN is present and no
+   matching <literal>dNSName</literal> SAN is present, the host IP address is
+   matched against the Common Name attribute.
   </para>
 
+  <note>
+    <para>
+      For backward compatibility with earlier versions of PostgreSQL, the host
+      IP address is verified in a manner different
+      from <ulink url="https://tools.ietf.org/html/rfc6125">RFC 6125</ulink>.
+      The host IP address is always matched against <literal>dNSName</literal>
+      SANs as well as <literal>iPAddress</literal> SANs, and can be matched
+      against the Common Name attribute if no relevant SANs exist.
+   </para>
+  </note>
+
   <para>
    To allow server certificate verification, one or more root certificates
    must be placed in the file <filename>~/.postgresql/root.crt</filename>
diff --git a/src/include/common/inet-common.h b/src/include/common/inet-common.h
new file mode 100644
index 0000000000..3ad72261b6
--- /dev/null
+++ b/src/include/common/inet-common.h
@@ -0,0 +1,35 @@
+/*-------------------------------------------------------------------------
+ *
+ * inet-common.h
+ *
+ *	  Common code for clients of the inet_* APIs.
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/common/inet-common.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef INET_COMMON_H
+#define INET_COMMON_H
+
+/*
+ * We use these values for the "family" field of inet_struct.
+ *
+ * Referencing all of the non-AF_INET types to AF_INET lets us work on
+ * machines which may not have the appropriate address family (like
+ * inet6 addresses when AF_INET6 isn't present) but doesn't cause a
+ * dump/reload requirement.  Pre-7.4 databases used AF_INET for the family
+ * type on disk.
+ *
+ * In a frontend build, we can't include inet.h, but we still need to have
+ * sensible definitions of these two constants.  (Frontend clients should
+ * include this header directly.)  Note that pg_inet_net_ntop()
+ * assumes that PGSQL_AF_INET is equal to AF_INET.
+ */
+#define PGSQL_AF_INET	(AF_INET + 0)
+#define PGSQL_AF_INET6	(AF_INET + 1)
+
+#endif							/* INET_COMMON_H */
diff --git a/src/include/utils/inet.h b/src/include/utils/inet.h
index 3073c0307e..c56c016987 100644
--- a/src/include/utils/inet.h
+++ b/src/include/utils/inet.h
@@ -14,6 +14,7 @@
 #ifndef INET_H
 #define INET_H
 
+#include "common/inet-common.h"
 #include "fmgr.h"
 
 /*
@@ -27,18 +28,6 @@ typedef struct
 	unsigned char ipaddr[16];	/* up to 128 bits of address */
 } inet_struct;
 
-/*
- * We use these values for the "family" field.
- *
- * Referencing all of the non-AF_INET types to AF_INET lets us work on
- * machines which may not have the appropriate address family (like
- * inet6 addresses when AF_INET6 isn't present) but doesn't cause a
- * dump/reload requirement.  Pre-7.4 databases used AF_INET for the family
- * type on disk.
- */
-#define PGSQL_AF_INET	(AF_INET + 0)
-#define PGSQL_AF_INET6	(AF_INET + 1)
-
 /*
  * Both INET and CIDR addresses are represented within Postgres as varlena
  * objects, ie, there is a varlena header in front of the struct type
diff --git a/src/interfaces/libpq/fe-secure-common.c b/src/interfaces/libpq/fe-secure-common.c
index bd46f08fae..2c0af62afe 100644
--- a/src/interfaces/libpq/fe-secure-common.c
+++ b/src/interfaces/libpq/fe-secure-common.c
@@ -19,9 +19,13 @@
 
 #include "postgres_fe.h"
 
+#include <arpa/inet.h>
+
+#include "common/inet-common.h"
 #include "fe-secure-common.h"
 
 #include "libpq-int.h"
+#include "port.h"
 #include "pqexpbuffer.h"
 
 /*
@@ -144,6 +148,106 @@ pq_verify_peer_name_matches_certificate_name(PGconn *conn,
 	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'))
+	{
+		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 = PGSQL_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;
+		}
+	}
+	else if (iplen == 16)
+	{
+		/* IPv6 */
+		unsigned char addr[16];
+
+		family = PGSQL_AF_INET6;
+
+		/*
+		 * pg_inet_net_pton() will accept CIDR masks, which we don't want to
+		 * match, so skip the comparison if the host string contains a slash.
+		 */
+		if (!strchr(host, '/')
+			&& pg_inet_net_pton(PGSQL_AF_INET6, host, addr, -1) == 128)
+		{
+			if (memcmp(ipdata, addr, iplen) == 0)
+				match = 1;
+		}
+	}
+	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.
  *
diff --git a/src/interfaces/libpq/fe-secure-common.h b/src/interfaces/libpq/fe-secure-common.h
index 1cca6d785a..d18db7138c 100644
--- a/src/interfaces/libpq/fe-secure-common.h
+++ b/src/interfaces/libpq/fe-secure-common.h
@@ -21,6 +21,10 @@
 extern int	pq_verify_peer_name_matches_certificate_name(PGconn *conn,
 														 const char *namedata, size_t namelen,
 														 char **store_name);
+extern int	pq_verify_peer_name_matches_certificate_ip(PGconn *conn,
+													   const unsigned char *addrdata,
+													   size_t addrlen,
+													   char **store_name);
 extern bool pq_verify_peer_name_matches_certificate(PGconn *conn);
 
 #endif							/* FE_SECURE_COMMON_H */
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index d81218a4cc..7656c3a75e 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -26,6 +26,7 @@
 #include <fcntl.h>
 #include <ctype.h>
 
+#include "common/inet-common.h"
 #include "libpq-fe.h"
 #include "fe-auth.h"
 #include "fe-secure-common.h"
@@ -72,6 +73,9 @@ static int	verify_cb(int ok, X509_STORE_CTX *ctx);
 static int	openssl_verify_peer_name_matches_certificate_name(PGconn *conn,
 															  ASN1_STRING *name,
 															  char **store_name);
+static int	openssl_verify_peer_name_matches_certificate_ip(PGconn *conn,
+															ASN1_OCTET_STRING *addr_entry,
+															char **store_name);
 static void destroy_ssl_system(void);
 static int	initialize_SSL(PGconn *conn);
 static PostgresPollingStatusType open_client_SSL(PGconn *);
@@ -509,6 +513,51 @@ openssl_verify_peer_name_matches_certificate_name(PGconn *conn, ASN1_STRING *nam
 	return pq_verify_peer_name_matches_certificate_name(conn, (const char *) namedata, len, store_name);
 }
 
+/*
+ * OpenSSL-specific wrapper around
+ * pq_verify_peer_name_matches_certificate_ip(), converting the
+ * ASN1_OCTET_STRING into a plain C string.
+ */
+static int
+openssl_verify_peer_name_matches_certificate_ip(PGconn *conn,
+												ASN1_OCTET_STRING *addr_entry,
+												char **store_name)
+{
+	int			len;
+	const unsigned char *addrdata;
+
+	/* Should not happen... */
+	if (addr_entry == NULL)
+	{
+		appendPQExpBufferStr(&conn->errorMessage,
+							 libpq_gettext("SSL certificate's address entry is missing\n"));
+		return -1;
+	}
+
+	/*
+	 * GEN_IPADD is an OCTET STRING containing an IP address in network byte
+	 * order.
+	 */
+#ifdef HAVE_ASN1_STRING_GET0_DATA
+	addrdata = ASN1_STRING_get0_data(addr_entry);
+#else
+	addrdata = ASN1_STRING_data(addr_entry);
+#endif
+	len = ASN1_STRING_length(addr_entry);
+
+	return pq_verify_peer_name_matches_certificate_ip(conn, addrdata, len, store_name);
+}
+
+static bool
+is_ip_address(const char *host)
+{
+	struct in_addr	dummy4;
+	unsigned char	dummy6[16];
+
+	return inet_aton(host, &dummy4)
+		|| (pg_inet_net_pton(PGSQL_AF_INET6, host, dummy6, -1) == 128);
+}
+
 /*
  *	Verify that the server certificate matches the hostname we connected to.
  *
@@ -522,6 +571,36 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 	STACK_OF(GENERAL_NAME) * peer_san;
 	int			i;
 	int			rc = 0;
+	char	   *host = conn->connhost[conn->whichhost].host;
+	int			host_type;
+	bool		check_cn = true;
+
+	Assert(host && host[0]); /* should be guaranteed by caller */
+
+	/*
+	 * We try to match the NSS behavior here, which is a slight departure from
+	 * the spec but seems to make more intuitive sense:
+	 *
+	 * If connhost contains a DNS name, and the certificate's SANs contain any
+	 * dNSName entries, then we'll ignore the Subject Common Name entirely;
+	 * otherwise, we fall back to checking the CN. (This behavior matches the
+	 * RFC.)
+	 *
+	 * If connhost contains an IP address, and the SANs contain iPAddress
+	 * entries, we again ignore the CN. Otherwise, we allow the CN to match,
+	 * EVEN IF there is a dNSName in the SANs. (RFC 6125 prohibits this: "A
+	 * client MUST NOT seek a match for a reference identifier of CN-ID if the
+	 * presented identifiers include a DNS-ID, SRV-ID, URI-ID, or any
+	 * application-specific identifier types supported by the client.")
+	 *
+	 * NOTE: Prior versions of libpq did not consider iPAddress entries at all,
+	 * so this new behavior might break a certificate that has different IP
+	 * addresses in the Subject CN and the SANs.
+	 */
+	if (is_ip_address(host))
+		host_type = GEN_IPADD;
+	else
+		host_type = GEN_DNS;
 
 	/*
 	 * First, get the Subject Alternative Names (SANs) from the certificate,
@@ -537,38 +616,62 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 		for (i = 0; i < san_len; i++)
 		{
 			const GENERAL_NAME *name = sk_GENERAL_NAME_value(peer_san, i);
+			char	   *alt_name = NULL;
 
-			if (name->type == GEN_DNS)
+			if (name->type == host_type)
 			{
-				char	   *alt_name;
+				/*
+				 * This SAN is of the same type (IP or DNS) as our host name, so
+				 * don't allow a fallback check of the CN.
+				 */
+				check_cn = false;
+			}
 
+			if (name->type == GEN_DNS)
+			{
 				(*names_examined)++;
 				rc = openssl_verify_peer_name_matches_certificate_name(conn,
 																	   name->d.dNSName,
 																	   &alt_name);
+			}
+			else if (name->type == GEN_IPADD)
+			{
+				(*names_examined)++;
+				rc = openssl_verify_peer_name_matches_certificate_ip(conn,
+																	 name->d.iPAddress,
+																	 &alt_name);
+			}
 
-				if (alt_name)
-				{
-					if (!*first_name)
-						*first_name = alt_name;
-					else
-						free(alt_name);
-				}
+			if (alt_name)
+			{
+				if (!*first_name)
+					*first_name = alt_name;
+				else
+					free(alt_name);
 			}
+
 			if (rc != 0)
+			{
+				/*
+				 * Either we hit an error or a match, and either way we should
+				 * not fall back to the CN.
+				 */
+				check_cn = false;
 				break;
+			}
 		}
 		sk_GENERAL_NAME_pop_free(peer_san, GENERAL_NAME_free);
 	}
 
 	/*
-	 * If there is no subjectAltName extension of type dNSName, check the
+	 * If there is no subjectAltName extension of the matching type, check the
 	 * Common Name.
 	 *
 	 * (Per RFC 2818 and RFC 6125, if the subjectAltName extension of type
-	 * dNSName is present, the CN must be ignored.)
+	 * dNSName is present, the CN must be ignored. We break this rule if host is
+	 * an IP address; see the comment above.)
 	 */
-	if (*names_examined == 0)
+	if (check_cn)
 	{
 		X509_NAME  *subject_name;
 
@@ -581,10 +684,20 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 												  NID_commonName, -1);
 			if (cn_index >= 0)
 			{
+				char   *common_name = NULL;
+
 				(*names_examined)++;
 				rc = openssl_verify_peer_name_matches_certificate_name(conn,
 																	   X509_NAME_ENTRY_get_data(X509_NAME_get_entry(subject_name, cn_index)),
-																	   first_name);
+																	   &common_name);
+
+				if (common_name)
+				{
+					if (!*first_name)
+						*first_name = common_name;
+					else
+						free(common_name);
+				}
 			}
 		}
 	}
diff --git a/src/port/inet_net_ntop.c b/src/port/inet_net_ntop.c
index b8ad69c390..af28056132 100644
--- a/src/port/inet_net_ntop.c
+++ b/src/port/inet_net_ntop.c
@@ -31,17 +31,7 @@ static const char rcsid[] = "Id: inet_net_ntop.c,v 1.1.2.2 2004/03/09 09:17:27 m
 #include <netinet/in.h>
 #include <arpa/inet.h>
 
-#ifndef FRONTEND
-#include "utils/inet.h"
-#else
-/*
- * In a frontend build, we can't include inet.h, but we still need to have
- * sensible definitions of these two constants.  Note that pg_inet_net_ntop()
- * assumes that PGSQL_AF_INET is equal to AF_INET.
- */
-#define PGSQL_AF_INET	(AF_INET + 0)
-#define PGSQL_AF_INET6	(AF_INET + 1)
-#endif
+#include "common/inet-common.h"
 
 
 #define NS_IN6ADDRSZ 16
diff --git a/src/port/inet_net_pton.c b/src/port/inet_net_pton.c
index bae50ba67e..5bae811c6f 100644
--- a/src/port/inet_net_pton.c
+++ b/src/port/inet_net_pton.c
@@ -33,18 +33,10 @@ static const char rcsid[] = "Id: inet_net_pton.c,v 1.4.2.3 2004/03/17 00:40:11 m
 #include <assert.h>
 #include <ctype.h>
 
+#include "common/inet-common.h"
 #ifndef FRONTEND
 #include "utils/builtins.h" /* pgrminclude ignore */	/* needed on some
 														 * platforms */
-#include "utils/inet.h"
-#else
-/*
- * In a frontend build, we can't include inet.h, but we still need to have
- * sensible definitions of these two constants.  Note that pg_inet_net_ntop()
- * assumes that PGSQL_AF_INET is equal to AF_INET.
- */
-#define PGSQL_AF_INET	(AF_INET + 0)
-#define PGSQL_AF_INET6	(AF_INET + 1)
 #endif
 
 
diff --git a/src/test/ssl/conf/server-cn-and-ip-alt-names.config b/src/test/ssl/conf/server-cn-and-ip-alt-names.config
new file mode 100644
index 0000000000..a6fa09bad3
--- /dev/null
+++ b/src/test/ssl/conf/server-cn-and-ip-alt-names.config
@@ -0,0 +1,24 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+# This certificate contains a CN and SANs for both IPv4 and IPv6.
+
+
+[ req ]
+distinguished_name     = req_distinguished_name
+req_extensions         = v3_req
+prompt                 = no
+
+[ req_distinguished_name ]
+# Note: According to RFC 2818 and 6125, the CN is ignored, when DNS names are
+# present in the SANs. But they are silent on whether the CN is checked when IP
+# addresses are present.
+CN = common-name.pg-ssltest.test
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
+subjectAltName = @alt_names
+
+[ alt_names ]
+IP.1 = 192.0.2.1
+IP.2 = 2001:DB8::1
diff --git a/src/test/ssl/conf/server-ip-alt-names.config b/src/test/ssl/conf/server-ip-alt-names.config
new file mode 100644
index 0000000000..c22f22951a
--- /dev/null
+++ b/src/test/ssl/conf/server-ip-alt-names.config
@@ -0,0 +1,19 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+# This certificate has a two IP-address SANs, and no CN.
+
+[ req ]
+distinguished_name     = req_distinguished_name
+req_extensions         = v3_req
+prompt                 = no
+
+[ req_distinguished_name ]
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
+subjectAltName = @alt_names
+
+[ alt_names ]
+IP.1 = 192.0.2.1
+IP.2 = 2001:DB8::1
diff --git a/src/test/ssl/conf/server-ip-cn-and-alt-names.config b/src/test/ssl/conf/server-ip-cn-and-alt-names.config
new file mode 100644
index 0000000000..a4087f0a18
--- /dev/null
+++ b/src/test/ssl/conf/server-ip-cn-and-alt-names.config
@@ -0,0 +1,21 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+# This certificate contains both a CN and SANs in IP address format.
+
+
+[ req ]
+distinguished_name     = req_distinguished_name
+req_extensions         = v3_req
+prompt                 = no
+
+[ req_distinguished_name ]
+CN = 192.0.2.1
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
+subjectAltName = @alt_names
+
+[ alt_names ]
+IP.1 = 192.0.2.2
+IP.2 = 2001:DB8::1
diff --git a/src/test/ssl/conf/server-ip-cn-and-dns-alt-names.config b/src/test/ssl/conf/server-ip-cn-and-dns-alt-names.config
new file mode 100644
index 0000000000..7121803b49
--- /dev/null
+++ b/src/test/ssl/conf/server-ip-cn-and-dns-alt-names.config
@@ -0,0 +1,21 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+# This certificate contains both a CN and SANs in IP address format.
+
+
+[ req ]
+distinguished_name     = req_distinguished_name
+req_extensions         = v3_req
+prompt                 = no
+
+[ req_distinguished_name ]
+CN = 192.0.2.1
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
+subjectAltName = @alt_names
+
+[ alt_names ]
+DNS.1 = dns1.alt-name.pg-ssltest.test
+DNS.2 = dns2.alt-name.pg-ssltest.test
diff --git a/src/test/ssl/conf/server-ip-cn-only.config b/src/test/ssl/conf/server-ip-cn-only.config
new file mode 100644
index 0000000000..585d8bdae8
--- /dev/null
+++ b/src/test/ssl/conf/server-ip-cn-only.config
@@ -0,0 +1,12 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+
+[ req ]
+distinguished_name     = req_distinguished_name
+prompt                 = no
+
+[ req_distinguished_name ]
+CN = 192.0.2.1
+OU = PostgreSQL test suite
+
+# No Subject Alternative Names
diff --git a/src/test/ssl/conf/server-ip-in-dnsname.config b/src/test/ssl/conf/server-ip-in-dnsname.config
new file mode 100644
index 0000000000..b15649aef7
--- /dev/null
+++ b/src/test/ssl/conf/server-ip-in-dnsname.config
@@ -0,0 +1,18 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+
+[ req ]
+distinguished_name     = req_distinguished_name
+req_extensions         = v3_req
+prompt                 = no
+
+[ req_distinguished_name ]
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
+subjectAltName = @alt_names
+
+# Normally IP addresses should not go into a dNSName.
+[ alt_names ]
+DNS.1 = 192.0.2.1
diff --git a/src/test/ssl/ssl/server-cn-and-ip-alt-names.crt b/src/test/ssl/ssl/server-cn-and-ip-alt-names.crt
new file mode 100644
index 0000000000..4e58c85ccb
--- /dev/null
+++ b/src/test/ssl/ssl/server-cn-and-ip-alt-names.crt
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDLzCCAhegAwIBAgIIICERKRE1UQAwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UE
+Aww3VGVzdCBDQSBmb3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHNl
+cnZlciBjZXJ0czAeFw0yMTExMjkxOTM1NTFaFw00OTA0MTYxOTM1NTFaMEYxHjAc
+BgNVBAsMFVBvc3RncmVTUUwgdGVzdCBzdWl0ZTEkMCIGA1UEAwwbY29tbW9uLW5h
+bWUucGctc3NsdGVzdC50ZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
+AQEA6+8IYKAFnZ7V+fDo1cyMpbGBLzCfJOQ/1o2jOGP4+GjpsZgv6S6UT2MheC8M
+iiEFrYwdsSIZyYc3jEZrluy/UuR0bCGtqU92BCqa0iBLhvHOgjR588u253eLxQtQ
+8iJn11QPrKMk35nMkmY8GfHt4sGFbvBL6+GpipHq7a6cde3Z+v4kCB5dKMYDUDtm
+3mJmviuGNAu5wOqItk2Yi5dwJs1054007KNH0Il43urxiOfnkLS0cG5kehboPf86
+vxBt3iHByrU/9/DY5IvQCfSXVNa6rb5w5/pGja9aCei6Mv1jQY/V8SMQTga+MOsA
+0WB9akxMi2NxwS2+BQ4k/McPlwIDAQABoyUwIzAhBgNVHREEGjAYhwTAAAIBhxAg
+AQ24AAAAAAAAAAAAAAABMA0GCSqGSIb3DQEBCwUAA4IBAQAQLo2RzC07dG9p+J3A
+W6C0p3Y+Os/YE2D9wfp4TIDTZxcRUQZ0S6ahF1N6sp8l9KHBJHPU1cUpRAU1oD+Y
+SqmnP/VJRRDTTj9Ytdc/Vuo2jeLpSYhVKrCqtjqIrCwYJFoYRmMoxTtJGlwA0hSd
+kwo3XYrALPUQWUErTYPvNfDNIuUwqUXNfS0CXuIOVN3LJ+shegg6Pwbh9B5T9NHx
+kH+HswajhdpdnZIgh0FYTlTCPILDrB49aOWwqLa54AUA6WXa35hPsP8SoqL9Eucq
+ifPhBYyadsjOb+70N8GbbAsDPN1jCX9L8RuNcEkxSCKCYx91cWXh7K5KMPuGlzB7
+j8xB
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-cn-and-ip-alt-names.key b/src/test/ssl/ssl/server-cn-and-ip-alt-names.key
new file mode 100644
index 0000000000..837eef996d
--- /dev/null
+++ b/src/test/ssl/ssl/server-cn-and-ip-alt-names.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA6+8IYKAFnZ7V+fDo1cyMpbGBLzCfJOQ/1o2jOGP4+GjpsZgv
+6S6UT2MheC8MiiEFrYwdsSIZyYc3jEZrluy/UuR0bCGtqU92BCqa0iBLhvHOgjR5
+88u253eLxQtQ8iJn11QPrKMk35nMkmY8GfHt4sGFbvBL6+GpipHq7a6cde3Z+v4k
+CB5dKMYDUDtm3mJmviuGNAu5wOqItk2Yi5dwJs1054007KNH0Il43urxiOfnkLS0
+cG5kehboPf86vxBt3iHByrU/9/DY5IvQCfSXVNa6rb5w5/pGja9aCei6Mv1jQY/V
+8SMQTga+MOsA0WB9akxMi2NxwS2+BQ4k/McPlwIDAQABAoIBAQCuNFKVNdKvrUYF
+RLJGmsAG3+eo9lern7TbML2ht39vu9dBwEMwA6qSa3mdCfBSVUuh9uE9lxY/TU3g
+j2aFi81A4VptNPjLGNblAKhMGnhp7UUzspeRQYuNoSFcnpxoDKtrvK/OIq/pQeBh
+AIfECHRDh+yEG32Tb44FuPQkB1eTYl8xbMEImrhNUaSjJk7tTsmydHy0DjmqHVKX
+HUj0TREfDBDOBiHtY0XV6Pu3bnqDH/TKLTfUf3UdfTuay3Yai9aEcRPWp9GrMO7G
+axsKCifTz6177gyr6Fv8HLeMZMh9rMZRn3e0zfaF6vrH1QnZZOts5jpUa0KugSCd
+//uC0iNxAoGBAPXVc3b+o3hY5gcwwpaW6JtsarDrmNRxrizqIDG7NgpqwdFXgTi6
+6q0t2pjv81ATqij69IcPkNSissyR4OEKnu/OFJWzreg8yLi75WHKi0E/6msHpwRk
+d1yP0Zgd05ots/yOjDSp593RagaPVvHBxMECZ/Tm3B+Tq55Azudd/zvLAoGBAPWw
+xf0oUEJl6NdUZD6K7eFc6jf8yrpD85dldeko6LeN8x0XlKKWvUDJ2+3oizXoQvCm
+8by6KOYEIo4MrtXuy9MmtPWfNvRBr+hsUHchIj7IgFa9bKXyK2FnJqu/8CbEymli
+eZu7hoOhelurhnFy1zSqwNO4GC+kw60Y/BO3Z1nlAoGAVOyYJtNwxXJwhKtjjYI0
+ePzLHrNE6J8c/Ick+AkkchTPP/JqwZ5Q0+KzUYITG+avMdkAAGhwMATEn8cFWLjC
+jzUyB0U7Hq9g5/CBHXdLBA+Ae9j46ZuLYH6OeW5UWz7OnsDfzpGjeA2QAxQhhQLb
+ZZHfN8tI39+zucfJskPWmGECgYEAg9guF1Fn6InJrqwR82IYj6SN6CeXHufSM392
+C/4xDDd3rDf4QlwECV2J0RzGf9I5Ae2EshNwWScE6Be0RweTh6cw2tJq6h7J6D8f
+2x4Dw49TF7klMdRIJUf2f5pLpHJccLswqTqzz7V69PCSABVxmUi8m6EiEYconp5W
+v7nfE2UCgYALrEqzncuSIX3q6TVAjnzT7gO4h8h2TUekIWdHQFldFx8R7Kncggnd
+48gQqhewchNR83UCcd7pPsCcTqu6UR1QRdq/DV5P6J3xdZ2iS/2gCM6hvWIvKZEv
+/ClnkyFCOW7zX6RKIXtRYZTV1kz3TajApi34RTIeIMTieaCarnBJbA==
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-ip-alt-names.crt b/src/test/ssl/ssl/server-ip-alt-names.crt
new file mode 100644
index 0000000000..8a1bc620bb
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-alt-names.crt
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDCTCCAfGgAwIBAgIIICERKREEUAAwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UE
+Aww3VGVzdCBDQSBmb3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHNl
+cnZlciBjZXJ0czAeFw0yMTExMjkxOTA0NTBaFw00OTA0MTYxOTA0NTBaMCAxHjAc
+BgNVBAsMFVBvc3RncmVTUUwgdGVzdCBzdWl0ZTCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBAOM8yB6aVWb17ujr3ayU62mxHQoqn4CvG9yXlJvGOGv/ursW
+Vs0UYJdc96LsNZN1szdm9ayNzCIw3eja+ULsjxCi6+3LM4pO76IORL/XFamlTPYb
+BZ4pHdZVB0nnZAAnWCZPyXdnjOKQ5+8unVXkfibkjj8UELBJ2snehsOa+CTkOBez
+zxYMqxAgbywLIYsW448brun7UXpWmqbGK+SsdGaIZ5Sb7Zezc5lt6CrLemTZTHHK
+7l4WZFCCEi4t3sgO8o1vDELD/IE5G8lyXvIdgJg6t8ssper7iCw6S8x+okhjiSjT
+vDLU2g4AanqZRZB49aPwTo0QUcJA2BCJxL9xLy8CAwEAAaMlMCMwIQYDVR0RBBow
+GIcEwAACAYcQIAENuAAAAAAAAAAAAAAAATANBgkqhkiG9w0BAQsFAAOCAQEAwZJ+
+8KpABTlMEgKnHIYb35ItGhtFiTLQta9RkXx7vaeDwpOdPP/IvuvpjpQZkobRgBsk
+bNM0KuJpd2mSTphQAt6eKQIdcPrkzvc/Yh9OK3YNLUAbu/ZhBUnBvFnUL4wn2f1U
+mfO+m8P/LxybwqKx7r1mbaB+tP3RTxxLcIMvm9ECPQEoBntfEL325Wdoj+WuQH5Y
+IvcM6FaCTkQsNIPbaBD5l5MhMLHRULZujbDjXqGSvRMQfns6np/biMjNdQA8NZ5z
+STeUFvkQbCxoA0YYLgoSHL5KhZjXrg2g+T+2TUyCTR/91xf9OoOjBZdixR0S0DzJ
+B1+5vnUjZaCfnSEA7A==
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-ip-alt-names.key b/src/test/ssl/ssl/server-ip-alt-names.key
new file mode 100644
index 0000000000..b210b3a991
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-alt-names.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEA4zzIHppVZvXu6OvdrJTrabEdCiqfgK8b3JeUm8Y4a/+6uxZW
+zRRgl1z3ouw1k3WzN2b1rI3MIjDd6Nr5QuyPEKLr7cszik7vog5Ev9cVqaVM9hsF
+nikd1lUHSedkACdYJk/Jd2eM4pDn7y6dVeR+JuSOPxQQsEnayd6Gw5r4JOQ4F7PP
+FgyrECBvLAshixbjjxuu6ftRelaapsYr5Kx0ZohnlJvtl7NzmW3oKst6ZNlMccru
+XhZkUIISLi3eyA7yjW8MQsP8gTkbyXJe8h2AmDq3yyyl6vuILDpLzH6iSGOJKNO8
+MtTaDgBqeplFkHj1o/BOjRBRwkDYEInEv3EvLwIDAQABAoIBACp3uY6+mSdc3wF4
+0zzlt/lQuHSl8plCIJrhWUyjhvfoGyXLzv0Uydh/72frbTfZz1yTSWauOXBKYa6a
+/eqb+0DIsf8G8uLuTaqjsAWKVOoXkoKMGkistn7P9UTCkdXVhIvkbWp7V8EgA7iX
+pZ/fzBPIsyzmuxe3NcR0ags0cxuxkNuu+YXDv1oTedmT2wS3CZq1d/T1Y/EOVIf8
+Iznd2aOverlsnt6iiQ3ZWdG/W5F8FhnrR/rrBdYsdCv6TH/KUYexnDOUYpayjDbu
+oAKnifPp6UqiOM4SuBL83OAz19jptp5vpF370BEVRs3eK0q+zo/mETjv9HsXdolZ
+lfoXA0ECgYEA/7nb2azbq/2muvXCh1ZxCEbn3mt8KXoJP/xkx/v9eEc/cc5Q9e0V
+2oGfjC2hSE+bjOWMwiUMD6uU+iRjhz5A3IvUxnoSdoL7H9p0hTqLMyP7dTDkoVF5
+aEuLMaiI5YEnfAFu9L5h8ZKieoQTBoscT06wnGjh9pBV9bthfTKA7ksCgYEA43sb
+55m9WL4kWCPwOAp3vdEAFyxzmZHlO26sEQOU/m5aN01pumYybBruziEXMI96yfTj
+VmXKReeYb6XUiCcs3fLSipD/+8/8CsjO4uMORtxWumXe8AbKZfysGFzL7wJlByGT
+38AGQwIG/XD8cKnaiEMX4E/3Owbcoxwixo3WZC0CgYEAovaqJ9mEU+Jc8h/TS7PG
+bGPjN1Z/1V6zrlcFUnw/Vvrwb3HvHglsN8cLCaW6df5lPjC6tq4tNX8+fPnbg0Ak
+zWc+vQzl3ygxKGdqgcyBEKIJiPETgcoN+GzL02V3d+oKY3f2YXlBqVSsvi6UgUL9
+U3zuB36/IQVyAhrbUZFxoGkCgYEAnaFAO+Nvrp/LhXwZyGuQf+rkmipGTIMpil5t
+QzjtNMV5JFszSWPpyrl7A0Ew1YiG+I0GP2c3m+sY2TzbIiGrWH0b4cMKbw63Qy3V
+FqlpyjaCrpVKv56k/7jv883RzuQk56Uf1+szK5mrCFITy2oXsVZ0pA4lbjSaDTjA
+7D968V0CgYEA+qKqXKL98+c5CMPnpf+0B1x2zgyUym1ouPfon2x5fhK84T53zDMA
+zfdUJ/SOZw6/c9vRF7RL8h+ZfFdIyoAXv4Tt6mIiZe7P+AUVg6XgJ0ce2MUSeWjI
+W8D4WdSi0jyqr99TuVBWhbTZJviMB3pHqKaHQ07hnd/lPtvzsiH12qk=
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-ip-cn-and-alt-names.crt b/src/test/ssl/ssl/server-ip-cn-and-alt-names.crt
new file mode 100644
index 0000000000..2be02feb03
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-and-alt-names.crt
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDHTCCAgWgAwIBAgIIICIBBBQ2MQAwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UE
+Aww3VGVzdCBDQSBmb3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHNl
+cnZlciBjZXJ0czAeFw0yMjAxMDQyMjM2MzFaFw00OTA1MjIyMjM2MzFaMDQxHjAc
+BgNVBAsMFVBvc3RncmVTUUwgdGVzdCBzdWl0ZTESMBAGA1UEAwwJMTkyLjAuMi4x
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwmqTdQJfs2Ti9tPitYp2
+27I0HvL/kNSgA6egFr0foRo0BorwJNIzdbV0+EnsfiBNTWL5It26gqO7UP3ms8t2
+vHD5gkXfT+f6ts0lVJEcIOkUD/8ws4Ic9Y4uPqb4gN+pUKqcxtmLW1TYk84MBK59
+Xz4yPPS6N+G/DMMeFHTNkM9EQwn/+DC3fDsWdGYM2GRWDTJGg1A5tSUcF+seu7i1
+Vg7XajBfsvgAUAsrAxV+X/sLZh94HY+paD6wfaI99mY2OXVc/XW/z1r9WQznor65
+ZkonNCaPfavqPG5vqnab9AyQcqPqmX8hf/xrniASBAkqNCctbASrFCIYvCJfGfmX
+EQIDAQABoyUwIzAhBgNVHREEGjAYhwTAAAIChxAgAQ24AAAAAAAAAAAAAAABMA0G
+CSqGSIb3DQEBCwUAA4IBAQBf7kmYfRYfnWk1OUfY3N1kaNg9piBBlFr9g+OQn9KU
+zirkN7s0ZQbCGxV1uJQBKS58NyE414Vorau77379emgYDcCBpDIYpkLiNujVrIOr
+ggRFKsFRgxu4/mw0BSgCcV8RPe9SWHZ90Mos7TMCnW/PdxOCD1wD0YMkcs0rwB3l
+0Kzc7jDnfOEvmgw/Ysm7v67ps+05Uq5VskQ6WrpSAw6kPD/QMuuBAX8ATPczIaox
+zAMyncq1IiSIwG93f3EoQQThdQ70C6G9vLcu9TtL6JAsEMFEzR99gt1Wsqvmgl9W
+kStzj1yjIWeo5gIsa4Jgcke1lZviWyrTxHDfyunYE5i5
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-ip-cn-and-alt-names.key b/src/test/ssl/ssl/server-ip-cn-and-alt-names.key
new file mode 100644
index 0000000000..54fe80fc68
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-and-alt-names.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAwmqTdQJfs2Ti9tPitYp227I0HvL/kNSgA6egFr0foRo0Borw
+JNIzdbV0+EnsfiBNTWL5It26gqO7UP3ms8t2vHD5gkXfT+f6ts0lVJEcIOkUD/8w
+s4Ic9Y4uPqb4gN+pUKqcxtmLW1TYk84MBK59Xz4yPPS6N+G/DMMeFHTNkM9EQwn/
++DC3fDsWdGYM2GRWDTJGg1A5tSUcF+seu7i1Vg7XajBfsvgAUAsrAxV+X/sLZh94
+HY+paD6wfaI99mY2OXVc/XW/z1r9WQznor65ZkonNCaPfavqPG5vqnab9AyQcqPq
+mX8hf/xrniASBAkqNCctbASrFCIYvCJfGfmXEQIDAQABAoIBAB6GgVST5NbT9lbu
++d+rN/JSzqA1Yy8oU19/iEFJvJec96I3WnFNl8rZjN4XLUy4YarO6XMyAUDV2Gll
+FD4Sqjf4PRTZR7DSKaleGIhoqFP6hK3mUY091rIves9XhBkoBPunbipCqgDTF5ZN
+edGaXBECQP0VJ8/yX/7u++AWXthnjDis9X0taZfFg/PYbV7SCJ1Hg1O/wEsgXlnC
+7mbL6wkCW0f6700B0x1kKbZqJY95xRqp6Ipq2lIQbJDdGywoj0WzKqNltf9cer+r
+cXl8WjeiMvvvpl4uGhckAbzUifUzxN6A3f1fu/XKtOmabMi9t7J4MRfgOgedgtQB
+0jaZGSkCgYEA+lBLnNY6M48HX2mdtr86+n41gh69v8Z7oNikJFDZkodrvI8uqE0i
+0XwnYPFddt8NbmuUhhuzI2M8RKhGLgdlbKpkSSVafnMfcxRmX2EAtWQgdvX1Iult
+752LWdBgSuw2vlzvy3T/GYnjMrXSCGput4amqojMEbvUGvIdSUMdHGMCgYEAxtU1
+WixKPL6aEnYy1f4bybzcNgGtl8PBRz9xw+P46g+ijOPoaG9O73Tr7An11AO003Ot
+DHhMW+b8yHLyxoKwS2sU2cN/lKB8xNQYZc1D61RNJlzgnHMXnA0lcH0I3M35fqKr
+/71pD1ZP40SSJS+od/KEjW80XzuOdyiXg8q81vsCgYEAnUPLbbsuj+whzrFVlFZr
+IKwgxCK6Rn3WeIUEA4kEWUpZxvsSbk0gPgtJ1l9uwFt9Xc2bX/KRRv93Aw/SH+Mn
+tvEK1uXwCBgePzgm5W/VeSFyQCthm1CbcHtD7Oa9SPVFo65SPjrAd3QpWVfgoMb1
+zrp7hhMyW0XuCgvpmHjhFk8CgYEAxq/thXM2p+bLLWGhwQcRG5G299zLbBl4PUsf
+0uEvLi17gJCKADoiRdSvoAn/9eHSQ26XYRuhKkDzHxcGlOmpY2PYzRa3mXyZ0VIk
+Iy5wDWwLQCeVZ6D22cClRfgb8BF/nFTPzVmn72SPpgoyhChQj7PvUynpyrRH07jj
+VxYziBsCgYAFr37Xbl0VnXVK+XU+vMwUZjcF4jpoCr7SFZqgRbW2GbYSUoMuPXns
+RnJh+Fvi1NUei+E5s1H4P1pVq4p0jFxP4GvH/qvNjnIn/Er3bbqvpox6dWUJXprq
+qTQSDIeoDC/V8cyRoIfqPvTVqY8Rgew6GEkv0bAImdxhoSng7vIseg==
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.crt b/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.crt
new file mode 100644
index 0000000000..23c06da01c
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.crt
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDQzCCAiugAwIBAgIIICIBBBQ2MQEwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UE
+Aww3VGVzdCBDQSBmb3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHNl
+cnZlciBjZXJ0czAeFw0yMjAxMDQyMjM2MzFaFw00OTA1MjIyMjM2MzFaMDQxHjAc
+BgNVBAsMFVBvc3RncmVTUUwgdGVzdCBzdWl0ZTESMBAGA1UEAwwJMTkyLjAuMi4x
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8xddbo/x2TOSIa/br8BN
+o/URdTr9+l2R5YojiZKDuLxiQVkgC30PJ2/CNFKIh2nHhRrzknI6sETVtrxZ+9V2
+qRc1yShVu462u0DHPRMIZnZIOZg3hlNB0cRWbOglUKlttIARNEQUcTUyPOtyo4/v
++u0Ej5NTNcHFbFT01vdD9MjQiCO3jKdAwPIb14jTg4C71EpZ+LuelDo4DzF2/XgG
+WqUTrgD/XnBU/60PU9Iy3G0nVpx21q6ppn9G7a9R+i8FjBcwW1T+cfsBDWhAv+bi
+RmSAkENf8L8TwOlDQUwROkfz3Hz36vuJjdkreQJsiqL0HnrnH5T5G9UzJO86FvZQ
+5wIDAQABo0swSTBHBgNVHREEQDA+gh1kbnMxLmFsdC1uYW1lLnBnLXNzbHRlc3Qu
+dGVzdIIdZG5zMi5hbHQtbmFtZS5wZy1zc2x0ZXN0LnRlc3QwDQYJKoZIhvcNAQEL
+BQADggEBAF+mfaw6iBPzpCgqq830pHRa3Yzm1aezt8SkeRohUYHNv/yCnDSRaqtj
+xbENih3lJMSTBL3g0wtTOHfH8ViC/h+lvYELHzXKic7gkjV7H5XETKGr0ZsjBBT2
+4cZQKbD9e0x0HrENXMYgGpBf747qL6uTOVJdG0s15hwpLq47bY5WUjXathejbpxW
+prmF8F+xaC52N9P/1VnqguQB909F4x1pyOK7D7tjFu+Y8Je7PHKbb6WY5K6xAv6t
+R17CY0749/FotlphquElUR2bs5Zzv5YrjUHPTcbwKvcH5cdNi93/u6NJt2xNAoYf
+aZERhX5TA9DYk4gC8OY0yGaYCIj3Dd4=
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.key b/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.key
new file mode 100644
index 0000000000..0ace41e0a1
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEogIBAAKCAQEA8xddbo/x2TOSIa/br8BNo/URdTr9+l2R5YojiZKDuLxiQVkg
+C30PJ2/CNFKIh2nHhRrzknI6sETVtrxZ+9V2qRc1yShVu462u0DHPRMIZnZIOZg3
+hlNB0cRWbOglUKlttIARNEQUcTUyPOtyo4/v+u0Ej5NTNcHFbFT01vdD9MjQiCO3
+jKdAwPIb14jTg4C71EpZ+LuelDo4DzF2/XgGWqUTrgD/XnBU/60PU9Iy3G0nVpx2
+1q6ppn9G7a9R+i8FjBcwW1T+cfsBDWhAv+biRmSAkENf8L8TwOlDQUwROkfz3Hz3
+6vuJjdkreQJsiqL0HnrnH5T5G9UzJO86FvZQ5wIDAQABAoIBAGv0BFoFMrHyZQLw
+xe7Wx6P4QTh+aiu1QgVdw0pk9nojrr62hbSUZRZuWyBBRsBcCW7i+Sgf8lA1QXNV
+UeC0e228EPa0It6YEi42JkTJHwHhpVFud7n/X0t4lajnryqTE1UFSp6bXTipFxZW
+uSJJ2ZjliRD5rApDcxkY4WJVjKg3aEt7P/DiM8iKGfyE6stq72VjEbJjdViMEcOP
+BNf0TiREZz5Mp7jAVWhpen0ebbLOBVWV4/ONNcL+yqR4mCEDUSFGewrTVX4zHL0A
+hYk198C5F8sFvEDnFkPco9sXMVanmLoI8sbhP4IIz9g4+GU6kFuj7fUKp11Azqv+
+3WQDKYECgYEA/XG4mmG/g8FG44y42mfZpUXWi1pwU4CQIrhkoU5j7EPQrvRboOOE
+Rv95jSwyZu4vCqjyI5FN1jCGTdhmt++R1e//zH6Hqa9Smo+jw7DtAFrCYd1JnCf1
+ToOwsYPHv4P7A8q8kc5vCNIv+AQSlP/wqdVNo3grdf7cGXkMtEY4F9UCgYEA9Yrq
+zWdnNGPATuSBqL6TSjQ37oR+dBD6WnGsiDenQkOzyDPFZ3CT1DjJghjEtxc8EfNf
+Oo8dMMR2q+5FZQo7WuqONEgyzKePiNR8RK2gOYpgdjN9bih1sAhHR10D26cpwlDJ
+bx7D5ZzENLbdZmfEiWwKswnaIhN4yMalgE0mP8sCgYAhzJy12ftUct4lUosEdX0N
+EXc/NlxshmSyfKzO5kllJNYbvvLJTg5B+agYL6C5IWKcpVNFcwdSXT5L+2QXe5eT
+VGJkvysQchUuD6HjYyD4PyJVMtGyRZHtWpqh0dU9sTg0lUD4oPMl1gIXrVNdE5Tg
+0VV9S3VgUxC/ROlw0TyB0QKBgGsVE0NS9hJF8mc1hko2GnwA++d8Rr2NbfElo+2f
+/8SJTA1ibpOm6AFkZpTjAl8qtdrKPVyHb16GP47Jkd/3r1z979hjKCxSYul0aWF2
+KusNKvZBjFEPOgv0AEniCb2wUCjbHI3mZ95qGLM4kKOJW4/m21+rS0MTJNjCsQic
+HLMzAoGAeCsY09d3m8xGeU+DuTPC6GH7Sgy/NBYqS5VaVNjb2jnuZlW2SSW2oiID
+4tXTi4ruKmHC898BfyFxhSMqub+tg3pVqIYADC71rnJLrVyc1SzoWzL7yMT3qFj7
+C7ZYZYmfG9agcZb5NkqKPTfCxkBhWbdgTTgBKVO/xQst8EUgko8=
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-ip-cn-only.crt b/src/test/ssl/ssl/server-ip-cn-only.crt
new file mode 100644
index 0000000000..9bf015cf18
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-only.crt
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC8TCCAdkCCCAhESkRN1IAMA0GCSqGSIb3DQEBCwUAMEIxQDA+BgNVBAMMN1Rl
+c3QgQ0EgZm9yIFBvc3RncmVTUUwgU1NMIHJlZ3Jlc3Npb24gdGVzdCBzZXJ2ZXIg
+Y2VydHMwHhcNMjExMTI5MTkzNzUyWhcNNDkwNDE2MTkzNzUyWjA0MR4wHAYDVQQL
+DBVQb3N0Z3JlU1FMIHRlc3Qgc3VpdGUxEjAQBgNVBAMMCTE5Mi4wLjIuMTCCASIw
+DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANWs1uUL71nHYF9Zj6p+M3MpYDvx
+32iCjVdtH5a2qpSWHXTg0rR8dLX0y92cvOYvMXHRajZT1avpHr8dooPYSVaXpGMK
+NvF/Qi+WFYovRbP2vmd1yv1cgW/FggbwJFWVobizIz4seyA4d0B2j9fqoi2OFBNP
+huW664SjF0u3p21tDy+43i2LNUMAKf6dnRR5Vqenath87LEU41tSLudu6NXgbFMk
+jvfNkl4d0w7YCzeXmklmSI+uaX3PlJJ4NzQO2j8w5BvnKVhNVD0KjgrXZ6nB/8F7
+Pg3XY+d7rJlwRgXemU6resWQDJ7+UaC9u7I4EIP+9lzCR/nNBqUktpHRmHUCAwEA
+ATANBgkqhkiG9w0BAQsFAAOCAQEAos1JncV8Yf4UaKl6h1GdYtcVtzFyJvBEnhRD
+07ldL+TYnfZiX8wK2ssBtM3cg/C78y5bzdUa5XGS83ZKQJFFdhE7PSnrvyNqyIqY
+ZgNBxto3gyvir+EjO1u9BAB0NP3r3gYoHRDZS1xOPPzt4WgjuUgTLM9k82GsqAbO
+UrOTOdRnkIqC5xLpa05EnRyJPRsR1w1PRJC2XXKnHIuFjMb4v7UuPwyCcX1P5ioc
+rQszQcORy/L+k0ezCkyweORg68htjYbBHuwOuiGfok6yKKDMzrTvD3lIslls6eX7
+4sI3XWqzkPmG9Vsxm9Vu9/Ma+PRO76VyCoIwBd+Ufg5vNXhMmw==
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-ip-cn-only.key b/src/test/ssl/ssl/server-ip-cn-only.key
new file mode 100644
index 0000000000..1966530e72
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-only.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEA1azW5QvvWcdgX1mPqn4zcylgO/HfaIKNV20flraqlJYddODS
+tHx0tfTL3Zy85i8xcdFqNlPVq+kevx2ig9hJVpekYwo28X9CL5YVii9Fs/a+Z3XK
+/VyBb8WCBvAkVZWhuLMjPix7IDh3QHaP1+qiLY4UE0+G5brrhKMXS7enbW0PL7je
+LYs1QwAp/p2dFHlWp6dq2HzssRTjW1Iu527o1eBsUySO982SXh3TDtgLN5eaSWZI
+j65pfc+Ukng3NA7aPzDkG+cpWE1UPQqOCtdnqcH/wXs+Dddj53usmXBGBd6ZTqt6
+xZAMnv5RoL27sjgQg/72XMJH+c0GpSS2kdGYdQIDAQABAoIBAQDNXviU4WnF8rmQ
+K7bH+dBdqbETLKC8BG7xTrMD2sINWlMpmUUrsEtE7+paMGHnJAj0CoF5gg5m0wN4
+UXV4H5QtpEad4p14dAYbUreVP2ZRWKEdM7xM1HKcCUu2e22QzObJbXQ8N+iHyX3k
++Y+7yYrjGiH1hYR0nbnsnAyx++zyYBSQeqzpdQwf/BLY5xZmyYWNfqbckiMpEqMs
+EmZmGXnCjIipzEC0LQHoSW9PNa92Z9bvuxOKYl8iHYDDXjvMRFoZBSiMXpzHQocb
+QlQ5F4ayfW2OrOhpNbY7niYM9GN3Bk9TgMP+0BkJE6uuktLYW35LY1M78CCPWcWb
+npJNK3QBAoGBAOxkGrhAHAysSmtirIyMdvySb76wb/Ukfi+AULKz20FI5j4/GXm9
+qCb2GeT+FFSUHeSC8f0EFnosRYkdBGruqeZioI+5rUkboYFJPspAHAuvg9kgtfF+
+kvphD4O4P/foYsEZRx66FHozDbhrrR5UXc7KzqRIASc/D3FOx2UFJLb1AoGBAOdm
+WcaMvYygl9ZW+ThWAR1xG1X70AGKwrlrpF2hBkWYxSurxSMXnD0DUzC9Nb4EyCaM
+c2uSqEZOKdW+XfXtK2DnqXKfb3YCVEoGN4gVfyuW/vxii/+ZxLo3md/b3vrkZEVp
+pfkXy/HoZ71YN7bNpcDpOnhml6vvuCRCYFnI1WuBAoGAC0shB6pwbJ6Sk5zMN47C
+ZICufAK75o9OxAAyWsdC81SDQ3gKRImuDeZ2CD2nRP8qim9DFl5qoH2a+Nj9DArI
+7SvLFfK9958tURrpuAnmDRzehLIOXzI33WRjtFxKGhLtHOKTRkGHlur3fdcPF0La
+lHWV971E6NYXa8diuU3Mmj0CgYBYd+ka3/QYL83dRKNDxp3mg7fPx9ZewI5yFZVh
+to6PTTkU2Tclk4FIUl0b5TsGyw06r7fxCMENIBUegwmpXGOZSPifuhUDKSDQrE/O
+12knYTNbitG7hy6Pg3JxA77cbTVo1FuAQHjYo+IFohSq7zTP7FtObOrP8XaVZksw
+CHiQAQKBgBW4EiA9AAnZ1LOpifAvM7bs0NHg95qTwtAL52WKom2ga2H+lMhxeu6Y
+hUSytC/f9kALVcYloZhkLYpO07x1gXmy7f4parMjA4Ex+4vfu3kPd8GiNGZ+AUJD
+nnJ1OINY9ziXJZfju7FpVWpkiuPzWCh6y/o3gZ/veq5mIUxuDMVa
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-ip-in-dnsname.crt b/src/test/ssl/ssl/server-ip-in-dnsname.crt
new file mode 100644
index 0000000000..78ad8d99c8
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-in-dnsname.crt
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC/DCCAeSgAwIBAgIIICIDFRVYUgAwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UE
+Aww3VGVzdCBDQSBmb3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHNl
+cnZlciBjZXJ0czAeFw0yMjAzMTUyMjU4NTJaFw00OTA3MzEyMjU4NTJaMCAxHjAc
+BgNVBAsMFVBvc3RncmVTUUwgdGVzdCBzdWl0ZTCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBAMpn5bP1/OfBQR/yvOkOBzxArE1j1YShVa2pcj896+CVDEgV
+N5Hluz7KHU/JYzNZCAHb5WAHuvXxKeoj4Ti5be1KsqO0mN1p+RMN7VlCpCpb0AWT
+z4z+I8TUhSZnmgghHvfW4RfcZMCcHq1vevVTDxR/cAbDPYpgBCD5F/SZMRyMDw5B
+7ILLmft0eqA1nCqavyqBCGZvx1ol8N5BfVdrDXp/rN5997khBWQRZ8g84FZyFZXf
+pwp57eu0OGQDzZFXoEL2t4OVld67K5jcclWVxHY6FGcHjCvyqs48PCPOR84anZwj
+GsqVOS6250/DWKBQO4KyhkTVf0AW/ICGSMOKkAkCAwEAAaMYMBYwFAYDVR0RBA0w
+C4IJMTkyLjAuMi4xMA0GCSqGSIb3DQEBCwUAA4IBAQDIAAH0WJKEpbPN0QihN6SF
+UA5WL4ixsBACo9OIAGkSnKeOeVEG5vvgOna0hjQcOcgtI1oCDLhULcjCuwxiIW6y
+QntOazyo0sooJr0hEm2WfipvIpQs6W9E1OTcs624BAVfkAwr6WT2VwoIAPcQD2nR
+tIQhSUIR9J7Q5WbzuQw7pthQhBfW/UPWw7vajel0r1dflbe0Cgp5WGNfp1kYy+Qf
+XW/YjkstZEP1KFm+TF58uxrIDmYboS8EerUREGQixijbI0AfXjShxtiyS63rbdpo
+3C0BPj9Yx2VtWi4U0qoef/iLJxJBCLvE/97+duPdKx0AkkOWA9VuenkWLp797UM8
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-ip-in-dnsname.key b/src/test/ssl/ssl/server-ip-in-dnsname.key
new file mode 100644
index 0000000000..ba319b001e
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-in-dnsname.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAymfls/X858FBH/K86Q4HPECsTWPVhKFVralyPz3r4JUMSBU3
+keW7PsodT8ljM1kIAdvlYAe69fEp6iPhOLlt7Uqyo7SY3Wn5Ew3tWUKkKlvQBZPP
+jP4jxNSFJmeaCCEe99bhF9xkwJwerW969VMPFH9wBsM9imAEIPkX9JkxHIwPDkHs
+gsuZ+3R6oDWcKpq/KoEIZm/HWiXw3kF9V2sNen+s3n33uSEFZBFnyDzgVnIVld+n
+Cnnt67Q4ZAPNkVegQva3g5WV3rsrmNxyVZXEdjoUZweMK/Kqzjw8I85HzhqdnCMa
+ypU5LrbnT8NYoFA7grKGRNV/QBb8gIZIw4qQCQIDAQABAoIBAA2kPP4JCTeRddMy
+Z/sJIAG2liZNITnkKcMflXyfrsMfKIm/LFSf+CO+OYWEHDR8vqZpbKcxPi+PRnTq
+YCaTkM4aZ7nS1S6vEsNu/90xOaFFONr3YFivVDfS3vp8pwv/N3gaumcCSqQUoZis
+18urAmwuPp2mEQK/f+e9AhlRLdcvlqDyKm+zMrVixK77Hj5JiEkh3rfZ3onHHKGE
+B7T2XRRqnZ4FCN9qLH2pMGUknZ4MGC9SlCyoerXFodb4DhKWQhJDRLjb8qP96r/E
+FGSg5WUiAERU/OgODoqZNTeIwIDB/f9NK45dEY3Hw6BsSFfU2VChrlNoVlzFUx2k
+yaH5Y4ECgYEA8rht3crh3GTy0jBJjNqB2iul8fkG/uiaiSvERWT/+KZnmV1+JGAW
+h2/wvd5apagOJjqKY0bCHMei/qYF9r4yJnkIy4qNper3QUz7TMCjsWduCm8S834A
+Z+Vwi3RBGJiQQH9Dfexko5sDjo+w5g4RsH52INCeReInNdxHOv06jZECgYEA1XrR
+QNwZlxHt3H93YKmKDZXikqW12Cuq6RSwf5VVdeuzV+pUN+/JaSgEuYsBilW7Q5p2
+gPROi0l8/eUPsBJb+dh1BcGzSjI2Kkzf66QOTG83S7tCPwQhwJUAylFuADvURjPQ
+qvqNjbQUomdm2QjBzyWtiFbolqxBgM3dnE6R/vkCgYBYGqQexx83LhmKPGbmTwal
+mARzkg59BxfZRN7IxcG4k0a1v98i+xISdYqwkP7cdOU18Tf8k1mwsrKytrcheqaf
+mn2bzJ5gJKs9s+DgWmjQ45dpCCqb4hfpnro8lKVwdSifkNKB6gYZ8RHYdMYkq+S1
+6SGeBbv95/qNrXjZq8POUQKBgHyaDwD4dsdCY79LdvYofrenQHOv3Q+rjTo2JT6S
+fysww6EQ2M89WiXSgc96Xw/LMl4nDfv+nMmXvyjCRgHS9XRC7yrJAEjSPeM6s4fq
+XZ4nW/ML/YKiesDZN3jfRoFEaoX/QFBLpcuLzG9uQw1ymwy5RSxK7b7kE+eGQU82
+XOihAoGBAI3xvT9fG3jRsSuw/8OQBlmDUFZcT0fRPRZ3pg8XlSreAam4b607d2WY
+u/bBHIclG3CLJ2EFqBtxl9AQeM0OTweF0KmV3dbtdBmaTbnhbK8/NLYnl5+aosEJ
+YrFKD8k8z6z+mYQs+7bAnfRa53TjfC7f24BpgEQyEfKL2fa3PF+J
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/sslfiles.mk b/src/test/ssl/sslfiles.mk
index 7ed3a30f5c..d6a3870079 100644
--- a/src/test/ssl/sslfiles.mk
+++ b/src/test/ssl/sslfiles.mk
@@ -22,9 +22,15 @@
 # key/certificate pair will be generated for you, signed by the appropriate CA.
 #
 SERVERS := server-cn-and-alt-names \
+	server-cn-and-ip-alt-names \
 	server-cn-only \
+	server-ip-cn-only \
+	server-ip-cn-and-alt-names \
+	server-ip-cn-and-dns-alt-names \
+	server-ip-in-dnsname \
 	server-single-alt-name \
 	server-multiple-alt-names \
+	server-ip-alt-names \
 	server-no-names \
 	server-revoked
 CLIENTS := client client-dn client-revoked client_ext
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index d8eeb085da..9cc6fed51f 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -229,6 +229,30 @@ $node->connect_fails(
 	  qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/
 );
 
+# Test with an IP address in the Common Name. This is a strange corner case that
+# nevertheless is supported, as long as the address string matches exactly.
+switch_server_cert($node, certfile => 'server-ip-cn-only');
+
+$common_connstr =
+  "$default_ssl_connstr user=ssltestuser dbname=trustdb sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+
+$node->connect_ok("$common_connstr host=192.0.2.1",
+	"IP address in the Common Name");
+
+$node->connect_fails(
+	"$common_connstr host=192.000.002.001",
+	"mismatch between host name and server certificate IP address",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.1" does not match host name "192.000.002.001"\E/
+);
+
+# Similarly, we'll also match an IP address in a dNSName SAN. (This is
+# long-standing behavior.)
+switch_server_cert($node, certfile => 'server-ip-in-dnsname');
+
+$node->connect_ok("$common_connstr host=192.0.2.1",
+	"IP address in a dNSName");
+
 # Test Subject Alternative Names.
 switch_server_cert($node, certfile => 'server-multiple-alt-names');
 
@@ -281,7 +305,54 @@ $node->connect_fails(
 	  qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/
 );
 
-# Test server certificate with a CN and SANs. Per RFCs 2818 and 6125, the CN
+# Test certificate with IP addresses in the SANs.
+switch_server_cert($node, certfile => 'server-ip-alt-names');
+
+$node->connect_ok(
+	"$common_connstr host=192.0.2.1",
+	"host matching an IPv4 address (Subject Alternative Name 1)");
+
+$node->connect_ok(
+	"$common_connstr host=192.000.002.001",
+	"host matching an IPv4 address in alternate form (Subject Alternative Name 1)");
+
+$node->connect_fails(
+	"$common_connstr host=192.0.2.2",
+	"host not matching an IPv4 address (Subject Alternative Name 1)",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.1" (and 1 other name) does not match host name "192.0.2.2"\E/
+);
+
+$node->connect_fails(
+	"$common_connstr host=192.0.2.1/32",
+	"IPv4 host with CIDR mask does not match",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.1" (and 1 other name) does not match host name "192.0.2.1\/32"\E/
+);
+
+$node->connect_ok(
+	"$common_connstr host=2001:DB8::1",
+	"host matching an IPv6 address (Subject Alternative Name 2)");
+
+$node->connect_ok(
+	"$common_connstr host=2001:db8:0:0:0:0:0:1",
+	"host matching an IPv6 address in alternate form (Subject Alternative Name 2)");
+
+$node->connect_fails(
+	"$common_connstr host=::1",
+	"host not matching an IPv6 address (Subject Alternative Name 2)",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.1" (and 1 other name) does not match host name "::1"\E/
+);
+
+$node->connect_fails(
+	"$common_connstr host=2001:DB8::1/128",
+	"IPv6 host with CIDR mask does not match",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.1" (and 1 other name) does not match host name "2001:DB8::1\/128"\E/
+);
+
+# Test server certificate with a CN and DNS SANs. Per RFCs 2818 and 6125, the CN
 # should be ignored when the certificate has both.
 switch_server_cert($node, certfile => 'server-cn-and-alt-names');
 
@@ -299,6 +370,39 @@ $node->connect_fails(
 	  qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/
 );
 
+# But we will fall back to check the CN if the SANs contain only IP addresses.
+switch_server_cert($node, certfile => 'server-cn-and-ip-alt-names');
+
+$node->connect_ok("$common_connstr host=common-name.pg-ssltest.test",
+	"certificate with both a CN and IP SANs matches CN");
+$node->connect_ok("$common_connstr host=192.0.2.1",
+	"certificate with both a CN and IP SANs matches SAN 1");
+$node->connect_ok("$common_connstr host=2001:db8::1",
+	"certificate with both a CN and IP SANs matches SAN 2");
+
+# And now the same tests, but with IP addresses and DNS names swapped.
+switch_server_cert($node, certfile => 'server-ip-cn-and-alt-names');
+
+$node->connect_ok("$common_connstr host=192.0.2.2",
+	"certificate with both an IP CN and IP SANs 1");
+$node->connect_ok("$common_connstr host=2001:db8::1",
+	"certificate with both an IP CN and IP SANs 2");
+$node->connect_fails(
+	"$common_connstr host=192.0.2.1",
+	"certificate with both an IP CN and IP SANs ignores CN",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.2" (and 1 other name) does not match host name "192.0.2.1"\E/
+);
+
+switch_server_cert($node, certfile => 'server-ip-cn-and-dns-alt-names');
+
+$node->connect_ok("$common_connstr host=192.0.2.1",
+	"certificate with both an IP CN and DNS SANs matches CN");
+$node->connect_ok("$common_connstr host=dns1.alt-name.pg-ssltest.test",
+	"certificate with both an IP CN and DNS SANs matches SAN 1");
+$node->connect_ok("$common_connstr host=dns2.alt-name.pg-ssltest.test",
+	"certificate with both an IP CN and DNS SANs matches SAN 2");
+
 # Finally, test a server certificate that has no CN or SANs. Of course, that's
 # not a very sensible certificate, but libpq should handle it gracefully.
 switch_server_cert($node, certfile => 'server-no-names');
-- 
2.25.1

v11-0003-squash-libpq-allow-IP-address-SANs-in-server-cer.patchtext/x-patch; name=v11-0003-squash-libpq-allow-IP-address-SANs-in-server-cer.patchDownload
From 44734feef1404d56849cb87e0bbbaef6d1291031 Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Wed, 5 Jan 2022 15:47:03 -0800
Subject: [PATCH v11 3/3] squash! libpq: allow IP address SANs in server certs

Per review, provide a pg_inet_pton() interface for IPv6 and refactor the
internals accordingly. IPv4 is not yet implemented. The call sites that
just want standard addresses without a CIDR mask have been updated to
use the new function.

Internally, the IPv6 parser now takes an allow_cidr boolean. I've
plumbed that all the way down to getbits() in order to minimize the
amount of code touched, at the expense of an unnecessary function call
in the failing case.
---
 src/include/port.h                       |  1 +
 src/interfaces/libpq/fe-secure-common.c  |  7 +--
 src/interfaces/libpq/fe-secure-openssl.c |  2 +-
 src/port/inet_net_pton.c                 | 66 ++++++++++++++++++------
 4 files changed, 52 insertions(+), 24 deletions(-)

diff --git a/src/include/port.h b/src/include/port.h
index 2852e5b58b..fd046f4c24 100644
--- a/src/include/port.h
+++ b/src/include/port.h
@@ -517,6 +517,7 @@ extern char *pg_inet_net_ntop(int af, const void *src, int bits,
 
 /* port/inet_net_pton.c */
 extern int	pg_inet_net_pton(int af, const char *src, void *dst, size_t size);
+extern int	pg_inet_pton(int af, const char *src, void *dst);
 
 /* port/pg_strong_random.c */
 extern void pg_strong_random_init(void);
diff --git a/src/interfaces/libpq/fe-secure-common.c b/src/interfaces/libpq/fe-secure-common.c
index 2c0af62afe..9c408df369 100644
--- a/src/interfaces/libpq/fe-secure-common.c
+++ b/src/interfaces/libpq/fe-secure-common.c
@@ -211,12 +211,7 @@ pq_verify_peer_name_matches_certificate_ip(PGconn *conn,
 
 		family = PGSQL_AF_INET6;
 
-		/*
-		 * pg_inet_net_pton() will accept CIDR masks, which we don't want to
-		 * match, so skip the comparison if the host string contains a slash.
-		 */
-		if (!strchr(host, '/')
-			&& pg_inet_net_pton(PGSQL_AF_INET6, host, addr, -1) == 128)
+		if (pg_inet_pton(PGSQL_AF_INET6, host, addr) == 1)
 		{
 			if (memcmp(ipdata, addr, iplen) == 0)
 				match = 1;
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index 7656c3a75e..b70bd2eb19 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -555,7 +555,7 @@ is_ip_address(const char *host)
 	unsigned char	dummy6[16];
 
 	return inet_aton(host, &dummy4)
-		|| (pg_inet_net_pton(PGSQL_AF_INET6, host, dummy6, -1) == 128);
+		|| (pg_inet_pton(PGSQL_AF_INET6, host, dummy6) == 1);
 }
 
 /*
diff --git a/src/port/inet_net_pton.c b/src/port/inet_net_pton.c
index 5bae811c6f..ae0d1fd2e3 100644
--- a/src/port/inet_net_pton.c
+++ b/src/port/inet_net_pton.c
@@ -42,8 +42,8 @@ static const char rcsid[] = "Id: inet_net_pton.c,v 1.4.2.3 2004/03/17 00:40:11 m
 
 static int	inet_net_pton_ipv4(const char *src, u_char *dst);
 static int	inet_cidr_pton_ipv4(const char *src, u_char *dst, size_t size);
-static int	inet_net_pton_ipv6(const char *src, u_char *dst);
-static int	inet_cidr_pton_ipv6(const char *src, u_char *dst, size_t size);
+static int	inet_pton_ipv6_internal(const char *src, u_char *dst, size_t size,
+									bool allow_cidr);
 
 
 /*
@@ -74,9 +74,9 @@ pg_inet_net_pton(int af, const char *src, void *dst, size_t size)
 				inet_net_pton_ipv4(src, dst) :
 				inet_cidr_pton_ipv4(src, dst, size);
 		case PGSQL_AF_INET6:
-			return size == -1 ?
-				inet_net_pton_ipv6(src, dst) :
-				inet_cidr_pton_ipv6(src, dst, size);
+			return inet_pton_ipv6_internal(src, dst,
+										   (size == -1) ? 16 : size,
+										   true /* allow CIDR */);
 		default:
 			errno = EAFNOSUPPORT;
 			return -1;
@@ -352,13 +352,22 @@ emsgsize:
 }
 
 static int
-getbits(const char *src, int *bitsp)
+getbits(const char *src, int *bitsp, bool allow_cidr)
 {
 	static const char digits[] = "0123456789";
 	int			n;
 	int			val;
 	char		ch;
 
+	if (!allow_cidr)
+	{
+		/*
+		 * If we got here, the top-level call is to pg_inet_pton() and the
+		 * caller supplied a CIDR mask, which isn't allowed.
+		 */
+		return 0;
+	}
+
 	val = 0;
 	n = 0;
 	while ((ch = *src++) != '\0')
@@ -385,7 +394,7 @@ getbits(const char *src, int *bitsp)
 }
 
 static int
-getv4(const char *src, u_char *dst, int *bitsp)
+getv4(const char *src, u_char *dst, int *bitsp, bool allow_cidr)
 {
 	static const char digits[] = "0123456789";
 	u_char	   *odst = dst;
@@ -416,7 +425,7 @@ getv4(const char *src, u_char *dst, int *bitsp)
 				return 0;
 			*dst++ = val;
 			if (ch == '/')
-				return getbits(src, bitsp);
+				return getbits(src, bitsp, allow_cidr);
 			val = 0;
 			n = 0;
 			continue;
@@ -431,18 +440,12 @@ getv4(const char *src, u_char *dst, int *bitsp)
 	return 1;
 }
 
-static int
-inet_net_pton_ipv6(const char *src, u_char *dst)
-{
-	return inet_cidr_pton_ipv6(src, dst, 16);
-}
-
 #define NS_IN6ADDRSZ 16
 #define NS_INT16SZ 2
 #define NS_INADDRSZ 4
 
 static int
-inet_cidr_pton_ipv6(const char *src, u_char *dst, size_t size)
+inet_pton_ipv6_internal(const char *src, u_char *dst, size_t size, bool allow_cidr)
 {
 	static const char xdigits_l[] = "0123456789abcdef",
 				xdigits_u[] = "0123456789ABCDEF";
@@ -510,13 +513,13 @@ inet_cidr_pton_ipv6(const char *src, u_char *dst, size_t size)
 			continue;
 		}
 		if (ch == '.' && ((tp + NS_INADDRSZ) <= endp) &&
-			getv4(curtok, tp, &bits) > 0)
+			getv4(curtok, tp, &bits, allow_cidr) > 0)
 		{
 			tp += NS_INADDRSZ;
 			saw_xdigit = 0;
 			break;				/* '\0' was seen by inet_pton4(). */
 		}
-		if (ch == '/' && getbits(src, &bits) > 0)
+		if (ch == '/' && getbits(src, &bits, allow_cidr) > 0)
 			break;
 		goto enoent;
 	}
@@ -530,6 +533,9 @@ inet_cidr_pton_ipv6(const char *src, u_char *dst, size_t size)
 	if (bits == -1)
 		bits = 128;
 
+	/* Without a CIDR mask, the above should guarantee a complete address. */
+	Assert(allow_cidr || bits == 128);
+
 	endp = tmp + 16;
 
 	if (colonp != NULL)
@@ -568,3 +574,29 @@ emsgsize:
 	errno = EMSGSIZE;
 	return -1;
 }
+
+/*
+ * Converts a network address in presentation format to network format. The main
+ * difference between this and pg_inet_net_pton() above is that CIDR notation is
+ * not allowed.
+ *
+ * The memory pointed to by dst depends on the address family:
+ *
+ *     PGSQL_AF_INET:  not implemented yet (returns -1 with EAFNOSUPPORT)
+ *     PGSQL_AF_INET6: *dst must be a u_char[16]
+ *
+ * Over and above the standard inet_pton() functionality, we also set errno on
+ * parse failure, with the same meanings as with pg_inet_net_pton().
+ */
+int
+pg_inet_pton(int af, const char *src, void *dst)
+{
+	if (af != PGSQL_AF_INET6)
+	{
+		/* Currently only IPv6 is implemented. */
+		errno = EAFNOSUPPORT;
+		return -1;
+	}
+
+	return (inet_pton_ipv6_internal(src, dst, 16, false /* no CIDR */) == 128);
+}
-- 
2.25.1

#41Peter Eisentraut
peter.eisentraut@enterprisedb.com
In reply to: Jacob Champion (#40)
Re: [PATCH] Accept IP addresses in server certificate SANs

On 28.03.22 22:21, Jacob Champion wrote:

On Mon, 2022-03-28 at 11:17 +0200, Daniel Gustafsson wrote:

Fixing up the switch_server_cert() calls and using default_ssl_connstr makes
the test pass for me. The required fixes are in the supplied 0004 diff, I kept
them separate to allow the original author to incorporate them without having
to dig them out to see what changed (named to match the git format-patch output
since I think the CFBot just applies the patches in alphabetical order).

Thanks! Those changes look good to me; I've folded them into v11. This
is rebased on a newer HEAD so it should fix the apply failures that
Greg pointed out.

I'm not happy about how inet_net_pton.o is repurposed here. That code
is clearly meant to support backend data types with specifically. Code like

+ /*
+  * pg_inet_net_pton() will accept CIDR masks, which we don't want to
+  * match, so skip the comparison if the host string contains a slash.
+  */

indicates that we are fighting against the API. Also, if someone ever
wants to change how those backend data types work, we then have to check
a bunch of other code as well.

I think we should be using inet_ntop()/inet_pton() directly here. We
can throw substitute implementations into libpgport if necessary, that's
not so difficult.

#42Jacob Champion
pchampion@vmware.com
In reply to: Peter Eisentraut (#41)
Re: [PATCH] Accept IP addresses in server certificate SANs

On Wed, 2022-03-30 at 13:37 +0200, Peter Eisentraut wrote:

On 28.03.22 22:21, Jacob Champion wrote:

On Mon, 2022-03-28 at 11:17 +0200, Daniel Gustafsson wrote:

Fixing up the switch_server_cert() calls and using default_ssl_connstr makes
the test pass for me. The required fixes are in the supplied 0004 diff, I kept
them separate to allow the original author to incorporate them without having
to dig them out to see what changed (named to match the git format-patch output
since I think the CFBot just applies the patches in alphabetical order).

Thanks! Those changes look good to me; I've folded them into v11. This
is rebased on a newer HEAD so it should fix the apply failures that
Greg pointed out.

I'm not happy about how inet_net_pton.o is repurposed here. That code
is clearly meant to support backend data types with specifically. Code like

+ /*
+  * pg_inet_net_pton() will accept CIDR masks, which we don't want to
+  * match, so skip the comparison if the host string contains a slash.
+  */

indicates that we are fighting against the API.

Horiguchi-san had the same concern upthread, I think. I replaced that
code in the next patch, but it was enough net-new stuff that I kept the
patches separate instead of merging them. I can change that if it's not
helpful for review.

Also, if someone ever
wants to change how those backend data types work, we then have to check
a bunch of other code as well.

I think we should be using inet_ntop()/inet_pton() directly here. We
can throw substitute implementations into libpgport if necessary, that's
not so difficult.

Is this request satisfied by the implementation of pg_inet_pton() in
patch 0003? If not, what needs to change?

Thanks,
--Jacob

#43Peter Eisentraut
peter.eisentraut@enterprisedb.com
In reply to: Jacob Champion (#42)
Re: [PATCH] Accept IP addresses in server certificate SANs

On 30.03.22 18:17, Jacob Champion wrote:

Also, if someone ever
wants to change how those backend data types work, we then have to check
a bunch of other code as well.

I think we should be using inet_ntop()/inet_pton() directly here. We
can throw substitute implementations into libpgport if necessary, that's
not so difficult.

Is this request satisfied by the implementation of pg_inet_pton() in
patch 0003? If not, what needs to change?

Why add a (failry complicated) pg_inet_pton() when a perfectly
reasonable inet_pton() exists?

I would get rid of all that refactoring and just have your code call
inet_pton()/inet_ntop() directly.

If you're worried about portability, and you don't want to go through
the effort of proving libpgport substitutes, just have your code raise
an error in the "#else" code paths. We can fill that in later if there
is demand.

#44Jacob Champion
pchampion@vmware.com
In reply to: Peter Eisentraut (#43)
1 attachment(s)
Re: [PATCH] Accept IP addresses in server certificate SANs

On Thu, 2022-03-31 at 16:32 +0200, Peter Eisentraut wrote:

Why add a (failry complicated) pg_inet_pton() when a perfectly
reasonable inet_pton() exists?

I think it was mostly just that inet_aton() and pg_inet_net_ntop() both
had ports, and I figured I might as well port the other one since we
already had the implementation. (I don't have a good intuition yet for
the community's preference for port vs dependency.)

I would get rid of all that refactoring and just have your code call
inet_pton()/inet_ntop() directly.

If you're worried about portability, and you don't want to go through
the effort of proving libpgport substitutes, just have your code raise
an error in the "#else" code paths. We can fill that in later if there
is demand.

Switched to inet_pton() in v12, with no #if/else for now. I think this
should work with Winsock as-is; let's see if the bot agrees...

Thanks,
--Jacob

Attachments:

v12-0001-libpq-allow-IP-address-SANs-in-server-certs.patchtext/x-patch; name=v12-0001-libpq-allow-IP-address-SANs-in-server-certs.patchDownload
From a973663f1b4b98a427692bb6291ddc2c3ce9bb4f Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Thu, 18 Nov 2021 15:36:18 -0800
Subject: [PATCH v12] libpq: allow IP address SANs in server certs

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.

- Move PGSQL_AF_INET* to inet-common.h to reduce copy-paste.

Co-authored-by: Kyotaro Horiguchi <horikyota.ntt@gmail.com>
Co-authored-by: Daniel Gustafsson <daniel@yesql.se>
---
 doc/src/sgml/libpq.sgml                       |  21 ++-
 src/backend/utils/adt/inet_net_pton.c         |   2 +-
 src/include/common/inet-common.h              |  35 +++++
 src/include/utils/inet.h                      |  13 +-
 src/interfaces/libpq/fe-secure-common.c       |  99 +++++++++++++
 src/interfaces/libpq/fe-secure-common.h       |   4 +
 src/interfaces/libpq/fe-secure-openssl.c      | 139 ++++++++++++++++--
 src/port/inet_net_ntop.c                      |  12 +-
 .../conf/server-cn-and-ip-alt-names.config    |  24 +++
 src/test/ssl/conf/server-ip-alt-names.config  |  19 +++
 .../conf/server-ip-cn-and-alt-names.config    |  21 +++
 .../server-ip-cn-and-dns-alt-names.config     |  21 +++
 src/test/ssl/conf/server-ip-cn-only.config    |  12 ++
 src/test/ssl/conf/server-ip-in-dnsname.config |  18 +++
 .../ssl/ssl/server-cn-and-ip-alt-names.crt    |  20 +++
 .../ssl/ssl/server-cn-and-ip-alt-names.key    |  27 ++++
 src/test/ssl/ssl/server-ip-alt-names.crt      |  19 +++
 src/test/ssl/ssl/server-ip-alt-names.key      |  27 ++++
 .../ssl/ssl/server-ip-cn-and-alt-names.crt    |  19 +++
 .../ssl/ssl/server-ip-cn-and-alt-names.key    |  27 ++++
 .../ssl/server-ip-cn-and-dns-alt-names.crt    |  20 +++
 .../ssl/server-ip-cn-and-dns-alt-names.key    |  27 ++++
 src/test/ssl/ssl/server-ip-cn-only.crt        |  18 +++
 src/test/ssl/ssl/server-ip-cn-only.key        |  27 ++++
 src/test/ssl/ssl/server-ip-in-dnsname.crt     |  18 +++
 src/test/ssl/ssl/server-ip-in-dnsname.key     |  27 ++++
 src/test/ssl/sslfiles.mk                      |   6 +
 src/test/ssl/t/001_ssltests.pl                | 110 +++++++++++++-
 28 files changed, 791 insertions(+), 41 deletions(-)
 create mode 100644 src/include/common/inet-common.h
 create mode 100644 src/test/ssl/conf/server-cn-and-ip-alt-names.config
 create mode 100644 src/test/ssl/conf/server-ip-alt-names.config
 create mode 100644 src/test/ssl/conf/server-ip-cn-and-alt-names.config
 create mode 100644 src/test/ssl/conf/server-ip-cn-and-dns-alt-names.config
 create mode 100644 src/test/ssl/conf/server-ip-cn-only.config
 create mode 100644 src/test/ssl/conf/server-ip-in-dnsname.config
 create mode 100644 src/test/ssl/ssl/server-cn-and-ip-alt-names.crt
 create mode 100644 src/test/ssl/ssl/server-cn-and-ip-alt-names.key
 create mode 100644 src/test/ssl/ssl/server-ip-alt-names.crt
 create mode 100644 src/test/ssl/ssl/server-ip-alt-names.key
 create mode 100644 src/test/ssl/ssl/server-ip-cn-and-alt-names.crt
 create mode 100644 src/test/ssl/ssl/server-ip-cn-and-alt-names.key
 create mode 100644 src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.crt
 create mode 100644 src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.key
 create mode 100644 src/test/ssl/ssl/server-ip-cn-only.crt
 create mode 100644 src/test/ssl/ssl/server-ip-cn-only.key
 create mode 100644 src/test/ssl/ssl/server-ip-in-dnsname.crt
 create mode 100644 src/test/ssl/ssl/server-ip-in-dnsname.key

diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 0b2a8720f0..b785008045 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -8356,16 +8356,31 @@ ldap://ldap.acme.com/cn=dbserver,cn=hosts?pgconnectinfo?base?(objectclass=*)
 
   <para>
    In <literal>verify-full</literal> mode, the host name is matched against the
-   certificate's Subject Alternative Name attribute(s), or against the
-   Common Name attribute if no Subject Alternative Name of type <literal>dNSName</literal> is
+   certificate's Subject Alternative Name attribute(s) (SAN), or against the
+   Common Name attribute if no SAN of type <literal>dNSName</literal> is
    present.  If the certificate's name attribute starts with an asterisk
    (<literal>*</literal>), the asterisk will be treated as
    a wildcard, which will match all characters <emphasis>except</emphasis> a dot
    (<literal>.</literal>). This means the certificate will not match subdomains.
    If the connection is made using an IP address instead of a host name, the
-   IP address will be matched (without doing any DNS lookups).
+   IP address will be matched (without doing any DNS lookups) against SANs of
+   type <literal>iPAddress</literal> or <literal>dNSName</literal>.  If no
+   <literal>iPAddress</literal> SAN is present and no
+   matching <literal>dNSName</literal> SAN is present, the host IP address is
+   matched against the Common Name attribute.
   </para>
 
+  <note>
+    <para>
+      For backward compatibility with earlier versions of PostgreSQL, the host
+      IP address is verified in a manner different
+      from <ulink url="https://tools.ietf.org/html/rfc6125">RFC 6125</ulink>.
+      The host IP address is always matched against <literal>dNSName</literal>
+      SANs as well as <literal>iPAddress</literal> SANs, and can be matched
+      against the Common Name attribute if no relevant SANs exist.
+   </para>
+  </note>
+
   <para>
    To allow server certificate verification, one or more root certificates
    must be placed in the file <filename>~/.postgresql/root.crt</filename>
diff --git a/src/backend/utils/adt/inet_net_pton.c b/src/backend/utils/adt/inet_net_pton.c
index d3221a1313..7752012ad9 100644
--- a/src/backend/utils/adt/inet_net_pton.c
+++ b/src/backend/utils/adt/inet_net_pton.c
@@ -29,9 +29,9 @@ static const char rcsid[] = "Id: inet_net_pton.c,v 1.4.2.3 2004/03/17 00:40:11 m
 #include <assert.h>
 #include <ctype.h>
 
+#include "common/inet-common.h"
 #include "utils/builtins.h" /* pgrminclude ignore */	/* needed on some
 														 * platforms */
-#include "utils/inet.h"
 
 
 static int	inet_net_pton_ipv4(const char *src, u_char *dst);
diff --git a/src/include/common/inet-common.h b/src/include/common/inet-common.h
new file mode 100644
index 0000000000..3ad72261b6
--- /dev/null
+++ b/src/include/common/inet-common.h
@@ -0,0 +1,35 @@
+/*-------------------------------------------------------------------------
+ *
+ * inet-common.h
+ *
+ *	  Common code for clients of the inet_* APIs.
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/common/inet-common.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef INET_COMMON_H
+#define INET_COMMON_H
+
+/*
+ * We use these values for the "family" field of inet_struct.
+ *
+ * Referencing all of the non-AF_INET types to AF_INET lets us work on
+ * machines which may not have the appropriate address family (like
+ * inet6 addresses when AF_INET6 isn't present) but doesn't cause a
+ * dump/reload requirement.  Pre-7.4 databases used AF_INET for the family
+ * type on disk.
+ *
+ * In a frontend build, we can't include inet.h, but we still need to have
+ * sensible definitions of these two constants.  (Frontend clients should
+ * include this header directly.)  Note that pg_inet_net_ntop()
+ * assumes that PGSQL_AF_INET is equal to AF_INET.
+ */
+#define PGSQL_AF_INET	(AF_INET + 0)
+#define PGSQL_AF_INET6	(AF_INET + 1)
+
+#endif							/* INET_COMMON_H */
diff --git a/src/include/utils/inet.h b/src/include/utils/inet.h
index 3073c0307e..c56c016987 100644
--- a/src/include/utils/inet.h
+++ b/src/include/utils/inet.h
@@ -14,6 +14,7 @@
 #ifndef INET_H
 #define INET_H
 
+#include "common/inet-common.h"
 #include "fmgr.h"
 
 /*
@@ -27,18 +28,6 @@ typedef struct
 	unsigned char ipaddr[16];	/* up to 128 bits of address */
 } inet_struct;
 
-/*
- * We use these values for the "family" field.
- *
- * Referencing all of the non-AF_INET types to AF_INET lets us work on
- * machines which may not have the appropriate address family (like
- * inet6 addresses when AF_INET6 isn't present) but doesn't cause a
- * dump/reload requirement.  Pre-7.4 databases used AF_INET for the family
- * type on disk.
- */
-#define PGSQL_AF_INET	(AF_INET + 0)
-#define PGSQL_AF_INET6	(AF_INET + 1)
-
 /*
  * Both INET and CIDR addresses are represented within Postgres as varlena
  * objects, ie, there is a varlena header in front of the struct type
diff --git a/src/interfaces/libpq/fe-secure-common.c b/src/interfaces/libpq/fe-secure-common.c
index bd46f08fae..6628eb8318 100644
--- a/src/interfaces/libpq/fe-secure-common.c
+++ b/src/interfaces/libpq/fe-secure-common.c
@@ -19,9 +19,13 @@
 
 #include "postgres_fe.h"
 
+#include <arpa/inet.h>
+
+#include "common/inet-common.h"
 #include "fe-secure-common.h"
 
 #include "libpq-int.h"
+#include "port.h"
 #include "pqexpbuffer.h"
 
 /*
@@ -144,6 +148,101 @@ pq_verify_peer_name_matches_certificate_name(PGconn *conn,
 	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'))
+	{
+		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 = PGSQL_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;
+		}
+	}
+	else if (iplen == 16)
+	{
+		/* IPv6 */
+		struct in6_addr addr;
+
+		family = PGSQL_AF_INET6;
+
+		if (inet_pton(AF_INET6, host, &addr) == 1)
+		{
+			if (memcmp(ipdata, &addr.s6_addr, iplen) == 0)
+				match = 1;
+		}
+	}
+	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.
  *
diff --git a/src/interfaces/libpq/fe-secure-common.h b/src/interfaces/libpq/fe-secure-common.h
index 1cca6d785a..d18db7138c 100644
--- a/src/interfaces/libpq/fe-secure-common.h
+++ b/src/interfaces/libpq/fe-secure-common.h
@@ -21,6 +21,10 @@
 extern int	pq_verify_peer_name_matches_certificate_name(PGconn *conn,
 														 const char *namedata, size_t namelen,
 														 char **store_name);
+extern int	pq_verify_peer_name_matches_certificate_ip(PGconn *conn,
+													   const unsigned char *addrdata,
+													   size_t addrlen,
+													   char **store_name);
 extern bool pq_verify_peer_name_matches_certificate(PGconn *conn);
 
 #endif							/* FE_SECURE_COMMON_H */
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index 0cba5c5cf2..1828a3cb8c 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -26,6 +26,7 @@
 #include <fcntl.h>
 #include <ctype.h>
 
+#include "common/inet-common.h"
 #include "libpq-fe.h"
 #include "fe-auth.h"
 #include "fe-secure-common.h"
@@ -72,6 +73,9 @@ static int	verify_cb(int ok, X509_STORE_CTX *ctx);
 static int	openssl_verify_peer_name_matches_certificate_name(PGconn *conn,
 															  ASN1_STRING *name,
 															  char **store_name);
+static int	openssl_verify_peer_name_matches_certificate_ip(PGconn *conn,
+															ASN1_OCTET_STRING *addr_entry,
+															char **store_name);
 static void destroy_ssl_system(void);
 static int	initialize_SSL(PGconn *conn);
 static PostgresPollingStatusType open_client_SSL(PGconn *);
@@ -509,6 +513,51 @@ openssl_verify_peer_name_matches_certificate_name(PGconn *conn, ASN1_STRING *nam
 	return pq_verify_peer_name_matches_certificate_name(conn, (const char *) namedata, len, store_name);
 }
 
+/*
+ * OpenSSL-specific wrapper around
+ * pq_verify_peer_name_matches_certificate_ip(), converting the
+ * ASN1_OCTET_STRING into a plain C string.
+ */
+static int
+openssl_verify_peer_name_matches_certificate_ip(PGconn *conn,
+												ASN1_OCTET_STRING *addr_entry,
+												char **store_name)
+{
+	int			len;
+	const unsigned char *addrdata;
+
+	/* Should not happen... */
+	if (addr_entry == NULL)
+	{
+		appendPQExpBufferStr(&conn->errorMessage,
+							 libpq_gettext("SSL certificate's address entry is missing\n"));
+		return -1;
+	}
+
+	/*
+	 * GEN_IPADD is an OCTET STRING containing an IP address in network byte
+	 * order.
+	 */
+#ifdef HAVE_ASN1_STRING_GET0_DATA
+	addrdata = ASN1_STRING_get0_data(addr_entry);
+#else
+	addrdata = ASN1_STRING_data(addr_entry);
+#endif
+	len = ASN1_STRING_length(addr_entry);
+
+	return pq_verify_peer_name_matches_certificate_ip(conn, addrdata, len, store_name);
+}
+
+static bool
+is_ip_address(const char *host)
+{
+	struct in_addr	dummy4;
+	struct in6_addr	dummy6;
+
+	return inet_aton(host, &dummy4)
+		|| (inet_pton(AF_INET6, host, &dummy6) == 1);
+}
+
 /*
  *	Verify that the server certificate matches the hostname we connected to.
  *
@@ -522,6 +571,36 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 	STACK_OF(GENERAL_NAME) * peer_san;
 	int			i;
 	int			rc = 0;
+	char	   *host = conn->connhost[conn->whichhost].host;
+	int			host_type;
+	bool		check_cn = true;
+
+	Assert(host && host[0]); /* should be guaranteed by caller */
+
+	/*
+	 * We try to match the NSS behavior here, which is a slight departure from
+	 * the spec but seems to make more intuitive sense:
+	 *
+	 * If connhost contains a DNS name, and the certificate's SANs contain any
+	 * dNSName entries, then we'll ignore the Subject Common Name entirely;
+	 * otherwise, we fall back to checking the CN. (This behavior matches the
+	 * RFC.)
+	 *
+	 * If connhost contains an IP address, and the SANs contain iPAddress
+	 * entries, we again ignore the CN. Otherwise, we allow the CN to match,
+	 * EVEN IF there is a dNSName in the SANs. (RFC 6125 prohibits this: "A
+	 * client MUST NOT seek a match for a reference identifier of CN-ID if the
+	 * presented identifiers include a DNS-ID, SRV-ID, URI-ID, or any
+	 * application-specific identifier types supported by the client.")
+	 *
+	 * NOTE: Prior versions of libpq did not consider iPAddress entries at all,
+	 * so this new behavior might break a certificate that has different IP
+	 * addresses in the Subject CN and the SANs.
+	 */
+	if (is_ip_address(host))
+		host_type = GEN_IPADD;
+	else
+		host_type = GEN_DNS;
 
 	/*
 	 * First, get the Subject Alternative Names (SANs) from the certificate,
@@ -537,38 +616,62 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 		for (i = 0; i < san_len; i++)
 		{
 			const GENERAL_NAME *name = sk_GENERAL_NAME_value(peer_san, i);
+			char	   *alt_name = NULL;
 
-			if (name->type == GEN_DNS)
+			if (name->type == host_type)
 			{
-				char	   *alt_name;
+				/*
+				 * This SAN is of the same type (IP or DNS) as our host name, so
+				 * don't allow a fallback check of the CN.
+				 */
+				check_cn = false;
+			}
 
+			if (name->type == GEN_DNS)
+			{
 				(*names_examined)++;
 				rc = openssl_verify_peer_name_matches_certificate_name(conn,
 																	   name->d.dNSName,
 																	   &alt_name);
+			}
+			else if (name->type == GEN_IPADD)
+			{
+				(*names_examined)++;
+				rc = openssl_verify_peer_name_matches_certificate_ip(conn,
+																	 name->d.iPAddress,
+																	 &alt_name);
+			}
 
-				if (alt_name)
-				{
-					if (!*first_name)
-						*first_name = alt_name;
-					else
-						free(alt_name);
-				}
+			if (alt_name)
+			{
+				if (!*first_name)
+					*first_name = alt_name;
+				else
+					free(alt_name);
 			}
+
 			if (rc != 0)
+			{
+				/*
+				 * Either we hit an error or a match, and either way we should
+				 * not fall back to the CN.
+				 */
+				check_cn = false;
 				break;
+			}
 		}
 		sk_GENERAL_NAME_pop_free(peer_san, GENERAL_NAME_free);
 	}
 
 	/*
-	 * If there is no subjectAltName extension of type dNSName, check the
+	 * If there is no subjectAltName extension of the matching type, check the
 	 * Common Name.
 	 *
 	 * (Per RFC 2818 and RFC 6125, if the subjectAltName extension of type
-	 * dNSName is present, the CN must be ignored.)
+	 * dNSName is present, the CN must be ignored. We break this rule if host is
+	 * an IP address; see the comment above.)
 	 */
-	if (*names_examined == 0)
+	if (check_cn)
 	{
 		X509_NAME  *subject_name;
 
@@ -581,10 +684,20 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 												  NID_commonName, -1);
 			if (cn_index >= 0)
 			{
+				char   *common_name = NULL;
+
 				(*names_examined)++;
 				rc = openssl_verify_peer_name_matches_certificate_name(conn,
 																	   X509_NAME_ENTRY_get_data(X509_NAME_get_entry(subject_name, cn_index)),
-																	   first_name);
+																	   &common_name);
+
+				if (common_name)
+				{
+					if (!*first_name)
+						*first_name = common_name;
+					else
+						free(common_name);
+				}
 			}
 		}
 	}
diff --git a/src/port/inet_net_ntop.c b/src/port/inet_net_ntop.c
index b8ad69c390..af28056132 100644
--- a/src/port/inet_net_ntop.c
+++ b/src/port/inet_net_ntop.c
@@ -31,17 +31,7 @@ static const char rcsid[] = "Id: inet_net_ntop.c,v 1.1.2.2 2004/03/09 09:17:27 m
 #include <netinet/in.h>
 #include <arpa/inet.h>
 
-#ifndef FRONTEND
-#include "utils/inet.h"
-#else
-/*
- * In a frontend build, we can't include inet.h, but we still need to have
- * sensible definitions of these two constants.  Note that pg_inet_net_ntop()
- * assumes that PGSQL_AF_INET is equal to AF_INET.
- */
-#define PGSQL_AF_INET	(AF_INET + 0)
-#define PGSQL_AF_INET6	(AF_INET + 1)
-#endif
+#include "common/inet-common.h"
 
 
 #define NS_IN6ADDRSZ 16
diff --git a/src/test/ssl/conf/server-cn-and-ip-alt-names.config b/src/test/ssl/conf/server-cn-and-ip-alt-names.config
new file mode 100644
index 0000000000..a6fa09bad3
--- /dev/null
+++ b/src/test/ssl/conf/server-cn-and-ip-alt-names.config
@@ -0,0 +1,24 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+# This certificate contains a CN and SANs for both IPv4 and IPv6.
+
+
+[ req ]
+distinguished_name     = req_distinguished_name
+req_extensions         = v3_req
+prompt                 = no
+
+[ req_distinguished_name ]
+# Note: According to RFC 2818 and 6125, the CN is ignored, when DNS names are
+# present in the SANs. But they are silent on whether the CN is checked when IP
+# addresses are present.
+CN = common-name.pg-ssltest.test
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
+subjectAltName = @alt_names
+
+[ alt_names ]
+IP.1 = 192.0.2.1
+IP.2 = 2001:DB8::1
diff --git a/src/test/ssl/conf/server-ip-alt-names.config b/src/test/ssl/conf/server-ip-alt-names.config
new file mode 100644
index 0000000000..c22f22951a
--- /dev/null
+++ b/src/test/ssl/conf/server-ip-alt-names.config
@@ -0,0 +1,19 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+# This certificate has a two IP-address SANs, and no CN.
+
+[ req ]
+distinguished_name     = req_distinguished_name
+req_extensions         = v3_req
+prompt                 = no
+
+[ req_distinguished_name ]
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
+subjectAltName = @alt_names
+
+[ alt_names ]
+IP.1 = 192.0.2.1
+IP.2 = 2001:DB8::1
diff --git a/src/test/ssl/conf/server-ip-cn-and-alt-names.config b/src/test/ssl/conf/server-ip-cn-and-alt-names.config
new file mode 100644
index 0000000000..a4087f0a18
--- /dev/null
+++ b/src/test/ssl/conf/server-ip-cn-and-alt-names.config
@@ -0,0 +1,21 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+# This certificate contains both a CN and SANs in IP address format.
+
+
+[ req ]
+distinguished_name     = req_distinguished_name
+req_extensions         = v3_req
+prompt                 = no
+
+[ req_distinguished_name ]
+CN = 192.0.2.1
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
+subjectAltName = @alt_names
+
+[ alt_names ]
+IP.1 = 192.0.2.2
+IP.2 = 2001:DB8::1
diff --git a/src/test/ssl/conf/server-ip-cn-and-dns-alt-names.config b/src/test/ssl/conf/server-ip-cn-and-dns-alt-names.config
new file mode 100644
index 0000000000..7121803b49
--- /dev/null
+++ b/src/test/ssl/conf/server-ip-cn-and-dns-alt-names.config
@@ -0,0 +1,21 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+# This certificate contains both a CN and SANs in IP address format.
+
+
+[ req ]
+distinguished_name     = req_distinguished_name
+req_extensions         = v3_req
+prompt                 = no
+
+[ req_distinguished_name ]
+CN = 192.0.2.1
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
+subjectAltName = @alt_names
+
+[ alt_names ]
+DNS.1 = dns1.alt-name.pg-ssltest.test
+DNS.2 = dns2.alt-name.pg-ssltest.test
diff --git a/src/test/ssl/conf/server-ip-cn-only.config b/src/test/ssl/conf/server-ip-cn-only.config
new file mode 100644
index 0000000000..585d8bdae8
--- /dev/null
+++ b/src/test/ssl/conf/server-ip-cn-only.config
@@ -0,0 +1,12 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+
+[ req ]
+distinguished_name     = req_distinguished_name
+prompt                 = no
+
+[ req_distinguished_name ]
+CN = 192.0.2.1
+OU = PostgreSQL test suite
+
+# No Subject Alternative Names
diff --git a/src/test/ssl/conf/server-ip-in-dnsname.config b/src/test/ssl/conf/server-ip-in-dnsname.config
new file mode 100644
index 0000000000..b15649aef7
--- /dev/null
+++ b/src/test/ssl/conf/server-ip-in-dnsname.config
@@ -0,0 +1,18 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+
+[ req ]
+distinguished_name     = req_distinguished_name
+req_extensions         = v3_req
+prompt                 = no
+
+[ req_distinguished_name ]
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
+subjectAltName = @alt_names
+
+# Normally IP addresses should not go into a dNSName.
+[ alt_names ]
+DNS.1 = 192.0.2.1
diff --git a/src/test/ssl/ssl/server-cn-and-ip-alt-names.crt b/src/test/ssl/ssl/server-cn-and-ip-alt-names.crt
new file mode 100644
index 0000000000..4e58c85ccb
--- /dev/null
+++ b/src/test/ssl/ssl/server-cn-and-ip-alt-names.crt
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDLzCCAhegAwIBAgIIICERKRE1UQAwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UE
+Aww3VGVzdCBDQSBmb3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHNl
+cnZlciBjZXJ0czAeFw0yMTExMjkxOTM1NTFaFw00OTA0MTYxOTM1NTFaMEYxHjAc
+BgNVBAsMFVBvc3RncmVTUUwgdGVzdCBzdWl0ZTEkMCIGA1UEAwwbY29tbW9uLW5h
+bWUucGctc3NsdGVzdC50ZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
+AQEA6+8IYKAFnZ7V+fDo1cyMpbGBLzCfJOQ/1o2jOGP4+GjpsZgv6S6UT2MheC8M
+iiEFrYwdsSIZyYc3jEZrluy/UuR0bCGtqU92BCqa0iBLhvHOgjR588u253eLxQtQ
+8iJn11QPrKMk35nMkmY8GfHt4sGFbvBL6+GpipHq7a6cde3Z+v4kCB5dKMYDUDtm
+3mJmviuGNAu5wOqItk2Yi5dwJs1054007KNH0Il43urxiOfnkLS0cG5kehboPf86
+vxBt3iHByrU/9/DY5IvQCfSXVNa6rb5w5/pGja9aCei6Mv1jQY/V8SMQTga+MOsA
+0WB9akxMi2NxwS2+BQ4k/McPlwIDAQABoyUwIzAhBgNVHREEGjAYhwTAAAIBhxAg
+AQ24AAAAAAAAAAAAAAABMA0GCSqGSIb3DQEBCwUAA4IBAQAQLo2RzC07dG9p+J3A
+W6C0p3Y+Os/YE2D9wfp4TIDTZxcRUQZ0S6ahF1N6sp8l9KHBJHPU1cUpRAU1oD+Y
+SqmnP/VJRRDTTj9Ytdc/Vuo2jeLpSYhVKrCqtjqIrCwYJFoYRmMoxTtJGlwA0hSd
+kwo3XYrALPUQWUErTYPvNfDNIuUwqUXNfS0CXuIOVN3LJ+shegg6Pwbh9B5T9NHx
+kH+HswajhdpdnZIgh0FYTlTCPILDrB49aOWwqLa54AUA6WXa35hPsP8SoqL9Eucq
+ifPhBYyadsjOb+70N8GbbAsDPN1jCX9L8RuNcEkxSCKCYx91cWXh7K5KMPuGlzB7
+j8xB
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-cn-and-ip-alt-names.key b/src/test/ssl/ssl/server-cn-and-ip-alt-names.key
new file mode 100644
index 0000000000..837eef996d
--- /dev/null
+++ b/src/test/ssl/ssl/server-cn-and-ip-alt-names.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA6+8IYKAFnZ7V+fDo1cyMpbGBLzCfJOQ/1o2jOGP4+GjpsZgv
+6S6UT2MheC8MiiEFrYwdsSIZyYc3jEZrluy/UuR0bCGtqU92BCqa0iBLhvHOgjR5
+88u253eLxQtQ8iJn11QPrKMk35nMkmY8GfHt4sGFbvBL6+GpipHq7a6cde3Z+v4k
+CB5dKMYDUDtm3mJmviuGNAu5wOqItk2Yi5dwJs1054007KNH0Il43urxiOfnkLS0
+cG5kehboPf86vxBt3iHByrU/9/DY5IvQCfSXVNa6rb5w5/pGja9aCei6Mv1jQY/V
+8SMQTga+MOsA0WB9akxMi2NxwS2+BQ4k/McPlwIDAQABAoIBAQCuNFKVNdKvrUYF
+RLJGmsAG3+eo9lern7TbML2ht39vu9dBwEMwA6qSa3mdCfBSVUuh9uE9lxY/TU3g
+j2aFi81A4VptNPjLGNblAKhMGnhp7UUzspeRQYuNoSFcnpxoDKtrvK/OIq/pQeBh
+AIfECHRDh+yEG32Tb44FuPQkB1eTYl8xbMEImrhNUaSjJk7tTsmydHy0DjmqHVKX
+HUj0TREfDBDOBiHtY0XV6Pu3bnqDH/TKLTfUf3UdfTuay3Yai9aEcRPWp9GrMO7G
+axsKCifTz6177gyr6Fv8HLeMZMh9rMZRn3e0zfaF6vrH1QnZZOts5jpUa0KugSCd
+//uC0iNxAoGBAPXVc3b+o3hY5gcwwpaW6JtsarDrmNRxrizqIDG7NgpqwdFXgTi6
+6q0t2pjv81ATqij69IcPkNSissyR4OEKnu/OFJWzreg8yLi75WHKi0E/6msHpwRk
+d1yP0Zgd05ots/yOjDSp593RagaPVvHBxMECZ/Tm3B+Tq55Azudd/zvLAoGBAPWw
+xf0oUEJl6NdUZD6K7eFc6jf8yrpD85dldeko6LeN8x0XlKKWvUDJ2+3oizXoQvCm
+8by6KOYEIo4MrtXuy9MmtPWfNvRBr+hsUHchIj7IgFa9bKXyK2FnJqu/8CbEymli
+eZu7hoOhelurhnFy1zSqwNO4GC+kw60Y/BO3Z1nlAoGAVOyYJtNwxXJwhKtjjYI0
+ePzLHrNE6J8c/Ick+AkkchTPP/JqwZ5Q0+KzUYITG+avMdkAAGhwMATEn8cFWLjC
+jzUyB0U7Hq9g5/CBHXdLBA+Ae9j46ZuLYH6OeW5UWz7OnsDfzpGjeA2QAxQhhQLb
+ZZHfN8tI39+zucfJskPWmGECgYEAg9guF1Fn6InJrqwR82IYj6SN6CeXHufSM392
+C/4xDDd3rDf4QlwECV2J0RzGf9I5Ae2EshNwWScE6Be0RweTh6cw2tJq6h7J6D8f
+2x4Dw49TF7klMdRIJUf2f5pLpHJccLswqTqzz7V69PCSABVxmUi8m6EiEYconp5W
+v7nfE2UCgYALrEqzncuSIX3q6TVAjnzT7gO4h8h2TUekIWdHQFldFx8R7Kncggnd
+48gQqhewchNR83UCcd7pPsCcTqu6UR1QRdq/DV5P6J3xdZ2iS/2gCM6hvWIvKZEv
+/ClnkyFCOW7zX6RKIXtRYZTV1kz3TajApi34RTIeIMTieaCarnBJbA==
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-ip-alt-names.crt b/src/test/ssl/ssl/server-ip-alt-names.crt
new file mode 100644
index 0000000000..8a1bc620bb
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-alt-names.crt
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDCTCCAfGgAwIBAgIIICERKREEUAAwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UE
+Aww3VGVzdCBDQSBmb3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHNl
+cnZlciBjZXJ0czAeFw0yMTExMjkxOTA0NTBaFw00OTA0MTYxOTA0NTBaMCAxHjAc
+BgNVBAsMFVBvc3RncmVTUUwgdGVzdCBzdWl0ZTCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBAOM8yB6aVWb17ujr3ayU62mxHQoqn4CvG9yXlJvGOGv/ursW
+Vs0UYJdc96LsNZN1szdm9ayNzCIw3eja+ULsjxCi6+3LM4pO76IORL/XFamlTPYb
+BZ4pHdZVB0nnZAAnWCZPyXdnjOKQ5+8unVXkfibkjj8UELBJ2snehsOa+CTkOBez
+zxYMqxAgbywLIYsW448brun7UXpWmqbGK+SsdGaIZ5Sb7Zezc5lt6CrLemTZTHHK
+7l4WZFCCEi4t3sgO8o1vDELD/IE5G8lyXvIdgJg6t8ssper7iCw6S8x+okhjiSjT
+vDLU2g4AanqZRZB49aPwTo0QUcJA2BCJxL9xLy8CAwEAAaMlMCMwIQYDVR0RBBow
+GIcEwAACAYcQIAENuAAAAAAAAAAAAAAAATANBgkqhkiG9w0BAQsFAAOCAQEAwZJ+
+8KpABTlMEgKnHIYb35ItGhtFiTLQta9RkXx7vaeDwpOdPP/IvuvpjpQZkobRgBsk
+bNM0KuJpd2mSTphQAt6eKQIdcPrkzvc/Yh9OK3YNLUAbu/ZhBUnBvFnUL4wn2f1U
+mfO+m8P/LxybwqKx7r1mbaB+tP3RTxxLcIMvm9ECPQEoBntfEL325Wdoj+WuQH5Y
+IvcM6FaCTkQsNIPbaBD5l5MhMLHRULZujbDjXqGSvRMQfns6np/biMjNdQA8NZ5z
+STeUFvkQbCxoA0YYLgoSHL5KhZjXrg2g+T+2TUyCTR/91xf9OoOjBZdixR0S0DzJ
+B1+5vnUjZaCfnSEA7A==
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-ip-alt-names.key b/src/test/ssl/ssl/server-ip-alt-names.key
new file mode 100644
index 0000000000..b210b3a991
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-alt-names.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEA4zzIHppVZvXu6OvdrJTrabEdCiqfgK8b3JeUm8Y4a/+6uxZW
+zRRgl1z3ouw1k3WzN2b1rI3MIjDd6Nr5QuyPEKLr7cszik7vog5Ev9cVqaVM9hsF
+nikd1lUHSedkACdYJk/Jd2eM4pDn7y6dVeR+JuSOPxQQsEnayd6Gw5r4JOQ4F7PP
+FgyrECBvLAshixbjjxuu6ftRelaapsYr5Kx0ZohnlJvtl7NzmW3oKst6ZNlMccru
+XhZkUIISLi3eyA7yjW8MQsP8gTkbyXJe8h2AmDq3yyyl6vuILDpLzH6iSGOJKNO8
+MtTaDgBqeplFkHj1o/BOjRBRwkDYEInEv3EvLwIDAQABAoIBACp3uY6+mSdc3wF4
+0zzlt/lQuHSl8plCIJrhWUyjhvfoGyXLzv0Uydh/72frbTfZz1yTSWauOXBKYa6a
+/eqb+0DIsf8G8uLuTaqjsAWKVOoXkoKMGkistn7P9UTCkdXVhIvkbWp7V8EgA7iX
+pZ/fzBPIsyzmuxe3NcR0ags0cxuxkNuu+YXDv1oTedmT2wS3CZq1d/T1Y/EOVIf8
+Iznd2aOverlsnt6iiQ3ZWdG/W5F8FhnrR/rrBdYsdCv6TH/KUYexnDOUYpayjDbu
+oAKnifPp6UqiOM4SuBL83OAz19jptp5vpF370BEVRs3eK0q+zo/mETjv9HsXdolZ
+lfoXA0ECgYEA/7nb2azbq/2muvXCh1ZxCEbn3mt8KXoJP/xkx/v9eEc/cc5Q9e0V
+2oGfjC2hSE+bjOWMwiUMD6uU+iRjhz5A3IvUxnoSdoL7H9p0hTqLMyP7dTDkoVF5
+aEuLMaiI5YEnfAFu9L5h8ZKieoQTBoscT06wnGjh9pBV9bthfTKA7ksCgYEA43sb
+55m9WL4kWCPwOAp3vdEAFyxzmZHlO26sEQOU/m5aN01pumYybBruziEXMI96yfTj
+VmXKReeYb6XUiCcs3fLSipD/+8/8CsjO4uMORtxWumXe8AbKZfysGFzL7wJlByGT
+38AGQwIG/XD8cKnaiEMX4E/3Owbcoxwixo3WZC0CgYEAovaqJ9mEU+Jc8h/TS7PG
+bGPjN1Z/1V6zrlcFUnw/Vvrwb3HvHglsN8cLCaW6df5lPjC6tq4tNX8+fPnbg0Ak
+zWc+vQzl3ygxKGdqgcyBEKIJiPETgcoN+GzL02V3d+oKY3f2YXlBqVSsvi6UgUL9
+U3zuB36/IQVyAhrbUZFxoGkCgYEAnaFAO+Nvrp/LhXwZyGuQf+rkmipGTIMpil5t
+QzjtNMV5JFszSWPpyrl7A0Ew1YiG+I0GP2c3m+sY2TzbIiGrWH0b4cMKbw63Qy3V
+FqlpyjaCrpVKv56k/7jv883RzuQk56Uf1+szK5mrCFITy2oXsVZ0pA4lbjSaDTjA
+7D968V0CgYEA+qKqXKL98+c5CMPnpf+0B1x2zgyUym1ouPfon2x5fhK84T53zDMA
+zfdUJ/SOZw6/c9vRF7RL8h+ZfFdIyoAXv4Tt6mIiZe7P+AUVg6XgJ0ce2MUSeWjI
+W8D4WdSi0jyqr99TuVBWhbTZJviMB3pHqKaHQ07hnd/lPtvzsiH12qk=
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-ip-cn-and-alt-names.crt b/src/test/ssl/ssl/server-ip-cn-and-alt-names.crt
new file mode 100644
index 0000000000..2be02feb03
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-and-alt-names.crt
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDHTCCAgWgAwIBAgIIICIBBBQ2MQAwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UE
+Aww3VGVzdCBDQSBmb3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHNl
+cnZlciBjZXJ0czAeFw0yMjAxMDQyMjM2MzFaFw00OTA1MjIyMjM2MzFaMDQxHjAc
+BgNVBAsMFVBvc3RncmVTUUwgdGVzdCBzdWl0ZTESMBAGA1UEAwwJMTkyLjAuMi4x
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwmqTdQJfs2Ti9tPitYp2
+27I0HvL/kNSgA6egFr0foRo0BorwJNIzdbV0+EnsfiBNTWL5It26gqO7UP3ms8t2
+vHD5gkXfT+f6ts0lVJEcIOkUD/8ws4Ic9Y4uPqb4gN+pUKqcxtmLW1TYk84MBK59
+Xz4yPPS6N+G/DMMeFHTNkM9EQwn/+DC3fDsWdGYM2GRWDTJGg1A5tSUcF+seu7i1
+Vg7XajBfsvgAUAsrAxV+X/sLZh94HY+paD6wfaI99mY2OXVc/XW/z1r9WQznor65
+ZkonNCaPfavqPG5vqnab9AyQcqPqmX8hf/xrniASBAkqNCctbASrFCIYvCJfGfmX
+EQIDAQABoyUwIzAhBgNVHREEGjAYhwTAAAIChxAgAQ24AAAAAAAAAAAAAAABMA0G
+CSqGSIb3DQEBCwUAA4IBAQBf7kmYfRYfnWk1OUfY3N1kaNg9piBBlFr9g+OQn9KU
+zirkN7s0ZQbCGxV1uJQBKS58NyE414Vorau77379emgYDcCBpDIYpkLiNujVrIOr
+ggRFKsFRgxu4/mw0BSgCcV8RPe9SWHZ90Mos7TMCnW/PdxOCD1wD0YMkcs0rwB3l
+0Kzc7jDnfOEvmgw/Ysm7v67ps+05Uq5VskQ6WrpSAw6kPD/QMuuBAX8ATPczIaox
+zAMyncq1IiSIwG93f3EoQQThdQ70C6G9vLcu9TtL6JAsEMFEzR99gt1Wsqvmgl9W
+kStzj1yjIWeo5gIsa4Jgcke1lZviWyrTxHDfyunYE5i5
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-ip-cn-and-alt-names.key b/src/test/ssl/ssl/server-ip-cn-and-alt-names.key
new file mode 100644
index 0000000000..54fe80fc68
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-and-alt-names.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAwmqTdQJfs2Ti9tPitYp227I0HvL/kNSgA6egFr0foRo0Borw
+JNIzdbV0+EnsfiBNTWL5It26gqO7UP3ms8t2vHD5gkXfT+f6ts0lVJEcIOkUD/8w
+s4Ic9Y4uPqb4gN+pUKqcxtmLW1TYk84MBK59Xz4yPPS6N+G/DMMeFHTNkM9EQwn/
++DC3fDsWdGYM2GRWDTJGg1A5tSUcF+seu7i1Vg7XajBfsvgAUAsrAxV+X/sLZh94
+HY+paD6wfaI99mY2OXVc/XW/z1r9WQznor65ZkonNCaPfavqPG5vqnab9AyQcqPq
+mX8hf/xrniASBAkqNCctbASrFCIYvCJfGfmXEQIDAQABAoIBAB6GgVST5NbT9lbu
++d+rN/JSzqA1Yy8oU19/iEFJvJec96I3WnFNl8rZjN4XLUy4YarO6XMyAUDV2Gll
+FD4Sqjf4PRTZR7DSKaleGIhoqFP6hK3mUY091rIves9XhBkoBPunbipCqgDTF5ZN
+edGaXBECQP0VJ8/yX/7u++AWXthnjDis9X0taZfFg/PYbV7SCJ1Hg1O/wEsgXlnC
+7mbL6wkCW0f6700B0x1kKbZqJY95xRqp6Ipq2lIQbJDdGywoj0WzKqNltf9cer+r
+cXl8WjeiMvvvpl4uGhckAbzUifUzxN6A3f1fu/XKtOmabMi9t7J4MRfgOgedgtQB
+0jaZGSkCgYEA+lBLnNY6M48HX2mdtr86+n41gh69v8Z7oNikJFDZkodrvI8uqE0i
+0XwnYPFddt8NbmuUhhuzI2M8RKhGLgdlbKpkSSVafnMfcxRmX2EAtWQgdvX1Iult
+752LWdBgSuw2vlzvy3T/GYnjMrXSCGput4amqojMEbvUGvIdSUMdHGMCgYEAxtU1
+WixKPL6aEnYy1f4bybzcNgGtl8PBRz9xw+P46g+ijOPoaG9O73Tr7An11AO003Ot
+DHhMW+b8yHLyxoKwS2sU2cN/lKB8xNQYZc1D61RNJlzgnHMXnA0lcH0I3M35fqKr
+/71pD1ZP40SSJS+od/KEjW80XzuOdyiXg8q81vsCgYEAnUPLbbsuj+whzrFVlFZr
+IKwgxCK6Rn3WeIUEA4kEWUpZxvsSbk0gPgtJ1l9uwFt9Xc2bX/KRRv93Aw/SH+Mn
+tvEK1uXwCBgePzgm5W/VeSFyQCthm1CbcHtD7Oa9SPVFo65SPjrAd3QpWVfgoMb1
+zrp7hhMyW0XuCgvpmHjhFk8CgYEAxq/thXM2p+bLLWGhwQcRG5G299zLbBl4PUsf
+0uEvLi17gJCKADoiRdSvoAn/9eHSQ26XYRuhKkDzHxcGlOmpY2PYzRa3mXyZ0VIk
+Iy5wDWwLQCeVZ6D22cClRfgb8BF/nFTPzVmn72SPpgoyhChQj7PvUynpyrRH07jj
+VxYziBsCgYAFr37Xbl0VnXVK+XU+vMwUZjcF4jpoCr7SFZqgRbW2GbYSUoMuPXns
+RnJh+Fvi1NUei+E5s1H4P1pVq4p0jFxP4GvH/qvNjnIn/Er3bbqvpox6dWUJXprq
+qTQSDIeoDC/V8cyRoIfqPvTVqY8Rgew6GEkv0bAImdxhoSng7vIseg==
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.crt b/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.crt
new file mode 100644
index 0000000000..23c06da01c
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.crt
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDQzCCAiugAwIBAgIIICIBBBQ2MQEwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UE
+Aww3VGVzdCBDQSBmb3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHNl
+cnZlciBjZXJ0czAeFw0yMjAxMDQyMjM2MzFaFw00OTA1MjIyMjM2MzFaMDQxHjAc
+BgNVBAsMFVBvc3RncmVTUUwgdGVzdCBzdWl0ZTESMBAGA1UEAwwJMTkyLjAuMi4x
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8xddbo/x2TOSIa/br8BN
+o/URdTr9+l2R5YojiZKDuLxiQVkgC30PJ2/CNFKIh2nHhRrzknI6sETVtrxZ+9V2
+qRc1yShVu462u0DHPRMIZnZIOZg3hlNB0cRWbOglUKlttIARNEQUcTUyPOtyo4/v
++u0Ej5NTNcHFbFT01vdD9MjQiCO3jKdAwPIb14jTg4C71EpZ+LuelDo4DzF2/XgG
+WqUTrgD/XnBU/60PU9Iy3G0nVpx21q6ppn9G7a9R+i8FjBcwW1T+cfsBDWhAv+bi
+RmSAkENf8L8TwOlDQUwROkfz3Hz36vuJjdkreQJsiqL0HnrnH5T5G9UzJO86FvZQ
+5wIDAQABo0swSTBHBgNVHREEQDA+gh1kbnMxLmFsdC1uYW1lLnBnLXNzbHRlc3Qu
+dGVzdIIdZG5zMi5hbHQtbmFtZS5wZy1zc2x0ZXN0LnRlc3QwDQYJKoZIhvcNAQEL
+BQADggEBAF+mfaw6iBPzpCgqq830pHRa3Yzm1aezt8SkeRohUYHNv/yCnDSRaqtj
+xbENih3lJMSTBL3g0wtTOHfH8ViC/h+lvYELHzXKic7gkjV7H5XETKGr0ZsjBBT2
+4cZQKbD9e0x0HrENXMYgGpBf747qL6uTOVJdG0s15hwpLq47bY5WUjXathejbpxW
+prmF8F+xaC52N9P/1VnqguQB909F4x1pyOK7D7tjFu+Y8Je7PHKbb6WY5K6xAv6t
+R17CY0749/FotlphquElUR2bs5Zzv5YrjUHPTcbwKvcH5cdNi93/u6NJt2xNAoYf
+aZERhX5TA9DYk4gC8OY0yGaYCIj3Dd4=
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.key b/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.key
new file mode 100644
index 0000000000..0ace41e0a1
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-and-dns-alt-names.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEogIBAAKCAQEA8xddbo/x2TOSIa/br8BNo/URdTr9+l2R5YojiZKDuLxiQVkg
+C30PJ2/CNFKIh2nHhRrzknI6sETVtrxZ+9V2qRc1yShVu462u0DHPRMIZnZIOZg3
+hlNB0cRWbOglUKlttIARNEQUcTUyPOtyo4/v+u0Ej5NTNcHFbFT01vdD9MjQiCO3
+jKdAwPIb14jTg4C71EpZ+LuelDo4DzF2/XgGWqUTrgD/XnBU/60PU9Iy3G0nVpx2
+1q6ppn9G7a9R+i8FjBcwW1T+cfsBDWhAv+biRmSAkENf8L8TwOlDQUwROkfz3Hz3
+6vuJjdkreQJsiqL0HnrnH5T5G9UzJO86FvZQ5wIDAQABAoIBAGv0BFoFMrHyZQLw
+xe7Wx6P4QTh+aiu1QgVdw0pk9nojrr62hbSUZRZuWyBBRsBcCW7i+Sgf8lA1QXNV
+UeC0e228EPa0It6YEi42JkTJHwHhpVFud7n/X0t4lajnryqTE1UFSp6bXTipFxZW
+uSJJ2ZjliRD5rApDcxkY4WJVjKg3aEt7P/DiM8iKGfyE6stq72VjEbJjdViMEcOP
+BNf0TiREZz5Mp7jAVWhpen0ebbLOBVWV4/ONNcL+yqR4mCEDUSFGewrTVX4zHL0A
+hYk198C5F8sFvEDnFkPco9sXMVanmLoI8sbhP4IIz9g4+GU6kFuj7fUKp11Azqv+
+3WQDKYECgYEA/XG4mmG/g8FG44y42mfZpUXWi1pwU4CQIrhkoU5j7EPQrvRboOOE
+Rv95jSwyZu4vCqjyI5FN1jCGTdhmt++R1e//zH6Hqa9Smo+jw7DtAFrCYd1JnCf1
+ToOwsYPHv4P7A8q8kc5vCNIv+AQSlP/wqdVNo3grdf7cGXkMtEY4F9UCgYEA9Yrq
+zWdnNGPATuSBqL6TSjQ37oR+dBD6WnGsiDenQkOzyDPFZ3CT1DjJghjEtxc8EfNf
+Oo8dMMR2q+5FZQo7WuqONEgyzKePiNR8RK2gOYpgdjN9bih1sAhHR10D26cpwlDJ
+bx7D5ZzENLbdZmfEiWwKswnaIhN4yMalgE0mP8sCgYAhzJy12ftUct4lUosEdX0N
+EXc/NlxshmSyfKzO5kllJNYbvvLJTg5B+agYL6C5IWKcpVNFcwdSXT5L+2QXe5eT
+VGJkvysQchUuD6HjYyD4PyJVMtGyRZHtWpqh0dU9sTg0lUD4oPMl1gIXrVNdE5Tg
+0VV9S3VgUxC/ROlw0TyB0QKBgGsVE0NS9hJF8mc1hko2GnwA++d8Rr2NbfElo+2f
+/8SJTA1ibpOm6AFkZpTjAl8qtdrKPVyHb16GP47Jkd/3r1z979hjKCxSYul0aWF2
+KusNKvZBjFEPOgv0AEniCb2wUCjbHI3mZ95qGLM4kKOJW4/m21+rS0MTJNjCsQic
+HLMzAoGAeCsY09d3m8xGeU+DuTPC6GH7Sgy/NBYqS5VaVNjb2jnuZlW2SSW2oiID
+4tXTi4ruKmHC898BfyFxhSMqub+tg3pVqIYADC71rnJLrVyc1SzoWzL7yMT3qFj7
+C7ZYZYmfG9agcZb5NkqKPTfCxkBhWbdgTTgBKVO/xQst8EUgko8=
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-ip-cn-only.crt b/src/test/ssl/ssl/server-ip-cn-only.crt
new file mode 100644
index 0000000000..9bf015cf18
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-only.crt
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC8TCCAdkCCCAhESkRN1IAMA0GCSqGSIb3DQEBCwUAMEIxQDA+BgNVBAMMN1Rl
+c3QgQ0EgZm9yIFBvc3RncmVTUUwgU1NMIHJlZ3Jlc3Npb24gdGVzdCBzZXJ2ZXIg
+Y2VydHMwHhcNMjExMTI5MTkzNzUyWhcNNDkwNDE2MTkzNzUyWjA0MR4wHAYDVQQL
+DBVQb3N0Z3JlU1FMIHRlc3Qgc3VpdGUxEjAQBgNVBAMMCTE5Mi4wLjIuMTCCASIw
+DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANWs1uUL71nHYF9Zj6p+M3MpYDvx
+32iCjVdtH5a2qpSWHXTg0rR8dLX0y92cvOYvMXHRajZT1avpHr8dooPYSVaXpGMK
+NvF/Qi+WFYovRbP2vmd1yv1cgW/FggbwJFWVobizIz4seyA4d0B2j9fqoi2OFBNP
+huW664SjF0u3p21tDy+43i2LNUMAKf6dnRR5Vqenath87LEU41tSLudu6NXgbFMk
+jvfNkl4d0w7YCzeXmklmSI+uaX3PlJJ4NzQO2j8w5BvnKVhNVD0KjgrXZ6nB/8F7
+Pg3XY+d7rJlwRgXemU6resWQDJ7+UaC9u7I4EIP+9lzCR/nNBqUktpHRmHUCAwEA
+ATANBgkqhkiG9w0BAQsFAAOCAQEAos1JncV8Yf4UaKl6h1GdYtcVtzFyJvBEnhRD
+07ldL+TYnfZiX8wK2ssBtM3cg/C78y5bzdUa5XGS83ZKQJFFdhE7PSnrvyNqyIqY
+ZgNBxto3gyvir+EjO1u9BAB0NP3r3gYoHRDZS1xOPPzt4WgjuUgTLM9k82GsqAbO
+UrOTOdRnkIqC5xLpa05EnRyJPRsR1w1PRJC2XXKnHIuFjMb4v7UuPwyCcX1P5ioc
+rQszQcORy/L+k0ezCkyweORg68htjYbBHuwOuiGfok6yKKDMzrTvD3lIslls6eX7
+4sI3XWqzkPmG9Vsxm9Vu9/Ma+PRO76VyCoIwBd+Ufg5vNXhMmw==
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-ip-cn-only.key b/src/test/ssl/ssl/server-ip-cn-only.key
new file mode 100644
index 0000000000..1966530e72
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-cn-only.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEA1azW5QvvWcdgX1mPqn4zcylgO/HfaIKNV20flraqlJYddODS
+tHx0tfTL3Zy85i8xcdFqNlPVq+kevx2ig9hJVpekYwo28X9CL5YVii9Fs/a+Z3XK
+/VyBb8WCBvAkVZWhuLMjPix7IDh3QHaP1+qiLY4UE0+G5brrhKMXS7enbW0PL7je
+LYs1QwAp/p2dFHlWp6dq2HzssRTjW1Iu527o1eBsUySO982SXh3TDtgLN5eaSWZI
+j65pfc+Ukng3NA7aPzDkG+cpWE1UPQqOCtdnqcH/wXs+Dddj53usmXBGBd6ZTqt6
+xZAMnv5RoL27sjgQg/72XMJH+c0GpSS2kdGYdQIDAQABAoIBAQDNXviU4WnF8rmQ
+K7bH+dBdqbETLKC8BG7xTrMD2sINWlMpmUUrsEtE7+paMGHnJAj0CoF5gg5m0wN4
+UXV4H5QtpEad4p14dAYbUreVP2ZRWKEdM7xM1HKcCUu2e22QzObJbXQ8N+iHyX3k
++Y+7yYrjGiH1hYR0nbnsnAyx++zyYBSQeqzpdQwf/BLY5xZmyYWNfqbckiMpEqMs
+EmZmGXnCjIipzEC0LQHoSW9PNa92Z9bvuxOKYl8iHYDDXjvMRFoZBSiMXpzHQocb
+QlQ5F4ayfW2OrOhpNbY7niYM9GN3Bk9TgMP+0BkJE6uuktLYW35LY1M78CCPWcWb
+npJNK3QBAoGBAOxkGrhAHAysSmtirIyMdvySb76wb/Ukfi+AULKz20FI5j4/GXm9
+qCb2GeT+FFSUHeSC8f0EFnosRYkdBGruqeZioI+5rUkboYFJPspAHAuvg9kgtfF+
+kvphD4O4P/foYsEZRx66FHozDbhrrR5UXc7KzqRIASc/D3FOx2UFJLb1AoGBAOdm
+WcaMvYygl9ZW+ThWAR1xG1X70AGKwrlrpF2hBkWYxSurxSMXnD0DUzC9Nb4EyCaM
+c2uSqEZOKdW+XfXtK2DnqXKfb3YCVEoGN4gVfyuW/vxii/+ZxLo3md/b3vrkZEVp
+pfkXy/HoZ71YN7bNpcDpOnhml6vvuCRCYFnI1WuBAoGAC0shB6pwbJ6Sk5zMN47C
+ZICufAK75o9OxAAyWsdC81SDQ3gKRImuDeZ2CD2nRP8qim9DFl5qoH2a+Nj9DArI
+7SvLFfK9958tURrpuAnmDRzehLIOXzI33WRjtFxKGhLtHOKTRkGHlur3fdcPF0La
+lHWV971E6NYXa8diuU3Mmj0CgYBYd+ka3/QYL83dRKNDxp3mg7fPx9ZewI5yFZVh
+to6PTTkU2Tclk4FIUl0b5TsGyw06r7fxCMENIBUegwmpXGOZSPifuhUDKSDQrE/O
+12knYTNbitG7hy6Pg3JxA77cbTVo1FuAQHjYo+IFohSq7zTP7FtObOrP8XaVZksw
+CHiQAQKBgBW4EiA9AAnZ1LOpifAvM7bs0NHg95qTwtAL52WKom2ga2H+lMhxeu6Y
+hUSytC/f9kALVcYloZhkLYpO07x1gXmy7f4parMjA4Ex+4vfu3kPd8GiNGZ+AUJD
+nnJ1OINY9ziXJZfju7FpVWpkiuPzWCh6y/o3gZ/veq5mIUxuDMVa
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-ip-in-dnsname.crt b/src/test/ssl/ssl/server-ip-in-dnsname.crt
new file mode 100644
index 0000000000..78ad8d99c8
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-in-dnsname.crt
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC/DCCAeSgAwIBAgIIICIDFRVYUgAwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UE
+Aww3VGVzdCBDQSBmb3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHNl
+cnZlciBjZXJ0czAeFw0yMjAzMTUyMjU4NTJaFw00OTA3MzEyMjU4NTJaMCAxHjAc
+BgNVBAsMFVBvc3RncmVTUUwgdGVzdCBzdWl0ZTCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBAMpn5bP1/OfBQR/yvOkOBzxArE1j1YShVa2pcj896+CVDEgV
+N5Hluz7KHU/JYzNZCAHb5WAHuvXxKeoj4Ti5be1KsqO0mN1p+RMN7VlCpCpb0AWT
+z4z+I8TUhSZnmgghHvfW4RfcZMCcHq1vevVTDxR/cAbDPYpgBCD5F/SZMRyMDw5B
+7ILLmft0eqA1nCqavyqBCGZvx1ol8N5BfVdrDXp/rN5997khBWQRZ8g84FZyFZXf
+pwp57eu0OGQDzZFXoEL2t4OVld67K5jcclWVxHY6FGcHjCvyqs48PCPOR84anZwj
+GsqVOS6250/DWKBQO4KyhkTVf0AW/ICGSMOKkAkCAwEAAaMYMBYwFAYDVR0RBA0w
+C4IJMTkyLjAuMi4xMA0GCSqGSIb3DQEBCwUAA4IBAQDIAAH0WJKEpbPN0QihN6SF
+UA5WL4ixsBACo9OIAGkSnKeOeVEG5vvgOna0hjQcOcgtI1oCDLhULcjCuwxiIW6y
+QntOazyo0sooJr0hEm2WfipvIpQs6W9E1OTcs624BAVfkAwr6WT2VwoIAPcQD2nR
+tIQhSUIR9J7Q5WbzuQw7pthQhBfW/UPWw7vajel0r1dflbe0Cgp5WGNfp1kYy+Qf
+XW/YjkstZEP1KFm+TF58uxrIDmYboS8EerUREGQixijbI0AfXjShxtiyS63rbdpo
+3C0BPj9Yx2VtWi4U0qoef/iLJxJBCLvE/97+duPdKx0AkkOWA9VuenkWLp797UM8
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-ip-in-dnsname.key b/src/test/ssl/ssl/server-ip-in-dnsname.key
new file mode 100644
index 0000000000..ba319b001e
--- /dev/null
+++ b/src/test/ssl/ssl/server-ip-in-dnsname.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAymfls/X858FBH/K86Q4HPECsTWPVhKFVralyPz3r4JUMSBU3
+keW7PsodT8ljM1kIAdvlYAe69fEp6iPhOLlt7Uqyo7SY3Wn5Ew3tWUKkKlvQBZPP
+jP4jxNSFJmeaCCEe99bhF9xkwJwerW969VMPFH9wBsM9imAEIPkX9JkxHIwPDkHs
+gsuZ+3R6oDWcKpq/KoEIZm/HWiXw3kF9V2sNen+s3n33uSEFZBFnyDzgVnIVld+n
+Cnnt67Q4ZAPNkVegQva3g5WV3rsrmNxyVZXEdjoUZweMK/Kqzjw8I85HzhqdnCMa
+ypU5LrbnT8NYoFA7grKGRNV/QBb8gIZIw4qQCQIDAQABAoIBAA2kPP4JCTeRddMy
+Z/sJIAG2liZNITnkKcMflXyfrsMfKIm/LFSf+CO+OYWEHDR8vqZpbKcxPi+PRnTq
+YCaTkM4aZ7nS1S6vEsNu/90xOaFFONr3YFivVDfS3vp8pwv/N3gaumcCSqQUoZis
+18urAmwuPp2mEQK/f+e9AhlRLdcvlqDyKm+zMrVixK77Hj5JiEkh3rfZ3onHHKGE
+B7T2XRRqnZ4FCN9qLH2pMGUknZ4MGC9SlCyoerXFodb4DhKWQhJDRLjb8qP96r/E
+FGSg5WUiAERU/OgODoqZNTeIwIDB/f9NK45dEY3Hw6BsSFfU2VChrlNoVlzFUx2k
+yaH5Y4ECgYEA8rht3crh3GTy0jBJjNqB2iul8fkG/uiaiSvERWT/+KZnmV1+JGAW
+h2/wvd5apagOJjqKY0bCHMei/qYF9r4yJnkIy4qNper3QUz7TMCjsWduCm8S834A
+Z+Vwi3RBGJiQQH9Dfexko5sDjo+w5g4RsH52INCeReInNdxHOv06jZECgYEA1XrR
+QNwZlxHt3H93YKmKDZXikqW12Cuq6RSwf5VVdeuzV+pUN+/JaSgEuYsBilW7Q5p2
+gPROi0l8/eUPsBJb+dh1BcGzSjI2Kkzf66QOTG83S7tCPwQhwJUAylFuADvURjPQ
+qvqNjbQUomdm2QjBzyWtiFbolqxBgM3dnE6R/vkCgYBYGqQexx83LhmKPGbmTwal
+mARzkg59BxfZRN7IxcG4k0a1v98i+xISdYqwkP7cdOU18Tf8k1mwsrKytrcheqaf
+mn2bzJ5gJKs9s+DgWmjQ45dpCCqb4hfpnro8lKVwdSifkNKB6gYZ8RHYdMYkq+S1
+6SGeBbv95/qNrXjZq8POUQKBgHyaDwD4dsdCY79LdvYofrenQHOv3Q+rjTo2JT6S
+fysww6EQ2M89WiXSgc96Xw/LMl4nDfv+nMmXvyjCRgHS9XRC7yrJAEjSPeM6s4fq
+XZ4nW/ML/YKiesDZN3jfRoFEaoX/QFBLpcuLzG9uQw1ymwy5RSxK7b7kE+eGQU82
+XOihAoGBAI3xvT9fG3jRsSuw/8OQBlmDUFZcT0fRPRZ3pg8XlSreAam4b607d2WY
+u/bBHIclG3CLJ2EFqBtxl9AQeM0OTweF0KmV3dbtdBmaTbnhbK8/NLYnl5+aosEJ
+YrFKD8k8z6z+mYQs+7bAnfRa53TjfC7f24BpgEQyEfKL2fa3PF+J
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/sslfiles.mk b/src/test/ssl/sslfiles.mk
index 7ed3a30f5c..d6a3870079 100644
--- a/src/test/ssl/sslfiles.mk
+++ b/src/test/ssl/sslfiles.mk
@@ -22,9 +22,15 @@
 # key/certificate pair will be generated for you, signed by the appropriate CA.
 #
 SERVERS := server-cn-and-alt-names \
+	server-cn-and-ip-alt-names \
 	server-cn-only \
+	server-ip-cn-only \
+	server-ip-cn-and-alt-names \
+	server-ip-cn-and-dns-alt-names \
+	server-ip-in-dnsname \
 	server-single-alt-name \
 	server-multiple-alt-names \
+	server-ip-alt-names \
 	server-no-names \
 	server-revoked
 CLIENTS := client client-dn client-revoked client_ext
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index d8eeb085da..051de4efb0 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -229,6 +229,30 @@ $node->connect_fails(
 	  qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/
 );
 
+# Test with an IP address in the Common Name. This is a strange corner case that
+# nevertheless is supported, as long as the address string matches exactly.
+switch_server_cert($node, certfile => 'server-ip-cn-only');
+
+$common_connstr =
+  "$default_ssl_connstr user=ssltestuser dbname=trustdb sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+
+$node->connect_ok("$common_connstr host=192.0.2.1",
+	"IP address in the Common Name");
+
+$node->connect_fails(
+	"$common_connstr host=192.000.002.001",
+	"mismatch between host name and server certificate IP address",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.1" does not match host name "192.000.002.001"\E/
+);
+
+# Similarly, we'll also match an IP address in a dNSName SAN. (This is
+# long-standing behavior.)
+switch_server_cert($node, certfile => 'server-ip-in-dnsname');
+
+$node->connect_ok("$common_connstr host=192.0.2.1",
+	"IP address in a dNSName");
+
 # Test Subject Alternative Names.
 switch_server_cert($node, certfile => 'server-multiple-alt-names');
 
@@ -281,7 +305,58 @@ $node->connect_fails(
 	  qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/
 );
 
-# Test server certificate with a CN and SANs. Per RFCs 2818 and 6125, the CN
+# Test certificate with IP addresses in the SANs.
+switch_server_cert($node, certfile => 'server-ip-alt-names');
+
+$node->connect_ok(
+	"$common_connstr host=192.0.2.1",
+	"host matching an IPv4 address (Subject Alternative Name 1)");
+
+$node->connect_ok(
+	"$common_connstr host=192.000.002.001",
+	"host matching an IPv4 address in alternate form (Subject Alternative Name 1)");
+
+$node->connect_fails(
+	"$common_connstr host=192.0.2.2",
+	"host not matching an IPv4 address (Subject Alternative Name 1)",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.1" (and 1 other name) does not match host name "192.0.2.2"\E/
+);
+
+$node->connect_fails(
+	"$common_connstr host=192.0.2.1/32",
+	"IPv4 host with CIDR mask does not match",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.1" (and 1 other name) does not match host name "192.0.2.1\/32"\E/
+);
+
+$node->connect_ok(
+	"$common_connstr host=2001:DB8::1",
+	"host matching an IPv6 address (Subject Alternative Name 2)");
+
+$node->connect_ok(
+	"$common_connstr host=2001:db8:0:0:0:0:0:1",
+	"host matching an IPv6 address in alternate form (Subject Alternative Name 2)");
+
+$node->connect_ok(
+	"$common_connstr host=2001:db8::0.0.0.1",
+	"host matching an IPv6 address in mixed form (Subject Alternative Name 2)");
+
+$node->connect_fails(
+	"$common_connstr host=::1",
+	"host not matching an IPv6 address (Subject Alternative Name 2)",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.1" (and 1 other name) does not match host name "::1"\E/
+);
+
+$node->connect_fails(
+	"$common_connstr host=2001:DB8::1/128",
+	"IPv6 host with CIDR mask does not match",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.1" (and 1 other name) does not match host name "2001:DB8::1\/128"\E/
+);
+
+# Test server certificate with a CN and DNS SANs. Per RFCs 2818 and 6125, the CN
 # should be ignored when the certificate has both.
 switch_server_cert($node, certfile => 'server-cn-and-alt-names');
 
@@ -299,6 +374,39 @@ $node->connect_fails(
 	  qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/
 );
 
+# But we will fall back to check the CN if the SANs contain only IP addresses.
+switch_server_cert($node, certfile => 'server-cn-and-ip-alt-names');
+
+$node->connect_ok("$common_connstr host=common-name.pg-ssltest.test",
+	"certificate with both a CN and IP SANs matches CN");
+$node->connect_ok("$common_connstr host=192.0.2.1",
+	"certificate with both a CN and IP SANs matches SAN 1");
+$node->connect_ok("$common_connstr host=2001:db8::1",
+	"certificate with both a CN and IP SANs matches SAN 2");
+
+# And now the same tests, but with IP addresses and DNS names swapped.
+switch_server_cert($node, certfile => 'server-ip-cn-and-alt-names');
+
+$node->connect_ok("$common_connstr host=192.0.2.2",
+	"certificate with both an IP CN and IP SANs 1");
+$node->connect_ok("$common_connstr host=2001:db8::1",
+	"certificate with both an IP CN and IP SANs 2");
+$node->connect_fails(
+	"$common_connstr host=192.0.2.1",
+	"certificate with both an IP CN and IP SANs ignores CN",
+	expected_stderr =>
+	  qr/\Qserver certificate for "192.0.2.2" (and 1 other name) does not match host name "192.0.2.1"\E/
+);
+
+switch_server_cert($node, certfile => 'server-ip-cn-and-dns-alt-names');
+
+$node->connect_ok("$common_connstr host=192.0.2.1",
+	"certificate with both an IP CN and DNS SANs matches CN");
+$node->connect_ok("$common_connstr host=dns1.alt-name.pg-ssltest.test",
+	"certificate with both an IP CN and DNS SANs matches SAN 1");
+$node->connect_ok("$common_connstr host=dns2.alt-name.pg-ssltest.test",
+	"certificate with both an IP CN and DNS SANs matches SAN 2");
+
 # Finally, test a server certificate that has no CN or SANs. Of course, that's
 # not a very sensible certificate, but libpq should handle it gracefully.
 switch_server_cert($node, certfile => 'server-no-names');
-- 
2.25.1

#45Peter Eisentraut
peter.eisentraut@enterprisedb.com
In reply to: Jacob Champion (#44)
Re: [PATCH] Accept IP addresses in server certificate SANs

On 31.03.22 20:15, Jacob Champion wrote:

On Thu, 2022-03-31 at 16:32 +0200, Peter Eisentraut wrote:

Why add a (failry complicated) pg_inet_pton() when a perfectly
reasonable inet_pton() exists?

I think it was mostly just that inet_aton() and pg_inet_net_ntop() both
had ports, and I figured I might as well port the other one since we
already had the implementation. (I don't have a good intuition yet for
the community's preference for port vs dependency.)

I would get rid of all that refactoring and just have your code call
inet_pton()/inet_ntop() directly.

If you're worried about portability, and you don't want to go through
the effort of proving libpgport substitutes, just have your code raise
an error in the "#else" code paths. We can fill that in later if there
is demand.

Switched to inet_pton() in v12, with no #if/else for now. I think this
should work with Winsock as-is; let's see if the bot agrees...

I have committed this.

I have removed the inet header refactoring that you had. That wasn't
necessary, since pg_inet_net_ntop() can use the normal AF_INET*
constants. The PGSQL_AF_INET* constants are only for the internal
storage of the inet/cidr types.

I have added a configure test for inet_pton(). We can check in the
build farm if it turns out to be necessary.

#46Jacob Champion
pchampion@vmware.com
In reply to: Peter Eisentraut (#45)
Re: [PATCH] Accept IP addresses in server certificate SANs

On Fri, 2022-04-01 at 16:07 +0200, Peter Eisentraut wrote:

I have committed this.

I have removed the inet header refactoring that you had. That wasn't
necessary, since pg_inet_net_ntop() can use the normal AF_INET*
constants. The PGSQL_AF_INET* constants are only for the internal
storage of the inet/cidr types.

I have added a configure test for inet_pton(). We can check in the
build farm if it turns out to be necessary.

Thanks!

--Jacob