Added hostname check described in RFC 2818 with OpenSSL.

pull/16/merge
Tatsuhiro Tsujikawa 2012-03-28 23:28:22 +09:00
parent cd00b012d9
commit 25ef6677e9
4 changed files with 148 additions and 22 deletions

View File

@ -42,6 +42,11 @@
#include <cerrno> #include <cerrno>
#include <cstring> #include <cstring>
#ifdef HAVE_OPENSSL
# include <openssl/x509.h>
# include <openssl/x509v3.h>
#endif // HAVE_OPENSSL
#ifdef HAVE_LIBGNUTLS #ifdef HAVE_LIBGNUTLS
# include <gnutls/x509.h> # include <gnutls/x509.h>
#endif // HAVE_LIBGNUTLS #endif // HAVE_LIBGNUTLS
@ -882,32 +887,71 @@ bool SocketCore::initiateSecureConnection(const std::string& hostname)
(fmt(MSG_CERT_VERIFICATION_FAILED, (fmt(MSG_CERT_VERIFICATION_FAILED,
X509_verify_cert_error_string(verifyResult))); X509_verify_cert_error_string(verifyResult)));
} }
X509_NAME* name = X509_get_subject_name(peerCert); int hostnameOK = -1;
if(!name) { GENERAL_NAMES* altNames;
throw DL_ABORT_EX("Could not get X509 name object from the certificate."); 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;
}
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;
}
}
}
}
GENERAL_NAMES_free(altNames);
} }
if(hostnameOK == -1) {
bool hostnameOK = false; X509_NAME* name = X509_get_subject_name(peerCert);
int lastpos = -1; if(!name) {
while(true) { throw DL_ABORT_EX
lastpos = X509_NAME_get_index_by_NID(name, NID_commonName, lastpos); ("Could not get X509 name object from the certificate.");
if(lastpos == -1) {
break;
} }
X509_NAME_ENTRY* entry = X509_NAME_get_entry(name, lastpos); int lastpos = -1;
unsigned char* out; while(true) {
int outlen = ASN1_STRING_to_UTF8(&out, X509_NAME_ENTRY_get_data(entry)); lastpos = X509_NAME_get_index_by_NID(name, NID_commonName, lastpos);
if(outlen < 0) { if(lastpos == -1) {
continue; break;
} }
std::string commonName(&out[0], &out[outlen]); X509_NAME_ENTRY* entry = X509_NAME_get_entry(name, lastpos);
OPENSSL_free(out); unsigned char* out;
if(commonName == hostname) { int outlen = ASN1_STRING_to_UTF8(&out,
hostnameOK = true; X509_NAME_ENTRY_get_data(entry));
break; 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); throw DL_ABORT_EX(MSG_HOSTNAME_NOT_MATCH);
} }
} }

View File

@ -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) bool startsWith(const std::string& a, const char* b)
{ {
return startsWith(a.begin(), a.end(), b); return startsWith(a.begin(), a.end(), b);

View File

@ -852,6 +852,24 @@ SharedHandle<T> copy(const SharedHandle<T>& a)
// * noProxyDomainMatch("sf.net", ".sf.net") returns false. // * noProxyDomainMatch("sf.net", ".sf.net") returns false.
bool noProxyDomainMatch(const std::string& hostname, const std::string& domain); 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 util
} // namespace aria2 } // namespace aria2

View File

@ -86,6 +86,7 @@ class UtilTest:public CppUnit::TestFixture {
CPPUNIT_TEST(testNoProxyDomainMatch); CPPUNIT_TEST(testNoProxyDomainMatch);
CPPUNIT_TEST(testInPrivateAddress); CPPUNIT_TEST(testInPrivateAddress);
CPPUNIT_TEST(testSecfmt); CPPUNIT_TEST(testSecfmt);
CPPUNIT_TEST(testTlsHostnameMatch);
CPPUNIT_TEST_SUITE_END(); CPPUNIT_TEST_SUITE_END();
private: private:
@ -157,6 +158,7 @@ public:
void testNoProxyDomainMatch(); void testNoProxyDomainMatch();
void testInPrivateAddress(); void testInPrivateAddress();
void testSecfmt(); void testSecfmt();
void testTlsHostnameMatch();
}; };
@ -1843,4 +1845,29 @@ void UtilTest::testSecfmt()
CPPUNIT_ASSERT_EQUAL(std::string("1h"), util::secfmt(3600)); 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 } // namespace aria2