mirror of
https://github.com/savoirfairelinux/opendht.git
synced 2026-11-08 04:21:51 +00:00
tests: cover keyless Data envelopes and session confirmation
Steady-state traffic uses Data envelopes, without sender keys nor credential, once the peer answered a request. Data envelopes only open from the session address, also authorize writes, and survive the receiver's epoch rotation.
This commit is contained in:
1 parent
853bb1eeed
commit
40560eec4e
3 files changed
+114
-18
No files matched your search
@@ -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<TransportTestNode>("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
|
||||
|
||||
@@ -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<uint8_t>(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()));
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in new issue
Block a user