Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "net/base/x509_openssl_util.h" | 5 #include "net/base/x509_openssl_util.h" |
| 6 | 6 |
| 7 #include "base/logging.h" | 7 #include "base/logging.h" |
| 8 #include "base/string_number_conversions.h" | 8 #include "base/string_number_conversions.h" |
| 9 #include "base/string_piece.h" | 9 #include "base/string_piece.h" |
| 10 #include "base/string_util.h" | |
| 10 #include "base/time.h" | 11 #include "base/time.h" |
| 12 #include "net/base/registry_controlled_domain.h" | |
| 11 | 13 |
| 12 namespace net { | 14 namespace net { |
| 13 | 15 |
| 14 namespace x509_openssl_util { | 16 namespace x509_openssl_util { |
| 15 | 17 |
| 16 namespace { | 18 namespace { |
| 17 | 19 |
| 18 // Helper for ParseDate. |*field| must contain at least |field_len| characters. | 20 // Helper for ParseDate. |*field| must contain at least |field_len| characters. |
| 19 // |*field| will be advanced by |field_len| on exit. |*ok| is set to false if | 21 // |*field| will be advanced by |field_len| on exit. |*ok| is set to false if |
| 20 // there is an error in parsing the number, but left untouched otherwise. | 22 // there is an error in parsing the number, but left untouched otherwise. |
| (...skipping 80 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 101 | 103 |
| 102 if (!valid) { | 104 if (!valid) { |
| 103 NOTREACHED() << "can't parse x509 date " << str_date; | 105 NOTREACHED() << "can't parse x509 date " << str_date; |
| 104 return false; | 106 return false; |
| 105 } | 107 } |
| 106 | 108 |
| 107 *time = base::Time::FromUTCExploded(exploded); | 109 *time = base::Time::FromUTCExploded(exploded); |
| 108 return true; | 110 return true; |
| 109 } | 111 } |
| 110 | 112 |
| 113 // Implemented as per http://tools.ietf.org/html/draft-saintandre-tls-server-id- check-09#section-4.4.3 | |
| 114 bool VerifyHostname(const std::string& hostname, | |
| 115 const std::vector<std::string>& cert_names) { | |
| 116 DCHECK(!hostname.empty()); | |
| 117 | |
| 118 // Simple host name validation. A valid domain name must only contain | |
| 119 // alpha, digits, hyphens, and dots. An IP address may have digits and dots, | |
| 120 // and also square braces and colons for IPv6 addresses. | |
|
wtc
2010/11/03 00:29:49
IPv6 address literals passed to this function shou
Ryan Sleevi
2010/11/03 03:42:21
BUG: I'm not sure that this function should consid
joth
2010/11/12 18:55:23
Done.
joth
2010/11/12 18:55:23
Done.
Removed all attempt at IP support, for now.
| |
| 121 bool found_alpha = false; | |
| 122 bool found_ip6_chars = false; | |
| 123 bool found_hyphen = false; | |
| 124 | |
| 125 std::string reference_name; | |
| 126 size_t first_dot_index = std::string::npos; | |
| 127 reference_name.reserve(hostname.length()); | |
| 128 for (std::string::const_iterator it = hostname.begin(); | |
| 129 it != hostname.end(); ++it) { | |
| 130 char c = *it; | |
| 131 if (IsAsciiAlpha(c)) { | |
| 132 found_alpha = true; | |
| 133 c = ToLowerASCII(c); | |
| 134 } else if (c == '.') { | |
| 135 if (first_dot_index == std::string::npos) | |
| 136 first_dot_index = reference_name.length(); | |
| 137 } else if (c == '[' || c == ']' || c == ':') { | |
| 138 found_ip6_chars = true; | |
| 139 } else if (c == '-') { | |
| 140 found_hyphen = true; | |
| 141 } else if (!IsAsciiDigit(c)) { | |
| 142 LOG(WARNING) << "Invalid char " << c << " in hostname " << hostname; | |
| 143 return false; | |
| 144 } | |
| 145 reference_name.push_back(c); | |
| 146 } | |
| 147 DCHECK(!reference_name.empty()); | |
| 148 if (found_hyphen && found_ip6_chars) { | |
| 149 LOG(WARNING) << "Mixed IPv6 domain name " << hostname; | |
| 150 return false; | |
| 151 } | |
| 152 | |
| 153 // |host_domain| is the remainder of |host| after the leading host component | |
| 154 // is stripped off, but includes the leading dot e.g. "www.f.com" -> ".f.com". | |
| 155 // If there is no meaningful domain part to |host| (e.g. it is an IP address | |
| 156 // or contains no dots) then |host_domain| will be empty. | |
| 157 base::StringPiece host_domain; | |
| 158 if (found_alpha && !found_ip6_chars && first_dot_index != std::string::npos) { | |
| 159 host_domain = reference_name; | |
| 160 host_domain.remove_prefix(first_dot_index); | |
| 161 DCHECK(host_domain.starts_with(".")); | |
| 162 | |
| 163 // Make sure the domain is not a registry domain, e.g. to avoid a dodgy cert | |
| 164 // claiming to be for *.org matching a hostname of chromium.org. | |
| 165 size_t registry_length = RegistryControlledDomainService::GetRegistryLength( | |
| 166 reference_name, true); | |
| 167 // The registry length does not include the leading dot, so add 1 for it. | |
| 168 if (host_domain.length() == registry_length + 1) { | |
|
wtc
2010/11/03 00:29:49
Nit: omit curly braces {} for one-liner "if" bodie
joth
2010/11/12 18:55:23
Done.
| |
| 169 host_domain.clear(); | |
| 170 } | |
| 171 } | |
| 172 | |
| 173 for (std::vector<std::string>::const_iterator it = cert_names.begin(); | |
| 174 it != cert_names.end(); ++it) { | |
| 175 // Catch badly corrupt cert names up front. | |
| 176 if (it->empty() || it->find('\0') != std::string::npos) { | |
| 177 LOG(WARNING) << "Bad name in cert: " << *it; | |
| 178 continue; | |
| 179 } | |
| 180 const std::string cert_name_string(StringToLowerASCII(*it)); | |
| 181 base::StringPiece cert_match(cert_name_string); | |
| 182 | |
| 183 // Remove trailing dot, if any. | |
| 184 if (cert_match.ends_with(".")) | |
| 185 cert_match.remove_suffix(1); | |
| 186 | |
| 187 // The hostname must be at least as long as the cert name it is matching, | |
| 188 // as we require the wildcard (if present) to match at least one character. | |
| 189 if (cert_match.length() > reference_name.length()) | |
| 190 continue; | |
| 191 | |
| 192 if (cert_match == reference_name) | |
| 193 return true; | |
| 194 | |
| 195 // Next see if this cert name starts with a wildcard, so long as the | |
| 196 // hostname we're matching against has a valid 'domain' part to match. | |
| 197 // TODO(joth): The "-10" version of draft-saintandre-tls-server-id-check | |
| 198 // allows the wildcard to appear anywhere in the leftmost label. Once this | |
| 199 // is finalized, we may need to update this check to use MatchPattern. | |
|
Ryan Sleevi
2010/11/03 03:42:21
@wtc: here
joth
2010/11/12 18:55:23
Done.
| |
| 200 if (host_domain.empty() || !cert_match.starts_with("*")) | |
| 201 continue; | |
| 202 | |
| 203 // Erase the * but not the . from the domain, as we need to include the dot | |
| 204 // in the comparison. | |
| 205 cert_match.remove_prefix(1); | |
| 206 | |
| 207 // Do character by character comparison on the remainder to see | |
| 208 // if we have a wildcard match. This intentionally does no special handling | |
| 209 // for any other wildcard characters in |domain|; alternatively it could | |
| 210 // detect these and skip those candidate cert names. | |
| 211 if (cert_match == host_domain) | |
| 212 return true; | |
| 213 } | |
| 214 DVLOG(1) << "Could not find any match for " << hostname | |
| 215 << " (canonicalized as " << reference_name | |
| 216 << ") in cert names " << JoinString(cert_names, '|'); | |
| 217 return false; | |
| 218 } | |
| 219 | |
| 111 } // namespace x509_openssl_util | 220 } // namespace x509_openssl_util |
| 112 | 221 |
| 113 } // namespace net | 222 } // namespace net |
| OLD | NEW |