Jelajahi Sumber

Added hostname check described in RFC 2818 with OpenSSL.

Tatsuhiro Tsujikawa 13 tahun lalu
induk
melakukan
25ef6677e9
4 mengubah file dengan 148 tambahan dan 22 penghapusan
  1. 66 22
      src/SocketCore.cc
  2. 37 0
      src/util.cc
  3. 18 0
      src/util.h
  4. 27 0
      test/UtilTest.cc

+ 66 - 22
src/SocketCore.cc

@@ -42,6 +42,11 @@
 #include <cerrno>
 #include <cstring>
 
+#ifdef HAVE_OPENSSL
+# include <openssl/x509.h>
+# include <openssl/x509v3.h>
+#endif // HAVE_OPENSSL
+
 #ifdef HAVE_LIBGNUTLS
 # include <gnutls/x509.h>
 #endif // HAVE_LIBGNUTLS
@@ -882,32 +887,71 @@ bool SocketCore::initiateSecureConnection(const std::string& hostname)
           (fmt(MSG_CERT_VERIFICATION_FAILED,
                X509_verify_cert_error_string(verifyResult)));
       }
-      X509_NAME* name = X509_get_subject_name(peerCert);
-      if(!name) {
-        throw DL_ABORT_EX("Could not get X509 name object from the certificate.");
-      }
-
-      bool hostnameOK = false;
-      int lastpos = -1;
-      while(true) {
-        lastpos = X509_NAME_get_index_by_NID(name, NID_commonName, lastpos);
-        if(lastpos == -1) {
-          break;
+      int hostnameOK = -1;
+      GENERAL_NAMES* altNames;
+      altNames = reinterpret_cast<GENERAL_NAMES*>
+        (X509_get_ext_d2i(peerCert, NID_subject_alt_name, NULL, NULL));
+      if(altNames) {
+        int addrType;
+        if(util::isNumericHost(hostname)) {
+          addrType = GEN_IPADD;
+        } else {
+          addrType = GEN_DNS;
         }
-        X509_NAME_ENTRY* entry = X509_NAME_get_entry(name, lastpos);
-        unsigned char* out;
-        int outlen = ASN1_STRING_to_UTF8(&out, X509_NAME_ENTRY_get_data(entry));
-        if(outlen < 0) {
-          continue;
+        size_t n = sk_GENERAL_NAME_num(altNames);
+        for(size_t i = 0; i < n; ++i) {
+          const GENERAL_NAME* altName = sk_GENERAL_NAME_value(altNames, i);
+          if(altName->type == addrType) {
+            const char* name =
+              reinterpret_cast<char*>(ASN1_STRING_data(altName->d.ia5));
+            size_t len = ASN1_STRING_length(altName->d.ia5);
+            if(addrType == GEN_DNS) {
+              if(util::tlsHostnameMatch(std::string(name, len), hostname)) {
+                hostnameOK = 1;
+                break;
+              } else {
+                hostnameOK = 0;
+              }
+            } else if(addrType == GEN_IPADD) {
+              if(hostname == std::string(name, len)) {
+                hostnameOK = 1;
+                break;
+              } else {
+                hostnameOK = 0;
+              }
+            }
+          }
         }
-        std::string commonName(&out[0], &out[outlen]);
-        OPENSSL_free(out);
-        if(commonName == hostname) {
-          hostnameOK = true;
-          break;
+        GENERAL_NAMES_free(altNames);
+      }
+      if(hostnameOK == -1) {
+        X509_NAME* name = X509_get_subject_name(peerCert);
+        if(!name) {
+          throw DL_ABORT_EX
+            ("Could not get X509 name object from the certificate.");
+        }
+        int lastpos = -1;
+        while(true) {
+          lastpos = X509_NAME_get_index_by_NID(name, NID_commonName, lastpos);
+          if(lastpos == -1) {
+            break;
+          }
+          X509_NAME_ENTRY* entry = X509_NAME_get_entry(name, lastpos);
+          unsigned char* out;
+          int outlen = ASN1_STRING_to_UTF8(&out,
+                                           X509_NAME_ENTRY_get_data(entry));
+          if(outlen < 0) {
+            continue;
+          }
+          std::string commonName(&out[0], &out[outlen]);
+          OPENSSL_free(out);
+          if(commonName == hostname) {
+            hostnameOK = 1;
+            break;
+          }
         }
       }
-      if(!hostnameOK) {
+      if(hostnameOK != 1) {
         throw DL_ABORT_EX(MSG_HOSTNAME_NOT_MATCH);
       }
     }

+ 37 - 0
src/util.cc

@@ -1611,6 +1611,43 @@ bool noProxyDomainMatch
   }
 }
 
+bool tlsHostnameMatch(const std::string& pattern, const std::string& hostname)
+{
+  int wildcardpos;
+  {
+    std::string::size_type pos = pattern.find('*');
+    if(pos == std::string::npos) {
+      return pattern == hostname;
+    } else if(pos > hostname.size()) {
+      return false;
+    } else {
+      wildcardpos = pos;
+    }
+  }
+  int i, j;
+  for(i = 0; i < wildcardpos; ++i) {
+    if(pattern[i] != hostname[i]) {
+      return false;
+    }
+  }
+  for(i = static_cast<int>(pattern.size())-1,
+        j = static_cast<int>(hostname.size())-1;
+      i > wildcardpos && j >= wildcardpos; --i, --j) {
+    if(pattern[i] != hostname[j]) {
+      return false;
+    }
+  }
+  if(i != wildcardpos) {
+    return false;
+  }
+  for(i = wildcardpos; i <= j; ++i) {
+    if(hostname[i] == '.') {
+      return false;
+    }
+  }
+  return true;
+}
+
 bool startsWith(const std::string& a, const char* b)
 {
   return startsWith(a.begin(), a.end(), b);

+ 18 - 0
src/util.h

@@ -852,6 +852,24 @@ SharedHandle<T> copy(const SharedHandle<T>& a)
 // * noProxyDomainMatch("sf.net", ".sf.net") returns false.
 bool noProxyDomainMatch(const std::string& hostname, const std::string& domain);
 
+// Checks hostname matches pattern as described in RFC 2818.
+//
+// Quoted from RFC 2818 section 3.1. Server Identity:
+//
+// Matching is performed using the matching rules specified by
+// [RFC2459].  If more than one identity of a given type is present in
+// the certificate (e.g., more than one dNSName name, a match in any
+// one of the set is considered acceptable.) Names may contain the
+// wildcard character * which is considered to match any single domain
+// name component or component fragment. E.g., *.a.com matches
+// foo.a.com but not bar.foo.a.com. f*.com matches foo.com but not
+// bar.com.
+//
+// If pattern contains multiple '*', this function considers left most
+// '*' as a wildcard character and other '*'s are considered just
+// character literals.
+bool tlsHostnameMatch(const std::string& pattern, const std::string& hostname);
+
 } // namespace util
 
 } // namespace aria2

+ 27 - 0
test/UtilTest.cc

@@ -86,6 +86,7 @@ class UtilTest:public CppUnit::TestFixture {
   CPPUNIT_TEST(testNoProxyDomainMatch);
   CPPUNIT_TEST(testInPrivateAddress);
   CPPUNIT_TEST(testSecfmt);
+  CPPUNIT_TEST(testTlsHostnameMatch);
   CPPUNIT_TEST_SUITE_END();
 private:
 
@@ -157,6 +158,7 @@ public:
   void testNoProxyDomainMatch();
   void testInPrivateAddress();
   void testSecfmt();
+  void testTlsHostnameMatch();
 };
 
 
@@ -1843,4 +1845,29 @@ void UtilTest::testSecfmt()
   CPPUNIT_ASSERT_EQUAL(std::string("1h"), util::secfmt(3600));
 }
 
+void UtilTest::testTlsHostnameMatch()
+{
+  CPPUNIT_ASSERT(util::tlsHostnameMatch("foo.com", "foo.com"));
+  CPPUNIT_ASSERT(util::tlsHostnameMatch("*.a.com", "foo.a.com"));
+  CPPUNIT_ASSERT(!util::tlsHostnameMatch("*.a.com", "bar.foo.a.com"));
+  CPPUNIT_ASSERT(util::tlsHostnameMatch("f*.com", "foo.com"));
+  CPPUNIT_ASSERT(!util::tlsHostnameMatch("f*.com", "bar.com"));
+  CPPUNIT_ASSERT(util::tlsHostnameMatch("foo.*", "foo.com"));
+  CPPUNIT_ASSERT(!util::tlsHostnameMatch("foo.*", "bar.com"));
+  CPPUNIT_ASSERT(util::tlsHostnameMatch("foo.*m", "foo.com"));
+  CPPUNIT_ASSERT(util::tlsHostnameMatch("foo.c*", "foo.com"));
+  CPPUNIT_ASSERT(util::tlsHostnameMatch("foo.com*", "foo.com"));
+  CPPUNIT_ASSERT(util::tlsHostnameMatch("*foo.com", "foo.com"));
+  CPPUNIT_ASSERT(util::tlsHostnameMatch("foo.b*z.com", "foo.baz.com"));
+  CPPUNIT_ASSERT(!util::tlsHostnameMatch("foo.b*z.com", "foo.bar.baz.com"));
+  CPPUNIT_ASSERT(util::tlsHostnameMatch("*", "foo"));
+  CPPUNIT_ASSERT(!util::tlsHostnameMatch("*", "foo.com"));
+  CPPUNIT_ASSERT(!util::tlsHostnameMatch("*.co*", "foo.com"));
+  CPPUNIT_ASSERT(!util::tlsHostnameMatch("fooo*.com", "foo.com"));
+  CPPUNIT_ASSERT(!util::tlsHostnameMatch("foo*foo.com", "foo.com"));
+  CPPUNIT_ASSERT(!util::tlsHostnameMatch("", "foo.com"));
+  CPPUNIT_ASSERT(util::tlsHostnameMatch("*", ""));
+  CPPUNIT_ASSERT(util::tlsHostnameMatch("", ""));
+}
+
 } // namespace aria2