blob: 8ed71ee6f241a7458c0f605d3ed8a1d4db556aa7 [file] [log] [blame]
/*
* Copyright (C) 2019 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:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. 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.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS 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 APPLE INC. OR ITS 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.
*/
#import "config.h"
#import "NfcConnection.h"
#if ENABLE(WEB_AUTHN) && HAVE(NEAR_FIELD)
#import "NfcService.h"
#import "WKNFReaderSessionDelegate.h"
#import <WebCore/FidoConstants.h>
#import <wtf/text/Base64.h>
namespace WebKit {
using namespace fido;
namespace {
inline bool compareVersion(NSData *data, const uint8_t version[], size_t versionSize)
{
if (data.length != versionSize)
return false;
return !memcmp(data.bytes, version, versionSize);
}
} // namespace
Ref<NfcConnection> NfcConnection::create(RetainPtr<NFReaderSession>&& session, NfcService& service)
{
return adoptRef(*new NfcConnection(WTFMove(session), service));
}
NfcConnection::NfcConnection(RetainPtr<NFReaderSession>&& session, NfcService& service)
: m_session(WTFMove(session))
, m_delegate(adoptNS([[WKNFReaderSessionDelegate alloc] initWithConnection:*this]))
, m_service(makeWeakPtr(service))
, m_retryTimer(RunLoop::main(), this, &NfcConnection::startPolling)
{
[m_session setDelegate:m_delegate.get()];
startPolling();
}
NfcConnection::~NfcConnection()
{
stop();
}
Vector<uint8_t> NfcConnection::transact(Vector<uint8_t>&& data) const
{
Vector<uint8_t> response;
@autoreleasepool {
auto responseData = [m_session transceive:[NSData dataWithBytes:data.data() length:data.size()]];
response.append(reinterpret_cast<const uint8_t*>(responseData.bytes), responseData.length);
}
return response;
}
void NfcConnection::stop() const
{
[m_session disconnectTag];
[m_session stopPolling];
[m_session endSession];
}
void NfcConnection::didDetectTags(NSArray *tags)
{
if (!m_service || !tags.count)
return;
// A physical NFC tag could have multiple interfaces.
// Therefore, we use tagID to detect if there are multiple physical tags.
NSData *tagID = ((NFTag *)tags[0]).tagID;
for (NFTag *tag : tags) {
if ([tagID isEqualToData:tag.tagID])
continue;
m_service->didDetectMultipleTags();
restartPolling();
return;
}
// FIXME(203234): Tell users to switch to a different tag if the tag is not of type NFTagTypeGeneric4A
// or can't speak U2F/FIDO2.
for (NFTag *tag : tags) {
if (tag.type != NFTagTypeGeneric4A || ![m_session connectTag:tag])
continue;
// Confirm the FIDO applet is avaliable before return.
// https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#nfc-applet-selection
@autoreleasepool {
auto versionData = [m_session transceive:[NSData dataWithBytes:kCtapNfcAppletSelectionCommand length:sizeof(kCtapNfcAppletSelectionCommand)]];
if (!versionData || (!compareVersion(versionData, kCtapNfcAppletSelectionU2f, sizeof(kCtapNfcAppletSelectionU2f)) && !compareVersion(versionData, kCtapNfcAppletSelectionCtap, sizeof(kCtapNfcAppletSelectionCtap)))) {
[m_session disconnectTag];
continue;
}
}
m_service->didConnectTag();
return;
}
restartPolling();
}
// NearField polling is a one shot polling. It halts after tags are detected.
// Therefore, a restart process is needed to resume polling after error.
void NfcConnection::restartPolling()
{
[m_session stopPolling];
m_retryTimer.startOneShot(1_s); // Magic number to give users enough time for reactions.
}
void NfcConnection::startPolling()
{
NSError *error = nil;
[m_session startPollingWithError:&error];
if (error)
LOG_ERROR("Couldn't start NFC reader polling: %@", error);
}
} // namespace WebKit
#endif // ENABLE(WEB_AUTHN) && HAVE(NEAR_FIELD)