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