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" |
| 11 | 12 |
| 12 namespace net { | 13 namespace net { |
| 13 | 14 |
| 14 namespace x509_openssl_util { | 15 namespace x509_openssl_util { |
| 15 | 16 |
| 16 namespace { | 17 namespace { |
| 17 | 18 |
| 18 // Helper for ParseDate. |*field| must contain at least |field_len| characters. | 19 // 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 | 20 // |*field| will be advanced by |field_len| on exit. |*ok| is set to false if |
| (...skipping 81 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 101 | 102 |
| 102 if (!valid) { | 103 if (!valid) { |
| 103 NOTREACHED() << "can't parse x509 date " << str_date; | 104 NOTREACHED() << "can't parse x509 date " << str_date; |
| 104 return false; | 105 return false; |
| 105 } | 106 } |
| 106 | 107 |
| 107 *time = base::Time::FromUTCExploded(exploded); | 108 *time = base::Time::FromUTCExploded(exploded); |
| 108 return true; | 109 return true; |
| 109 } | 110 } |
| 110 | 111 |
| 112 // TODO(joth): Investigate if we can upstream this into the OpenSSL library, | |
| 113 // to avoid duplicating this logic across projects. | |
| 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. | |
| 121 std::string reference_name; | |
| 122 reference_name.reserve(hostname.length()); | |
| 123 | |
| 124 bool found_alpha = false; | |
| 125 bool found_ip6_chars = false; | |
| 126 bool found_hyphen = false; | |
| 127 int dot_count = 0; | |
| 128 | |
| 129 size_t first_dot_index = std::string::npos; | |
| 130 for (std::string::const_iterator it = hostname.begin(); | |
| 131 it != hostname.end(); ++it) { | |
|
agl
2010/11/15 17:55:04
indentation looks to be off by a space here.
joth
2010/11/16 14:01:10
Done.
| |
| 132 char c = *it; | |
| 133 if (IsAsciiAlpha(c)) { | |
| 134 found_alpha = true; | |
| 135 c = base::ToLowerASCII(c); | |
| 136 } else if (c == '.') { | |
| 137 ++dot_count; | |
| 138 if (first_dot_index == std::string::npos) | |
| 139 first_dot_index = reference_name.length(); | |
| 140 } else if (c == ':') { | |
| 141 found_ip6_chars = true; | |
| 142 } else if (c == '-') { | |
| 143 found_hyphen = true; | |
| 144 } else if (!IsAsciiDigit(c)) { | |
| 145 LOG(WARNING) << "Invalid char " << c << " in hostname " << hostname; | |
| 146 return false; | |
| 147 } | |
| 148 reference_name.push_back(c); | |
| 149 } | |
| 150 DCHECK(!reference_name.empty()); | |
| 151 | |
| 152 // TODO(joth): Add IP address support. See http://crbug.com/62973. | |
| 153 if (found_ip6_chars || !found_alpha) { | |
| 154 NOTIMPLEMENTED() << hostname; | |
| 155 return false; | |
| 156 } | |
| 157 | |
| 158 // |wildcard_domain| is the remainder of |host| after the leading host | |
| 159 // component is stripped off, but includes the leading dot e.g. | |
| 160 // "www.f.com" -> ".f.com". | |
| 161 // If there is no meaningful domain part to |host| (e.g. it is an IP address | |
| 162 // or contains no dots) then |wildcard_domain| will be empty. | |
| 163 // We required at least 3 components (i.e. 2 dots) as a basic protection | |
| 164 // against too-broad wild-carding. | |
| 165 base::StringPiece wildcard_domain; | |
| 166 if (found_alpha && !found_ip6_chars && dot_count >= 2) { | |
| 167 DCHECK(first_dot_index != std::string::npos); | |
| 168 wildcard_domain = reference_name; | |
| 169 wildcard_domain.remove_prefix(first_dot_index); | |
| 170 DCHECK(wildcard_domain.starts_with(".")); | |
| 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 // Note the "-10" version of draft-saintandre-tls-server-id-check allows | |
| 198 // the wildcard to appear anywhere in the leftmost label, rather than | |
|
agl
2010/11/15 17:55:04
double space here.
joth
2010/11/16 14:01:10
Done.
| |
| 199 // requiring it to be the only character. See | |
|
agl
2010/11/15 17:55:04
'See' what?
joth
2010/11/16 14:01:10
Done.
See http://crbug.com/60719
| |
| 200 if (wildcard_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 == wildcard_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 |