diff --git a/tests/test_networkengine.cpp b/tests/test_networkengine.cpp index a4992f5b..ff5be6a0 100644 --- a/tests/test_networkengine.cpp +++ b/tests/test_networkengine.cpp @@ -1085,6 +1085,15 @@ NetworkEngineTester::testTransportEncryptsAfterKeyExchange() CPPUNIT_ASSERT(isEnvelope(b.socket->sends.at(0), transport::EnvelopeType::Session)); b.deliverTo(a); CPPUNIT_ASSERT(done); + + // b answered our request: both have the session, keys are left out + done = false; + a.ping(a.node(b), done); + CPPUNIT_ASSERT(isEnvelope(a.socket->sends.at(0), transport::EnvelopeType::Data)); + a.deliverTo(b); + CPPUNIT_ASSERT(isEnvelope(b.socket->sends.at(0), transport::EnvelopeType::Data)); + b.deliverTo(a); + CPPUNIT_ASSERT(done); } void @@ -1259,9 +1268,8 @@ NetworkEngineTester::testTransportPrivateNetwork() CPPUNIT_ASSERT(done); auto peerB = a.node(b)->getTransport(); CPPUNIT_ASSERT(peerB and peerB->isAuthorized(std::chrono::system_clock::now())); - // b only saw our credential in the unauthenticated hello - CPPUNIT_ASSERT(not peerB->acked); + // b only saw our credential in the unauthenticated hello done = false; a.ping(a.node(b), done); CPPUNIT_ASSERT(isEnvelope(a.socket->sends.at(0), transport::EnvelopeType::Session, true)); @@ -1269,15 +1277,13 @@ NetworkEngineTester::testTransportPrivateNetwork() CPPUNIT_ASSERT(isEnvelope(b.socket->sends.at(0), transport::EnvelopeType::Session, true)); b.deliverTo(a); CPPUNIT_ASSERT(done); - CPPUNIT_ASSERT(peerB->acked); - // b accepted our credential: no need to send it anymore + // b accepted our request: Data envelopes, without credential done = false; a.ping(a.node(b), done); - CPPUNIT_ASSERT(isEnvelope(a.socket->sends.at(0), transport::EnvelopeType::Session)); + CPPUNIT_ASSERT(isEnvelope(a.socket->sends.at(0), transport::EnvelopeType::Data)); a.deliverTo(b); - // we haven't replied to b yet: it still sends its credential - CPPUNIT_ASSERT(isEnvelope(b.socket->sends.at(0), transport::EnvelopeType::Session, true)); + CPPUNIT_ASSERT(isEnvelope(b.socket->sends.at(0), transport::EnvelopeType::Data)); b.deliverTo(a); CPPUNIT_ASSERT(done); } @@ -1350,31 +1356,52 @@ NetworkEngineTester::testTransportEpochRotation() auto captured = a.socket->sends.at(0); CPPUNIT_ASSERT(isEnvelope(captured, transport::EnvelopeType::Session)); a.deliverTo(b); + auto oldReply = b.socket->sends.at(0); + b.deliverTo(a); + a.ping(a.node(b), done); + auto capturedData = a.socket->sends.at(0); + CPPUNIT_ASSERT(isEnvelope(capturedData, transport::EnvelopeType::Data)); + a.deliverTo(b); b.deliverTo(a); - advance(a, std::chrono::minutes(30)); + // b rotates: a's Data envelopes still open with b's previous epoch advance(b, std::chrono::minutes(30)); - a.takeSent(); b.takeSent(); + done = false; + a.ping(a.node(b), done); + CPPUNIT_ASSERT(isEnvelope(a.socket->sends.at(0), transport::EnvelopeType::Data)); + a.deliverTo(b); + // b answers with its new ephemeral key, until a confirms the new session + CPPUNIT_ASSERT(isEnvelope(b.socket->sends.at(0), transport::EnvelopeType::Session)); + CPPUNIT_ASSERT(ephemeralKey(b.socket->sends.at(0)) != ephemeralKey(oldReply)); + b.deliverTo(a); + CPPUNIT_ASSERT(done); + done = false; + a.ping(a.node(b), done); + CPPUNIT_ASSERT(isEnvelope(a.socket->sends.at(0), transport::EnvelopeType::Data)); + a.deliverTo(b); + CPPUNIT_ASSERT(isEnvelope(b.socket->sends.at(0), transport::EnvelopeType::Data)); + b.deliverTo(a); + CPPUNIT_ASSERT(done); - // a uses a new ephemeral key, with the one b used before rotating + // a rotates: new ephemeral key + advance(a, std::chrono::minutes(30)); + a.takeSent(); done = false; a.ping(a.node(b), done); CPPUNIT_ASSERT(isEnvelope(a.socket->sends.at(0), transport::EnvelopeType::Session)); CPPUNIT_ASSERT(ephemeralKey(a.socket->sends.at(0)) != ephemeralKey(captured)); a.deliverTo(b); - CPPUNIT_ASSERT(isEnvelope(b.socket->sends.at(0), transport::EnvelopeType::Session)); b.deliverTo(a); CPPUNIT_ASSERT(done); - // The previous ephemeral key is still accepted for one period + // Keys of the previous epoch are accepted for one period, then erased b.engine->processMessage(captured.data.data(), captured.data.size(), a.addr); CPPUNIT_ASSERT_EQUAL((size_t) 1, b.takeSent().size()); - - // then erased advance(b, std::chrono::minutes(30)); b.takeSent(); b.engine->processMessage(captured.data.data(), captured.data.size(), a.addr); + b.engine->processMessage(capturedData.data.data(), capturedData.data.size(), a.addr); CPPUNIT_ASSERT(b.socket->sends.empty()); } @@ -1396,13 +1423,12 @@ NetworkEngineTester::testTransportPeerRestart() b->deliverTo(a); CPPUNIT_ASSERT(done); } - CPPUNIT_ASSERT(a.node(*b)->getTransport()->acked); - // b restarts with the same key: it knows neither our ephemeral key nor our credential + // b restarts with the same key: it lost the session and our credential b = std::make_unique("127.0.0.3", configB, 3); done = false; a.ping(a.node(*b), done); - CPPUNIT_ASSERT(isEnvelope(a.socket->sends.at(0), transport::EnvelopeType::Session)); + CPPUNIT_ASSERT(isEnvelope(a.socket->sends.at(0), transport::EnvelopeType::Data)); a.deliverTo(*b); CPPUNIT_ASSERT(b->socket->sends.empty()); @@ -1505,6 +1531,23 @@ NetworkEngineTester::testTransportSessionAuthorizesWrites() CPPUNIT_ASSERT_EQUAL(1, b.listens); b.deliverTo(a); CPPUNIT_ASSERT(token and token->empty()); + + // Data envelopes also come from the address that got b's ephemeral key + token.reset(); + a.engine->sendListen(a.node(b), + InfoHash::getRandom(rd), + {}, + {}, + 2, + [&token](const net::Request&, net::RequestAnswer&& answer) { + token = std::move(answer.ntoken); + }, + {}); + CPPUNIT_ASSERT(isEnvelope(a.socket->sends.at(0), transport::EnvelopeType::Data)); + a.deliverTo(b); + CPPUNIT_ASSERT_EQUAL(2, b.listens); + b.deliverTo(a); + CPPUNIT_ASSERT(token and token->empty()); } void @@ -1578,6 +1621,17 @@ NetworkEngineTester::testTransportEphemeralBoundToAddress() CPPUNIT_ASSERT(b.socket->sends.empty()); b.engine->processMessage(fromA.data.data(), fromA.data.size(), a.addr); CPPUNIT_ASSERT_EQUAL((size_t) 1, b.socket->sends.size()); + + // So does a Data envelope, without any key derivation + a.takeSent(); + b.deliverTo(a); + a.ping(a.node(b), done); + auto data = a.socket->sends.at(0); + CPPUNIT_ASSERT(isEnvelope(data, transport::EnvelopeType::Data)); + b.engine->processMessage(data.data.data(), data.data.size(), c.addr); + CPPUNIT_ASSERT(b.socket->sends.empty()); + b.engine->processMessage(data.data.data(), data.data.size(), a.addr); + CPPUNIT_ASSERT_EQUAL((size_t) 1, b.socket->sends.size()); } } // namespace test diff --git a/tests/test_transport.cpp b/tests/test_transport.cpp index e8ef839e..5642838d 100644 --- a/tests/test_transport.cpp +++ b/tests/test_transport.cpp @@ -195,6 +195,46 @@ TransportTester::testOpenRejectsTampering() CPPUNIT_ASSERT(not open(sessionKeys(b, eb, a.pub, ea.pub, 1)->rx, envelope.data(), envelope.size())); } +void +TransportTester::testDataEnvelope() +{ + auto a = KeyPair::generate(), ea = KeyPair::generate(); + auto b = KeyPair::generate(), eb = KeyPair::generate(); + auto keys = *sessionKeys(a, ea, b.pub, eb.pub, 0); + auto msg = message(); + + auto envelope = sealData(keys.tx, msg.data(), msg.size()); + CPPUNIT_ASSERT_EQUAL(msg.size() + DATA_ENVELOPE_OVERHEAD, envelope.size()); + CPPUNIT_ASSERT_EQUAL((size_t) 30, DATA_ENVELOPE_OVERHEAD); + CPPUNIT_ASSERT(isEnvelope(envelope.data(), envelope.size())); + CPPUNIT_ASSERT_THROW(msgpack::unpack((const char*) envelope.data(), envelope.size()), std::exception); + auto header = parseHeader(envelope.data(), envelope.size()); + CPPUNIT_ASSERT(header and header->type == EnvelopeType::Data and not header->credential); + + auto opened = open(sessionKeys(b, eb, a.pub, ea.pub, 0)->rx, envelope.data(), envelope.size()); + CPPUNIT_ASSERT(opened and *opened == msg); + + for (size_t i : {(size_t) 1, DATA_HEADER_SIZE, DATA_HEADER_SIZE + NONCE_SIZE, envelope.size() - 1}) { + auto tampered = envelope; + tampered[i] ^= 0x01; + CPPUNIT_ASSERT(not open(keys.tx, tampered.data(), tampered.size())); + } + CPPUNIT_ASSERT(not open(keys.tx, envelope.data(), DATA_ENVELOPE_OVERHEAD - 1)); + CPPUNIT_ASSERT(not open(keys.rx, envelope.data(), envelope.size())); + + // Same key, other type: the type is authenticated + auto session = seal(keys.tx, EnvelopeType::Session, a.pub, ea.pub, msg.data(), msg.size()); + Blob relabeled {ENVELOPE_MARKER, static_cast(EnvelopeType::Data)}; + relabeled.insert(relabeled.end(), session.begin() + HEADER_SIZE, session.end()); + CPPUNIT_ASSERT(not open(keys.tx, relabeled.data(), relabeled.size())); + + // Data never carries a credential + auto flagged = envelope; + flagged[1] |= CREDENTIAL_FLAG; + CPPUNIT_ASSERT(not parseHeader(flagged.data(), flagged.size())); + CPPUNIT_ASSERT(not open(keys.tx, flagged.data(), flagged.size())); +} + void TransportTester::testParseHeader() { @@ -203,7 +243,7 @@ TransportTester::testParseHeader() auto envelope = seal(Key {}, EnvelopeType::Session, a.pub, ea.pub, msg.data(), msg.size()); CPPUNIT_ASSERT(parseHeader(envelope.data(), HEADER_SIZE)); CPPUNIT_ASSERT(not parseHeader(envelope.data(), HEADER_SIZE - 1)); - for (uint8_t type : {0, 4, 0x80, 0x84}) { + for (uint8_t type : {0, 5, 0x80, 0x84, 0x85}) { auto unknown = envelope; unknown[1] = type; CPPUNIT_ASSERT(not parseHeader(unknown.data(), unknown.size())); diff --git a/tests/test_transport.h b/tests/test_transport.h index a6ae1e17..7c23fcca 100644 --- a/tests/test_transport.h +++ b/tests/test_transport.h @@ -18,6 +18,7 @@ class TransportTester : public CppUnit::TestFixture CPPUNIT_TEST(testAddressKey); CPPUNIT_TEST(testSealOpen); CPPUNIT_TEST(testOpenRejectsTampering); + CPPUNIT_TEST(testDataEnvelope); CPPUNIT_TEST(testParseHeader); CPPUNIT_TEST(testEnvelopeIsNotMsgpack); CPPUNIT_TEST(testHello); @@ -37,6 +38,7 @@ public: void testAddressKey(); void testSealOpen(); void testOpenRejectsTampering(); + void testDataEnvelope(); void testParseHeader(); void testEnvelopeIsNotMsgpack(); void testHello();