[WebAuthN] Support CTAP HID authenticators on macOS
[WebKit-https.git] / Source / WebKit / UIProcess / WebAuthentication / fido / CtapHidDriver.cpp
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/RunLoop.h>
33 #include <wtf/text/Base64.h>
34
35 namespace WebKit {
36 using namespace fido;
37
38 CtapHidDriver::Worker::Worker(UniqueRef<HidConnection>&& connection)
39     : m_connection(WTFMove(connection))
40 {
41     m_connection->initialize();
42 }
43
44 CtapHidDriver::Worker::~Worker()
45 {
46     m_connection->terminate();
47 }
48
49 void CtapHidDriver::Worker::transact(fido::FidoHidMessage&& requestMessage, MessageCallback&& callback)
50 {
51     ASSERT(m_state == State::Idle);
52     m_state = State::Write;
53     m_requestMessage = WTFMove(requestMessage);
54     m_responseMessage.reset();
55     m_callback = WTFMove(callback);
56
57     m_connection->send(m_requestMessage->popNextPacket(), [weakThis = makeWeakPtr(*this)](HidConnection::DataSent sent) mutable {
58         ASSERT(RunLoop::isMain());
59         if (!weakThis)
60             return;
61         weakThis->write(sent);
62     });
63 }
64
65 void CtapHidDriver::Worker::write(HidConnection::DataSent sent)
66 {
67     ASSERT(m_state == State::Write);
68     if (sent != HidConnection::DataSent::Yes) {
69         returnMessage(std::nullopt);
70         return;
71     }
72
73     if (!m_requestMessage->numPackets()) {
74         m_state = State::Read;
75         m_connection->registerDataReceivedCallback([weakThis = makeWeakPtr(*this)](Vector<uint8_t>&& data) mutable {
76             ASSERT(RunLoop::isMain());
77             if (!weakThis)
78                 return;
79             weakThis->read(data);
80         });
81         return;
82     }
83
84     m_connection->send(m_requestMessage->popNextPacket(), [weakThis = makeWeakPtr(*this)](HidConnection::DataSent sent) mutable {
85         ASSERT(RunLoop::isMain());
86         if (!weakThis)
87             return;
88         weakThis->write(sent);
89     });
90 }
91
92 void CtapHidDriver::Worker::read(const Vector<uint8_t>& data)
93 {
94     ASSERT(m_state == State::Read);
95     if (!m_responseMessage) {
96         m_responseMessage = FidoHidMessage::createFromSerializedData(data);
97         // The first few reports could be for other applications, and therefore ignore those.
98         if (!m_responseMessage || m_responseMessage->channelId() != m_requestMessage->channelId()) {
99             LOG_ERROR("Couldn't parse a hid init packet: %s", m_responseMessage ? "wrong channel id." : "bad data.");
100             m_responseMessage.reset();
101             return;
102         }
103     } else {
104         if (!m_responseMessage->addContinuationPacket(data)) {
105             LOG_ERROR("Couldn't parse a hid continuation packet.");
106             returnMessage(std::nullopt);
107             return;
108         }
109     }
110
111     if (m_responseMessage->messageComplete()) {
112         // A KeepAlive cmd could be sent between a request and a response to indicate that
113         // the authenticator is waiting for user consent. Keep listening for the response.
114         if (m_responseMessage->cmd() == FidoHidDeviceCommand::kKeepAlive) {
115             m_responseMessage.reset();
116             return;
117         }
118         returnMessage(WTFMove(m_responseMessage));
119         return;
120     }
121 }
122
123 void CtapHidDriver::Worker::returnMessage(std::optional<fido::FidoHidMessage>&& message)
124 {
125     m_state = State::Idle;
126     m_connection->unregisterDataReceivedCallback();
127     m_callback(WTFMove(message));
128 }
129
130 CtapHidDriver::CtapHidDriver(UniqueRef<HidConnection>&& connection)
131     : m_worker(makeUniqueRef<Worker>(WTFMove(connection)))
132 {
133 }
134
135 void CtapHidDriver::transact(Vector<uint8_t>&& data, ResponseCallback&& callback)
136 {
137     ASSERT(m_state == State::Idle);
138     m_state = State::AllocateChannel;
139     m_requestData = WTFMove(data);
140     m_responseCallback = WTFMove(callback);
141
142     // Allocate a channel.
143     // FIXME(191533): Get a real nonce.
144     auto initCommand = FidoHidMessage::create(kHidBroadcastChannel, FidoHidDeviceCommand::kInit, { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 });
145     ASSERT(initCommand);
146     m_worker->transact(WTFMove(*initCommand), [weakThis = makeWeakPtr(*this)](std::optional<FidoHidMessage>&& response) mutable {
147         ASSERT(RunLoop::isMain());
148         if (!weakThis)
149             return;
150         weakThis->continueAfterChannelAllocated(WTFMove(response));
151     });
152 }
153
154 void CtapHidDriver::continueAfterChannelAllocated(std::optional<FidoHidMessage>&& message)
155 {
156     ASSERT(m_state == State::AllocateChannel);
157     if (!message) {
158         returnResponse({ });
159         return;
160     }
161     m_state = State::Ready;
162     ASSERT(message->channelId() == m_channelId);
163
164     // FIXME(191534): Check Payload including nonce and everything
165     auto payload = message->getMessagePayload();
166     size_t index = 8;
167     ASSERT(payload.size() == kHidInitResponseSize);
168     m_channelId = static_cast<uint32_t>(payload[index++]) << 24;
169     m_channelId |= static_cast<uint32_t>(payload[index++]) << 16;
170     m_channelId |= static_cast<uint32_t>(payload[index++]) << 8;
171     m_channelId |= static_cast<uint32_t>(payload[index]);
172     auto cmd = FidoHidMessage::create(m_channelId, FidoHidDeviceCommand::kCbor, m_requestData);
173     ASSERT(cmd);
174     m_worker->transact(WTFMove(*cmd), [weakThis = makeWeakPtr(*this)](std::optional<FidoHidMessage>&& response) mutable {
175         ASSERT(RunLoop::isMain());
176         if (!weakThis)
177             return;
178         weakThis->continueAfterResponseReceived(WTFMove(response));
179     });
180 }
181
182 void CtapHidDriver::continueAfterResponseReceived(std::optional<fido::FidoHidMessage>&& message)
183 {
184     ASSERT(m_state == State::Ready);
185     ASSERT(!message || message->channelId() == m_channelId);
186     returnResponse(message ? message->getMessagePayload() : Vector<uint8_t>());
187 }
188
189 void CtapHidDriver::returnResponse(Vector<uint8_t>&& response)
190 {
191     // Reset state before calling the response callback to avoid being deleted.
192     m_state = State::Idle;
193     m_channelId = fido::kHidBroadcastChannel;
194     m_requestData = { };
195     m_responseCallback(WTFMove(response));
196 }
197
198 } // namespace WebKit
199
200 #endif // ENABLE(WEB_AUTHN) && PLATFORM(MAC)