Rewritten TLS hostname check based on RFC 6125.

pull/16/merge
Tatsuhiro Tsujikawa 2012-03-30 23:49:14 +09:00
parent e4e9562c92
commit 0a9abd89c6
5 changed files with 141 additions and 113 deletions

View File

@ -887,71 +887,61 @@ 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)));
} }
int hostnameOK = -1;
GENERAL_NAMES* altNames; GENERAL_NAMES* altNames;
std::string commonName;
std::vector<std::string> dnsNames;
std::vector<std::string> ipAddrs;
altNames = reinterpret_cast<GENERAL_NAMES*> altNames = reinterpret_cast<GENERAL_NAMES*>
(X509_get_ext_d2i(peerCert, NID_subject_alt_name, NULL, NULL)); (X509_get_ext_d2i(peerCert, NID_subject_alt_name, NULL, NULL));
if(altNames) { if(altNames) {
int addrType; auto_delete<GENERAL_NAMES*> altNamesDeleter
if(util::isNumericHost(hostname)) { (altNames, GENERAL_NAMES_free);
addrType = GEN_IPADD;
} else {
addrType = GEN_DNS;
}
size_t n = sk_GENERAL_NAME_num(altNames); size_t n = sk_GENERAL_NAME_num(altNames);
for(size_t i = 0; i < n; ++i) { for(size_t i = 0; i < n; ++i) {
const GENERAL_NAME* altName = sk_GENERAL_NAME_value(altNames, i); const GENERAL_NAME* altName = sk_GENERAL_NAME_value(altNames, i);
if(altName->type == addrType) { if(altName->type == GEN_DNS) {
const char* name = const char* name =
reinterpret_cast<char*>(ASN1_STRING_data(altName->d.ia5)); reinterpret_cast<char*>(ASN1_STRING_data(altName->d.ia5));
size_t len = ASN1_STRING_length(altName->d.ia5); if(!name) {
if(addrType == GEN_DNS) { continue;
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;
}
} }
} size_t len = ASN1_STRING_length(altName->d.ia5);
} dnsNames.push_back(std::string(name, len));
GENERAL_NAMES_free(altNames); } else if(altName->type == GEN_IPADD) {
} const unsigned char* ipAddr = altName->d.iPAddress->data;
if(hostnameOK == -1) { if(!ipAddr) {
X509_NAME* name = X509_get_subject_name(peerCert); continue;
if(!name) { }
throw DL_ABORT_EX size_t len = altName->d.iPAddress->length;
("Could not get X509 name object from the certificate."); ipAddrs.push_back(std::string(reinterpret_cast<const char*>(ipAddr),
} len));
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 != 1) { X509_NAME* subjectName = X509_get_subject_name(peerCert);
if(!subjectName) {
throw DL_ABORT_EX
("Could not get X509 name object from the certificate.");
}
int lastpos = -1;
while(1) {
lastpos = X509_NAME_get_index_by_NID(subjectName, NID_commonName,
lastpos);
if(lastpos == -1) {
break;
}
X509_NAME_ENTRY* entry = X509_NAME_get_entry(subjectName, lastpos);
unsigned char* out;
int outlen = ASN1_STRING_to_UTF8(&out,
X509_NAME_ENTRY_get_data(entry));
if(outlen < 0) {
continue;
}
commonName.assign(&out[0], &out[outlen]);
OPENSSL_free(out);
break;
}
if(!net::verifyHostname(hostname, dnsNames, ipAddrs, commonName)) {
throw DL_ABORT_EX(MSG_HOSTNAME_NOT_MATCH); throw DL_ABORT_EX(MSG_HOSTNAME_NOT_MATCH);
} }
} }
@ -1311,6 +1301,43 @@ size_t getBinAddr(unsigned char* dest, const std::string& ip)
return len; return len;
} }
bool verifyHostname(const std::string& hostname,
const std::vector<std::string>& dnsNames,
const std::vector<std::string>& ipAddrs,
const std::string& commonName)
{
if(util::isNumericHost(hostname)) {
// We need max 16 bytes to store IPv6 address.
unsigned char binAddr[16];
size_t addrLen = getBinAddr(binAddr, hostname);
if(addrLen == 0) {
return false;
}
if(ipAddrs.empty()) {
return addrLen == commonName.size() &&
memcmp(binAddr, commonName.c_str(), addrLen) == 0;
}
for(std::vector<std::string>::const_iterator i = ipAddrs.begin(),
eoi = ipAddrs.end(); i != eoi; ++i) {
if(addrLen == (*i).size() &&
memcmp(binAddr, (*i).c_str(), addrLen) == 0) {
return true;
}
}
} else {
if(dnsNames.empty()) {
return util::tlsHostnameMatch(commonName, hostname);
}
for(std::vector<std::string>::const_iterator i = dnsNames.begin(),
eoi = dnsNames.end(); i != eoi; ++i) {
if(util::tlsHostnameMatch(*i, hostname)) {
return true;
}
}
}
return false;
}
} // namespace net } // namespace net
} // namespace aria2 } // namespace aria2

View File

@ -391,6 +391,13 @@ namespace net {
// IPv6. Return 0 if error occurred. // IPv6. Return 0 if error occurred.
size_t getBinAddr(unsigned char* dest, const std::string& ip); size_t getBinAddr(unsigned char* dest, const std::string& ip);
// Verifies hostname against presented identifiers in the certificate.
// The implementation is based on the procedure described in RFC 6125.
bool verifyHostname(const std::string& hostname,
const std::vector<std::string>& dnsNames,
const std::vector<std::string>& ipAddrs,
const std::string& commonName);
} // namespace net } // namespace net
} // namespace aria2 } // namespace aria2

View File

@ -1620,39 +1620,49 @@ bool noProxyDomainMatch
bool tlsHostnameMatch(const std::string& pattern, const std::string& hostname) bool tlsHostnameMatch(const std::string& pattern, const std::string& hostname)
{ {
int wildcardpos; // Do case-insensitive match. At least 2 dots are required to enable
{ // wildcard match.
std::string::size_type pos = pattern.find('*'); std::string::const_iterator ptLeftLabelEnd = std::find(pattern.begin(),
if(pos == std::string::npos) { pattern.end(),
return pattern == hostname; '.');
} else if(pos > hostname.size()) { bool wildcardEnabled = true;
return false; if(ptLeftLabelEnd == pattern.end() ||
} else { std::find(ptLeftLabelEnd+1, pattern.end(), '.') == pattern.end()) {
wildcardpos = pos; wildcardEnabled = false;
}
} }
int i, j; if(!wildcardEnabled) {
for(i = 0; i < wildcardpos; ++i) { return strieq(pattern.begin(), pattern.end(),
if(pattern[i] != hostname[i]) { hostname.begin(), hostname.end());
return false;
}
} }
for(i = static_cast<int>(pattern.size())-1, std::string::const_iterator ptWildcard = std::find(pattern.begin(),
j = static_cast<int>(hostname.size())-1; ptLeftLabelEnd,
i > wildcardpos && j >= wildcardpos; --i, --j) { '*');
if(pattern[i] != hostname[j]) { if(ptWildcard == ptLeftLabelEnd) {
return false; return strieq(pattern.begin(), pattern.end(),
} hostname.begin(), hostname.end());
} }
if(i != wildcardpos) { std::string::const_iterator hnLeftLabelEnd = std::find(hostname.begin(),
hostname.end(),
'.');
if(!strieq(ptLeftLabelEnd, pattern.end(), hnLeftLabelEnd, hostname.end())) {
return false; return false;
} }
for(i = wildcardpos; i <= j; ++i) { // Don't attempt to match a presented identifier where the wildcard
if(hostname[i] == '.') { // character is embedded within an A-label.
return false; if(istartsWith(pattern, "xn--")) {
} return strieq(pattern.begin(), ptLeftLabelEnd,
hostname.begin(), hnLeftLabelEnd);
} }
return true; // Perform wildcard match. Here '*' must match at least one
// character.
if(hnLeftLabelEnd - hostname.begin() < ptLeftLabelEnd - pattern.begin()) {
return false;
}
return
istartsWith(hostname.begin(), hnLeftLabelEnd,
pattern.begin(), ptWildcard) &&
iendsWith(hostname.begin(), hnLeftLabelEnd,
ptWildcard+1, ptLeftLabelEnd);
} }
bool startsWith(const std::string& a, const char* b) bool startsWith(const std::string& a, const char* b)

View File

@ -852,22 +852,7 @@ 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. // Checks hostname matches pattern as described in RFC 6125.
//
// 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); bool tlsHostnameMatch(const std::string& pattern, const std::string& hostname);
} // namespace util } // namespace util

View File

@ -1847,27 +1847,26 @@ void UtilTest::testSecfmt()
void UtilTest::testTlsHostnameMatch() void UtilTest::testTlsHostnameMatch()
{ {
CPPUNIT_ASSERT(util::tlsHostnameMatch("foo.com", "foo.com")); CPPUNIT_ASSERT(util::tlsHostnameMatch("Foo.com", "foo.com"));
CPPUNIT_ASSERT(util::tlsHostnameMatch("*.a.com", "foo.a.com")); CPPUNIT_ASSERT(util::tlsHostnameMatch("*.a.com", "foo.a.com"));
CPPUNIT_ASSERT(!util::tlsHostnameMatch("*.a.com", "bar.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", "foo.com"));
CPPUNIT_ASSERT(!util::tlsHostnameMatch("f*.com", "bar.com")); CPPUNIT_ASSERT(!util::tlsHostnameMatch("*.com", "bar.com"));
CPPUNIT_ASSERT(util::tlsHostnameMatch("foo.*", "foo.com")); CPPUNIT_ASSERT(util::tlsHostnameMatch("com", "com"));
CPPUNIT_ASSERT(!util::tlsHostnameMatch("foo.*", "bar.com")); CPPUNIT_ASSERT(!util::tlsHostnameMatch("foo.*", "foo.com"));
CPPUNIT_ASSERT(util::tlsHostnameMatch("foo.*m", "foo.com")); CPPUNIT_ASSERT(util::tlsHostnameMatch("a.foo.com", "A.foo.com"));
CPPUNIT_ASSERT(util::tlsHostnameMatch("foo.c*", "foo.com")); CPPUNIT_ASSERT(!util::tlsHostnameMatch("a.foo.com", "b.foo.com"));
CPPUNIT_ASSERT(util::tlsHostnameMatch("foo.com*", "foo.com")); CPPUNIT_ASSERT(!util::tlsHostnameMatch("*a.foo.com", "a.foo.com"));
CPPUNIT_ASSERT(util::tlsHostnameMatch("*foo.com", "foo.com")); CPPUNIT_ASSERT(util::tlsHostnameMatch("*a.foo.com", "ba.foo.com"));
CPPUNIT_ASSERT(util::tlsHostnameMatch("foo.b*z.com", "foo.baz.com")); CPPUNIT_ASSERT(!util::tlsHostnameMatch("a*.foo.com", "a.foo.com"));
CPPUNIT_ASSERT(!util::tlsHostnameMatch("foo.b*z.com", "foo.bar.baz.com")); CPPUNIT_ASSERT(util::tlsHostnameMatch("a*.foo.com", "ab.foo.com"));
CPPUNIT_ASSERT(util::tlsHostnameMatch("*", "foo")); CPPUNIT_ASSERT(!util::tlsHostnameMatch("foo.b*z.foo.com", "foo.baz.foo.com"));
CPPUNIT_ASSERT(!util::tlsHostnameMatch("*", "foo.com")); CPPUNIT_ASSERT(util::tlsHostnameMatch("B*z.foo.com", "bAZ.Foo.com"));
CPPUNIT_ASSERT(!util::tlsHostnameMatch("*.co*", "foo.com")); CPPUNIT_ASSERT(!util::tlsHostnameMatch("b*z.foo.com", "bz.foo.com"));
CPPUNIT_ASSERT(!util::tlsHostnameMatch("fooo*.com", "foo.com")); CPPUNIT_ASSERT(!util::tlsHostnameMatch("*", "foo"));
CPPUNIT_ASSERT(!util::tlsHostnameMatch("foo*foo.com", "foo.com")); CPPUNIT_ASSERT(!util::tlsHostnameMatch("*", ""));
CPPUNIT_ASSERT(!util::tlsHostnameMatch("", "foo.com"));
CPPUNIT_ASSERT(util::tlsHostnameMatch("*", ""));
CPPUNIT_ASSERT(util::tlsHostnameMatch("", "")); CPPUNIT_ASSERT(util::tlsHostnameMatch("", ""));
CPPUNIT_ASSERT(!util::tlsHostnameMatch("xn--*.a.b", "xn--c.a.b"));
} }
} // namespace aria2 } // namespace aria2