| // Copyright 2018 The Chromium Authors. All rights reserved. |
| // Copyright (C) 2018 Apple Inc. All rights reserved. |
| // |
| // Redistribution and use in source and binary forms, with or without |
| // modification, are permitted provided that the following conditions are |
| // met: |
| // |
| // * Redistributions of source code must retain the above copyright |
| // notice, this list of conditions and the following disclaimer. |
| // * Redistributions in binary form must reproduce the above |
| // copyright notice, this list of conditions and the following disclaimer |
| // in the documentation and/or other materials provided with the |
| // distribution. |
| // * Neither the name of Google Inc. nor the names of its |
| // contributors may be used to endorse or promote products derived from |
| // this software without specific prior written permission. |
| // |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| #include "config.h" |
| #include "DeviceResponseConverter.h" |
| |
| #if ENABLE(WEB_AUTHN) |
| |
| #include "AuthenticatorSupportedOptions.h" |
| #include "CBORReader.h" |
| #include "CBORWriter.h" |
| #include "WebAuthenticationConstants.h" |
| #include "WebAuthenticationUtils.h" |
| #include <wtf/StdSet.h> |
| #include <wtf/Vector.h> |
| |
| namespace fido { |
| using namespace WebCore; |
| using CBOR = cbor::CBORValue; |
| |
| constexpr size_t kResponseCodeLength = 1; |
| |
| static ProtocolVersion convertStringToProtocolVersion(const String& version) |
| { |
| if (version == kCtap2Version) |
| return ProtocolVersion::kCtap; |
| if (version == kU2fVersion) |
| return ProtocolVersion::kU2f; |
| |
| return ProtocolVersion::kUnknown; |
| } |
| |
| CtapDeviceResponseCode getResponseCode(const Vector<uint8_t>& buffer) |
| { |
| if (buffer.isEmpty()) |
| return CtapDeviceResponseCode::kCtap2ErrInvalidCBOR; |
| |
| auto code = static_cast<CtapDeviceResponseCode>(buffer[0]); |
| return isCtapDeviceResponseCode(code) ? code : CtapDeviceResponseCode::kCtap2ErrInvalidCBOR; |
| } |
| |
| static Vector<uint8_t> getCredentialId(const Vector<uint8_t>& authenticatorData) |
| { |
| const size_t credentialIdLengthOffset = rpIdHashLength + flagsLength + signCounterLength + aaguidLength; |
| |
| if (authenticatorData.size() < credentialIdLengthOffset + credentialIdLengthLength) |
| return { }; |
| size_t credentialIdLength = (static_cast<size_t>(authenticatorData[credentialIdLengthOffset]) << 8) | static_cast<size_t>(authenticatorData[credentialIdLengthOffset + 1]); |
| |
| if (authenticatorData.size() < credentialIdLengthOffset + credentialIdLengthLength + credentialIdLength) |
| return { }; |
| Vector<uint8_t> credentialId; |
| credentialId.reserveInitialCapacity(credentialIdLength); |
| auto beginIt = authenticatorData.begin() + credentialIdLengthOffset + credentialIdLengthLength; |
| credentialId.appendRange(beginIt, beginIt + credentialIdLength); |
| return credentialId; |
| } |
| |
| |
| // Decodes byte array response from authenticator to CBOR value object and |
| // checks for correct encoding format. |
| Optional<PublicKeyCredentialData> readCTAPMakeCredentialResponse(const Vector<uint8_t>& inBuffer, const WebCore::AttestationConveyancePreference& attestation) |
| { |
| if (inBuffer.size() <= kResponseCodeLength) |
| return WTF::nullopt; |
| |
| Vector<uint8_t> buffer; |
| buffer.append(inBuffer.data() + 1, inBuffer.size() - 1); |
| Optional<CBOR> decodedResponse = cbor::CBORReader::read(buffer); |
| if (!decodedResponse || !decodedResponse->isMap()) |
| return WTF::nullopt; |
| const auto& decodedMap = decodedResponse->getMap(); |
| |
| auto it = decodedMap.find(CBOR(1)); |
| if (it == decodedMap.end() || !it->second.isString()) |
| return WTF::nullopt; |
| auto format = it->second.clone(); |
| |
| it = decodedMap.find(CBOR(2)); |
| if (it == decodedMap.end() || !it->second.isByteString()) |
| return WTF::nullopt; |
| auto authenticatorData = it->second.clone(); |
| |
| auto credentialId = getCredentialId(authenticatorData.getByteString()); |
| if (credentialId.isEmpty()) |
| return WTF::nullopt; |
| |
| it = decodedMap.find(CBOR(3)); |
| if (it == decodedMap.end() || !it->second.isMap()) |
| return WTF::nullopt; |
| auto attStmt = it->second.clone(); |
| |
| Optional<Vector<uint8_t>> attestationObject; |
| if (attestation == AttestationConveyancePreference::None) { |
| // The reason why we can't directly pass authenticatorData/format/attStmt to buildAttestationObject |
| // is that they are CBORValue instead of the raw type. |
| // Also, format and attStmt are omitted as they are not useful in none attestation. |
| attestationObject = buildAttestationObject(Vector<uint8_t>(authenticatorData.getByteString()), "", { }, attestation); |
| } else { |
| CBOR::MapValue attestationObjectMap; |
| attestationObjectMap[CBOR("authData")] = WTFMove(authenticatorData); |
| attestationObjectMap[CBOR("fmt")] = WTFMove(format); |
| attestationObjectMap[CBOR("attStmt")] = WTFMove(attStmt); |
| attestationObject = cbor::CBORWriter::write(CBOR(WTFMove(attestationObjectMap))); |
| } |
| |
| return PublicKeyCredentialData { ArrayBuffer::create(credentialId.data(), credentialId.size()), true, nullptr, ArrayBuffer::create(attestationObject.value().data(), attestationObject.value().size()), nullptr, nullptr, nullptr, WTF::nullopt }; |
| } |
| |
| Optional<PublicKeyCredentialData> readCTAPGetAssertionResponse(const Vector<uint8_t>& inBuffer) |
| { |
| if (inBuffer.size() <= kResponseCodeLength) |
| return WTF::nullopt; |
| |
| Vector<uint8_t> buffer; |
| buffer.append(inBuffer.data() + 1, inBuffer.size() - 1); |
| Optional<CBOR> decodedResponse = cbor::CBORReader::read(buffer); |
| |
| if (!decodedResponse || !decodedResponse->isMap()) |
| return WTF::nullopt; |
| |
| auto& responseMap = decodedResponse->getMap(); |
| |
| RefPtr<ArrayBuffer> credentialId; |
| auto it = responseMap.find(CBOR(1)); |
| if (it != responseMap.end() && it->second.isMap()) { |
| auto& credential = it->second.getMap(); |
| auto itr = credential.find(CBOR(kCredentialIdKey)); |
| if (itr == credential.end() || !itr->second.isByteString()) |
| return WTF::nullopt; |
| auto& id = itr->second.getByteString(); |
| credentialId = ArrayBuffer::create(id.data(), id.size()); |
| } |
| |
| it = responseMap.find(CBOR(2)); |
| if (it == responseMap.end() || !it->second.isByteString()) |
| return WTF::nullopt; |
| auto& authData = it->second.getByteString(); |
| |
| it = responseMap.find(CBOR(3)); |
| if (it == responseMap.end() || !it->second.isByteString()) |
| return WTF::nullopt; |
| auto& signature = it->second.getByteString(); |
| |
| RefPtr<ArrayBuffer> userHandle; |
| it = responseMap.find(CBOR(4)); |
| if (it != responseMap.end() && it->second.isMap()) { |
| auto& user = it->second.getMap(); |
| auto itr = user.find(CBOR(kEntityIdMapKey)); |
| if (itr == user.end() || !itr->second.isByteString()) |
| return WTF::nullopt; |
| auto& id = itr->second.getByteString(); |
| userHandle = ArrayBuffer::create(id.data(), id.size()); |
| } |
| |
| return PublicKeyCredentialData { WTFMove(credentialId), false, nullptr, nullptr, ArrayBuffer::create(authData.data(), authData.size()), ArrayBuffer::create(signature.data(), signature.size()), WTFMove(userHandle), WTF::nullopt }; |
| } |
| |
| Optional<AuthenticatorGetInfoResponse> readCTAPGetInfoResponse(const Vector<uint8_t>& inBuffer) |
| { |
| if (inBuffer.size() <= kResponseCodeLength || getResponseCode(inBuffer) != CtapDeviceResponseCode::kSuccess) |
| return WTF::nullopt; |
| |
| Vector<uint8_t> buffer; |
| buffer.append(inBuffer.data() + 1, inBuffer.size() - 1); |
| Optional<CBOR> decodedResponse = cbor::CBORReader::read(buffer); |
| if (!decodedResponse || !decodedResponse->isMap()) |
| return WTF::nullopt; |
| const auto& responseMap = decodedResponse->getMap(); |
| |
| auto it = responseMap.find(CBOR(1)); |
| if (it == responseMap.end() || !it->second.isArray()) |
| return WTF::nullopt; |
| StdSet<ProtocolVersion> protocolVersions; |
| for (const auto& version : it->second.getArray()) { |
| if (!version.isString()) |
| return WTF::nullopt; |
| |
| auto protocol = convertStringToProtocolVersion(version.getString()); |
| if (protocol == ProtocolVersion::kUnknown) { |
| LOG_ERROR("Unexpected protocol version received."); |
| continue; |
| } |
| |
| if (!protocolVersions.insert(protocol).second) |
| return WTF::nullopt; |
| } |
| if (protocolVersions.empty()) |
| return WTF::nullopt; |
| |
| it = responseMap.find(CBOR(3)); |
| if (it == responseMap.end() || !it->second.isByteString() || it->second.getByteString().size() != aaguidLength) |
| return WTF::nullopt; |
| |
| AuthenticatorGetInfoResponse response(WTFMove(protocolVersions), Vector<uint8_t>(it->second.getByteString())); |
| |
| it = responseMap.find(CBOR(2)); |
| if (it != responseMap.end()) { |
| if (!it->second.isArray()) |
| return WTF::nullopt; |
| |
| Vector<String> extensions; |
| for (const auto& extension : it->second.getArray()) { |
| if (!extension.isString()) |
| return WTF::nullopt; |
| |
| extensions.append(extension.getString()); |
| } |
| response.setExtensions(WTFMove(extensions)); |
| } |
| |
| AuthenticatorSupportedOptions options; |
| it = responseMap.find(CBOR(4)); |
| if (it != responseMap.end()) { |
| if (!it->second.isMap()) |
| return WTF::nullopt; |
| const auto& optionMap = it->second.getMap(); |
| auto optionMapIt = optionMap.find(CBOR(kPlatformDeviceMapKey)); |
| if (optionMapIt != optionMap.end()) { |
| if (!optionMapIt->second.isBool()) |
| return WTF::nullopt; |
| |
| options.setIsPlatformDevice(optionMapIt->second.getBool()); |
| } |
| |
| optionMapIt = optionMap.find(CBOR(kResidentKeyMapKey)); |
| if (optionMapIt != optionMap.end()) { |
| if (!optionMapIt->second.isBool()) |
| return WTF::nullopt; |
| |
| options.setSupportsResidentKey(optionMapIt->second.getBool()); |
| } |
| |
| optionMapIt = optionMap.find(CBOR(kUserPresenceMapKey)); |
| if (optionMapIt != optionMap.end()) { |
| if (!optionMapIt->second.isBool()) |
| return WTF::nullopt; |
| |
| options.setUserPresenceRequired(optionMapIt->second.getBool()); |
| } |
| |
| optionMapIt = optionMap.find(CBOR(kUserVerificationMapKey)); |
| if (optionMapIt != optionMap.end()) { |
| if (!optionMapIt->second.isBool()) |
| return WTF::nullopt; |
| |
| if (optionMapIt->second.getBool()) |
| options.setUserVerificationAvailability(AuthenticatorSupportedOptions::UserVerificationAvailability::kSupportedAndConfigured); |
| else |
| options.setUserVerificationAvailability(AuthenticatorSupportedOptions::UserVerificationAvailability::kSupportedButNotConfigured); |
| } |
| |
| optionMapIt = optionMap.find(CBOR(kClientPinMapKey)); |
| if (optionMapIt != optionMap.end()) { |
| if (!optionMapIt->second.isBool()) |
| return WTF::nullopt; |
| |
| if (optionMapIt->second.getBool()) |
| options.setClientPinAvailability(AuthenticatorSupportedOptions::ClientPinAvailability::kSupportedAndPinSet); |
| else |
| options.setClientPinAvailability(AuthenticatorSupportedOptions::ClientPinAvailability::kSupportedButPinNotSet); |
| } |
| response.setOptions(WTFMove(options)); |
| } |
| |
| it = responseMap.find(CBOR(5)); |
| if (it != responseMap.end()) { |
| if (!it->second.isUnsigned()) |
| return WTF::nullopt; |
| |
| response.setMaxMsgSize(it->second.getUnsigned()); |
| } |
| |
| it = responseMap.find(CBOR(6)); |
| if (it != responseMap.end()) { |
| if (!it->second.isArray()) |
| return WTF::nullopt; |
| |
| Vector<uint8_t> supportedPinProtocols; |
| for (const auto& protocol : it->second.getArray()) { |
| if (!protocol.isUnsigned()) |
| return WTF::nullopt; |
| |
| supportedPinProtocols.append(protocol.getUnsigned()); |
| } |
| response.setPinProtocols(WTFMove(supportedPinProtocols)); |
| } |
| |
| return WTFMove(response); |
| } |
| |
| } // namespace fido |
| |
| #endif // ENABLE(WEB_AUTHN) |