#ifdef USE_SSL_ENGINE
#include <openssl/engine.h>
#endif
+#include <openssl/x509v3.h>
static bool verify_peer_name_matches_certificate(PGconn *);
static int verify_cb(int ok, X509_STORE_CTX *ctx);
+static int verify_peer_name_matches_certificate_name(PGconn *conn,
+ ASN1_STRING *name,
+ char **store_name);
static void destroy_ssl_system(void);
static int initialize_SSL(PGconn *conn);
static PostgresPollingStatusType open_client_SSL(PGconn *);
/*
- * Verify that common name resolves to peer.
+ * Check if a name from a server's certificate matches the peer's hostname.
+ *
+ * Returns 1 if the name matches, and 0 if it does not. On error, returns
+ * -1, and sets the libpq error message.
+ *
+ * The name extracted from the certificate is returned in *store_name. The
+ * caller is responsible for freeing it.
*/
-static bool
-verify_peer_name_matches_certificate(PGconn *conn)
+static int
+verify_peer_name_matches_certificate_name(PGconn *conn, ASN1_STRING *name_entry,
+ char **store_name)
{
- char *peer_cn;
- int r;
int len;
- bool result;
+ char *name;
+ unsigned char *namedata;
+ int result;
- /*
- * If told not to verify the peer name, don't do it. Return true
- * indicating that the verification was successful.
- */
- if (strcmp(conn->sslmode, "verify-full") != 0)
- return true;
+ *store_name = NULL;
+
+ /* Should not happen... */
+ if (name_entry == NULL)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL certificate's name entry is missing\n"));
+ return -1;
+ }
/*
- * Extract the common name from the certificate.
+ * GEN_DNS can be only IA5String, equivalent to US ASCII.
*
- * XXX: Should support alternate names here
+ * There is no guarantee the string returned from the certificate is
+ * NULL-terminated, so make a copy that is.
*/
- /* First find out the name's length and allocate a buffer for it. */
- len = X509_NAME_get_text_by_NID(X509_get_subject_name(conn->peer),
- NID_commonName, NULL, 0);
- if (len == -1)
+ namedata = ASN1_STRING_data(name_entry);
+ len = ASN1_STRING_length(name_entry);
+ name = malloc(len + 1);
+ if (name == NULL)
{
printfPQExpBuffer(&conn->errorMessage,
- libpq_gettext("could not get server common name from server certificate\n"));
- return false;
+ libpq_gettext("out of memory\n"));
+ return -1;
}
- peer_cn = malloc(len + 1);
- if (peer_cn == NULL)
+ memcpy(name, namedata, len);
+ name[len] = '\0';
+
+ /*
+ * Reject embedded NULLs in certificate common or alternative name to
+ * prevent attacks like CVE-2009-4034.
+ */
+ if (len != strlen(name))
{
+ free(name);
printfPQExpBuffer(&conn->errorMessage,
- libpq_gettext("out of memory\n"));
- return false;
+ libpq_gettext("SSL certificate's name contains embedded null\n"));
+ return -1;
}
- r = X509_NAME_get_text_by_NID(X509_get_subject_name(conn->peer),
- NID_commonName, peer_cn, len + 1);
- if (r != len)
+ if (pg_strcasecmp(name, conn->pghost) == 0)
{
- /* Got different length than on the first call. Shouldn't happen. */
- printfPQExpBuffer(&conn->errorMessage,
- libpq_gettext("could not get server common name from server certificate\n"));
- free(peer_cn);
- return false;
+ /* Exact name match */
+ result = 1;
+ }
+ else if (wildcard_certificate_match(name, conn->pghost))
+ {
+ /* Matched wildcard name */
+ result = 1;
+ }
+ else
+ {
+ result = 0;
}
- peer_cn[len] = '\0';
+
+ *store_name = name;
+ return result;
+}
+
+/*
+ * Verify that the server certificate matches the hostname we connected to.
+ *
+ * The certificate's Common Name and Subject Alternative Names are considered.
+ */
+static bool
+verify_peer_name_matches_certificate(PGconn *conn)
+{
+ int names_examined = 0;
+ bool found_match = false;
+ bool got_error = false;
+ char *first_name = NULL;
+ STACK_OF(GENERAL_NAME) *peer_san;
+ int i;
+ int rc;
/*
- * Reject embedded NULLs in certificate common name to prevent attacks
- * like CVE-2009-4034.
+ * If told not to verify the peer name, don't do it. Return true
+ * indicating that the verification was successful.
*/
- if (len != strlen(peer_cn))
+ if (strcmp(conn->sslmode, "verify-full") != 0)
+ return true;
+
+ /* Check that we have a hostname to compare with. */
+ if (!(conn->pghost && conn->pghost[0] != '\0'))
{
printfPQExpBuffer(&conn->errorMessage,
- libpq_gettext("SSL certificate's common name contains embedded null\n"));
- free(peer_cn);
+ libpq_gettext("host name must be specified for a verified SSL connection\n"));
return false;
}
/*
- * We got the peer's common name. Now compare it against the originally
- * given hostname.
+ * First, get the Subject Alternative Names (SANs) from the certificate,
+ * and compare them against the originally given hostname.
*/
- if (!(conn->pghost && conn->pghost[0] != '\0'))
+ peer_san = (STACK_OF(GENERAL_NAME) *)
+ X509_get_ext_d2i(conn->peer, NID_subject_alt_name, NULL, NULL);
+
+ if (peer_san)
{
- printfPQExpBuffer(&conn->errorMessage,
- libpq_gettext("host name must be specified for a verified SSL connection\n"));
- result = false;
+ int san_len = sk_GENERAL_NAME_num(peer_san);
+
+ for (i = 0; i < san_len; i++)
+ {
+ const GENERAL_NAME *name = sk_GENERAL_NAME_value(peer_san, i);
+
+ if (name->type == GEN_DNS)
+ {
+ char *alt_name;
+
+ names_examined++;
+ rc = verify_peer_name_matches_certificate_name(conn,
+ name->d.dNSName,
+ &alt_name);
+ if (rc == -1)
+ got_error = true;
+ if (rc == 1)
+ found_match = true;
+
+ if (alt_name)
+ {
+ if (!first_name)
+ first_name = alt_name;
+ else
+ free(alt_name);
+ }
+ }
+ if (found_match || got_error)
+ break;
+ }
+ sk_GENERAL_NAME_free(peer_san);
}
+ /*
+ * If there is no subjectAltName extension, check the Common Name.
+ *
+ * (Per RFC 2818 and RFC 6125, if the subjectAltName extension is present,
+ * the CN must be ignored.)
+ */
else
{
- if (pg_strcasecmp(peer_cn, conn->pghost) == 0)
- /* Exact name match */
- result = true;
- else if (wildcard_certificate_match(peer_cn, conn->pghost))
- /* Matched wildcard certificate */
- result = true;
+ X509_NAME *subject_name;
+
+ subject_name = X509_get_subject_name(conn->peer);
+ if (subject_name != NULL)
+ {
+ int cn_index;
+
+ cn_index = X509_NAME_get_index_by_NID(subject_name,
+ NID_commonName, -1);
+ if (cn_index >= 0)
+ {
+ names_examined++;
+ rc = verify_peer_name_matches_certificate_name(
+ conn,
+ X509_NAME_ENTRY_get_data(
+ X509_NAME_get_entry(subject_name, cn_index)),
+ &first_name);
+
+ if (rc == -1)
+ got_error = true;
+ else if (rc == 1)
+ found_match = true;
+ }
+ }
+ }
+
+ if (!found_match && !got_error)
+ {
+ /*
+ * No match. Include the name from the server certificate in the
+ * error message, to aid debugging broken configurations. If there
+ * are multiple names, only print the first one to avoid an overly
+ * long error message.
+ */
+ if (names_examined > 1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_ngettext("server certificate for \"%s\" (and %d other name) does not match host name \"%s\"\n",
+ "server certificate for \"%s\" (and %d other names) does not match host name \"%s\"\n",
+ names_examined - 1),
+ first_name, names_examined - 1, conn->pghost);
+ }
+ else if (names_examined == 1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("server certificate for \"%s\" does not match host name \"%s\"\n"),
+ first_name, conn->pghost);
+ }
else
{
printfPQExpBuffer(&conn->errorMessage,
- libpq_gettext("server common name \"%s\" does not match host name \"%s\"\n"),
- peer_cn, conn->pghost);
- result = false;
+ libpq_gettext("could not get server's hostname from server certificate\n"));
}
}
- free(peer_cn);
- return result;
+ /* clean up */
+ if (first_name)
+ free(first_name);
+
+ return found_match && !got_error;
}
#ifdef ENABLE_THREAD_SAFETY