${CMAKE_CURRENT_SOURCE_DIR}/qr_code_shower.cpp
${CMAKE_CURRENT_SOURCE_DIR}/tunnel.cpp
${CMAKE_CURRENT_SOURCE_DIR}/websockets.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/handshake.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/encrypted_tunnel.cpp
)
SET(WEBAUTHN_BLE_SOURCES ${WEBAUTHN_BLE_SOURCES} PARENT_SCOPE)
* limitations under the License
*/
#include "bluetooth_advert.h"
+#include "common.h"
#include "crypto/encryptor.h"
#include "crypto/hmac.h"
#include "log/log.h"
int serviceDataLen,
void *userData) noexcept {
constexpr auto FIDO_CABLE_UUID16 = std::string_view{"FFF9"};
- constexpr size_t ADVERT_LEN = 16;
+ constexpr size_t ADVERT_LEN = BLUETOOTH_ADVERT_LEN;
constexpr size_t ENCRYPTED_ADVERT_TAG_LEN = 4;
constexpr size_t EXPECTED_SERVICE_DATA_LEN = ADVERT_LEN + ENCRYPTED_ADVERT_TAG_LEN;
if (serviceUUID == FIDO_CABLE_UUID16 && serviceDataLen == EXPECTED_SERVICE_DATA_LEN) {
UnpackedBleAdvert UnpackDecryptedAdvert(const CryptoBuffer &decryptedAdvert)
{
- assert(decryptedAdvert.size() == 16);
- if (decryptedAdvert[0] != 0) {
- throw std::runtime_error{"First byte of decrypted advert is 0 but it shouldn't be"};
- }
+ assert(decryptedAdvert.size() == BLUETOOTH_ADVERT_LEN);
+ if (decryptedAdvert[0] != 0)
+ throw std::runtime_error{"First byte of decrypted advert should have value 0"};
+
UnpackedBleAdvert res;
std::memcpy(res.nonce.data(), decryptedAdvert.data() + 1, res.nonce.size());
std::memcpy(res.routingId.data(), decryptedAdvert.data() + 11, res.routingId.size());
#include <array>
#include <utility> // for std::move
+constexpr size_t QR_SECRET_LEN = 16;
+constexpr size_t BLUETOOTH_ADVERT_LEN = 16;
+
typedef std::array<char, 2> Hint;
template <typename T>
LogError("Invalid key length");
throw OpensslError{};
}
+ constexpr int TAG_LEN = 16;
+ if (ciphertext.size() < TAG_LEN) {
+ LogError("ciphertext length shorter than tag length");
+ throw OpensslError{};
+ }
EVP_CIPHER_CTX *ctx = nullptr;
- constexpr int TAG_LEN = 16;
int len = 0;
size_t outputLen = 0;
CryptoBuffer output(ciphertext.size() - TAG_LEN);
namespace {
-constexpr size_t KEY_AND_HASH_LEN = 32;
-
CryptoBuffer NonceToInitialVector(uint64_t nonce) noexcept
{
CryptoBuffer res(12);
namespace Crypto::Noise {
+constexpr size_t KEY_AND_HASH_LEN = 32;
+
class SymmetricState {
public:
enum HandshakeKind {
#include "crypto/common.h"
+constexpr size_t EID_KEY_LEN = 64;
+constexpr size_t TUNNEL_ID_LEN = 16;
+constexpr size_t PSK_LEN = 32;
+
enum class KeyPurpose : uint8_t {
EIDKey = 1,
TunnelID = 2,
--- /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 "encrypted_tunnel.h"
+#include "exception.h"
+
+#include <utility>
+
+EncryptedTunnel::EncryptedTunnel(std::unique_ptr<ITunnel> tunnel,
+ CipherState reader,
+ CipherState writer)
+: m_tunnel{std::move(tunnel)}, m_reader{std::move(reader)}, m_writer{std::move(writer)}
+{
+}
+
+CryptoBuffer EncryptedTunnel::ReadBinary()
+{
+ CryptoBuffer ciphertext = m_tunnel->ReadBinary();
+ auto msg = m_reader.DecryptWithAd(ciphertext, {});
+ if (msg.empty())
+ THROW_UNKNOWN("invalid message");
+
+ size_t paddingBytes = msg.back();
+ if (paddingBytes + 1 > msg.size())
+ THROW_UNKNOWN("invalid message");
+
+ msg.resize(msg.size() - paddingBytes - 1);
+ return msg;
+}
+
+void EncryptedTunnel::WriteBinary(CryptoBuffer msg)
+{
+ if (msg.size() > (1 << 20))
+ THROW_UNKNOWN("message too large");
+
+ constexpr size_t paddingGranularity = 32;
+ size_t extraBytes = paddingGranularity - msg.size() % paddingGranularity;
+ msg.resize(msg.size() + extraBytes, 0);
+ msg.back() = static_cast<uint8_t>(extraBytes) - 1;
+
+ auto ciphertext = m_writer.EncryptWithAd(msg, {});
+ m_tunnel->WriteBinary(ciphertext);
+}
+
+void EncryptedTunnel::Cancel() { m_tunnel->Cancel(); }
--- /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 "crypto/common.h"
+#include "crypto/noise/noise.h"
+#include "tunnel.h"
+
+#include <memory>
+
+class IEncryptedTunnel {
+public:
+ virtual CryptoBuffer ReadBinary() = 0;
+
+ virtual void WriteBinary(CryptoBuffer msg) = 0;
+
+ // May be called from the other thread.
+ virtual void Cancel() = 0;
+};
+
+class EncryptedTunnel : public IEncryptedTunnel {
+public:
+ using CipherState = Crypto::Noise::SymmetricState::CipherState;
+
+ explicit EncryptedTunnel(std::unique_ptr<ITunnel> tunnel,
+ CipherState reader,
+ CipherState writer);
+
+ CryptoBuffer ReadBinary() override;
+
+ void WriteBinary(CryptoBuffer msg) override;
+
+ void Cancel() override;
+
+private:
+ std::unique_ptr<ITunnel> m_tunnel;
+ CipherState m_reader;
+ CipherState m_writer;
+};
--- /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 "bluetooth_advert.h"
+#include "crypto/ecdh.h"
+#include "crypto/noise/noise.h"
+#include "derive_key.h"
+#include "exception.h"
+#include "handshake.h"
+#include "log/log.h"
+#include "lowercase_hex_string_of.h"
+#include "tunnel_server_domain.h"
+
+#include <utility>
+
+namespace {
+
+struct InitialHandshakeMessage {
+ CryptoBuffer msg;
+ Crypto::X9_62_P_256_Key ephermalKey;
+ Crypto::Noise::SymmetricState noise;
+};
+
+InitialHandshakeMessage initialHandshakeMessage(const CryptoBuffer &psk,
+ const Crypto::X9_62_P_256_Key &privKey)
+{
+ InitialHandshakeMessage res{
+ CryptoBuffer{},
+ Crypto::X9_62_P_256_Key::Create(),
+ Crypto::Noise::SymmetricState{
+ Crypto::Noise::SymmetricState::HandshakeKind::KNpsk0_P256_AESGCM_SHA256},
+ };
+ res.noise.MixHash(CryptoBuffer{1});
+ res.noise.MixHash(privKey.ExportPublicKey(false));
+
+ res.noise.MixKeyAndHash(psk);
+
+ auto exportedEphermalKeyPublic = res.ephermalKey.ExportPublicKey(false);
+ res.noise.MixHash(exportedEphermalKeyPublic);
+ res.noise.MixKey(exportedEphermalKeyPublic);
+
+ res.msg = std::move(exportedEphermalKeyPublic);
+ auto ciphertext = res.noise.EncryptAndHash({});
+ res.msg.insert(res.msg.end(), ciphertext.begin(), ciphertext.end());
+ return res;
+}
+
+struct ProcessedHandshakeResponse {
+ Crypto::Noise::SymmetricState::SplitRes splitRes;
+ CryptoBuffer handshakeHash;
+};
+
+ProcessedHandshakeResponse processHandshakeResponse(const CryptoBuffer &peerHandshakeMessage,
+ const Crypto::X9_62_P_256_Key &ephermalKey,
+ const Crypto::X9_62_P_256_Key &privKey,
+ Crypto::Noise::SymmetricState &&noise)
+{
+ constexpr size_t p256X962Length = 1 + 32 + 32;
+ if (peerHandshakeMessage.size() < p256X962Length) {
+ LogError("invalid handshake response length: " << peerHandshakeMessage.size()
+ << " expected: " << p256X962Length);
+ throw Unknown{};
+ }
+
+ auto peerPointBytes =
+ CryptoBuffer(peerHandshakeMessage.begin(), peerHandshakeMessage.begin() + p256X962Length);
+ auto ciphertext =
+ CryptoBuffer(peerHandshakeMessage.begin() + p256X962Length, peerHandshakeMessage.end());
+
+ noise.MixHash(peerPointBytes);
+ noise.MixKey(peerPointBytes);
+
+ auto peerPublicKey = Crypto::X9_62_P_256_Key::ImportPublicKey(peerPointBytes);
+ noise.MixKey(Crypto::deriveECDHSharedSecret(ephermalKey, peerPublicKey));
+ noise.MixKey(Crypto::deriveECDHSharedSecret(privKey, peerPublicKey));
+
+ auto plaintext = noise.DecryptAndHash(ciphertext);
+ if (!plaintext.empty()) {
+ LogError("non-empty decrypted handshake response");
+ throw Unknown{};
+ }
+
+ return ProcessedHandshakeResponse{
+ noise.Split(),
+ noise.GetHandshakeHash(),
+ };
+}
+
+} // namespace
+
+Handshake::Handshake(std::unique_ptr<ITunnel> tunnel) : m_tunnel{std::move(tunnel)} {}
+
+std::unique_ptr<IEncryptedTunnel>
+Handshake::ConnectAndDoQrHandshake(const CryptoBuffer &qrSecret,
+ const CryptoBuffer &decryptedBleAdvert,
+ const Crypto::X9_62_P_256_Key &identityKey)
+{
+ auto unpackedAdvert = UnpackDecryptedAdvert(decryptedBleAdvert);
+ LogDebug("unpacked BLE advert: nonce = "
+ << LowercaseHexStringOf(unpackedAdvert.nonce)
+ << ", routingId = " << LowercaseHexStringOf(unpackedAdvert.routingId)
+ << ", encodedTunnelServerDomain = " << unpackedAdvert.encodedTunnelServerDomain);
+
+ auto tunnelServerDomain = DecodeTunnelServerDomain(unpackedAdvert.encodedTunnelServerDomain);
+ LogDebug("decoded tunnel server domain: " << tunnelServerDomain);
+
+ auto tunnelId = DeriveKey(qrSecret, {}, KeyPurpose::TunnelID, TUNNEL_ID_LEN);
+
+ std::string url = "wss://";
+ url += tunnelServerDomain;
+ url += "/cable/connect/";
+ url += LowercaseHexStringOf(unpackedAdvert.routingId);
+ url += '/';
+ url += LowercaseHexStringOf(tunnelId);
+
+ m_tunnel->Connect(url); // Should throw if Cancel is already called.
+
+ // Handshake
+ auto psk = DeriveKey(qrSecret, decryptedBleAdvert, KeyPurpose::PSK, PSK_LEN);
+ auto [msg, ephermalKey, noise] = initialHandshakeMessage(psk, identityKey);
+
+ m_tunnel->WriteBinary(msg); // Should throw if Cancel is already called.
+
+ CryptoBuffer response = m_tunnel->ReadBinary(); // Should throw if Cancel is already called.
+
+ // process response
+ auto [splitRes, handshakeHash] =
+ processHandshakeResponse(response, ephermalKey, identityKey, std::move(noise));
+
+ auto guard = std::unique_lock{m_lock};
+ if (m_cancelled)
+ throw Cancelled{};
+
+ return std::make_unique<EncryptedTunnel>(
+ std::move(m_tunnel), std::move(splitRes.second), std::move(splitRes.first));
+}
+
+void Handshake::Cancel()
+{
+ auto guard = std::lock_guard{m_lock};
+ m_cancelled = true;
+ if (m_tunnel)
+ m_tunnel->Cancel();
+}
--- /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 "crypto/ec_key.h"
+#include "encrypted_tunnel.h"
+#include "tunnel.h"
+
+#include <mutex>
+
+class IHandshake {
+public:
+ IHandshake() = default;
+ IHandshake(const IHandshake &) = delete;
+ IHandshake(IHandshake &&) = delete;
+ IHandshake &operator=(const IHandshake &) = delete;
+ IHandshake &operator=(IHandshake &&) = delete;
+ virtual ~IHandshake() = default;
+
+ // May be called only once. Throws Cancelled on cancel.
+ virtual std::unique_ptr<IEncryptedTunnel>
+ ConnectAndDoQrHandshake(const CryptoBuffer &qrSecret,
+ const CryptoBuffer &decryptedBleAdvert,
+ const Crypto::X9_62_P_256_Key &identityKey) = 0;
+
+ // May be called from the other threads.
+ virtual void Cancel() = 0;
+};
+
+class Handshake : public IHandshake {
+public:
+ explicit Handshake(std::unique_ptr<ITunnel> tunnel = std::make_unique<Tunnel>());
+
+ std::unique_ptr<IEncryptedTunnel>
+ ConnectAndDoQrHandshake(const CryptoBuffer &qrSecret,
+ const CryptoBuffer &decryptedBleAdvert,
+ const Crypto::X9_62_P_256_Key &identityKey) override;
+
+ void Cancel() override;
+
+private:
+ std::unique_ptr<ITunnel> m_tunnel;
+
+ bool m_cancelled = false;
+ std::mutex m_lock;
+};
#include "log/log.h"
#include "lowercase_hex_string_of.h"
#include "qr_transaction.h"
-#include "tunnel_server_domain.h"
#include <utility>
void *displayQrCodeCallbackUserData,
Hint hint,
std::unique_ptr<IQrCodeShower> qrCodeShower,
- IBtAdvertScannerUPtr btAdvertScanner)
+ IBtAdvertScannerUPtr btAdvertScanner,
+ std::unique_ptr<IHandshake> handshake)
: m_displayQrCodeCallback{displayQrCodeCallback},
m_displayQrCodeCallbackUserData{displayQrCodeCallbackUserData},
m_hint{std::move(hint)},
m_qrCodeShower{std::move(qrCodeShower)},
- m_btAdvertScanner{std::move(btAdvertScanner)}
+ m_btAdvertScanner{std::move(btAdvertScanner)},
+ m_handshake{std::move(handshake)}
{
}
updateStateAndCheckForCancel(State::SHOWING_QR_CODE);
- CryptoBuffer qrSecret = Crypto::RandomBytes(16);
+ 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),
updateStateAndCheckForCancel(State::AWAITING_BLE_ADVERT);
CryptoBuffer decryptedAdvert;
- auto eidKey = DeriveKey(qrSecret, {}, KeyPurpose::EIDKey, 64);
+ auto eidKey = DeriveKey(qrSecret, {}, KeyPurpose::EIDKey, EID_KEY_LEN);
int err = m_btAdvertScanner->AwaitAdvert(eidKey, decryptedAdvert); // may throw
if (err == BT_ERROR_CANCELLED)
throw Cancelled{};
throw Unknown{};
}
- auto unpackedAdvert = UnpackDecryptedAdvert(decryptedAdvert);
- LogDebug("unpacked BLE advert: nonce = "
- << LowercaseHexStringOf(unpackedAdvert.nonce)
- << ", routingId = " << LowercaseHexStringOf(unpackedAdvert.routingId)
- << ", encodedTunnelServerDomain = " << unpackedAdvert.encodedTunnelServerDomain);
+ updateStateAndCheckForCancel(State::DOING_HANDSHAKE);
- auto tunnelServerDomain = DecodeTunnelServerDomain(unpackedAdvert.encodedTunnelServerDomain);
- LogDebug("decoded tunnel server domain: " << tunnelServerDomain);
+ m_encryptedTunnel =
+ m_handshake->ConnectAndDoQrHandshake(qrSecret, decryptedAdvert, identityKey);
+
+ updateStateAndCheckForCancel(State::READING_FROM_ENCRYPTED_TUNNEL);
+
+ {
+ auto msg = m_encryptedTunnel->ReadBinary();
+ LogDebug("Decrypted GetInfo: " << LowercaseHexStringOf(msg));
+ }
updateStateAndCheckForCancel(State::NOT_IN_PROGRESS);
}
if (err != BT_ERROR_NONE)
throw Unknown{};
} break;
+ case State::DOING_HANDSHAKE: {
+ m_handshake->Cancel();
+ } break;
+ case State::READING_FROM_ENCRYPTED_TUNNEL: {
+ m_encryptedTunnel->Cancel();
+ } break;
}
m_state = State::CANCELLED;
}
#pragma once
#include "bluetooth_advert.h"
+#include "handshake.h"
#include "qr_code_shower.h"
#include "transaction.h"
void *displayQrCodeCallbackUserData,
Hint hint,
std::unique_ptr<IQrCodeShower> qrCodeShower = std::make_unique<QrCodeShower>(),
- IBtAdvertScannerUPtr btAdvertScanner = std::make_unique<BtAdvertScanner>());
+ IBtAdvertScannerUPtr btAdvertScanner = std::make_unique<BtAdvertScanner>(),
+ std::unique_ptr<IHandshake> handshake = std::make_unique<Handshake>());
void PerformTransaction() override;
Hint m_hint;
std::unique_ptr<IQrCodeShower> m_qrCodeShower;
IBtAdvertScannerUPtr m_btAdvertScanner;
+ std::unique_ptr<IHandshake> m_handshake;
+ std::unique_ptr<IEncryptedTunnel> m_encryptedTunnel;
enum class State {
CANCELLED,
NOT_IN_PROGRESS,
SHOWING_QR_CODE,
AWAITING_BLE_ADVERT,
+ DOING_HANDSHAKE,
+ READING_FROM_ENCRYPTED_TUNNEL,
};
std::mutex m_lock;
#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"
Request request,
// If null, default implementations will be set in the function body to control excepitions.
std::unique_ptr<IQrCodeShower> qrCodeShower = nullptr,
- IBtAdvertScannerUPtr btAdvertScanner = nullptr)
+ IBtAdvertScannerUPtr btAdvertScanner = nullptr,
+ std::unique_ptr<IHandshake> handshake = nullptr)
{
wauthn_error_e result;
try {
qrCodeShower = std::make_unique<QrCodeShower>();
if (!btAdvertScanner)
btAdvertScanner = std::make_unique<BtAdvertScanner>();
+ if (!handshake)
+ handshake = std::make_unique<Handshake>();
{
std::lock_guard<std::mutex> lock(m_mutex);
request.callbacks->user_data,
ToHint(request),
std::move(qrCodeShower),
- std::move(btAdvertScanner));
+ std::move(btAdvertScanner),
+ std::move(handshake));
}
}
${CMAKE_CURRENT_SOURCE_DIR}/lowercase_hex_string_of_tests.cpp
${CMAKE_CURRENT_SOURCE_DIR}/qr_transaction_tests.cpp
${CMAKE_CURRENT_SOURCE_DIR}/state_assisted_transaction_tests.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/handshake_tests.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/encrypted_tunnel_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
int Cancel() override { throw std::runtime_error{"should not be called"}; }
};
+class MHandshake : public IHandshake {
+public:
+ std::unique_ptr<IEncryptedTunnel>
+ ConnectAndDoQrHandshake(const CryptoBuffer & /*qrSecret*/,
+ const CryptoBuffer & /*decryptedBleAdvert*/,
+ const Crypto::X9_62_P_256_Key & /*identityKey*/) 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<MBtAdvertScanner>(),
+ std::make_unique<MHandshake>());
}
void mocked_wah_get_assertion(const wauthn_client_data_s *client_data,
RequestHandler::Instance().Process(client_data,
RequestGA{options, callbacks},
std::make_unique<QrCodeShower>(),
- std::make_unique<MBtAdvertScanner>());
+ std::make_unique<MBtAdvertScanner>(),
+ std::make_unique<MHandshake>());
}
void InvokeMc(const wauthn_client_data_s *clientData,
#include "bluetooth_advert.h"
#include "bt_error_to_string.h"
+#include "common.h"
#include "crypto/encryptor.h"
#include "crypto/hmac.h"
#include "crypto/random.h"
#include "derive_key.h"
#include "get_random.h"
+#include "lowercase_hex_string_of.h"
#include "test_cancel_from_the_other_thread.h"
#include "turn_bluetooth.h"
using std::cerr;
using std::endl;
-namespace {
-
-std::string BinToHex(const char *data, int len)
-{
- std::string res;
- for (int i = 0; i < len; i++) {
- char byte[3];
- sprintf(byte, "%02x", data[i]);
- res.append(byte);
- }
- return res;
-}
-
-} // anonymous namespace
-
TEST(BluetoothTest, safely_defering_callback_to_be_run_after_g_main_loop_run)
{
// This test ensures this logic works in the Bluetooth:StopLEScan() preventing a deadlock
const char *serviceData,
int serviceDataLen,
void * /*userData*/) noexcept {
- cerr << "Scanned advert: UUID = " << serviceUUID
- << " data = " << BinToHex(serviceData, serviceDataLen) << endl;
+ cerr << "Scanned advert: UUID = " << serviceUUID << " data = "
+ << LowercaseHexStringOf(CryptoBuffer{serviceData, serviceData + serviceDataLen})
+ << endl;
return IBluetooth::Scanning::CONTINUE;
};
EXPECT_EQ(err = bt.StartLEAdvertScanAndAwaitStop(callback, nullptr), BT_ERROR_CANCELLED)
namespace {
-constexpr size_t EID_KEY_LEN = 64;
-constexpr size_t ADVERT_PLAINTEXT_LEN = 16;
-
CryptoBuffer RandomAdvertPlaintext()
{
- auto res = Crypto::RandomBytes(ADVERT_PLAINTEXT_LEN);
+ auto res = Crypto::RandomBytes(BLUETOOTH_ADVERT_LEN);
res[0] = 0;
return res;
}
CryptoBuffer RandomEidKey()
{
- return DeriveKey(Crypto::RandomBytes(16), {}, KeyPurpose::EIDKey, EID_KEY_LEN);
+ return DeriveKey(Crypto::RandomBytes(QR_SECRET_LEN), {}, KeyPurpose::EIDKey, EID_KEY_LEN);
}
ServiceData GenerateServiceData(const CryptoBuffer &eidKey, const CryptoBuffer &advertPlaintext)
{
assert(eidKey.size() == EID_KEY_LEN);
- assert(advertPlaintext.size() == ADVERT_PLAINTEXT_LEN);
+ assert(advertPlaintext.size() == BLUETOOTH_ADVERT_LEN);
auto aesKey = CryptoBuffer(eidKey.begin(), eidKey.begin() + 32);
auto encryptedAdvert = Crypto::EncryptAes256ECB(aesKey, advertPlaintext);
auto hmac = Crypto::HmacSha256(hmacKey, encryptedAdvert);
auto res = ServiceData{};
- assert(encryptedAdvert.size() == 16);
- res.raw.resize(20);
- std::memcpy(res.raw.data(), encryptedAdvert.data(), 16);
- std::memcpy(res.raw.data() + 16, hmac.data(), 4);
+ assert(encryptedAdvert.size() == BLUETOOTH_ADVERT_LEN);
+ res.raw.resize(BLUETOOTH_ADVERT_LEN + 4);
+ std::memcpy(res.raw.data(), encryptedAdvert.data(), BLUETOOTH_ADVERT_LEN);
+ std::memcpy(res.raw.data() + BLUETOOTH_ADVERT_LEN, hmac.data(), 4);
return res;
}
1, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'x', 'y', 'z', 0xb4, 0x3f};
EXPECT_THAT([&] { UnpackDecryptedAdvert(decryptedAdvert); },
testing::ThrowsMessage<std::runtime_error>(
- "First byte of decrypted advert is 0 but it shouldn't be"));
+ "First byte of decrypted advert should have value 0"));
}
output[0] ^= 1;
EXPECT_THROW(Crypto::DecryptAes256GCM(key, iv, aad, output), Crypto::OpensslError);
+
+ // Too short ciphertext
+ EXPECT_THROW(Crypto::DecryptAes256GCM(key, iv, aad, {0x1, 0x2, 0x3}), Crypto::OpensslError);
}
TEST(EncryptAes256GCM, EncryptEmptyPlaintextTestCase7)
--- /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 "crypto/openssl_error.h"
+#include "crypto/random.h"
+#include "encrypted_tunnel.h"
+#include "test_cancel_from_the_other_thread.h"
+#include "tunnel.h"
+
+#include <gmock/gmock.h>
+#include <stdexcept>
+
+namespace {
+
+class MTunnel : public ITunnel {
+public:
+ void Connect(const std::string & /*url*/) override
+ {
+ throw std::runtime_error{"Connect() should not be called"};
+ }
+
+ void WriteBinary(const std::vector<uint8_t> &msg) override { m_lastWrittenMsg = msg; }
+
+ std::vector<uint8_t> ReadBinary() override
+ {
+ auto msg = std::move(m_nextMsgToRead);
+ m_nextMsgToRead.clear(); // Reset variable.
+ return msg;
+ }
+
+ void Disconnect() override {}
+
+ void Cancel() override { throw std::runtime_error{"Cancel() should not be called"}; }
+
+ CryptoBuffer m_lastWrittenMsg;
+ CryptoBuffer m_nextMsgToRead;
+};
+
+} // namespace
+
+TEST(EncryptedTunnel, works)
+{
+ auto reader = Crypto::Noise::SymmetricState::CipherState{
+ CryptoBuffer(Crypto::Noise::KEY_AND_HASH_LEN, 0x0)};
+ auto otherEndWriter = reader;
+ auto writer = Crypto::Noise::SymmetricState::CipherState{
+ CryptoBuffer(Crypto::Noise::KEY_AND_HASH_LEN, 0xab)};
+ auto otherEndReader = writer;
+
+ auto tunnelUPtr = std::make_unique<MTunnel>();
+ auto *tunnel = tunnelUPtr.get();
+ auto encryptedTunnel =
+ EncryptedTunnel(std::move(tunnelUPtr), std::move(reader), std::move(writer));
+
+ // Test writing
+ for (int i = 0; i < 0x123; ++i) {
+ auto msg = CryptoBuffer{static_cast<uint8_t>(i & 0xff)};
+ auto paddedMsg = msg;
+ paddedMsg.resize(32, 0);
+ paddedMsg.back() = 30;
+ encryptedTunnel.WriteBinary(msg);
+ EXPECT_EQ(otherEndReader.DecryptWithAd(tunnel->m_lastWrittenMsg, {}), paddedMsg);
+ }
+
+ // Values come from this implementation after verifying the Noise handshake and decrypting
+ // GetInfo message works with webauthn.io
+ EXPECT_EQ(
+ tunnel->m_lastWrittenMsg,
+ (CryptoBuffer{0x26, 0x8b, 0x2f, 0xf6, 0x19, 0x0c, 0xbf, 0x73, 0x92, 0x8a, 0xb4, 0x1a,
+ 0x2e, 0xb7, 0x3e, 0xd9, 0x3a, 0xd8, 0x15, 0xcb, 0x92, 0xc7, 0x65, 0x3f,
+ 0x46, 0x18, 0x62, 0xd7, 0xb2, 0x08, 0x8b, 0x9b, 0x9d, 0xd0, 0xf8, 0x5b,
+ 0x09, 0x95, 0x93, 0x04, 0xae, 0x0e, 0x91, 0x75, 0x82, 0xc0, 0xd5, 0x39}));
+
+ // Test reading and reading padding.
+ for (int blocks = 0; blocks < 10; ++blocks) {
+ for (int i = blocks * 32; i < (blocks + 1) * 32; ++i) {
+ auto msg = Crypto::RandomBytes(i);
+ auto paddedMsg = msg;
+ paddedMsg.resize((blocks + 1) * 32);
+ paddedMsg.back() = (blocks + 1) * 32 - 1 - i;
+ tunnel->m_nextMsgToRead = otherEndWriter.EncryptWithAd(paddedMsg, {});
+ EXPECT_EQ(encryptedTunnel.ReadBinary(), msg);
+ }
+ }
+
+ // Test writing and writting padding.
+ for (int blocks = 0; blocks < 10; ++blocks) {
+ for (int i = blocks * 32; i < (blocks + 1) * 32; ++i) {
+ auto msg = Crypto::RandomBytes(i);
+ auto paddedMsg = msg;
+ paddedMsg.resize((blocks + 1) * 32);
+ paddedMsg.back() = (blocks + 1) * 32 - 1 - i;
+ encryptedTunnel.WriteBinary(msg);
+ EXPECT_EQ(otherEndReader.DecryptWithAd(tunnel->m_lastWrittenMsg, {}), paddedMsg);
+ }
+ }
+}
+
+TEST(EncryptedTunnel, invalid_data)
+{
+ auto readerWriter = Crypto::Noise::SymmetricState::CipherState{
+ CryptoBuffer(Crypto::Noise::KEY_AND_HASH_LEN, 0x1)};
+
+ auto tunnelUPtr = std::make_unique<MTunnel>();
+ auto *tunnel = tunnelUPtr.get();
+ auto encryptedTunnel = EncryptedTunnel(std::move(tunnelUPtr), readerWriter, readerWriter);
+ // Reading: decryption error
+ tunnel->m_nextMsgToRead = {};
+ EXPECT_THROW(encryptedTunnel.ReadBinary(), Crypto::OpensslError);
+ tunnel->m_nextMsgToRead = CryptoBuffer(32, 0x0);
+ EXPECT_THROW(encryptedTunnel.ReadBinary(), Crypto::OpensslError);
+ // Reading: empty decrypted message
+ tunnel->m_nextMsgToRead = readerWriter.EncryptWithAd({}, {});
+ EXPECT_THAT([&] { encryptedTunnel.ReadBinary(); },
+ testing::ThrowsMessage<Unknown>(testing::HasSubstr("invalid message")));
+ // Reading: invalid paddingBytes
+ auto msg = CryptoBuffer(32, 0);
+ msg.back() = 32;
+ tunnel->m_nextMsgToRead = readerWriter.EncryptWithAd(msg, {});
+ EXPECT_THAT([&] { encryptedTunnel.ReadBinary(); },
+ testing::ThrowsMessage<Unknown>(testing::HasSubstr("invalid message")));
+
+ // Writing
+ EXPECT_THAT([&] { encryptedTunnel.WriteBinary(CryptoBuffer((1 << 20) + 1, 0)); },
+ testing::ThrowsMessage<Unknown>(testing::HasSubstr("message too large")));
+}
+
+namespace {
+
+class OTMEchoTunnel : public ITunnel {
+public:
+ void Connect(const std::string & /*url*/) override
+ {
+ throw std::runtime_error{"Connect() should not be called"};
+ }
+
+ void WriteBinary(const std::vector<uint8_t> &msg) override
+ {
+ m_cancelFacilitator.WithCancelCheck([&] { m_lastWrittenMsg = msg; });
+ }
+
+ std::vector<uint8_t> ReadBinary() override
+ {
+ std::vector<uint8_t> msg;
+ m_cancelFacilitator.WithCancelCheck([&] { msg = m_lastWrittenMsg; });
+ return msg;
+ }
+
+ void Disconnect() override {}
+
+ void Cancel() override { m_cancelFacilitator.Cancel(); }
+
+private:
+ CryptoBuffer m_lastWrittenMsg;
+ CancelFacilitator m_cancelFacilitator;
+};
+
+} // namespace
+
+TEST(EncryptedTunnel, cancel_from_the_other_thread)
+{
+ auto readerWriter = Crypto::Noise::SymmetricState::CipherState{
+ CryptoBuffer(Crypto::Noise::KEY_AND_HASH_LEN, 0x5f)};
+ auto makeEncryptedTunnel = [&] {
+ return EncryptedTunnel(std::make_unique<OTMEchoTunnel>(), readerWriter, readerWriter);
+ };
+ TestCancelFromTheOtherThread<IEncryptedTunnel>(
+ 400, 40, makeEncryptedTunnel, [&](IEncryptedTunnel &encryptedTunnel) {
+ for (int i = 0; i < 2; ++i) {
+ auto msg = Crypto::RandomBytes(get_random(0, 100));
+ encryptedTunnel.WriteBinary(msg);
+ EXPECT_EQ(encryptedTunnel.ReadBinary(), msg);
+ }
+ });
+}
--- /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 "bluetooth_advert.h"
+#include "common.h"
+#include "crypto/ec_key.h"
+#include "crypto/ecdh.h"
+#include "crypto/noise/noise.h"
+#include "crypto/random.h"
+#include "derive_key.h"
+#include "handshake.h"
+#include "lowercase_hex_string_of.h"
+#include "test_cancel_from_the_other_thread.h"
+#include "tunnel_server_domain.h"
+
+#include <stdexcept>
+
+namespace {
+
+CryptoBuffer GenerateDecryptedBluetoothAdvert()
+{
+ auto res = Crypto::RandomBytes(BLUETOOTH_ADVERT_LEN);
+ res[0] = 0;
+ res[res.size() - 2] = 0x01;
+ res.back() = 0;
+ assert(UnpackDecryptedAdvert(res).encodedTunnelServerDomain == 1);
+ return res;
+}
+
+class MTunnel : public ITunnel {
+public:
+ explicit MTunnel(const CryptoBuffer &qrSecret,
+ const CryptoBuffer &compressedPlatformIdentityPublicKey,
+ const CryptoBuffer &decryptedBleAdvert,
+ const CryptoBuffer &getInfoMsg)
+ : m_qrSecret{qrSecret},
+ m_platformIdentityPublicKey{
+ Crypto::X9_62_P_256_Key::ImportPublicKey(compressedPlatformIdentityPublicKey)},
+ m_decryptedBleAdvert{decryptedBleAdvert},
+ m_getInfoMsg{getInfoMsg}
+ {
+ }
+
+ void Connect(const std::string &url) override
+ {
+ auto unpackedBleAdvert = UnpackDecryptedAdvert(m_decryptedBleAdvert);
+ auto tunnelId = DeriveKey(m_qrSecret, {}, KeyPurpose::TunnelID, TUNNEL_ID_LEN);
+
+ std::string expectedUrl = "wss://";
+ expectedUrl += DecodeTunnelServerDomain(unpackedBleAdvert.encodedTunnelServerDomain);
+ expectedUrl += "/cable/connect/";
+ expectedUrl += LowercaseHexStringOf(unpackedBleAdvert.routingId);
+ expectedUrl += '/';
+ expectedUrl += LowercaseHexStringOf(tunnelId);
+
+ EXPECT_EQ(url, expectedUrl);
+ }
+
+ void WriteBinary(const std::vector<uint8_t> &msg) override
+ {
+ // Process msg
+ Crypto::Noise::SymmetricState noise{
+ Crypto::Noise::SymmetricState::HandshakeKind::KNpsk0_P256_AESGCM_SHA256};
+ noise.MixHash(CryptoBuffer{1});
+ noise.MixHash(m_platformIdentityPublicKey.ExportPublicKey(false));
+
+ auto psk = DeriveKey(m_qrSecret, m_decryptedBleAdvert, KeyPurpose::PSK, PSK_LEN);
+ noise.MixKeyAndHash(psk);
+
+ auto exportedPublicKeyLen = m_platformIdentityPublicKey.ExportPublicKey(false).size();
+ ASSERT_GE(msg.size(), exportedPublicKeyLen);
+ auto platformEphermalPublicKeyBytes =
+ CryptoBuffer{msg.data(), msg.data() + exportedPublicKeyLen};
+
+ noise.MixHash(platformEphermalPublicKeyBytes);
+ noise.MixKey(platformEphermalPublicKeyBytes);
+
+ auto ciphertext = CryptoBuffer{msg.begin() + exportedPublicKeyLen, msg.end()};
+ auto plaintext = noise.DecryptAndHash(ciphertext);
+ ASSERT_TRUE(plaintext.empty());
+
+ // Prepare response
+ auto platformEphermalPublicKey =
+ Crypto::X9_62_P_256_Key::ImportPublicKey(platformEphermalPublicKeyBytes);
+
+ auto response = m_authenticatorEphermalKey.ExportPublicKey(false);
+ noise.MixHash(response);
+ noise.MixKey(response);
+
+ noise.MixKey(
+ Crypto::deriveECDHSharedSecret(m_authenticatorEphermalKey, platformEphermalPublicKey));
+ noise.MixKey(Crypto::deriveECDHSharedSecret(m_authenticatorEphermalKey,
+ m_platformIdentityPublicKey));
+
+ ciphertext = noise.EncryptAndHash({});
+ response.insert(response.end(), ciphertext.begin(), ciphertext.end());
+ m_handshakeResponse = std::move(response);
+
+ // Prepare getInfoMsg
+ auto splitRes = noise.Split();
+ auto getInfoMsg = m_getInfoMsg;
+ constexpr size_t paddingGranularity = 32;
+ auto extraBytes = paddingGranularity - getInfoMsg.size() % paddingGranularity;
+ getInfoMsg.resize(getInfoMsg.size() + extraBytes, 0);
+ getInfoMsg.back() = static_cast<uint8_t>(extraBytes) - 1;
+
+ m_encryptedGetInfoMsg = splitRes.second.EncryptWithAd(getInfoMsg, {});
+ }
+
+ std::vector<uint8_t> ReadBinary() override
+ {
+ ++m_readNo;
+ if (m_readNo == 1)
+ return m_handshakeResponse;
+ if (m_readNo == 2)
+ return m_encryptedGetInfoMsg;
+ throw std::runtime_error{"Unexpected ReadBinary() call"};
+ }
+
+ void Disconnect() override
+ {
+ ++m_disconnectNo;
+ if (m_disconnectNo != 1)
+ throw std::runtime_error{"Disconnect() called more than once"};
+ }
+
+ void Cancel() override { throw std::runtime_error{"Unexpected Cancel() call"}; }
+
+private:
+ const CryptoBuffer &m_qrSecret;
+ Crypto::X9_62_P_256_Key m_platformIdentityPublicKey;
+ const CryptoBuffer &m_decryptedBleAdvert;
+ const CryptoBuffer &m_getInfoMsg;
+
+ Crypto::X9_62_P_256_Key m_authenticatorEphermalKey = Crypto::X9_62_P_256_Key::Create();
+ CryptoBuffer m_handshakeResponse;
+ CryptoBuffer m_encryptedGetInfoMsg;
+ int m_readNo = 0;
+ int m_disconnectNo = 0;
+};
+
+class HandshakeTest {
+public:
+ Handshake MakeHandshake()
+ {
+ return Handshake(std::make_unique<MTunnel>(
+ qrSecret, compressedIdentityPublicKey, decryptedBleAdvert, getInfoMsg));
+ }
+
+ void RunAndTestHandshake(IHandshake &handshake)
+ {
+ auto encryptedTunnel =
+ handshake.ConnectAndDoQrHandshake(qrSecret, decryptedBleAdvert, identityKey);
+ EXPECT_EQ(encryptedTunnel->ReadBinary(), getInfoMsg);
+ }
+
+protected:
+ CryptoBuffer qrSecret = Crypto::RandomBytes(QR_SECRET_LEN);
+ Crypto::X9_62_P_256_Key identityKey = Crypto::X9_62_P_256_Key::Create();
+ CryptoBuffer compressedIdentityPublicKey = identityKey.ExportPublicKey(true);
+ CryptoBuffer decryptedBleAdvert = GenerateDecryptedBluetoothAdvert();
+ CryptoBuffer getInfoMsg = Crypto::RandomBytes(get_random(0, 77));
+};
+
+} // namespace
+
+TEST(Handshake, works)
+{
+ HandshakeTest test;
+ auto handshake = test.MakeHandshake();
+ test.RunAndTestHandshake(handshake);
+}
+
+namespace {
+
+class OTMTunnel : public MTunnel {
+public:
+ using MTunnel::MTunnel;
+
+ void Connect(const std::string &url) override
+ {
+ m_cancelFacilitator.WithCancelCheck([&] { MTunnel::Connect(url); });
+ }
+
+ void WriteBinary(const std::vector<uint8_t> &msg) override
+ {
+ m_cancelFacilitator.WithCancelCheck([&] { MTunnel::WriteBinary(msg); });
+ }
+
+ std::vector<uint8_t> ReadBinary() override
+ {
+ std::vector<uint8_t> msg;
+ m_cancelFacilitator.WithCancelCheck([&] { msg = MTunnel::ReadBinary(); });
+ return msg;
+ }
+
+ void Disconnect() override
+ {
+ m_cancelFacilitator.WithCancelCheck([&] { MTunnel::Disconnect(); });
+ }
+
+ void Cancel() override { m_cancelFacilitator.Cancel(); }
+
+private:
+ CancelFacilitator m_cancelFacilitator;
+};
+
+class OTMHandshakeTest : public HandshakeTest {
+public:
+ Handshake MakeHandshake()
+ {
+ return Handshake(std::make_unique<OTMTunnel>(
+ qrSecret, compressedIdentityPublicKey, decryptedBleAdvert, getInfoMsg));
+ }
+};
+
+} // namespace
+
+TEST(Handshake, cancel_from_the_other_thread)
+{
+ OTMHandshakeTest test;
+ auto makeHandshake = [&] { return test.MakeHandshake(); };
+ TestCancelFromTheOtherThread<IHandshake>(200, 20, makeHandshake, [&](IHandshake &handshake) {
+ test.RunAndTestHandshake(handshake);
+ });
+}
QR_CODE_ENDED,
BLE_ADVERT_STARTED,
BLE_ADVERT_ENDED,
+ HANDSHAKE_STARTED,
+ HANDSHAKE_ENDED,
+ READING_FROM_ENCRYPTED_TUNNEL_STARTED,
+ READING_FROM_ENCRYPTED_TUNNEL_ENDED,
FINISHED,
};
enum class CancelCalledOn : int {
NONE,
BLE_ADVERT,
+ HANDSHAKE,
+ ENCRYPTED_TUNNEL,
};
using TestState = TransactionTestState<Event, CancelCalledOn>;
public:
explicit MBtAdvertScanner(TestState &testState) : Tester{testState} {}
- int AwaitAdvert(const CryptoBuffer & /*eidKey*/, CryptoBuffer &decryptedAdvert) override
+ int AwaitAdvert(const CryptoBuffer & /*eidKey*/, CryptoBuffer & /*decryptedAdvert*/) override
{
SCOPED_TRACE("");
UpdateAndCheckState(Event::BLE_ADVERT_STARTED, Event::BLE_ADVERT_ENDED, true);
- decryptedAdvert.assign(16, 0);
return BT_ERROR_NONE;
}
}
};
+class MEncryptedTunnel : public IEncryptedTunnel, public Tester {
+public:
+ explicit MEncryptedTunnel(TestState &testState) : Tester{testState} {}
+
+ CryptoBuffer ReadBinary() override
+ {
+ SCOPED_TRACE("");
+ UpdateAndCheckState(Event::READING_FROM_ENCRYPTED_TUNNEL_STARTED,
+ Event::READING_FROM_ENCRYPTED_TUNNEL_ENDED,
+ true);
+ return {};
+ }
+
+ void WriteBinary(CryptoBuffer /*msg*/) override
+ {
+ throw std::runtime_error{"Should not be called"};
+ }
+
+ void Cancel() override
+ {
+ SCOPED_TRACE("");
+ CheckCancel(CancelCalledOn::ENCRYPTED_TUNNEL);
+ }
+};
+
+class MHandshake : public IHandshake, public Tester {
+public:
+ explicit MHandshake(TestState &testState) : Tester{testState} {}
+
+ std::unique_ptr<IEncryptedTunnel>
+ ConnectAndDoQrHandshake(const CryptoBuffer & /*qrSecret*/,
+ const CryptoBuffer & /*decryptedBleAdvert*/,
+ const Crypto::X9_62_P_256_Key & /*identityKey*/) override
+ {
+ SCOPED_TRACE("");
+ UpdateAndCheckState(Event::HANDSHAKE_STARTED, Event::HANDSHAKE_ENDED, true);
+ return std::make_unique<MEncryptedTunnel>(m_testState);
+ }
+
+ void Cancel() override
+ {
+ SCOPED_TRACE("");
+ CheckCancel(CancelCalledOn::HANDSHAKE);
+ }
+};
+
} // namespace
TEST(QrTransaction, Cancel)
nullptr,
{},
std::make_unique<MQrCodeShower>(testState),
- std::make_unique<MBtAdvertScanner>(testState));
+ std::make_unique<MBtAdvertScanner>(testState),
+ std::make_unique<MHandshake>(testState));
};
// Before PerformTransaction()
{
EXPECT_EQ(testState.m_lastEvent, Event::BLE_ADVERT_ENDED);
EXPECT_EQ(testState.m_cancelCalledOn, CancelCalledOn::BLE_ADVERT);
}
+ // In MHandshake
+ {
+ auto transaction = makeTransaction();
+ testState.Reset(Event::HANDSHAKE_STARTED, transaction);
+ EXPECT_THROW(transaction.PerformTransaction(), Cancelled);
+ EXPECT_EQ(testState.m_lastEvent, Event::HANDSHAKE_STARTED);
+ EXPECT_EQ(testState.m_cancelCalledOn, CancelCalledOn::HANDSHAKE);
+ }
+ // After MHandshake
+ {
+ auto transaction = makeTransaction();
+ testState.Reset(Event::HANDSHAKE_ENDED, transaction);
+ EXPECT_THROW(transaction.PerformTransaction(), Cancelled);
+ EXPECT_EQ(testState.m_lastEvent, Event::HANDSHAKE_ENDED);
+ EXPECT_EQ(testState.m_cancelCalledOn, CancelCalledOn::HANDSHAKE);
+ }
+ // In MEncryptedTunnel
+ {
+ auto transaction = makeTransaction();
+ testState.Reset(Event::READING_FROM_ENCRYPTED_TUNNEL_STARTED, transaction);
+ EXPECT_THROW(transaction.PerformTransaction(), Cancelled);
+ EXPECT_EQ(testState.m_lastEvent, Event::READING_FROM_ENCRYPTED_TUNNEL_STARTED);
+ EXPECT_EQ(testState.m_cancelCalledOn, CancelCalledOn::ENCRYPTED_TUNNEL);
+ }
+ // After MEncryptedTunnel
+ {
+ auto transaction = makeTransaction();
+ testState.Reset(Event::READING_FROM_ENCRYPTED_TUNNEL_ENDED, transaction);
+ EXPECT_THROW(transaction.PerformTransaction(), Cancelled);
+ EXPECT_EQ(testState.m_lastEvent, Event::READING_FROM_ENCRYPTED_TUNNEL_ENDED);
+ EXPECT_EQ(testState.m_cancelCalledOn, CancelCalledOn::ENCRYPTED_TUNNEL);
+ }
// No Cancel
{
auto transaction = makeTransaction();
testState.Reset(Event::FINISHED, transaction);
EXPECT_NO_THROW(transaction.PerformTransaction());
- EXPECT_EQ(testState.m_lastEvent, Event::BLE_ADVERT_ENDED);
+ EXPECT_EQ(testState.m_lastEvent, Event::READING_FROM_ENCRYPTED_TUNNEL_ENDED);
EXPECT_EQ(testState.m_cancelCalledOn, CancelCalledOn::NONE);
}
}
class OTMBtAdvertScanner : public IBtAdvertScanner {
public:
- int AwaitAdvert(const CryptoBuffer & /*eidKey*/, CryptoBuffer &decryptedAdvert) override
+ int AwaitAdvert(const CryptoBuffer & /*eidKey*/, CryptoBuffer & /*decryptedAdvert*/) override
{
auto res = BT_ERROR_NONE;
- m_cancelFacilitator.WithCancelCheck([&] { decryptedAdvert.assign(16, 0); },
- [&] { res = BT_ERROR_CANCELLED; });
+ m_cancelFacilitator.WithCancelCheck([] {}, [&] { res = BT_ERROR_CANCELLED; });
return res;
}
CancelFacilitator m_cancelFacilitator;
};
+class OTMEncryptedTunnel : public IEncryptedTunnel {
+ CryptoBuffer ReadBinary() override
+ {
+ m_cancelFacilitator.CancelCheck();
+ return {};
+ }
+
+ void WriteBinary(CryptoBuffer /*msg*/) override { m_cancelFacilitator.CancelCheck(); }
+
+ void Cancel() override { m_cancelFacilitator.Cancel(); }
+
+private:
+ CancelFacilitator m_cancelFacilitator;
+};
+
+class OTMHandshake : public IHandshake {
+public:
+ std::unique_ptr<IEncryptedTunnel>
+ ConnectAndDoQrHandshake(const CryptoBuffer & /*qrSecret*/,
+ const CryptoBuffer & /*decryptedBleAdvert*/,
+ const Crypto::X9_62_P_256_Key & /*identityKey*/) override
+ {
+ m_cancelFacilitator.CancelCheck();
+ return std::make_unique<OTMEncryptedTunnel>();
+ }
+
+ void Cancel() override { m_cancelFacilitator.Cancel(); }
+
+private:
+ CancelFacilitator m_cancelFacilitator;
+};
+
} // namespace
TEST(QrTransaction, cancel_from_the_other_thread)
nullptr,
{},
std::make_unique<OTMQrCodeShower>(),
- std::make_unique<OTMBtAdvertScanner>());
+ std::make_unique<OTMBtAdvertScanner>(),
+ std::make_unique<OTMHandshake>());
};
TestCancelFromTheOtherThread<ITransaction>(
400, 40, makeTransaction, [](ITransaction &transaction) {
m_testState.m_cancelCalledOn = cancelCalledOn;
}
-private:
+protected:
TransactionTestState<Event, CancelCalledOn> &m_testState;
};