Find reasons for the SSL common name invalid error.

BUG=392310

Review URL: https://codereview.chromium.org/376333003

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@287942 0039d316-1c4b-4281-b951-d872f2087c98
parent 2f15d8ba
......@@ -311,11 +311,13 @@ SSLBlockingPage::SSLBlockingPage(
&request_tracker_);
}
}
if (SSLErrorInfo::NetErrorToErrorType(cert_error_) ==
SSLErrorInfo::CERT_DATE_INVALID) {
SSLErrorClassification::RecordUMAStatistics(overridable_ &&
!strict_enforcement_);
}
SSLErrorClassification ssl_error_classification(
base::Time::NowFromSystemTime(),
request_url_,
*ssl_info_.cert.get());
ssl_error_classification.RecordUMAStatistics(
overridable_ && !strict_enforcement_, cert_error_);
#if defined(ENABLE_CAPTIVE_PORTAL_DETECTION)
CaptivePortalService* captive_portal_service =
......
......@@ -5,41 +5,109 @@
#ifndef CHROME_BROWSER_SSL_SSL_ERROR_CLASSIFICATION_H_
#define CHROME_BROWSER_SSL_SSL_ERROR_CLASSIFICATION_H_
#include <string>
#include <vector>
#include "base/time/time.h"
#include "net/cert/x509_certificate.h"
#include "url/gurl.h"
// This class calculates the severity scores for the different type of SSL
// errors.
class SSLErrorClassification {
public:
SSLErrorClassification(base::Time current_time,
SSLErrorClassification(const base::Time& current_time,
const GURL& url,
const net::X509Certificate& cert);
~SSLErrorClassification();
// This method checks whether the user clock is in the past or not.
static bool IsUserClockInThePast(base::Time time_now);
// Returns true if the system time is in the past.
static bool IsUserClockInThePast(const base::Time& time_now);
// This method checks whether the system time is too far in the future or
// the user is using a version of Chrome which is more than 1 year old.
static bool IsUserClockInTheFuture(base::Time time_now);
// Returns true if the system time is too far in the future or the user is
// using a version of Chrome which is more than 1 year old.
static bool IsUserClockInTheFuture(const base::Time& time_now);
static bool IsWindowsVersionSP3OrLower();
// A method which calculates the severity score when the ssl error is
// CERT_DATE_INVALID.
float InvalidDateSeverityScore() const;
// A function which calculates the severity score when the ssl error is
// CERT_DATE_INVALID, returns a score between 0.0 and 1.0, higher values
// being more severe, indicating how severe the certificate's invalid
// date error is.
float InvalidDateSeverityScore(int cert_error) const;
// A function which calculates the severity score when the ssl error is
// when the SSL error is |CERT_COMMON_NAME_INVALID|, returns a score between
// between 0.0 and 1.0, higher values being more severe, indicating how
// severe the certificate's common name invalid error is.
float InvalidCommonNameSeverityScore(int cert_error) const;
static void RecordUMAStatistics(bool overridable);
void RecordUMAStatistics(bool overridable, int cert_error);
base::TimeDelta TimePassedSinceExpiry() const;
private:
FRIEND_TEST_ALL_PREFIXES(SSLErrorClassification, TestDateInvalidScore);
FRIEND_TEST_ALL_PREFIXES(SSLErrorClassificationTest, TestDateInvalidScore);
FRIEND_TEST_ALL_PREFIXES(SSLErrorClassificationTest, TestNameMismatch);
FRIEND_TEST_ALL_PREFIXES(SSLErrorClassificationTest,
TestHostNameHasKnownTLD);
typedef std::vector<std::string> Tokens;
// Returns true if the hostname has a known Top Level Domain.
static bool IsHostNameKnownTLD(const std::string& host_name);
// Returns true if the site's hostname differs from one of the DNS
// names in the certificate (CN or SANs) only by the presence or
// absence of the single-label prefix "www". E.g.:
//
// www.example.com ~ example.com -> true
// example.com ~ www.example.com -> true
// www.food.example.com ~ example.com -> false
// mail.example.com ~ example.com -> false
bool IsWWWSubDomainMatch() const;
// Returns true if |child| is a subdomain of any of the |potential_parents|.
bool NameUnderAnyNames(const Tokens& child,
const std::vector<Tokens>& potential_parents) const;
// Returns true if any of the |potential_children| is a subdomain of the
// |parent|. The inverse case should be treated carefully as this is most
// likely a MITM attack. We don't want foo.appspot.com to be able to MITM for
// appspot.com.
bool AnyNamesUnderName(const std::vector<Tokens>& potential_children,
const Tokens& parent) const;
// Returns true if |hostname| is too broad for the scope of a wildcard
// certificate. E.g.:
//
// a.b.example.com ~ *.example.com --> true
// b.example.com ~ *.example.com --> false
bool IsSubDomainOutsideWildcard(const Tokens& hostname) const;
float CalculateScoreTimePassedSinceExpiry() const;
static std::vector<Tokens> GetTokenizedDNSNames(
const std::vector<std::string>& dns_names);
// If |potential_subdomain| is a subdomain of |parent|, returns the
// number of DNS labels by which |potential_subdomain| is under
// |parent|. Otherwise, returns 0.
//
// For example,
//
// FindSubDomainDifference(Tokenize("a.b.example.com"),
// Tokenize("example.com"))
// --> 2.
size_t FindSubDomainDifference(const Tokens& potential_subdomain,
const Tokens& parent) const;
static Tokens Tokenize(const std::string& name);
// This stores the current time.
base::Time current_time_;
const GURL& request_url_;
// This stores the certificate.
const net::X509Certificate& cert_;
};
......
......@@ -5,37 +5,151 @@
#include "chrome/browser/ssl/ssl_error_classification.h"
#include "base/files/file_path.h"
#include "base/strings/string_split.h"
#include "base/time/time.h"
#include "net/base/test_data_directory.h"
#include "net/cert/x509_cert_types.h"
#include "net/cert/x509_certificate.h"
#include "net/test/cert_test_util.h"
#include "net/test/test_certificate_data.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
using base::Time;
TEST(SSLErrorClassification, TestDateInvalidScore) {
TEST(SSLErrorClassificationTest, TestDateInvalidScore) {
base::FilePath certs_dir = net::GetTestCertsDirectory();
scoped_refptr<net::X509Certificate> expired_cert =
net::ImportCertFromFile(certs_dir, "expired_cert.pem");
base::Time time;
GURL origin("https://example.com");
{
EXPECT_TRUE(base::Time::FromString("Wed, 03 Jan 2007 12:00:00 GMT", &time));
SSLErrorClassification ssl_error(time, *expired_cert);
SSLErrorClassification ssl_error(time, origin, *expired_cert);
EXPECT_FLOAT_EQ(0.2f, ssl_error.CalculateScoreTimePassedSinceExpiry());
}
{
EXPECT_TRUE(base::Time::FromString("Sat, 06 Jan 2007 12:00:00 GMT", &time));
SSLErrorClassification ssl_error(time, *expired_cert);
SSLErrorClassification ssl_error(time, origin, *expired_cert);
EXPECT_FLOAT_EQ(0.3f, ssl_error.CalculateScoreTimePassedSinceExpiry());
}
{
EXPECT_TRUE(base::Time::FromString("Mon, 08 Jan 2007 12:00:00 GMT", &time));
SSLErrorClassification ssl_error(time, *expired_cert);
SSLErrorClassification ssl_error(time, origin, *expired_cert);
EXPECT_FLOAT_EQ(0.4f, ssl_error.CalculateScoreTimePassedSinceExpiry());
}
}
TEST(SSLErrorClassificationTest, TestNameMismatch) {
scoped_refptr<net::X509Certificate> google_cert(
net::X509Certificate::CreateFromBytes(
reinterpret_cast<const char*>(google_der), sizeof(google_der)));
ASSERT_NE(static_cast<net::X509Certificate*>(NULL), google_cert);
base::Time time = base::Time::NowFromSystemTime();
std::vector<std::string> dns_names_google;
dns_names_google.push_back("www");
dns_names_google.push_back("google");
dns_names_google.push_back("com");
std::vector<std::vector<std::string>> dns_name_tokens_google;
dns_name_tokens_google.push_back(dns_names_google);
{
GURL origin("https://google.com");
std::string host_name = origin.host();
std::vector<std::string> host_name_tokens;
base::SplitStringDontTrim(host_name, '.', &host_name_tokens);
SSLErrorClassification ssl_error(time, origin, *google_cert);
EXPECT_TRUE(ssl_error.IsWWWSubDomainMatch());
EXPECT_FALSE(ssl_error.NameUnderAnyNames(host_name_tokens,
dns_name_tokens_google));
EXPECT_FALSE(ssl_error.AnyNamesUnderName(dns_name_tokens_google,
host_name_tokens));
EXPECT_FALSE(ssl_error.IsSubDomainOutsideWildcard(host_name_tokens));
}
{
GURL origin("https://foo.blah.google.com");
std::string host_name = origin.host();
std::vector<std::string> host_name_tokens;
base::SplitStringDontTrim(host_name, '.', &host_name_tokens);
SSLErrorClassification ssl_error(time, origin, *google_cert);
EXPECT_FALSE(ssl_error.IsWWWSubDomainMatch());
EXPECT_FALSE(ssl_error.NameUnderAnyNames(host_name_tokens,
dns_name_tokens_google));
EXPECT_FALSE(ssl_error.AnyNamesUnderName(dns_name_tokens_google,
host_name_tokens));
}
{
GURL origin("https://foo.www.google.com");
std::string host_name = origin.host();
std::vector<std::string> host_name_tokens;
base::SplitStringDontTrim(host_name, '.', &host_name_tokens);
SSLErrorClassification ssl_error(time, origin, *google_cert);
EXPECT_FALSE(ssl_error.IsWWWSubDomainMatch());
EXPECT_TRUE(ssl_error.NameUnderAnyNames(host_name_tokens,
dns_name_tokens_google));
EXPECT_FALSE(ssl_error.AnyNamesUnderName(dns_name_tokens_google,
host_name_tokens));
}
{
GURL origin("https://www.google.com.foo");
std::string host_name = origin.host();
std::vector<std::string> host_name_tokens;
base::SplitStringDontTrim(host_name, '.', &host_name_tokens);
SSLErrorClassification ssl_error(time, origin, *google_cert);
EXPECT_FALSE(ssl_error.IsWWWSubDomainMatch());
EXPECT_FALSE(ssl_error.NameUnderAnyNames(host_name_tokens,
dns_name_tokens_google));
EXPECT_FALSE(ssl_error.AnyNamesUnderName(dns_name_tokens_google,
host_name_tokens));
}
{
GURL origin("https://www.foogoogle.com.");
std::string host_name = origin.host();
std::vector<std::string> host_name_tokens;
base::SplitStringDontTrim(host_name, '.', &host_name_tokens);
SSLErrorClassification ssl_error(time, origin, *google_cert);
EXPECT_FALSE(ssl_error.IsWWWSubDomainMatch());
EXPECT_FALSE(ssl_error.NameUnderAnyNames(host_name_tokens,
dns_name_tokens_google));
EXPECT_FALSE(ssl_error.AnyNamesUnderName(dns_name_tokens_google,
host_name_tokens));
}
scoped_refptr<net::X509Certificate> webkit_cert(
net::X509Certificate::CreateFromBytes(
reinterpret_cast<const char*>(webkit_der), sizeof(webkit_der)));
ASSERT_NE(static_cast<net::X509Certificate*>(NULL), webkit_cert);
std::vector<std::string> dns_names_webkit;
dns_names_webkit.push_back("webkit");
dns_names_webkit.push_back("org");
std::vector<std::vector<std::string>> dns_name_tokens_webkit;
dns_name_tokens_webkit.push_back(dns_names_webkit);
{
GURL origin("https://a.b.webkit.org");
std::string host_name = origin.host();
std::vector<std::string> host_name_tokens;
base::SplitStringDontTrim(host_name, '.', &host_name_tokens);
SSLErrorClassification ssl_error(time, origin, *webkit_cert);
EXPECT_FALSE(ssl_error.IsWWWSubDomainMatch());
EXPECT_FALSE(ssl_error.NameUnderAnyNames(host_name_tokens,
dns_name_tokens_webkit));
EXPECT_FALSE(ssl_error.AnyNamesUnderName(dns_name_tokens_webkit,
host_name_tokens));
EXPECT_TRUE(ssl_error.IsSubDomainOutsideWildcard(host_name_tokens));
}
}
TEST(SSLErrorClassificationTest, TestHostNameHasKnownTLD) {
std::string url1 = "www.google.com";
std::string url2 = "b.appspot.com";
std::string url3 = "a.private";
EXPECT_TRUE(SSLErrorClassification::IsHostNameKnownTLD(url1));
EXPECT_TRUE(SSLErrorClassification::IsHostNameKnownTLD(url2));
EXPECT_FALSE(SSLErrorClassification::IsHostNameKnownTLD(url3));
}
......@@ -9946,14 +9946,30 @@ Therefore, the affected-histogram name has to have at least one dot in it.
enum="SSLNonAttackCauses">
<owner>felt@chromium.org</owner>
<summary>
Possible non-attack causes of the non-overridable SSL interstitial.
Possible non-attack causes of the non-overridable SSL interstitial. Many
errors are not reported in this histogram and new errors may be added over
time, therefore one should not look at the breakdown of this histogram (one
bucket divided by the sum) because that will be inaccurate. Instead, one
should look at each bucket count divided by the count of the ssl errors of
that type. E.g. WWW mismatch is recorded only when the ssl error is
CERT_COMMON_NAME_INVALID, so one should look at the bucket count of WWW
mismatch divided by the bucket count of CERT_COMMON_NAME_INVALID in the
histogram interstitial.ssl_error_type.
</summary>
</histogram>
<histogram name="interstitial.ssl.cause.overridable" enum="SSLNonAttackCauses">
<owner>felt@chromium.org</owner>
<summary>
Possible non-attack causes of the overridable SSL interstitial.
Possible non-attack causes of the overridable SSL interstitial. Many errors
are not reported in this histogram and new errors may be added over time,
therefore one should not look at the breakdown of this histogram (one bucket
divided by the sum) because that will be inaccurate. Instead, one should
look at each bucket count divided by the count of the ssl errors of that
type. E.g. WWW mismatch is recorded only when the ssl error is
CERT_COMMON_NAME_INVALID, so one should look at the bucket count of WWW
mismatch divided by the bucket count of CERT_COMMON_NAME_INVALID in the
histogram interstitial.ssl_error_type.
</summary>
</histogram>
......@@ -48564,6 +48580,37 @@ Therefore, the affected-histogram name has to have at least one dot in it.
<enum name="SSLNonAttackCauses" type="int">
<int value="0" label="CLOCK_PAST: System clock set early"/>
<int value="1" label="CLOCK_FUTURE: System clock set late"/>
<int value="2"
label="WWW_SUBDOMAIN_MATCH: Difference between the URL and the DNS is
www">
This cause is recorded if the ssl error is CERT_COMMON_NAME_INVALID and the
hostname differs from one of the DNS names in the certificate (CN or SANs)
only by the presence or absence of the single-label prefix &quot;www&quot;.
This case is not recored if the host name is not a known TLD.
</int>
<int value="3" label="SUBDOMAIN_MATCH: The URL is a subdomain of the DNS">
This cause is recorded if the ssl error is CERT_COMMON_NAME_INVALID and the
difference between the URL and the DNS name is not &quot;www&quot;. This
case is not recorded if the host name is not a known TLD.
</int>
<int value="4"
label="SUBDOMAIN_INVERSE_MATCH: The DNS is a subdomian of the URL">
This cause is recorded if the ssl error is CERT_COMMON_NAME_INVALID and the
difference between the DNS name and the URL is not &quot;www&quot;. This
case is not recorded if the host name is not a known TLD.
</int>
<int value="5"
label="SUBDOMAIN_OUTSIDE_WILDCARD: The URL is outside the scope of the
wildcard certificate">
This cause is recorded only if the ssl error is CERT_COMMON_NAME_INVALID, we
have received a wildcard certificate and the scope of a wildcard certificate
is too narrow for the hostname. This case is not recorded if the host name
is not a known TLD.
</int>
<int value="6"
label="HOST_NAME_NOT_KNOWN_TLD: The host name is not a known TLD">
This cause is recorded only for CERT_COMMON_NAME_INVALID errors.
</int>
</enum>
<enum name="SSLResponseTypesV2" type="int">
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment