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