1 // Copyright 2018 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 "device/fido/u2f_sign_operation.h"
6 
7 #include <utility>
8 
9 #include "base/bind.h"
10 #include "base/strings/string_number_conversions.h"
11 #include "base/threading/sequenced_task_runner_handle.h"
12 #include "components/apdu/apdu_response.h"
13 #include "components/device_event_log/device_event_log.h"
14 #include "device/fido/authenticator_get_assertion_response.h"
15 #include "device/fido/ctap_get_assertion_request.h"
16 #include "device/fido/device_response_converter.h"
17 #include "device/fido/fido_device.h"
18 #include "device/fido/fido_parsing_utils.h"
19 #include "device/fido/u2f_command_constructor.h"
20 
21 namespace device {
22 
U2fSignOperation(FidoDevice * device,const CtapGetAssertionRequest & request,DeviceResponseCallback callback)23 U2fSignOperation::U2fSignOperation(FidoDevice* device,
24                                    const CtapGetAssertionRequest& request,
25                                    DeviceResponseCallback callback)
26     : DeviceOperation(device, request, std::move(callback)) {}
27 
28 U2fSignOperation::~U2fSignOperation() = default;
29 
Start()30 void U2fSignOperation::Start() {
31   if (!request().allow_list.empty()) {
32     if (request().alternative_application_parameter.has_value()) {
33       // Try the alternative value first. This is because the U2F Zero
34       // authenticator (at least) crashes if we try the wrong AppID first.
35       app_param_type_ = ApplicationParameterType::kAlternative;
36     }
37     WinkAndTrySign();
38   } else {
39     // In order to make U2F authenticators blink on sign request with an empty
40     // allow list, we send fake enrollment to the device and error out when the
41     // user has provided presence.
42     WinkAndTryFakeEnrollment();
43   }
44 }
45 
Cancel()46 void U2fSignOperation::Cancel() {
47   canceled_ = true;
48 }
49 
WinkAndTrySign()50 void U2fSignOperation::WinkAndTrySign() {
51   device()->TryWink(
52       base::BindOnce(&U2fSignOperation::TrySign, weak_factory_.GetWeakPtr()));
53 }
54 
TrySign()55 void U2fSignOperation::TrySign() {
56   DispatchDeviceRequest(
57       ConvertToU2fSignCommand(request(), app_param_type_, key_handle()),
58       base::BindOnce(&U2fSignOperation::OnSignResponseReceived,
59                      weak_factory_.GetWeakPtr()));
60 }
61 
OnSignResponseReceived(base::Optional<std::vector<uint8_t>> device_response)62 void U2fSignOperation::OnSignResponseReceived(
63     base::Optional<std::vector<uint8_t>> device_response) {
64   if (canceled_) {
65     return;
66   }
67 
68   auto result = apdu::ApduResponse::Status::SW_WRONG_DATA;
69   const auto apdu_response =
70       device_response
71           ? apdu::ApduResponse::CreateFromMessage(std::move(*device_response))
72           : base::nullopt;
73   if (apdu_response) {
74     result = apdu_response->status();
75   }
76 
77   // Older U2F devices may respond with the length of the input as an error
78   // response if the length is unexpected.
79   if (result == static_cast<apdu::ApduResponse::Status>(key_handle().size())) {
80     result = apdu::ApduResponse::Status::SW_WRONG_LENGTH;
81   }
82 
83   switch (result) {
84     case apdu::ApduResponse::Status::SW_NO_ERROR: {
85       auto application_parameter =
86           app_param_type_ == ApplicationParameterType::kPrimary
87               ? fido_parsing_utils::CreateSHA256Hash(request().rp_id)
88               : request().alternative_application_parameter.value_or(
89                     std::array<uint8_t, kRpIdHashLength>());
90       auto sign_response =
91           AuthenticatorGetAssertionResponse::CreateFromU2fSignResponse(
92               std::move(application_parameter), apdu_response->data(),
93               key_handle());
94       if (!sign_response) {
95         std::move(callback())
96             .Run(CtapDeviceResponseCode::kCtap2ErrOther, base::nullopt);
97         return;
98       }
99 
100       FIDO_LOG(DEBUG)
101           << "Received successful U2F sign response from authenticator: "
102           << base::HexEncode(apdu_response->data().data(),
103                              apdu_response->data().size());
104       std::move(callback())
105           .Run(CtapDeviceResponseCode::kSuccess, std::move(sign_response));
106       break;
107     }
108 
109     case apdu::ApduResponse::Status::SW_WRONG_DATA:
110     case apdu::ApduResponse::Status::SW_WRONG_LENGTH:
111       if (app_param_type_ == ApplicationParameterType::kAlternative) {
112         // |application_parameter_| failed, but there is also
113         // the primary value to try.
114         app_param_type_ = ApplicationParameterType::kPrimary;
115         WinkAndTrySign();
116       } else if (++current_key_handle_index_ < request().allow_list.size()) {
117         // Key is not for this device. Try signing with the next key.
118         if (request().alternative_application_parameter.has_value()) {
119           app_param_type_ = ApplicationParameterType::kAlternative;
120         }
121         WinkAndTrySign();
122       } else {
123         // No provided key was accepted by this device. Send registration
124         // (i.e. fake enroll) request to device.
125         TryFakeEnrollment();
126       }
127       break;
128 
129     case apdu::ApduResponse::Status::SW_CONDITIONS_NOT_SATISFIED:
130       // Waiting for user touch. Retry after 200 milliseconds delay.
131       base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
132           FROM_HERE,
133           base::BindOnce(&U2fSignOperation::WinkAndTrySign,
134                          weak_factory_.GetWeakPtr()),
135           kU2fRetryDelay);
136       break;
137 
138     default:
139       // Some sort of failure occurred. Abandon this device and move on.
140       std::move(callback())
141           .Run(CtapDeviceResponseCode::kCtap2ErrOther, base::nullopt);
142       return;
143   }
144 }
145 
WinkAndTryFakeEnrollment()146 void U2fSignOperation::WinkAndTryFakeEnrollment() {
147   device()->TryWink(base::BindOnce(&U2fSignOperation::TryFakeEnrollment,
148                                    weak_factory_.GetWeakPtr()));
149 }
150 
TryFakeEnrollment()151 void U2fSignOperation::TryFakeEnrollment() {
152   DispatchDeviceRequest(
153       ConstructBogusU2fRegistrationCommand(),
154       base::BindOnce(&U2fSignOperation::OnEnrollmentResponseReceived,
155                      weak_factory_.GetWeakPtr()));
156 }
157 
OnEnrollmentResponseReceived(base::Optional<std::vector<uint8_t>> device_response)158 void U2fSignOperation::OnEnrollmentResponseReceived(
159     base::Optional<std::vector<uint8_t>> device_response) {
160   if (canceled_) {
161     return;
162   }
163 
164   auto result = apdu::ApduResponse::Status::SW_WRONG_DATA;
165   if (device_response) {
166     const auto apdu_response =
167         apdu::ApduResponse::CreateFromMessage(std::move(*device_response));
168     if (apdu_response) {
169       result = apdu_response->status();
170     }
171   }
172 
173   switch (result) {
174     case apdu::ApduResponse::Status::SW_NO_ERROR:
175       std::move(callback())
176           .Run(CtapDeviceResponseCode::kCtap2ErrNoCredentials, base::nullopt);
177       break;
178 
179     case apdu::ApduResponse::Status::SW_CONDITIONS_NOT_SATISFIED:
180       // Waiting for user touch. Retry after 200 milliseconds delay.
181       base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
182           FROM_HERE,
183           base::BindOnce(&U2fSignOperation::TryFakeEnrollment,
184                          weak_factory_.GetWeakPtr()),
185           kU2fRetryDelay);
186       break;
187 
188     default:
189       // Some sort of failure occurred. Abandon this device and move on.
190       std::move(callback())
191           .Run(CtapDeviceResponseCode::kCtap2ErrOther, base::nullopt);
192       return;
193   }
194 }
195 
key_handle() const196 const std::vector<uint8_t>& U2fSignOperation::key_handle() const {
197   DCHECK_LT(current_key_handle_index_, request().allow_list.size());
198   return request().allow_list.at(current_key_handle_index_).id();
199 }
200 
201 }  // namespace device
202