x509_openssl_util.cc revision 72a454cd3513ac24fbdd0e0cb9ad70b86a99b801
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 3// found in the LICENSE file. 4 5#include "net/base/x509_openssl_util.h" 6 7#include <algorithm> 8 9#include "base/logging.h" 10#include "base/string_piece.h" 11#include "base/string_util.h" 12#include "net/base/x509_cert_types.h" 13 14namespace net { 15 16namespace x509_openssl_util { 17 18bool ParsePrincipalKeyAndValueByIndex(X509_NAME* name, 19 int index, 20 std::string* key, 21 std::string* value) { 22 X509_NAME_ENTRY* entry = X509_NAME_get_entry(name, index); 23 if (!entry) 24 return false; 25 26 if (key) { 27 ASN1_OBJECT* object = X509_NAME_ENTRY_get_object(entry); 28 key->assign(OBJ_nid2sn(OBJ_obj2nid(object))); 29 } 30 31 ASN1_STRING* data = X509_NAME_ENTRY_get_data(entry); 32 if (!data) 33 return false; 34 35 unsigned char* buf = NULL; 36 int len = ASN1_STRING_to_UTF8(&buf, data); 37 if (len <= 0) 38 return false; 39 40 value->assign(reinterpret_cast<const char*>(buf), len); 41 OPENSSL_free(buf); 42 return true; 43} 44 45bool ParsePrincipalValueByIndex(X509_NAME* name, 46 int index, 47 std::string* value) { 48 return ParsePrincipalKeyAndValueByIndex(name, index, NULL, value); 49} 50 51bool ParsePrincipalValueByNID(X509_NAME* name, int nid, std::string* value) { 52 int index = X509_NAME_get_index_by_NID(name, nid, -1); 53 if (index < 0) 54 return false; 55 56 return ParsePrincipalValueByIndex(name, index, value); 57} 58 59bool ParseDate(ASN1_TIME* x509_time, base::Time* time) { 60 if (!x509_time || 61 (x509_time->type != V_ASN1_UTCTIME && 62 x509_time->type != V_ASN1_GENERALIZEDTIME)) 63 return false; 64 65 base::StringPiece str_date(reinterpret_cast<const char*>(x509_time->data), 66 x509_time->length); 67 68 CertDateFormat format = x509_time->type == V_ASN1_UTCTIME ? 69 CERT_DATE_FORMAT_UTC_TIME : CERT_DATE_FORMAT_GENERALIZED_TIME; 70 return ParseCertificateDate(str_date, format, time); 71} 72 73// TODO(joth): Investigate if we can upstream this into the OpenSSL library, 74// to avoid duplicating this logic across projects. 75bool VerifyHostname(const std::string& hostname, 76 const std::vector<std::string>& cert_names) { 77 DCHECK(!hostname.empty()); 78 79 // Simple host name validation. A valid domain name must only contain 80 // alpha, digits, hyphens, and dots. An IP address may have digits and dots, 81 // and also square braces and colons for IPv6 addresses. 82 std::string reference_name; 83 reference_name.reserve(hostname.length()); 84 85 bool found_alpha = false; 86 bool found_ip6_chars = false; 87 bool found_hyphen = false; 88 int dot_count = 0; 89 90 size_t first_dot_index = std::string::npos; 91 for (std::string::const_iterator it = hostname.begin(); 92 it != hostname.end(); ++it) { 93 char c = *it; 94 if (IsAsciiAlpha(c)) { 95 found_alpha = true; 96 c = base::ToLowerASCII(c); 97 } else if (c == '.') { 98 ++dot_count; 99 if (first_dot_index == std::string::npos) 100 first_dot_index = reference_name.length(); 101 } else if (c == ':') { 102 found_ip6_chars = true; 103 } else if (c == '-') { 104 found_hyphen = true; 105 } else if (!IsAsciiDigit(c)) { 106 LOG(WARNING) << "Invalid char " << c << " in hostname " << hostname; 107 return false; 108 } 109 reference_name.push_back(c); 110 } 111 DCHECK(!reference_name.empty()); 112 113 if (found_ip6_chars || !found_alpha) { 114 // For now we just do simple localhost IP address support, primarily as 115 // it's needed by the test server. TODO(joth): Replace this with full IP 116 // address support. See http://crbug.com/62973 117 if (hostname == "127.0.0.1" && 118 std::find(cert_names.begin(), cert_names.end(), hostname) != 119 cert_names.end()) { 120 DVLOG(1) << "Allowing localhost IP certificate: " << hostname; 121 return true; 122 } 123 NOTIMPLEMENTED() << hostname; // See comment above. 124 return false; 125 } 126 127 // |wildcard_domain| is the remainder of |host| after the leading host 128 // component is stripped off, but includes the leading dot e.g. 129 // "www.f.com" -> ".f.com". 130 // If there is no meaningful domain part to |host| (e.g. it is an IP address 131 // or contains no dots) then |wildcard_domain| will be empty. 132 // We required at least 3 components (i.e. 2 dots) as a basic protection 133 // against too-broad wild-carding. 134 base::StringPiece wildcard_domain; 135 if (found_alpha && !found_ip6_chars && dot_count >= 2) { 136 DCHECK(first_dot_index != std::string::npos); 137 wildcard_domain = reference_name; 138 wildcard_domain.remove_prefix(first_dot_index); 139 DCHECK(wildcard_domain.starts_with(".")); 140 } 141 142 for (std::vector<std::string>::const_iterator it = cert_names.begin(); 143 it != cert_names.end(); ++it) { 144 // Catch badly corrupt cert names up front. 145 if (it->empty() || it->find('\0') != std::string::npos) { 146 LOG(WARNING) << "Bad name in cert: " << *it; 147 continue; 148 } 149 const std::string cert_name_string(StringToLowerASCII(*it)); 150 base::StringPiece cert_match(cert_name_string); 151 152 // Remove trailing dot, if any. 153 if (cert_match.ends_with(".")) 154 cert_match.remove_suffix(1); 155 156 // The hostname must be at least as long as the cert name it is matching, 157 // as we require the wildcard (if present) to match at least one character. 158 if (cert_match.length() > reference_name.length()) 159 continue; 160 161 if (cert_match == reference_name) 162 return true; 163 164 // Next see if this cert name starts with a wildcard, so long as the 165 // hostname we're matching against has a valid 'domain' part to match. 166 // Note the "-10" version of draft-saintandre-tls-server-id-check allows 167 // the wildcard to appear anywhere in the leftmost label, rather than 168 // requiring it to be the only character. See also http://crbug.com/60719 169 if (wildcard_domain.empty() || !cert_match.starts_with("*")) 170 continue; 171 172 // Erase the * but not the . from the domain, as we need to include the dot 173 // in the comparison. 174 cert_match.remove_prefix(1); 175 176 // Do character by character comparison on the remainder to see 177 // if we have a wildcard match. This intentionally does no special handling 178 // for any other wildcard characters in |domain|; alternatively it could 179 // detect these and skip those candidate cert names. 180 if (cert_match == wildcard_domain) 181 return true; 182 } 183 DVLOG(1) << "Could not find any match for " << hostname 184 << " (canonicalized as " << reference_name 185 << ") in cert names " << JoinString(cert_names, '|'); 186 return false; 187} 188 189} // namespace x509_openssl_util 190 191} // namespace net 192