1/*
2 * Copyright (C) 2018 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "CtapHidDriver.h"
28
29#if ENABLE(WEB_AUTHN) && PLATFORM(MAC)
30
31#include <WebCore/FidoConstants.h>
32#include <wtf/RandomNumber.h>
33#include <wtf/RunLoop.h>
34#include <wtf/Vector.h>
35#include <wtf/text/Base64.h>
36
37namespace WebKit {
38using namespace fido;
39
40CtapHidDriver::Worker::Worker(UniqueRef<HidConnection>&& connection)
41 : m_connection(WTFMove(connection))
42{
43 m_connection->initialize();
44}
45
46CtapHidDriver::Worker::~Worker()
47{
48 m_connection->terminate();
49}
50
51void CtapHidDriver::Worker::transact(fido::FidoHidMessage&& requestMessage, MessageCallback&& callback)
52{
53 ASSERT(m_state == State::Idle);
54 m_state = State::Write;
55 m_requestMessage = WTFMove(requestMessage);
56 m_responseMessage.reset();
57 m_callback = WTFMove(callback);
58
59 // HidConnection could hold data from other applications, and thereofore invalidate it before each transaction.
60 m_connection->invalidateCache();
61 m_connection->send(m_requestMessage->popNextPacket(), [weakThis = makeWeakPtr(*this)](HidConnection::DataSent sent) mutable {
62 ASSERT(RunLoop::isMain());
63 if (!weakThis)
64 return;
65 weakThis->write(sent);
66 });
67}
68
69void CtapHidDriver::Worker::write(HidConnection::DataSent sent)
70{
71 ASSERT(m_state == State::Write);
72 if (sent != HidConnection::DataSent::Yes) {
73 returnMessage(WTF::nullopt);
74 return;
75 }
76
77 if (!m_requestMessage->numPackets()) {
78 m_state = State::Read;
79 m_connection->registerDataReceivedCallback([weakThis = makeWeakPtr(*this)](Vector<uint8_t>&& data) mutable {
80 ASSERT(RunLoop::isMain());
81 if (!weakThis)
82 return;
83 weakThis->read(data);
84 });
85 return;
86 }
87
88 m_connection->send(m_requestMessage->popNextPacket(), [weakThis = makeWeakPtr(*this)](HidConnection::DataSent sent) mutable {
89 ASSERT(RunLoop::isMain());
90 if (!weakThis)
91 return;
92 weakThis->write(sent);
93 });
94}
95
96void CtapHidDriver::Worker::read(const Vector<uint8_t>& data)
97{
98 ASSERT(m_state == State::Read);
99 if (!m_responseMessage) {
100 m_responseMessage = FidoHidMessage::createFromSerializedData(data);
101 // The first few reports could be for other applications, and therefore ignore those.
102 if (!m_responseMessage || m_responseMessage->channelId() != m_requestMessage->channelId()) {
103 LOG_ERROR("Couldn't parse a hid init packet: %s", m_responseMessage ? "wrong channel id." : "bad data.");
104 m_responseMessage.reset();
105 return;
106 }
107 } else {
108 if (!m_responseMessage->addContinuationPacket(data)) {
109 LOG_ERROR("Couldn't parse a hid continuation packet.");
110 returnMessage(WTF::nullopt);
111 return;
112 }
113 }
114
115 if (m_responseMessage->messageComplete()) {
116 // A KeepAlive cmd could be sent between a request and a response to indicate that
117 // the authenticator is waiting for user consent. Keep listening for the response.
118 if (m_responseMessage->cmd() == FidoHidDeviceCommand::kKeepAlive) {
119 m_responseMessage.reset();
120 return;
121 }
122 returnMessage(WTFMove(m_responseMessage));
123 return;
124 }
125}
126
127void CtapHidDriver::Worker::returnMessage(Optional<fido::FidoHidMessage>&& message)
128{
129 m_state = State::Idle;
130 m_connection->unregisterDataReceivedCallback();
131 m_callback(WTFMove(message));
132}
133
134CtapHidDriver::CtapHidDriver(UniqueRef<HidConnection>&& connection)
135 : m_worker(makeUniqueRef<Worker>(WTFMove(connection)))
136 , m_nonce(kHidInitNonceLength)
137{
138}
139
140void CtapHidDriver::transact(Vector<uint8_t>&& data, ResponseCallback&& callback)
141{
142 ASSERT(m_state == State::Idle);
143 m_state = State::AllocateChannel;
144 m_channelId = kHidBroadcastChannel;
145 m_requestData = WTFMove(data);
146 m_responseCallback = WTFMove(callback);
147
148 // Allocate a channel.
149 // Use a pseudo random nonce instead of a cryptographically strong one as the nonce
150 // is mainly for identifications.
151 size_t steps = kHidInitNonceLength / sizeof(uint32_t);
152 ASSERT(!(kHidInitNonceLength % sizeof(uint32_t)) && steps >= 1);
153 for (size_t i = 0; i < steps; ++i) {
154 uint32_t weakRandom = weakRandomUint32();
155 memcpy(m_nonce.data() + i * sizeof(uint32_t), &weakRandom, sizeof(uint32_t));
156 }
157
158 auto initCommand = FidoHidMessage::create(m_channelId, FidoHidDeviceCommand::kInit, m_nonce);
159 ASSERT(initCommand);
160 m_worker->transact(WTFMove(*initCommand), [weakThis = makeWeakPtr(*this)](Optional<FidoHidMessage>&& response) mutable {
161 ASSERT(RunLoop::isMain());
162 if (!weakThis)
163 return;
164 weakThis->continueAfterChannelAllocated(WTFMove(response));
165 });
166}
167
168void CtapHidDriver::continueAfterChannelAllocated(Optional<FidoHidMessage>&& message)
169{
170 ASSERT(m_state == State::AllocateChannel);
171 if (!message) {
172 returnResponse({ });
173 return;
174 }
175 ASSERT(message->channelId() == m_channelId);
176
177 auto payload = message->getMessagePayload();
178 ASSERT(payload.size() == kHidInitResponseSize);
179 // Restart the transaction in the next run loop when nonce mismatches.
180 if (memcmp(payload.data(), m_nonce.data(), m_nonce.size())) {
181 m_state = State::Idle;
182 RunLoop::main().dispatch([weakThis = makeWeakPtr(*this), data = WTFMove(m_requestData), callback = WTFMove(m_responseCallback)]() mutable {
183 if (!weakThis)
184 return;
185 weakThis->transact(WTFMove(data), WTFMove(callback));
186 });
187 return;
188 }
189
190 m_state = State::Ready;
191 auto index = kHidInitNonceLength;
192 m_channelId = static_cast<uint32_t>(payload[index++]) << 24;
193 m_channelId |= static_cast<uint32_t>(payload[index++]) << 16;
194 m_channelId |= static_cast<uint32_t>(payload[index++]) << 8;
195 m_channelId |= static_cast<uint32_t>(payload[index]);
196 // FIXME(191534): Check the reset of the payload.
197 auto cmd = FidoHidMessage::create(m_channelId, m_protocol == ProtocolVersion::kCtap ? FidoHidDeviceCommand::kCbor : FidoHidDeviceCommand::kMsg, m_requestData);
198 ASSERT(cmd);
199 m_worker->transact(WTFMove(*cmd), [weakThis = makeWeakPtr(*this)](Optional<FidoHidMessage>&& response) mutable {
200 ASSERT(RunLoop::isMain());
201 if (!weakThis)
202 return;
203 weakThis->continueAfterResponseReceived(WTFMove(response));
204 });
205}
206
207void CtapHidDriver::continueAfterResponseReceived(Optional<fido::FidoHidMessage>&& message)
208{
209 ASSERT(m_state == State::Ready);
210 ASSERT(!message || message->channelId() == m_channelId);
211 returnResponse(message ? message->getMessagePayload() : Vector<uint8_t>());
212}
213
214void CtapHidDriver::returnResponse(Vector<uint8_t>&& response)
215{
216 // Reset state before calling the response callback to avoid being deleted.
217 m_state = State::Idle;
218 m_responseCallback(WTFMove(response));
219}
220
221} // namespace WebKit
222
223#endif // ENABLE(WEB_AUTHN) && PLATFORM(MAC)
224