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 "WebAuthenticationConstants.h"
39#include <wtf/StdSet.h>
40#include <wtf/Vector.h>
41
42namespace fido {
43using namespace WebCore;
44using CBOR = cbor::CBORValue;
45
46constexpr size_t kResponseCodeLength = 1;
47
48static ProtocolVersion convertStringToProtocolVersion(const String& version)
49{
50 if (version == kCtap2Version)
51 return ProtocolVersion::kCtap;
52 if (version == kU2fVersion)
53 return ProtocolVersion::kU2f;
54
55 return ProtocolVersion::kUnknown;
56}
57
58CtapDeviceResponseCode getResponseCode(const Vector<uint8_t>& buffer)
59{
60 if (buffer.isEmpty())
61 return CtapDeviceResponseCode::kCtap2ErrInvalidCBOR;
62
63 auto code = static_cast<CtapDeviceResponseCode>(buffer[0]);
64 return isCtapDeviceResponseCode(code) ? code : CtapDeviceResponseCode::kCtap2ErrInvalidCBOR;
65}
66
67static Vector<uint8_t> getCredentialId(const Vector<uint8_t>& authenticatorData)
68{
69 const size_t credentialIdLengthOffset = rpIdHashLength + flagsLength + signCounterLength + aaguidLength;
70
71 if (authenticatorData.size() < credentialIdLengthOffset + credentialIdLengthLength)
72 return { };
73 size_t credentialIdLength = (static_cast<size_t>(authenticatorData[credentialIdLengthOffset]) << 8) | static_cast<size_t>(authenticatorData[credentialIdLengthOffset + 1]);
74
75 if (authenticatorData.size() < credentialIdLengthOffset + credentialIdLengthLength + credentialIdLength)
76 return { };
77 Vector<uint8_t> credentialId;
78 credentialId.reserveInitialCapacity(credentialIdLength);
79 auto beginIt = authenticatorData.begin() + credentialIdLengthOffset + credentialIdLengthLength;
80 credentialId.appendRange(beginIt, beginIt + credentialIdLength);
81 return credentialId;
82}
83
84
85// Decodes byte array response from authenticator to CBOR value object and
86// checks for correct encoding format.
87Optional<PublicKeyCredentialData> readCTAPMakeCredentialResponse(const Vector<uint8_t>& inBuffer)
88{
89 if (inBuffer.size() <= kResponseCodeLength)
90 return WTF::nullopt;
91
92 Vector<uint8_t> buffer;
93 buffer.append(inBuffer.data() + 1, inBuffer.size() - 1);
94 Optional<CBOR> decodedResponse = cbor::CBORReader::read(buffer);
95 if (!decodedResponse || !decodedResponse->isMap())
96 return WTF::nullopt;
97 const auto& decodedMap = decodedResponse->getMap();
98
99 auto it = decodedMap.find(CBOR(1));
100 if (it == decodedMap.end() || !it->second.isString())
101 return WTF::nullopt;
102 auto format = it->second.clone();
103
104 it = decodedMap.find(CBOR(2));
105 if (it == decodedMap.end() || !it->second.isByteString())
106 return WTF::nullopt;
107 auto authenticatorData = it->second.clone();
108
109 auto credentialId = getCredentialId(authenticatorData.getByteString());
110 if (credentialId.isEmpty())
111 return WTF::nullopt;
112
113 it = decodedMap.find(CBOR(3));
114 if (it == decodedMap.end() || !it->second.isMap())
115 return WTF::nullopt;
116 auto attStmt = it->second.clone();
117
118 CBOR::MapValue attestationObjectMap;
119 attestationObjectMap[CBOR("authData")] = WTFMove(authenticatorData);
120 attestationObjectMap[CBOR("fmt")] = WTFMove(format);
121 attestationObjectMap[CBOR("attStmt")] = WTFMove(attStmt);
122 auto attestationObject = cbor::CBORWriter::write(CBOR(WTFMove(attestationObjectMap)));
123
124 return PublicKeyCredentialData { ArrayBuffer::create(credentialId.data(), credentialId.size()), true, nullptr, ArrayBuffer::create(attestationObject.value().data(), attestationObject.value().size()), nullptr, nullptr, nullptr, WTF::nullopt };
125}
126
127Optional<PublicKeyCredentialData> readCTAPGetAssertionResponse(const Vector<uint8_t>& inBuffer)
128{
129 if (inBuffer.size() <= kResponseCodeLength)
130 return WTF::nullopt;
131
132 Vector<uint8_t> buffer;
133 buffer.append(inBuffer.data() + 1, inBuffer.size() - 1);
134 Optional<CBOR> decodedResponse = cbor::CBORReader::read(buffer);
135
136 if (!decodedResponse || !decodedResponse->isMap())
137 return WTF::nullopt;
138
139 auto& responseMap = decodedResponse->getMap();
140
141 RefPtr<ArrayBuffer> credentialId;
142 auto it = responseMap.find(CBOR(1));
143 if (it != responseMap.end() && it->second.isMap()) {
144 auto& credential = it->second.getMap();
145 auto itr = credential.find(CBOR(kCredentialIdKey));
146 if (itr == credential.end() || !itr->second.isByteString())
147 return WTF::nullopt;
148 auto& id = itr->second.getByteString();
149 credentialId = ArrayBuffer::create(id.data(), id.size());
150 }
151
152 it = responseMap.find(CBOR(2));
153 if (it == responseMap.end() || !it->second.isByteString())
154 return WTF::nullopt;
155 auto& authData = it->second.getByteString();
156
157 it = responseMap.find(CBOR(3));
158 if (it == responseMap.end() || !it->second.isByteString())
159 return WTF::nullopt;
160 auto& signature = it->second.getByteString();
161
162 RefPtr<ArrayBuffer> userHandle;
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 WTF::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), WTF::nullopt };
174}
175
176Optional<AuthenticatorGetInfoResponse> readCTAPGetInfoResponse(const Vector<uint8_t>& inBuffer)
177{
178 if (inBuffer.size() <= kResponseCodeLength || getResponseCode(inBuffer) != CtapDeviceResponseCode::kSuccess)
179 return WTF::nullopt;
180
181 Vector<uint8_t> buffer;
182 buffer.append(inBuffer.data() + 1, inBuffer.size() - 1);
183 Optional<CBOR> decodedResponse = cbor::CBORReader::read(buffer);
184 if (!decodedResponse || !decodedResponse->isMap())
185 return WTF::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 WTF::nullopt;
191 StdSet<ProtocolVersion> protocolVersions;
192 for (const auto& version : it->second.getArray()) {
193 if (!version.isString())
194 return WTF::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 WTF::nullopt;
204 }
205 if (protocolVersions.empty())
206 return WTF::nullopt;
207
208 it = responseMap.find(CBOR(3));
209 if (it == responseMap.end() || !it->second.isByteString() || it->second.getByteString().size() != aaguidLength)
210 return WTF::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 WTF::nullopt;
218
219 Vector<String> extensions;
220 for (const auto& extension : it->second.getArray()) {
221 if (!extension.isString())
222 return WTF::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 WTF::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 WTF::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 WTF::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 WTF::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 WTF::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 WTF::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 WTF::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 WTF::nullopt;
295
296 Vector<uint8_t> supportedPinProtocols;
297 for (const auto& protocol : it->second.getArray()) {
298 if (!protocol.isUnsigned())
299 return WTF::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)
312