1// Copyright (c) 2012 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 "chromeos/network/client_cert_util.h"
6
7#include <cert.h>
8#include <pk11pub.h>
9
10#include <list>
11#include <string>
12#include <vector>
13
14#include "base/strings/string_number_conversions.h"
15#include "base/strings/stringprintf.h"
16#include "base/values.h"
17#include "chromeos/network/certificate_pattern.h"
18#include "chromeos/network/network_event_log.h"
19#include "components/onc/onc_constants.h"
20#include "net/base/net_errors.h"
21#include "net/cert/cert_database.h"
22#include "net/cert/nss_cert_database.h"
23#include "net/cert/scoped_nss_types.h"
24#include "net/cert/x509_cert_types.h"
25#include "net/cert/x509_certificate.h"
26#include "third_party/cros_system_api/dbus/service_constants.h"
27
28namespace chromeos {
29
30namespace client_cert {
31
32namespace {
33
34const char kDefaultTPMPin[] = "111111";
35
36std::string GetStringFromDictionary(const base::DictionaryValue& dict,
37                                    const std::string& key) {
38  std::string s;
39  dict.GetStringWithoutPathExpansion(key, &s);
40  return s;
41}
42
43void GetClientCertTypeAndPattern(
44    const base::DictionaryValue& dict_with_client_cert,
45    ClientCertConfig* cert_config) {
46  using namespace ::onc::client_cert;
47  dict_with_client_cert.GetStringWithoutPathExpansion(
48      kClientCertType, &cert_config->client_cert_type);
49
50  if (cert_config->client_cert_type == kPattern) {
51    const base::DictionaryValue* pattern = NULL;
52    dict_with_client_cert.GetDictionaryWithoutPathExpansion(kClientCertPattern,
53                                                            &pattern);
54    if (pattern) {
55      bool success = cert_config->pattern.ReadFromONCDictionary(*pattern);
56      DCHECK(success);
57    }
58  }
59}
60
61}  // namespace
62
63// Returns true only if any fields set in this pattern match exactly with
64// similar fields in the principal.  If organization_ or organizational_unit_
65// are set, then at least one of the organizations or units in the principal
66// must match.
67bool CertPrincipalMatches(const IssuerSubjectPattern& pattern,
68                          const net::CertPrincipal& principal) {
69  if (!pattern.common_name().empty() &&
70      pattern.common_name() != principal.common_name) {
71    return false;
72  }
73
74  if (!pattern.locality().empty() &&
75      pattern.locality() != principal.locality_name) {
76    return false;
77  }
78
79  if (!pattern.organization().empty()) {
80    if (std::find(principal.organization_names.begin(),
81                  principal.organization_names.end(),
82                  pattern.organization()) ==
83        principal.organization_names.end()) {
84      return false;
85    }
86  }
87
88  if (!pattern.organizational_unit().empty()) {
89    if (std::find(principal.organization_unit_names.begin(),
90                  principal.organization_unit_names.end(),
91                  pattern.organizational_unit()) ==
92        principal.organization_unit_names.end()) {
93      return false;
94    }
95  }
96
97  return true;
98}
99
100std::string GetPkcs11AndSlotIdFromEapCertId(const std::string& cert_id,
101                                            int* slot_id) {
102  *slot_id = -1;
103  if (cert_id.empty())
104    return std::string();
105
106  size_t delimiter_pos = cert_id.find(':');
107  if (delimiter_pos == std::string::npos) {
108    // No delimiter found, so |cert_id| only contains the PKCS11 id.
109    return cert_id;
110  }
111  if (delimiter_pos + 1 >= cert_id.size()) {
112    LOG(ERROR) << "Empty PKCS11 id in cert id.";
113    return std::string();
114  }
115  int parsed_slot_id;
116  if (base::StringToInt(cert_id.substr(0, delimiter_pos), &parsed_slot_id))
117    *slot_id = parsed_slot_id;
118  else
119    LOG(ERROR) << "Slot ID is not an integer. Cert ID is: " << cert_id << ".";
120  return cert_id.substr(delimiter_pos + 1);
121}
122
123void GetClientCertFromShillProperties(
124    const base::DictionaryValue& shill_properties,
125    ConfigType* cert_config_type,
126    int* tpm_slot,
127    std::string* pkcs11_id) {
128  *cert_config_type = CONFIG_TYPE_NONE;
129  *tpm_slot = -1;
130  pkcs11_id->clear();
131
132  // Look for VPN specific client certificate properties.
133  //
134  // VPN Provider values are read from the "Provider" dictionary, not the
135  // "Provider.Type", etc keys (which are used only to set the values).
136  const base::DictionaryValue* provider_properties = NULL;
137  if (shill_properties.GetDictionaryWithoutPathExpansion(
138          shill::kProviderProperty, &provider_properties)) {
139    // Look for OpenVPN specific properties.
140    if (provider_properties->GetStringWithoutPathExpansion(
141            shill::kOpenVPNClientCertIdProperty, pkcs11_id)) {
142      *cert_config_type = CONFIG_TYPE_OPENVPN;
143      return;
144    }
145    // Look for L2TP-IPsec specific properties.
146    if (provider_properties->GetStringWithoutPathExpansion(
147                   shill::kL2tpIpsecClientCertIdProperty, pkcs11_id)) {
148      std::string cert_slot;
149      provider_properties->GetStringWithoutPathExpansion(
150          shill::kL2tpIpsecClientCertSlotProperty, &cert_slot);
151      if (!cert_slot.empty() && !base::StringToInt(cert_slot, tpm_slot)) {
152        LOG(ERROR) << "Cert slot is not an integer: " << cert_slot << ".";
153        return;
154      }
155
156      *cert_config_type = CONFIG_TYPE_IPSEC;
157    }
158    return;
159  }
160
161  // Look for EAP specific client certificate properties, which can either be
162  // part of a WiFi or EthernetEAP configuration.
163  std::string cert_id;
164  if (shill_properties.GetStringWithoutPathExpansion(shill::kEapCertIdProperty,
165                                                     &cert_id)) {
166    // Shill requires both CertID and KeyID for TLS connections, despite the
167    // fact that by convention they are the same ID, because one identifies
168    // the certificate and the other the private key.
169    std::string key_id;
170    shill_properties.GetStringWithoutPathExpansion(shill::kEapKeyIdProperty,
171                                                   &key_id);
172    // Assume the configuration to be invalid, if the two IDs are not identical.
173    if (cert_id != key_id) {
174      LOG(ERROR) << "EAP CertID differs from KeyID";
175      return;
176    }
177    *pkcs11_id = GetPkcs11AndSlotIdFromEapCertId(cert_id, tpm_slot);
178    *cert_config_type = CONFIG_TYPE_EAP;
179  }
180}
181
182void SetShillProperties(const ConfigType cert_config_type,
183                        const int tpm_slot,
184                        const std::string& pkcs11_id,
185                        base::DictionaryValue* properties) {
186  switch (cert_config_type) {
187    case CONFIG_TYPE_NONE: {
188      return;
189    }
190    case CONFIG_TYPE_OPENVPN: {
191      properties->SetStringWithoutPathExpansion(shill::kOpenVPNPinProperty,
192                                                kDefaultTPMPin);
193      properties->SetStringWithoutPathExpansion(
194          shill::kOpenVPNClientCertIdProperty, pkcs11_id);
195      break;
196    }
197    case CONFIG_TYPE_IPSEC: {
198      properties->SetStringWithoutPathExpansion(shill::kL2tpIpsecPinProperty,
199                                                kDefaultTPMPin);
200      properties->SetStringWithoutPathExpansion(
201          shill::kL2tpIpsecClientCertSlotProperty, base::IntToString(tpm_slot));
202      properties->SetStringWithoutPathExpansion(
203          shill::kL2tpIpsecClientCertIdProperty, pkcs11_id);
204      break;
205    }
206    case CONFIG_TYPE_EAP: {
207      properties->SetStringWithoutPathExpansion(shill::kEapPinProperty,
208                                                kDefaultTPMPin);
209      std::string key_id =
210          base::StringPrintf("%i:%s", tpm_slot, pkcs11_id.c_str());
211
212      // Shill requires both CertID and KeyID for TLS connections, despite the
213      // fact that by convention they are the same ID, because one identifies
214      // the certificate and the other the private key.
215      properties->SetStringWithoutPathExpansion(shill::kEapCertIdProperty,
216                                                key_id);
217      properties->SetStringWithoutPathExpansion(shill::kEapKeyIdProperty,
218                                                key_id);
219      break;
220    }
221  }
222}
223
224void SetEmptyShillProperties(const ConfigType cert_config_type,
225                             base::DictionaryValue* properties) {
226  switch (cert_config_type) {
227    case CONFIG_TYPE_NONE: {
228      return;
229    }
230    case CONFIG_TYPE_OPENVPN: {
231      properties->SetStringWithoutPathExpansion(shill::kOpenVPNPinProperty,
232                                                std::string());
233      properties->SetStringWithoutPathExpansion(
234          shill::kOpenVPNClientCertIdProperty, std::string());
235      break;
236    }
237    case CONFIG_TYPE_IPSEC: {
238      properties->SetStringWithoutPathExpansion(shill::kL2tpIpsecPinProperty,
239                                                std::string());
240      properties->SetStringWithoutPathExpansion(
241          shill::kL2tpIpsecClientCertSlotProperty, std::string());
242      properties->SetStringWithoutPathExpansion(
243          shill::kL2tpIpsecClientCertIdProperty, std::string());
244      break;
245    }
246    case CONFIG_TYPE_EAP: {
247      properties->SetStringWithoutPathExpansion(shill::kEapPinProperty,
248                                                std::string());
249      // Shill requires both CertID and KeyID for TLS connections, despite the
250      // fact that by convention they are the same ID, because one identifies
251      // the certificate and the other the private key.
252      properties->SetStringWithoutPathExpansion(shill::kEapCertIdProperty,
253                                                std::string());
254      properties->SetStringWithoutPathExpansion(shill::kEapKeyIdProperty,
255                                                std::string());
256      break;
257    }
258  }
259}
260
261ClientCertConfig::ClientCertConfig()
262    : location(CONFIG_TYPE_NONE),
263      client_cert_type(onc::client_cert::kClientCertTypeNone) {
264}
265
266void OncToClientCertConfig(const base::DictionaryValue& network_config,
267                           ClientCertConfig* cert_config) {
268  using namespace ::onc;
269
270  *cert_config = ClientCertConfig();
271
272  const base::DictionaryValue* dict_with_client_cert = NULL;
273
274  const base::DictionaryValue* wifi = NULL;
275  network_config.GetDictionaryWithoutPathExpansion(network_config::kWiFi,
276                                                   &wifi);
277  if (wifi) {
278    const base::DictionaryValue* eap = NULL;
279    wifi->GetDictionaryWithoutPathExpansion(wifi::kEAP, &eap);
280    if (!eap)
281      return;
282
283    dict_with_client_cert = eap;
284    cert_config->location = CONFIG_TYPE_EAP;
285  }
286
287  const base::DictionaryValue* vpn = NULL;
288  network_config.GetDictionaryWithoutPathExpansion(network_config::kVPN, &vpn);
289  if (vpn) {
290    const base::DictionaryValue* openvpn = NULL;
291    vpn->GetDictionaryWithoutPathExpansion(vpn::kOpenVPN, &openvpn);
292    const base::DictionaryValue* ipsec = NULL;
293    vpn->GetDictionaryWithoutPathExpansion(vpn::kIPsec, &ipsec);
294    if (openvpn) {
295      dict_with_client_cert = openvpn;
296      cert_config->location = CONFIG_TYPE_OPENVPN;
297    } else if (ipsec) {
298      dict_with_client_cert = ipsec;
299      cert_config->location = CONFIG_TYPE_IPSEC;
300    } else {
301      return;
302    }
303  }
304
305  const base::DictionaryValue* ethernet = NULL;
306  network_config.GetDictionaryWithoutPathExpansion(network_config::kEthernet,
307                                                   &ethernet);
308  if (ethernet) {
309    const base::DictionaryValue* eap = NULL;
310    ethernet->GetDictionaryWithoutPathExpansion(wifi::kEAP, &eap);
311    if (!eap)
312      return;
313    dict_with_client_cert = eap;
314    cert_config->location = CONFIG_TYPE_EAP;
315  }
316
317  if (dict_with_client_cert)
318    GetClientCertTypeAndPattern(*dict_with_client_cert, cert_config);
319}
320
321bool IsCertificateConfigured(const ConfigType cert_config_type,
322                             const base::DictionaryValue& service_properties) {
323  // VPN certificate properties are read from the Provider dictionary.
324  const base::DictionaryValue* provider_properties = NULL;
325  service_properties.GetDictionaryWithoutPathExpansion(
326      shill::kProviderProperty, &provider_properties);
327  switch (cert_config_type) {
328    case CONFIG_TYPE_NONE:
329      return true;
330    case CONFIG_TYPE_OPENVPN:
331      // OpenVPN generally requires a passphrase and we don't know whether or
332      // not one is required, so always return false here.
333      return false;
334    case CONFIG_TYPE_IPSEC: {
335      if (!provider_properties)
336        return false;
337
338      std::string client_cert_id;
339      provider_properties->GetStringWithoutPathExpansion(
340          shill::kL2tpIpsecClientCertIdProperty, &client_cert_id);
341      return !client_cert_id.empty();
342    }
343    case CONFIG_TYPE_EAP: {
344      std::string cert_id = GetStringFromDictionary(
345          service_properties, shill::kEapCertIdProperty);
346      std::string key_id = GetStringFromDictionary(
347          service_properties, shill::kEapKeyIdProperty);
348      std::string identity = GetStringFromDictionary(
349          service_properties, shill::kEapIdentityProperty);
350      return !cert_id.empty() && !key_id.empty() && !identity.empty();
351    }
352  }
353  NOTREACHED();
354  return false;
355}
356
357}  // namespace client_cert
358
359}  // namespace chromeos
360