8000 Support Subject Alternative Names in SSL server certificates. · postgrespro/postgres@acd08d7 · GitHub
[go: up one dir, main page]

Skip to content 8000
files changed
+202
-58
lines changed

3 files changed

+202
-58
lines changed

src/interfaces/libpq/fe-misc.c

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1210,14 +1210,14 @@ PQenv2encoding(void)
12101210

12111211
#ifdef ENABLE_NLS
12121212

1213-
char *
1214-
libpq_gettext(const char *msgid)
1213+
static void
1214+
libpq_binddomain()
12151215
{
12161216
static bool already_bound = false;
12171217

12181218
if (!already_bound)
12191219
{
1220-
/* dgettext() preserves errno, but bindtextdomain() doesn't */
1220+
/* bindtextdomain() does not preserve errno */
12211221
#ifdef WIN32
12221222
int save_errno = GetLastError();
12231223
#else
@@ -1237,8 +1237,20 @@ libpq_gettext(const char *msgid)
12371237
errno = save_errno;
12381238
#endif
12391239
}
1240+
}
12401241

1242+
char *
1243+
libpq_gettext(const char *msgid)
1244+
{
1245+
libpq_binddomain();
12411246
return dgettext(PG_TEXTDOMAIN("libpq"), msgid);
12421247
}
12431248

1249+
char *
1250+
libpq_ngettext(const char *msgid, const char *msgid_plural, unsigned long n)
1251+
{
1252+
libpq_binddomain();
1253+
return dngettext(PG_TEXTDOMAIN("libpq"), msgid, msgid_plural, n);
1254+
}
1255+
12441256
#endif /* ENABLE_NLS */

src/interfaces/libpq/fe-secure-openssl.c

Lines changed: 183 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,13 @@
6060
#ifdef USE_SSL_ENGINE
6161
#include <openssl/engine.h>
6262
#endif
63+
#include <openssl/x509v3.h>
6364

6465
static bool verify_peer_name_matches_certificate(PGconn *);
6566
static int verify_cb(int ok, X509_STORE_CTX *ctx);
67+
static int verify_peer_name_matches_certificate_name(PGconn *conn,
68+
ASN1_STRING *name,
69+
char **store_name);
6670
static void destroy_ssl_system(void);
6771
static int initialize_SSL(PGconn *conn);
6872
static PostgresPollingStatusType open_client_SSL(PGconn *);
@@ -473,98 +477,222 @@ wildcard_certificate_match(const char *pattern, const char *string)
473477

474478

475479
/*
476-
* Verify that common name resolves to peer.
480+
* Check if a name from a server's certificate matches the peer's hostname.
481+
*
482+
* Returns 1 if the name matches, and 0 if it does not. On error, returns
483+
* -1, and sets the libpq error message.
484+
*
485+
* The name extracted from the certificate is returned in *store_name. The
486+
* caller is responsible for freeing it.
477487
*/
478-
static bool
479-
verify_peer_name_matches_certificate(PGconn *conn)
488+
static int
489+
verify_peer_name_matches_certificate_name(PGconn *conn, ASN1_STRING *name_entry,
490+
char **store_name)
480491
{
481-
char *peer_cn;
482-
int r;
483492
int len;
484-
bool result;
493+
char *name;
494+
unsigned char *namedata;
495+
int result;
485496

486-
/*
487-
* If told not to verify the peer name, don't do it. Return true
488-
* indicating that the verification was successful.
489-
*/
490-
if (strcmp(conn->sslmode, "verify-full") != 0)
491-
return true;
497+
*store_name = NULL;
498+
499+
/* Should not happen... */
500+
if (name_entry == NULL)
501+
{
502+
printfPQExpBuffer(&conn->errorMessage,
503+
libpq_gettext("SSL certificate's name entry is missing\n"));
504+
return -1;
505+
}
492506

493507
/*
494-
* Extract the common name from the certificate.
508+
* GEN_DNS can be only IA5String, equivalent to US ASCII.
495509
*
496-
* XXX: Should support alternate names here
510+
* There is no guarantee the string returned from the certificate is
511+
* NULL-terminated, so make a copy that is.
497512
*/
498-
/* First find out the name's length and allocate a buffer for it. */
499-
len = X509_NAME_get_text_by_NID(X509_get_subject_name(conn->peer),
500-
NID_commonName, NULL, 0);
501-
if (len == -1)
513+
namedata = ASN1_STRING_data(name_entry);
514+
len = ASN1_STRING_length(name_entry);
515+
name = malloc(len + 1);
516+
if (name == NULL)
502517
{
503518
printfPQExpBuffer(&conn->errorMessage,
504-
libpq_gettext("could not get server common name from server certificate\n"));
505-
return false;
519+
libpq_gettext("out of memory\n"));
520+
return -1;
506521
}
507-
peer_cn = malloc(len + 1);
508-
if (peer_cn == NULL)
522+
memcpy(name, namedata, len);
523+
name[len] = '\0';
524+
525+
/*
526+
* Reject embedded NULLs in certificate common or alternative name to
527+
* prevent attacks like CVE-2009-4034.
528+
*/
529+
if (len != strlen(name))
509530
{
531+
free(name);
510532
printfPQExpBuffer(&conn->errorMessage,
511-
libpq_gettext("out of memory\n"));
512-
return false;
533+
libpq_gettext("SSL certificate's name contains embedded null\n"));
534+
return -1;
513535
}
514536

515-
r = X509_NAME_get_text_by_NID(X509_get_subject_name(conn->peer),
516-
NID_commonName, peer_cn, len + 1);
517-
if (r != len)
537+
if (pg_strcasecmp(name, conn->pghost) == 0)
518538
{
519-
/* Got different length than on the first call. Shouldn't happen. */
520-
printfPQExpBuffer(&conn->errorMessage,
521-
libpq_gettext("could not get server common name from server certificate\n"));
522-
free(peer_cn);
523-
return false;
539+
/* Exact name match */
540+
result = 1;
541+
}
542+
else if (wildcard_certificate_match(name, conn->pghost))
543+
{
544+
/* Matched wildcard name */
545+
result = 1;
546+
}
547+
else
548+
{
549+
result = 0;
524550
}
525-
peer_cn[len] = '\0';
551+
552+
*store_name = name;
553+
return result;
554+
}
555+
556+
/*
557+
* Verify that the server certificate matches the hostname we connected to.
558+
*
559+
* The certificate's Common Name and Subject Alternative Names are considered.
560+
*/
561+
static bool
562+
verify_peer_name_matches_certificate(PGconn *conn)
563+
{
564+
int names_examined = 0;
565+
bool found_match = false;
566+
bool got_error = false;
567+
char *first_name = NULL;
568+
STACK_OF(GENERAL_NAME) *peer_san;
569+
int i;
570+
int rc;
526571

527572
/*
528-
* Reject embedded NULLs in certificate common name to prevent attacks
529-
* like CVE-2009-4034.
573+
* If told not to verify the peer name, don't do it. Return true
574+
* indicating that the verification was successful.
530575
*/
531-
if (len != strlen(peer_cn))
576+
if (strcmp(conn->sslmode, "verify-full") != 0)
577+
return true;
578+
579+
/* Check that we have a hostname to compare with. */
580+
if (!(conn->pghost && conn->pghost[0] != '\0'))
532581
{
533582
printfPQExpBuffer(&conn->errorMessage,
534-
libpq_gettext("SSL certificate's common name contains embedded null\n"));
535-
free(peer_cn);
583+
libpq_gettext("host name must be specified for a verified SSL connection\n"));
536584
return false;
537585
}
538586

539587
/*
540-
* We got the peer's common name. Now compare it against the originally
541-
* given hostname.
588+
* First, get the Subject Alternative Names (SANs) from the certificate,
589+
* and compare them against the originally given hostname.
542590
*/
543-
if (!(conn->pghost && conn->pghost[0] != '\0'))
591+
peer_san = (STACK_OF(GENERAL_NAME) *)
592+
X509_get_ext_d2i(conn->peer, NID_subject_alt_name, NULL, NULL);
593+
594+
if (peer_san)
544595
{
545-
printfPQExpBuffer(&conn->errorMessage,
546-
libpq_gettext("host name must be specified for a verified SSL connection\n"));
547-
result = false;
596+
int san_len = sk_GENERAL_NAME_num(peer_san);
597+
598+
for (i = 0; i < san_len; i++)
599+
{
600+
const GENERAL_NAME *name = sk_GENERAL_NAME_value(peer_san, i);
601+
602+
if (name->type == GEN_DNS)
603+
{
604+
char *alt_name;
605+
606+
names_examined++;
607+
rc = verify_peer_name_matches_certificate_name(conn,
608+
name->d.dNSName,
609+
&alt_name);
610+
if (rc == -1)
611+
got_error = true;
612+
if (rc == 1)
613+
found_match = true;
614+
615+
if (alt_name)
616+
{
617+
if (!first_name)
618+
first_name = alt_name;
619+
else
620+
free(alt_name);
621+
}
622+
}
623+
if (found_match || got_error)
624+
break;
625+
}
626+
sk_GENERAL_NAME_free(peer_san);
548627
}
628+
/*
629+
* If there is no subjectAltName extension, check the Common Name.
630+
*
631+
* (Per RFC 2818 and RFC 6125, if the subjectAltName extension is present,
632+
* the CN must be ignored.)
633+
*/
549634
else
550635
{
551-
if (pg_strcasecmp(peer_cn, conn->pghost) == 0)
552-
/* Exact name match */
553-
result = true;
554-
else if (wildcard_certificate_match(peer_cn, conn->pghost))
555-
/* Matched wildcard certificate */
556-
result = true;
636+
X509_NAME *subject_name;
637+
638+
subject_name = X509_get_subject_name(conn->peer);
639+
if (subject_name != NULL)
640+
{
641+
int cn_index;
642+
643+
cn_index = X509_NAME_get_index_by_NID(subject_name,
644+
NID_commonName, -1);
645+
if (cn_index >= 0)
646+
{
647+
names_examined++;
648+
rc = verify_peer_name_matches_certificate_name(
649+
conn,
650+
X509_NAME_ENTRY_get_data(
651+
X509_NAME_get_entry(subject_name, cn_index)),
652+
&first_name);
653+
654+
if (rc == -1)
655+
got_error = true;
656+
else if (rc == 1)
657+
found_match = true;
658+
}
659+
}
660+
}
661+
662+
if (!found_match && !got_error)
663+
{
664+
/*
665+
* No match. Include the name from the server certificate in the
666+
* error message, to aid debugging broken configurations. If there
667+
* are multiple names, only print the first one to avoid an overly
668+
* long error message.
669+
*/
670+
if (names_examined > 1)
671+
{
672+
printfPQExpBuffer(&conn->errorMessage,
673+
libpq_ngettext("server certificate for \"%s\" (and %d other name) does not match host name \"%s\"\n",
674+
"server certificate for \"%s\" (and %d other names) does not match host name \"%s\"\n",
675+
names_examined - 1),
676+
first_name, names_examined - 1, conn->pghost);
677+
}
678+
else if (names_examined == 1)
679+
{
680+
printfPQExpBuffer(&conn->errorMessage,
681+
libpq_gettext("server certificate for \"%s\" does not match host name \"%s\"\n"),
682+
first_name, conn->pghost);
683+
}
557684
else
558685
{
559686
printfPQExpBuffer(&conn->errorMessage,
560-
libpq_gettext("server common name \"%s\" does not match host name \"%s\"\n"),
561-
peer_cn, conn->pghost);
562-
result = false;
687+
libpq_gettext("could not get server's hostname from server certificate\n"));
563688
}
564689
}
565690

566-
free(peer_cn);
567-
return result;
691+
/* clean up */
692+
if (first_name)
693+
free(first_name);
694+
695+
return found_match && !got_error;
568696
}
569697

570698
#ifdef ENABLE_THREAD_SAFETY

src/interfaces/libpq/libpq-int.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -653,8 +653,12 @@ extern ssize_t pgtls_write(PGconn *conn, const void *ptr, size_t len);
653653
extern char *
654654
libpq_gettext(const char *msgid)
655655
__attribute__((format_arg(1)));
656+
extern char *
657+
libpq_ngettext(const char *msgid, const char *msgid_plural, unsigned long n)
658+
__attribute__((format_arg(1))) __attribute__((format_arg(2)));
656659
#else
657660
#define libpq_gettext(x) (x)
661+
#define libpq_ngettext(s, p, n) ((n) == 1 ? (s) : (p))
658662
#endif
659663

660664
/*

0 commit comments

Comments
 (0)
0