[WebAuthN] Support CTAP HID authenticators on macOS
[WebKit-https.git] / Source / WebCore / Modules / webauthn / fido / DeviceResponseConverter.cpp
1 // Copyright 2018 The Chromium Authors. All rights reserved.
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 are
6 // met:
7 //
8 //    * Redistributions of source code must retain the above copyright
9 // notice, this list of conditions and the following disclaimer.
10 //    * Redistributions in binary form must reproduce the above
11 // copyright notice, this list of conditions and the following disclaimer
12 // in the documentation and/or other materials provided with the
13 // distribution.
14 //    * Neither the name of Google Inc. nor the names of its
15 // contributors may be used to endorse or promote products derived from
16 // this software without specific prior written permission.
17 //
18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30 #include "config.h"
31 #include "DeviceResponseConverter.h"
32
33 #if ENABLE(WEB_AUTHN)
34
35 #include "AuthenticatorSupportedOptions.h"
36 #include "CBORReader.h"
37 #include "CBORWriter.h"
38 #include <wtf/StdSet.h>
39 #include <wtf/Vector.h>
40
41 namespace fido {
42 using namespace WebCore;
43 using CBOR = cbor::CBORValue;
44
45 constexpr size_t kResponseCodeLength = 1;
46
47 static ProtocolVersion convertStringToProtocolVersion(const String& version)
48 {
49     if (version == kCtap2Version)
50         return ProtocolVersion::kCtap;
51     if (version == kU2fVersion)
52         return ProtocolVersion::kU2f;
53
54     return ProtocolVersion::kUnknown;
55 }
56
57 CtapDeviceResponseCode getResponseCode(const Vector<uint8_t>& buffer)
58 {
59     if (buffer.isEmpty())
60         return CtapDeviceResponseCode::kCtap2ErrInvalidCBOR;
61
62     auto code = static_cast<CtapDeviceResponseCode>(buffer[0]);
63     return isCtapDeviceResponseCode(code) ? code : CtapDeviceResponseCode::kCtap2ErrInvalidCBOR;
64 }
65
66 static Vector<uint8_t> getCredentialId(const Vector<uint8_t>& authenticatorData)
67 {
68     const size_t credentialIdLengthOffset = kRpIdHashLength + kFlagsLength + kSignCounterLength + kAaguidLength;
69
70     if (authenticatorData.size() < credentialIdLengthOffset + kCredentialIdLengthLength)
71         return { };
72     size_t credentialIdLength = (static_cast<size_t>(authenticatorData[credentialIdLengthOffset]) << 8) | static_cast<size_t>(authenticatorData[credentialIdLengthOffset + 1]);
73
74     if (authenticatorData.size() < credentialIdLengthOffset + kCredentialIdLengthLength + credentialIdLength)
75         return { };
76     Vector<uint8_t> credentialId;
77     credentialId.reserveInitialCapacity(credentialIdLength);
78     auto beginIt = authenticatorData.begin() + credentialIdLengthOffset + kCredentialIdLengthLength;
79     credentialId.appendRange(beginIt, beginIt + credentialIdLength);
80     return credentialId;
81 }
82
83
84 // Decodes byte array response from authenticator to CBOR value object and
85 // checks for correct encoding format.
86 std::optional<PublicKeyCredentialData> readCTAPMakeCredentialResponse(const Vector<uint8_t>& inBuffer)
87 {
88     if (inBuffer.size() <= kResponseCodeLength)
89         return std::nullopt;
90
91     Vector<uint8_t> buffer;
92     buffer.append(inBuffer.data() + 1, inBuffer.size() - 1);
93     std::optional<CBOR> decodedResponse = cbor::CBORReader::read(buffer);
94     if (!decodedResponse || !decodedResponse->isMap())
95         return std::nullopt;
96     const auto& decodedMap = decodedResponse->getMap();
97
98     auto it = decodedMap.find(CBOR(1));
99     if (it == decodedMap.end() || !it->second.isString())
100         return std::nullopt;
101     auto format = it->second.clone();
102
103     it = decodedMap.find(CBOR(2));
104     if (it == decodedMap.end() || !it->second.isByteString())
105         return std::nullopt;
106     auto authenticatorData = it->second.clone();
107
108     auto credentialId = getCredentialId(authenticatorData.getByteString());
109     if (credentialId.isEmpty())
110         return std::nullopt;
111
112     it = decodedMap.find(CBOR(3));
113     if (it == decodedMap.end() || !it->second.isMap())
114         return std::nullopt;
115     auto attStmt = it->second.clone();
116
117     CBOR::MapValue attestationObjectMap;
118     attestationObjectMap[CBOR("authData")] = WTFMove(authenticatorData);
119     attestationObjectMap[CBOR("fmt")] = WTFMove(format);
120     attestationObjectMap[CBOR("attStmt")] = WTFMove(attStmt);
121     auto attestationObject = cbor::CBORWriter::write(CBOR(WTFMove(attestationObjectMap)));
122
123     return PublicKeyCredentialData { ArrayBuffer::create(credentialId.data(), credentialId.size()), true, nullptr, ArrayBuffer::create(attestationObject.value().data(), attestationObject.value().size()), nullptr, nullptr, nullptr };
124 }
125
126 std::optional<PublicKeyCredentialData> readCTAPGetAssertionResponse(const Vector<uint8_t>& inBuffer)
127 {
128     if (inBuffer.size() <= kResponseCodeLength)
129         return std::nullopt;
130
131     Vector<uint8_t> buffer;
132     buffer.append(inBuffer.data() + 1, inBuffer.size() - 1);
133     std::optional<CBOR> decodedResponse = cbor::CBORReader::read(buffer);
134
135     if (!decodedResponse || !decodedResponse->isMap())
136         return std::nullopt;
137
138     auto& responseMap = decodedResponse->getMap();
139
140     RefPtr<ArrayBuffer> credentialId;
141     auto it = responseMap.find(CBOR(1));
142     if (it != responseMap.end() && it->second.isMap()) {
143         auto& credential = it->second.getMap();
144         auto itr = credential.find(CBOR(kCredentialIdKey));
145         if (itr == credential.end() || !itr->second.isByteString())
146             return std::nullopt;
147         auto& id = itr->second.getByteString();
148         credentialId = ArrayBuffer::create(id.data(), id.size());
149     }
150
151     it = responseMap.find(CBOR(2));
152     if (it == responseMap.end() || !it->second.isByteString())
153         return std::nullopt;
154     auto& authData = it->second.getByteString();
155
156     it = responseMap.find(CBOR(3));
157     if (it == responseMap.end() || !it->second.isByteString())
158         return std::nullopt;
159     auto& signature = it->second.getByteString();
160
161     // FIXME(191521): Properly handle null userHandle.
162     RefPtr<ArrayBuffer> userHandle = ArrayBuffer::create(1, 1);
163     it = responseMap.find(CBOR(4));
164     if (it != responseMap.end() && it->second.isMap()) {
165         auto& user = it->second.getMap();
166         auto itr = user.find(CBOR(kEntityIdMapKey));
167         if (itr == user.end() || !itr->second.isByteString())
168             return std::nullopt;
169         auto& id = itr->second.getByteString();
170         userHandle = ArrayBuffer::create(id.data(), id.size());
171     }
172
173     return PublicKeyCredentialData { WTFMove(credentialId), false, nullptr, nullptr, ArrayBuffer::create(authData.data(), authData.size()), ArrayBuffer::create(signature.data(), signature.size()), WTFMove(userHandle) };
174 }
175
176 std::optional<AuthenticatorGetInfoResponse> readCTAPGetInfoResponse(const Vector<uint8_t>& inBuffer)
177 {
178     if (inBuffer.size() <= kResponseCodeLength || getResponseCode(inBuffer) != CtapDeviceResponseCode::kSuccess)
179         return std::nullopt;
180
181     Vector<uint8_t> buffer;
182     buffer.append(inBuffer.data() + 1, inBuffer.size() - 1);
183     std::optional<CBOR> decodedResponse = cbor::CBORReader::read(buffer);
184     if (!decodedResponse || !decodedResponse->isMap())
185         return std::nullopt;
186     const auto& responseMap = decodedResponse->getMap();
187
188     auto it = responseMap.find(CBOR(1));
189     if (it == responseMap.end() || !it->second.isArray() || it->second.getArray().size() > 2)
190         return std::nullopt;
191     StdSet<ProtocolVersion> protocolVersions;
192     for (const auto& version : it->second.getArray()) {
193         if (!version.isString())
194             return std::nullopt;
195
196         auto protocol = convertStringToProtocolVersion(version.getString());
197         if (protocol == ProtocolVersion::kUnknown) {
198             LOG_ERROR("Unexpected protocol version received.");
199             continue;
200         }
201
202         if (!protocolVersions.insert(protocol).second)
203             return std::nullopt;
204     }
205     if (protocolVersions.empty())
206         return std::nullopt;
207
208     it = responseMap.find(CBOR(3));
209     if (it == responseMap.end() || !it->second.isByteString() || it->second.getByteString().size() != kAaguidLength)
210         return std::nullopt;
211
212     AuthenticatorGetInfoResponse response(WTFMove(protocolVersions), Vector<uint8_t>(it->second.getByteString()));
213
214     it = responseMap.find(CBOR(2));
215     if (it != responseMap.end()) {
216         if (!it->second.isArray())
217             return std::nullopt;
218
219         Vector<String> extensions;
220         for (const auto& extension : it->second.getArray()) {
221             if (!extension.isString())
222                 return std::nullopt;
223
224             extensions.append(extension.getString());
225         }
226         response.setExtensions(WTFMove(extensions));
227     }
228
229     AuthenticatorSupportedOptions options;
230     it = responseMap.find(CBOR(4));
231     if (it != responseMap.end()) {
232         if (!it->second.isMap())
233             return std::nullopt;
234         const auto& optionMap = it->second.getMap();
235         auto optionMapIt = optionMap.find(CBOR(kPlatformDeviceMapKey));
236         if (optionMapIt != optionMap.end()) {
237             if (!optionMapIt->second.isBool())
238                 return std::nullopt;
239
240             options.setIsPlatformDevice(optionMapIt->second.getBool());
241         }
242
243         optionMapIt = optionMap.find(CBOR(kResidentKeyMapKey));
244         if (optionMapIt != optionMap.end()) {
245             if (!optionMapIt->second.isBool())
246                 return std::nullopt;
247
248             options.setSupportsResidentKey(optionMapIt->second.getBool());
249         }
250
251         optionMapIt = optionMap.find(CBOR(kUserPresenceMapKey));
252         if (optionMapIt != optionMap.end()) {
253             if (!optionMapIt->second.isBool())
254                 return std::nullopt;
255
256             options.setUserPresenceRequired(optionMapIt->second.getBool());
257         }
258
259         optionMapIt = optionMap.find(CBOR(kUserVerificationMapKey));
260         if (optionMapIt != optionMap.end()) {
261             if (!optionMapIt->second.isBool())
262                 return std::nullopt;
263
264             if (optionMapIt->second.getBool())
265                 options.setUserVerificationAvailability(AuthenticatorSupportedOptions::UserVerificationAvailability::kSupportedAndConfigured);
266             else
267                 options.setUserVerificationAvailability(AuthenticatorSupportedOptions::UserVerificationAvailability::kSupportedButNotConfigured);
268         }
269
270         optionMapIt = optionMap.find(CBOR(kClientPinMapKey));
271         if (optionMapIt != optionMap.end()) {
272             if (!optionMapIt->second.isBool())
273                 return std::nullopt;
274
275             if (optionMapIt->second.getBool())
276                 options.setClientPinAvailability(AuthenticatorSupportedOptions::ClientPinAvailability::kSupportedAndPinSet);
277             else
278                 options.setClientPinAvailability(AuthenticatorSupportedOptions::ClientPinAvailability::kSupportedButPinNotSet);
279         }
280         response.setOptions(WTFMove(options));
281     }
282
283     it = responseMap.find(CBOR(5));
284     if (it != responseMap.end()) {
285         if (!it->second.isUnsigned())
286             return std::nullopt;
287
288         response.setMaxMsgSize(it->second.getUnsigned());
289     }
290
291     it = responseMap.find(CBOR(6));
292     if (it != responseMap.end()) {
293         if (!it->second.isArray())
294             return std::nullopt;
295
296         Vector<uint8_t> supportedPinProtocols;
297         for (const auto& protocol : it->second.getArray()) {
298             if (!protocol.isUnsigned())
299                 return std::nullopt;
300
301             supportedPinProtocols.append(protocol.getUnsigned());
302         }
303         response.setPinProtocols(WTFMove(supportedPinProtocols));
304     }
305
306     return WTFMove(response);
307 }
308
309 } // namespace fido
310
311 #endif // ENABLE(WEB_AUTHN)