#include "crypto/random.h"
#include "exception.h"
#include "log/log.h"
+#include "lowercase_hex_string_of.h"
#include "tunnel_server_domain.h"
#include <chrono>
}
buffer.resize(encoder.GetBufferSize());
+ LogDebug("QR code CBOR buffer before encoding: " << LowercaseHexStringOf(buffer));
fidoUri = "FIDO:/" + DigitEncode(buffer);
+ LogDebug("QR code contents encoded: " << fidoUri);
}
void Container::CloseContainer(const CborEncoder &mapEncoder) noexcept
typedef std::vector<uint8_t> Buffer;
typedef std::basic_string_view<uint8_t> BufferView;
-
-inline Buffer ToBuffer(const BufferView &bv) { return Buffer(bv.begin(), bv.end()); }
-
-template <class T>
-BufferView ToBufferView(T &buffer) noexcept
-{
- static_assert(sizeof(decltype(*buffer.data())) == 1, "for reinterpret_cast below");
- return BufferView{reinterpret_cast<const uint8_t *>(buffer.data()), buffer.size()};
-}
-
-// Use with string literals only, because it assumes the input array to be null-terminated.
-template <size_t N>
-BufferView BUFFER_VIEW(const char (&data)[N])
-{
- return BufferView(reinterpret_cast<const uint8_t *>(data), N - 1);
-}
const wauthn_pubkey_cred_creation_options_s &options)
{
if (!options.rp)
- THROW_INVALID_PARAM("options.rp is null");
+ THROW_INVALID_PARAM("options.rp is NULL");
return DoCommand<MakeCredentialCommand, MCResult>(
"MakeCredential", clientData, options, options.rp->id);
}
Buffer shutdownMsg;
shutdown.Serialize(shutdownMsg);
m_encryptedTunnel->WriteBinary(shutdownMsg);
-
- // TODO: implement 2 minute waiting feature
-
return res;
}
+void CtapMessageProcessor::ProcessFollowingUpdateMsgs(
+ std::function<void(UpdateMessage &&)> updateMsgCallback)
+{
+ m_encryptedTunnel->CloseConnectionAfter(120e6);
+ try {
+ for (;;) {
+ auto msg = m_encryptedTunnel->ReadBinary();
+ LogDebug("Decrypted message: " << LowercaseHexStringOf(msg));
+ if (msg.empty() || msg[0] != static_cast<uint8_t>(MessageType::UPDATE))
+ THROW_UNKNOWN("Received message is not an UPDATE message");
+
+ auto msgBv = BufferView{msg.data() + 1, msg.size() - 1};
+ UpdateMessage upMsg;
+ upMsg.Deserialize(msgBv);
+ updateMsgCallback(std::move(upMsg));
+ }
+ } catch (const ExceptionTunnelClosed &) {
+ LogDebug("Tunnel has closed, stopping reading.");
+ }
+}
+
void VerifyRpIdHash(const char *rpId, const BufferView &rpIdHash)
{
assert(rpId);
#include "encrypted_tunnel.h"
#include "message.h"
+#include <functional>
#include <memory>
#include <webauthn-types.h>
class ICtapMessageProcessor {
public:
- struct MCResult {
+ template <class Response>
+ struct Result {
PostHandshakeResponse getInfo;
UpdateMessage upMsg;
- MakeCredentialResponse response;
- };
-
- struct GAResult {
- PostHandshakeResponse getInfo;
- UpdateMessage upMsg;
- GetAssertionResponse response;
+ Response response;
};
virtual void SetEncryptedTunnel(std::unique_ptr<IEncryptedTunnel> encryptedTunnel) = 0;
+ using MCResult = Result<MakeCredentialResponse>;
+
virtual MCResult MakeCredential(const wauthn_client_data_s &clientData,
const wauthn_pubkey_cred_creation_options_s &options) = 0;
+ using GAResult = Result<GetAssertionResponse>;
+
virtual GAResult GetAssertion(const wauthn_client_data_s &clientData,
const wauthn_pubkey_cred_request_options_s &options) = 0;
+ virtual void
+ ProcessFollowingUpdateMsgs(std::function<void(UpdateMessage &&)> updateMsgCallback) = 0;
+
// May be called from the other thread.
virtual void Cancel() = 0;
GAResult GetAssertion(const wauthn_client_data_s &clientData,
const wauthn_pubkey_cred_request_options_s &options) override;
+ void
+ ProcessFollowingUpdateMsgs(std::function<void(UpdateMessage &&)> updateMsgCallback) override;
+
void Cancel() override;
private:
m_tunnel->WriteBinary(ciphertext);
}
+void EncryptedTunnel::CloseConnectionAfter(uint32_t usecs)
+{
+ m_tunnel->CloseConnectionAfter(usecs);
+}
+
void EncryptedTunnel::Cancel() { m_tunnel->Cancel(); }
virtual void WriteBinary(CryptoBuffer msg) = 0;
+ virtual void CloseConnectionAfter(uint32_t usecs) = 0;
+
// May be called from the other thread.
virtual void Cancel() = 0;
// Needed as derived classes are handled with pointers to this interface
- virtual ~IEncryptedTunnel(){};
+ virtual ~IEncryptedTunnel() = default;
};
class EncryptedTunnel : public IEncryptedTunnel {
void WriteBinary(CryptoBuffer msg) override;
+ void CloseConnectionAfter(uint32_t usecs) override;
+
void Cancel() override;
private:
} // namespace Exception
-#define THROW_UNKNOWN(...) LOGGED_THROW(::Exception::Unknown, __VA_ARGS__)
-#define THROW_INVALID_PARAM(...) LOGGED_THROW(::Exception::InvalidParam, __VA_ARGS__)
-#define THROW_PERMISSION_DENIED(...) LOGGED_THROW(::Exception::PermissionDenied, __VA_ARGS__)
-#define THROW_UNSUPPORTED(...) LOGGED_THROW(::Exception::NotSupported, __VA_ARGS__)
-#define THROW_NOT_ALLOWED(...) LOGGED_THROW(::Exception::NotAllowed, __VA_ARGS__)
-#define THROW_INVALID_STATE(...) LOGGED_THROW(::Exception::InvalidState, __VA_ARGS__)
-#define THROW_ENCODING(...) LOGGED_THROW(::Exception::EncodingFailed, __VA_ARGS__)
-#define THROW_MEMORY() LOGGED_THROW(::Exception::MemoryError, "Memory error")
-#define THROW_CANCELLED() LOGGED_THROW(::Exception::Cancelled, "Operation cancelled")
-#define THROW_TIMEOUT(...) LOGGED_THROW(::Exception::Timeout, __VA_ARGS__)
+#define THROW_UNKNOWN(...) ERROR_LOGGED_THROW(::Exception::Unknown, __VA_ARGS__)
+#define THROW_INVALID_PARAM(...) ERROR_LOGGED_THROW(::Exception::InvalidParam, __VA_ARGS__)
+#define THROW_PERMISSION_DENIED(...) ERROR_LOGGED_THROW(::Exception::PermissionDenied, __VA_ARGS__)
+#define THROW_UNSUPPORTED(...) ERROR_LOGGED_THROW(::Exception::NotSupported, __VA_ARGS__)
+#define THROW_NOT_ALLOWED(...) ERROR_LOGGED_THROW(::Exception::NotAllowed, __VA_ARGS__)
+#define THROW_INVALID_STATE(...) ERROR_LOGGED_THROW(::Exception::InvalidState, __VA_ARGS__)
+#define THROW_ENCODING(...) ERROR_LOGGED_THROW(::Exception::EncodingFailed, __VA_ARGS__)
+#define THROW_MEMORY() ERROR_LOGGED_THROW(::Exception::MemoryError, "Memory error")
+#define THROW_TIMEOUT(...) ERROR_LOGGED_THROW(::Exception::Timeout, __VA_ARGS__)
+
+#define THROW_CANCELLED() DEBUG_LOGGED_THROW(::Exception::Cancelled, "Operation cancelled")
} catch (...) { \
}
-#define LOGGED_THROW(exceptionType, msg) \
+#define ERROR_LOGGED_THROW(exceptionType, msg) \
do { \
std::ostringstream oss; \
oss << msg; \
TRY_LOG_ERROR("Throwing exception: " << oss.str()); \
throw exceptionType(oss.str()); \
} while (false)
+
+#define DEBUG_LOGGED_THROW(exceptionType, msg) \
+ do { \
+ std::ostringstream oss; \
+ oss << msg; \
+ TRY_LOG_DEBUG("Throwing exception: " << oss.str()); \
+ throw exceptionType(oss.str()); \
+ } while (false)
Buffer m_handshakeSignature;
};
- const std::optional<LinkData> &GetLinkData() const { return m_linkData; }
+ const std::optional<LinkData> &GetLinkData() const noexcept { return m_linkData; }
private:
std::optional<LinkData> m_linkData;
const CryptoBuffer &identityKeyCompressed,
const Hint &hint,
bool stateAssisted,
- wauthn_cb_display_qrcode displayQrCodeCallback,
- void *displayQrCodeCallbackUserData)
+ std::function<void(std::string &&)> &&qrCodeCallback)
{
std::string fidoUri;
CborEncoding::Cbor cbor;
cbor.EncodeQRContents(identityKeyCompressed, qrSecret, hint, stateAssisted, fidoUri);
- displayQrCodeCallback(fidoUri.c_str(), displayQrCodeCallbackUserData);
+ qrCodeCallback(std::move(fidoUri));
}
#include "common.h"
#include "crypto/common.h"
-#include <webauthn-hal.h>
+#include <functional>
+#include <string>
class IQrCodeShower {
public:
const CryptoBuffer &identityKeyCompressed,
const Hint &hint,
bool stateAssisted,
- wauthn_cb_display_qrcode displayQrCodeCallback,
- void *displayQrCodeCallbackUserData) = 0;
+ std::function<void(std::string &&)> &&qrCodeCallback) = 0;
};
class QrCodeShower : public IQrCodeShower {
const CryptoBuffer &identityKeyCompressed,
const Hint &hint,
bool stateAssisted,
- wauthn_cb_display_qrcode displayQrCodeCallback,
- void *displayQrCodeCallbackUserData) override;
+ std::function<void(std::string &&)> &&qrCodeCallback) override;
};
#include <utility>
-QrTransaction::QrTransaction(wauthn_cb_display_qrcode displayQrCodeCallback,
- void *displayQrCodeCallbackUserData,
- Hint hint,
- std::unique_ptr<IQrCodeShower> qrCodeShower,
- IBtAdvertScannerUPtr btAdvertScanner,
- std::unique_ptr<IHandshake> handshake,
- std::unique_ptr<ICtapMessageProcessor> ctapMessageProcessor)
-: m_displayQrCodeCallback{displayQrCodeCallback},
- m_displayQrCodeCallbackUserData{displayQrCodeCallbackUserData},
- m_hint{std::move(hint)},
- m_qrCodeShower{std::move(qrCodeShower)},
- m_btAdvertScanner{std::move(btAdvertScanner)},
- m_handshake{std::move(handshake)},
- m_ctapMessageProcessor{std::move(ctapMessageProcessor)}
+void QrTransaction::Initialize(Hint &&hint,
+ std::function<void(std::string &&)> &&qrCodeCallback,
+ std::unique_ptr<IQrCodeShower> &&qrCodeShower,
+ IBtAdvertScannerUPtr &&btAdvertScanner,
+ std::unique_ptr<IHandshake> &&handshake,
+ std::unique_ptr<ICtapMessageProcessor> &&ctapMessageProcessor)
{
+ m_hint = std::move(hint);
+ m_qrCodeCallback = std::move(qrCodeCallback);
+ m_qrCodeShower = std::move(qrCodeShower);
+ m_btAdvertScanner = std::move(btAdvertScanner);
+ m_handshake = std::move(handshake);
+ m_ctapMessageProcessor = std::move(ctapMessageProcessor);
}
-ITransaction::MCResult
-QrTransaction::PerformTransaction(const wauthn_client_data_s &clientData,
- const wauthn_pubkey_cred_creation_options_s &options)
+ITransaction::MCResult QrTransaction::Perform(const wauthn_client_data_s &clientData,
+ const wauthn_pubkey_cred_creation_options_s &options)
{
return DoPerformTransaction<ITransaction::MCResult>(
&ICtapMessageProcessor::MakeCredential, clientData, options);
}
-ITransaction::GAResult
-QrTransaction::PerformTransaction(const wauthn_client_data_s &clientData,
- const wauthn_pubkey_cred_request_options_s &options)
+ITransaction::GAResult QrTransaction::Perform(const wauthn_client_data_s &clientData,
+ const wauthn_pubkey_cred_request_options_s &options)
{
return DoPerformTransaction<ITransaction::GAResult>(
&ICtapMessageProcessor::GetAssertion, clientData, options);
}
+void QrTransaction::ProcessFollowingUpdateMsgs(
+ std::function<void(UpdateMessage &&)> updateMsgCallback)
+{
+ m_ctapMessageProcessor->ProcessFollowingUpdateMsgs(std::move(updateMsgCallback));
+}
+
template <class Result, class CmdResult, class Options>
Result QrTransaction::DoPerformTransaction(
CmdResult (ICtapMessageProcessor::*cmd)(const wauthn_client_data_s &, const Options &),
CryptoBuffer qrSecret = Crypto::RandomBytes(QR_SECRET_LEN);
Crypto::X9_62_P_256_Key identityKey = Crypto::X9_62_P_256_Key::Create();
- m_qrCodeShower->ShowQrCode(qrSecret,
- identityKey.ExportPublicKey(true),
- m_hint,
- true,
- m_displayQrCodeCallback,
- m_displayQrCodeCallbackUserData);
+ m_qrCodeShower->ShowQrCode(
+ qrSecret, identityKey.ExportPublicKey(true), m_hint, true, std::move(m_qrCodeCallback));
UpdateStateAndCheckForCancel(State::AWAITING_BLE_ADVERT);
UpdateStateAndCheckForCancel(State::CTAP_MESSAGE_PROCESSING);
- auto cmdRes = ((*m_ctapMessageProcessor).*cmd)(clientData, options);
- const auto &linkData = cmdRes.upMsg.GetLinkData();
+ auto ctapRes = ((*m_ctapMessageProcessor).*cmd)(clientData, options);
+ const auto &linkData = ctapRes.upMsg.GetLinkData();
if (linkData) {
auto pubKey = Crypto::X9_62_P_256_Key::ImportPublicKey(linkData->m_authenticatorPublicKey);
if (!verifySignature(
UpdateStateAndCheckForCancel(State::NOT_IN_PROGRESS);
- return {std::move(cmdRes.getInfo),
- std::move(cmdRes.upMsg),
- std::move(cmdRes.response),
- identityKey.ExportPrivateKey(),
- std::move(tunnelServerDomain)};
+ return {std::move(ctapRes), identityKey.ExportPrivateKey(), std::move(tunnelServerDomain)};
}
void QrTransaction::UpdateStateAndCheckForCancel(State state)
#include "qr_code_shower.h"
#include "transaction.h"
-class QrTransaction : public ITransaction {
+class IQrTransaction : public ITransaction {
public:
- QrTransaction(wauthn_cb_display_qrcode displayQrCodeCallback,
- void *displayQrCodeCallbackUserData,
- Hint hint,
- std::unique_ptr<IQrCodeShower> qrCodeShower = std::make_unique<QrCodeShower>(),
- IBtAdvertScannerUPtr btAdvertScanner = std::make_unique<BtAdvertScanner>(),
- std::unique_ptr<IHandshake> handshake = std::make_unique<Handshake>(),
- std::unique_ptr<ICtapMessageProcessor> ctapMessageProcessor =
- std::make_unique<CtapMessageProcessor>());
+ virtual void
+ Initialize(Hint &&hint,
+ std::function<void(std::string &&)> &&qrCodeCallback,
+ std::unique_ptr<IQrCodeShower> &&qrCodeShower = std::make_unique<QrCodeShower>(),
+ IBtAdvertScannerUPtr &&btAdvertScanner = std::make_unique<BtAdvertScanner>(),
+ std::unique_ptr<IHandshake> &&handshake = std::make_unique<Handshake>(),
+ std::unique_ptr<ICtapMessageProcessor> &&ctapMessageProcessor =
+ std::make_unique<CtapMessageProcessor>()) = 0;
+};
+
+class QrTransaction : public IQrTransaction {
+public:
+ QrTransaction() noexcept = default;
+
+ void Initialize(Hint &&hint,
+ std::function<void(std::string &&)> &&qrCodeCallback,
+ std::unique_ptr<IQrCodeShower> &&qrCodeShower,
+ IBtAdvertScannerUPtr &&btAdvertScanner,
+ std::unique_ptr<IHandshake> &&handshake,
+ std::unique_ptr<ICtapMessageProcessor> &&ctapMessageProcessor) override;
+
+ MCResult Perform(const wauthn_client_data_s &clientData,
+ const wauthn_pubkey_cred_creation_options_s &options) override;
- MCResult PerformTransaction(const wauthn_client_data_s &clientData,
- const wauthn_pubkey_cred_creation_options_s &options) override;
+ GAResult Perform(const wauthn_client_data_s &clientData,
+ const wauthn_pubkey_cred_request_options_s &options) override;
- GAResult PerformTransaction(const wauthn_client_data_s &clientData,
- const wauthn_pubkey_cred_request_options_s &options) override;
+ void
+ ProcessFollowingUpdateMsgs(std::function<void(UpdateMessage &&)> updateMsgCallback) override;
void Cancel() override;
const wauthn_client_data_s &clientData,
const Options &options);
- wauthn_cb_display_qrcode m_displayQrCodeCallback;
- void *m_displayQrCodeCallbackUserData;
- Hint m_hint;
+ Hint m_hint{};
+ std::function<void(std::string &&)> m_qrCodeCallback;
std::unique_ptr<IQrCodeShower> m_qrCodeShower;
IBtAdvertScannerUPtr m_btAdvertScanner;
std::unique_ptr<IHandshake> m_handshake;
}
}
+wauthn_hybrid_linked_data_s *
+RequestHandler::AssignAndGetLinkedData(ForCallbacksCommon &forCallbacks) noexcept
+{
+ const auto &linkData = forCallbacks.upMsg.GetLinkData();
+ if (linkData) {
+ auto &ld = forCallbacks.linkedData;
+ Assign(ld.contact_id, forCallbacks.ldContactId, linkData->m_contactId);
+ Assign(ld.link_id, forCallbacks.ldLinkId, linkData->m_linkId);
+ Assign(ld.link_secret, forCallbacks.ldLinkSecret, linkData->m_linkSecret);
+ Assign(ld.authenticator_pubkey,
+ forCallbacks.ldAuthenticatorPubKey,
+ linkData->m_authenticatorPublicKey);
+ Assign(
+ ld.authenticator_name, forCallbacks.ldAuthenticatorName, linkData->m_authenticatorName);
+ Assign(ld.signature, forCallbacks.ldHandshakeSignature, linkData->m_handshakeSignature);
+ Assign(ld.tunnel_server_domain,
+ forCallbacks.ldTunnelServerDomain,
+ forCallbacks.tunnelServerDomain);
+ Assign(ld.identity_key, forCallbacks.ldIdentityKey, forCallbacks.clientPlatformPrivKey);
+ return &ld;
+ }
+ return nullptr;
+}
+
RequestHandler::RequestHandler() noexcept
{
int err = pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, nullptr);
#pragma once
#include "base64.h"
-#include "bluetooth_advert.h"
#include "common.h"
#include "exception.h"
-#include "handshake.h"
-#include "log/log.h"
-#include "qr_code_shower.h"
#include "qr_transaction.h"
#include "state_assisted_transaction.h"
#include "to_wauthn_const_buff.h"
#include <memory>
#include <mutex>
-#include <webauthn-hal.h>
+#include <webauthn-types.h>
-struct RequestMC {
- const wauthn_pubkey_cred_creation_options_s *options;
- wauthn_mc_callbacks_s *callbacks;
+enum class RequestKind {
+ MC,
+ GA,
};
-struct RequestGA {
- const wauthn_pubkey_cred_request_options_s *options;
- wauthn_ga_callbacks_s *callbacks;
+template <RequestKind>
+struct Request {};
+
+template <>
+struct Request<RequestKind::MC> {
+ static constexpr auto KIND = RequestKind::MC;
+ static constexpr Hint HINT = {'m', 'c'};
+
+ const wauthn_pubkey_cred_creation_options_s *options = nullptr;
+ const wauthn_mc_callbacks_s *callbacks = nullptr;
};
-[[nodiscard]] constexpr Hint ToHint(const RequestMC &) noexcept { return {'m', 'c'}; }
+template <>
+struct Request<RequestKind::GA> {
+ static constexpr auto KIND = RequestKind::GA;
+ static constexpr Hint HINT = {'g', 'a'};
-[[nodiscard]] constexpr Hint ToHint(const RequestGA &) noexcept { return {'g', 'a'}; }
+ const wauthn_pubkey_cred_request_options_s *options = nullptr;
+ const wauthn_ga_callbacks_s *callbacks = nullptr;
+};
-enum class RequestKind {
- MC,
- GA,
+template <RequestKind>
+struct ForCallbacks {};
+
+struct ForCallbacksCommon {
+ UpdateMessage upMsg;
+ Buffer clientPlatformPrivKey;
+ std::string tunnelServerDomain;
+
+ wauthn_const_buffer_s responseAuthenticatorData = {};
+
+ wauthn_const_buffer_s credentialRawId = {};
+ std::string credentialIdInBase64;
+ wauthn_const_buffer_s credentialId = {};
+
+ wauthn_const_buffer_s ldContactId = {};
+ wauthn_const_buffer_s ldLinkId = {};
+ wauthn_const_buffer_s ldLinkSecret = {};
+ wauthn_const_buffer_s ldAuthenticatorPubKey = {};
+ wauthn_const_buffer_s ldAuthenticatorName = {};
+ wauthn_const_buffer_s ldHandshakeSignature = {};
+ wauthn_const_buffer_s ldTunnelServerDomain = {};
+ wauthn_const_buffer_s ldIdentityKey = {};
+
+ wauthn_hybrid_linked_data_s linkedData = {};
};
-[[nodiscard]] constexpr RequestKind ToKind(const RequestMC &) noexcept { return RequestKind::MC; }
+template <>
+struct ForCallbacks<RequestKind::MC> : public ForCallbacksCommon {
+ MakeCredentialResponse ctapResponse;
+
+ wauthn_const_buffer_s responseAttestationObject = {};
+ wauthn_const_buffer_s responseSubjectPubkeyInfo = {};
-[[nodiscard]] constexpr RequestKind ToKind(const RequestGA &) noexcept { return RequestKind::GA; }
+ wauthn_authenticator_attestation_response_s response = {};
+
+ wauthn_pubkey_credential_attestation_s ca = {};
+};
+template <>
+struct ForCallbacks<RequestKind::GA> : public ForCallbacksCommon {
+ GetAssertionResponse ctapResponse;
+
+ wauthn_const_buffer_s responseSignature = {};
+ wauthn_const_buffer_s responseUserHandle = {};
+
+ wauthn_authenticator_assertion_response_s response = {};
+
+ wauthn_pubkey_credential_assertion_s ca = {};
+};
+
+// Information gained from tests with real authenticators:
+// - iPhone and Android allows two tunnels connected to it simultaneously. Test was performed this
+// way:
+// 1. QR-initiated MakeCredential
+// 2. Started awaiting UPDATE messages for MakeCredential connection
+// 3. In case of iPhone QR-initiated GetAssertion, in case of Android state-assisted GetAssertion
+// 4. Started awaiting UPDATE messages for GetAssertion connection
+// 5. After approx. 2 minutes iPhone closed MakeCredential connection
+// 6. Just after that authenticator closed GetAssertion connection.
class RequestHandler {
public:
static RequestHandler &Instance() noexcept;
wauthn_error_e Cancel() noexcept;
- template <class Request>
- void Process(
- const wauthn_client_data_s *clientData,
- Request request,
- // If null, default implementations will be set in the function body to control exceptions.
- std::unique_ptr<IQrCodeShower> qrCodeShower = nullptr,
- IBtAdvertScannerUPtr btAdvertScanner = nullptr,
- std::unique_ptr<IHandshake> handshake = nullptr,
- std::unique_ptr<ICtapMessageProcessor> ctapMessageProcessor = nullptr) noexcept
+ template <RequestKind REQUEST_KIND>
+ void Process(const wauthn_client_data_s *clientData,
+ Request<REQUEST_KIND> request,
+ IQrTransaction &&qrTransaction,
+ IStateAssistedTransaction &&stateAssistedTransaction) noexcept
{
- using namespace Exception;
+ if (!request.callbacks) {
+ TRY_LOG_ERROR("Missing callbacks");
+ return; // No way to report the result, so we do nothing.
+ }
+ if (!request.callbacks->response_callback) {
+ TRY_LOG_ERROR("Missing response_callback");
+ return; // No way to report the result, so we do nothing.
+ }
- wauthn_error_e result;
- try {
- if (!request.callbacks || !request.callbacks->response_callback)
- LogError("Missing response callback");
-
- if (clientData == nullptr || request.options == nullptr)
- throw InvalidParam{};
-
- if (!qrCodeShower)
- qrCodeShower = std::make_unique<QrCodeShower>();
- if (!btAdvertScanner)
- btAdvertScanner = std::make_unique<BtAdvertScanner>();
- if (!handshake)
- handshake = std::make_unique<Handshake>();
- if (!ctapMessageProcessor)
- ctapMessageProcessor = std::make_unique<CtapMessageProcessor>();
-
- {
- std::lock_guard<std::mutex> lock(m_mutex);
- if (m_currentTransaction != nullptr) {
- LogError("New request received while processing another one");
- throw NotAllowed{}; // Server should not allow it
- }
+ static constexpr auto SUCCESSFUL_RESULT = WAUTHN_ERROR_NONE;
+ ForCallbacks<REQUEST_KIND> forCallbacks;
+ static_assert(std::is_nothrow_default_constructible_v<decltype(forCallbacks)>,
+ "Needed as it is not guarded by try-catch");
+ auto result = SUCCESSFUL_RESULT;
+ ITransaction *transaction = nullptr;
+ if (!request.callbacks->linked_data_callback) {
+ TRY_LOG_ERROR("Missing linked_data_callback");
+ result = WAUTHN_ERROR_INVALID_PARAMETER;
+ } else if (!clientData) {
+ TRY_LOG_ERROR("Missing client_data");
+ result = WAUTHN_ERROR_INVALID_PARAMETER;
+ } else if (!request.options) {
+ TRY_LOG_ERROR("Missing request options");
+ result = WAUTHN_ERROR_INVALID_PARAMETER;
+ } else if (!request.options->linked_device && !request.callbacks->qrcode_callback) {
+ TRY_LOG_ERROR("Missing qrcode_callback");
+ result = WAUTHN_ERROR_INVALID_PARAMETER;
+ } else {
+ try {
if (request.options->linked_device) {
- m_currentTransaction =
- std::make_unique<StateAssistedTransaction>(ToHint(request),
- std::move(handshake),
- std::move(btAdvertScanner),
- std::move(ctapMessageProcessor));
+ transaction = &stateAssistedTransaction;
+ stateAssistedTransaction.Initialize(Hint{Request<REQUEST_KIND>::HINT});
} else {
- if (!request.callbacks || !request.callbacks->qrcode_callback) {
- LogError("Missing QR callback");
- throw InvalidParam{};
- }
- m_currentTransaction =
- std::make_unique<QrTransaction>(request.callbacks->qrcode_callback,
- request.callbacks->user_data,
- ToHint(request),
- std::move(qrCodeShower),
- std::move(btAdvertScanner),
- std::move(handshake),
- std::move(ctapMessageProcessor));
+ transaction = &qrTransaction;
+ qrTransaction.Initialize(
+ Hint{Request<REQUEST_KIND>::HINT}, [&](std::string &&qrCodeContents) {
+ try {
+ request.callbacks->qrcode_callback(qrCodeContents.c_str(),
+ request.callbacks->user_data);
+ } catch (const std::exception &e) {
+ TRY_LOG_ERROR(
+ "qrcode_callback has thrown an exception: " << e.what());
+ throw; // safe - it will be caught below
+ } catch (...) {
+ TRY_LOG_ERROR("qrcode_callback has thrown an unknown exception");
+ throw; // safe - it will be caught below
+ }
+ });
}
+ SetCurrentTransaction(*transaction);
+ ProcessTransactionResult(clientData->client_data_json,
+ forCallbacks,
+ transaction->Perform(*clientData, *request.options));
+ } catch (const Exception::ExceptionBase &e) {
+ result = e.Code();
+ } catch (const std::bad_alloc &) {
+ result = WAUTHN_ERROR_MEMORY;
+ } catch (const std::exception &e) {
+ TRY_LOG_ERROR("Caught unexpected exception: " << e.what());
+ result = WAUTHN_ERROR_UNKNOWN;
+ } catch (...) {
+ TRY_LOG_ERROR("Caught unknown exception");
+ result = WAUTHN_ERROR_UNKNOWN;
}
-
- auto cleanup = OnScopeExit([&] {
- try {
- std::lock_guard<std::mutex> lock(m_mutex);
- m_currentTransaction = nullptr;
- } catch (...) {
- TRY_LOG_ERROR("Mutex lock failed");
- result = WAUTHN_ERROR_UNKNOWN;
- }
- });
-
- auto transactionRes =
- m_currentTransaction->PerformTransaction(*clientData, *request.options);
- if (request.callbacks && request.callbacks->response_callback) {
- ProcessTransactionResult(transactionRes,
- *clientData,
- request.callbacks->response_callback,
- request.callbacks->user_data);
- return;
- }
- } catch (const ExceptionBase &ex) {
- result = ex.Code();
- } catch (const std::bad_alloc &) {
- result = WAUTHN_ERROR_MEMORY;
+ }
+ // Reset m_currentTransaction so that other requests can be handled
+ try {
+ UnsetCurrentTransaction();
} catch (...) {
+ TRY_LOG_ERROR("Mutex lock failed");
result = WAUTHN_ERROR_UNKNOWN;
}
-
- if (request.callbacks && request.callbacks->response_callback) {
+ // response_callback
+ bool responseCallbackHasThrown = false;
+ try {
+ request.callbacks->response_callback(result == SUCCESSFUL_RESULT ? &forCallbacks.ca
+ : nullptr,
+ result,
+ request.callbacks->user_data);
+ } catch (const std::exception &e) {
+ TRY_LOG_ERROR("response_callback has thrown an exception: " << e.what());
+ responseCallbackHasThrown = true;
+ } catch (...) {
+ TRY_LOG_ERROR("response_callback has thrown an unknown exception");
+ responseCallbackHasThrown = true;
+ }
+ if (result != SUCCESSFUL_RESULT)
+ return;
+ // linked_data_callback
+ result = WAUTHN_ERROR_NONE;
+ if (responseCallbackHasThrown)
+ result = WAUTHN_ERROR_UNKNOWN;
+ else {
try {
- request.callbacks->response_callback(nullptr, result, request.callbacks->user_data);
+ transaction->ProcessFollowingUpdateMsgs([&](UpdateMessage &&upMsg) {
+ forCallbacks.upMsg = std::move(upMsg);
+ try {
+ request.callbacks->linked_data_callback(
+ AssignAndGetLinkedData(forCallbacks),
+ WAUTHN_ERROR_NONE_AND_WAIT,
+ request.callbacks->user_data);
+ } catch (const std::exception &e) {
+ TRY_LOG_ERROR("linked_data_callback has thrown an exception: " << e.what());
+ throw; // safe - it will be caught below
+ } catch (...) {
+ LogError("linked_data_callback has thrown an unknown exception");
+ throw; // safe - it will be caught below
+ }
+ });
+ } catch (const Exception::ExceptionBase &e) {
+ result = e.Code();
+ } catch (const std::bad_alloc &) {
+ result = WAUTHN_ERROR_MEMORY;
+ } catch (const std::exception &e) {
+ TRY_LOG_ERROR("Caught unexpected exception: " << e.what());
+ result = WAUTHN_ERROR_UNKNOWN;
} catch (...) {
- TRY_LOG_ERROR("Response callback has thrown an exception");
+ result = WAUTHN_ERROR_UNKNOWN;
}
}
+ // The final call of linked_data_callback
+ try {
+ request.callbacks->linked_data_callback(nullptr, result, request.callbacks->user_data);
+ } catch (const std::exception &e) {
+ TRY_LOG_ERROR("linked_data_callback has thrown an exception: " << e.what());
+ } catch (...) {
+ TRY_LOG_ERROR("linked_data_callback has thrown an exception");
+ }
}
- template <class TransactionRes, class Callback>
- void ProcessTransactionResult(const TransactionRes &transactionRes,
- const wauthn_client_data_s &clientData,
- Callback &&callback,
- void *callbackUserData)
+private:
+ void SetCurrentTransaction(ITransaction &transaction)
{
- static constexpr bool isMC = std::is_same_v<TransactionRes, ITransaction::MCResult>;
- auto dependent = [&] {
- if constexpr (isMC) {
- struct {
- wauthn_const_buffer_s attestationObject;
- wauthn_const_buffer_s subjectPubkeyInfo;
- wauthn_authenticator_attestation_response_s response;
- wauthn_pubkey_credential_attestation_s ca;
- } val{};
- return val;
- } else {
- struct {
- wauthn_const_buffer_s responseSignature;
- wauthn_const_buffer_s responseUserHandle;
- wauthn_authenticator_assertion_response_s response;
- wauthn_pubkey_credential_assertion_s ca;
- } val{};
- return val;
- }
- }();
-
- auto assign =
- [](wauthn_const_buffer_s *&dest, wauthn_const_buffer_s &localVar, auto &&value) {
- localVar = ToWauthnConstBuff(std::forward<decltype(value)>(value));
- dest = &localVar;
- };
-
- wauthn_const_buffer_s responseAuthenticatorData;
- wauthn_const_buffer_s rawId;
+ auto lock = std::lock_guard{m_mutex};
+ if (m_currentTransaction) {
+ THROW_NOT_ALLOWED("New request received while processing another one.");
+ }
+ m_currentTransaction = &transaction;
+ }
- if constexpr (isMC) {
- auto &attestationData = transactionRes.response.m_authData.m_attestationData.value();
+ void UnsetCurrentTransaction()
+ {
+ auto lock = std::lock_guard{m_mutex};
+ m_currentTransaction = nullptr;
+ }
- assign(dependent.response.attestation_object,
- dependent.attestationObject,
- transactionRes.response.m_attestationObject);
- dependent.response.transports = WAUTHN_TRANSPORT_NONE;
- for (const auto &transport : transactionRes.getInfo.m_transports) {
+ template <RequestKind REQUEST_KIND, class TransactionResult>
+ void ProcessTransactionResult(wauthn_const_buffer_s *clientDataJson,
+ ForCallbacks<REQUEST_KIND> &forCallbacks,
+ TransactionResult &&transactionRes)
+ {
+ forCallbacks.upMsg = std::move(transactionRes.ctapResult.upMsg);
+ forCallbacks.clientPlatformPrivKey = std::move(transactionRes.clientPlatformPrivKey);
+ forCallbacks.tunnelServerDomain = std::move(transactionRes.tunnelServerDomain);
+ forCallbacks.ctapResponse = std::move(transactionRes.ctapResult.response);
+
+ forCallbacks.response.client_data_json = clientDataJson;
+ Assign(forCallbacks.response.authenticator_data,
+ forCallbacks.responseAuthenticatorData,
+ forCallbacks.ctapResponse.m_authDataRaw);
+
+ if constexpr (REQUEST_KIND == RequestKind::MC) {
+ Assign(forCallbacks.response.attestation_object,
+ forCallbacks.responseAttestationObject,
+ forCallbacks.ctapResponse.m_attestationObject);
+
+ auto &responseTransports = forCallbacks.response.transports;
+ responseTransports = WAUTHN_TRANSPORT_NONE;
+ for (const auto &transport : transactionRes.ctapResult.getInfo.m_transports) {
if (transport == "internal")
- dependent.response.transports |= WAUTHN_TRANSPORT_INTERNAL;
+ responseTransports |= WAUTHN_TRANSPORT_INTERNAL;
else if (transport == "hybrid")
- dependent.response.transports |= WAUTHN_TRANSPORT_HYBRID;
+ responseTransports |= WAUTHN_TRANSPORT_HYBRID;
else
THROW_UNKNOWN("Unexpected transport: " << transport);
}
- assign(dependent.response.subject_pubkey_info,
- dependent.subjectPubkeyInfo,
- attestationData.m_publicKeyDer);
- dependent.response.pubkey_alg = attestationData.m_alg;
- assign(dependent.ca.rawId, rawId, attestationData.m_credentialId);
- } else {
- dependent.response.attestation_object = nullptr;
- assign(dependent.response.signature,
- dependent.responseSignature,
- transactionRes.response.m_signature);
+ auto &attestationData = forCallbacks.ctapResponse.m_authData.m_attestationData.value();
- dependent.responseUserHandle = ToWauthnConstBuff(transactionRes.response.m_userId);
- dependent.response.user_handle =
- dependent.responseUserHandle.size != 0 ? &dependent.responseUserHandle : nullptr;
+ Assign(forCallbacks.response.subject_pubkey_info,
+ forCallbacks.responseSubjectPubkeyInfo,
+ attestationData.m_publicKeyDer);
+ forCallbacks.response.pubkey_alg = attestationData.m_alg;
- assign(dependent.ca.rawId, rawId, transactionRes.response.m_credentialId);
+ Assign(forCallbacks.ca.rawId,
+ forCallbacks.credentialRawId,
+ attestationData.m_credentialId);
+ } else {
+ forCallbacks.response.attestation_object = nullptr;
+ Assign(forCallbacks.response.signature,
+ forCallbacks.responseSignature,
+ forCallbacks.ctapResponse.m_signature);
+
+ forCallbacks.responseUserHandle = ToWauthnConstBuff(forCallbacks.ctapResponse.m_userId);
+ forCallbacks.response.user_handle = forCallbacks.responseUserHandle.size != 0
+ ? &forCallbacks.responseUserHandle
+ : nullptr;
+
+ Assign(forCallbacks.ca.rawId,
+ forCallbacks.credentialRawId,
+ forCallbacks.ctapResponse.m_credentialId);
}
- dependent.response.client_data_json = clientData.client_data_json;
- assign(dependent.response.authenticator_data,
- responseAuthenticatorData,
- transactionRes.response.m_authDataRaw);
-
- auto idInBase64 = Base64UrlEncode(BufferView{rawId.data, rawId.size});
- wauthn_const_buffer_s id;
- wauthn_const_buffer_s ldContactId;
- wauthn_const_buffer_s ldLinkId;
- wauthn_const_buffer_s ldLinkSecret;
- wauthn_const_buffer_s ldAuthenticatorPubKey;
- wauthn_const_buffer_s ldAuthenticatorName;
- wauthn_const_buffer_s ldHandshakeSignature;
- wauthn_const_buffer_s ldTunnelServerDomain;
- wauthn_const_buffer_s ldIdentityKey;
-
- wauthn_hybrid_linked_data_s linkedDevice;
- const auto &linkData = transactionRes.upMsg.GetLinkData();
- if (linkData) {
- assign(linkedDevice.contact_id, ldContactId, linkData->m_contactId);
- assign(linkedDevice.link_id, ldLinkId, linkData->m_linkId);
- assign(linkedDevice.link_secret, ldLinkSecret, linkData->m_linkSecret);
- assign(linkedDevice.authenticator_pubkey,
- ldAuthenticatorPubKey,
- linkData->m_authenticatorPublicKey);
- assign(linkedDevice.authenticator_name,
- ldAuthenticatorName,
- linkData->m_authenticatorName);
- assign(linkedDevice.signature, ldHandshakeSignature, linkData->m_handshakeSignature);
- assign(linkedDevice.tunnel_server_domain,
- ldTunnelServerDomain,
- transactionRes.tunnelServerDomain);
- assign(linkedDevice.identity_key, ldIdentityKey, transactionRes.clientPlatformPrivKey);
- }
+ forCallbacks.credentialIdInBase64 = Base64UrlEncode(
+ BufferView{forCallbacks.credentialRawId.data, forCallbacks.credentialRawId.size});
- assign(dependent.ca.id, id, idInBase64);
- dependent.ca.type = PCT_PUBLIC_KEY;
- dependent.ca.response = &dependent.response;
- dependent.ca.authenticator_attachment = AA_CROSS_PLATFORM;
- dependent.ca.extensions = nullptr;
- dependent.ca.linked_device = linkData ? &linkedDevice : nullptr;
+ Assign(forCallbacks.ca.id, forCallbacks.credentialId, forCallbacks.credentialIdInBase64);
+ forCallbacks.ca.type = PCT_PUBLIC_KEY;
+ forCallbacks.ca.response = &forCallbacks.response;
+ forCallbacks.ca.authenticator_attachment = AA_CROSS_PLATFORM;
+ forCallbacks.ca.extensions = nullptr;
+ forCallbacks.ca.linked_device = AssignAndGetLinkedData(forCallbacks);
+ }
- try {
- std::forward<Callback>(callback)(&dependent.ca, WAUTHN_ERROR_NONE, callbackUserData);
- } catch (...) {
- TRY_LOG_ERROR("Response callback has thrown an exception");
- }
+ template <class T>
+ void Assign(wauthn_const_buffer_s *&dest, wauthn_const_buffer_s &storageVar, T &&value) noexcept
+ {
+ storageVar = ToWauthnConstBuff(std::forward<T>(value));
+ dest = &storageVar;
}
-private:
+ wauthn_hybrid_linked_data_s *AssignAndGetLinkedData(ForCallbacksCommon &forCallbacks) noexcept;
+
RequestHandler() noexcept;
std::mutex m_mutex;
- std::unique_ptr<ITransaction> m_currentTransaction;
+ ITransaction *m_currentTransaction = nullptr;
};
#include "state_assisted_transaction.h"
#include "verify_signature.h"
-StateAssistedTransaction::StateAssistedTransaction(
- Hint hint,
- std::unique_ptr<IHandshake> handshake,
- IBtAdvertScannerUPtr btAdvertScanner,
- std::unique_ptr<ICtapMessageProcessor> ctapMessageProcessor)
-: m_hint{std::move(hint)},
- m_handshake{std::move(handshake)},
- m_btAdvertScanner{std::move(btAdvertScanner)},
- m_ctapMessageProcessor{std::move(ctapMessageProcessor)}
+void StateAssistedTransaction::Initialize(
+ Hint &&hint,
+ std::unique_ptr<IHandshake> &&handshake,
+ IBtAdvertScannerUPtr &&btAdvertScanner,
+ std::unique_ptr<ICtapMessageProcessor> &&ctapMessageProcessor)
{
+ m_hint = std::move(hint);
+ m_handshake = std::move(handshake);
+ m_btAdvertScanner = std::move(btAdvertScanner);
+ m_ctapMessageProcessor = std::move(ctapMessageProcessor);
}
ITransaction::MCResult
-StateAssistedTransaction::PerformTransaction(const wauthn_client_data_s &clientData,
- const wauthn_pubkey_cred_creation_options_s &options)
+StateAssistedTransaction::Perform(const wauthn_client_data_s &clientData,
+ const wauthn_pubkey_cred_creation_options_s &options)
{
return DoPerformTransaction<ITransaction::MCResult>(
&ICtapMessageProcessor::MakeCredential, clientData, options);
}
ITransaction::GAResult
-StateAssistedTransaction::PerformTransaction(const wauthn_client_data_s &clientData,
- const wauthn_pubkey_cred_request_options_s &options)
+StateAssistedTransaction::Perform(const wauthn_client_data_s &clientData,
+ const wauthn_pubkey_cred_request_options_s &options)
{
return DoPerformTransaction<ITransaction::GAResult>(
&ICtapMessageProcessor::GetAssertion, clientData, options);
}
+void StateAssistedTransaction::ProcessFollowingUpdateMsgs(
+ std::function<void(UpdateMessage &&)> updateMsgCallback)
+{
+ m_ctapMessageProcessor->ProcessFollowingUpdateMsgs(std::move(updateMsgCallback));
+}
+
template <class Result, class CmdResult, class Options>
Result StateAssistedTransaction::DoPerformTransaction(
CmdResult (ICtapMessageProcessor::*cmd)(const wauthn_client_data_s &, const Options &),
const Options &options)
{
if (!options.linked_device)
- THROW_INVALID_PARAM("linked_device is null");
+ THROW_INVALID_PARAM("linked_device is NULL");
const wauthn_hybrid_linked_data_s &linkedDevice = *options.linked_device;
if (!linkedDevice.tunnel_server_domain)
- THROW_INVALID_PARAM("linked_device->tunnel_server_domain is null");
+ THROW_INVALID_PARAM("linked_device->tunnel_server_domain is NULL");
auto tunnelServerDomain =
std::string(reinterpret_cast<const char *>(linkedDevice.tunnel_server_domain->data),
linkedDevice.tunnel_server_domain->size);
if (!linkedDevice.contact_id)
- THROW_INVALID_PARAM("linked_device->contact_id is null");
+ THROW_INVALID_PARAM("linked_device->contact_id is NULL");
auto contactId = BufferView{linkedDevice.contact_id->data, linkedDevice.contact_id->size};
Buffer clientPayload(33, '\0');
{
auto map = encoder.OpenMap(3);
if (!linkedDevice.link_id)
- THROW_INVALID_PARAM("linked_device->link_id is null");
+ THROW_INVALID_PARAM("linked_device->link_id is NULL");
map.AppendByteStringAt(1, *linkedDevice.link_id);
map.AppendByteStringAt(2, clientNonce);
map.AppendTextStringAt(3, std::string_view{m_hint.data(), m_hint.size()});
CryptoBuffer decryptedBleAdvert;
if (!linkedDevice.link_secret)
- THROW_INVALID_PARAM("linked_device->link_secret is null");
+ THROW_INVALID_PARAM("linked_device->link_secret is NULL");
auto linkSecret = Buffer{linkedDevice.link_secret->data,
linkedDevice.link_secret->data + linkedDevice.link_secret->size};
auto eidKey = DeriveKey(linkSecret, clientNonce, KeyPurpose::EIDKey, EID_KEY_LEN);
auto psk = DeriveKey(linkSecret, decryptedBleAdvert, KeyPurpose::PSK, PSK_LEN);
if (!linkedDevice.authenticator_pubkey)
- THROW_INVALID_PARAM("linked_device->authenticator_pubkey is null");
+ THROW_INVALID_PARAM("linked_device->authenticator_pubkey is NULL");
auto authenticatorPubKey = CryptoBuffer{linkedDevice.authenticator_pubkey->data,
linkedDevice.authenticator_pubkey->data +
linkedDevice.authenticator_pubkey->size};
UpdateStateAndCheckForCancel(State::CTAP_MESSAGE_PROCESSING);
if (!linkedDevice.identity_key)
- THROW_INVALID_PARAM("linked_device->identity_key is null");
+ THROW_INVALID_PARAM("linked_device->identity_key is NULL");
auto clientPlatformPrivKey =
Buffer{linkedDevice.identity_key->data,
linkedDevice.identity_key->data + linkedDevice.identity_key->size};
- auto cmdRes = ((*m_ctapMessageProcessor).*cmd)(clientData, options);
- const auto &linkData = cmdRes.upMsg.GetLinkData();
+ auto ctapRes = ((*m_ctapMessageProcessor).*cmd)(clientData, options);
+ const auto &linkData = ctapRes.upMsg.GetLinkData();
if (linkData) {
LogWarning("Unexpected UPDATE message in state-assisted transaction: handling it normally");
auto identityKey = Crypto::X9_62_P_256_Key::ImportPrivateKey(clientPlatformPrivKey);
UpdateStateAndCheckForCancel(State::NOT_IN_PROGRESS);
- return {std::move(cmdRes.getInfo),
- std::move(cmdRes.upMsg),
- std::move(cmdRes.response),
- std::move(clientPlatformPrivKey),
- std::move(tunnelServerDomain)};
+ return {std::move(ctapRes), std::move(clientPlatformPrivKey), std::move(tunnelServerDomain)};
}
void StateAssistedTransaction::UpdateStateAndCheckForCancel(State state)
#include "handshake.h"
#include "transaction.h"
-class StateAssistedTransaction : public ITransaction {
+class IStateAssistedTransaction : public ITransaction {
public:
- StateAssistedTransaction(
- Hint hint,
- std::unique_ptr<IHandshake> handshake = std::make_unique<Handshake>(),
- IBtAdvertScannerUPtr btAdvertScanner = std::make_unique<BtAdvertScanner>(),
- std::unique_ptr<ICtapMessageProcessor> ctapMessageProcessor =
- std::make_unique<CtapMessageProcessor>());
+ virtual void
+ Initialize(Hint &&hint,
+ std::unique_ptr<IHandshake> &&handshake = std::make_unique<Handshake>(),
+ IBtAdvertScannerUPtr &&btAdvertScanner = std::make_unique<BtAdvertScanner>(),
+ std::unique_ptr<ICtapMessageProcessor> &&ctapMessageProcessor =
+ std::make_unique<CtapMessageProcessor>()) = 0;
+};
+
+class StateAssistedTransaction : public IStateAssistedTransaction {
+public:
+ StateAssistedTransaction() noexcept = default;
+
+ void Initialize(Hint &&hint,
+ std::unique_ptr<IHandshake> &&handshake,
+ IBtAdvertScannerUPtr &&btAdvertScanner,
+ std::unique_ptr<ICtapMessageProcessor> &&ctapMessageProcessor) override;
+
+ MCResult Perform(const wauthn_client_data_s &clientData,
+ const wauthn_pubkey_cred_creation_options_s &options) override;
- MCResult PerformTransaction(const wauthn_client_data_s &clientData,
- const wauthn_pubkey_cred_creation_options_s &options) override;
+ GAResult Perform(const wauthn_client_data_s &clientData,
+ const wauthn_pubkey_cred_request_options_s &options) override;
- GAResult PerformTransaction(const wauthn_client_data_s &clientData,
- const wauthn_pubkey_cred_request_options_s &options) override;
+ void
+ ProcessFollowingUpdateMsgs(std::function<void(UpdateMessage &&)> updateMsgCallback) override;
void Cancel() override;
const wauthn_client_data_s &clientData,
const Options &options);
- Hint m_hint;
+ Hint m_hint{};
std::unique_ptr<IHandshake> m_handshake;
IBtAdvertScannerUPtr m_btAdvertScanner;
std::unique_ptr<ICtapMessageProcessor> m_ctapMessageProcessor;
#include "webauthn-types.h"
+#include <cstdint>
#include <cstring>
template <class T>
#pragma once
+#include "ctap_message_processor.h"
#include "message.h"
+#include <functional>
#include <string>
#include <webauthn-types.h>
virtual ~ITransaction() = default;
- template <class Response>
+ template <class CtapResult>
struct Result {
- PostHandshakeResponse getInfo;
- UpdateMessage upMsg;
- Response response;
+ CtapResult ctapResult;
Buffer clientPlatformPrivKey;
std::string tunnelServerDomain;
};
- using MCResult = Result<MakeCredentialResponse>;
+ using MCResult = Result<ICtapMessageProcessor::MCResult>;
// Throws Cancelled on cancel.
- virtual MCResult PerformTransaction(const wauthn_client_data_s &clientData,
- const wauthn_pubkey_cred_creation_options_s &options) = 0;
+ virtual MCResult Perform(const wauthn_client_data_s &clientData,
+ const wauthn_pubkey_cred_creation_options_s &options) = 0;
- using GAResult = Result<GetAssertionResponse>;
+ using GAResult = Result<ICtapMessageProcessor::GAResult>;
// Throws Cancelled on cancel.
- virtual GAResult PerformTransaction(const wauthn_client_data_s &clientData,
- const wauthn_pubkey_cred_request_options_s &options) = 0;
+ virtual GAResult Perform(const wauthn_client_data_s &clientData,
+ const wauthn_pubkey_cred_request_options_s &options) = 0;
+
+ virtual void
+ ProcessFollowingUpdateMsgs(std::function<void(UpdateMessage &&)> updateMsgCallback) = 0;
// May be called from other threads.
virtual void Cancel() = 0;
PAIR(LWS_CALLBACK_EVENT_WAIT_CANCELLED),
PAIR(LWS_CALLBACK_CONNECTING),
PAIR(LWS_CALLBACK_VHOST_CERT_AGING),
+ PAIR(LWS_CALLBACK_TIMER),
};
const std::unordered_map<enum lws_close_status, std::string> CLOSE_STATUS2STR = {
return msg;
}
+void Tunnel::CloseConnectionAfter(uint32_t usecs)
+{
+ TRY_LOG_DEBUG("setting close connection after to " << usecs << " microseconds.");
+ if (!m_connection)
+ THROW_INVALID_STATE("BUG: no active connection to schedule close for");
+ m_ws->SetTimer(m_connection, usecs);
+}
+
void Tunnel::Disconnect()
{
if (!m_context) {
break;
case LWS_CALLBACK_VHOST_CERT_AGING:
- if (m_state != State::CONNECTED && m_state != State::WRITING) {
- LogError("Unexpected event");
- m_state = State::FAILED;
- return true;
- }
+ // Was seen happening as soon as just after LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED,
+ // as well as after the connection is fully connected. Therefore ignore this event
+ // completely as it may happen in any m_state.
break;
case LWS_CALLBACK_WS_PEER_INITIATED_CLOSE: {
if (csit != CLOSE_STATUS2STR.end())
description = csit->second;
- LogError("Peer initiated close. Code: " << code << " (" << description << ")");
+ LogWarning("Peer initiated close. Code: " << code << " (" << description << ")");
} else {
- LogError("Peer initiated close");
+ LogWarning("Peer initiated close");
}
- m_state = State::FAILED;
+ m_state = State::WILL_DISCONNECT;
// returning false will echo the close and then close the connection
return false;
}
case LWS_CALLBACK_CLIENT_CLOSED: {
- m_state = State::FAILED;
+ m_state = State::WILL_DISCONNECT;
return true;
}
break;
}
+ case LWS_CALLBACK_TIMER:
+ if (m_context) {
+ // Tunnel timeout, close the connection.
+ m_state = State::WILL_DISCONNECT;
+ // Simply returning true is not enough for this event :(
+ m_ws->CancelService(m_context);
+ }
+ return true;
+
case LWS_CALLBACK_EVENT_WAIT_CANCELLED: {
std::unique_lock<std::mutex> lock(m_mutex);
if (m_cancelled) {
m_state = State::FAILED;
return true;
}
+ if (m_state == State::WILL_DISCONNECT)
+ return true;
// LWS_CALLBACK_EVENT_WAIT_CANCELLED may happen during successful connecting
if (m_state != State::DISCONNECTED) {
LogError("Unexpected event");
case LWS_CALLBACK_WSI_DESTROY:
case LWS_CALLBACK_PROTOCOL_DESTROY:
- if (m_state == State::DISCONNECTED) {
+ switch (m_state) {
+ case State::CONNECTED:
+ case State::WILL_DISCONNECT:
+ case State::FAILED: break;
+ default:
LogError("Unexpected event");
m_state = State::FAILED;
return true;
if (m_cancelled) {
DestroyContext(lock);
THROW_CANCELLED();
+ } else if (m_state == State::WILL_DISCONNECT) {
+ DestroyContext(lock);
+ throw ExceptionTunnelClosed{};
} else if (m_state != State::CONNECTED) {
DestroyContext(lock);
THROW_INVALID_STATE("Invalid tunnel state");
#pragma once
+#include "exception.h"
#include "websockets.h"
#include <cstdint>
std::string value;
};
+struct ExceptionTunnelClosed : public Exception::InvalidState {
+ ExceptionTunnelClosed() : Exception{"Tunnel connection closed."} {}
+};
+
class ITunnel {
protected:
ITunnel() = default;
std::optional<ExtraHttpHeader> extraHttpHeader = std::nullopt) = 0;
virtual void WriteBinary(const std::vector<uint8_t> &msg) = 0;
virtual std::vector<uint8_t> ReadBinary() = 0;
+ // Make the tunnel close the connection after @p usecs microseconds, no matter what.
+ virtual void CloseConnectionAfter(uint32_t usecs) = 0;
virtual void Disconnect() = 0;
virtual void Cancel() = 0;
};
std::optional<ExtraHttpHeader> extraHttpHeader = std::nullopt) override;
void WriteBinary(const std::vector<uint8_t> &msg) override;
std::vector<uint8_t> ReadBinary() override;
+ void CloseConnectionAfter(uint32_t usecs) override;
void Disconnect() override;
/*
DISCONNECTED,
FAILED,
CONNECTED,
+ WILL_DISCONNECT,
WRITING,
};
const wauthn_pubkey_cred_creation_options_s *options,
wauthn_mc_callbacks_s *callbacks)
{
- RequestHandler::Instance().Process(client_data, RequestMC{options, callbacks});
+ RequestHandler::Instance().Process(client_data,
+ Request<RequestKind::MC>{options, callbacks},
+ QrTransaction{},
+ StateAssistedTransaction{});
}
WEBAUTHN_HAL_API
const wauthn_pubkey_cred_request_options_s *options,
wauthn_ga_callbacks_s *callbacks)
{
- RequestHandler::Instance().Process(client_data, RequestGA{options, callbacks});
+ RequestHandler::Instance().Process(client_data,
+ Request<RequestKind::GA>{options, callbacks},
+ QrTransaction{},
+ StateAssistedTransaction{});
}
WEBAUTHN_HAL_API
return lws_write(Lws2Native(wsi), buf, len, LWS_WRITE_BINARY);
}
+void Websockets::SetTimer(Lws *wsi, lws_usec_t usecs) noexcept
+{
+ lws_set_timer_usecs(Lws2Native(wsi), usecs);
+}
+
void Websockets::ContextDestroy(LwsContext *context) noexcept
{
lws_context_destroy(Context2Native(context));
*/
virtual int WriteBinary(Lws *wsi, unsigned char *buf, size_t len) noexcept = 0;
+ /*
+ * @brief Schedule LWS_CALLBACK_TIMER event to happen after specified number of microseconds
+ *
+ * @param[in] wsi The connection to be used to send event to
+ * @param[in] usecs Number of microseconds after which the event will occur, or
+ * LWS_SET_TIMER_USEC_CANCEL to remove existing scheduled event
+ */
+ virtual void SetTimer(Lws *wsi, lws_usec_t usecs) noexcept = 0;
+
/*
* @brief Destroys given context and releases resources and closes connection if necessary
*
bool IsFirstFragment(Lws *wsi) const noexcept override;
bool IsFinalFragment(Lws *wsi) const noexcept override;
int WriteBinary(Lws *wsi, unsigned char *buf, size_t len) noexcept override;
+ void SetTimer(Lws *wsi, lws_usec_t usecs) noexcept override;
void ContextDestroy(LwsContext *context) noexcept override;
void SetListener(IWebsocketsListener *listener) noexcept override { m_listener = listener; }
SET(UNIT_TESTS_SOURCES
${WEBAUTHN_BLE_SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/main.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/api_tests.cpp
${CMAKE_CURRENT_SOURCE_DIR}/bluetooth_tests.cpp
${CMAKE_CURRENT_SOURCE_DIR}/cbor_tests.cpp
${CMAKE_CURRENT_SOURCE_DIR}/turn_bluetooth.cpp
${CMAKE_CURRENT_SOURCE_DIR}/message_tests.cpp
${CMAKE_CURRENT_SOURCE_DIR}/ctap_message_processor_tests.cpp
${CMAKE_CURRENT_SOURCE_DIR}/qr_code_shower_tests.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/request_handler_tests.cpp
${CMAKE_CURRENT_SOURCE_DIR}/crypto/hkdf_unittest.cpp
${CMAKE_CURRENT_SOURCE_DIR}/crypto/hmac_unittest.cpp
${CMAKE_CURRENT_SOURCE_DIR}/crypto/sha2_unittest.cpp
+++ /dev/null
-/*
- * Copyright (c) 2023 Samsung Electronics Co., Ltd All Rights Reserved
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-#include "bluetooth_advert.h"
-#include "crypto/ec_key.h"
-#include "crypto/random.h"
-#include "exception.h"
-#include "qr_code_shower.h"
-#include "request_handler.h"
-
-#include <cstring>
-#include <functional>
-#include <gtest/gtest.h>
-#include <stdexcept>
-#include <utility>
-#include <webauthn-hal.h>
-
-namespace {
-
-template <typename... Args>
-class CallbackInfo {
-public:
- void Reset()
- {
- m_calls = 0;
- m_argsCheckOk = false;
- }
-
- void Call(const Args... args)
- {
- m_calls++;
- if (m_argsChecker)
- m_argsCheckOk = m_argsChecker(args...);
- }
-
- void CalledOnce(int line)
- {
- EXPECT_EQ(m_calls, 1) << "line: " << line;
- if (m_argsChecker) {
- EXPECT_TRUE(m_argsCheckOk) << "line: " << line;
- }
- Reset();
- }
-
- void NotCalled(int line)
- {
- EXPECT_EQ(m_calls, 0) << "line: " << line;
- Reset();
- }
-
- void SetArgsChecker(std::function<bool(Args...)> fn) { m_argsChecker = std::move(fn); }
-
-protected:
- unsigned m_calls;
- std::function<bool(Args...)> m_argsChecker;
- bool m_argsCheckOk;
-};
-
-CallbackInfo<const char *, void *> qrCallback;
-CallbackInfo<const wauthn_pubkey_credential_attestation_s *, wauthn_error_e, void *> mcCallback;
-CallbackInfo<const wauthn_pubkey_credential_assertion_s *, wauthn_error_e, void *> gaCallback;
-
-void QrCallback(const char *qr_contents, void *user_data)
-{
- qrCallback.Call(qr_contents, user_data);
-}
-
-void QrThrowingCallback(const char *qr_contents, void *user_data)
-{
- qrCallback.Call(qr_contents, user_data);
- throw Exception::InvalidParam();
-}
-
-void McCallback(const wauthn_pubkey_credential_attestation_s *pubkey_cred,
- wauthn_error_e result,
- void *user_data)
-{
- mcCallback.Call(pubkey_cred, result, user_data);
-}
-
-void GaCallback(const wauthn_pubkey_credential_assertion_s *pubkey_cred,
- wauthn_error_e result,
- void *user_data)
-{
- gaCallback.Call(pubkey_cred, result, user_data);
-}
-
-class MBtAdvertScanner : public IBtAdvertScanner {
-public:
- void AwaitAdvert(const CryptoBuffer & /*eidKey*/, CryptoBuffer & /*decryptedAdvert*/) override
- {
- throw std::runtime_error{"should not be called"};
- }
-
- void Cancel() override { throw std::runtime_error{"should not be called"}; }
-};
-
-class MHandshake : public IHandshake {
-public:
- void QrInitiatedConnect(const std::string &, const BufferView &, const BufferView &) override
- {
- throw std::runtime_error{"should not be called"};
- }
-
- HandshakeResult DoQrInitiatedHandshake(const CryptoBuffer &,
- const Crypto::X9_62_P_256_Key &) override
- {
- throw std::runtime_error{"should not be called"};
- }
-
- virtual void
- StateAssistedConnect(const std::string &, const BufferView &, const BufferView &) override
- {
- throw std::runtime_error{"should not be called"};
- }
-
- HandshakeResult DoStateAssistedHandshake(const CryptoBuffer &, const CryptoBuffer &) override
- {
- throw std::runtime_error{"should not be called"};
- }
-
- void Cancel() override { throw std::runtime_error{"should not be called"}; }
-};
-
-class MCtapMessageProcessor : public ICtapMessageProcessor {
- void SetEncryptedTunnel(std::unique_ptr<IEncryptedTunnel>) override
- {
- throw std::runtime_error{"should not be called"};
- }
-
- MCResult MakeCredential(const wauthn_client_data_s &,
- const wauthn_pubkey_cred_creation_options_s &) override
- {
- throw std::runtime_error{"should not be called"};
- }
-
- GAResult GetAssertion(const wauthn_client_data_s &,
- const wauthn_pubkey_cred_request_options_s &) override
- {
- throw std::runtime_error{"should not be called"};
- }
-
- void Cancel() override { throw std::runtime_error{"should not be called"}; }
-};
-
-void mocked_wah_make_credential(const wauthn_client_data_s *client_data,
- const wauthn_pubkey_cred_creation_options_s *options,
- wauthn_mc_callbacks_s *callbacks)
-{
- RequestHandler::Instance().Process(client_data,
- RequestMC{options, callbacks},
- std::make_unique<QrCodeShower>(),
- std::make_unique<MBtAdvertScanner>(),
- std::make_unique<MHandshake>(),
- std::make_unique<MCtapMessageProcessor>());
-}
-
-void mocked_wah_get_assertion(const wauthn_client_data_s *client_data,
- const wauthn_pubkey_cred_request_options_s *options,
- wauthn_ga_callbacks_s *callbacks)
-{
- RequestHandler::Instance().Process(client_data,
- RequestGA{options, callbacks},
- std::make_unique<QrCodeShower>(),
- std::make_unique<MBtAdvertScanner>(),
- std::make_unique<MHandshake>(),
- std::make_unique<MCtapMessageProcessor>());
-}
-
-void InvokeMc(const wauthn_client_data_s *clientData,
- const wauthn_pubkey_cred_creation_options_s *options,
- wauthn_cb_display_qrcode qrcodeCallback,
- wauthn_cb_mc_on_response responseCallback,
- void *userData)
-{
- wauthn_mc_callbacks_s mcCb;
- mcCb.qrcode_callback = qrcodeCallback;
- mcCb.response_callback = responseCallback;
- mcCb.user_data = userData;
-
- mocked_wah_make_credential(clientData, options, &mcCb);
-}
-
-void InvokeGa(const wauthn_client_data_s *clientData,
- const wauthn_pubkey_cred_request_options_s *options,
- wauthn_cb_display_qrcode qrcodeCallback,
- wauthn_cb_ga_on_response responseCallback,
- void *userData)
-{
- wauthn_ga_callbacks_s gaCb;
- gaCb.qrcode_callback = qrcodeCallback;
- gaCb.response_callback = responseCallback;
- gaCb.user_data = userData;
-
- mocked_wah_get_assertion(clientData, options, &gaCb);
-}
-
-template <typename T>
-std::function<bool(const T *, wauthn_error_e, void *)> invalidParamChecker(void *userData)
-{
- return [=](const T *pubkey_cred, wauthn_error_e result, void *user_data) {
- return pubkey_cred == nullptr && result == WAUTHN_ERROR_INVALID_PARAMETER &&
- user_data == userData;
- };
-}
-
-} // namespace
-
-TEST(ApiTest, testInvalidArguments)
-{
- wauthn_client_data_s client;
- wauthn_pubkey_cred_creation_options_s mcOptions;
- wauthn_pubkey_cred_request_options_s gaOptions;
- size_t userData = 123'456;
-
- memset(&client, 0, sizeof client);
- memset(&mcOptions, 0, sizeof mcOptions);
- memset(&gaOptions, 0, sizeof gaOptions);
-
- auto mcInvalid = invalidParamChecker<wauthn_pubkey_credential_attestation_s>(&userData);
- auto gaInvalid = invalidParamChecker<wauthn_pubkey_credential_assertion_s>(&userData);
-
- mocked_wah_make_credential(&client, &mcOptions, nullptr);
- InvokeMc(&client, &mcOptions, nullptr, nullptr, nullptr);
- qrCallback.NotCalled(__LINE__);
- mcCallback.NotCalled(__LINE__);
- gaCallback.NotCalled(__LINE__);
-
- mcCallback.SetArgsChecker(mcInvalid);
-
- InvokeMc(nullptr, &mcOptions, QrCallback, McCallback, &userData);
- qrCallback.NotCalled(__LINE__);
- mcCallback.CalledOnce(__LINE__);
- gaCallback.NotCalled(__LINE__);
-
- InvokeMc(&client, nullptr, QrCallback, McCallback, &userData);
- qrCallback.NotCalled(__LINE__);
- mcCallback.CalledOnce(__LINE__);
- gaCallback.NotCalled(__LINE__);
-
- mocked_wah_get_assertion(&client, &gaOptions, nullptr);
- InvokeGa(&client, &gaOptions, nullptr, nullptr, nullptr);
- qrCallback.NotCalled(__LINE__);
- mcCallback.NotCalled(__LINE__);
- gaCallback.NotCalled(__LINE__);
-
- gaCallback.SetArgsChecker(gaInvalid);
-
- InvokeGa(nullptr, &gaOptions, QrCallback, GaCallback, &userData);
- qrCallback.NotCalled(__LINE__);
- mcCallback.NotCalled(__LINE__);
- gaCallback.CalledOnce(__LINE__);
-
- InvokeGa(&client, nullptr, QrCallback, GaCallback, &userData);
- qrCallback.NotCalled(__LINE__);
- mcCallback.NotCalled(__LINE__);
- gaCallback.CalledOnce(__LINE__);
-
- EXPECT_EQ(wah_cancel(), WAUTHN_ERROR_NOT_ALLOWED);
-}
-
-TEST(ApiTest, testQrCallback)
-{
- wauthn_client_data_s client;
- wauthn_pubkey_cred_creation_options_s mcOptions;
- wauthn_pubkey_cred_request_options_s gaOptions;
- size_t userData = 123'456;
-
- memset(&client, 0, sizeof client);
- memset(&mcOptions, 0, sizeof mcOptions);
- memset(&gaOptions, 0, sizeof gaOptions);
-
- auto qrCheck = [&](const char *qr_contents, void *user_data) {
- static const std::string PREFIX = "FIDO:/";
-
- if (strncmp(qr_contents, PREFIX.c_str(), PREFIX.size()) != 0)
- return false;
-
- return &userData == user_data;
- };
-
- auto mcInvalid = invalidParamChecker<wauthn_pubkey_credential_attestation_s>(&userData);
- auto gaInvalid = invalidParamChecker<wauthn_pubkey_credential_assertion_s>(&userData);
-
- qrCallback.SetArgsChecker(qrCheck);
- mcCallback.SetArgsChecker(mcInvalid);
- gaCallback.SetArgsChecker(gaInvalid);
-
- // Use throwing qr callback to stop further processing
-
- InvokeMc(&client, &mcOptions, QrThrowingCallback, McCallback, &userData);
- qrCallback.CalledOnce(__LINE__);
- mcCallback.CalledOnce(__LINE__);
- gaCallback.NotCalled(__LINE__);
-
- InvokeGa(&client, &gaOptions, QrThrowingCallback, GaCallback, &userData);
- qrCallback.CalledOnce(__LINE__);
- mcCallback.NotCalled(__LINE__);
- gaCallback.CalledOnce(__LINE__);
-}
--- /dev/null
+/*
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+#pragma once
+
+#include "common.h"
+
+inline Buffer ToBuffer(const BufferView &bv) { return Buffer(bv.begin(), bv.end()); }
+
+// Use with string literals only, because it assumes the input array to be null-terminated.
+template <size_t N>
+Buffer BUFFER(const char (&data)[N])
+{
+ return Buffer{reinterpret_cast<const uint8_t *>(data),
+ reinterpret_cast<const uint8_t *>(data) + N - 1};
+}
--- /dev/null
+/*
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+#pragma once
+
+#include "common.h"
+
+template <class T>
+BufferView ToBufferView(T &buffer) noexcept
+{
+ static_assert(sizeof(decltype(*buffer.data())) == 1, "for reinterpret_cast below");
+ return BufferView{reinterpret_cast<const uint8_t *>(buffer.data()), buffer.size()};
+}
+
+// Use with string literals only, because it assumes the input array to be null-terminated.
+template <size_t N>
+BufferView BUFFER_VIEW(const char (&data)[N])
+{
+ return BufferView(reinterpret_cast<const uint8_t *>(data), N - 1);
+}
* limitations under the License
*/
+#include "buffer.h"
+#include "buffer_view.h"
#include "ctap_message_processor.h"
#include "exception.h"
+#include "message_examples.h"
#include "test_cancel_from_the_other_thread.h"
#include "to_wauthn_const_buff.h"
class MEncryptedTunnel : public IEncryptedTunnel {
public:
- explicit MEncryptedTunnel(std::vector<CryptoBuffer> messagesToRead)
+ explicit MEncryptedTunnel(std::vector<CryptoBuffer> &&messagesToRead)
: m_messagesToRead{std::move(messagesToRead)}
{
}
EXPECT_EQ(m_nextMsgToRead, 1); // Writing after reading GetInfo
} else {
EXPECT_EQ(m_nextMsgToRead,
- m_messagesToRead.size()); // Writting after reading everything
+ m_messagesToRead.size()); // Writing after reading everything
}
m_writtenMessages.emplace_back(std::move(msg));
}
+ void CloseConnectionAfter(uint32_t) override {}
+
void Cancel() override
{
ADD_FAILURE() << "This should not be called";
return std::forward<Func>(func)(clientData, options);
}
-const auto IPHONE_POST_HANDSHAKE_RESPONSE = BUFFER_VIEW(
- "\xa1\x01\x58\x4f\xa5\x01\x82\x68\x46\x49\x44\x4f\x5f\x32\x5f\x30\x68\x46\x49\x44\x4f"
- "\x5f\x32\x5f\x31\x02\x81\x69\x6c\x61\x72\x67\x65\x42\x6c\x6f\x62\x03\x50\xf2\x4a\x8e"
- "\x70\xd0\xd3\xf8\x2c\x29\x37\x32\x52\x3c\xc4\xde\x5a\x04\xa2\x62\x72\x6b\xf5\x62\x75"
- "\x76\xf5\x09\x82\x68\x69\x6e\x74\x65\x72\x6e\x61\x6c\x66\x68\x79\x62\x72\x69\x64");
-
-const auto ANDROID_POST_HANDSHAKE_RESPONSE = BUFFER_VIEW(
- "\xa1\x01\x58\x53\xa5\x01\x82\x68\x46\x49\x44\x4f\x5f\x32\x5f\x30\x68\x46\x49\x44\x4f\x5f"
- "\x32\x5f\x31\x02\x81\x63\x70\x72\x66\x03\x50\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
- "\x00\x00\x00\x00\x00\x04\xa4\x62\x72\x6b\xf5\x62\x75\x70\xf5\x62\x75\x76\xf5\x64\x70\x6c"
- "\x61\x74\xf4\x09\x82\x68\x69\x6e\x74\x65\x72\x6e\x61\x6c\x66\x68\x79\x62\x72\x69\x64");
-
void CheckIphonePostHandshakeResponse(const PostHandshakeResponse &phr)
{
EXPECT_EQ(phr.m_versions, (std::vector<std::string>{"FIDO_2_0", "FIDO_2_1"}));
void CheckAndroidUpdateMessage(const UpdateMessage &upMsg, const BufferView &rawUpdateMsg)
{
ASSERT_NE(upMsg.GetLinkData(), std::nullopt);
- EXPECT_EQ(ToBufferView(upMsg.GetLinkData()->m_contactId), rawUpdateMsg.substr(174, 152));
- EXPECT_EQ(ToBufferView(upMsg.GetLinkData()->m_linkId), rawUpdateMsg.substr(328, 8));
- EXPECT_EQ(ToBufferView(upMsg.GetLinkData()->m_linkSecret), rawUpdateMsg.substr(339, 32));
+ EXPECT_EQ(ToBufferView(upMsg.GetLinkData()->m_contactId), rawUpdateMsg.substr(185, 152));
+ EXPECT_EQ(ToBufferView(upMsg.GetLinkData()->m_linkId), rawUpdateMsg.substr(339, 8));
+ EXPECT_EQ(ToBufferView(upMsg.GetLinkData()->m_linkSecret), rawUpdateMsg.substr(350, 32));
EXPECT_EQ(ToBufferView(upMsg.GetLinkData()->m_authenticatorPublicKey),
- rawUpdateMsg.substr(374, 65));
- EXPECT_EQ(ToBufferView(upMsg.GetLinkData()->m_authenticatorName), rawUpdateMsg.substr(441, 16));
+ rawUpdateMsg.substr(385, 65));
+ EXPECT_EQ(ToBufferView(upMsg.GetLinkData()->m_authenticatorName), rawUpdateMsg.substr(452, 5));
EXPECT_EQ(ToBufferView(upMsg.GetLinkData()->m_handshakeSignature),
rawUpdateMsg.substr(460, 32));
}
-const auto IPHONE_EXAMPLE_MAKE_CREDENTIAL_RAW_REQUEST = BUFFER_VIEW(
- "\x01\x01\xa4\x01\x58\x20\x44\x13\x6f\xa3\x55\xb3\x67\x8a\x11\x46\xad\x16\xf7\xe8\x64\x9e"
- "\x94\xfb\x4f\xc2\x1f\xe7\x7e\x83\x10\xc0\x60\xf6\x1c\xaa\xff\x8a\x02\xa2\x62\x69\x64\x68"
- "\x61\x63\x6d\x65\x2e\x63\x6f\x6d\x64\x6e\x61\x6d\x65\x64\x41\x63\x6d\x65\x03\xa3\x62\x69"
- "\x64\x48\x00\x01\x02\x03\x04\x05\x06\x07\x64\x6e\x61\x6d\x65\x69\x75\x73\x65\x72\x20\x6e"
- "\x61\x6d\x65\x6b\x64\x69\x73\x70\x6c\x61\x79\x4e\x61\x6d\x65\x71\x75\x73\x65\x72\x20\x64"
- "\x69\x73\x70\x6c\x61\x79\x20\x6e\x61\x6d\x65\x04\x82\xa2\x63\x61\x6c\x67\x26\x64\x74\x79"
- "\x70\x65\x6a\x70\x75\x62\x6c\x69\x63\x2d\x6b\x65\x79\xa2\x63\x61\x6c\x67\x39\x01\x00\x64"
- "\x74\x79\x70\x65\x6a\x70\x75\x62\x6c\x69\x63\x2d\x6b\x65\x79");
-
-const auto IPHONE_EXAMPLE_MAKE_CREDENTIAL_RAW_RESPONSE = BUFFER_VIEW(
- "\x01\x00\xa4\x01\x64\x6e\x6f\x6e\x65\x02\x58\x98\x11\x94\x22\x8d\xa8\xfd\xbd\xee\xfd"
- "\x26\x1b\xd7\xb6\x59\x5c\xfd\x70\xa5\x0d\x70\xc6\x40\x7b\xcf\x01\x3d\xe9\x6d\x4e\xfb"
- "\x17\xde\x5d\x00\x00\x00\x00\xfb\xfc\x30\x07\x15\x4e\x4e\xcc\x8c\x0b\x6e\x02\x05\x57"
- "\xd7\xbd\x00\x14\x7c\xe0\x64\xdc\xae\x09\x40\x6a\x9a\x2f\xae\x13\x5e\x01\x63\x4a\xbd"
- "\x50\xb5\xd2\xa5\x01\x02\x03\x26\x20\x01\x21\x58\x20\x70\x9a\xdb\x3b\x95\x96\x6b\xc0"
- "\x9c\x68\x31\xf2\xf7\x38\xfe\x19\x1d\x7c\x55\xb7\xbf\xde\xb7\x11\x5a\x5e\xab\xef\x10"
- "\xaf\xb4\x6d\x22\x58\x20\xab\x15\xf0\x1d\x37\x94\xcb\x9b\xaf\xd3\x92\xc3\x0a\x07\x03"
- "\x82\xa8\xc7\xc5\x88\x8e\xed\xdf\xaf\xb0\x80\x39\x4d\x4e\x67\x8c\xd1\x03\xa0\x06\xa0");
-
-const auto IPHONE_EXAMPLE_MAKE_CREDENTIAL_RESPONSE_PUBLIC_KEY_DER = BUFFER_VIEW(
- "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01"
- "\x07\x03\x42\x00\x04\x70\x9a\xdb\x3b\x95\x96\x6b\xc0\x9c\x68\x31\xf2\xf7\x38\xfe\x19\x1d"
- "\x7c\x55\xb7\xbf\xde\xb7\x11\x5a\x5e\xab\xef\x10\xaf\xb4\x6d\xab\x15\xf0\x1d\x37\x94\xcb"
- "\x9b\xaf\xd3\x92\xc3\x0a\x07\x03\x82\xa8\xc7\xc5\x88\x8e\xed\xdf\xaf\xb0\x80\x39\x4d\x4e"
- "\x67\x8c\xd1");
-
const auto CTAP_SHUTDOWN_MESSAGE = BUFFER_VIEW("\x00");
} // namespace
TEST(CtapMessageProcessor, IphoneExampleMakeCredential)
{
- auto rawMCResp = IPHONE_EXAMPLE_MAKE_CREDENTIAL_RAW_RESPONSE;
+ auto rawMCResp = BUFFER_VIEW(IPHONE_EXAMPLE_MAKE_CREDENTIAL_RAW_RESPONSE);
auto tunnelUPtr = std::make_unique<MEncryptedTunnel>(std::vector<CryptoBuffer>{
- ToBuffer(IPHONE_POST_HANDSHAKE_RESPONSE),
+ BUFFER(IPHONE_EXAMPLE_POST_HANDSHAKE_RAW_RESPONSE),
ToBuffer(rawMCResp),
});
auto tunnel = tunnelUPtr.get();
// Sent messages
ASSERT_EQ(tunnel->WrittenMessages().size(), 2);
EXPECT_EQ(ToBufferView(tunnel->WrittenMessages()[0]),
- IPHONE_EXAMPLE_MAKE_CREDENTIAL_RAW_REQUEST);
+ BUFFER_VIEW(IPHONE_EXAMPLE_MAKE_CREDENTIAL_RAW_REQUEST));
EXPECT_EQ(ToBufferView(tunnel->WrittenMessages()[1]), CTAP_SHUTDOWN_MESSAGE);
// res.getInfo
CheckIphonePostHandshakeResponse(res.getInfo);
ASSERT_NE(res.response.m_authData.m_attestationData, std::nullopt);
EXPECT_EQ(res.response.m_authData.m_attestationData->m_credentialId, rawMCResp.substr(67, 20));
EXPECT_EQ(ToBufferView(res.response.m_authData.m_attestationData->m_publicKeyDer),
- IPHONE_EXAMPLE_MAKE_CREDENTIAL_RESPONSE_PUBLIC_KEY_DER);
+ BUFFER_VIEW(IPHONE_EXAMPLE_MAKE_CREDENTIAL_RAW_RESPONSE_PUBLIC_KEY_DER));
EXPECT_EQ(res.response.m_authData.m_attestationData->m_alg,
WAUTHN_COSE_ALGORITHM_ECDSA_P256_WITH_SHA256);
EXPECT_EQ(ToBufferView(res.response.m_attestationObject), rawMCResp.substr(2));
EXPECT_EQ(res.response.m_largeBlobKey, Buffer{});
}
-namespace {
-
-const auto ANDROID_EXAMPLE_MAKE_CREDENTIAL_RAW_REQUEST = BUFFER_VIEW(
- "\x01\x01\xa4\x01\x58\x20\x44\x13\x6f\xa3\x55\xb3\x67\x8a\x11\x46\xad\x16\xf7\xe8\x64\x9e"
- "\x94\xfb\x4f\xc2\x1f\xe7\x7e\x83\x10\xc0\x60\xf6\x1c\xaa\xff\x8a\x02\xa2\x62\x69\x64\x68"
- "\x61\x63\x6d\x65\x2e\x63\x6f\x6d\x64\x6e\x61\x6d\x65\x64\x41\x63\x6d\x65\x03\xa3\x62\x69"
- "\x64\x48\x00\x01\x02\x03\x04\x05\x06\x07\x64\x6e\x61\x6d\x65\x69\x75\x73\x65\x72\x20\x6e"
- "\x61\x6d\x65\x6b\x64\x69\x73\x70\x6c\x61\x79\x4e\x61\x6d\x65\x71\x75\x73\x65\x72\x20\x64"
- "\x69\x73\x70\x6c\x61\x79\x20\x6e\x61\x6d\x65\x04\x82\xa2\x63\x61\x6c\x67\x26\x64\x74\x79"
- "\x70\x65\x6a\x70\x75\x62\x6c\x69\x63\x2d\x6b\x65\x79\xa2\x63\x61\x6c\x67\x39\x01\x00\x64"
- "\x74\x79\x70\x65\x6a\x70\x75\x62\x6c\x69\x63\x2d\x6b\x65\x79");
-
-const auto ANDROID_EXAMPLE_MAKE_CREDENTIAL_RAW_RESPONSE = BUFFER_VIEW(
- "\x01\x00\xa3\x01\x64\x6e\x6f\x6e\x65\x02\x58\xa4\x11\x94\x22\x8d\xa8\xfd\xbd\xee\xfd\x26"
- "\x1b\xd7\xb6\x59\x5c\xfd\x70\xa5\x0d\x70\xc6\x40\x7b\xcf\x01\x3d\xe9\x6d\x4e\xfb\x17\xde"
- "\x45\x00\x00\x00\x00\x53\x41\x4d\x53\x55\x4e\x47\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
- "\x20\xde\x2f\x46\x7f\xb4\xf6\xc7\xbe\x49\x42\xdb\x80\xa4\x3d\xcd\xf2\x1b\xc9\xf7\x31\x54"
- "\x9c\x7b\x58\xc8\x2b\x22\x46\x1d\x17\x43\x1d\xa5\x01\x02\x03\x26\x20\x01\x21\x58\x20\x9f"
- "\x80\x80\xc9\xac\xf9\x92\x66\x12\x3a\xcf\x0e\xf8\x7e\x5c\x3f\xbf\xb4\x34\xc8\x96\xa0\xe2"
- "\x54\xbe\xeb\x8c\x62\xb2\x5d\x54\xc8\x22\x58\x20\xc2\x9f\x0d\xc8\x8d\x81\x8f\x57\x51\x46"
- "\x93\x31\xc0\x59\xce\x13\x5f\xbb\x3d\x65\x39\xc2\x89\x68\x77\x52\xb8\x80\x0c\x86\xf9\xd6"
- "\x03\xa0");
-
-const auto ANDROID_EXAMPLE_MAKE_CREDENTIAL_RESPONSE_PUBLIC_KEY_DER = BUFFER_VIEW(
- "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01"
- "\x07\x03\x42\x00\x04\x9f\x80\x80\xc9\xac\xf9\x92\x66\x12\x3a\xcf\x0e\xf8\x7e\x5c\x3f\xbf"
- "\xb4\x34\xc8\x96\xa0\xe2\x54\xbe\xeb\x8c\x62\xb2\x5d\x54\xc8\xc2\x9f\x0d\xc8\x8d\x81\x8f"
- "\x57\x51\x46\x93\x31\xc0\x59\xce\x13\x5f\xbb\x3d\x65\x39\xc2\x89\x68\x77\x52\xb8\x80\x0c"
- "\x86\xf9\xd6");
-
-} // namespace
-
TEST(CtapMessageProcessor, AndroidExampleMakeCredentialWithoutUpdateMessage)
{
- auto rawMCResp = ANDROID_EXAMPLE_MAKE_CREDENTIAL_RAW_RESPONSE;
+ auto rawMCResp = BUFFER_VIEW(ANDROID_EXAMPLE_MAKE_CREDENTIAL_RAW_RESPONSE);
auto tunnelUPtr = std::make_unique<MEncryptedTunnel>(std::vector<CryptoBuffer>{
- ToBuffer(ANDROID_POST_HANDSHAKE_RESPONSE),
+ BUFFER(ANDROID_EXAMPLE_POST_HANDSHAKE_RAW_RESPONSE),
ToBuffer(rawMCResp),
});
auto tunnel = tunnelUPtr.get();
// Sent messages
ASSERT_EQ(tunnel->WrittenMessages().size(), 2);
EXPECT_EQ(ToBufferView(tunnel->WrittenMessages()[0]),
- ANDROID_EXAMPLE_MAKE_CREDENTIAL_RAW_REQUEST);
+ BUFFER_VIEW(ANDROID_EXAMPLE_MAKE_CREDENTIAL_RAW_REQUEST));
EXPECT_EQ(ToBufferView(tunnel->WrittenMessages()[1]), CTAP_SHUTDOWN_MESSAGE);
// res.getInfo
CheckAndroidPostHandshakeResponse(res.getInfo);
ASSERT_NE(res.response.m_authData.m_attestationData, std::nullopt);
EXPECT_EQ(res.response.m_authData.m_attestationData->m_credentialId, rawMCResp.substr(67, 32));
EXPECT_EQ(ToBufferView(res.response.m_authData.m_attestationData->m_publicKeyDer),
- ANDROID_EXAMPLE_MAKE_CREDENTIAL_RESPONSE_PUBLIC_KEY_DER);
+ BUFFER_VIEW(ANDROID_EXAMPLE_MAKE_CREDENTIAL_RAW_RESPONSE_PUBLIC_KEY_DER));
EXPECT_EQ(res.response.m_authData.m_attestationData->m_alg,
WAUTHN_COSE_ALGORITHM_ECDSA_P256_WITH_SHA256);
EXPECT_EQ(ToBufferView(res.response.m_attestationObject), rawMCResp.substr(2));
EXPECT_EQ(res.response.m_largeBlobKey, Buffer{});
}
-namespace {
-
-const auto ANDROID_EXAMPLE_RAW_UPDATE_MSG = BUFFER_VIEW(
- "\x02\xa2\x00\x58\xa4\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
- "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
- "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
- "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
- "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
- "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
- "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
- "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xa7\x01\x58\x98\x63\x76"
- "\x33\x31\x4f\x51\x6b\x6a\x50\x4c\x73\x3a\x41\x50\x41\x39\x31\x62\x47\x6a\x54\x72\x41\x38"
- "\x33\x33\x61\x57\x62\x42\x43\x49\x79\x58\x73\x74\x78\x43\x30\x36\x52\x71\x43\x78\x35\x43"
- "\x34\x61\x76\x33\x65\x37\x57\x4d\x6a\x39\x77\x64\x59\x44\x67\x64\x6d\x48\x69\x4e\x48\x4a"
- "\x30\x68\x4f\x36\x4a\x46\x78\x5f\x39\x69\x5a\x6d\x68\x53\x55\x30\x62\x6f\x6c\x61\x6d\x71"
- "\x63\x50\x33\x59\x6b\x71\x66\x6e\x38\x4d\x55\x65\x74\x4e\x35\x4c\x58\x73\x45\x6d\x6f\x55"
- "\x55\x72\x56\x68\x4a\x56\x51\x33\x48\x5a\x38\x68\x6c\x61\x6c\x53\x44\x4c\x61\x4e\x6d\x6c"
- "\x77\x38\x41\x51\x70\x79\x65\x67\x6b\x5a\x34\x65\x61\x73\x78\x73\x62\x6f\x02\x48\x9c\x23"
- "\xe9\xd7\x3e\x9a\x8b\x74\x03\x58\x20\xc5\x55\xf3\x1b\xe9\x76\xc7\x2b\x3f\x7f\x42\xd4\x19"
- "\x99\x06\x73\x2d\xde\xa3\x19\xd0\x07\x83\xae\xa8\x09\xab\x38\x9c\xd7\x46\x4f\x04\x58\x41"
- "\x04\x61\x65\xd4\xff\x17\x71\x10\x23\xc6\xc9\x75\x58\x87\xf6\x32\x1f\x51\x4d\x22\xe1\xbe"
- "\x70\xdd\xe6\x7e\x7b\x39\x63\xf1\x38\x38\x7b\x39\x4f\xad\x8b\xc6\xb7\x05\x6d\xa7\x3f\xe3"
- "\x94\x7c\x87\x52\x8d\x53\x1f\xdb\x70\xc0\x2b\x5d\xc9\xc1\xe2\xd8\x78\x1b\x1f\xbd\x3a\x05"
- "\x70\x47\x61\x6c\x61\x78\x79\x20\x53\x32\x34\x20\x55\x6c\x74\x72\x61\x06\x58\x20\x53\xb3"
- "\x57\x6b\xc6\x84\xeb\x1e\xdb\x50\xde\xad\x42\xc9\xef\x6c\xd3\x3b\x82\xb2\xa5\xfa\x76\x28"
- "\x0f\xc1\x57\x5f\x57\xdc\x0c\x63\x19\x03\xe7\xf5");
-
-} // namespace
-
-TEST(CtapMessageProcessor, AndroidExampleMakeCredentialWitUpdateMessage)
+TEST(CtapMessageProcessor, AndroidExampleMakeCredentialWithUpdateMessage)
{
- auto rawUpdateMsg = ANDROID_EXAMPLE_RAW_UPDATE_MSG;
- auto rawMCResp = ANDROID_EXAMPLE_MAKE_CREDENTIAL_RAW_RESPONSE;
+ auto rawUpdateMsg = BUFFER_VIEW(ANDROID_EXAMPLE_RAW_UPDATE_MSG);
+ auto rawMCResp = BUFFER_VIEW(ANDROID_EXAMPLE_MAKE_CREDENTIAL_RAW_RESPONSE);
auto tunnelUPtr = std::make_unique<MEncryptedTunnel>(std::vector<CryptoBuffer>{
- ToBuffer(ANDROID_POST_HANDSHAKE_RESPONSE),
+ BUFFER(ANDROID_EXAMPLE_POST_HANDSHAKE_RAW_RESPONSE),
ToBuffer(rawUpdateMsg),
ToBuffer(rawMCResp),
});
// Sent messages
ASSERT_EQ(tunnel->WrittenMessages().size(), 2);
EXPECT_EQ(ToBufferView(tunnel->WrittenMessages()[0]),
- ANDROID_EXAMPLE_MAKE_CREDENTIAL_RAW_REQUEST);
+ BUFFER_VIEW(ANDROID_EXAMPLE_MAKE_CREDENTIAL_RAW_REQUEST));
EXPECT_EQ(ToBufferView(tunnel->WrittenMessages()[1]), CTAP_SHUTDOWN_MESSAGE);
// res.getInfo
CheckAndroidPostHandshakeResponse(res.getInfo);
ASSERT_NE(res.response.m_authData.m_attestationData, std::nullopt);
EXPECT_EQ(res.response.m_authData.m_attestationData->m_credentialId, rawMCResp.substr(67, 32));
EXPECT_EQ(ToBufferView(res.response.m_authData.m_attestationData->m_publicKeyDer),
- ANDROID_EXAMPLE_MAKE_CREDENTIAL_RESPONSE_PUBLIC_KEY_DER);
+ BUFFER_VIEW(ANDROID_EXAMPLE_MAKE_CREDENTIAL_RAW_RESPONSE_PUBLIC_KEY_DER));
EXPECT_EQ(res.response.m_authData.m_attestationData->m_alg,
WAUTHN_COSE_ALGORITHM_ECDSA_P256_WITH_SHA256);
EXPECT_EQ(ToBufferView(res.response.m_attestationObject), rawMCResp.substr(2));
EXPECT_EQ(res.response.m_largeBlobKey, Buffer{});
}
-namespace {
-
-const auto IPHONE_EXAMPLE_GET_ASSERTION_RAW_REQUEST = BUFFER_VIEW(
- "\x01\x02\xa4\x01\x68\x61\x63\x6d\x65\x2e\x63\x6f\x6d\x02\x58\x20\x44\x13\x6f\xa3\x55\xb3"
- "\x67\x8a\x11\x46\xad\x16\xf7\xe8\x64\x9e\x94\xfb\x4f\xc2\x1f\xe7\x7e\x83\x10\xc0\x60\xf6"
- "\x1c\xaa\xff\x8a\x03\x81\xa3\x62\x69\x64\x54\xef\x42\x5d\x64\xe3\xed\xe0\xaf\x1e\x44\xe8"
- "\xd4\xf3\xd7\x8c\xa0\x93\x3c\x39\xfc\x64\x74\x79\x70\x65\x6a\x70\x75\x62\x6c\x69\x63\x2d"
- "\x6b\x65\x79\x6a\x74\x72\x61\x6e\x73\x70\x6f\x72\x74\x73\x82\x66\x68\x79\x62\x72\x69\x64"
- "\x68\x69\x6e\x74\x65\x72\x6e\x61\x6c\x05\xa1\x62\x75\x76\xf5");
-
-const auto IPHONE_EXAMPLE_GET_ASSERTION_RAW_RESPONSE = BUFFER_VIEW(
- "\x01\x00\xa5\x01\xa2\x62\x69\x64\x54\xef\x42\x5d\x64\xe3\xed\xe0\xaf\x1e\x44\xe8\xd4\xf3"
- "\xd7\x8c\xa0\x93\x3c\x39\xfc\x64\x74\x79\x70\x65\x6a\x70\x75\x62\x6c\x69\x63\x2d\x6b\x65"
- "\x79\x02\x58\x25\x11\x94\x22\x8d\xa8\xfd\xbd\xee\xfd\x26\x1b\xd7\xb6\x59\x5c\xfd\x70\xa5"
- "\x0d\x70\xc6\x40\x7b\xcf\x01\x3d\xe9\x6d\x4e\xfb\x17\xde\x1d\x00\x00\x00\x00\x03\x58\x48"
- "\x30\x46\x02\x21\x00\xf7\x3d\xb9\x8b\xc4\xc4\xc5\x08\xf8\x24\x3e\xdc\x79\x04\x26\x24\x69"
- "\xb7\xa9\x4a\xa2\x5a\xac\x49\x6c\x72\x3a\xf2\x31\x61\x7b\x4a\x02\x21\x00\xc9\x94\x3c\x24"
- "\x15\x9e\x94\xee\x55\xb3\xba\xe9\x5c\xfd\xb9\xbd\x08\xc0\xef\x10\x2d\xe9\x75\xef\xcf\x27"
- "\x1c\x16\x5b\x69\xb9\xe1\x04\xa3\x62\x69\x64\x48\x00\x01\x02\x03\x04\x05\x06\x07\x64\x6e"
- "\x61\x6d\x65\x60\x6b\x64\x69\x73\x70\x6c\x61\x79\x4e\x61\x6d\x65\x60\x08\xa0");
-
-const auto IPHONE_EXAMPLE_GET_ASSERTION_CREDENTIAL_ID =
- BUFFER_VIEW("\xef\x42\x5d\x64\xe3\xed\xe0\xaf\x1e\x44\xe8\xd4\xf3\xd7\x8c\xa0\x93\x3c\x39\xfc");
-
-} // namespace
-
TEST(CtapMessageProcessor, IphoneExampleGetAssertion)
{
- auto rawGAResp = IPHONE_EXAMPLE_GET_ASSERTION_RAW_RESPONSE;
+ auto rawGAResp = BUFFER_VIEW(IPHONE_EXAMPLE_GET_ASSERTION_RAW_RESPONSE);
auto tunnelUPtr = std::make_unique<MEncryptedTunnel>(std::vector<CryptoBuffer>{
- ToBuffer(IPHONE_POST_HANDSHAKE_RESPONSE),
+ BUFFER(IPHONE_EXAMPLE_POST_HANDSHAKE_RAW_RESPONSE),
ToBuffer(rawGAResp),
});
auto tunnel = tunnelUPtr.get();
[&](const auto &clientData, const auto &options) {
return cmp.GetAssertion(clientData, options);
},
- IPHONE_EXAMPLE_GET_ASSERTION_CREDENTIAL_ID);
+ BUFFER_VIEW(IPHONE_EXAMPLE_GET_ASSERTION_RAW_RESPONSE_CREDENTIAL_ID));
// Sent messages
ASSERT_EQ(tunnel->WrittenMessages().size(), 2);
- EXPECT_EQ(ToBufferView(tunnel->WrittenMessages()[0]), IPHONE_EXAMPLE_GET_ASSERTION_RAW_REQUEST);
+ EXPECT_EQ(ToBufferView(tunnel->WrittenMessages()[0]),
+ BUFFER_VIEW(IPHONE_EXAMPLE_GET_ASSERTION_RAW_REQUEST));
EXPECT_EQ(ToBufferView(tunnel->WrittenMessages()[1]), CTAP_SHUTDOWN_MESSAGE);
// res.getInfo
CheckIphonePostHandshakeResponse(res.getInfo);
EXPECT_EQ(res.response.m_userDisplayName, "");
}
-namespace {
-
-const auto ANDROID_EXAMPLE_GET_ASSERTION_RAW_REQUEST = BUFFER_VIEW(
- "\x01\x02\xa4\x01\x68\x61\x63\x6d\x65\x2e\x63\x6f\x6d\x02\x58\x20\x44\x13\x6f\xa3\x55\xb3"
- "\x67\x8a\x11\x46\xad\x16\xf7\xe8\x64\x9e\x94\xfb\x4f\xc2\x1f\xe7\x7e\x83\x10\xc0\x60\xf6"
- "\x1c\xaa\xff\x8a\x03\x81\xa3\x62\x69\x64\x58\x20\x9e\x39\x33\x8a\xa5\xf2\x71\xee\x29\x8b"
- "\x9e\xab\x7a\xf2\x04\x73\xce\x0a\x6a\x5d\x1d\x8e\xf7\x14\xa7\x23\x0b\xc3\x44\x87\xea\xcd"
- "\x64\x74\x79\x70\x65\x6a\x70\x75\x62\x6c\x69\x63\x2d\x6b\x65\x79\x6a\x74\x72\x61\x6e\x73"
- "\x70\x6f\x72\x74\x73\x82\x66\x68\x79\x62\x72\x69\x64\x68\x69\x6e\x74\x65\x72\x6e\x61\x6c"
- "\x05\xa1\x62\x75\x76\xf5");
-
-const auto ANDROID_EXAMPLE_GET_ASSERTION_RAW_RESPONSE = BUFFER_VIEW(
- "\x01\x00\xa3\x01\xa2\x62\x69\x64\x58\x20\x9e\x39\x33\x8a\xa5\xf2\x71\xee\x29\x8b\x9e\xab"
- "\x7a\xf2\x04\x73\xce\x0a\x6a\x5d\x1d\x8e\xf7\x14\xa7\x23\x0b\xc3\x44\x87\xea\xcd\x64\x74"
- "\x79\x70\x65\x6a\x70\x75\x62\x6c\x69\x63\x2d\x6b\x65\x79\x02\x58\x25\x11\x94\x22\x8d\xa8"
- "\xfd\xbd\xee\xfd\x26\x1b\xd7\xb6\x59\x5c\xfd\x70\xa5\x0d\x70\xc6\x40\x7b\xcf\x01\x3d\xe9"
- "\x6d\x4e\xfb\x17\xde\x05\x00\x00\x00\x00\x03\x58\x48\x30\x46\x02\x21\x00\xc4\x34\xe3\x98"
- "\x33\xac\xd5\x9a\x88\x5d\x00\x90\x56\x0f\x10\xde\x7a\x0e\xd0\xd4\x02\x76\xcc\x28\x74\xf0"
- "\xa9\x39\x7e\x19\x08\x19\x02\x21\x00\xff\xf1\x13\x5b\x28\x75\xce\xe5\x1c\x9b\x1c\xaa\xfe"
- "\x6e\x41\x5d\x67\x7c\x0c\xee\x64\xe6\x18\xd5\x81\x95\xd1\xae\xc1\xa2\xdb\x5a");
-
-const auto ANDROID_EXAMPLE_GET_ASSERTION_CREDENTIAL_ID =
- BUFFER_VIEW("\x9e\x39\x33\x8a\xa5\xf2\x71\xee\x29\x8b\x9e\xab\x7a\xf2\x04\x73\xce\x0a"
- "\x6a\x5d\x1d\x8e\xf7\x14\xa7\x23\x0b\xc3\x44\x87\xea\xcd");
-
-} // namespace
-
TEST(CtapMessageProcessor, AndroidExampleGetAssertionWithoutUpdate)
{
- auto rawGAResp = ANDROID_EXAMPLE_GET_ASSERTION_RAW_RESPONSE;
+ auto rawGAResp = BUFFER_VIEW(ANDROID_EXAMPLE_GET_ASSERTION_RAW_RESPONSE);
auto tunnelUPtr = std::make_unique<MEncryptedTunnel>(std::vector<CryptoBuffer>{
- ToBuffer(ANDROID_POST_HANDSHAKE_RESPONSE),
+ BUFFER(ANDROID_EXAMPLE_POST_HANDSHAKE_RAW_RESPONSE),
ToBuffer(rawGAResp),
});
auto tunnel = tunnelUPtr.get();
[&](const auto &clientData, const auto &options) {
return cmp.GetAssertion(clientData, options);
},
- ANDROID_EXAMPLE_GET_ASSERTION_CREDENTIAL_ID);
+ BUFFER_VIEW(ANDROID_EXAMPLE_GET_ASSERTION_RAW_RESPONSE_CREDENTIAL_ID));
// Sent messages
ASSERT_EQ(tunnel->WrittenMessages().size(), 2);
EXPECT_EQ(ToBufferView(tunnel->WrittenMessages()[0]),
- ANDROID_EXAMPLE_GET_ASSERTION_RAW_REQUEST);
+ BUFFER_VIEW(ANDROID_EXAMPLE_GET_ASSERTION_RAW_REQUEST));
EXPECT_EQ(ToBufferView(tunnel->WrittenMessages()[1]), CTAP_SHUTDOWN_MESSAGE);
// res.getInfo
CheckAndroidPostHandshakeResponse(res.getInfo);
EXPECT_EQ(res.response.m_userDisplayName, "");
}
-TEST(CtapMessageProcessor, AndroidExampleGetAssertionWitUpdate)
+TEST(CtapMessageProcessor, AndroidExampleGetAssertionWithUpdate)
{
- auto rawUpdateMsg = ANDROID_EXAMPLE_RAW_UPDATE_MSG;
- auto rawGAResp = ANDROID_EXAMPLE_GET_ASSERTION_RAW_RESPONSE;
+ auto rawUpdateMsg = BUFFER_VIEW(ANDROID_EXAMPLE_RAW_UPDATE_MSG);
+ auto rawGAResp = BUFFER_VIEW(ANDROID_EXAMPLE_GET_ASSERTION_RAW_RESPONSE);
auto tunnelUPtr = std::make_unique<MEncryptedTunnel>(std::vector<CryptoBuffer>{
- ToBuffer(ANDROID_POST_HANDSHAKE_RESPONSE),
+ BUFFER(ANDROID_EXAMPLE_POST_HANDSHAKE_RAW_RESPONSE),
ToBuffer(rawUpdateMsg),
ToBuffer(rawGAResp),
});
[&](const auto &clientData, const auto &options) {
return cmp.GetAssertion(clientData, options);
},
- ANDROID_EXAMPLE_GET_ASSERTION_CREDENTIAL_ID);
+ BUFFER_VIEW(ANDROID_EXAMPLE_GET_ASSERTION_RAW_RESPONSE_CREDENTIAL_ID));
// Sent messages
ASSERT_EQ(tunnel->WrittenMessages().size(), 2);
EXPECT_EQ(ToBufferView(tunnel->WrittenMessages()[0]),
- ANDROID_EXAMPLE_GET_ASSERTION_RAW_REQUEST);
+ BUFFER_VIEW(ANDROID_EXAMPLE_GET_ASSERTION_RAW_REQUEST));
EXPECT_EQ(ToBufferView(tunnel->WrittenMessages()[1]), CTAP_SHUTDOWN_MESSAGE);
// res.getInfo
CheckAndroidPostHandshakeResponse(res.getInfo);
static constexpr auto doTest = [](const auto &clientData, const auto &options) {
auto cmp = CtapMessageProcessor{};
cmp.SetEncryptedTunnel(std::make_unique<MEncryptedTunnel>(
- std::vector<CryptoBuffer>{ToBuffer(IPHONE_POST_HANDSHAKE_RESPONSE)}));
+ std::vector<CryptoBuffer>{BUFFER(IPHONE_EXAMPLE_POST_HANDSHAKE_RAW_RESPONSE)}));
EXPECT_THROW(cmp.MakeCredential(clientData, options), Exception::InvalidParam);
};
WithDefaultMCArgs([](auto &clientData, auto &options) {
static constexpr auto doTest = [](const auto &clientData, const auto &options) {
auto cmp = CtapMessageProcessor{};
cmp.SetEncryptedTunnel(std::make_unique<MEncryptedTunnel>(
- std::vector<CryptoBuffer>{ToBuffer(IPHONE_POST_HANDSHAKE_RESPONSE)}));
+ std::vector<CryptoBuffer>{BUFFER(IPHONE_EXAMPLE_POST_HANDSHAKE_RAW_RESPONSE)}));
EXPECT_THROW(cmp.GetAssertion(clientData, options), Exception::InvalidParam);
};
WithDefaultGAArgs(
});
};
- auto rawPostHandshakeResp = IPHONE_POST_HANDSHAKE_RESPONSE;
- auto rawMCResp = IPHONE_EXAMPLE_MAKE_CREDENTIAL_RAW_RESPONSE;
+ auto rawPostHandshakeResp = BUFFER_VIEW(IPHONE_EXAMPLE_POST_HANDSHAKE_RAW_RESPONSE);
+ auto rawMCResp = BUFFER_VIEW(IPHONE_EXAMPLE_MAKE_CREDENTIAL_RAW_RESPONSE);
for (size_t i = 0; i < rawPostHandshakeResp.size(); ++i)
test(BufferView{rawPostHandshakeResp.data(), i}, rawMCResp);
for (size_t i = 0; i < rawMCResp.size(); ++i)
});
};
- auto rawPostHandshakeResp = ANDROID_POST_HANDSHAKE_RESPONSE;
- auto rawUpdateMsg = ANDROID_EXAMPLE_RAW_UPDATE_MSG;
- auto rawMCResp = ANDROID_EXAMPLE_MAKE_CREDENTIAL_RAW_RESPONSE;
+ auto rawPostHandshakeResp = BUFFER_VIEW(ANDROID_EXAMPLE_POST_HANDSHAKE_RAW_RESPONSE);
+ auto rawUpdateMsg = BUFFER_VIEW(ANDROID_EXAMPLE_RAW_UPDATE_MSG);
+ auto rawMCResp = BUFFER_VIEW(ANDROID_EXAMPLE_MAKE_CREDENTIAL_RAW_RESPONSE);
for (size_t i = 0; i < rawPostHandshakeResp.size(); ++i)
test(BufferView{rawPostHandshakeResp.data(), i}, rawUpdateMsg, rawMCResp);
for (size_t i = 0; i < rawUpdateMsg.size(); ++i)
[&](const auto &clientData, const auto &options) {
EXPECT_THROW(cmp.GetAssertion(clientData, options), Exception::Unknown);
},
- ANDROID_EXAMPLE_GET_ASSERTION_CREDENTIAL_ID);
+ BUFFER_VIEW(ANDROID_EXAMPLE_GET_ASSERTION_RAW_RESPONSE_CREDENTIAL_ID));
};
- auto rawPostHandshakeResp = IPHONE_POST_HANDSHAKE_RESPONSE;
- auto rawGAResp = ANDROID_EXAMPLE_GET_ASSERTION_RAW_RESPONSE;
+ auto rawPostHandshakeResp = BUFFER_VIEW(IPHONE_EXAMPLE_POST_HANDSHAKE_RAW_RESPONSE);
+ auto rawGAResp = BUFFER_VIEW(ANDROID_EXAMPLE_GET_ASSERTION_RAW_RESPONSE);
for (size_t i = 0; i < rawPostHandshakeResp.size(); ++i)
test(BufferView{rawPostHandshakeResp.data(), i}, rawGAResp);
for (size_t i = 0; i < rawGAResp.size(); ++i)
[&](const auto &clientData, const auto &options) {
EXPECT_THROW(cmp.GetAssertion(clientData, options), Exception::Unknown);
},
- ANDROID_EXAMPLE_GET_ASSERTION_CREDENTIAL_ID);
+ BUFFER_VIEW(ANDROID_EXAMPLE_GET_ASSERTION_RAW_RESPONSE_CREDENTIAL_ID));
};
- auto rawPostHandshakeResp = ANDROID_POST_HANDSHAKE_RESPONSE;
- auto rawUpdateMsg = ANDROID_EXAMPLE_RAW_UPDATE_MSG;
- auto rawGAResp = ANDROID_EXAMPLE_GET_ASSERTION_RAW_RESPONSE;
+ auto rawPostHandshakeResp = BUFFER_VIEW(ANDROID_EXAMPLE_POST_HANDSHAKE_RAW_RESPONSE);
+ auto rawUpdateMsg = BUFFER_VIEW(ANDROID_EXAMPLE_RAW_UPDATE_MSG);
+ auto rawGAResp = BUFFER_VIEW(ANDROID_EXAMPLE_GET_ASSERTION_RAW_RESPONSE);
for (size_t i = 0; i < rawPostHandshakeResp.size(); ++i)
test(BufferView{rawPostHandshakeResp.data(), i}, rawUpdateMsg, rawGAResp);
for (size_t i = 0; i < rawUpdateMsg.size(); ++i)
m_cancelFacilitator.WithCancelCheck([&] { MEncryptedTunnel::WriteBinary(std::move(msg)); });
}
+ void CloseConnectionAfter(uint32_t) override {}
+
void Cancel() override { m_cancelFacilitator.Cancel(); }
private:
TEST(CtapMessageProcessor, MakeCredential_without_update_message_cancel_from_the_other_thread)
{
auto makeCMP = [&] {
- CtapMessageProcessor cmp;
- cmp.SetEncryptedTunnel(
+ auto cmp = std::make_unique<CtapMessageProcessor>();
+ cmp->SetEncryptedTunnel(
std::make_unique<CancellationMEncryptedTunnel>(std::vector<CryptoBuffer>{
- ToBuffer(IPHONE_POST_HANDSHAKE_RESPONSE),
- ToBuffer(IPHONE_EXAMPLE_MAKE_CREDENTIAL_RAW_RESPONSE),
+ BUFFER(IPHONE_EXAMPLE_POST_HANDSHAKE_RAW_RESPONSE),
+ BUFFER(IPHONE_EXAMPLE_MAKE_CREDENTIAL_RAW_RESPONSE),
}));
return cmp;
};
TEST(CtapMessageProcessor, MakeCredential_with_update_message_cancel_from_the_other_thread)
{
auto makeCMP = [&] {
- CtapMessageProcessor cmp;
- cmp.SetEncryptedTunnel(
+ auto cmp = std::make_unique<CtapMessageProcessor>();
+ cmp->SetEncryptedTunnel(
std::make_unique<CancellationMEncryptedTunnel>(std::vector<CryptoBuffer>{
- ToBuffer(ANDROID_POST_HANDSHAKE_RESPONSE),
- ToBuffer(ANDROID_EXAMPLE_RAW_UPDATE_MSG),
- ToBuffer(ANDROID_EXAMPLE_MAKE_CREDENTIAL_RAW_RESPONSE),
+ BUFFER(ANDROID_EXAMPLE_POST_HANDSHAKE_RAW_RESPONSE),
+ BUFFER(ANDROID_EXAMPLE_RAW_UPDATE_MSG),
+ BUFFER(ANDROID_EXAMPLE_MAKE_CREDENTIAL_RAW_RESPONSE),
}));
return cmp;
};
TEST(CtapMessageProcessor, GetAssertion_without_update_message_cancel_from_the_other_thread)
{
auto makeCMP = [&] {
- CtapMessageProcessor cmp;
- cmp.SetEncryptedTunnel(
+ auto cmp = std::make_unique<CtapMessageProcessor>();
+ cmp->SetEncryptedTunnel(
std::make_unique<CancellationMEncryptedTunnel>(std::vector<CryptoBuffer>{
- ToBuffer(IPHONE_POST_HANDSHAKE_RESPONSE),
- ToBuffer(IPHONE_EXAMPLE_GET_ASSERTION_RAW_RESPONSE),
+ BUFFER(IPHONE_EXAMPLE_POST_HANDSHAKE_RAW_RESPONSE),
+ BUFFER(IPHONE_EXAMPLE_GET_ASSERTION_RAW_RESPONSE),
}));
return cmp;
};
200, 20, makeCMP, [](ICtapMessageProcessor &cmp) {
WithDefaultGAArgs([&](const auto &clientData,
const auto &options) { cmp.GetAssertion(clientData, options); },
- IPHONE_EXAMPLE_GET_ASSERTION_CREDENTIAL_ID);
+ BUFFER_VIEW(IPHONE_EXAMPLE_GET_ASSERTION_RAW_RESPONSE_CREDENTIAL_ID));
});
}
TEST(CtapMessageProcessor, GetAssertion_with_update_message_cancel_from_the_other_thread)
{
auto makeCMP = [&] {
- CtapMessageProcessor cmp;
- cmp.SetEncryptedTunnel(
+ auto cmp = std::make_unique<CtapMessageProcessor>();
+ cmp->SetEncryptedTunnel(
std::make_unique<CancellationMEncryptedTunnel>(std::vector<CryptoBuffer>{
- ToBuffer(ANDROID_POST_HANDSHAKE_RESPONSE),
- ToBuffer(ANDROID_EXAMPLE_RAW_UPDATE_MSG),
- ToBuffer(ANDROID_EXAMPLE_GET_ASSERTION_RAW_RESPONSE),
+ BUFFER(ANDROID_EXAMPLE_POST_HANDSHAKE_RAW_RESPONSE),
+ BUFFER(ANDROID_EXAMPLE_RAW_UPDATE_MSG),
+ BUFFER(ANDROID_EXAMPLE_GET_ASSERTION_RAW_RESPONSE),
}));
return cmp;
};
TestCancelFromTheOtherThread<ICtapMessageProcessor>(
200, 20, makeCMP, [](ICtapMessageProcessor &cmp) {
- WithDefaultGAArgs([&](const auto &clientData,
- const auto &options) { cmp.GetAssertion(clientData, options); },
- ANDROID_EXAMPLE_GET_ASSERTION_CREDENTIAL_ID);
+ WithDefaultGAArgs(
+ [&](const auto &clientData, const auto &options) {
+ cmp.GetAssertion(clientData, options);
+ },
+ BUFFER_VIEW(ANDROID_EXAMPLE_GET_ASSERTION_RAW_RESPONSE_CREDENTIAL_ID));
});
}
+namespace {
+
+class ProcessingFollowingUpdateMsgsMEncryptedTunnel : public IEncryptedTunnel {
+public:
+ ProcessingFollowingUpdateMsgsMEncryptedTunnel(std::vector<CryptoBuffer> &&messagesToRead)
+ : m_messagesToRead{std::move(messagesToRead)}
+ {
+ }
+
+ CryptoBuffer ReadBinary() override
+ {
+ m_called += "ReadBinary;";
+ if (m_nextMsgToRead == m_messagesToRead.size())
+ throw ExceptionTunnelClosed{};
+ return std::move(m_messagesToRead[m_nextMsgToRead++]);
+ }
+
+ void CloseConnectionAfter(uint32_t usecs) override
+ {
+ m_called += "CloseConnectionAfter;";
+ EXPECT_EQ(usecs, 120'000'000);
+ }
+
+ void WriteBinary(CryptoBuffer) override
+ {
+ ADD_FAILURE() << "This should not be called";
+ throw std::runtime_error{""};
+ }
+
+ void Cancel() override
+ {
+ ADD_FAILURE() << "This should not be called";
+ throw std::runtime_error{""};
+ }
+
+ std::string m_called;
+
+private:
+ std::vector<CryptoBuffer> m_messagesToRead;
+ size_t m_nextMsgToRead = 0;
+};
+
+} // namespace
+
+TEST(CtapMessageProcessor, ProcessFollowingUpdateMsgs_invalid_message_type)
+{
+ auto encryptedTunnelUPtr = std::make_unique<ProcessingFollowingUpdateMsgsMEncryptedTunnel>(
+ std::vector<CryptoBuffer>{BUFFER(ANDROID_EXAMPLE_GET_ASSERTION_RAW_RESPONSE)});
+ auto encryptedTunnel = encryptedTunnelUPtr.get();
+
+ auto cmp = CtapMessageProcessor{};
+ cmp.SetEncryptedTunnel(std::move(encryptedTunnelUPtr));
+
+ EXPECT_THROW(cmp.ProcessFollowingUpdateMsgs(
+ [](UpdateMessage &&) { ADD_FAILURE() << "This should not be called"; }),
+ Exception::Unknown);
+ EXPECT_EQ(encryptedTunnel->m_called, "CloseConnectionAfter;ReadBinary;");
+}
+
+TEST(CtapMessageProcessor, ProcessFollowingUpdateMsgs_parse_error_of_the_truncated_update_message)
+{
+ const auto EXAMPLE_UPDATE_MSG = BUFFER_VIEW(ANDROID_EXAMPLE_RAW_UPDATE_MSG);
+ for (size_t len = 0; len + 1 < EXAMPLE_UPDATE_MSG.size(); ++len) {
+ auto encryptedTunnelUPtr = std::make_unique<ProcessingFollowingUpdateMsgsMEncryptedTunnel>(
+ std::vector<CryptoBuffer>{ToBuffer(EXAMPLE_UPDATE_MSG.substr(0, len))});
+ auto encryptedTunnel = encryptedTunnelUPtr.get();
+
+ auto cmp = CtapMessageProcessor{};
+ cmp.SetEncryptedTunnel(std::move(encryptedTunnelUPtr));
+
+ EXPECT_THROW(cmp.ProcessFollowingUpdateMsgs(
+ [](UpdateMessage &&) { ADD_FAILURE() << "This should not be called"; }),
+ Exception::Unknown);
+ EXPECT_EQ(encryptedTunnel->m_called, "CloseConnectionAfter;ReadBinary;");
+ }
+}
+
+TEST(CtapMessageProcessor, ProcessFollowingUpdateMsgs_passes_parsed_messages_to_callback)
+{
+ for (size_t msgNum = 0; msgNum < 17; ++msgNum) {
+ auto messages = std::vector<CryptoBuffer>(msgNum, BUFFER(ANDROID_EXAMPLE_RAW_UPDATE_MSG));
+ for (size_t i = 0; i < msgNum; ++i) {
+ ASSERT_GE(messages[i].size(), 5);
+ messages[i].end()[-5] = i; // last byte of the handshake signature
+ }
+
+ auto encryptedTunnelUPtr =
+ std::make_unique<ProcessingFollowingUpdateMsgsMEncryptedTunnel>(std::move(messages));
+ auto encryptedTunnel = encryptedTunnelUPtr.get();
+
+ auto cmp = CtapMessageProcessor{};
+ cmp.SetEncryptedTunnel(std::move(encryptedTunnelUPtr));
+
+ size_t callbackCalledNum = 0;
+ EXPECT_NO_THROW(cmp.ProcessFollowingUpdateMsgs([&](UpdateMessage &&updateMsg) {
+ ++callbackCalledNum;
+
+ UpdateMessage expectedUpdateMsg;
+ auto bv = BUFFER_VIEW(ANDROID_EXAMPLE_RAW_UPDATE_MSG);
+ bv.remove_prefix(1);
+ expectedUpdateMsg.Deserialize(bv);
+
+ ASSERT_NE(updateMsg.GetLinkData(), std::nullopt);
+ ASSERT_NE(expectedUpdateMsg.GetLinkData(), std::nullopt);
+
+ auto &linkData = *updateMsg.GetLinkData();
+ auto &expectedLinkData = *expectedUpdateMsg.GetLinkData();
+
+ EXPECT_EQ(linkData.m_contactId, expectedLinkData.m_contactId);
+ EXPECT_EQ(linkData.m_linkId, expectedLinkData.m_linkId);
+ EXPECT_EQ(linkData.m_linkSecret, expectedLinkData.m_linkSecret);
+ EXPECT_EQ(linkData.m_authenticatorPublicKey, expectedLinkData.m_authenticatorPublicKey);
+ EXPECT_EQ(linkData.m_authenticatorName, expectedLinkData.m_authenticatorName);
+
+ auto expectedSignature = expectedLinkData.m_handshakeSignature;
+ ASSERT_FALSE(expectedSignature.empty());
+ expectedSignature.back() = callbackCalledNum - 1;
+ EXPECT_EQ(linkData.m_handshakeSignature, expectedSignature);
+ }));
+
+ std::string expectedCalled = "CloseConnectionAfter;";
+ for (size_t i = 0; i < msgNum + 1; ++i) {
+ expectedCalled += "ReadBinary;";
+ }
+ EXPECT_EQ(encryptedTunnel->m_called, expectedCalled);
+ }
+}
+
TEST(CtapMessageProcessor, RpIdValidation)
{
static constexpr uint8_t blob[] =
#include "tunnel.h"
#include <gmock/gmock.h>
+#include <limits>
#include <stdexcept>
namespace {
return msg;
}
+ void CloseConnectionAfter(uint32_t) override
+ {
+ throw std::runtime_error{"CloseConnectionAfter() should not be called"};
+ }
+
void Disconnect() override {}
void Cancel() override { throw std::runtime_error{"Cancel() should not be called"}; }
namespace {
+class CloseConnectionAfterMTunnel : public ITunnel {
+public:
+ void Connect(const std::string &, std::optional<ExtraHttpHeader>)
+ {
+ throw std::runtime_error{__FUNCTION__ + std::string{"() should not be called"}};
+ }
+
+ void WriteBinary(const std::vector<uint8_t> &)
+ {
+ throw std::runtime_error{__FUNCTION__ + std::string{"() should not be called"}};
+ }
+
+ std::vector<uint8_t> ReadBinary()
+ {
+ throw std::runtime_error{__FUNCTION__ + std::string{"() should not be called"}};
+ }
+
+ void CloseConnectionAfter(uint32_t usecs)
+ {
+ m_called += "CloseConnectionAfter;";
+ m_closeConnectionAfterUsecs = usecs;
+ }
+
+ void Disconnect()
+ {
+ throw std::runtime_error{__FUNCTION__ + std::string{"() should not be called"}};
+ }
+
+ void Cancel()
+ {
+ throw std::runtime_error{__FUNCTION__ + std::string{"() should not be called"}};
+ }
+
+ std::string m_called;
+ uint32_t m_closeConnectionAfterUsecs = 0;
+};
+
+} // namespace
+
+TEST(EncryptedTunnel, CloseConnectionAfter)
+{
+ auto tunnelUPtr = std::make_unique<CloseConnectionAfterMTunnel>();
+ auto tunnel = tunnelUPtr.get();
+ auto readerWriter = Crypto::Noise::SymmetricState::CipherState{
+ CryptoBuffer(Crypto::Noise::KEY_AND_HASH_LEN, 0x5f)};
+
+ auto encryptedTunnel = EncryptedTunnel{std::move(tunnelUPtr), readerWriter, readerWriter};
+ auto usecs =
+ get_random(std::numeric_limits<uint32_t>::min(), std::numeric_limits<uint32_t>::max());
+ encryptedTunnel.CloseConnectionAfter(usecs);
+
+ EXPECT_EQ(tunnel->m_called, "CloseConnectionAfter;");
+ EXPECT_EQ(tunnel->m_closeConnectionAfterUsecs, usecs);
+}
+
+namespace {
+
class CancellationMEchoTunnel : public ITunnel {
public:
void Connect(const std::string &, std::optional<ExtraHttpHeader>) override
return msg;
}
+ void CloseConnectionAfter(uint32_t) override
+ {
+ throw std::runtime_error{"CloseConnectionAfter() should not be called"};
+ }
+
void Disconnect() override {}
void Cancel() override { m_cancelFacilitator.Cancel(); }
throw std::runtime_error{""};
}
+ void CloseConnectionAfter(uint32_t) override
+ {
+ ADD_FAILURE() << "This should not be called";
+ throw std::runtime_error{""};
+ }
+
void Disconnect() override
{
ADD_FAILURE() << "This should not be called";
#include <cstring>
#include <iostream>
+#include <mutex>
#include <mv_barcode.h>
#include <optional>
#include <string>
+#include <thread>
#include <webauthn-hal.h>
namespace {
};
struct TestContents {
- bool status;
- std::string path;
+ std::mutex mutex;
+ bool negative;
+ bool succeeded;
+ std::string qrCodeImagePath;
Buffer credentialRawId;
Buffer userId;
unsigned transports = WAUTHN_TRANSPORT_NONE;
std::optional<LinkedData> linkedData;
- bool negative = false;
+ bool seenLastMCUpdateCallback;
+ bool seenLastGAUpdateCallback;
+
+ std::thread GAThread;
void UpdateLinkedData(const wauthn_hybrid_linked_data_s *ld)
{
linkedData->signature = ToBuffer(*ld->signature);
linkedData->tunnelServerDomain = ToBuffer(*ld->tunnel_server_domain);
linkedData->identityKey = ToBuffer(*ld->identity_key);
+ std::cout << " linked data: authenticator's public key: "
+ << LowercaseHexStringOf(linkedData->authenticatorPubKey) << std::endl;
} else {
linkedData = std::nullopt;
}
}
};
-void DisplayQR(struct TestContents &contents)
+void DisplayQR(struct TestContents &testContents)
{
int ret;
- std::string command = "chsmack -a '_' ";
- command += contents.path.c_str();
+ std::string command = "chsmack -a '_' '";
+ command += testContents.qrCodeImagePath.c_str();
+ command += '\'';
ret = system(command.c_str());
if (ret) {
std::cout << "chsmack command failed\n"
<< "System() returned: " << ret << std::endl;
- contents.status = false;
+ testContents.succeeded = false;
return;
}
command = "launch_app org.tizen.image-viewer Path ";
- command += contents.path.c_str();
+ command += testContents.qrCodeImagePath.c_str();
+ std::cout << "Launching QR code viewer..." << std::endl;
ret = system(command.c_str());
if (ret) {
std::cout << "launch_app command failed\n"
<< "System() returned: " << ret << std::endl;
- contents.status = false;
+ testContents.succeeded = false;
}
}
-void GenerateAndDisplayQR(const std::string &encoded, struct TestContents &contents)
+void GenerateAndDisplayQR(const std::string &encoded, struct TestContents &testContents)
{
int ret;
- int width = 300;
- int height = 300;
- int qr_version = 5;
+ static constexpr int width = 300;
+ static constexpr int height = 300;
+ static constexpr int qr_version = 5;
mv_engine_config_h engine_cfg = NULL;
mv_barcode_type_e type = MV_BARCODE_QR;
qr_enc_mode,
qr_ecc,
qr_version,
- contents.path.c_str(),
+ testContents.qrCodeImagePath.c_str(),
image_format);
if (ret != MEDIA_VISION_ERROR_NONE) {
std::cout << "mv_barcode_generate_image failed\n"
<< "Error code: " << ret << std::endl;
- contents.status = false;
+ testContents.succeeded = false;
} else {
- DisplayQR(contents);
+ DisplayQR(testContents);
}
}
-void DisplayQRCallback(const char *qr_contents, void *data)
+void DisplayQRCallback(const char *qrContents, void *data)
{
- std::string encoded(qr_contents);
-
+ std::cout << "qrcode_callback() was called." << std::endl;
auto testContents = static_cast<TestContents *>(data);
- if (testContents->path.empty() || !qr_contents) {
- std::cout << "qrcode_callback failed" << std::endl;
- std::cout << "QRcode path: " << testContents->path << std::endl;
- std::cout << "qr_contents: " << (qr_contents ? qr_contents : "null") << std::endl;
- testContents->status = false;
+ if (!qrContents) {
+ std::cout << "qrcode_callback(): NULL qrContents" << std::endl;
+ testContents->succeeded = false;
return;
}
+ if (testContents->qrCodeImagePath.empty()) {
+ std::cout << "qrcode_callback(): empty QR code path" << std::endl;
+ testContents->succeeded = false;
+ return;
+ }
+
if (testContents->negative)
GenerateAndDisplayQR("https://tinyurl.com/5x2bcwjy", *testContents);
- else
- GenerateAndDisplayQR(encoded, *testContents);
+ else {
+ GenerateAndDisplayQR(qrContents, *testContents);
+ }
}
void MCCallback(const wauthn_pubkey_credential_attestation_s *pubkey_cred,
wauthn_error_e result,
- void *data)
-{
- auto *testContents = static_cast<TestContents *>(data);
- if (pubkey_cred == nullptr || result != WAUTHN_ERROR_NONE || testContents->path.empty()) {
- std::cout << __FUNCTION__ << ": response_callback failed\n"
- << "Error code: " << result << std::endl;
- testContents->status = false;
- return;
- }
-
- testContents->credentialRawId = ToBuffer(*pubkey_cred->rawId);
- std::cerr << "MC: credentialRawId: " << LowercaseHexStringOf(testContents->credentialRawId)
- << std::endl;
-
- testContents->transports = pubkey_cred->response->transports;
-
- testContents->UpdateLinkedData(pubkey_cred->linked_device);
-}
+ void *data);
void GACallback(const wauthn_pubkey_credential_assertion_s *pubkey_cred,
wauthn_error_e result,
- void *data)
-{
- auto *testContents = static_cast<TestContents *>(data);
- if (pubkey_cred == nullptr || result != WAUTHN_ERROR_NONE || testContents->path.empty()) {
- std::cout << __FUNCTION__ << ": response_callback failed\n"
- << "Error code: " << result << std::endl;
- testContents->status = false;
- return;
- }
+ void *data);
- auto credentialRawId = ToBuffer(*pubkey_cred->rawId);
- if (credentialRawId != testContents->credentialRawId) {
- std::cerr << "Error: invalid credentialRawId in GA: "
- << LowercaseHexStringOf(credentialRawId) << std::endl;
- testContents->status = false;
- return;
- }
+void MCUpdateLinkedDataCallback(const wauthn_hybrid_linked_data_s *linkedData,
+ wauthn_error_e result,
+ void *data);
- if (pubkey_cred->response->user_handle) {
- auto userHandle = ToBuffer(*pubkey_cred->response->user_handle);
- if (userHandle != testContents->userId) {
- std::cerr << "Error: invalid userHandle in GA: " << LowercaseHexStringOf(userHandle)
- << std::endl;
- testContents->status = false;
- return;
- }
- }
+void GAUpdateLinkedDataCallback(const wauthn_hybrid_linked_data_s *linkedData,
+ wauthn_error_e result,
+ void *data);
- testContents->UpdateLinkedData(pubkey_cred->linked_device);
-}
+constexpr inline char CLIENT_DATA_JSON[] = "{}";
+constexpr inline char RP_ID[] = "acme.com";
+constexpr inline char RP_NAME[] = "Acme";
+constexpr inline char USER_ID[] = "\x00\x01\x02\x03\x04\x05\x06\x07";
+constexpr inline char USER_NAME[] = "user name";
+constexpr inline char USER_DISPLAY_NAME[] = "user display name";
-bool Test(struct TestContents &testContents)
+void TestMC(struct TestContents &testContents)
{
- /* MakeCredential */
- unsigned char clientDataJson[] = "{}";
- wauthn_const_buffer_s clientDataJsonBuff;
- clientDataJsonBuff.data = clientDataJson;
- clientDataJsonBuff.size = sizeof(clientDataJson) - 1;
+ auto clientDataJsonBuff = ToWauthnConstBuff(CLIENT_DATA_JSON);
wauthn_client_data_s clientData;
clientData.client_data_json = &clientDataJsonBuff;
clientData.hash_alg = WAUTHN_HASH_ALGORITHM_SHA_256;
- char rpName[] = "Acme";
- char rpId[] = "acme.com";
wauthn_rp_entity_s rp;
- rp.name = rpName;
- rp.id = rpId;
-
- unsigned char userId[] = "\x00\x01\x02\x03\x04\x05\x06\x07";
- wauthn_const_buffer_s userIdBuff;
- userIdBuff.data = userId;
- userIdBuff.size = sizeof(userId) - 1;
+ rp.name = RP_NAME;
+ rp.id = RP_ID;
+ auto userIdBuff =
+ wauthn_const_buffer_s{reinterpret_cast<const uint8_t *>(USER_ID), sizeof(USER_ID) - 1};
testContents.userId = ToBuffer(userIdBuff);
- char userName[] = "user name";
- char userDisplayName[] = "user display name";
wauthn_user_entity_s user;
user.id = &userIdBuff;
- user.name = userName;
- user.display_name = userDisplayName;
+ user.name = USER_NAME;
+ user.display_name = USER_DISPLAY_NAME;
wauthn_pubkey_cred_param_s params[] = {
{PCT_PUBLIC_KEY, WAUTHN_COSE_ALGORITHM_ECDSA_P256_WITH_SHA256 },
wauthn_mc_callbacks_s mcCallbacks;
mcCallbacks.qrcode_callback = DisplayQRCallback;
mcCallbacks.response_callback = MCCallback;
+ mcCallbacks.linked_data_callback = MCUpdateLinkedDataCallback;
mcCallbacks.user_data = &testContents;
- int err = TurnBluetoothOn();
- if (err != BT_ERROR_NONE) {
- std::cout << "Turning bluetooth on failed with " << BtErrorToString(err) << std::endl;
- return false;
- }
-
wah_make_credential(&clientData, &mcOptions, &mcCallbacks);
+}
- if (!testContents.status) {
- return false;
- }
+void TestGA(struct TestContents &testContents)
+{
+ auto lock = std::unique_lock{testContents.mutex};
+ auto clientDataJsonBuff = ToWauthnConstBuff(CLIENT_DATA_JSON);
- /* GetAssertion */
+ wauthn_client_data_s clientData;
+ clientData.client_data_json = &clientDataJsonBuff;
+ clientData.hash_alg = WAUTHN_HASH_ALGORITHM_SHA_256;
- wauthn_const_buffer_s credentialId = ToWauthnConstBuff(testContents.credentialRawId);
+ auto credentialRawIdCopy =
+ testContents.credentialRawId; // Copy so that we don't care about it changing.
+ wauthn_const_buffer_s credentialId = ToWauthnConstBuff(credentialRawIdCopy);
wauthn_pubkey_cred_descriptor_s pubkeyCredDescriptor;
pubkeyCredDescriptor.type = PCT_PUBLIC_KEY;
pubkeyCredDescriptors.size = 1;
pubkeyCredDescriptors.descriptors = &pubkeyCredDescriptor;
+ auto linkedData = testContents.linkedData; // Copy so that we don't care about it changing.
+
wauthn_const_buffer_s contactId;
wauthn_const_buffer_s linkId;
wauthn_const_buffer_s linkSecret;
wauthn_const_buffer_s identityKey;
wauthn_hybrid_linked_data_s linkedDevice;
- if (testContents.linkedData) {
- contactId = ToWauthnConstBuff(testContents.linkedData->contactId);
- linkId = ToWauthnConstBuff(testContents.linkedData->linkId);
- linkSecret = ToWauthnConstBuff(testContents.linkedData->linkSecret);
- authenticatorPubKey = ToWauthnConstBuff(testContents.linkedData->authenticatorPubKey);
- authenticatorName = ToWauthnConstBuff(testContents.linkedData->authenticatorName);
- signature = ToWauthnConstBuff(testContents.linkedData->signature);
- tunnelServerDomain = ToWauthnConstBuff(testContents.linkedData->tunnelServerDomain);
- identityKey = ToWauthnConstBuff(testContents.linkedData->identityKey);
+ if (linkedData) {
+ contactId = ToWauthnConstBuff(linkedData->contactId);
+ linkId = ToWauthnConstBuff(linkedData->linkId);
+ linkSecret = ToWauthnConstBuff(linkedData->linkSecret);
+ authenticatorPubKey = ToWauthnConstBuff(linkedData->authenticatorPubKey);
+ authenticatorName = ToWauthnConstBuff(linkedData->authenticatorName);
+ signature = ToWauthnConstBuff(linkedData->signature);
+ tunnelServerDomain = ToWauthnConstBuff(linkedData->tunnelServerDomain);
+ identityKey = ToWauthnConstBuff(linkedData->identityKey);
linkedDevice.contact_id = &contactId;
linkedDevice.link_id = &linkId;
}
wauthn_pubkey_cred_request_options_s gaOptions;
- gaOptions.timeout = 60000; // 60s
- gaOptions.rpId = rpId;
+ std::memset(&gaOptions, 0, sizeof(gaOptions)); // For future compatibility.
+ gaOptions.timeout = 60000; // 60s
+ gaOptions.rpId = RP_ID;
gaOptions.user_verification = UVR_REQUIRED;
gaOptions.allow_credentials = &pubkeyCredDescriptors;
gaOptions.hints = nullptr;
gaOptions.attestation = AP_NONE;
gaOptions.attestation_formats = nullptr;
gaOptions.extensions = nullptr;
- gaOptions.linked_device = testContents.linkedData ? &linkedDevice : nullptr;
+ gaOptions.linked_device = linkedData ? &linkedDevice : nullptr;
wauthn_ga_callbacks_s gaCallbacks;
gaCallbacks.qrcode_callback = DisplayQRCallback;
gaCallbacks.response_callback = GACallback;
+ gaCallbacks.linked_data_callback = GAUpdateLinkedDataCallback;
gaCallbacks.user_data = &testContents;
+ lock.unlock();
wah_get_assertion(&clientData, &gaOptions, &gaCallbacks);
+}
+
+bool Test(struct TestContents &testContents)
+{
+ TestMC(testContents);
+ if (testContents.GAThread.joinable()) // Safe to access as changed only in the current thread.
+ testContents.GAThread.join();
+ return testContents.succeeded;
+}
+
+void MCCallback(const wauthn_pubkey_credential_attestation_s *pubkey_cred,
+ wauthn_error_e result,
+ void *data)
+{
+ std::cout << "MakeCredential: response_callback() was called." << std::endl;
+ auto *testContents = static_cast<TestContents *>(data);
+ if (result != WAUTHN_ERROR_NONE) {
+ std::cout << __FUNCTION__ << ": response_callback failed with code " << result << std::endl;
+ testContents->succeeded = false;
+ return;
+ }
+ if (!pubkey_cred) {
+ std::cout << __FUNCTION__ << ": pubkey_cred is NULL" << std::endl;
+ testContents->succeeded = false;
+ return;
+ }
+ if (!pubkey_cred->rawId) {
+ std::cout << __FUNCTION__ << ": pubkey_cred->rawId is NULL" << std::endl;
+ testContents->succeeded = false;
+ return;
+ }
+ if (!pubkey_cred->response) {
+ std::cout << __FUNCTION__ << ": pubkey_cred->response is NULL" << std::endl;
+ testContents->succeeded = false;
+ return;
+ }
- return testContents.status;
+ testContents->credentialRawId = ToBuffer(*pubkey_cred->rawId);
+ std::cout << "MakeCredential: credentialRawId: "
+ << LowercaseHexStringOf(testContents->credentialRawId) << std::endl;
+ testContents->transports = pubkey_cred->response->transports;
+ testContents->UpdateLinkedData(pubkey_cred->linked_device);
+
+ testContents->GAThread = std::thread([testContents] { TestGA(*testContents); });
+
+ std::cout << "MakeCredential: awaiting potential CTAP UPDATE messages..." << std::endl;
+}
+
+void GACallback(const wauthn_pubkey_credential_assertion_s *pubkey_cred,
+ wauthn_error_e result,
+ void *data)
+{
+ std::cout << "GetAssertion: response_callback() was called." << std::endl;
+ auto *testContents = static_cast<TestContents *>(data);
+ auto lock = std::lock_guard{testContents->mutex};
+ if (result != WAUTHN_ERROR_NONE) {
+ std::cout << __FUNCTION__ << ": response_callback failed with code " << result << std::endl;
+ testContents->succeeded = false;
+ return;
+ }
+ if (!pubkey_cred) {
+ std::cout << __FUNCTION__ << ": pubkey_cred is NULL" << std::endl;
+ testContents->succeeded = false;
+ return;
+ }
+ if (!pubkey_cred->rawId) {
+ std::cout << __FUNCTION__ << ": pubkey_cred->rawId is NULL" << std::endl;
+ testContents->succeeded = false;
+ return;
+ }
+ if (!pubkey_cred->response) {
+ std::cout << __FUNCTION__ << ": pubkey_cred->response is NULL" << std::endl;
+ testContents->succeeded = false;
+ return;
+ }
+
+ auto credentialRawId = ToBuffer(*pubkey_cred->rawId);
+ if (credentialRawId != testContents->credentialRawId) {
+ std::cout << "Error: invalid credentialRawId in GA: "
+ << LowercaseHexStringOf(credentialRawId) << std::endl;
+ testContents->succeeded = false;
+ return;
+ }
+
+ if (pubkey_cred->response->user_handle) {
+ auto userHandle = ToBuffer(*pubkey_cred->response->user_handle);
+ if (userHandle != testContents->userId) {
+ std::cout << "Error: invalid userHandle in GA: " << LowercaseHexStringOf(userHandle)
+ << std::endl;
+ testContents->succeeded = false;
+ return;
+ }
+ }
+
+ testContents->UpdateLinkedData(pubkey_cred->linked_device);
+
+ std::cout << "GetAssertion: awaiting potential CTAP UPDATE messages..." << std::endl;
+}
+
+void UpdateLinkedDataCallbackImpl(const wauthn_hybrid_linked_data_s *linkedData,
+ wauthn_error_e result,
+ TestContents &testContents,
+ std::lock_guard<std::mutex> & /*lock*/,
+ bool &seenLastUpdateCallback)
+{
+ if (seenLastUpdateCallback) {
+ std::cout
+ << __FUNCTION__
+ << ": update_linked_data callback called after it was called with WAUTHN_ERROR_NONE"
+ << std::endl;
+ testContents.succeeded = false;
+ return;
+ }
+ if (result == WAUTHN_ERROR_NONE)
+ seenLastUpdateCallback = true;
+ else if (result != WAUTHN_ERROR_NONE_AND_WAIT) {
+ std::cout << __FUNCTION__ << ": update_linked_data callback failed with code: " << result
+ << std::endl;
+ testContents.succeeded = false;
+ return;
+ }
+ testContents.UpdateLinkedData(linkedData);
+}
+
+void MCUpdateLinkedDataCallback(const wauthn_hybrid_linked_data_s *linkedData,
+ wauthn_error_e result,
+ void *data)
+{
+ std::cout << "MakeCredential: update_linked_data() was called." << std::endl;
+ auto *testContents = static_cast<TestContents *>(data);
+ auto lock = std::lock_guard{testContents->mutex};
+ UpdateLinkedDataCallbackImpl(
+ linkedData, result, *testContents, lock, testContents->seenLastMCUpdateCallback);
+ if (result == WAUTHN_ERROR_NONE)
+ std::cout << "MakeCredential: finished awaiting potential CTAP UPDATE messages."
+ << std::endl;
+}
+
+void GAUpdateLinkedDataCallback(const wauthn_hybrid_linked_data_s *linkedData,
+ wauthn_error_e result,
+ void *data)
+{
+ std::cout << "GetAssertion: update_linked_data() was called." << std::endl;
+ auto *testContents = static_cast<TestContents *>(data);
+ auto lock = std::lock_guard{testContents->mutex};
+ UpdateLinkedDataCallbackImpl(
+ linkedData, result, *testContents, lock, testContents->seenLastGAUpdateCallback);
+ if (result == WAUTHN_ERROR_NONE)
+ std::cout << "GetAssertion: finished awaiting potential CTAP UPDATE messages." << std::endl;
}
} // anonymous namespace
int main(int argc, char *argv[])
{
- TestContents testContents = {
- true, "/tmp/qrcode.png", {}, {}, WAUTHN_TRANSPORT_NONE, std::nullopt, false};
-
- if (argc > 1 && std::string_view("-n") == argv[1])
+ TestContents testContents = {{},
+ false,
+ true,
+ "/tmp/qrcode.png",
+ {},
+ {},
+ WAUTHN_TRANSPORT_NONE,
+ std::nullopt,
+ false,
+ false,
+ {}};
+
+ if (argc > 2) {
+ std::cout << "Usage: " << (argv[0] ? argv[0] : "webauthn-ble-manual-tests") << " [-n]\n"
+ << "\nOptions:\n"
+ << " -n Negative test -- with invalid QR code\n";
+ return 3;
+ }
+ if (argc == 2 && std::string_view("-n") == argv[1])
testContents.negative = true;
+ int err = TurnBluetoothOn();
+ if (err != BT_ERROR_NONE) {
+ std::cout << "Turning bluetooth on failed with " << BtErrorToString(err) << std::endl;
+ return 2;
+ }
+
int ret = 0;
if (Test(testContents))
std::cout << "Test passed.\n";
--- /dev/null
+/*
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+#pragma once
+
+// Example iPhone post handshake message received after handshake
+constexpr inline char IPHONE_EXAMPLE_POST_HANDSHAKE_RAW_RESPONSE[] =
+ "\xa1\x01\x58\x4f\xa5\x01\x82\x68\x46\x49\x44\x4f\x5f\x32\x5f\x30\x68\x46\x49\x44\x4f\x5f\x32"
+ "\x5f\x31\x02\x81\x69\x6c\x61\x72\x67\x65\x42\x6c\x6f\x62\x03\x50\xf2\x4a\x8e\x70\xd0\xd3\xf8"
+ "\x2c\x29\x37\x32\x52\x3c\xc4\xde\x5a\x04\xa2\x62\x72\x6b\xf5\x62\x75\x76\xf5\x09\x82\x68\x69"
+ "\x6e\x74\x65\x72\x6e\x61\x6c\x66\x68\x79\x62\x72\x69\x64";
+
+// Example Android post handshake message received after handshake
+constexpr inline char ANDROID_EXAMPLE_POST_HANDSHAKE_RAW_RESPONSE[] =
+ "\xa1\x01\x58\x53\xa5\x01\x82\x68\x46\x49\x44\x4f\x5f\x32\x5f\x30\x68\x46\x49\x44\x4f\x5f\x32"
+ "\x5f\x31\x02\x81\x63\x70\x72\x66\x03\x50\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x04\xa4\x62\x72\x6b\xf5\x62\x75\x70\xf5\x62\x75\x76\xf5\x64\x70\x6c\x61\x74\xf4"
+ "\x09\x82\x68\x69\x6e\x74\x65\x72\x6e\x61\x6c\x66\x68\x79\x62\x72\x69\x64";
+
+// Example iPhone MakeCredential request collected during manual testing
+constexpr inline char IPHONE_EXAMPLE_MAKE_CREDENTIAL_RAW_REQUEST[] =
+ "\x01\x01\xa4\x01\x58\x20\x44\x13\x6f\xa3\x55\xb3\x67\x8a\x11\x46\xad\x16\xf7\xe8\x64\x9e\x94"
+ "\xfb\x4f\xc2\x1f\xe7\x7e\x83\x10\xc0\x60\xf6\x1c\xaa\xff\x8a\x02\xa2\x62\x69\x64\x68\x61\x63"
+ "\x6d\x65\x2e\x63\x6f\x6d\x64\x6e\x61\x6d\x65\x64\x41\x63\x6d\x65\x03\xa3\x62\x69\x64\x48\x00"
+ "\x01\x02\x03\x04\x05\x06\x07\x64\x6e\x61\x6d\x65\x69\x75\x73\x65\x72\x20\x6e\x61\x6d\x65\x6b"
+ "\x64\x69\x73\x70\x6c\x61\x79\x4e\x61\x6d\x65\x71\x75\x73\x65\x72\x20\x64\x69\x73\x70\x6c\x61"
+ "\x79\x20\x6e\x61\x6d\x65\x04\x82\xa2\x63\x61\x6c\x67\x26\x64\x74\x79\x70\x65\x6a\x70\x75\x62"
+ "\x6c\x69\x63\x2d\x6b\x65\x79\xa2\x63\x61\x6c\x67\x39\x01\x00\x64\x74\x79\x70\x65\x6a\x70\x75"
+ "\x62\x6c\x69\x63\x2d\x6b\x65\x79";
+
+// Example iPhone MakeCredential response collected during manual testing
+constexpr inline char IPHONE_EXAMPLE_MAKE_CREDENTIAL_RAW_RESPONSE[] =
+ "\x01\x00\xa4\x01\x64\x6e\x6f\x6e\x65\x02\x58\x98\x11\x94\x22\x8d\xa8\xfd\xbd\xee\xfd\x26\x1b"
+ "\xd7\xb6\x59\x5c\xfd\x70\xa5\x0d\x70\xc6\x40\x7b\xcf\x01\x3d\xe9\x6d\x4e\xfb\x17\xde\x5d\x00"
+ "\x00\x00\x00\xfb\xfc\x30\x07\x15\x4e\x4e\xcc\x8c\x0b\x6e\x02\x05\x57\xd7\xbd\x00\x14\x7c\xe0"
+ "\x64\xdc\xae\x09\x40\x6a\x9a\x2f\xae\x13\x5e\x01\x63\x4a\xbd\x50\xb5\xd2\xa5\x01\x02\x03\x26"
+ "\x20\x01\x21\x58\x20\x70\x9a\xdb\x3b\x95\x96\x6b\xc0\x9c\x68\x31\xf2\xf7\x38\xfe\x19\x1d\x7c"
+ "\x55\xb7\xbf\xde\xb7\x11\x5a\x5e\xab\xef\x10\xaf\xb4\x6d\x22\x58\x20\xab\x15\xf0\x1d\x37\x94"
+ "\xcb\x9b\xaf\xd3\x92\xc3\x0a\x07\x03\x82\xa8\xc7\xc5\x88\x8e\xed\xdf\xaf\xb0\x80\x39\x4d\x4e"
+ "\x67\x8c\xd1\x03\xa0\x06\xa0";
+
+// Attestation data part of the IPHONE_EXAMPLE_MAKE_CREDENTIAL_RAW_RESPONSE
+constexpr inline char IPHONE_EXAMPLE_MAKE_CREDENTIAL_RAW_RESPONSE_ATTESTATION_DATA[] =
+ "\xfb\xfc\x30\x07\x15\x4e\x4e\xcc\x8c\x0b\x6e\x02\x05\x57\xd7\xbd\x00\x14\x92\xa5\x07\x16\x15"
+ "\x56\x78\x93\x64\x86\x22\xaf\xca\x66\x3d\x24\x50\x13\x8c\x58\xa5\x01\x02\x03\x26\x20\x01\x21"
+ "\x58\x20\xc8\x39\x05\x61\x42\x33\x3e\x54\xcd\x7d\xed\x55\xb7\x64\xbd\x24\xb8\x6b\x31\x67\x07"
+ "\x4d\x37\x28\x0a\xbe\x86\x37\x13\x6c\x71\xca\x22\x58\x20\x35\x57\xa0\x4e\x67\x8b\x96\x87\x3c"
+ "\x00\xc2\x97\xf7\xf3\x5b\x60\x97\x2a\x18\x06\x48\xe5\x5a\xcb\x33\xbe\x8d\x47\x17\x56\x5a\xd0";
+
+// Parsed public key DER part of the IPHONE_EXAMPLE_MAKE_CREDENTIAL_RAW_RESPONSE
+constexpr inline char IPHONE_EXAMPLE_MAKE_CREDENTIAL_RAW_RESPONSE_PUBLIC_KEY_DER[] =
+ "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07"
+ "\x03\x42\x00\x04\x70\x9a\xdb\x3b\x95\x96\x6b\xc0\x9c\x68\x31\xf2\xf7\x38\xfe\x19\x1d\x7c\x55"
+ "\xb7\xbf\xde\xb7\x11\x5a\x5e\xab\xef\x10\xaf\xb4\x6d\xab\x15\xf0\x1d\x37\x94\xcb\x9b\xaf\xd3"
+ "\x92\xc3\x0a\x07\x03\x82\xa8\xc7\xc5\x88\x8e\xed\xdf\xaf\xb0\x80\x39\x4d\x4e\x67\x8c\xd1";
+
+// Example Android MakeCredential request collected during manual testing
+constexpr inline char ANDROID_EXAMPLE_MAKE_CREDENTIAL_RAW_REQUEST[] =
+ "\x01\x01\xa4\x01\x58\x20\x44\x13\x6f\xa3\x55\xb3\x67\x8a\x11\x46\xad\x16\xf7\xe8\x64\x9e\x94"
+ "\xfb\x4f\xc2\x1f\xe7\x7e\x83\x10\xc0\x60\xf6\x1c\xaa\xff\x8a\x02\xa2\x62\x69\x64\x68\x61\x63"
+ "\x6d\x65\x2e\x63\x6f\x6d\x64\x6e\x61\x6d\x65\x64\x41\x63\x6d\x65\x03\xa3\x62\x69\x64\x48\x00"
+ "\x01\x02\x03\x04\x05\x06\x07\x64\x6e\x61\x6d\x65\x69\x75\x73\x65\x72\x20\x6e\x61\x6d\x65\x6b"
+ "\x64\x69\x73\x70\x6c\x61\x79\x4e\x61\x6d\x65\x71\x75\x73\x65\x72\x20\x64\x69\x73\x70\x6c\x61"
+ "\x79\x20\x6e\x61\x6d\x65\x04\x82\xa2\x63\x61\x6c\x67\x26\x64\x74\x79\x70\x65\x6a\x70\x75\x62"
+ "\x6c\x69\x63\x2d\x6b\x65\x79\xa2\x63\x61\x6c\x67\x39\x01\x00\x64\x74\x79\x70\x65\x6a\x70\x75"
+ "\x62\x6c\x69\x63\x2d\x6b\x65\x79";
+
+// Example Android MakeCredential response collected during manual testing
+constexpr inline char ANDROID_EXAMPLE_MAKE_CREDENTIAL_RAW_RESPONSE[] =
+ "\x01\x00\xa3\x01\x64\x6e\x6f\x6e\x65\x02\x58\xa4\x11\x94\x22\x8d\xa8\xfd\xbd\xee\xfd\x26\x1b"
+ "\xd7\xb6\x59\x5c\xfd\x70\xa5\x0d\x70\xc6\x40\x7b\xcf\x01\x3d\xe9\x6d\x4e\xfb\x17\xde\x45\x00"
+ "\x00\x00\x00\x53\x41\x4d\x53\x55\x4e\x47\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\xde\x2f"
+ "\x46\x7f\xb4\xf6\xc7\xbe\x49\x42\xdb\x80\xa4\x3d\xcd\xf2\x1b\xc9\xf7\x31\x54\x9c\x7b\x58\xc8"
+ "\x2b\x22\x46\x1d\x17\x43\x1d\xa5\x01\x02\x03\x26\x20\x01\x21\x58\x20\x9f\x80\x80\xc9\xac\xf9"
+ "\x92\x66\x12\x3a\xcf\x0e\xf8\x7e\x5c\x3f\xbf\xb4\x34\xc8\x96\xa0\xe2\x54\xbe\xeb\x8c\x62\xb2"
+ "\x5d\x54\xc8\x22\x58\x20\xc2\x9f\x0d\xc8\x8d\x81\x8f\x57\x51\x46\x93\x31\xc0\x59\xce\x13\x5f"
+ "\xbb\x3d\x65\x39\xc2\x89\x68\x77\x52\xb8\x80\x0c\x86\xf9\xd6\x03\xa0";
+
+// Parsed public key DER part of the ANDROID_EXAMPLE_MAKE_CREDENTIAL_RAW_RESPONSE
+constexpr inline char ANDROID_EXAMPLE_MAKE_CREDENTIAL_RAW_RESPONSE_PUBLIC_KEY_DER[] =
+ "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07"
+ "\x03\x42\x00\x04\x9f\x80\x80\xc9\xac\xf9\x92\x66\x12\x3a\xcf\x0e\xf8\x7e\x5c\x3f\xbf\xb4\x34"
+ "\xc8\x96\xa0\xe2\x54\xbe\xeb\x8c\x62\xb2\x5d\x54\xc8\xc2\x9f\x0d\xc8\x8d\x81\x8f\x57\x51\x46"
+ "\x93\x31\xc0\x59\xce\x13\x5f\xbb\x3d\x65\x39\xc2\x89\x68\x77\x52\xb8\x80\x0c\x86\xf9\xd6";
+
+// Example iPhone GetAssertion request collected during manual testing
+constexpr inline char IPHONE_EXAMPLE_GET_ASSERTION_RAW_REQUEST[] =
+ "\x01\x02\xa4\x01\x68\x61\x63\x6d\x65\x2e\x63\x6f\x6d\x02\x58\x20\x44\x13\x6f\xa3\x55\xb3\x67"
+ "\x8a\x11\x46\xad\x16\xf7\xe8\x64\x9e\x94\xfb\x4f\xc2\x1f\xe7\x7e\x83\x10\xc0\x60\xf6\x1c\xaa"
+ "\xff\x8a\x03\x81\xa3\x62\x69\x64\x54\xef\x42\x5d\x64\xe3\xed\xe0\xaf\x1e\x44\xe8\xd4\xf3\xd7"
+ "\x8c\xa0\x93\x3c\x39\xfc\x64\x74\x79\x70\x65\x6a\x70\x75\x62\x6c\x69\x63\x2d\x6b\x65\x79\x6a"
+ "\x74\x72\x61\x6e\x73\x70\x6f\x72\x74\x73\x82\x66\x68\x79\x62\x72\x69\x64\x68\x69\x6e\x74\x65"
+ "\x72\x6e\x61\x6c\x05\xa1\x62\x75\x76\xf5";
+
+// Example iPhone GetAssertion response collected during manual testing
+constexpr inline char IPHONE_EXAMPLE_GET_ASSERTION_RAW_RESPONSE[] =
+ "\x01\x00\xa5\x01\xa2\x62\x69\x64\x54\xef\x42\x5d\x64\xe3\xed\xe0\xaf\x1e\x44\xe8\xd4\xf3\xd7"
+ "\x8c\xa0\x93\x3c\x39\xfc\x64\x74\x79\x70\x65\x6a\x70\x75\x62\x6c\x69\x63\x2d\x6b\x65\x79\x02"
+ "\x58\x25\x11\x94\x22\x8d\xa8\xfd\xbd\xee\xfd\x26\x1b\xd7\xb6\x59\x5c\xfd\x70\xa5\x0d\x70\xc6"
+ "\x40\x7b\xcf\x01\x3d\xe9\x6d\x4e\xfb\x17\xde\x1d\x00\x00\x00\x00\x03\x58\x48\x30\x46\x02\x21"
+ "\x00\xf7\x3d\xb9\x8b\xc4\xc4\xc5\x08\xf8\x24\x3e\xdc\x79\x04\x26\x24\x69\xb7\xa9\x4a\xa2\x5a"
+ "\xac\x49\x6c\x72\x3a\xf2\x31\x61\x7b\x4a\x02\x21\x00\xc9\x94\x3c\x24\x15\x9e\x94\xee\x55\xb3"
+ "\xba\xe9\x5c\xfd\xb9\xbd\x08\xc0\xef\x10\x2d\xe9\x75\xef\xcf\x27\x1c\x16\x5b\x69\xb9\xe1\x04"
+ "\xa3\x62\x69\x64\x48\x00\x01\x02\x03\x04\x05\x06\x07\x64\x6e\x61\x6d\x65\x60\x6b\x64\x69\x73"
+ "\x70\x6c\x61\x79\x4e\x61\x6d\x65\x60\x08\xa0";
+
+// Credential id part of the IPHONE_EXAMPLE_GET_ASSERTION_RAW_RESPONSE
+constexpr inline char IPHONE_EXAMPLE_GET_ASSERTION_RAW_RESPONSE_CREDENTIAL_ID[] =
+ "\xef\x42\x5d\x64\xe3\xed\xe0\xaf\x1e\x44\xe8\xd4\xf3\xd7\x8c\xa0\x93\x3c\x39\xfc";
+
+// Example Android GetAssertion request collected during manual testing
+constexpr inline char ANDROID_EXAMPLE_GET_ASSERTION_RAW_REQUEST[] =
+ "\x01\x02\xa4\x01\x68\x61\x63\x6d\x65\x2e\x63\x6f\x6d\x02\x58\x20\x44\x13\x6f\xa3\x55\xb3\x67"
+ "\x8a\x11\x46\xad\x16\xf7\xe8\x64\x9e\x94\xfb\x4f\xc2\x1f\xe7\x7e\x83\x10\xc0\x60\xf6\x1c\xaa"
+ "\xff\x8a\x03\x81\xa3\x62\x69\x64\x58\x20\x9e\x39\x33\x8a\xa5\xf2\x71\xee\x29\x8b\x9e\xab\x7a"
+ "\xf2\x04\x73\xce\x0a\x6a\x5d\x1d\x8e\xf7\x14\xa7\x23\x0b\xc3\x44\x87\xea\xcd\x64\x74\x79\x70"
+ "\x65\x6a\x70\x75\x62\x6c\x69\x63\x2d\x6b\x65\x79\x6a\x74\x72\x61\x6e\x73\x70\x6f\x72\x74\x73"
+ "\x82\x66\x68\x79\x62\x72\x69\x64\x68\x69\x6e\x74\x65\x72\x6e\x61\x6c\x05\xa1\x62\x75\x76\xf5";
+
+// Example Android GetAssertion response collected during manual testing
+constexpr inline char ANDROID_EXAMPLE_GET_ASSERTION_RAW_RESPONSE[] =
+ "\x01\x00\xa3\x01\xa2\x62\x69\x64\x58\x20\x9e\x39\x33\x8a\xa5\xf2\x71\xee\x29\x8b\x9e\xab\x7a"
+ "\xf2\x04\x73\xce\x0a\x6a\x5d\x1d\x8e\xf7\x14\xa7\x23\x0b\xc3\x44\x87\xea\xcd\x64\x74\x79\x70"
+ "\x65\x6a\x70\x75\x62\x6c\x69\x63\x2d\x6b\x65\x79\x02\x58\x25\x11\x94\x22\x8d\xa8\xfd\xbd\xee"
+ "\xfd\x26\x1b\xd7\xb6\x59\x5c\xfd\x70\xa5\x0d\x70\xc6\x40\x7b\xcf\x01\x3d\xe9\x6d\x4e\xfb\x17"
+ "\xde\x05\x00\x00\x00\x00\x03\x58\x48\x30\x46\x02\x21\x00\xc4\x34\xe3\x98\x33\xac\xd5\x9a\x88"
+ "\x5d\x00\x90\x56\x0f\x10\xde\x7a\x0e\xd0\xd4\x02\x76\xcc\x28\x74\xf0\xa9\x39\x7e\x19\x08\x19"
+ "\x02\x21\x00\xff\xf1\x13\x5b\x28\x75\xce\xe5\x1c\x9b\x1c\xaa\xfe\x6e\x41\x5d\x67\x7c\x0c\xee"
+ "\x64\xe6\x18\xd5\x81\x95\xd1\xae\xc1\xa2\xdb\x5a";
+
+// Credential id part of the ANDROID_EXAMPLE_GET_ASSERTION_RAW_RESPONSE
+constexpr inline char ANDROID_EXAMPLE_GET_ASSERTION_RAW_RESPONSE_CREDENTIAL_ID[] =
+ "\x9e\x39\x33\x8a\xa5\xf2\x71\xee\x29\x8b\x9e\xab\x7a\xf2\x04\x73\xce\x0a\x6a\x5d\x1d\x8e\xf7"
+ "\x14\xa7\x23\x0b\xc3\x44\x87\xea\xcd";
+
+// TODO: move the rest from the ctap processor tests
+
+// Example value received from an Android authenticator during manual testing
+constexpr inline char ANDROID_EXAMPLE_RAW_UPDATE_MSG[] =
+ "\x02\xa2\x00\x58\xaf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xa7\x01\x58"
+ "\x98\x64\x2d\x51\x6b\x57\x6e\x54\x6d\x77\x53\x45\x3a\x41\x50\x41\x39\x31\x62\x48\x76\x72\x44"
+ "\x72\x39\x53\x64\x44\x31\x71\x39\x4a\x69\x4d\x55\x78\x52\x38\x57\x30\x41\x5a\x48\x57\x61\x56"
+ "\x31\x4d\x46\x4b\x36\x77\x4f\x56\x78\x70\x76\x38\x30\x47\x37\x59\x57\x79\x5a\x51\x72\x77\x68"
+ "\x6b\x6c\x66\x57\x55\x31\x4a\x6e\x6b\x6e\x59\x78\x4e\x6d\x45\x69\x64\x33\x4f\x4e\x59\x6f\x4d"
+ "\x77\x6f\x6a\x34\x56\x33\x51\x69\x33\x68\x6f\x59\x57\x39\x6d\x31\x71\x42\x6e\x4f\x76\x55\x69"
+ "\x63\x55\x57\x49\x68\x4d\x63\x56\x42\x64\x69\x73\x39\x69\x67\x4d\x5a\x34\x79\x2d\x48\x7a\x6b"
+ "\x31\x35\x65\x42\x4d\x63\x58\x39\x6e\x63\x6a\x69\x6b\x74\x2d\x02\x48\xc3\x38\xcc\xd2\x66\x09"
+ "\x9c\xff\x03\x58\x20\xcc\x2f\xaa\xf2\x37\x92\x3e\xda\xa1\x1f\xc8\x7e\x3e\x60\xe5\x52\xf6\xa0"
+ "\xe9\x67\x78\xa7\x31\xda\x1e\xa9\xd6\x6c\x29\xd9\xa6\x5b\x04\x58\x41\x04\x4d\x7a\xcd\xb4\x1c"
+ "\x63\x70\x56\x43\x37\xc3\x7d\x20\xa3\x05\x64\x8d\x16\x6c\xa2\x41\xaa\x96\x90\xea\x9a\xde\xd2"
+ "\x01\xab\x3d\x93\x08\x13\x54\xd9\x99\x16\x18\x80\xfd\x3b\x21\x26\xf4\xbb\xdb\x43\x35\x80\xed"
+ "\x3e\x2b\xe2\x43\x5a\x31\xcf\xc6\x05\x5d\x3e\xfe\xb5\x05\x65\x4d\x69\x20\x41\x32\x06\x58\x20"
+ "\x35\x5a\xbb\xcf\x8f\x6a\xca\xaa\xda\x49\xee\x3d\xf0\xf3\xd6\x4c\x88\x35\x1b\xd6\x69\x51\x2f"
+ "\x98\xe6\xad\x8e\x80\xcf\x50\xb0\x09\x19\x03\xe7\xf5";
* limitations under the License
*/
+#include "buffer.h"
+#include "buffer_view.h"
#include "exception.h"
#include "message.h"
+#include "message_examples.h"
+#include "wauthn_const_buff_equals.h"
#include <ctap_message_processor.h>
#include <gtest/gtest.h>
}
// from chromium/device/fido/fido_test_data.h
-constexpr uint8_t SAMPLE_MAKE_CREDENTIAL_REQUEST[] = {
+constexpr inline uint8_t SAMPLE_MAKE_CREDENTIAL_REQUEST[] = {
// clang-format off
// CTAP message type (not present in chromium, added in webauthn-ble)
0x01,
};
// from chromium/device/fido/fido_test_data.h
-constexpr uint8_t SAMPLE_GET_ASSERTION_REQUEST[] = {
+constexpr inline uint8_t SAMPLE_GET_ASSERTION_REQUEST[] = {
// clang-format off
// CTAP message type (not present in chromium, added in webauthn-ble)
0x01,
TEST(Messages, ParsePostHandshakeMessage1)
{
- // example iPhone post handshake message received after handshake
- const uint8_t blob[] =
- "\xa1\x01\x58\x4f\xa5\x01\x82\x68\x46\x49\x44\x4f\x5f\x32\x5f\x30\x68\x46\x49\x44\x4f\x5f"
- "\x32\x5f\x31\x02\x81\x69\x6c\x61\x72\x67\x65\x42\x6c\x6f\x62\x03\x50\xf2\x4a\x8e\x70\xd0"
- "\xd3\xf8\x2c\x29\x37\x32\x52\x3c\xc4\xde\x5a\x04\xa2\x62\x72\x6b\xf5\x62\x75\x76\xf5\x09"
- "\x82\x68\x69\x6e\x74\x65\x72\x6e\x61\x6c\x66\x68\x79\x62\x72\x69\x64";
-
- BufferView view(blob, sizeof(blob) - 1);
-
// read post handshake message
PostHandshakeResponse msg;
+ auto view = BUFFER_VIEW(IPHONE_EXAMPLE_POST_HANDSHAKE_RAW_RESPONSE);
ASSERT_NO_THROW(msg.Deserialize(view));
AssertEq(msg.m_versions, {"FIDO_2_0", "FIDO_2_1"});
TEST(Messages, ParsePostHandshakeMessage2)
{
- // example Android post handshake message received after handshake
- const uint8_t blob[] =
- "\xa1\x01\x58\x53\xa5\x01\x82\x68\x46\x49\x44\x4f\x5f\x32\x5f\x30\x68\x46\x49\x44\x4f\x5f"
- "\x32\x5f\x31\x02\x81\x63\x70\x72\x66\x03\x50\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
- "\x00\x00\x00\x00\x00\x04\xa4\x62\x72\x6b\xf5\x62\x75\x70\xf5\x62\x75\x76\xf5\x64\x70\x6c"
- "\x61\x74\xf4\x09\x82\x68\x69\x6e\x74\x65\x72\x6e\x61\x6c\x66\x68\x79\x62\x72\x69\x64";
-
- BufferView view(blob, sizeof(blob) - 1);
-
// read post handshake message
PostHandshakeResponse msg;
+ auto view = BUFFER_VIEW(ANDROID_EXAMPLE_POST_HANDSHAKE_RAW_RESPONSE);
ASSERT_NO_THROW(msg.Deserialize(view));
AssertEq(msg.m_versions, {"FIDO_2_0", "FIDO_2_1"});
TEST(Messages, ParseMakeCredentialResponse1)
{
- // example make credential response received from iPhone authenticator (w/o CTAP message type
- // prefix)
- constexpr uint8_t blob[] =
- "\x00\xa4\x01\x64\x6e\x6f\x6e\x65\x02\x58\x98\x11\x94\x22\x8d\xa8\xfd\xbd\xee\xfd\x26\x1b"
- "\xd7\xb6\x59\x5c\xfd\x70\xa5\x0d\x70\xc6\x40\x7b\xcf\x01\x3d\xe9\x6d\x4e\xfb\x17\xde\x5d"
- "\x00\x00\x00\x00\xfb\xfc\x30\x07\x15\x4e\x4e\xcc\x8c\x0b\x6e\x02\x05\x57\xd7\xbd\x00\x14"
- "\x92\xa5\x07\x16\x15\x56\x78\x93\x64\x86\x22\xaf\xca\x66\x3d\x24\x50\x13\x8c\x58\xa5\x01"
- "\x02\x03\x26\x20\x01\x21\x58\x20\xc8\x39\x05\x61\x42\x33\x3e\x54\xcd\x7d\xed\x55\xb7\x64"
- "\xbd\x24\xb8\x6b\x31\x67\x07\x4d\x37\x28\x0a\xbe\x86\x37\x13\x6c\x71\xca\x22\x58\x20\x35"
- "\x57\xa0\x4e\x67\x8b\x96\x87\x3c\x00\xc2\x97\xf7\xf3\x5b\x60\x97\x2a\x18\x06\x48\xe5\x5a"
- "\xcb\x33\xbe\x8d\x47\x17\x56\x5a\xd0\x03\xa0\x06\xa0";
-
- BufferView view(blob, sizeof(blob) - 1);
-
- // read post handshake message
MakeCredentialResponse msg;
+ auto view = BUFFER_VIEW(IPHONE_EXAMPLE_MAKE_CREDENTIAL_RAW_RESPONSE);
+ view.remove_prefix(1); // remove CTAP message type prefix
ASSERT_NO_THROW(msg.Deserialize(view));
+ const auto *blob =
+ reinterpret_cast<const uint8_t *>(IPHONE_EXAMPLE_MAKE_CREDENTIAL_RAW_RESPONSE) + 1;
ASSERT_EQ(msg.m_authDataRaw.size(), 152);
AssertEq(msg.m_authDataRaw, blob + 11, msg.m_authDataRaw.size());
AssertEq(msg.m_authData.m_rpIdHash, blob + 11, 32);
ASSERT_EQ(msg.m_authData.m_flags, blob[43]);
ASSERT_TRUE(msg.m_authData.m_attestationData.has_value());
AssertEq(msg.m_authData.m_attestationData->m_credentialId, blob + 66, 20);
- ASSERT_FALSE(msg.m_authData.m_attestationData->m_publicKeyDer.empty());
+ ASSERT_EQ(msg.m_authData.m_attestationData->m_publicKeyDer,
+ BUFFER(IPHONE_EXAMPLE_MAKE_CREDENTIAL_RAW_RESPONSE_PUBLIC_KEY_DER));
ASSERT_EQ(msg.m_authData.m_attestationData->m_alg,
WAUTHN_COSE_ALGORITHM_ECDSA_P256_WITH_SHA256);
AssertEq(msg.m_attestationObject, view.data(), view.size());
TEST(Messages, ParseMakeCredentialResponse2)
{
- // example make credential response received from Android authenticator (w/o CTAP message type
- // prefix)
- constexpr uint8_t blob[] =
- "\x00\xa3\x01\x64\x6e\x6f\x6e\x65\x02\x58\xa4\x11\x94\x22\x8d\xa8\xfd\xbd\xee\xfd\x26\x1b"
- "\xd7\xb6\x59\x5c\xfd\x70\xa5\x0d\x70\xc6\x40\x7b\xcf\x01\x3d\xe9\x6d\x4e\xfb\x17\xde\x45"
- "\x00\x00\x00\x00\x53\x41\x4d\x53\x55\x4e\x47\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20"
- "\x8c\x5b\x4d\x2f\x62\x5f\xa0\xcf\x61\x44\xba\x1a\xa3\x2f\x5b\x4b\x65\xff\x21\x7c\xcb\x0d"
- "\x24\x10\x16\xa3\xb8\xc8\x2c\x94\xe6\x7f\xa5\x01\x02\x03\x26\x20\x01\x21\x58\x20\xf8\xa3"
- "\xeb\x21\x36\xfa\xc3\xb0\xce\xae\xb5\x04\x54\x59\xff\xc1\x01\xb1\x64\xc6\x37\xf1\x42\x66"
- "\x36\x48\x25\x68\x55\xe8\x7b\xb2\x22\x58\x20\x9b\xca\x03\x74\x3c\x7f\x9a\x4e\x64\x4b\xeb"
- "\xb8\x8a\xa2\x15\x59\x20\x92\x86\x60\xa6\x65\x13\xad\x84\x9c\x1c\x15\xe0\x41\x7b\xe5\x03"
- "\xa0";
-
- BufferView view(blob, sizeof(blob) - 1);
-
- // read post handshake message
MakeCredentialResponse msg;
+ auto view = BUFFER_VIEW(ANDROID_EXAMPLE_MAKE_CREDENTIAL_RAW_RESPONSE);
+ view.remove_prefix(1); // remove CTAP message type prefix
ASSERT_NO_THROW(msg.Deserialize(view));
+ const auto *blob =
+ reinterpret_cast<const uint8_t *>(ANDROID_EXAMPLE_MAKE_CREDENTIAL_RAW_RESPONSE) + 1;
ASSERT_EQ(msg.m_authDataRaw.size(), 164);
AssertEq(msg.m_authDataRaw, blob + 11, msg.m_authDataRaw.size());
AssertEq(msg.m_authData.m_rpIdHash, blob + 11, 32);
ASSERT_EQ(msg.m_authData.m_flags, blob[43]);
ASSERT_TRUE(msg.m_authData.m_attestationData.has_value());
AssertEq(msg.m_authData.m_attestationData->m_credentialId, blob + 66, 32);
- ASSERT_FALSE(msg.m_authData.m_attestationData->m_publicKeyDer.empty());
+ ASSERT_EQ(msg.m_authData.m_attestationData->m_publicKeyDer,
+ BUFFER(ANDROID_EXAMPLE_MAKE_CREDENTIAL_RAW_RESPONSE_PUBLIC_KEY_DER));
ASSERT_EQ(msg.m_authData.m_attestationData->m_alg,
WAUTHN_COSE_ALGORITHM_ECDSA_P256_WITH_SHA256);
AssertEq(msg.m_attestationObject, view.data(), view.size());
TEST(Messages, ParseGetAssertionResponse)
{
- // sample response received from iPhone authenticator during manual testing
- constexpr uint8_t blob[] =
- "\x00\xa5\x01\xa2\x62\x69\x64\x54\xc7\x3c\x11\x45\x4b\x19\x08\xe5\x2c\x16\x8d\x45\x81\xf5"
- "\x9f\xb1\x15\x00\xbf\x51\x64\x74\x79\x70\x65\x6a\x70\x75\x62\x6c\x69\x63\x2d\x6b\x65\x79"
- "\x02\x58\x25\x11\x94\x22\x8d\xa8\xfd\xbd\xee\xfd\x26\x1b\xd7\xb6\x59\x5c\xfd\x70\xa5\x0d"
- "\x70\xc6\x40\x7b\xcf\x01\x3d\xe9\x6d\x4e\xfb\x17\xde\x1d\x00\x00\x00\x00\x03\x58\x48\x30"
- "\x46\x02\x21\x00\xe2\x6e\xb7\xab\x29\x48\xcb\x1c\x72\xc9\xe2\x91\xc9\x74\xff\x2b\x44\xd9"
- "\xdf\x8a\xb2\x3c\xc9\x5a\xc0\xe7\xb8\x65\xc4\xe5\xde\x08\x02\x21\x00\xb1\xea\x2c\x5f\xf3"
- "\x41\x71\x0f\x18\x04\x77\xe1\x73\xf9\xbc\xac\x0f\x01\x14\x91\x33\x79\xca\xe2\x87\x22\xbb"
- "\x8b\xda\xe2\x77\x22\x04\xa3\x62\x69\x64\x48\x00\x01\x02\x03\x04\x05\x06\x07\x64\x6e\x61"
- "\x6d\x65\x60\x6b\x64\x69\x73\x70\x6c\x61\x79\x4e\x61\x6d\x65\x60\x08\xa0";
- BufferView view(blob, sizeof(blob) - 1);
-
GetAssertionResponse msg;
+ auto view = BUFFER_VIEW(IPHONE_EXAMPLE_GET_ASSERTION_RAW_RESPONSE);
+ view.remove_prefix(1); // remove CTAP message type prefix
ASSERT_NO_THROW(msg.Deserialize(view));
+ const auto *blob =
+ reinterpret_cast<const uint8_t *>(IPHONE_EXAMPLE_GET_ASSERTION_RAW_RESPONSE) + 1;
ASSERT_GE(msg.m_authDataRaw.size(), 37);
AssertEq(msg.m_authDataRaw, blob + 47, msg.m_authDataRaw.size());
AssertEq(msg.m_authData.m_rpIdHash, blob + 47, 32);
TEST(Messages, ParseUpdateMessage)
{
- // sample value received from an Android authenticator during manual testing
- constexpr uint8_t blob[] =
- "\xa2\x00\x58\xaf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
- "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
- "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
- "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
- "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
- "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
- "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
- "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
- "\x00\x00\x00\x01\xa7\x01\x58\x98\x64\x2d\x51\x6b\x57\x6e\x54\x6d\x77\x53\x45\x3a\x41\x50"
- "\x41\x39\x31\x62\x48\x76\x72\x44\x72\x39\x53\x64\x44\x31\x71\x39\x4a\x69\x4d\x55\x78\x52"
- "\x38\x57\x30\x41\x5a\x48\x57\x61\x56\x31\x4d\x46\x4b\x36\x77\x4f\x56\x78\x70\x76\x38\x30"
- "\x47\x37\x59\x57\x79\x5a\x51\x72\x77\x68\x6b\x6c\x66\x57\x55\x31\x4a\x6e\x6b\x6e\x59\x78"
- "\x4e\x6d\x45\x69\x64\x33\x4f\x4e\x59\x6f\x4d\x77\x6f\x6a\x34\x56\x33\x51\x69\x33\x68\x6f"
- "\x59\x57\x39\x6d\x31\x71\x42\x6e\x4f\x76\x55\x69\x63\x55\x57\x49\x68\x4d\x63\x56\x42\x64"
- "\x69\x73\x39\x69\x67\x4d\x5a\x34\x79\x2d\x48\x7a\x6b\x31\x35\x65\x42\x4d\x63\x58\x39\x6e"
- "\x63\x6a\x69\x6b\x74\x2d\x02\x48\xc3\x38\xcc\xd2\x66\x09\x9c\xff\x03\x58\x20\xcc\x2f\xaa"
- "\xf2\x37\x92\x3e\xda\xa1\x1f\xc8\x7e\x3e\x60\xe5\x52\xf6\xa0\xe9\x67\x78\xa7\x31\xda\x1e"
- "\xa9\xd6\x6c\x29\xd9\xa6\x5b\x04\x58\x41\x04\x4d\x7a\xcd\xb4\x1c\x63\x70\x56\x43\x37\xc3"
- "\x7d\x20\xa3\x05\x64\x8d\x16\x6c\xa2\x41\xaa\x96\x90\xea\x9a\xde\xd2\x01\xab\x3d\x93\x08"
- "\x13\x54\xd9\x99\x16\x18\x80\xfd\x3b\x21\x26\xf4\xbb\xdb\x43\x35\x80\xed\x3e\x2b\xe2\x43"
- "\x5a\x31\xcf\xc6\x05\x5d\x3e\xfe\xb5\x05\x65\x4d\x69\x20\x41\x32\x06\x58\x20\x35\x5a\xbb"
- "\xcf\x8f\x6a\xca\xaa\xda\x49\xee\x3d\xf0\xf3\xd6\x4c\x88\x35\x1b\xd6\x69\x51\x2f\x98\xe6"
- "\xad\x8e\x80\xcf\x50\xb0\x09\x19\x03\xe7\xf5";
- BufferView view(blob, sizeof(blob) - 1);
-
UpdateMessage msg;
+ auto view = BUFFER_VIEW(ANDROID_EXAMPLE_RAW_UPDATE_MSG);
+ view.remove_prefix(1); // remove CTAP message type prefix
ASSERT_NO_THROW(msg.Deserialize(view));
auto linkDataOpt = msg.GetLinkData();
ASSERT_TRUE(linkDataOpt.has_value());
const auto &linkData = *linkDataOpt;
+ const auto *blob = reinterpret_cast<const uint8_t *>(ANDROID_EXAMPLE_RAW_UPDATE_MSG) + 1;
AssertEq(linkData.m_contactId, blob + 184, 152);
AssertEq(linkData.m_linkId, blob + 338, 8);
AssertEq(linkData.m_linkSecret, blob + 349, 32);
{
QrCodeShower qrCodeShower;
int calledNumTimes = 0;
- static constexpr auto callback = [](const char *qrContents, void *userData) {
- EXPECT_THAT(qrContents, testing::StartsWith("FIDO:/"));
- ++*static_cast<int *>(userData);
- };
- qrCodeShower.ShowQrCode({}, {}, {'m', 'c'}, true, callback, &calledNumTimes);
+ qrCodeShower.ShowQrCode({}, {}, {'m', 'c'}, true, [&](std::string &&contents) {
+ EXPECT_THAT(contents, testing::StartsWith("FIDO:/"));
+ ++calledNumTimes;
+ });
EXPECT_EQ(calledNumTimes, 1);
}
public:
explicit MQrCodeShower(TestState &testState) : Tester{testState} {}
- void ShowQrCode(const CryptoBuffer & /*qrSecret*/,
- const CryptoBuffer & /*identityKeyCompressed*/,
- const Hint & /*hint*/,
- bool /*stateAssisted*/,
- wauthn_cb_display_qrcode /*displayQrCodeCallback*/,
- void * /*displayQrCodeCallbackUserData*/) override
+ void ShowQrCode(const CryptoBuffer &,
+ const CryptoBuffer &,
+ const Hint &,
+ bool,
+ std::function<void(std::string &&)> &&) override
{
SCOPED_TRACE("");
UpdateAndCheckState(Event::QR_CODE_STARTED, Event::QR_CODE_ENDED, false);
TestState testState;
auto makeTransaction = [&] {
- return QrTransaction(
- nullptr,
- nullptr,
+ auto transaction = std::make_unique<QrTransaction>();
+ transaction->Initialize(
{},
+ [](std::string &&) {},
std::make_unique<MQrCodeShower>(testState),
std::make_unique<
MBtAdvertScanner<Event, CancelCalledOn, &GenerateDecryptedBluetoothAdvert>>(
testState),
std::make_unique<MHandshake>(testState),
std::make_unique<MCtapMessageProcessor<Event, CancelCalledOn>>(testState));
+ return transaction;
};
- // Before PerformTransaction()
+ // Before Perform()
{
auto transaction = makeTransaction();
- testState.Reset(Event::RESET, transaction);
- EXPECT_THROW(transaction.PerformTransaction(clientData, options), Cancelled);
+ testState.Reset(Event::RESET, *transaction);
+ EXPECT_THROW(transaction->Perform(clientData, options), Cancelled);
EXPECT_EQ(testState.m_lastEvent, Event::RESET);
EXPECT_EQ(testState.m_cancelCalledOn, CancelCalledOn::NONE);
}
// In MQrCodeShower
{
auto transaction = makeTransaction();
- testState.Reset(Event::QR_CODE_STARTED, transaction);
- EXPECT_THROW(transaction.PerformTransaction(clientData, options), Cancelled);
+ testState.Reset(Event::QR_CODE_STARTED, *transaction);
+ EXPECT_THROW(transaction->Perform(clientData, options), Cancelled);
EXPECT_EQ(testState.m_lastEvent, Event::QR_CODE_STARTED);
// QrCodeShower has no Cancel()
EXPECT_EQ(testState.m_cancelCalledOn, CancelCalledOn::NONE);
// After MQrCodeShower
{
auto transaction = makeTransaction();
- testState.Reset(Event::QR_CODE_ENDED, transaction);
- EXPECT_THROW(transaction.PerformTransaction(clientData, options), Cancelled);
+ testState.Reset(Event::QR_CODE_ENDED, *transaction);
+ EXPECT_THROW(transaction->Perform(clientData, options), Cancelled);
EXPECT_EQ(testState.m_lastEvent, Event::QR_CODE_ENDED);
// QrCodeShower has no Cancel()
EXPECT_EQ(testState.m_cancelCalledOn, CancelCalledOn::NONE);
// In MBleAdvertScanner
{
auto transaction = makeTransaction();
- testState.Reset(Event::AWAITING_BLE_ADVERT_STARTED, transaction);
- EXPECT_THROW(transaction.PerformTransaction(clientData, options), Cancelled);
+ testState.Reset(Event::AWAITING_BLE_ADVERT_STARTED, *transaction);
+ EXPECT_THROW(transaction->Perform(clientData, options), Cancelled);
EXPECT_EQ(testState.m_lastEvent, Event::AWAITING_BLE_ADVERT_STARTED);
EXPECT_EQ(testState.m_cancelCalledOn, CancelCalledOn::BLE_ADVERT);
}
// After MBleAdvertScanner
{
auto transaction = makeTransaction();
- testState.Reset(Event::AWAITING_BLE_ADVERT_ENDED, transaction);
- EXPECT_THROW(transaction.PerformTransaction(clientData, options), Cancelled);
+ testState.Reset(Event::AWAITING_BLE_ADVERT_ENDED, *transaction);
+ EXPECT_THROW(transaction->Perform(clientData, options), Cancelled);
EXPECT_EQ(testState.m_lastEvent, Event::AWAITING_BLE_ADVERT_ENDED);
EXPECT_EQ(testState.m_cancelCalledOn, CancelCalledOn::BLE_ADVERT);
}
// In MHandshake::QrInitiatedConnect()
{
auto transaction = makeTransaction();
- testState.Reset(Event::CONNECTING_STARTED, transaction);
- EXPECT_THROW(transaction.PerformTransaction(clientData, options), Cancelled);
+ testState.Reset(Event::CONNECTING_STARTED, *transaction);
+ EXPECT_THROW(transaction->Perform(clientData, options), Cancelled);
EXPECT_EQ(testState.m_lastEvent, Event::CONNECTING_STARTED);
EXPECT_EQ(testState.m_cancelCalledOn, CancelCalledOn::HANDSHAKE);
}
// After MHandshake::QrInitiatedConnect()
{
auto transaction = makeTransaction();
- testState.Reset(Event::CONNECTING_ENDED, transaction);
- EXPECT_THROW(transaction.PerformTransaction(clientData, options), Cancelled);
+ testState.Reset(Event::CONNECTING_ENDED, *transaction);
+ EXPECT_THROW(transaction->Perform(clientData, options), Cancelled);
EXPECT_EQ(testState.m_lastEvent, Event::CONNECTING_ENDED);
EXPECT_EQ(testState.m_cancelCalledOn, CancelCalledOn::HANDSHAKE);
}
// In MHandshake::DoQrInitiatedHandshake()
{
auto transaction = makeTransaction();
- testState.Reset(Event::HANDSHAKE_STARTED, transaction);
- EXPECT_THROW(transaction.PerformTransaction(clientData, options), Cancelled);
+ testState.Reset(Event::HANDSHAKE_STARTED, *transaction);
+ EXPECT_THROW(transaction->Perform(clientData, options), Cancelled);
EXPECT_EQ(testState.m_lastEvent, Event::HANDSHAKE_STARTED);
EXPECT_EQ(testState.m_cancelCalledOn, CancelCalledOn::HANDSHAKE);
}
// After MHandshake::DoQrInitiatedHandshake()
{
auto transaction = makeTransaction();
- testState.Reset(Event::HANDSHAKE_ENDED, transaction);
- EXPECT_THROW(transaction.PerformTransaction(clientData, options), Cancelled);
+ testState.Reset(Event::HANDSHAKE_ENDED, *transaction);
+ EXPECT_THROW(transaction->Perform(clientData, options), Cancelled);
EXPECT_EQ(testState.m_lastEvent, Event::HANDSHAKE_ENDED);
EXPECT_EQ(testState.m_cancelCalledOn, CancelCalledOn::HANDSHAKE);
}
// In MCtapMessageProcessor
{
auto transaction = makeTransaction();
- testState.Reset(Event::MC_OR_GA_STARTED, transaction);
- EXPECT_THROW(transaction.PerformTransaction(clientData, options), Cancelled);
+ testState.Reset(Event::MC_OR_GA_STARTED, *transaction);
+ EXPECT_THROW(transaction->Perform(clientData, options), Cancelled);
EXPECT_EQ(testState.m_lastEvent, Event::MC_OR_GA_STARTED);
EXPECT_EQ(testState.m_cancelCalledOn, CancelCalledOn::CTAP_MESSAGE_PROCESSOR);
}
// After MCtapMessageProcessor
{
auto transaction = makeTransaction();
- testState.Reset(Event::MC_OR_GA_ENDED, transaction);
- EXPECT_THROW(transaction.PerformTransaction(clientData, options), Cancelled);
+ testState.Reset(Event::MC_OR_GA_ENDED, *transaction);
+ EXPECT_THROW(transaction->Perform(clientData, options), Cancelled);
EXPECT_EQ(testState.m_lastEvent, Event::MC_OR_GA_ENDED);
EXPECT_EQ(testState.m_cancelCalledOn, CancelCalledOn::CTAP_MESSAGE_PROCESSOR);
}
// No Cancel
{
auto transaction = makeTransaction();
- testState.Reset(Event::FINISHED, transaction);
- EXPECT_NO_THROW(transaction.PerformTransaction(clientData, options));
+ testState.Reset(Event::FINISHED, *transaction);
+ EXPECT_NO_THROW(transaction->Perform(clientData, options));
EXPECT_EQ(testState.m_lastEvent, Event::MC_OR_GA_ENDED);
EXPECT_EQ(testState.m_cancelCalledOn, CancelCalledOn::NONE);
}
const CryptoBuffer &,
const Hint &,
bool,
- wauthn_cb_display_qrcode,
- void *) override
+ std::function<void(std::string &&)> &&) override
{
m_cancelFacilitator.CancelCheck();
}
} // namespace
-TEST(QrTransaction, cancel_from_the_other_thread)
+TEST(QrTransaction, CancelFromTheOtherThread)
{
auto makeTransaction = [&] {
- return QrTransaction(
- nullptr,
- nullptr,
+ auto transaction = std::make_unique<QrTransaction>();
+ transaction->Initialize(
{},
+ [](std::string &&) {},
std::make_unique<CancellationMQrCodeShower>(),
std::make_unique<CancellationMBtAdvertScanner<&GenerateDecryptedBluetoothAdvert>>(),
std::make_unique<CancellationMHandshake>(),
std::make_unique<CancellationMCtapMessageProcessor>());
+ return transaction;
};
TestCancelFromTheOtherThread<ITransaction>(
400, 40, makeTransaction, [](ITransaction &transaction) {
auto clientData = wauthn_client_data_s{};
auto mcOptions = wauthn_pubkey_cred_creation_options_s{};
- transaction.PerformTransaction(clientData, mcOptions);
+ transaction.Perform(clientData, mcOptions);
auto gaOptions = wauthn_pubkey_cred_request_options_s{};
- transaction.PerformTransaction(clientData, gaOptions);
+ transaction.Perform(clientData, gaOptions);
+ });
+}
+
+namespace {
+
+class ShouldNotBeCalledMQrCodeShower : public IQrCodeShower {
+public:
+ void ShowQrCode(const CryptoBuffer &,
+ const CryptoBuffer &,
+ const Hint &,
+ bool,
+ std::function<void(std::string &&)> &&) override
+ {
+ ADD_FAILURE() << "This should not be called";
+ throw std::runtime_error{""};
+ }
+};
+
+} // namespace
+
+TEST(QrTransaction, ProcessFollowingUpdateMsgs)
+{
+ TestTransactionProcessFollowingUpdateMsgs(
+ [](std::unique_ptr<ICtapMessageProcessor> &&ctapMessageProcessor) {
+ auto transaction = std::make_unique<QrTransaction>();
+ transaction->Initialize(
+ {},
+ [](std::string &&) {
+ ADD_FAILURE() << "This should not be called";
+ throw std::runtime_error{""};
+ },
+ std::make_unique<ShouldNotBeCalledMQrCodeShower>(),
+ std::make_unique<ShouldNotBeCalledMBtAdvertScanner>(),
+ std::make_unique<ShouldNotBeCalledMHandshake>(),
+ std::move(ctapMessageProcessor));
+ return transaction;
});
}
--- /dev/null
+/*
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd. All rights reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+#include "buffer.h"
+#include "buffer_view.h"
+#include "message_examples.h"
+#include "request_handler.h"
+#include "test_cancel_from_the_other_thread.h"
+#include "to_wauthn_const_buff.h"
+#include "wauthn_const_buff_equals.h"
+
+#include <gtest/gtest.h>
+#include <optional>
+#include <tuple>
+#include <webauthn-types.h>
+
+namespace {
+
+class ShouldNotBeUsedMQrTransaction : public IQrTransaction {
+public:
+ ShouldNotBeUsedMQrTransaction() noexcept = default;
+
+ void Initialize(Hint &&,
+ std::function<void(std::string &&)> &&,
+ std::unique_ptr<IQrCodeShower> &&,
+ IBtAdvertScannerUPtr &&,
+ std::unique_ptr<IHandshake> &&,
+ std::unique_ptr<ICtapMessageProcessor> &&) override
+ {
+ ADD_FAILURE() << "This should not be called";
+ throw std::runtime_error{""};
+ }
+
+ MCResult Perform(const wauthn_client_data_s &,
+ const wauthn_pubkey_cred_creation_options_s &) override
+ {
+ ADD_FAILURE() << "This should not be called";
+ throw std::runtime_error{""};
+ }
+
+ GAResult Perform(const wauthn_client_data_s &,
+ const wauthn_pubkey_cred_request_options_s &) override
+ {
+ ADD_FAILURE() << "This should not be called";
+ throw std::runtime_error{""};
+ }
+
+ void ProcessFollowingUpdateMsgs(std::function<void(UpdateMessage &&)>) override
+ {
+ ADD_FAILURE() << "This should not be called";
+ throw std::runtime_error{""};
+ }
+
+ void Cancel() override
+ {
+ ADD_FAILURE() << "This should not be called";
+ throw std::runtime_error{""};
+ }
+};
+
+class ShouldNotBeUsedMStateAssistedTransaction : public IStateAssistedTransaction {
+public:
+ ShouldNotBeUsedMStateAssistedTransaction() noexcept = default;
+
+ void Initialize(Hint &&,
+ std::unique_ptr<IHandshake> &&,
+ IBtAdvertScannerUPtr &&,
+ std::unique_ptr<ICtapMessageProcessor> &&) override
+ {
+ ADD_FAILURE() << "This should not be called";
+ throw std::runtime_error{""};
+ }
+
+ MCResult Perform(const wauthn_client_data_s &,
+ const wauthn_pubkey_cred_creation_options_s &) override
+ {
+ ADD_FAILURE() << "This should not be called";
+ throw std::runtime_error{""};
+ }
+
+ GAResult Perform(const wauthn_client_data_s &,
+ const wauthn_pubkey_cred_request_options_s &) override
+ {
+ ADD_FAILURE() << "This should not be called";
+ throw std::runtime_error{""};
+ }
+
+ void ProcessFollowingUpdateMsgs(std::function<void(UpdateMessage &&)>) override
+ {
+ ADD_FAILURE() << "This should not be called";
+ throw std::runtime_error{""};
+ }
+
+ void Cancel() override
+ {
+ ADD_FAILURE() << "This should not be called";
+ throw std::runtime_error{""};
+ }
+};
+
+CtapResponse::AuthData::AttestationData ExampleAttestationData()
+{
+ auto rawAttestationData =
+ BUFFER_VIEW(IPHONE_EXAMPLE_MAKE_CREDENTIAL_RAW_RESPONSE_ATTESTATION_DATA);
+ return CtapResponse::AuthData::AttestationData(rawAttestationData);
+}
+
+ICtapMessageProcessor::MCResult ExampleCtapMCResult()
+{
+ ICtapMessageProcessor::MCResult res;
+ res.response.m_authData.m_attestationData = ExampleAttestationData();
+ return res;
+}
+
+class GenericMQrTransaction : public IQrTransaction {
+public:
+ struct Options {
+ bool callQrCallback = false;
+ std::string qrCodeContents;
+ ICtapMessageProcessor::MCResult ctapMCResult = ExampleCtapMCResult();
+ ICtapMessageProcessor::GAResult ctapGAResult;
+ Buffer identityKey;
+ std::string tunnelServerDomain;
+ std::function<void(MCResult &)> mcResultProcessor = [](MCResult &) {};
+ std::function<void(GAResult &)> gaResultProcessor = [](GAResult &) {};
+ std::function<void(std::function<void(UpdateMessage &&)>)> followingCtapUpdatesProcessor =
+ [](std::function<void(UpdateMessage &&)> &&) {
+ }; // simulates no following update messages
+ };
+
+ explicit GenericMQrTransaction(Options &&options) noexcept : m_options{std::move(options)} {}
+
+ void Initialize(Hint &&hint,
+ std::function<void(std::string &&)> &&qrCodeCallback,
+ std::unique_ptr<IQrCodeShower> && /*qrCodeShower*/,
+ IBtAdvertScannerUPtr && /*btAdvertScanner*/,
+ std::unique_ptr<IHandshake> && /*handshake*/,
+ std::unique_ptr<ICtapMessageProcessor> && /*ctapMessageProcessor*/) override
+ {
+ m_hint = std::move(hint);
+ m_qrCodeCallback = std::move(qrCodeCallback);
+ }
+
+ MCResult Perform(const wauthn_client_data_s &,
+ const wauthn_pubkey_cred_creation_options_s &) override
+ {
+ EXPECT_EQ(m_hint, (Hint{'m', 'c'}));
+ if (m_options.callQrCallback)
+ m_qrCodeCallback(std::move(m_options.qrCodeContents));
+ auto res = MCResult{
+ m_options.ctapMCResult,
+ m_options.identityKey,
+ m_options.tunnelServerDomain,
+ };
+ m_options.mcResultProcessor(res);
+ return res;
+ }
+
+ GAResult Perform(const wauthn_client_data_s &,
+ const wauthn_pubkey_cred_request_options_s &) override
+ {
+ EXPECT_EQ(m_hint, (Hint{'g', 'a'}));
+ if (m_options.callQrCallback)
+ m_qrCodeCallback(std::move(m_options.qrCodeContents));
+ auto res = GAResult{
+ m_options.ctapGAResult,
+ m_options.identityKey,
+ m_options.tunnelServerDomain,
+ };
+ m_options.gaResultProcessor(res);
+ return res;
+ }
+
+ void
+ ProcessFollowingUpdateMsgs(std::function<void(UpdateMessage &&)> updateMsgCallback) override
+ {
+ m_options.followingCtapUpdatesProcessor(updateMsgCallback);
+ }
+
+ void Cancel() override
+ {
+ ADD_FAILURE() << "This should not be called";
+ throw std::runtime_error{""};
+ }
+
+private:
+ Options m_options;
+ Hint m_hint = {};
+ std::function<void(std::string &&)> m_qrCodeCallback;
+};
+
+class GenericMStateAssistedTransaction : public IStateAssistedTransaction {
+public:
+ struct Options {
+ ICtapMessageProcessor::MCResult ctapMCResult = ExampleCtapMCResult();
+ ICtapMessageProcessor::GAResult ctapGAResult;
+ Buffer identityKey;
+ std::string tunnelServerDomain;
+ std::function<void(MCResult &)> mcResultProcessor = [](MCResult &) {};
+ std::function<void(GAResult &)> gaResultProcessor = [](GAResult &) {};
+ std::function<void(std::function<void(UpdateMessage &&)>)> followingCtapUpdatesProcessor =
+ [](std::function<void(UpdateMessage &&)> &&) {
+ }; // simulates no following update messages
+ };
+
+ explicit GenericMStateAssistedTransaction(Options &&options) noexcept
+ : m_options{std::move(options)}
+ {
+ }
+
+ void Initialize(Hint &&hint,
+ std::unique_ptr<IHandshake> && /*handshake*/,
+ IBtAdvertScannerUPtr && /*btAdvertScanner*/,
+ std::unique_ptr<ICtapMessageProcessor> && /*ctapMessageProcessor*/) override
+ {
+ m_hint = std::move(hint);
+ }
+
+ MCResult Perform(const wauthn_client_data_s &,
+ const wauthn_pubkey_cred_creation_options_s &) override
+ {
+ EXPECT_EQ(m_hint, (Hint{'m', 'c'}));
+ auto res = MCResult{
+ m_options.ctapMCResult,
+ m_options.identityKey,
+ m_options.tunnelServerDomain,
+ };
+ m_options.mcResultProcessor(res);
+ return res;
+ }
+
+ GAResult Perform(const wauthn_client_data_s &,
+ const wauthn_pubkey_cred_request_options_s &) override
+ {
+ EXPECT_EQ(m_hint, (Hint{'g', 'a'}));
+ auto res = GAResult{
+ m_options.ctapGAResult,
+ m_options.identityKey,
+ m_options.tunnelServerDomain,
+ };
+ m_options.gaResultProcessor(res);
+ return res;
+ }
+
+ void
+ ProcessFollowingUpdateMsgs(std::function<void(UpdateMessage &&)> updateMsgCallback) override
+ {
+ m_options.followingCtapUpdatesProcessor(updateMsgCallback);
+ }
+
+ void Cancel() override
+ {
+ ADD_FAILURE() << "This should not be called";
+ throw std::runtime_error{""};
+ }
+
+private:
+ Options m_options;
+ Hint m_hint = {};
+};
+
+struct CommonTest {
+ wauthn_client_data_s clientData = {};
+ wauthn_client_data_s *clientDataPtr = &clientData;
+};
+
+struct MakeCredentialTest : public CommonTest {
+ wauthn_pubkey_cred_creation_options_s options = {};
+ wauthn_pubkey_cred_creation_options_s *optionsPtr = &options;
+};
+
+struct GetAssertionTest : public CommonTest {
+ wauthn_pubkey_cred_request_options_s options = {};
+ wauthn_pubkey_cred_request_options_s *optionsPtr = &options;
+};
+
+template <class... Tests>
+struct QrInitiatedTest : public Tests... {};
+
+struct LinkedData {
+ wauthn_const_buffer_s contactId = ToWauthnConstBuff("contact id");
+ wauthn_const_buffer_s linkId = ToWauthnConstBuff("link id");
+ wauthn_const_buffer_s linkSecret = ToWauthnConstBuff("link secret");
+ wauthn_const_buffer_s authenticatorPubKey = ToWauthnConstBuff("authenticator public key");
+ wauthn_const_buffer_s authenticatorName = ToWauthnConstBuff("authenticator name");
+ wauthn_const_buffer_s signature = ToWauthnConstBuff("signature");
+ wauthn_const_buffer_s tunnelServerDomain = ToWauthnConstBuff("tunnel server domain");
+ wauthn_const_buffer_s identityKey = ToWauthnConstBuff("identity key");
+
+ wauthn_hybrid_linked_data_s linkedData{
+ &contactId,
+ &linkId,
+ &linkSecret,
+ &authenticatorPubKey,
+ &authenticatorName,
+ &signature,
+ &tunnelServerDomain,
+ &identityKey,
+ };
+};
+
+template <class... Tests>
+struct StateAssistedTest : public Tests... {
+ LinkedData linkedData;
+
+ StateAssistedTest() noexcept { this->options.linked_device = &linkedData.linkedData; }
+};
+
+template <template <class> class Test, class PreTransactionCallback, class PostTransactionCallback>
+void TestQrTransactionMakeCredential(const PreTransactionCallback &preTransactionCallback,
+ const PostTransactionCallback &postTransactionCallback)
+{
+ SCOPED_TRACE("QR-initiated MakeCredential");
+ QrInitiatedTest<MakeCredentialTest, Test<wauthn_mc_callbacks_s>> test;
+ GenericMQrTransaction::Options options;
+ preTransactionCallback(test, options);
+ RequestHandler::Instance().Process(test.clientDataPtr,
+ Request<RequestKind::MC>{test.optionsPtr, test.callbacksPtr},
+ GenericMQrTransaction{std::move(options)},
+ ShouldNotBeUsedMStateAssistedTransaction{});
+ postTransactionCallback(test);
+}
+
+template <template <class> class Test, class PreTransactionCallback, class PostTransactionCallback>
+void TestQrTransactionGetAssertion(const PreTransactionCallback &preTransactionCallback,
+ const PostTransactionCallback &postTransactionCallback)
+{
+ SCOPED_TRACE("QR-initiated GetAssertion");
+ QrInitiatedTest<GetAssertionTest, Test<wauthn_ga_callbacks_s>> test;
+ GenericMQrTransaction::Options options;
+ preTransactionCallback(test, options);
+ RequestHandler::Instance().Process(test.clientDataPtr,
+ Request<RequestKind::GA>{test.optionsPtr, test.callbacksPtr},
+ GenericMQrTransaction{std::move(options)},
+ ShouldNotBeUsedMStateAssistedTransaction{});
+ postTransactionCallback(test);
+}
+
+template <template <class> class Test, class PreTransactionCallback, class PostTransactionCallback>
+void TestBothQrTransactions(const PreTransactionCallback &preTransactionCallback,
+ const PostTransactionCallback &postTransactionCallback)
+{
+ TestQrTransactionMakeCredential<Test>(preTransactionCallback, postTransactionCallback);
+ TestQrTransactionGetAssertion<Test>(preTransactionCallback, postTransactionCallback);
+}
+
+template <template <class> class Test, class PreTransactionCallback, class PostTransactionCallback>
+void TestStateAssistedTransactionMakeCredential(
+ const PreTransactionCallback &preTransactionCallback,
+ const PostTransactionCallback &postTransactionCallback)
+{
+ SCOPED_TRACE("state-assisted MakeCredential");
+ StateAssistedTest<MakeCredentialTest, Test<wauthn_mc_callbacks_s>> test;
+ GenericMStateAssistedTransaction::Options options;
+ preTransactionCallback(test, options);
+ RequestHandler::Instance().Process(test.clientDataPtr,
+ Request<RequestKind::MC>{test.optionsPtr, test.callbacksPtr},
+ ShouldNotBeUsedMQrTransaction{},
+ GenericMStateAssistedTransaction{std::move(options)});
+ postTransactionCallback(test);
+}
+
+template <template <class> class Test, class PreTransactionCallback, class PostTransactionCallback>
+void TestStateAssistedTransactionGetAssertion(
+ const PreTransactionCallback &preTransactionCallback,
+ const PostTransactionCallback &postTransactionCallback)
+{
+ SCOPED_TRACE("state-assisted GetAssertion");
+ StateAssistedTest<GetAssertionTest, Test<wauthn_ga_callbacks_s>> test;
+ GenericMStateAssistedTransaction::Options options;
+ preTransactionCallback(test, options);
+ RequestHandler::Instance().Process(test.clientDataPtr,
+ Request<RequestKind::GA>{test.optionsPtr, test.callbacksPtr},
+ ShouldNotBeUsedMQrTransaction{},
+ GenericMStateAssistedTransaction{std::move(options)});
+ postTransactionCallback(test);
+}
+
+template <template <class> class Test, class PreTransactionCallback, class PostTransactionCallback>
+void TestBothStateAssistedTransactions(const PreTransactionCallback &preTransactionCallback,
+ const PostTransactionCallback &postTransactionCallback)
+{
+ TestStateAssistedTransactionMakeCredential<Test>(preTransactionCallback,
+ postTransactionCallback);
+ TestStateAssistedTransactionGetAssertion<Test>(preTransactionCallback, postTransactionCallback);
+}
+
+template <template <class> class Test, class PreTransactionCallback, class PostTransactionCallback>
+void TestAll4Transactions(const PreTransactionCallback &preTransactionCallback,
+ const PostTransactionCallback &postTransactionCallback)
+{
+ TestBothQrTransactions<Test>(preTransactionCallback, postTransactionCallback);
+ TestBothStateAssistedTransactions<Test>(preTransactionCallback, postTransactionCallback);
+}
+
+template <class WauthnCallbacks>
+struct InvalidParamsTest {
+ std::string called;
+ WauthnCallbacks callbacks = {
+ [](const char *, void *) {
+ FAIL() << "qrcode_callback should not be called when parameters are invalid;";
+ },
+ [](const auto *pubkeyCred, wauthn_error_e result, void *data) {
+ auto &test = *static_cast<InvalidParamsTest *>(data);
+ test.called += "response_callback;";
+ EXPECT_EQ(result, WAUTHN_ERROR_INVALID_PARAMETER);
+ EXPECT_EQ(pubkeyCred, nullptr);
+ },
+ [](const auto *, wauthn_error_e, void *) {
+ FAIL() << "linked_data_callback should not be called when parameters are invalid;";
+ },
+ this,
+ };
+ WauthnCallbacks *callbacksPtr = &callbacks;
+};
+
+} // namespace
+
+TEST(RequestHandler, transaction_is_not_performed_with_nullptr_callbacks)
+{
+ TestAll4Transactions<InvalidParamsTest>(
+ [](auto &test, auto &options) {
+ test.callbacksPtr = nullptr;
+ options.mcResultProcessor = [&](auto &) { test.called += "result_processor;"; };
+ options.gaResultProcessor = [&](auto &) { test.called += "result_processor;"; };
+ options.followingCtapUpdatesProcessor = [&](auto &&) {
+ test.called += "update_processor;";
+ };
+ },
+ [](auto &test) { EXPECT_EQ(test.called, ""); });
+}
+
+TEST(RequestHandler, qr_initiated_transaction_is_not_performed_with_nullptr_qrcode_callback)
+{
+ TestBothQrTransactions<InvalidParamsTest>(
+ [](auto &test, auto &options) {
+ test.callbacks.qrcode_callback = nullptr;
+ options.mcResultProcessor = [&](auto &) { test.called += "result_processor;"; };
+ options.gaResultProcessor = [&](auto &) { test.called += "result_processor;"; };
+ options.followingCtapUpdatesProcessor = [&](auto &&) {
+ test.called += "update_processor;";
+ };
+ },
+ [](auto &test) { EXPECT_EQ(test.called, "response_callback;"); });
+}
+
+namespace {
+
+template <class WauthnCallbacks>
+struct SuccessfulRequestTest {
+ std::string called;
+ WauthnCallbacks callbacks = {
+ [](const char *, void *) {},
+ [](const auto *pubkeyCred, wauthn_error_e result, void *data) {
+ auto &test = *static_cast<SuccessfulRequestTest *>(data);
+ test.called += "response_callback;";
+ EXPECT_EQ(result, WAUTHN_ERROR_NONE);
+ EXPECT_NE(pubkeyCred, nullptr);
+ },
+ [](const auto *linkedData, wauthn_error_e result, void *data) {
+ auto &test = *static_cast<SuccessfulRequestTest *>(data);
+ test.called += "linked_data_callback;";
+ EXPECT_EQ(result, WAUTHN_ERROR_NONE);
+ EXPECT_EQ(linkedData, nullptr);
+ },
+ this,
+ };
+ WauthnCallbacks *callbacksPtr = &callbacks;
+};
+
+//
+} // namespace
+
+TEST(RequestHandler, state_assisted_transaction_is_performed_with_nullptr_qrcode_callback)
+{
+ TestBothStateAssistedTransactions<SuccessfulRequestTest>(
+ [](auto &test, auto &options) {
+ test.callbacks.qrcode_callback = nullptr;
+ options.mcResultProcessor = [&](auto &) { test.called += "result_processor;"; };
+ options.gaResultProcessor = [&](auto &) { test.called += "result_processor;"; };
+ options.followingCtapUpdatesProcessor = [&](auto &&) {
+ test.called += "update_processor;";
+ };
+ },
+ [](auto &test) {
+ EXPECT_EQ(test.called,
+ "result_processor;response_callback;update_processor;linked_data_callback;");
+ });
+}
+
+TEST(RequestHandler, transaction_is_not_performed_with_nullptr_response_callback)
+{
+ TestAll4Transactions<InvalidParamsTest>(
+ [](auto &test, auto &options) {
+ test.callbacks.response_callback = nullptr;
+ options.mcResultProcessor = [&](auto &) { test.called += "result_processor;"; };
+ options.gaResultProcessor = [&](auto &) { test.called += "result_processor;"; };
+ options.followingCtapUpdatesProcessor = [&](auto &&) {
+ test.called += "update_processor;";
+ };
+ },
+ [](auto &test) { EXPECT_EQ(test.called, ""); });
+}
+
+TEST(RequestHandler, transaction_is_not_performed_with_nullptr_linked_data_callback)
+{
+ TestAll4Transactions<InvalidParamsTest>(
+ [](auto &test, auto &options) {
+ test.callbacks.linked_data_callback = nullptr;
+ options.mcResultProcessor = [&](auto &) { test.called += "result_processor;"; };
+ options.gaResultProcessor = [&](auto &) { test.called += "result_processor;"; };
+ options.followingCtapUpdatesProcessor = [&](auto &&) {
+ test.called += "update_processor;";
+ };
+ },
+ [](auto &test) { EXPECT_EQ(test.called, "response_callback;"); });
+}
+
+TEST(RequestHandler, transaction_is_not_performed_with_nullptr_client_data)
+{
+ TestAll4Transactions<InvalidParamsTest>(
+ [](auto &test, auto &options) {
+ test.clientDataPtr = nullptr;
+ options.mcResultProcessor = [&](auto &) { test.called += "result_processor;"; };
+ options.gaResultProcessor = [&](auto &) { test.called += "result_processor;"; };
+ options.followingCtapUpdatesProcessor = [&](auto &&) {
+ test.called += "update_processor;";
+ };
+ },
+ [](auto &test) { EXPECT_EQ(test.called, "response_callback;"); });
+}
+
+TEST(RequestHandler, transaction_is_not_performed_with_nullptr_options)
+{
+ TestAll4Transactions<InvalidParamsTest>(
+ [](auto &test, auto &options) {
+ test.optionsPtr = nullptr;
+ options.mcResultProcessor = [&](auto &) { test.called += "result_processor;"; };
+ options.gaResultProcessor = [&](auto &) { test.called += "result_processor;"; };
+ options.followingCtapUpdatesProcessor = [&](auto &&) {
+ test.called += "update_processor;";
+ };
+ },
+ [](auto &test) { EXPECT_EQ(test.called, "response_callback;"); });
+}
+
+namespace {
+
+template <class WauthnCallbacks>
+struct QrCallbackTest {
+ int counter = 0;
+ std::optional<std::string> receivedQrContents;
+ std::optional<wauthn_error_e> responseCallbackRes;
+ std::optional<wauthn_error_e> linkedDataCallbackRes;
+ WauthnCallbacks callbacks = {
+ [](const char *qrContents, void *data) {
+ auto &test = *static_cast<QrCallbackTest *>(data);
+ ++test.counter;
+ if (qrContents)
+ test.receivedQrContents = std::string{qrContents};
+ },
+ [](const auto *pubkeyCred, wauthn_error_e result, void *data) {
+ auto &test = *static_cast<QrCallbackTest *>(data);
+ test.responseCallbackRes = result;
+ EXPECT_EQ(pubkeyCred == nullptr, result != WAUTHN_ERROR_NONE);
+ },
+ [](const auto *linkedData, wauthn_error_e result, void *data) {
+ auto &test = *static_cast<QrCallbackTest *>(data);
+ test.linkedDataCallbackRes = result;
+ EXPECT_EQ(linkedData == nullptr, result != WAUTHN_ERROR_NONE_AND_WAIT);
+ },
+ this,
+ };
+ const WauthnCallbacks *callbacksPtr = &callbacks;
+};
+
+} // namespace
+
+TEST(RequestHandler, qr_initiated_transaction_calls_qr_callback_once)
+{
+ TestBothQrTransactions<QrCallbackTest>(
+ [](auto & /*test*/, GenericMQrTransaction::Options &options) {
+ options.callQrCallback = true;
+ },
+ [](auto &test) {
+ EXPECT_EQ(test.counter, 1);
+ EXPECT_EQ(test.receivedQrContents, "");
+ EXPECT_EQ(test.responseCallbackRes, WAUTHN_ERROR_NONE);
+ EXPECT_EQ(test.linkedDataCallbackRes, WAUTHN_ERROR_NONE);
+ });
+}
+
+TEST(RequestHandler, qr_initiated_transaction_qr_callback_qr_contents)
+{
+ [[maybe_unused]] static constexpr auto QR_CODE_CONTENTS = std::string_view{"FIDO:/01234"};
+ TestBothQrTransactions<QrCallbackTest>(
+ [](auto & /*test*/, GenericMQrTransaction::Options &options) {
+ options.callQrCallback = true;
+ options.qrCodeContents = QR_CODE_CONTENTS;
+ },
+ [](auto &test) {
+ EXPECT_EQ(test.counter, 1);
+ EXPECT_EQ(test.receivedQrContents, QR_CODE_CONTENTS);
+ EXPECT_EQ(test.responseCallbackRes, WAUTHN_ERROR_NONE);
+ EXPECT_EQ(test.linkedDataCallbackRes, WAUTHN_ERROR_NONE);
+ });
+}
+
+TEST(RequestHandler, state_assisted_transaction_does_not_call_qr_callback)
+{
+ TestBothStateAssistedTransactions<QrCallbackTest>(
+ [](auto &, GenericMStateAssistedTransaction::Options &) {},
+ [](auto &test) {
+ EXPECT_EQ(test.counter, 0);
+ EXPECT_EQ(test.receivedQrContents, std::nullopt);
+ EXPECT_EQ(test.responseCallbackRes, WAUTHN_ERROR_NONE);
+ EXPECT_EQ(test.linkedDataCallbackRes, WAUTHN_ERROR_NONE);
+ });
+}
+
+namespace {
+
+template <class WauthnCallbacks>
+struct PotentialErrorsTest {
+ std::string called;
+ std::optional<wauthn_error_e> responseCallbackRes;
+ std::optional<wauthn_error_e> linkedDataCallbackRes;
+ WauthnCallbacks callbacks = {
+ [](const char *, void *) {},
+ [](const auto *pubkeyCred, wauthn_error_e result, void *data) {
+ auto &test = *static_cast<PotentialErrorsTest *>(data);
+ test.called += "response_callback;";
+ test.responseCallbackRes = result;
+ EXPECT_EQ(pubkeyCred == nullptr, result != WAUTHN_ERROR_NONE);
+ },
+ [](const auto *linkedData, wauthn_error_e result, void *data) {
+ auto &test = *static_cast<PotentialErrorsTest *>(data);
+ test.called += "linked_data_callback;";
+ test.linkedDataCallbackRes = result;
+ EXPECT_EQ(linkedData == nullptr, result != WAUTHN_ERROR_NONE_AND_WAIT);
+ },
+ this,
+ };
+ WauthnCallbacks *callbacksPtr = &callbacks;
+};
+
+} // namespace
+
+TEST(RequestHandler, perform_transaction_throws_std_bad_alloc)
+{
+ TestAll4Transactions<PotentialErrorsTest>(
+ [](auto &test, auto &options) {
+ options.mcResultProcessor = [](auto &) { throw std::bad_alloc{}; };
+ options.gaResultProcessor = [](auto &) { throw std::bad_alloc{}; };
+ options.followingCtapUpdatesProcessor = [&](auto &&) {
+ test.called += "update_processor;";
+ };
+ },
+ [](auto &test) {
+ EXPECT_EQ(test.called, "response_callback;");
+ EXPECT_EQ(test.responseCallbackRes, WAUTHN_ERROR_MEMORY);
+ });
+}
+
+TEST(RequestHandler, perform_transaction_throws_runtime_error)
+{
+ TestAll4Transactions<PotentialErrorsTest>(
+ [](auto &test, auto &options) {
+ options.mcResultProcessor = [](auto &) { throw std::runtime_error{"abc"}; };
+ options.gaResultProcessor = [](auto &) { throw std::runtime_error{"abc"}; };
+ options.followingCtapUpdatesProcessor = [&](auto &&) {
+ test.called += "update_processor;";
+ };
+ },
+ [](auto &test) {
+ EXPECT_EQ(test.called, "response_callback;");
+ EXPECT_EQ(test.responseCallbackRes, WAUTHN_ERROR_UNKNOWN);
+ });
+}
+
+TEST(RequestHandler, perform_transaction_throws_int)
+{
+ TestAll4Transactions<PotentialErrorsTest>(
+ [](auto &test, auto &options) {
+ options.mcResultProcessor = [](auto &) { throw 42; };
+ options.gaResultProcessor = [](auto &) { throw 42; };
+ options.followingCtapUpdatesProcessor = [&](auto &&) {
+ test.called += "update_processor;";
+ };
+ },
+ [](auto &test) {
+ EXPECT_EQ(test.called, "response_callback;");
+ EXPECT_EQ(test.responseCallbackRes, WAUTHN_ERROR_UNKNOWN);
+ });
+}
+
+TEST(RequestHandler, invalid_transport_in_make_credential_transaction_response)
+{
+ static constexpr auto preCallback = [](auto &test, auto &options) {
+ options.mcResultProcessor = [](ITransaction::MCResult &mcRes) {
+ mcRes.ctapResult.getInfo.m_transports.emplace_back("some unknown transport");
+ };
+ options.followingCtapUpdatesProcessor = [&](auto &&) {
+ test.called += "update_processor;";
+ };
+ };
+ static constexpr auto postCallback = [](auto &test) {
+ EXPECT_EQ(test.called, "response_callback;");
+ EXPECT_EQ(test.responseCallbackRes, WAUTHN_ERROR_UNKNOWN);
+ };
+ TestQrTransactionMakeCredential<PotentialErrorsTest>(preCallback, postCallback);
+ TestStateAssistedTransactionMakeCredential<PotentialErrorsTest>(preCallback, postCallback);
+}
+
+namespace {
+
+template <void (*THROW_CALLBACK)()>
+void TestResponseCallbackThrows()
+{
+ TestAll4Transactions<PotentialErrorsTest>(
+ [](auto &test, auto &options) {
+ options.mcResultProcessor = [&](auto &) { test.called += "result_processor;"; };
+ options.gaResultProcessor = [&](auto &) { test.called += "result_processor;"; };
+ options.followingCtapUpdatesProcessor = [&](auto &&) {
+ test.called += "update_processor;";
+ };
+ test.callbacks.response_callback =
+ [](const auto * /*pubkeyCred*/, wauthn_error_e result, void *data) {
+ auto &innerTest = *static_cast<std::remove_reference_t<decltype(test)> *>(data);
+ innerTest.called += "response_callback;";
+ innerTest.responseCallbackRes = result;
+ THROW_CALLBACK();
+ };
+ test.callbacks.linked_data_callback =
+ [](const auto * /*linkedData*/, wauthn_error_e result, void *data) {
+ auto &innerTest = *static_cast<std::remove_reference_t<decltype(test)> *>(data);
+ innerTest.called += "linked_data_callback;";
+ innerTest.linkedDataCallbackRes = result;
+ };
+ test.callbacks.user_data = &test;
+ },
+ [](auto &test) {
+ EXPECT_EQ(test.called, "result_processor;response_callback;linked_data_callback;");
+ EXPECT_EQ(test.responseCallbackRes, WAUTHN_ERROR_NONE);
+ EXPECT_EQ(test.linkedDataCallbackRes, WAUTHN_ERROR_UNKNOWN);
+ });
+}
+
+void ThrowRuntimeError() { throw std::runtime_error{"xyz"}; }
+
+void ThrowInt() { throw 42; }
+
+} // namespace
+
+TEST(RequestHandler, response_callback_throws_runtime_error)
+{
+ TestResponseCallbackThrows<ThrowRuntimeError>();
+}
+
+TEST(RequestHandler, response_callback_throws_int) { TestResponseCallbackThrows<ThrowInt>(); }
+
+namespace {
+
+UpdateMessage ExampleUpdateMessage()
+{
+ auto bv = BUFFER_VIEW(ANDROID_EXAMPLE_RAW_UPDATE_MSG);
+ bv.remove_prefix(1); // Remove CTAP message type prefix.
+ UpdateMessage msg;
+ msg.Deserialize(bv);
+ return msg;
+}
+
+enum class WhichLinkedDataCallbackWillThrow {
+ FROM_UPDATE_PROCESSOR,
+ LAST,
+};
+
+template <void (*THROW_CALLBACK)(),
+ WhichLinkedDataCallbackWillThrow WHICH_LINKED_CALLBACK_WILL_THROW>
+void TestLinkedDataCallbackThrows()
+{
+ TestAll4Transactions<PotentialErrorsTest>(
+ [](auto &test, auto &options) {
+ options.mcResultProcessor = [&](auto &) { test.called += "result_processor;"; };
+ options.gaResultProcessor = [&](auto &) { test.called += "result_processor;"; };
+ options.followingCtapUpdatesProcessor = [&](auto &&updateMessageCallback) {
+ updateMessageCallback(ExampleUpdateMessage());
+ test.called += "update_processor;";
+ };
+ test.callbacks.response_callback =
+ [](const auto * /*pubkeyCred*/, wauthn_error_e result, void *data) {
+ auto &innerTest = *static_cast<std::remove_reference_t<decltype(test)> *>(data);
+ innerTest.called += "response_callback;";
+ innerTest.responseCallbackRes = result;
+ };
+ test.callbacks.linked_data_callback =
+ [](const auto * /*linkedData*/, wauthn_error_e result, void *data) {
+ auto &innerTest = *static_cast<std::remove_reference_t<decltype(test)> *>(data);
+ innerTest.called += "linked_data_callback;";
+ innerTest.linkedDataCallbackRes = result;
+ switch (WHICH_LINKED_CALLBACK_WILL_THROW) {
+ case WhichLinkedDataCallbackWillThrow::FROM_UPDATE_PROCESSOR:
+ if (result == WAUTHN_ERROR_NONE_AND_WAIT)
+ THROW_CALLBACK();
+ break;
+ case WhichLinkedDataCallbackWillThrow::LAST:
+ if (result == WAUTHN_ERROR_NONE)
+ THROW_CALLBACK();
+ break;
+ }
+ };
+ test.callbacks.user_data = &test;
+ },
+ [](auto &test) {
+ EXPECT_EQ(test.responseCallbackRes, WAUTHN_ERROR_NONE);
+ switch (WHICH_LINKED_CALLBACK_WILL_THROW) {
+ case WhichLinkedDataCallbackWillThrow::FROM_UPDATE_PROCESSOR:
+ EXPECT_EQ(test.called,
+ "result_processor;response_callback;linked_data_callback;linked_data_"
+ "callback;");
+ EXPECT_EQ(test.linkedDataCallbackRes, WAUTHN_ERROR_UNKNOWN);
+ break;
+ case WhichLinkedDataCallbackWillThrow::LAST:
+ EXPECT_EQ(
+ test.called,
+ "result_processor;response_callback;linked_data_callback;update_processor;"
+ "linked_data_callback;");
+ EXPECT_EQ(test.linkedDataCallbackRes, WAUTHN_ERROR_NONE);
+ break;
+ }
+ });
+}
+
+} // namespace
+
+TEST(RequestHandler, linked_data_callback_throws_runtime_error)
+{
+ TestLinkedDataCallbackThrows<ThrowRuntimeError,
+ WhichLinkedDataCallbackWillThrow::FROM_UPDATE_PROCESSOR>();
+ TestLinkedDataCallbackThrows<ThrowRuntimeError, WhichLinkedDataCallbackWillThrow::LAST>();
+}
+
+TEST(RequestHandler, linked_data_callback_throws_int)
+{
+ TestLinkedDataCallbackThrows<ThrowInt,
+ WhichLinkedDataCallbackWillThrow::FROM_UPDATE_PROCESSOR>();
+ TestLinkedDataCallbackThrows<ThrowInt, WhichLinkedDataCallbackWillThrow::LAST>();
+}
+
+namespace {
+
+template <class WauthnCallbacks>
+struct ResponseCallbackArgumentsTest {
+ static constexpr const char EXAMPLE_CLIENT_JSON[] = "{\"some_json\": 42}";
+
+ std::string called;
+ bool addLinkedData = false;
+ bool nonEmptyUserId = false;
+ WauthnCallbacks callbacks = {
+ [](const char *, void *) {},
+ [](const auto *pubkeyCred, wauthn_error_e result, void *data) {
+ auto &test = *static_cast<ResponseCallbackArgumentsTest *>(data);
+ test.called += "response_callback;";
+ EXPECT_EQ(result, WAUTHN_ERROR_NONE);
+
+ ASSERT_NE(pubkeyCred, nullptr);
+ EXPECT_EQ(pubkeyCred->type, PCT_PUBLIC_KEY);
+
+ ASSERT_NE(pubkeyCred->response, nullptr);
+ EXPECT_EQ(pubkeyCred->response->client_data_json, BUFFER_VIEW(EXAMPLE_CLIENT_JSON));
+ EXPECT_EQ(pubkeyCred->response->authenticator_data, BUFFER_VIEW("auth data"));
+ if constexpr (std::is_same_v<decltype(pubkeyCred),
+ const wauthn_pubkey_credential_attestation_s *>) {
+ EXPECT_EQ(pubkeyCred->response->attestation_object,
+ BUFFER_VIEW("attestation object"));
+ EXPECT_EQ(pubkeyCred->response->transports,
+ WAUTHN_TRANSPORT_INTERNAL | WAUTHN_TRANSPORT_HYBRID);
+ auto attestationData = ExampleAttestationData();
+ EXPECT_EQ(pubkeyCred->response->subject_pubkey_info,
+ ToBufferView(attestationData.m_publicKeyDer));
+ EXPECT_EQ(pubkeyCred->response->pubkey_alg, attestationData.m_alg);
+ EXPECT_EQ(pubkeyCred->rawId, attestationData.m_credentialId);
+ auto idInBase64 = Base64UrlEncode(attestationData.m_credentialId);
+ EXPECT_EQ(pubkeyCred->id, ToBufferView(idInBase64));
+ } else {
+ EXPECT_EQ(pubkeyCred->response->attestation_object, nullptr);
+ EXPECT_EQ(pubkeyCred->response->signature, BUFFER_VIEW("signature"));
+ if (test.nonEmptyUserId)
+ EXPECT_EQ(pubkeyCred->response->user_handle, BUFFER_VIEW("user handle"));
+ else
+ EXPECT_EQ(pubkeyCred->response->user_handle, nullptr);
+ EXPECT_EQ(pubkeyCred->rawId, BUFFER_VIEW("credential id"));
+ auto idInBase64 = Base64UrlEncode(BUFFER_VIEW("credential id"));
+ EXPECT_EQ(pubkeyCred->id, ToBufferView(idInBase64));
+ }
+
+ EXPECT_EQ(pubkeyCred->authenticator_attachment, AA_CROSS_PLATFORM);
+ EXPECT_EQ(pubkeyCred->extensions, nullptr);
+
+ if (test.addLinkedData) {
+ ASSERT_NE(pubkeyCred->linked_device, nullptr);
+ auto updateMessage = ExampleUpdateMessage();
+ auto &linkData = updateMessage.GetLinkData().value();
+ EXPECT_EQ(pubkeyCred->linked_device->contact_id,
+ ToBufferView(linkData.m_contactId));
+ EXPECT_EQ(pubkeyCred->linked_device->link_id, ToBufferView(linkData.m_linkId));
+ EXPECT_EQ(pubkeyCred->linked_device->link_secret,
+ ToBufferView(linkData.m_linkSecret));
+ EXPECT_EQ(pubkeyCred->linked_device->authenticator_pubkey,
+ ToBufferView(linkData.m_authenticatorPublicKey));
+ EXPECT_EQ(pubkeyCred->linked_device->authenticator_name,
+ ToBufferView(linkData.m_authenticatorName));
+ EXPECT_EQ(pubkeyCred->linked_device->signature,
+ ToBufferView(linkData.m_handshakeSignature));
+ EXPECT_EQ(pubkeyCred->linked_device->tunnel_server_domain,
+ BUFFER_VIEW("some.tunnel.domain"));
+ EXPECT_EQ(pubkeyCred->linked_device->identity_key,
+ BUFFER_VIEW("client platform priv key"));
+ } else
+ EXPECT_EQ(pubkeyCred->linked_device, nullptr);
+ },
+ [](const auto *linkedData, wauthn_error_e result, void *data) {
+ auto &test = *static_cast<ResponseCallbackArgumentsTest *>(data);
+ test.called += "linked_data_callback;";
+ EXPECT_EQ(result, WAUTHN_ERROR_NONE);
+ EXPECT_EQ(linkedData, nullptr);
+ },
+ this,
+ };
+ WauthnCallbacks *callbacksPtr = &callbacks;
+};
+
+} // namespace
+
+TEST(RequestHandler, response_callback_pubkey_cred_is_valid)
+{
+ wauthn_const_buffer_s clientDataJson;
+ for (bool addLinkedData : {false, true}) {
+ for (bool nonEmptyUserId : {false, true}) {
+ TestAll4Transactions<ResponseCallbackArgumentsTest>(
+ [&](auto &test, auto &options) {
+ // Test params
+ test.addLinkedData = addLinkedData;
+ test.nonEmptyUserId = nonEmptyUserId;
+ // Transaction params
+ clientDataJson = ToWauthnConstBuff(test.EXAMPLE_CLIENT_JSON);
+ test.clientData.client_data_json = &clientDataJson;
+ auto transactionResultProcessor = [&](auto &result) {
+ result.ctapResult.response.m_authDataRaw = BUFFER("auth data");
+ if constexpr (std::is_same_v<decltype(result), ITransaction::MCResult &>) {
+ result.ctapResult.response.m_attestationObject =
+ BUFFER("attestation object");
+ result.ctapResult.getInfo.m_transports = {"internal", "hybrid"};
+ result.ctapResult.response.m_authData.m_attestationData =
+ ExampleAttestationData();
+ } else {
+ result.ctapResult.response.m_signature = BUFFER("signature");
+ result.ctapResult.response.m_userId =
+ nonEmptyUserId ? BUFFER("user handle") : Buffer{};
+ result.ctapResult.response.m_credentialId = BUFFER("credential id");
+ }
+ if (test.addLinkedData) {
+ result.ctapResult.upMsg = ExampleUpdateMessage();
+ result.clientPlatformPrivKey = BUFFER("client platform priv key");
+ result.tunnelServerDomain = "some.tunnel.domain";
+ }
+ };
+ options.mcResultProcessor = transactionResultProcessor;
+ options.gaResultProcessor = transactionResultProcessor;
+ },
+ [&](auto &test) {
+ EXPECT_EQ(test.called, "response_callback;linked_data_callback;");
+ });
+ }
+ }
+}
+
+namespace {
+
+template <class WauthnCallbacks>
+struct LinkedDataCallbackArgumentsTest {
+ std::string called;
+ int remainingLinkedDataCallbackCalls = 0;
+ WauthnCallbacks callbacks = {
+ [](const char *, void *) {},
+ [](const auto *pubkeyCred, wauthn_error_e result, void *data) {
+ auto &test = *static_cast<LinkedDataCallbackArgumentsTest *>(data);
+ test.called += "response_callback;";
+ EXPECT_EQ(result, WAUTHN_ERROR_NONE);
+ EXPECT_NE(pubkeyCred, nullptr);
+ },
+ [](const auto *linkedData, wauthn_error_e result, void *data) {
+ auto &test = *static_cast<LinkedDataCallbackArgumentsTest *>(data);
+ test.called += "linked_data_callback;";
+ if (--test.remainingLinkedDataCallbackCalls == 0) {
+ EXPECT_EQ(result, WAUTHN_ERROR_NONE);
+ EXPECT_EQ(linkedData, nullptr);
+ } else {
+ EXPECT_EQ(result, WAUTHN_ERROR_NONE_AND_WAIT);
+ ASSERT_NE(linkedData, nullptr);
+ auto updateMessage = ExampleUpdateMessage();
+ auto &linkData = updateMessage.GetLinkData().value();
+ EXPECT_EQ(linkedData->contact_id, ToBufferView(linkData.m_contactId));
+ EXPECT_EQ(linkedData->link_id, ToBufferView(linkData.m_linkId));
+ EXPECT_EQ(linkedData->link_secret, ToBufferView(linkData.m_linkSecret));
+ EXPECT_EQ(linkedData->authenticator_pubkey,
+ ToBufferView(linkData.m_authenticatorPublicKey));
+ EXPECT_EQ(linkedData->authenticator_name,
+ ToBufferView(linkData.m_authenticatorName));
+ EXPECT_EQ(linkedData->signature, ToBufferView(linkData.m_handshakeSignature));
+ EXPECT_EQ(linkedData->tunnel_server_domain, BUFFER_VIEW("some.tunnel.domain"));
+ EXPECT_EQ(linkedData->identity_key, BUFFER_VIEW("client platform priv key"));
+ }
+ },
+ this,
+ };
+ WauthnCallbacks *callbacksPtr = &callbacks;
+};
+
+} // namespace
+
+TEST(RequestHandler, linked_data_callback_linked_data_is_valid)
+{
+ for (int updateMessagesNum = 0; updateMessagesNum < 10; ++updateMessagesNum) {
+ TestAll4Transactions<LinkedDataCallbackArgumentsTest>(
+ [&](auto &test, auto &options) {
+ auto transactionResultProcessor = [&](auto &result) {
+ result.clientPlatformPrivKey = BUFFER("client platform priv key");
+ result.tunnelServerDomain = "some.tunnel.domain";
+ };
+ options.mcResultProcessor = transactionResultProcessor;
+ options.gaResultProcessor = transactionResultProcessor;
+ options.followingCtapUpdatesProcessor = [&](auto &&updateMessageCallback) {
+ test.called += "update_processor;";
+ for (int i = 0; i < updateMessagesNum; ++i) {
+ updateMessageCallback(ExampleUpdateMessage());
+ }
+ };
+
+ test.remainingLinkedDataCallbackCalls = updateMessagesNum + 1;
+ },
+ [&](auto &test) {
+ std::string expected = "response_callback;update_processor;";
+ for (int i = 0; i < updateMessagesNum + 1; ++i) {
+ expected += "linked_data_callback;";
+ }
+ EXPECT_EQ(test.called, expected);
+ });
+ }
+}
+
+namespace {
+
+template <class BaseTransaction>
+class CancellationTransaction : public BaseTransaction {
+ typename BaseTransaction::MCResult
+ Perform(const wauthn_client_data_s &, const wauthn_pubkey_cred_creation_options_s &) override
+ {
+ m_cancelFacilitator.CancelCheck();
+ return typename BaseTransaction::MCResult{
+ ExampleCtapMCResult(),
+ {},
+ "",
+ };
+ }
+
+ typename BaseTransaction::GAResult
+ Perform(const wauthn_client_data_s &, const wauthn_pubkey_cred_request_options_s &) override
+ {
+ m_cancelFacilitator.CancelCheck();
+ return typename BaseTransaction::GAResult{
+ {},
+ {},
+ "",
+ };
+ }
+
+ void ProcessFollowingUpdateMsgs(std::function<void(UpdateMessage &&)>) override
+ {
+ m_cancelFacilitator
+ .IgnorePendingCancellation(); // We only check if this function is being
+ // cancelled after starting, as this is disallowed.
+ try {
+ m_cancelFacilitator.CancelCheck();
+ } catch (const Exception::Cancelled &) {
+ FAIL() << "This method should not be allowed to cancel by the RequestHandler";
+ }
+ }
+
+ void Cancel() override { m_cancelFacilitator.Cancel(); }
+
+private:
+ CancelFacilitator m_cancelFacilitator;
+};
+
+class CancellationMQrTransaction : public CancellationTransaction<IQrTransaction> {
+public:
+ CancellationMQrTransaction() = default;
+
+ void Initialize(Hint &&,
+ std::function<void(std::string &&)> &&,
+ std::unique_ptr<IQrCodeShower> &&,
+ IBtAdvertScannerUPtr &&,
+ std::unique_ptr<IHandshake> &&,
+ std::unique_ptr<ICtapMessageProcessor> &&) override
+ {
+ }
+};
+
+class CancellationMStateAssistedTransaction
+: public CancellationTransaction<IStateAssistedTransaction> {
+public:
+ CancellationMStateAssistedTransaction() = default;
+
+ void Initialize(Hint &&,
+ std::unique_ptr<IHandshake> &&,
+ IBtAdvertScannerUPtr &&,
+ std::unique_ptr<ICtapMessageProcessor> &&) override
+ {
+ }
+};
+
+class RequestHandlerCancellationWrapper {
+public:
+ static void Cancel() { RequestHandler::Instance().Cancel(); }
+};
+
+template <template <class...> class Test,
+ RequestKind REQUEST_KIND,
+ class QrTransaction,
+ class StateAssistedTransaction>
+void TestCancellationFromTheOtherThread()
+{
+ auto makeCancellationTest = [&] { return RequestHandlerCancellationWrapper{}; };
+ TestCancelFromTheOtherThread<RequestHandlerCancellationWrapper>(
+ 800,
+ 20,
+ makeCancellationTest,
+ [](RequestHandlerCancellationWrapper &) {
+ Test<std::conditional_t<REQUEST_KIND == RequestKind::MC,
+ MakeCredentialTest,
+ GetAssertionTest>,
+ PotentialErrorsTest<std::conditional_t<REQUEST_KIND == RequestKind::MC,
+ wauthn_mc_callbacks_s,
+ wauthn_ga_callbacks_s>>>
+ test;
+ RequestHandler::Instance().Process(
+ test.clientDataPtr,
+ Request<REQUEST_KIND>{test.optionsPtr, &test.callbacks},
+ QrTransaction{},
+ StateAssistedTransaction{});
+ if (test.responseCallbackRes == WAUTHN_ERROR_NONE) {
+ EXPECT_EQ(test.called, "response_callback;linked_data_callback;");
+ EXPECT_EQ(test.linkedDataCallbackRes, WAUTHN_ERROR_NONE);
+ } else if (test.responseCallbackRes == WAUTHN_ERROR_CANCELLED) {
+ EXPECT_EQ(test.called, "response_callback;");
+ throw Exception::Cancelled{};
+ } else {
+ FAIL() << test.called << '\n'
+ << (test.responseCallbackRes
+ ? std::to_string(static_cast<int>(*test.responseCallbackRes))
+ : "nullopt");
+ }
+ },
+ false);
+}
+
+} // namespace
+
+TEST(RequestHandler, cancel_from_the_other_thread_qr_initiated_make_credential)
+{
+ TestCancellationFromTheOtherThread<QrInitiatedTest,
+ RequestKind::MC,
+ CancellationMQrTransaction,
+ ShouldNotBeUsedMStateAssistedTransaction>();
+}
+
+TEST(RequestHandler, cancel_from_the_other_thread_qr_initiated_get_assertion)
+{
+ TestCancellationFromTheOtherThread<QrInitiatedTest,
+ RequestKind::GA,
+ CancellationMQrTransaction,
+ ShouldNotBeUsedMStateAssistedTransaction>();
+}
+
+TEST(RequestHandler, cancel_from_the_other_thread_state_assisted_make_credential)
+{
+ TestCancellationFromTheOtherThread<StateAssistedTest,
+ RequestKind::MC,
+ ShouldNotBeUsedMQrTransaction,
+ CancellationMStateAssistedTransaction>();
+}
+
+TEST(RequestHandler, cancel_from_the_other_thread_state_assisted_get_assertion)
+{
+ TestCancellationFromTheOtherThread<StateAssistedTest,
+ RequestKind::GA,
+ ShouldNotBeUsedMQrTransaction,
+ CancellationMStateAssistedTransaction>();
+}
+
+namespace {
+
+template <size_t idx1, size_t idx2, class Func, class... Tests1, class... Tests2>
+void RunWithAllCombinationsImpl(const std::tuple<Tests1...> &tests1,
+ const std::tuple<Tests2...> &tests2,
+ const Func &func)
+{
+ if constexpr (idx1 == sizeof...(Tests1))
+ return;
+ else if constexpr (idx2 == sizeof...(Tests2))
+ return RunWithAllCombinationsImpl<idx1 + 1, 0>(tests1, tests2, func);
+ else {
+ func(std::get<idx1>(tests1), std::get<idx2>(tests2));
+ RunWithAllCombinationsImpl<idx1, idx2 + 1>(tests1, tests2, func);
+ }
+}
+
+template <class Func, class... Tests1, class... Tests2>
+void RunWithAllCombinations(const std::tuple<Tests1...> &tests1,
+ const std::tuple<Tests2...> &tests2,
+ const Func &func)
+{
+ RunWithAllCombinationsImpl<0, 0>(tests1, tests2, func);
+}
+
+template <template <class> class FirstTest, template <class> class SecondTest, class Func>
+void RunWithAll4x4Combinations(const Func &func)
+{
+ RunWithAllCombinations(
+ std::tuple{
+ [](const auto &preCallback, const auto &postCallback) {
+ TestQrTransactionMakeCredential<FirstTest>(preCallback, postCallback);
+ },
+ [](const auto &preCallback, const auto &postCallback) {
+ TestQrTransactionGetAssertion<FirstTest>(preCallback, postCallback);
+ },
+ [](const auto &preCallback, const auto &postCallback) {
+ TestStateAssistedTransactionMakeCredential<FirstTest>(preCallback, postCallback);
+ },
+ [](const auto &preCallback, const auto &postCallback) {
+ TestStateAssistedTransactionGetAssertion<FirstTest>(preCallback, postCallback);
+ },
+ },
+ std::tuple{
+ [](const auto &preCallback, const auto &postCallback) {
+ TestQrTransactionMakeCredential<SecondTest>(preCallback, postCallback);
+ },
+ [](const auto &preCallback, const auto &postCallback) {
+ TestQrTransactionGetAssertion<SecondTest>(preCallback, postCallback);
+ },
+ [](const auto &preCallback, const auto &postCallback) {
+ TestStateAssistedTransactionMakeCredential<SecondTest>(preCallback, postCallback);
+ },
+ [](const auto &preCallback, const auto &postCallback) {
+ TestStateAssistedTransactionGetAssertion<SecondTest>(preCallback, postCallback);
+ },
+ },
+ func);
+}
+
+template <class WauthnCallbacks>
+struct SecondSimultaneousRequestTest {
+ std::string called;
+ WauthnCallbacks callbacks = {
+ [](const char *, void *) {},
+ [](const auto *pubkeyCred, wauthn_error_e result, void *data) {
+ auto &test = *static_cast<SecondSimultaneousRequestTest *>(data);
+ test.called += "response_callback;";
+ EXPECT_EQ(result, WAUTHN_ERROR_NOT_ALLOWED);
+ EXPECT_EQ(pubkeyCred, nullptr);
+ },
+ [](const auto * /*linkedData*/, wauthn_error_e /*result*/, void *data) {
+ auto &test = *static_cast<SecondSimultaneousRequestTest *>(data);
+ test.called += "linked_data_callback;";
+ FAIL() << "this callback should not be called";
+ },
+ this,
+ };
+ WauthnCallbacks *callbacksPtr = &callbacks;
+};
+
+} // namespace
+
+TEST(RequestHandler, two_simultaneous_requests)
+{
+ RunWithAll4x4Combinations<SuccessfulRequestTest, SecondSimultaneousRequestTest>(
+ [&](const auto &firstRequestTest, const auto &secondRequestTest) {
+ auto startSecondRequestPromise = std::promise<void>{};
+ auto startSecondRequestFuture = startSecondRequestPromise.get_future();
+ auto secondRequestCompletedPromise = std::promise<void>{};
+ auto secondRequestCompletedFuture = secondRequestCompletedPromise.get_future();
+
+ auto secondRequestThread = std::thread{};
+
+ firstRequestTest(
+ [&](auto & /*test*/, auto &options) {
+ // Run the second request while the transaction of the first request is in
+ // progress.
+ auto resultProcessor = [&](auto &) {
+ // Start the second request.
+ startSecondRequestPromise.set_value();
+ // Wait for the second request to fail.
+ secondRequestCompletedFuture.get();
+ };
+ options.mcResultProcessor = resultProcessor;
+ options.gaResultProcessor = resultProcessor;
+
+ secondRequestThread = std::thread{[&] {
+ // Wait for the first request to enter transaction.
+ startSecondRequestFuture.get();
+ secondRequestTest([](auto &, auto &) {},
+ [](auto &innerTest) {
+ EXPECT_EQ(innerTest.called, "response_callback;");
+ });
+ // Continue the first request.
+ secondRequestCompletedPromise.set_value();
+ }};
+ },
+ [&](auto &test) {
+ EXPECT_EQ(test.called, "response_callback;linked_data_callback;");
+ secondRequestThread.join();
+ });
+ });
+}
+
+TEST(RequestHandler, two_requests_second_one_executes_while_the_first_awaits_update_messages)
+{
+ RunWithAll4x4Combinations<SuccessfulRequestTest, SuccessfulRequestTest>(
+ [&](const auto &firstRequestTest, const auto &secondRequestTest) {
+ auto startSecondRequestPromise = std::promise<void>{};
+ auto startSecondRequestFuture = startSecondRequestPromise.get_future();
+ auto secondRequestCompletedPromise = std::promise<void>{};
+ auto secondRequestCompletedFuture = secondRequestCompletedPromise.get_future();
+
+ auto secondRequestThread = std::thread{};
+
+ firstRequestTest(
+ [&](auto & /*test*/, auto &options) {
+ options.followingCtapUpdatesProcessor =
+ [&](std::function<void(UpdateMessage &&)> &&) {
+ // Start the second request.
+ startSecondRequestPromise.set_value();
+ // Wait for the second request to fail.
+ secondRequestCompletedFuture.get();
+ };
+
+ secondRequestThread = std::thread{[&] {
+ // Wait for the first request to enter transaction.
+ startSecondRequestFuture.get();
+ secondRequestTest([](auto &, auto &) {},
+ [](auto &innerTest) {
+ EXPECT_EQ(innerTest.called,
+ "response_callback;linked_data_callback;");
+ });
+ // Continue the first request.
+ secondRequestCompletedPromise.set_value();
+ }};
+ },
+ [&](auto &test) {
+ EXPECT_EQ(test.called, "response_callback;linked_data_callback;");
+ secondRequestThread.join();
+ });
+ });
+}
auto clientData = wauthn_client_data_s{};
TestState testState;
auto makeTransaction = [&] {
- return StateAssistedTransaction(
+ auto transaction = std::make_unique<StateAssistedTransaction>();
+ transaction->Initialize(
{},
std::make_unique<MHandshake>(testState),
std::make_unique<
MBtAdvertScanner<Event, CancelCalledOn, &DecryptedBluetoothAdvert>>(testState),
std::make_unique<MCtapMessageProcessor<Event, CancelCalledOn>>(testState));
+ return transaction;
};
- // Before PerformTransaction()
+ // Before Perform()
{
auto transaction = makeTransaction();
- testState.Reset(Event::RESET, transaction);
- EXPECT_THROW(transaction.PerformTransaction(clientData, options), Cancelled);
+ testState.Reset(Event::RESET, *transaction);
+ EXPECT_THROW(transaction->Perform(clientData, options), Cancelled);
EXPECT_EQ(testState.m_lastEvent, Event::RESET);
EXPECT_EQ(testState.m_cancelCalledOn, CancelCalledOn::NONE);
}
// In MHandshake::StateAssistedConnect()
{
auto transaction = makeTransaction();
- testState.Reset(Event::CONNECTING_STARTED, transaction);
- EXPECT_THROW(transaction.PerformTransaction(clientData, options), Cancelled);
+ testState.Reset(Event::CONNECTING_STARTED, *transaction);
+ EXPECT_THROW(transaction->Perform(clientData, options), Cancelled);
EXPECT_EQ(testState.m_lastEvent, Event::CONNECTING_STARTED);
EXPECT_EQ(testState.m_cancelCalledOn, CancelCalledOn::CONNECTING_OR_HANDSHAKE);
}
// After MHandshake::StateAssistedConnect()
{
auto transaction = makeTransaction();
- testState.Reset(Event::CONNECTING_ENDED, transaction);
- EXPECT_THROW(transaction.PerformTransaction(clientData, options), Cancelled);
+ testState.Reset(Event::CONNECTING_ENDED, *transaction);
+ EXPECT_THROW(transaction->Perform(clientData, options), Cancelled);
EXPECT_EQ(testState.m_lastEvent, Event::CONNECTING_ENDED);
EXPECT_EQ(testState.m_cancelCalledOn, CancelCalledOn::CONNECTING_OR_HANDSHAKE);
}
// In MBleAdvertScanner
{
auto transaction = makeTransaction();
- testState.Reset(Event::AWAITING_BLE_ADVERT_STARTED, transaction);
- EXPECT_THROW(transaction.PerformTransaction(clientData, options), Cancelled);
+ testState.Reset(Event::AWAITING_BLE_ADVERT_STARTED, *transaction);
+ EXPECT_THROW(transaction->Perform(clientData, options), Cancelled);
EXPECT_EQ(testState.m_lastEvent, Event::AWAITING_BLE_ADVERT_STARTED);
EXPECT_EQ(testState.m_cancelCalledOn, CancelCalledOn::BLE_ADVERT);
}
// After MBleAdvertScanner
{
auto transaction = makeTransaction();
- testState.Reset(Event::AWAITING_BLE_ADVERT_ENDED, transaction);
- EXPECT_THROW(transaction.PerformTransaction(clientData, options), Cancelled);
+ testState.Reset(Event::AWAITING_BLE_ADVERT_ENDED, *transaction);
+ EXPECT_THROW(transaction->Perform(clientData, options), Cancelled);
EXPECT_EQ(testState.m_lastEvent, Event::AWAITING_BLE_ADVERT_ENDED);
EXPECT_EQ(testState.m_cancelCalledOn, CancelCalledOn::BLE_ADVERT);
}
// In MHandshake::DoStateAssistedHandshake()
{
auto transaction = makeTransaction();
- testState.Reset(Event::HANDSHAKE_STARTED, transaction);
- EXPECT_THROW(transaction.PerformTransaction(clientData, options), Cancelled);
+ testState.Reset(Event::HANDSHAKE_STARTED, *transaction);
+ EXPECT_THROW(transaction->Perform(clientData, options), Cancelled);
EXPECT_EQ(testState.m_lastEvent, Event::HANDSHAKE_STARTED);
EXPECT_EQ(testState.m_cancelCalledOn, CancelCalledOn::CONNECTING_OR_HANDSHAKE);
}
// After MHandshake::DoStateAssistedHandshake()
{
auto transaction = makeTransaction();
- testState.Reset(Event::HANDSHAKE_ENDED, transaction);
- EXPECT_THROW(transaction.PerformTransaction(clientData, options), Cancelled);
+ testState.Reset(Event::HANDSHAKE_ENDED, *transaction);
+ EXPECT_THROW(transaction->Perform(clientData, options), Cancelled);
EXPECT_EQ(testState.m_lastEvent, Event::HANDSHAKE_ENDED);
EXPECT_EQ(testState.m_cancelCalledOn, CancelCalledOn::CONNECTING_OR_HANDSHAKE);
}
// In MCtapMessageProcessor
{
auto transaction = makeTransaction();
- testState.Reset(Event::MC_OR_GA_STARTED, transaction);
- EXPECT_THROW(transaction.PerformTransaction(clientData, options), Cancelled);
+ testState.Reset(Event::MC_OR_GA_STARTED, *transaction);
+ EXPECT_THROW(transaction->Perform(clientData, options), Cancelled);
EXPECT_EQ(testState.m_lastEvent, Event::MC_OR_GA_STARTED);
EXPECT_EQ(testState.m_cancelCalledOn, CancelCalledOn::CTAP_MESSAGE_PROCESSOR);
}
// After MCtapMessageProcessor
{
auto transaction = makeTransaction();
- testState.Reset(Event::MC_OR_GA_ENDED, transaction);
- EXPECT_THROW(transaction.PerformTransaction(clientData, options), Cancelled);
+ testState.Reset(Event::MC_OR_GA_ENDED, *transaction);
+ EXPECT_THROW(transaction->Perform(clientData, options), Cancelled);
EXPECT_EQ(testState.m_lastEvent, Event::MC_OR_GA_ENDED);
EXPECT_EQ(testState.m_cancelCalledOn, CancelCalledOn::CTAP_MESSAGE_PROCESSOR);
}
// No Cancel
{
auto transaction = makeTransaction();
- testState.Reset(Event::FINISHED, transaction);
- EXPECT_NO_THROW(transaction.PerformTransaction(clientData, options));
+ testState.Reset(Event::FINISHED, *transaction);
+ EXPECT_NO_THROW(transaction->Perform(clientData, options));
EXPECT_EQ(testState.m_lastEvent, Event::MC_OR_GA_ENDED);
EXPECT_EQ(testState.m_cancelCalledOn, CancelCalledOn::NONE);
}
auto performTransaction = [](const auto &options) {
auto clientData = wauthn_client_data_s{};
TestState testState;
- auto transaction = StateAssistedTransaction(
+ auto transaction = StateAssistedTransaction();
+ transaction.Initialize(
{},
std::make_unique<MHandshake>(testState),
std::make_unique<
MBtAdvertScanner<Event, CancelCalledOn, &DecryptedBluetoothAdvert>>(testState),
std::make_unique<MCtapMessageProcessor<Event, CancelCalledOn>>(testState));
- EXPECT_THROW(transaction.PerformTransaction(clientData, options),
- Exception::InvalidParam);
+ EXPECT_THROW(transaction.Perform(clientData, options), Exception::InvalidParam);
};
// MakeCredential
{
} // namespace
-TEST(StateAssistedTransaction, cancel_from_the_other_thread)
+TEST(StateAssistedTransaction, CancelFromTheOtherThread)
{
auto makeTransaction = [&] {
- return StateAssistedTransaction(
+ auto transaction = std::make_unique<StateAssistedTransaction>();
+ transaction->Initialize(
{},
std::make_unique<CancellationMHandshake>(),
std::make_unique<CancellationMBtAdvertScanner<&DecryptedBluetoothAdvert>>(),
std::make_unique<CancellationMCtapMessageProcessor>());
+ return transaction;
};
TestCancelFromTheOtherThread<ITransaction>(
400, 40, makeTransaction, [](ITransaction &transaction) {
auto mcOptions = wauthn_pubkey_cred_creation_options_s{};
mcOptions.linked_device = &linkedDevice.linkedDevice;
- transaction.PerformTransaction(clientData, mcOptions);
+ transaction.Perform(clientData, mcOptions);
auto gaOptions = wauthn_pubkey_cred_request_options_s{};
gaOptions.linked_device = &linkedDevice.linkedDevice;
- transaction.PerformTransaction(clientData, gaOptions);
+ transaction.Perform(clientData, gaOptions);
+ });
+}
+
+TEST(StateAssistedTransaction, ProcessFollowingUpdateMsgs)
+{
+ TestTransactionProcessFollowingUpdateMsgs(
+ [](std::unique_ptr<ICtapMessageProcessor> &&ctapMessageProcessor) {
+ auto transaction = std::make_unique<StateAssistedTransaction>();
+ transaction->Initialize({},
+ std::make_unique<ShouldNotBeCalledMHandshake>(),
+ std::make_unique<ShouldNotBeCalledMBtAdvertScanner>(),
+ std::move(ctapMessageProcessor));
+ return transaction;
});
}
template <class Func, class CallbackOnCancel>
void WithCancelCheck(Func &&func, CallbackOnCancel &&callbackOnCancel)
{
+ if (m_ignorePendingCancellation &&
+ m_cancellationFuture.wait_for(std::chrono::nanoseconds{0}) !=
+ std::future_status::timeout) {
+ // Cancellation already happened, so we ignore it as requested.
+ std::forward<Func>(func)();
+ return;
+ }
// If Cancel happens before operation.
if (m_cancellationFuture.wait_for(std::chrono::nanoseconds{get_random(0, 100'000)}) !=
std::future_status::timeout) {
WithCancelCheck([] {});
}
+ void IgnorePendingCancellation() { m_ignorePendingCancellation = true; }
+
// May be called only once. May be called from the other thread.
void Cancel() { m_cancellationPromise.set_value(); }
private:
+ bool m_ignorePendingCancellation = false;
std::promise<void> m_cancellationPromise;
std::future<void> m_cancellationFuture = m_cancellationPromise.get_future();
};
+namespace detail {
+
+template <class>
+constexpr inline bool is_unique_ptr = false;
+
+template <class T>
+constexpr inline bool is_unique_ptr<std::unique_ptr<T>> = true;
+
+} // namespace detail
+
+/// @p maker may return derived class of @p Interface or unique_ptr of derived class of @p Interface
template <class Interface, class Func, class TransactionInvoker>
void TestCancelFromTheOtherThread(size_t reps,
size_t minExpectedCancellationsNum,
Func maker,
- TransactionInvoker transactionInvoker)
+ TransactionInvoker transactionInvoker,
+ bool testCancellingBeforeStartingTransaction = true)
{
+ static constexpr auto getInterfacePtr = [](auto &transaction) {
+ if constexpr (detail::is_unique_ptr<std::remove_reference_t<decltype(transaction)>>)
+ return transaction.get();
+ else
+ return &transaction;
+ };
auto tp = std::chrono::steady_clock::now();
constexpr size_t REPS = 32;
for (size_t i = 0; i < REPS; ++i) {
auto transaction = maker();
- transactionInvoker(static_cast<Interface &>(transaction));
+ transactionInvoker(static_cast<Interface &>(*getInterfacePtr(transaction)));
}
const std::chrono::nanoseconds avgTimeOfFullTransaction =
(std::chrono::steady_clock::now() - tp) / REPS;
- // Test cancelling before starting the transaction.
- {
+ if (testCancellingBeforeStartingTransaction) {
auto transactionObj = maker();
- Interface *transaction = &transactionObj;
+ Interface *transaction = getInterfacePtr(transactionObj);
transaction->Cancel();
EXPECT_THROW(transactionInvoker(*transaction), Exception::Cancelled);
}
auto lock = std::unique_lock{mutex};
for (size_t i = 0; i < reps; ++i) {
auto transactionObj = maker();
- transaction = &transactionObj;
+ transaction = getInterfacePtr(transactionObj);
// Wake up canceller and wait for it to become ready to cancel.
state = State::NEW_TRANSACTION_READY;
cv.notify_all();
#include "bluetooth_advert.h"
#include "common.h"
-#include "crypto/random.h"
#include "ctap_message_processor.h"
+#include "handshake.h"
#include "test_cancel_from_the_other_thread.h"
#include "transaction_tester.h"
return {};
}
+ void ProcessFollowingUpdateMsgs(std::function<void(UpdateMessage &&)>) override
+ {
+ ADD_FAILURE() << "This should not be called";
+ throw std::runtime_error{""};
+ }
+
void Cancel() override
{
SCOPED_TRACE("");
return {};
}
+ void ProcessFollowingUpdateMsgs(std::function<void(UpdateMessage &&)>) override
+ {
+ ADD_FAILURE() << "This should not be called";
+ throw std::runtime_error{""};
+ }
+
void Cancel() override { m_cancelFacilitator.Cancel(); }
private:
CancelFacilitator m_cancelFacilitator;
};
+
+class ShouldNotBeCalledMBtAdvertScanner : public BtAdvertScanner {
+public:
+ void AwaitAdvert(const CryptoBuffer &, CryptoBuffer &) override
+ {
+ ADD_FAILURE() << "This should not be called";
+ throw std::runtime_error{""};
+ }
+
+ void Cancel() override
+ {
+ ADD_FAILURE() << "This should not be called";
+ throw std::runtime_error{""};
+ }
+};
+
+class ShouldNotBeCalledMHandshake : public IHandshake {
+public:
+ virtual void
+ QrInitiatedConnect(const std::string &, const BufferView &, const BufferView &) override
+ {
+ ADD_FAILURE() << "This should not be called";
+ throw std::runtime_error{""};
+ }
+
+ HandshakeResult DoQrInitiatedHandshake(const CryptoBuffer &,
+ const Crypto::X9_62_P_256_Key &) override
+ {
+ ADD_FAILURE() << "This should not be called";
+ throw std::runtime_error{""};
+ }
+
+ virtual void
+ StateAssistedConnect(const std::string &, const BufferView &, const BufferView &) override
+ {
+ ADD_FAILURE() << "This should not be called";
+ throw std::runtime_error{""};
+ }
+
+ HandshakeResult DoStateAssistedHandshake(const CryptoBuffer &, const CryptoBuffer &) override
+ {
+ ADD_FAILURE() << "This should not be called";
+ throw std::runtime_error{""};
+ }
+
+ void Cancel() override
+ {
+ ADD_FAILURE() << "This should not be called";
+ throw std::runtime_error{""};
+ }
+};
+
+class UpdateMsgsProcessingMCtapMessageProcessor : public ICtapMessageProcessor {
+public:
+ void SetEncryptedTunnel(std::unique_ptr<IEncryptedTunnel>) override
+ {
+ ADD_FAILURE() << "This should not be called";
+ throw std::runtime_error{""};
+ }
+
+ MCResult MakeCredential(const wauthn_client_data_s &,
+ const wauthn_pubkey_cred_creation_options_s &) override
+ {
+ ADD_FAILURE() << "This should not be called";
+ throw std::runtime_error{""};
+ }
+
+ GAResult GetAssertion(const wauthn_client_data_s &,
+ const wauthn_pubkey_cred_request_options_s &) override
+ {
+ ADD_FAILURE() << "This should not be called";
+ throw std::runtime_error{""};
+ }
+
+ void
+ ProcessFollowingUpdateMsgs(std::function<void(UpdateMessage &&)> updateMsgCallback) override
+ {
+ ++m_processFollowingUpdateMsgsCalledNum;
+ // Check the right callback is called
+ for (int i = 0; i < m_updateMsgCallbackReps; ++i) {
+ updateMsgCallback(UpdateMessage{});
+ }
+ }
+
+ void Cancel() override
+ {
+ ADD_FAILURE() << "This should not be called";
+ throw std::runtime_error{""};
+ }
+
+ int m_updateMsgCallbackReps = 0;
+ int m_processFollowingUpdateMsgsCalledNum = 0;
+};
+
+template <class TransactionMaker>
+void TestTransactionProcessFollowingUpdateMsgs(TransactionMaker &&transactionMaker)
+{
+ auto ctapMessageProcessorUPtr = std::make_unique<UpdateMsgsProcessingMCtapMessageProcessor>();
+ auto ctapMessageProcessor = ctapMessageProcessorUPtr.get();
+
+ auto transaction =
+ std::forward<TransactionMaker>(transactionMaker)(std::move(ctapMessageProcessorUPtr));
+
+ const auto updateCallbackReps = get_random(0, 17);
+ ctapMessageProcessor->m_updateMsgCallbackReps = updateCallbackReps;
+ int reps = 0;
+ transaction->ProcessFollowingUpdateMsgs([&](UpdateMessage &&) { ++reps; });
+ EXPECT_EQ(reps, updateCallbackReps);
+ EXPECT_EQ(ctapMessageProcessor->m_processFollowingUpdateMsgsCalledNum, 1);
+}
#pragma once
+#include "transaction.h"
+
#include <cassert>
#include <gtest/gtest.h>
#include <stdexcept>
#include "../get_random.h"
#include "../test_cancel_from_the_other_thread.h"
#include "auto_tests.h"
+#include "constants.h"
#include "exception.h"
#include "tunnel.h"
/*
* Tests below are executed using fully mocked websockets implementation only (MockedSockets)
*/
-TEST(TunnelMockedTests, Cancelling)
+TEST(TunnelMockedTests, Cancel)
{
const std::string test = "sdfjisdjkfhdfjkghsdfkghdgkjhd";
std::vector<uint8_t> in, out;
LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER,
LWS_CALLBACK_CLIENT_CLOSED,
LWS_CALLBACK_VHOST_CERT_AGING,
- LWS_CALLBACK_PROTOCOL_INIT
+ LWS_CALLBACK_PROTOCOL_INIT,
+ LWS_CALLBACK_WSI_DESTROY, // LWS_CALLBACK_PROTOCOL_DESTROY has the same logic
+ LWS_CALLBACK_TIMER,
};
struct EventInfo {
EXPECT_LT(successes, injectionsNum);
}
-TEST(TunnelMockedTests, CancellingFromOtherThread)
+TEST(TunnelMockedTests, CancelFromTheOtherThread)
{
class Transaction {
public:
Transaction() : m_tunnel(std::make_shared<DelayedSockets>()) {}
- void PerformTransaction()
+ void Perform()
{
std::vector<uint8_t> in, out(42);
m_tunnel.Connect(TestUrl());
auto makeTransaction = [&] { return Transaction(); };
TestCancelFromTheOtherThread<Transaction>(
- 400, 40, makeTransaction, [](Transaction &transaction) {
- transaction.PerformTransaction();
- });
+ 400, 40, makeTransaction, [](Transaction &transaction) { transaction.Perform(); });
}
bool IsFinalFragment(Lws *) const noexcept override { return true; }
int WriteBinary(Lws *lws, unsigned char *buf, size_t len) noexcept override;
+
+ void SetTimer(Lws *, lws_usec_t) noexcept override {}
+
void ContextDestroy(LwsContext *lwsContext) noexcept override;
void SetListener(IWebsocketsListener *listener) noexcept override { m_listener = listener; }
#include "manual_tests.h"
#include <cassert>
+#include <chrono>
namespace {
EXPECT_THROW(tunnel.ReadBinary(), InvalidState);
}
-TEST(TunnelDummyServerTests, CancellingFromOtherThread)
+TEST(TunnelDummyServerTests, CloseConnectionAfter)
+{
+ static constexpr auto REPS = 5;
+ for (int rep = 0; rep < REPS; ++rep) {
+ UnsafeTunnel tunnel;
+ EXPECT_NO_THROW(tunnel.Connect(TestUrl()));
+
+ const auto usecs = get_random(0, 1'000'000);
+ tunnel.CloseConnectionAfter(usecs);
+
+ auto tp = std::chrono::steady_clock::now();
+ EXPECT_THROW(tunnel.ReadBinary(), ExceptionTunnelClosed);
+ auto duration = std::chrono::steady_clock::now() - tp;
+
+ EXPECT_GE(duration, std::chrono::microseconds{usecs});
+ EXPECT_LT(duration, std::chrono::seconds{10});
+ }
+}
+
+TEST(TunnelDummyServerTests, CancelFromTheOtherThread)
{
class Transaction {
public:
- void PerformTransaction()
+ void Perform()
{
std::vector<uint8_t> in, out(42);
m_tunnel.Connect(TestUrl());
auto makeTransaction = [&] { return Transaction(); };
TestCancelFromTheOtherThread<Transaction>(
- 400, 40, makeTransaction, [](Transaction &transaction) {
- transaction.PerformTransaction();
- });
+ 400, 40, makeTransaction, [](Transaction &transaction) { transaction.Perform(); });
}
int main(int argc, char *argv[])
--- /dev/null
+/*
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd. All rights reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+#include <common.h>
+#include <webauthn-types.h>
+
+constexpr bool operator==(const wauthn_const_buffer_s &wcb, const BufferView &bv) noexcept
+{
+ return BufferView{wcb.data, wcb.size} == bv;
+}
+
+constexpr bool operator==(const wauthn_const_buffer_s *wcb, const BufferView &bv) noexcept
+{
+ return wcb != nullptr && *wcb == bv;
+}