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