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/attestation/attestation_flow.h"
6
7#include "base/bind.h"
8#include "chromeos/cryptohome/async_method_caller.h"
9#include "chromeos/dbus/cryptohome_client.h"
10
11namespace chromeos {
12namespace attestation {
13
14namespace {
15
16// Redirects to one of three callbacks based on a boolean value and dbus call
17// status.
18//
19// Parameters
20//   on_true - Called when status=succes and value=true.
21//   on_false - Called when status=success and value=false.
22//   on_fail - Called when status=failure.
23//   status - The D-Bus operation status.
24//   value - The value returned by the D-Bus operation.
25void DBusBoolRedirectCallback(const base::Closure& on_true,
26                              const base::Closure& on_false,
27                              const base::Closure& on_fail,
28                              DBusMethodCallStatus status,
29                              bool value) {
30  if (status != DBUS_METHOD_CALL_SUCCESS) {
31    LOG(ERROR) << "Attestation: Failed to query enrollment state.";
32    if (!on_fail.is_null())
33      on_fail.Run();
34    return;
35  }
36  const base::Closure& task = value ? on_true : on_false;
37  if (!task.is_null())
38    task.Run();
39}
40
41void DBusDataMethodCallback(
42    const AttestationFlow::CertificateCallback& callback,
43    DBusMethodCallStatus status,
44    bool result,
45    const std::string& data) {
46  if (status != DBUS_METHOD_CALL_SUCCESS) {
47    LOG(ERROR) << "Attestation: DBus data operation failed.";
48    if (!callback.is_null())
49      callback.Run(false, "");
50    return;
51  }
52  if (!callback.is_null())
53    callback.Run(result, data);
54}
55
56AttestationKeyType GetKeyTypeForProfile(
57    AttestationCertificateProfile profile) {
58  switch (profile) {
59    case PROFILE_ENTERPRISE_MACHINE_CERTIFICATE:
60      return KEY_DEVICE;
61    case PROFILE_ENTERPRISE_USER_CERTIFICATE:
62    case PROFILE_CONTENT_PROTECTION_CERTIFICATE:
63      return KEY_USER;
64  }
65  NOTREACHED();
66  return KEY_USER;
67}
68
69std::string GetKeyNameForProfile(AttestationCertificateProfile profile,
70                                 const std::string& origin) {
71  switch (profile) {
72    case PROFILE_ENTERPRISE_MACHINE_CERTIFICATE:
73      return kEnterpriseMachineKey;
74    case PROFILE_ENTERPRISE_USER_CERTIFICATE:
75      return kEnterpriseUserKey;
76    case PROFILE_CONTENT_PROTECTION_CERTIFICATE:
77      return std::string(kContentProtectionKeyPrefix) + origin;
78  }
79  NOTREACHED();
80  return "";
81}
82
83}  // namespace
84
85AttestationFlow::AttestationFlow(cryptohome::AsyncMethodCaller* async_caller,
86                                 CryptohomeClient* cryptohome_client,
87                                 scoped_ptr<ServerProxy> server_proxy)
88    : async_caller_(async_caller),
89      cryptohome_client_(cryptohome_client),
90      server_proxy_(server_proxy.Pass()),
91      weak_factory_(this) {
92}
93
94AttestationFlow::~AttestationFlow() {
95}
96
97void AttestationFlow::GetCertificate(
98    AttestationCertificateProfile certificate_profile,
99    const std::string& user_id,
100    const std::string& request_origin,
101    bool force_new_key,
102    const CertificateCallback& callback) {
103  // If this device has not enrolled with the Privacy CA, we need to do that
104  // first.  Once enrolled we can proceed with the certificate request.
105  base::Closure do_cert_request = base::Bind(
106      &AttestationFlow::StartCertificateRequest,
107      weak_factory_.GetWeakPtr(),
108      certificate_profile,
109      user_id,
110      request_origin,
111      force_new_key,
112      callback);
113  base::Closure on_enroll_failure = base::Bind(callback, false, "");
114  base::Closure do_enroll = base::Bind(&AttestationFlow::StartEnroll,
115                                       weak_factory_.GetWeakPtr(),
116                                       on_enroll_failure,
117                                       do_cert_request);
118  cryptohome_client_->TpmAttestationIsEnrolled(base::Bind(
119      &DBusBoolRedirectCallback,
120      do_cert_request,  // If enrolled, proceed with cert request.
121      do_enroll,        // If not enrolled, initiate enrollment.
122      on_enroll_failure));
123}
124
125void AttestationFlow::StartEnroll(const base::Closure& on_failure,
126                                  const base::Closure& next_task) {
127  // Get the attestation service to create a Privacy CA enrollment request.
128  async_caller_->AsyncTpmAttestationCreateEnrollRequest(
129      server_proxy_->GetType(),
130      base::Bind(&AttestationFlow::SendEnrollRequestToPCA,
131                 weak_factory_.GetWeakPtr(),
132                 on_failure,
133                 next_task));
134}
135
136void AttestationFlow::SendEnrollRequestToPCA(const base::Closure& on_failure,
137                                             const base::Closure& next_task,
138                                             bool success,
139                                             const std::string& data) {
140  if (!success) {
141    LOG(ERROR) << "Attestation: Failed to create enroll request.";
142    if (!on_failure.is_null())
143      on_failure.Run();
144    return;
145  }
146
147  // Send the request to the Privacy CA.
148  server_proxy_->SendEnrollRequest(
149      data,
150      base::Bind(&AttestationFlow::SendEnrollResponseToDaemon,
151                 weak_factory_.GetWeakPtr(),
152                 on_failure,
153                 next_task));
154}
155
156void AttestationFlow::SendEnrollResponseToDaemon(
157    const base::Closure& on_failure,
158    const base::Closure& next_task,
159    bool success,
160    const std::string& data) {
161  if (!success) {
162    LOG(ERROR) << "Attestation: Enroll request failed.";
163    if (!on_failure.is_null())
164      on_failure.Run();
165    return;
166  }
167
168  // Forward the response to the attestation service to complete enrollment.
169  async_caller_->AsyncTpmAttestationEnroll(
170      server_proxy_->GetType(),
171      data,
172      base::Bind(&AttestationFlow::OnEnrollComplete,
173                 weak_factory_.GetWeakPtr(),
174                 on_failure,
175                 next_task));
176}
177
178void AttestationFlow::OnEnrollComplete(const base::Closure& on_failure,
179                                       const base::Closure& next_task,
180                                       bool success,
181                                       cryptohome::MountError /*not_used*/) {
182  if (!success) {
183    LOG(ERROR) << "Attestation: Failed to complete enrollment.";
184    if (!on_failure.is_null())
185      on_failure.Run();
186    return;
187  }
188
189  // Enrollment has successfully completed, we can move on to whatever is next.
190  if (!next_task.is_null())
191    next_task.Run();
192}
193
194void AttestationFlow::StartCertificateRequest(
195    AttestationCertificateProfile certificate_profile,
196    const std::string& user_id,
197    const std::string& request_origin,
198    bool generate_new_key,
199    const CertificateCallback& callback) {
200  AttestationKeyType key_type = GetKeyTypeForProfile(certificate_profile);
201  std::string key_name = GetKeyNameForProfile(certificate_profile,
202                                              request_origin);
203  if (generate_new_key) {
204    // Get the attestation service to create a Privacy CA certificate request.
205    async_caller_->AsyncTpmAttestationCreateCertRequest(
206        server_proxy_->GetType(),
207        certificate_profile,
208        user_id,
209        request_origin,
210        base::Bind(&AttestationFlow::SendCertificateRequestToPCA,
211                   weak_factory_.GetWeakPtr(),
212                   key_type,
213                   user_id,
214                   key_name,
215                   callback));
216  } else {
217    // If the key already exists, query the existing certificate.
218    base::Closure on_key_exists = base::Bind(
219        &AttestationFlow::GetExistingCertificate,
220        weak_factory_.GetWeakPtr(),
221        key_type,
222        user_id,
223        key_name,
224        callback);
225    // If the key does not exist, call this method back with |generate_new_key|
226    // set to true.
227    base::Closure on_key_not_exists = base::Bind(
228        &AttestationFlow::StartCertificateRequest,
229        weak_factory_.GetWeakPtr(),
230        certificate_profile,
231        user_id,
232        request_origin,
233        true,
234        callback);
235    cryptohome_client_->TpmAttestationDoesKeyExist(
236        key_type,
237        user_id,
238        key_name,
239        base::Bind(&DBusBoolRedirectCallback,
240            on_key_exists,
241            on_key_not_exists,
242            base::Bind(callback, false, "")));
243  }
244}
245
246void AttestationFlow::SendCertificateRequestToPCA(
247    AttestationKeyType key_type,
248    const std::string& user_id,
249    const std::string& key_name,
250    const CertificateCallback& callback,
251    bool success,
252    const std::string& data) {
253  if (!success) {
254    LOG(ERROR) << "Attestation: Failed to create certificate request.";
255    if (!callback.is_null())
256      callback.Run(false, "");
257    return;
258  }
259
260  // Send the request to the Privacy CA.
261  server_proxy_->SendCertificateRequest(
262      data,
263      base::Bind(&AttestationFlow::SendCertificateResponseToDaemon,
264                 weak_factory_.GetWeakPtr(),
265                 key_type,
266                 user_id,
267                 key_name,
268                 callback));
269}
270
271void AttestationFlow::SendCertificateResponseToDaemon(
272    AttestationKeyType key_type,
273    const std::string& user_id,
274    const std::string& key_name,
275    const CertificateCallback& callback,
276    bool success,
277    const std::string& data) {
278  if (!success) {
279    LOG(ERROR) << "Attestation: Certificate request failed.";
280    if (!callback.is_null())
281      callback.Run(false, "");
282    return;
283  }
284
285  // Forward the response to the attestation service to complete the operation.
286  async_caller_->AsyncTpmAttestationFinishCertRequest(data,
287                                                      key_type,
288                                                      user_id,
289                                                      key_name,
290                                                      base::Bind(callback));
291}
292
293void AttestationFlow::GetExistingCertificate(
294    AttestationKeyType key_type,
295    const std::string& user_id,
296    const std::string& key_name,
297    const CertificateCallback& callback) {
298  cryptohome_client_->TpmAttestationGetCertificate(
299      key_type,
300      user_id,
301      key_name,
302      base::Bind(&DBusDataMethodCallback, callback));
303}
304
305ServerProxy::~ServerProxy() {}
306
307PrivacyCAType ServerProxy::GetType() {
308  return DEFAULT_PCA;
309}
310
311}  // namespace attestation
312}  // namespace chromeos
313