1 | /* |
2 | * Copyright (C) 2019 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 "U2fHidAuthenticator.h" |
28 | |
29 | #if ENABLE(WEB_AUTHN) && PLATFORM(MAC) |
30 | |
31 | #include "CtapHidDriver.h" |
32 | #include <WebCore/ApduResponse.h> |
33 | #include <WebCore/ExceptionData.h> |
34 | #include <WebCore/U2fCommandConstructor.h> |
35 | #include <WebCore/U2fResponseConverter.h> |
36 | #include <wtf/RunLoop.h> |
37 | #include <wtf/text/StringConcatenateNumbers.h> |
38 | |
39 | namespace WebKit { |
40 | using namespace WebCore; |
41 | using namespace apdu; |
42 | using namespace fido; |
43 | |
44 | namespace { |
45 | const unsigned retryTimeOutValueMs = 200; |
46 | } |
47 | |
48 | U2fHidAuthenticator::U2fHidAuthenticator(std::unique_ptr<CtapHidDriver>&& driver) |
49 | : m_driver(WTFMove(driver)) |
50 | , m_retryTimer(RunLoop::main(), this, &U2fHidAuthenticator::retryLastCommand) |
51 | { |
52 | // FIXME(191520): We need a way to convert std::unique_ptr to UniqueRef. |
53 | ASSERT(m_driver); |
54 | } |
55 | |
56 | void U2fHidAuthenticator::makeCredential() |
57 | { |
58 | if (!isConvertibleToU2fRegisterCommand(requestData().creationOptions)) { |
59 | receiveRespond(ExceptionData { NotSupportedError, "Cannot convert the request to U2F command."_s }); |
60 | return; |
61 | } |
62 | if (!requestData().creationOptions.excludeCredentials.isEmpty()) { |
63 | ASSERT(!m_nextListIndex); |
64 | checkExcludeList(m_nextListIndex++); |
65 | return; |
66 | } |
67 | issueRegisterCommand(); |
68 | } |
69 | |
70 | void U2fHidAuthenticator::checkExcludeList(size_t index) |
71 | { |
72 | if (index >= requestData().creationOptions.excludeCredentials.size()) { |
73 | issueRegisterCommand(); |
74 | return; |
75 | } |
76 | auto u2fCmd = convertToU2fCheckOnlySignCommand(requestData().hash, requestData().creationOptions, requestData().creationOptions.excludeCredentials[index]); |
77 | ASSERT(u2fCmd); |
78 | issueNewCommand(WTFMove(*u2fCmd), CommandType::CheckOnlyCommand); |
79 | } |
80 | |
81 | void U2fHidAuthenticator::issueRegisterCommand() |
82 | { |
83 | auto u2fCmd = convertToU2fRegisterCommand(requestData().hash, requestData().creationOptions); |
84 | ASSERT(u2fCmd); |
85 | issueNewCommand(WTFMove(*u2fCmd), CommandType::RegisterCommand); |
86 | } |
87 | |
88 | void U2fHidAuthenticator::getAssertion() |
89 | { |
90 | if (!isConvertibleToU2fSignCommand(requestData().requestOptions)) { |
91 | receiveRespond(ExceptionData { NotSupportedError, "Cannot convert the request to U2F command."_s }); |
92 | return; |
93 | } |
94 | ASSERT(!m_nextListIndex); |
95 | issueSignCommand(m_nextListIndex++); |
96 | } |
97 | |
98 | void U2fHidAuthenticator::issueSignCommand(size_t index) |
99 | { |
100 | if (index >= requestData().requestOptions.allowCredentials.size()) { |
101 | receiveRespond(ExceptionData { NotAllowedError, "No credentials from the allowCredentials list is found in the authenticator."_s }); |
102 | return; |
103 | } |
104 | auto u2fCmd = convertToU2fSignCommand(requestData().hash, requestData().requestOptions, requestData().requestOptions.allowCredentials[index].idVector, m_isAppId); |
105 | ASSERT(u2fCmd); |
106 | issueNewCommand(WTFMove(*u2fCmd), CommandType::SignCommand); |
107 | } |
108 | |
109 | void U2fHidAuthenticator::issueNewCommand(Vector<uint8_t>&& command, CommandType type) |
110 | { |
111 | m_lastCommand = WTFMove(command); |
112 | m_lastCommandType = type; |
113 | issueCommand(m_lastCommand, m_lastCommandType); |
114 | } |
115 | |
116 | void U2fHidAuthenticator::issueCommand(const Vector<uint8_t>& command, CommandType type) |
117 | { |
118 | m_driver->transact(Vector<uint8_t>(command), [weakThis = makeWeakPtr(*this), type](Vector<uint8_t>&& data) { |
119 | ASSERT(RunLoop::isMain()); |
120 | if (!weakThis) |
121 | return; |
122 | weakThis->responseReceived(WTFMove(data), type); |
123 | }); |
124 | } |
125 | |
126 | void U2fHidAuthenticator::responseReceived(Vector<uint8_t>&& response, CommandType type) |
127 | { |
128 | auto apduResponse = ApduResponse::createFromMessage(response); |
129 | if (!apduResponse) { |
130 | receiveRespond(ExceptionData { UnknownError, "Couldn't parse the APDU response."_s }); |
131 | return; |
132 | } |
133 | |
134 | switch (type) { |
135 | case CommandType::RegisterCommand: |
136 | continueRegisterCommandAfterResponseReceived(WTFMove(*apduResponse)); |
137 | return; |
138 | case CommandType::CheckOnlyCommand: |
139 | continueCheckOnlyCommandAfterResponseReceived(WTFMove(*apduResponse)); |
140 | return; |
141 | case CommandType::BogusCommand: |
142 | continueBogusCommandAfterResponseReceived(WTFMove(*apduResponse)); |
143 | return; |
144 | case CommandType::SignCommand: |
145 | continueSignCommandAfterResponseReceived(WTFMove(*apduResponse)); |
146 | return; |
147 | } |
148 | ASSERT_NOT_REACHED(); |
149 | } |
150 | |
151 | void U2fHidAuthenticator::continueRegisterCommandAfterResponseReceived(ApduResponse&& apduResponse) |
152 | { |
153 | switch (apduResponse.status()) { |
154 | case ApduResponse::Status::SW_NO_ERROR: { |
155 | auto response = readU2fRegisterResponse(requestData().creationOptions.rp.id, apduResponse.data(), requestData().creationOptions.attestation); |
156 | if (!response) { |
157 | receiveRespond(ExceptionData { UnknownError, "Couldn't parse the U2F register response."_s }); |
158 | return; |
159 | } |
160 | receiveRespond(WTFMove(*response)); |
161 | return; |
162 | } |
163 | case ApduResponse::Status::SW_CONDITIONS_NOT_SATISFIED: |
164 | // Polling is required during test of user presence. |
165 | m_retryTimer.startOneShot(Seconds::fromMilliseconds(retryTimeOutValueMs)); |
166 | return; |
167 | default: |
168 | receiveRespond(ExceptionData { UnknownError, makeString("Unknown internal error. Error code: " , static_cast<unsigned>(apduResponse.status())) }); |
169 | } |
170 | } |
171 | |
172 | void U2fHidAuthenticator::continueCheckOnlyCommandAfterResponseReceived(ApduResponse&& apduResponse) |
173 | { |
174 | switch (apduResponse.status()) { |
175 | case ApduResponse::Status::SW_NO_ERROR: |
176 | case ApduResponse::Status::SW_CONDITIONS_NOT_SATISFIED: |
177 | issueNewCommand(constructBogusU2fRegistrationCommand(), CommandType::BogusCommand); |
178 | return; |
179 | default: |
180 | checkExcludeList(m_nextListIndex++); |
181 | } |
182 | } |
183 | |
184 | void U2fHidAuthenticator::continueBogusCommandAfterResponseReceived(ApduResponse&& apduResponse) |
185 | { |
186 | switch (apduResponse.status()) { |
187 | case ApduResponse::Status::SW_NO_ERROR: |
188 | receiveRespond(ExceptionData { InvalidStateError, "At least one credential matches an entry of the excludeCredentials list in the authenticator."_s }); |
189 | return; |
190 | case ApduResponse::Status::SW_CONDITIONS_NOT_SATISFIED: |
191 | // Polling is required during test of user presence. |
192 | m_retryTimer.startOneShot(Seconds::fromMilliseconds(retryTimeOutValueMs)); |
193 | return; |
194 | default: |
195 | receiveRespond(ExceptionData { UnknownError, makeString("Unknown internal error. Error code: " , static_cast<unsigned>(apduResponse.status())) }); |
196 | } |
197 | } |
198 | |
199 | void U2fHidAuthenticator::continueSignCommandAfterResponseReceived(ApduResponse&& apduResponse) |
200 | { |
201 | switch (apduResponse.status()) { |
202 | case ApduResponse::Status::SW_NO_ERROR: { |
203 | Optional<PublicKeyCredentialData> response; |
204 | if (m_isAppId) { |
205 | ASSERT(requestData().requestOptions.extensions && !requestData().requestOptions.extensions->appid.isNull()); |
206 | response = readU2fSignResponse(requestData().requestOptions.extensions->appid, requestData().requestOptions.allowCredentials[m_nextListIndex - 1].idVector, apduResponse.data()); |
207 | } else |
208 | response = readU2fSignResponse(requestData().requestOptions.rpId, requestData().requestOptions.allowCredentials[m_nextListIndex - 1].idVector, apduResponse.data()); |
209 | if (!response) { |
210 | receiveRespond(ExceptionData { UnknownError, "Couldn't parse the U2F sign response."_s }); |
211 | return; |
212 | } |
213 | if (m_isAppId) |
214 | response->appid = m_isAppId; |
215 | |
216 | receiveRespond(WTFMove(*response)); |
217 | return; |
218 | } |
219 | case ApduResponse::Status::SW_CONDITIONS_NOT_SATISFIED: |
220 | // Polling is required during test of user presence. |
221 | m_retryTimer.startOneShot(Seconds::fromMilliseconds(retryTimeOutValueMs)); |
222 | return; |
223 | case ApduResponse::Status::SW_WRONG_DATA: |
224 | if (requestData().requestOptions.extensions && !requestData().requestOptions.extensions->appid.isNull()) { |
225 | if (!m_isAppId) { |
226 | m_isAppId = true; |
227 | issueSignCommand(m_nextListIndex - 1); |
228 | return; |
229 | } |
230 | m_isAppId = false; |
231 | } |
232 | issueSignCommand(m_nextListIndex++); |
233 | return; |
234 | default: |
235 | issueSignCommand(m_nextListIndex++); |
236 | } |
237 | } |
238 | |
239 | } // namespace WebKit |
240 | |
241 | #endif // ENABLE(WEB_AUTHN) && PLATFORM(MAC) |
242 | |