From ddf36f30a5b08ef2db758c32c6e6219b299a6ba9 Mon Sep 17 00:00:00 2001 From: Jacob Champion 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 +#include + #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