diff --git a/llarp/service/endpoint.cpp b/llarp/service/endpoint.cpp index 0df104efe..255688373 100644 --- a/llarp/service/endpoint.cpp +++ b/llarp/service/endpoint.cpp @@ -405,11 +405,7 @@ namespace llarp const auto& keyfile = m_state->m_Keyfile; if (!keyfile.empty()) { - if (!m_Identity.EnsureKeys(keyfile, Router()->keyManager()->needBackup())) - { - LogError("Can't ensure keyfile [", keyfile, "]"); - return false; - } + m_Identity.EnsureKeys(keyfile, Router()->keyManager()->needBackup()); } else { diff --git a/llarp/service/identity.cpp b/llarp/service/identity.cpp index 108a92a32..6750da89e 100644 --- a/llarp/service/identity.cpp +++ b/llarp/service/identity.cpp @@ -84,19 +84,14 @@ namespace llarp return CryptoManager::instance()->sign(sig, signkey, buf); } - bool + void Identity::EnsureKeys(const std::string& fname, bool needBackup) { std::array tmp; llarp_buffer_t buf(tmp); - std::error_code ec; - bool exists = fs::exists(fname, ec); - if (ec) - { - LogError("Could not query file status for ", fname, ": ", ec.message()); - return false; - } + // this can throw + bool exists = fs::exists(fname); if (exists and needBackup) { @@ -110,24 +105,23 @@ namespace llarp // regen and encode RegenerateKeys(); if (!BEncode(&buf)) - return false; + throw std::length_error("failed to encode new identity"); // rewind buf.sz = buf.cur - buf.base; buf.cur = buf.base; // write auto optional_f = util::OpenFileStream(fname, std::ios::binary); if (!optional_f) - return false; + throw std::runtime_error(stringify("can not open ", fname)); auto& f = *optional_f; if (!f.is_open()) - return false; + throw std::runtime_error(stringify("did not open ", fname)); f.write((char*)buf.cur, buf.sz); } - if (!fs::is_regular_file(fname)) + if (not fs::is_regular_file(fname)) { - LogError("keyfile ", fname, " is not a regular file"); - return false; + throw std::invalid_argument(stringify(fname, " is not a regular file")); } // read file @@ -137,11 +131,11 @@ namespace llarp inf.seekg(0, std::ios::beg); if (sz > sizeof(tmp)) - return false; + throw std::length_error("service identity too big"); // decode inf.read((char*)buf.base, sz); if (!bencode_decode_dict(*this, &buf)) - return false; + throw std::length_error("could not decode service identity"); std::optional van; if (!vanity.IsZero()) @@ -149,7 +143,10 @@ namespace llarp // update pubkeys pub.Update(seckey_topublic(signkey), seckey_topublic(enckey), van); auto crypto = CryptoManager::instance(); - return crypto->derive_subkey_private(derivedSignKey, signkey, 1); + if (not crypto->derive_subkey_private(derivedSignKey, signkey, 1)) + { + throw std::runtime_error("failed to derive subkey"); + } } std::optional diff --git a/llarp/service/identity.hpp b/llarp/service/identity.hpp index d1d4487b9..77635ef9e 100644 --- a/llarp/service/identity.hpp +++ b/llarp/service/identity.hpp @@ -37,7 +37,7 @@ namespace llarp BEncode(llarp_buffer_t* buf) const; /// @param needBackup determines whether existing keys will be cycled - bool + void EnsureKeys(const std::string& fpath, bool needBackup); bool diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 83fff5339..c749d9cf8 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -33,7 +33,6 @@ add_executable(testAll routing/llarp_routing_transfer_traffic.cpp routing/test_llarp_routing_obtainexitmessage.cpp service/test_llarp_service_address.cpp - service/test_llarp_service_identity.cpp test_llarp_encrypted_frame.cpp test_llarp_router_contact.cpp util/meta/test_llarp_util_memfn.cpp @@ -77,6 +76,8 @@ add_executable(catchAll service/test_llarp_service_name.cpp exit/test_llarp_exit_context.cpp iwp/test_iwp_session.cpp + service/test_llarp_service_identity.cpp + test_util.cpp check_main.cpp) target_link_libraries(catchAll PUBLIC liblokinet Catch2::Catch2) diff --git a/test/service/test_llarp_service_identity.cpp b/test/service/test_llarp_service_identity.cpp index 2c35e4dbd..d152eae87 100644 --- a/test/service/test_llarp_service_identity.cpp +++ b/test/service/test_llarp_service_identity.cpp @@ -8,123 +8,45 @@ #include #include -#include #include - -#include -#include +#include using namespace llarp; -using namespace testing; - -struct HiddenServiceTest : public test::LlarpTest<> -{ - service::Identity ident; -}; -TEST_F(HiddenServiceTest, TestAddressToFromString) +TEST_CASE("test service address from string") { + + service::Identity ident{}; + auto str = ident.pub.Addr().ToString(); service::Address addr; - ASSERT_TRUE(addr.FromString(str)); - ASSERT_TRUE(addr == ident.pub.Addr()); -} - -struct ServiceIdentityTest : public test::LlarpTest<> -{ - ServiceIdentityTest() - { - } -}; - -template < typename Arg > -std::function< void(Arg&) > -FillArg(byte_t val) -{ - return [=](Arg& arg) { arg.Fill(val); }; -} - -TEST_F(ServiceIdentityTest, EnsureKeys) -{ - fs::path p = test::randFilename(); - ASSERT_FALSE(fs::exists(fs::status(p))); - - test::FileGuard guard(p); - - const SecretKey k; - - EXPECT_CALL(m_crypto, derive_subkey_private(_, _, _, _)) - .WillRepeatedly(Return(true)); - - EXPECT_CALL(m_crypto, encryption_keygen(_)) - .WillOnce(WithArg< 0 >(FillArg< SecretKey >(0x01))); - - EXPECT_CALL(m_crypto, identity_keygen(_)) - .WillOnce(WithArg< 0 >(FillArg< SecretKey >(0x02))); - - EXPECT_CALL(m_crypto, pqe_keygen(_)) - .WillOnce(WithArg< 0 >(FillArg< PQKeyPair >(0x03))); - - service::Identity identity; - ASSERT_TRUE(identity.EnsureKeys(p.string(), false)); - ASSERT_TRUE(fs::exists(fs::status(p))); - - // Verify what is on disk is what is what was generated - service::Identity other; - // No need to set more mocks, as we shouldn't need to re-keygen - ASSERT_TRUE(other.EnsureKeys(p.string(), false)); - ASSERT_EQ(identity, other); -} - -TEST_F(ServiceIdentityTest, EnsureKeysDir) -{ - fs::path p = test::randFilename(); - ASSERT_FALSE(fs::exists(fs::status(p))); - - test::FileGuard guard(p); - std::error_code code; - ASSERT_TRUE(fs::create_directory(p, code)) << code; - - service::Identity identity; - ASSERT_FALSE(identity.EnsureKeys(p.string(), false)); + CHECK(addr.FromString(str)); + CHECK(addr == ident.pub.Addr()); } -TEST_F(ServiceIdentityTest, EnsureKeysBrokenFile) +TEST_CASE("test service::Identity throws on error") { fs::path p = test::randFilename(); - ASSERT_FALSE(fs::exists(fs::status(p))); + CHECK(not fs::exists(fs::status(p))); test::FileGuard guard(p); std::error_code code; std::fstream file; file.open(p.string(), std::ios::out); - ASSERT_TRUE(file.is_open()) << p; + CHECK(file.is_open()); + file << p; file.close(); service::Identity identity; - ASSERT_FALSE(identity.EnsureKeys(p.string(), false)); + REQUIRE_THROWS(identity.EnsureKeys(p, false)); } -struct RealCryptographyTest : public ::testing::Test -{ - std::unique_ptr< CryptoManager > _manager; - void - SetUp() - { - _manager = std::make_unique< CryptoManager >(new sodium::CryptoLibSodium()); - } - - void - TearDown() - { - _manager.reset(); - } -}; - -TEST_F(RealCryptographyTest, TestKnownDerivation) +TEST_CASE("test subkey derivation", "[crypto]") { + CryptoManager manager(new sodium::CryptoLibSodium()); + // These values came out of a run of Tor's test code, so that we can confirm we are doing the same // blinding subkey crypto math as Tor. Our hash value is generated differently so we use the hash // from a Tor random test suite run. @@ -152,27 +74,29 @@ TEST_F(RealCryptographyTest, TestKnownDerivation) }}; SecretKey root{seed}; - ASSERT_EQ(root.toPublic(), PubKey{root_pub_data}); + CHECK(root.toPublic() == PubKey{root_pub_data}); PrivateKey root_key; - ASSERT_TRUE(root.toPrivate(root_key)); - ASSERT_EQ(root_key, PrivateKey{root_key_data}); + CHECK(root.toPrivate(root_key)); + CHECK(root_key == PrivateKey{root_key_data}); auto crypto = CryptoManager::instance(); PrivateKey aprime; // a' - ASSERT_TRUE(crypto->derive_subkey_private(aprime, root, 0, &hash)); + CHECK(crypto->derive_subkey_private(aprime, root, 0, &hash)); // We use a different signing hash than Tor, so only the private key value (the first 32 bytes) // will match: - ASSERT_EQ(aprime.ToHex().substr(0, 64), PrivateKey{derived_key_data}.ToHex().substr(0, 64)); + CHECK(aprime.ToHex().substr(0, 64) == PrivateKey{derived_key_data}.ToHex().substr(0, 64)); PubKey Aprime; // A' - ASSERT_TRUE(crypto->derive_subkey(Aprime, root.toPublic(), 0, &hash)); - ASSERT_EQ(Aprime, PubKey{derived_pub_data}); + CHECK(crypto->derive_subkey(Aprime, root.toPublic(), 0, &hash)); + CHECK(Aprime == PubKey{derived_pub_data}); } -TEST_F(RealCryptographyTest, TestRootSigning) +TEST_CASE("test root key signing" , "[crypto]") { + CryptoManager manager(new sodium::CryptoLibSodium()); + auto crypto = CryptoManager::instance(); SecretKey root_key; crypto->identity_keygen(root_key); @@ -185,68 +109,72 @@ TEST_F(RealCryptographyTest, TestRootSigning) llarp_buffer_t nibbs_buf{nibbs.data(), nibbs.size()}; Signature sig_sodium; - ASSERT_TRUE(crypto->sign(sig_sodium, root_key, nibbs_buf)); + CHECK(crypto->sign(sig_sodium, root_key, nibbs_buf)); PrivateKey root_privkey; - ASSERT_TRUE(root_key.toPrivate(root_privkey)); + CHECK(root_key.toPrivate(root_privkey)); Signature sig_ours; - ASSERT_TRUE(crypto->sign(sig_ours, root_privkey, nibbs_buf)); + CHECK(crypto->sign(sig_ours, root_privkey, nibbs_buf)); - ASSERT_EQ(sig_sodium, sig_ours); + CHECK(sig_sodium == sig_ours); } -TEST_F(RealCryptographyTest, TestGenerateDeriveKey) +TEST_CASE("Test generate derived key", "[crypto]") { + CryptoManager manager(new sodium::CryptoLibSodium()); + auto crypto = CryptoManager::instance(); SecretKey root_key; crypto->identity_keygen(root_key); PrivateKey root_privkey; - ASSERT_TRUE(root_key.toPrivate(root_privkey)); + CHECK(root_key.toPrivate(root_privkey)); PrivateKey a; PubKey A; - ASSERT_TRUE(root_key.toPrivate(a)); - ASSERT_TRUE(a.toPublic(A)); - ASSERT_EQ(A, root_key.toPublic()); + CHECK(root_key.toPrivate(a)); + CHECK(a.toPublic(A)); + CHECK(A == root_key.toPublic()); { // paranoid check to ensure this works as expected PubKey aB; crypto_scalarmult_ed25519_base(aB.data(), a.data()); - ASSERT_EQ(A, aB); + CHECK(A == aB); } PrivateKey aprime; // a' - ASSERT_TRUE(crypto->derive_subkey_private(aprime, root_key, 1)); + CHECK(crypto->derive_subkey_private(aprime, root_key, 1)); PubKey Aprime; // A' - ASSERT_TRUE(crypto->derive_subkey(Aprime, A, 1)); + CHECK(crypto->derive_subkey(Aprime, A, 1)); // We should also be able to derive A' via a': PubKey Aprime_alt; - ASSERT_TRUE(aprime.toPublic(Aprime_alt)); + CHECK(aprime.toPublic(Aprime_alt)); - ASSERT_EQ(Aprime, Aprime_alt); + CHECK(Aprime == Aprime_alt); // Generate using the same constant and make sure we get an identical privkey (including the // signing hash value) PrivateKey aprime_repeat; - ASSERT_TRUE(crypto->derive_subkey_private(aprime_repeat, root_key, 1)); - ASSERT_EQ(aprime_repeat, aprime); + CHECK(crypto->derive_subkey_private(aprime_repeat, root_key, 1)); + CHECK(aprime_repeat == aprime); // Generate another using a different constant and make sure we get something different PrivateKey a2; PubKey A2; - ASSERT_TRUE(crypto->derive_subkey_private(a2, root_key, 2)); - ASSERT_TRUE(crypto->derive_subkey(A2, A, 2)); - ASSERT_NE(A2, Aprime); - ASSERT_NE(a2.ToHex().substr(0, 64), aprime.ToHex().substr(0, 64)); - ASSERT_NE(a2.ToHex().substr(64), aprime.ToHex().substr(64)); // The hash should be different too + CHECK(crypto->derive_subkey_private(a2, root_key, 2)); + CHECK(crypto->derive_subkey(A2, A, 2)); + CHECK(A2 != Aprime); + CHECK(a2.ToHex().substr(0, 64) != aprime.ToHex().substr(0, 64)); + CHECK(a2.ToHex().substr(64) != aprime.ToHex().substr(64)); // The hash should be different too } -TEST_F(RealCryptographyTest, TestSignUsingDerivedKey) +TEST_CASE("Test signing with derived key", "[crypto]") { + CryptoManager manager(new sodium::CryptoLibSodium()); + auto crypto = CryptoManager::instance(); SecretKey root_key; crypto->identity_keygen(root_key); @@ -269,17 +197,19 @@ TEST_F(RealCryptographyTest, TestSignUsingDerivedKey) llarp_buffer_t buf(s.data(), s.size()); Signature sig; - ASSERT_TRUE(crypto->sign(sig, aprime, buf)); - - ASSERT_TRUE(crypto->verify(Aprime, buf, sig)); + + CHECK(crypto->sign(sig, aprime, buf)); + CHECK(crypto->verify(Aprime, buf, sig)); } -TEST_F(RealCryptographyTest, TestEncryptAndSignIntroSet) +TEST_CASE("Test sign and encrypt introset", "[crypto]") { + CryptoManager manager(new sodium::CryptoLibSodium()); + service::Identity ident; ident.RegenerateKeys(); service::Address addr; - ASSERT_TRUE(ident.pub.CalculateAddress(addr.as_array())); + CHECK(ident.pub.CalculateAddress(addr.as_array())); service::IntroSet I; auto now = time_now_ms(); I.T = now; @@ -293,10 +223,11 @@ TEST_F(RealCryptographyTest, TestEncryptAndSignIntroSet) } const auto maybe = ident.EncryptAndSignIntroSet(I, now); - ASSERT_TRUE(maybe.has_value()); - ASSERT_TRUE(maybe->Verify(now)); + CHECK(maybe.has_value()); + CHECK(maybe->Verify(now)); PubKey blind_key; const PubKey root_key(addr.as_array()); auto crypto = CryptoManager::instance(); - ASSERT_TRUE(crypto->derive_subkey(blind_key, root_key, 1)); + CHECK(crypto->derive_subkey(blind_key, root_key, 1)); + CHECK(blind_key == maybe->derivedSigningKey); }