Introduce makeBlockPtr for lambdas
[WebKit-https.git] / Source / WebKit / UIProcess / WebAuthentication / Mock / MockHidConnection.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 "MockHidConnection.h"
28
29 #if ENABLE(WEB_AUTHN) && PLATFORM(MAC)
30
31 #include <WebCore/AuthenticatorGetInfoResponse.h>
32 #include <WebCore/CBORReader.h>
33 #include <WebCore/FidoConstants.h>
34 #include <wtf/BlockPtr.h>
35 #include <wtf/CryptographicallyRandomNumber.h>
36 #include <wtf/RunLoop.h>
37 #include <wtf/text/Base64.h>
38
39 namespace WebKit {
40 using Mock = MockWebAuthenticationConfiguration::Hid;
41 using namespace WebCore;
42 using namespace cbor;
43 using namespace fido;
44
45 namespace MockHidConnectionInternal {
46 // https://fidoalliance.org/specs/fido-v2.0-ps-20170927/fido-client-to-authenticator-protocol-v2.0-ps-20170927.html#mandatory-commands
47 const size_t CtapChannelIdSize = 4;
48 const uint8_t CtapKeepAliveStatusProcessing = 1;
49 // https://fidoalliance.org/specs/fido-v2.0-ps-20170927/fido-client-to-authenticator-protocol-v2.0-ps-20170927.html#commands
50 const int64_t CtapMakeCredentialRequestOptionsKey = 7;
51 const int64_t CtapGetAssertionRequestOptionsKey = 5;
52 }
53
54 MockHidConnection::MockHidConnection(IOHIDDeviceRef device, const MockWebAuthenticationConfiguration& configuration)
55     : HidConnection(device)
56     , m_configuration(configuration)
57 {
58 }
59
60 void MockHidConnection::initialize()
61 {
62     m_initialized = true;
63 }
64
65 void MockHidConnection::terminate()
66 {
67     m_terminated = true;
68 }
69
70 void MockHidConnection::send(Vector<uint8_t>&& data, DataSentCallback&& callback)
71 {
72     ASSERT(m_initialized);
73     auto task = makeBlockPtr([weakThis = makeWeakPtr(*this), data = WTFMove(data), callback = WTFMove(callback)]() mutable {
74         ASSERT(!RunLoop::isMain());
75         RunLoop::main().dispatch([weakThis, data = WTFMove(data), callback = WTFMove(callback)]() mutable {
76             if (!weakThis) {
77                 callback(DataSent::No);
78                 return;
79             }
80
81             weakThis->assembleRequest(WTFMove(data));
82
83             auto sent = DataSent::Yes;
84             if (weakThis->stagesMatch() && weakThis->m_configuration.hid->error == Mock::Error::DataNotSent)
85                 sent = DataSent::No;
86             callback(sent);
87         });
88     });
89     dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), task.get());
90 }
91
92 void MockHidConnection::registerDataReceivedCallbackInternal()
93 {
94     if (stagesMatch() && m_configuration.hid->error == Mock::Error::EmptyReport) {
95         receiveReport({ });
96         shouldContinueFeedReports();
97         return;
98     }
99     if (!m_configuration.hid->fastDataArrival)
100         feedReports();
101 }
102
103 void MockHidConnection::assembleRequest(Vector<uint8_t>&& data)
104 {
105     if (!m_requestMessage) {
106         m_requestMessage = FidoHidMessage::createFromSerializedData(data);
107         ASSERT(m_requestMessage);
108     } else {
109         auto status = m_requestMessage->addContinuationPacket(data);
110         ASSERT_UNUSED(status, status);
111     }
112
113     if (m_requestMessage->messageComplete())
114         parseRequest();
115 }
116
117 void MockHidConnection::parseRequest()
118 {
119     using namespace MockHidConnectionInternal;
120
121     ASSERT(m_requestMessage);
122     // Set stages.
123     if (m_requestMessage->cmd() == FidoHidDeviceCommand::kInit) {
124         auto previousSubStage = m_subStage;
125         m_subStage = Mock::SubStage::Init;
126         if (previousSubStage == Mock::SubStage::Msg)
127             m_stage = Mock::Stage::Request;
128     }
129     if (m_requestMessage->cmd() == FidoHidDeviceCommand::kCbor)
130         m_subStage = Mock::SubStage::Msg;
131
132     // Set options.
133     if (m_stage == Mock::Stage::Request && m_subStage == Mock::SubStage::Msg) {
134         m_requireResidentKey = false;
135         m_requireUserVerification = false;
136
137         auto payload = m_requestMessage->getMessagePayload();
138         ASSERT(payload.size());
139         auto cmd = static_cast<CtapRequestCommand>(payload[0]);
140         payload.remove(0);
141         auto requestMap = CBORReader::read(payload);
142         ASSERT(requestMap);
143
144         if (cmd == CtapRequestCommand::kAuthenticatorMakeCredential) {
145             auto it = requestMap->getMap().find(CBORValue(CtapMakeCredentialRequestOptionsKey)); // Find options.
146             if (it != requestMap->getMap().end()) {
147                 auto& optionMap = it->second.getMap();
148
149                 auto itr = optionMap.find(CBORValue(kResidentKeyMapKey));
150                 if (itr != optionMap.end())
151                     m_requireResidentKey = itr->second.getBool();
152
153                 itr = optionMap.find(CBORValue(kUserVerificationMapKey));
154                 if (itr != optionMap.end())
155                     m_requireUserVerification = itr->second.getBool();
156             }
157         }
158
159         if (cmd == CtapRequestCommand::kAuthenticatorGetAssertion) {
160             auto it = requestMap->getMap().find(CBORValue(CtapGetAssertionRequestOptionsKey)); // Find options.
161             if (it != requestMap->getMap().end()) {
162                 auto& optionMap = it->second.getMap();
163                 auto itr = optionMap.find(CBORValue(kUserVerificationMapKey));
164                 if (itr != optionMap.end())
165                     m_requireUserVerification = itr->second.getBool();
166             }
167         }
168     }
169
170     // Store nonce.
171     if (m_subStage == Mock::SubStage::Init) {
172         m_nonce = m_requestMessage->getMessagePayload();
173         ASSERT(m_nonce.size() == kHidInitNonceLength);
174     }
175
176     m_currentChannel = m_requestMessage->channelId();
177     m_requestMessage = std::nullopt;
178     if (m_configuration.hid->fastDataArrival)
179         feedReports();
180 }
181
182 void MockHidConnection::feedReports()
183 {
184     using namespace MockHidConnectionInternal;
185
186     if (m_subStage == Mock::SubStage::Init) {
187         Vector<uint8_t> payload;
188         payload.reserveInitialCapacity(kHidInitResponseSize);
189         payload.appendVector(m_nonce);
190         if (stagesMatch() && m_configuration.hid->error == Mock::Error::WrongNonce)
191             payload[0]--;
192         payload.grow(kHidInitResponseSize);
193         cryptographicallyRandomValues(payload.data() + payload.size(), CtapChannelIdSize);
194         auto channel = kHidBroadcastChannel;
195         if (stagesMatch() && m_configuration.hid->error == Mock::Error::WrongChannelId)
196             channel--;
197         FidoHidInitPacket initPacket(channel, FidoHidDeviceCommand::kInit, WTFMove(payload), payload.size());
198         receiveReport(initPacket.getSerializedData());
199         shouldContinueFeedReports();
200         return;
201     }
202
203     std::optional<FidoHidMessage> message;
204     if (m_stage == Mock::Stage::Info && m_subStage == Mock::SubStage::Msg) {
205         auto infoData = encodeAsCBOR(AuthenticatorGetInfoResponse({ ProtocolVersion::kCtap }, Vector<uint8_t>(kAaguidLength, 0u)));
206         infoData.insert(0, static_cast<uint8_t>(CtapDeviceResponseCode::kSuccess)); // Prepend status code.
207         if (stagesMatch() && m_configuration.hid->error == Mock::Error::WrongChannelId)
208             message = FidoHidMessage::create(m_currentChannel - 1, FidoHidDeviceCommand::kCbor, infoData);
209         else
210             message = FidoHidMessage::create(m_currentChannel, FidoHidDeviceCommand::kCbor, infoData);
211     }
212
213     if (m_stage == Mock::Stage::Request && m_subStage == Mock::SubStage::Msg) {
214         if (m_configuration.hid->keepAlive) {
215             m_configuration.hid->keepAlive = false;
216             FidoHidInitPacket initPacket(m_currentChannel, FidoHidDeviceCommand::kKeepAlive, { CtapKeepAliveStatusProcessing }, 1);
217             receiveReport(initPacket.getSerializedData());
218             continueFeedReports();
219             return;
220         }
221         if (stagesMatch() && m_configuration.hid->error == Mock::Error::UnsupportedOptions && (m_requireResidentKey || m_requireUserVerification))
222             message = FidoHidMessage::create(m_currentChannel, FidoHidDeviceCommand::kCbor, { static_cast<uint8_t>(CtapDeviceResponseCode::kCtap2ErrUnsupportedOption) });
223         else {
224             Vector<uint8_t> payload;
225             auto status = base64Decode(m_configuration.hid->payloadBase64, payload);
226             ASSERT_UNUSED(status, status);
227             message = FidoHidMessage::create(m_currentChannel, FidoHidDeviceCommand::kCbor, payload);
228         }
229     }
230
231     ASSERT(message);
232     bool isFirst = true;
233     while (message->numPackets()) {
234         auto report = message->popNextPacket();
235         if (!isFirst && stagesMatch() && m_configuration.hid->error == Mock::Error::WrongChannelId)
236             report = FidoHidContinuationPacket(m_currentChannel - 1, 0, { }).getSerializedData();
237         // Packets are feed asynchronously to mimic actual data transmission.
238         RunLoop::main().dispatch([report = WTFMove(report), weakThis = makeWeakPtr(*this)]() mutable {
239             if (!weakThis)
240                 return;
241             weakThis->receiveReport(WTFMove(report));
242         });
243         isFirst = false;
244     }
245 }
246
247 bool MockHidConnection::stagesMatch() const
248 {
249     return m_configuration.hid->stage == m_stage && m_configuration.hid->subStage == m_subStage;
250 }
251
252 void MockHidConnection::shouldContinueFeedReports()
253 {
254     if (!m_configuration.hid->continueAfterErrorData)
255         return;
256     m_configuration.hid->continueAfterErrorData = false;
257     m_configuration.hid->error = Mock::Error::Success;
258     continueFeedReports();
259 }
260
261 void MockHidConnection::continueFeedReports()
262 {
263     // Send actual response for the next run.
264     RunLoop::main().dispatch([weakThis = makeWeakPtr(*this)]() mutable {
265         if (!weakThis)
266             return;
267         weakThis->feedReports();
268     });
269 }
270
271 } // namespace WebKit
272
273 #endif // ENABLE(WEB_AUTHN) && PLATFORM(MAC)