On Mon, Sep 8, 2014 at 8:04 PM, Heikki Linnakangas
<hlinnakan...@vmware.com> wrote:
> On 09/05/2014 07:30 PM, Alexey Klyukin wrote:

>> The error does not state whether the names comes from the CN or from
>> the SAN section.
>
>
> I'd reword that slightly, to:
>
> psql: server certificate for "example.com" (and 2 other names) does not
> match host name "example-foo.com"
>
> I never liked the current wording, "server name "foo"" very much. I think
> it's important to use the word "server certificate" in the error message, to
> make it clear where the problem is.

+1

>
> For translations, that message should be "pluralized", but there is no
> libpq_ngettext macro equivalent to ngettext(), like libpq_gettext. I wonder
> if that was left out on purpose, or if we just haven't needed that in libpq
> before. Anyway, I think we need to add that for this.

Well, the translation guidelines in the documentation suggest avoiding
plurals if possible, but I don't like rephrasing the sentence with
'names total: %d', so attached is my attempt to add the function.
I have also moved the one-time library binding code to the function of its own.

>
>> This version also checks for the availability of the subject name, it
>> looks like RFC 5280 does not require it at all.
>>
>> 4.1.2.6.  Subject
>>
>>     The subject field identifies the entity associated with the public
>>     key stored in the subject public key field.  The subject name MAY be
>>     carried in the subject field and/or the subjectAltName extension.
>
>
> Ok.
>
>> The pattern of allocating the name in the subroutine and then
>> reporting it (and releasing when necessary) in the main function is a
>> little bit ugly, but it looks to me the least ugly of anything else I
>> could come up (i.e. extracting those names at the time the error
>> message is shown).
>
>
> I reworked that a bit, see attached. What do you think?

Thank you, I like your approach of unconditionally allocating and
freeing memory, it makes the code easier to read.
2 minor changes I've made in v7 (in addition to adding the libpq_ngettext):

- the verify_peer_name_matches_certificate does not try to return -1
(since it returns bool, it would be misinterpreted as true)
- removed the !got_error && !found_match check from the for loop
header to the loop body per style comment from Tom in the beginning of
this thread.


>
> I think this is ready for commit, except for two things:
>
> 1. The pluralization of the message for translation
>
> 2. I still wonder if we should follow the RFC 6125 and not check the Common
> Name at all, if SANs are present. I don't really understand the point of
> that rule, and it seems unlikely to pose a security issue in practice,
> because surely a CA won't sign a certificate with a bogus/wrong CN, because
> an older client that doesn't look at the SANs at all would use the CN
> anyway. But still... what does a Typical Web Browser do?
>

At least Firefox and Safari seem to follow RFC strictly, according to
some anecdotical evidences (I haven't got enough time to dig into the
source code yet):

http://superuser.com/questions/230136/primary-common-name-in-subjectaltname
https://bugzilla.mozilla.org/show_bug.cgi?id=238142

Chrome seem to follow them, so the major open-source browsers are
ignoring the Common Name if the SubjectAltName is present.
Still, I see no win in ignoring CN (except for the shorter code), it
just annoys some users that forgot to put the CN name also in the SAN
section, so my 5 cents is that we should check both.

Regards,
-- 
Alexey Klyukin
diff --git a/src/interfaces/libpq/fe-misc.c b/src/interfaces/libpq/fe-misc.c
new file mode 100644
index fc930bd..a082268
*** a/src/interfaces/libpq/fe-misc.c
--- b/src/interfaces/libpq/fe-misc.c
*************** static int      pqSendSome(PGconn *conn, int 
*** 64,69 ****
--- 64,71 ----
  static int pqSocketCheck(PGconn *conn, int forRead, int forWrite,
                          time_t end_time);
  static int    pqSocketPoll(int sock, int forRead, int forWrite, time_t 
end_time);
+ static void libpq_bindomain();
+ 
  
  /*
   * PQlibVersion: return the libpq version number
*************** PQenv2encoding(void)
*** 1210,1223 ****
  
  #ifdef ENABLE_NLS
  
! char *
! libpq_gettext(const char *msgid)
  {
        static bool already_bound = false;
  
        if (!already_bound)
        {
!               /* dgettext() preserves errno, but bindtextdomain() doesn't */
  #ifdef WIN32
                int                     save_errno = GetLastError();
  #else
--- 1212,1225 ----
  
  #ifdef ENABLE_NLS
  
! static void
! libpq_bindomain()
  {
        static bool already_bound = false;
  
        if (!already_bound)
        {
!               /* bindtextdomain() does not preserve errno */
  #ifdef WIN32
                int                     save_errno = GetLastError();
  #else
*************** libpq_gettext(const char *msgid)
*** 1237,1244 ****
--- 1239,1258 ----
                errno = save_errno;
  #endif
        }
+ }
  
+ char *
+ libpq_gettext(const char *msgid)
+ {
+       libpq_bindomain();
        return dgettext(PG_TEXTDOMAIN("libpq"), msgid);
  }
  
+ char *
+ libpq_ngettext(const char *msgid, const char *msgid_plural, unsigned long n)
+ {
+       libpq_bindomain();
+       return dngettext(PG_TEXTDOMAIN("libpq"), msgid, msgid_plural, n);
+ }
+ 
  #endif   /* ENABLE_NLS */
diff --git a/src/interfaces/libpq/fe-secure-openssl.c 
b/src/interfaces/libpq/fe-secure-openssl.c
new file mode 100644
index f950fc3..c51e8ff
*** a/src/interfaces/libpq/fe-secure-openssl.c
--- b/src/interfaces/libpq/fe-secure-openssl.c
***************
*** 60,68 ****
--- 60,72 ----
  #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 *);
*************** wildcard_certificate_match(const char *p
*** 473,570 ****
  
  
  /*
!  *    Verify that common name resolves to peer.
   */
! static bool
! verify_peer_name_matches_certificate(PGconn *conn)
  {
-       char       *peer_cn;
-       int                     r;
        int                     len;
!       bool            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;
  
        /*
!        * Extract the common name from the certificate.
         *
!        * XXX: Should support alternate names here
         */
!       /* 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)
        {
                printfPQExpBuffer(&conn->errorMessage,
!                                                 libpq_gettext("could not get 
server common name from server certificate\n"));
!               return false;
        }
!       peer_cn = malloc(len + 1);
!       if (peer_cn == NULL)
        {
                printfPQExpBuffer(&conn->errorMessage,
!                                                 libpq_gettext("out of 
memory\n"));
!               return false;
        }
  
!       r = X509_NAME_get_text_by_NID(X509_get_subject_name(conn->peer),
!                                                                 
NID_commonName, peer_cn, len + 1);
!       if (r != len)
        {
!               /* 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;
        }
!       peer_cn[len] = '\0';
  
        /*
!        * Reject embedded NULLs in certificate common name to prevent attacks
!        * like CVE-2009-4034.
         */
!       if (len != strlen(peer_cn))
        {
                printfPQExpBuffer(&conn->errorMessage,
!                                                 libpq_gettext("SSL 
certificate's common name contains embedded null\n"));
!               free(peer_cn);
                return false;
        }
  
        /*
!        * We got the peer's common name. Now compare it against the originally
!        * given hostname.
         */
!       if (!(conn->pghost && conn->pghost[0] != '\0'))
        {
!               printfPQExpBuffer(&conn->errorMessage,
!                                                 libpq_gettext("host name must 
be specified for a verified SSL connection\n"));
!               result = false;
        }
!       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;
                else
                {
                        printfPQExpBuffer(&conn->errorMessage,
!                                                         libpq_gettext("server 
common name \"%s\" does not match host name \"%s\"\n"),
!                                                         peer_cn, 
conn->pghost);
!                       result = false;
                }
        }
  
!       free(peer_cn);
!       return result;
  }
  
  #ifdef ENABLE_THREAD_SAFETY
--- 477,704 ----
  
  
  /*
!  * 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 int
! verify_peer_name_matches_certificate_name(PGconn *conn, ASN1_STRING 
*name_entry,
!                                                                               
  char **store_name)
  {
        int                     len;
!       char       *name;
!       unsigned char *namedata;
!       int                     result;
  
!       *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;
!       }
  
        /*
!        * GEN_DNS can be only IA5String, equivalent to US ASCII.
         *
!        * There is no guarantee the string returned from the certificate is
!        * NULL-terminated, so make a copy that is.
         */
!       namedata = ASN1_STRING_data(name_entry);
!       len = ASN1_STRING_length(name_entry);
!       name = malloc(len + 1);
!       if (name == NULL)
        {
                printfPQExpBuffer(&conn->errorMessage,
!                                                 libpq_gettext("out of 
memory\n"));
!               return -1;
        }
!       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("SSL certificate's name contains embedded 
null\n"));
!               return -1;
        }
  
!       if (pg_strcasecmp(name, conn->pghost) == 0)
        {
!               /* Exact name match */
!               result = 1;
!       }
!       else if (wildcard_certificate_match(name, conn->pghost))
!       {
!               /* Matched wildcard certificate */
!               result = 1;
!       }
!       else
!       {
!               result = 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;
  
        /*
!        * 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;
! 
!       /* Check that we have a hostname to compare with. */
!       if (!(conn->pghost && conn->pghost[0] != '\0'))
        {
                printfPQExpBuffer(&conn->errorMessage,
!                                                 libpq_gettext("host name must 
be specified for a verified SSL connection\n"));
                return false;
        }
  
        /*
!        * First, get the Subject Alternative Names (SANs) from the certificate,
!        * and compare them against the originally given hostname.
         */
!       peer_san = (STACK_OF(GENERAL_NAME) *)
!               X509_get_ext_d2i(conn->peer, NID_subject_alt_name, NULL, NULL);
! 
!       if (peer_san)
        {
!               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 no match to any of the SANs, check the Common Name.
!        */
!       if (!found_match && !got_error)
        {
!               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)
!                       {
!                               /*
!                                * We prefer the Common Name over SANs in the 
error message,
!                                * if both are present.
!                                */
!                               if (first_name)
!                                       free(first_name);
! 
!                               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 host 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("could 
not get server's hostname from server certificate\n"));
                }
        }
  
!       /* clean up */
!       if (first_name)
!               free(first_name);
! 
!       return found_match && !got_error;
  }
  
  #ifdef ENABLE_THREAD_SAFETY
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
new file mode 100644
index 6032904..9808ab4
*** a/src/interfaces/libpq/libpq-int.h
--- b/src/interfaces/libpq/libpq-int.h
*************** extern ssize_t pgtls_write(PGconn *conn,
*** 653,660 ****
--- 653,664 ----
  extern char *
  libpq_gettext(const char *msgid)
  __attribute__((format_arg(1)));
+ extern char *
+ libpq_ngettext(const char *msgid, const char *msgid_plural, unsigned long n)
+ __attribute__((format_arg(1))) __attribute__((format_arg(2)));
  #else
  #define libpq_gettext(x) (x)
+ #define libpq_ngettext(s,p,n) ((n) == 1 ? (s) : (p))
  #endif
  
  /*
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to