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
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
From 3081b1e0f7af60241978c77bba71e806ba5c25f6 Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchamp...@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

From 84987001f0fc7b5a6018c3f54684b780fee204cd Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchamp...@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

Reply via email to