mirror of
https://github.com/PurpleI2P/i2pd.git
synced 2026-03-29 08:50:14 +00:00
post-quantum support for outgoing SSU2 session
This commit is contained in:
@@ -67,6 +67,8 @@ namespace crypto
|
||||
void GenerateKeys ();
|
||||
void GetPublicKey (uint8_t * pub) const;
|
||||
void SetPublicKey (const uint8_t * pub);
|
||||
size_t GetKeyLen () const { return m_KeyLen; };
|
||||
size_t GetCTLen () const { return m_CTLen; };
|
||||
void Encaps (uint8_t * ciphertext, uint8_t * shared);
|
||||
void Decaps (const uint8_t * ciphertext, uint8_t * shared);
|
||||
|
||||
|
||||
@@ -165,7 +165,7 @@ namespace transport
|
||||
header.h.connID = GetDestConnID (); // dest id
|
||||
RAND_bytes (header.buf + 8, 4); // random packet num
|
||||
header.h.type = eSSU2PeerTest;
|
||||
header.h.flags[0] = 2; // ver
|
||||
header.h.flags[0] = GetVersion (); // ver
|
||||
header.h.flags[1] = (uint8_t)i2p::context.GetNetID (); // netID
|
||||
header.h.flags[2] = 0; // flag
|
||||
memcpy (h, header.buf, 16);
|
||||
@@ -278,7 +278,7 @@ namespace transport
|
||||
header.h.connID = GetDestConnID (); // dest id
|
||||
RAND_bytes (header.buf + 8, 4); // random packet num
|
||||
header.h.type = eSSU2HolePunch;
|
||||
header.h.flags[0] = 2; // ver
|
||||
header.h.flags[0] = GetVersion (); // ver
|
||||
header.h.flags[1] = (uint8_t)i2p::context.GetNetID (); // netID
|
||||
header.h.flags[2] = 0; // flag
|
||||
memcpy (h, header.buf, 16);
|
||||
|
||||
@@ -80,7 +80,7 @@ namespace transport
|
||||
}
|
||||
|
||||
SSU2Session::SSU2Session (SSU2Server& server, std::shared_ptr<const i2p::data::RouterInfo> in_RemoteRouter,
|
||||
std::shared_ptr<const i2p::data::RouterInfo::Address> addr, bool noise):
|
||||
std::shared_ptr<const i2p::data::RouterInfo::Address> addr, bool noise, uint8_t version):
|
||||
TransportSession (in_RemoteRouter, SSU2_CONNECT_TIMEOUT),
|
||||
m_Server (server), m_Address (addr), m_RemoteTransports (0), m_RemotePeerTestTransports (0),
|
||||
m_RemoteVersion (0), m_DestConnID (0), m_SourceConnID (0), m_State (eSSU2SessionStateUnknown),
|
||||
@@ -92,7 +92,8 @@ namespace transport
|
||||
m_RTO (SSU2_INITIAL_RTO), m_RelayTag (0),m_ConnectTimer (server.GetService ()),
|
||||
m_TerminationReason (eSSU2TerminationReasonNormalClose),
|
||||
m_MaxPayloadSize (SSU2_MIN_PACKET_SIZE - IPV6_HEADER_SIZE - UDP_HEADER_SIZE - 32), // min size
|
||||
m_LastResendTime (0), m_LastResendAttemptTime (0), m_NextRouterInfoResendTime(0), m_NumRanges (0)
|
||||
m_LastResendTime (0), m_LastResendAttemptTime (0), m_NextRouterInfoResendTime(0),
|
||||
m_NumRanges (0), m_Version (version)
|
||||
{
|
||||
if (noise)
|
||||
m_NoiseState.reset (new i2p::crypto::NoiseSymmetricState);
|
||||
@@ -100,7 +101,14 @@ namespace transport
|
||||
{
|
||||
// outgoing
|
||||
if (noise)
|
||||
{
|
||||
#if OPENSSL_PQ
|
||||
if (m_Version > 2)
|
||||
InitNoiseXKStateMLKEM1 (*m_NoiseState, (i2p::data::CryptoKeyType)(m_Version + 2), m_Address->s);
|
||||
else
|
||||
#endif
|
||||
InitNoiseXKState1 (*m_NoiseState, m_Address->s);
|
||||
}
|
||||
m_RemoteEndpoint = boost::asio::ip::udp::endpoint (m_Address->host, m_Address->port);
|
||||
m_RemoteTransports = in_RemoteRouter->GetCompatibleTransports (false);
|
||||
m_RemoteVersion = in_RemoteRouter->GetVersion ();
|
||||
@@ -130,7 +138,10 @@ namespace transport
|
||||
ScheduleConnectTimer ();
|
||||
auto token = m_Server.FindOutgoingToken (m_RemoteEndpoint);
|
||||
if (token)
|
||||
SendSessionRequest (token);
|
||||
{
|
||||
if (!SendSessionRequest (token))
|
||||
Terminate ();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_State = eSSU2SessionStateUnknown;
|
||||
@@ -184,7 +195,7 @@ namespace transport
|
||||
htobe32buf (payload + 4, nonce);
|
||||
htobe32buf (payload + 8, relayTag);
|
||||
htobe32buf (payload + 12, ts/1000);
|
||||
payload[16] = 2; // ver
|
||||
payload[16] = m_Version; // ver
|
||||
size_t asz = CreateEndpoint (payload + 18, m_MaxPayloadSize - 18, boost::asio::ip::udp::endpoint (localAddress->host, localAddress->port));
|
||||
if (!asz) return false;
|
||||
payload[17] = asz;
|
||||
@@ -293,6 +304,9 @@ namespace transport
|
||||
m_SentHandshakePacket.reset (nullptr);
|
||||
m_SessionConfirmedFragment.reset (nullptr);
|
||||
m_PathChallenge.reset (nullptr);
|
||||
#if OPENSSL_PQ
|
||||
m_PQKeys.reset (nullptr);
|
||||
#endif
|
||||
if (!m_IntermediateQueue.empty ())
|
||||
m_SendQueue.splice (m_SendQueue.end (), m_IntermediateQueue);
|
||||
for (auto& it: m_SendQueue)
|
||||
@@ -333,6 +347,9 @@ namespace transport
|
||||
m_NoiseState.reset (nullptr);
|
||||
m_SessionConfirmedFragment.reset (nullptr);
|
||||
m_SentHandshakePacket.reset (nullptr);
|
||||
#if OPENSSL_PQ
|
||||
m_PQKeys.reset (nullptr);
|
||||
#endif
|
||||
m_ConnectTimer.cancel ();
|
||||
SetTerminationTimeout (SSU2_TERMINATION_TIMEOUT);
|
||||
m_NextRouterInfoResendTime = i2p::util::GetMillisecondsSinceEpoch () +
|
||||
@@ -707,7 +724,7 @@ namespace transport
|
||||
return true;
|
||||
}
|
||||
|
||||
void SSU2Session::SendSessionRequest (uint64_t token)
|
||||
bool SSU2Session::SendSessionRequest (uint64_t token)
|
||||
{
|
||||
// we are Alice
|
||||
m_EphemeralKeys = i2p::transport::transports.GetNextX25519KeysPair ();
|
||||
@@ -722,17 +739,28 @@ namespace transport
|
||||
header.h.connID = m_DestConnID; // dest id
|
||||
RAND_bytes (header.buf + 8, 4); // random packet num
|
||||
header.h.type = eSSU2SessionRequest;
|
||||
header.h.flags[0] = 2; // ver
|
||||
header.h.flags[0] = m_Version; // ver
|
||||
header.h.flags[1] = (uint8_t)i2p::context.GetNetID (); // netID
|
||||
header.h.flags[2] = 0; // flag
|
||||
memcpy (headerX, &m_SourceConnID, 8); // source id
|
||||
memcpy (headerX + 8, &token, 8); // token
|
||||
memcpy (headerX + 16, m_EphemeralKeys->GetPublicKey (), 32); // X
|
||||
// payload
|
||||
payload[0] = eSSU2BlkDateTime;
|
||||
htobe16buf (payload + 1, 4);
|
||||
htobe32buf (payload + 3, (ts + 500)/1000);
|
||||
size_t payloadSize = 7;
|
||||
size_t payloadSize = 0, offset = 0;
|
||||
#if OPENSSL_PQ
|
||||
if (m_Version > 2)
|
||||
{
|
||||
i2p::data::CryptoKeyType cryptoType = (i2p::data::CryptoKeyType)(m_Version + 2);
|
||||
m_PQKeys = i2p::crypto::CreateMLKEMKeys (cryptoType);
|
||||
m_PQKeys->GenerateKeys ();
|
||||
offset = m_PQKeys->GetKeyLen () + 16;
|
||||
payloadSize += offset;
|
||||
}
|
||||
#endif
|
||||
payload[payloadSize] = eSSU2BlkDateTime;
|
||||
htobe16buf (payload + payloadSize + 1, 4);
|
||||
htobe32buf (payload + payloadSize + 3, (ts + 500)/1000);
|
||||
payloadSize += 7;
|
||||
if (GetRouterStatus () == eRouterStatusFirewalled && m_Address->IsIntroducer ())
|
||||
{
|
||||
if (!m_Server.IsMaxNumIntroducers (m_RemoteEndpoint.address ().is_v4 ()) ||
|
||||
@@ -744,19 +772,41 @@ namespace transport
|
||||
payloadSize += 3;
|
||||
}
|
||||
}
|
||||
payloadSize += CreatePaddingBlock (payload + payloadSize, 40 - payloadSize, 1);
|
||||
payloadSize += CreatePaddingBlock (payload + payloadSize, 40 + offset - payloadSize, 1);
|
||||
// KDF for session request
|
||||
#if OPENSSL_PQ
|
||||
if (m_Version > 2)
|
||||
m_NoiseState->MixHash (GetRemoteIdentity ()->GetIdentHash (), 32); // h = SHA256(h || bhash)
|
||||
#endif
|
||||
m_NoiseState->MixHash ({ {header.buf, 16}, {headerX, 16} }); // h = SHA256(h || header)
|
||||
m_NoiseState->MixHash (m_EphemeralKeys->GetPublicKey (), 32); // h = SHA256(h || aepk);
|
||||
m_NoiseState->MixHash (m_EphemeralKeys->GetPublicKey (), 32); // h = SHA256(h || aepk)
|
||||
uint8_t sharedSecret[32];
|
||||
m_EphemeralKeys->Agree (m_Address->s, sharedSecret);
|
||||
m_NoiseState->MixKey (sharedSecret);
|
||||
// encrypt
|
||||
const uint8_t nonce[12] = {0}; // always 0
|
||||
i2p::crypto::AEADChaCha20Poly1305 (payload, payloadSize, m_NoiseState->m_H, 32, m_NoiseState->m_CK + 32, nonce, payload, payloadSize + 16, true);
|
||||
#if OPENSSL_PQ
|
||||
if (m_PQKeys)
|
||||
{
|
||||
size_t keyLen = m_PQKeys->GetKeyLen ();
|
||||
std::vector<uint8_t> encapsKey(keyLen);
|
||||
m_PQKeys->GetPublicKey (encapsKey.data ());
|
||||
if (!m_NoiseState->Encrypt (encapsKey.data (), payload, keyLen))
|
||||
{
|
||||
LogPrint (eLogWarning, "SSU2: SessionRequest ML-KEM encap_key frame AEAD encryption failed ");
|
||||
return false;
|
||||
}
|
||||
m_NoiseState->MixHash (payload, keyLen + 16); // h = SHA256(h || ciphertext)
|
||||
}
|
||||
#endif
|
||||
if (!m_NoiseState->Encrypt (payload + offset, payload + offset, payloadSize - offset))
|
||||
{
|
||||
LogPrint (eLogWarning, "SSU2: SessionRequest payload encryption failed ");
|
||||
return false;
|
||||
}
|
||||
payloadSize += 16;
|
||||
header.ll[0] ^= CreateHeaderMask (m_Address->i, payload + (payloadSize - 24));
|
||||
header.ll[1] ^= CreateHeaderMask (m_Address->i, payload + (payloadSize - 12));
|
||||
const uint8_t nonce[12] = {0}; // always 0
|
||||
m_Server.ChaCha20 (headerX, 48, m_Address->i, nonce, headerX);
|
||||
m_NoiseState->MixHash (payload, payloadSize); // h = SHA256(h || encrypted payload from Session Request) for SessionCreated
|
||||
m_SentHandshakePacket->payloadSize = payloadSize;
|
||||
@@ -770,8 +820,9 @@ namespace transport
|
||||
else
|
||||
{
|
||||
LogPrint (eLogWarning, "SSU2: SessionRequest request to ", m_RemoteEndpoint, " already pending");
|
||||
Terminate ();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void SSU2Session::ProcessSessionRequest (Header& header, uint8_t * buf, size_t len)
|
||||
@@ -845,7 +896,7 @@ namespace transport
|
||||
header.h.connID = m_DestConnID; // dest id
|
||||
RAND_bytes (header.buf + 8, 4); // random packet num
|
||||
header.h.type = eSSU2SessionCreated;
|
||||
header.h.flags[0] = 2; // ver
|
||||
header.h.flags[0] = m_Version; // ver
|
||||
header.h.flags[1] = (uint8_t)i2p::context.GetNetID (); // netID
|
||||
header.h.flags[2] = 0; // flag
|
||||
memcpy (headerX, &m_SourceConnID, 8); // source id
|
||||
@@ -918,15 +969,42 @@ namespace transport
|
||||
uint8_t headerX[48];
|
||||
m_Server.ChaCha20 (buf + 16, 48, kh2, nonce, headerX);
|
||||
// KDF for SessionCreated
|
||||
#if OPENSSL_PQ
|
||||
if (m_Version > 2)
|
||||
m_NoiseState->MixHash (i2p::context.GetIdentHash (), 32); // h = SHA256(h || bhash)
|
||||
#endif
|
||||
m_NoiseState->MixHash ( { {header.buf, 16}, {headerX, 16} } ); // h = SHA256(h || header)
|
||||
m_NoiseState->MixHash (headerX + 16, 32); // h = SHA256(h || bepk);
|
||||
uint8_t sharedSecret[32];
|
||||
m_EphemeralKeys->Agree (headerX + 16, sharedSecret);
|
||||
m_NoiseState->MixKey (sharedSecret);
|
||||
size_t offset = 64;
|
||||
#if OPENSSL_PQ
|
||||
if (m_Version > 2 && m_PQKeys)
|
||||
{
|
||||
i2p::data::CryptoKeyType cryptoType = (i2p::data::CryptoKeyType)(m_Version + 2);
|
||||
size_t cipherTextLen = i2p::crypto::GetMLKEMCipherTextLen (cryptoType);
|
||||
std::vector<uint8_t> kemCiphertext(cipherTextLen);
|
||||
if (!m_NoiseState->Decrypt (buf + offset, kemCiphertext.data (), cipherTextLen))
|
||||
{
|
||||
LogPrint (eLogWarning, "SSU2: SessionCreated ML-KEM ciphertext section AEAD decryption failed");
|
||||
return false;
|
||||
}
|
||||
m_NoiseState->MixHash (buf + offset, cipherTextLen + 16);
|
||||
offset += cipherTextLen + 16;
|
||||
m_PQKeys->Decaps (kemCiphertext.data (), sharedSecret);
|
||||
m_NoiseState->MixKey (sharedSecret);
|
||||
}
|
||||
#endif
|
||||
// decrypt
|
||||
uint8_t * payload = buf + 64;
|
||||
std::vector<uint8_t> decryptedPayload(len - 80);
|
||||
if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len - 80, m_NoiseState->m_H, 32,
|
||||
if (offset + 16 > len)
|
||||
{
|
||||
LogPrint (eLogWarning, "SSU2: SessionCreated message is too short ", len);
|
||||
return false;
|
||||
}
|
||||
uint8_t * payload = buf + offset;
|
||||
std::vector<uint8_t> decryptedPayload(len - offset - 16);
|
||||
if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len - offset - 16, m_NoiseState->m_H, 32,
|
||||
m_NoiseState->m_CK + 32, nonce, decryptedPayload.data (), decryptedPayload.size (), false))
|
||||
{
|
||||
LogPrint (eLogWarning, "SSU2: SessionCreated AEAD verification failed ");
|
||||
@@ -934,7 +1012,7 @@ namespace transport
|
||||
i2p::data::netdb.SetUnreachable (GetRemoteIdentity ()->GetIdentHash (), true); // assume wrong s key
|
||||
return false;
|
||||
}
|
||||
m_NoiseState->MixHash (payload, len - 64); // h = SHA256(h || encrypted payload from SessionCreated) for SessionConfirmed
|
||||
m_NoiseState->MixHash (payload, len - offset); // h = SHA256(h || encrypted payload from SessionCreated) for SessionConfirmed
|
||||
// payload
|
||||
m_State = eSSU2SessionStateSessionCreatedReceived;
|
||||
HandlePayload (decryptedPayload.data (), decryptedPayload.size ());
|
||||
@@ -1436,9 +1514,14 @@ namespace transport
|
||||
LogPrint (eLogWarning, "SSU2: Retry token is zero");
|
||||
return false;
|
||||
}
|
||||
#if OPENSSL_PQ
|
||||
if (m_Version > 2)
|
||||
InitNoiseXKStateMLKEM1 (*m_NoiseState, (i2p::data::CryptoKeyType)(m_Version + 2), m_Address->s);
|
||||
else
|
||||
#endif
|
||||
InitNoiseXKState1 (*m_NoiseState, m_Address->s); // reset Noise TODO: check state
|
||||
SendSessionRequest (token);
|
||||
return true;
|
||||
|
||||
return SendSessionRequest (token);
|
||||
}
|
||||
|
||||
bool SSU2Session::ProcessHolePunch (uint8_t * buf, size_t len)
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <boost/asio.hpp>
|
||||
#include "version.h"
|
||||
#include "Crypto.h"
|
||||
#include "PostQuantum.h"
|
||||
#include "RouterInfo.h"
|
||||
#include "RouterContext.h"
|
||||
#include "TransportSession.h"
|
||||
@@ -243,7 +244,7 @@ namespace transport
|
||||
public:
|
||||
|
||||
SSU2Session (SSU2Server& server, std::shared_ptr<const i2p::data::RouterInfo> in_RemoteRouter = nullptr,
|
||||
std::shared_ptr<const i2p::data::RouterInfo::Address> addr = nullptr, bool noise = true);
|
||||
std::shared_ptr<const i2p::data::RouterInfo::Address> addr = nullptr, bool noise = true, uint8_t version = 2);
|
||||
virtual ~SSU2Session ();
|
||||
|
||||
void SetRemoteEndpoint (const boost::asio::ip::udp::endpoint& ep) { m_RemoteEndpoint = ep; };
|
||||
@@ -287,6 +288,7 @@ namespace transport
|
||||
protected:
|
||||
|
||||
SSU2Server& GetServer () { return m_Server; }
|
||||
uint8_t GetVersion () const { return m_Version; }
|
||||
RouterStatus GetRouterStatus () const;
|
||||
void SetRouterStatus (RouterStatus status) const;
|
||||
size_t GetMaxPayloadSize () const { return m_MaxPayloadSize; }
|
||||
@@ -321,7 +323,7 @@ namespace transport
|
||||
void ProcessSessionRequest (Header& header, uint8_t * buf, size_t len);
|
||||
void ProcessTokenRequest (Header& header, uint8_t * buf, size_t len);
|
||||
|
||||
void SendSessionRequest (uint64_t token = 0);
|
||||
bool SendSessionRequest (uint64_t token = 0);
|
||||
void SendSessionCreated (const uint8_t * X);
|
||||
void SendSessionConfirmed (const uint8_t * Y);
|
||||
void KDFDataPhase (uint8_t * keydata_ab, uint8_t * keydata_ba);
|
||||
@@ -372,6 +374,9 @@ namespace transport
|
||||
std::unique_ptr<i2p::crypto::NoiseSymmetricState> m_NoiseState;
|
||||
std::unique_ptr<HandshakePacket> m_SessionConfirmedFragment; // for Bob if applicable or second fragment for Alice
|
||||
std::unique_ptr<HandshakePacket> m_SentHandshakePacket; // SessionRequest, SessionCreated or SessionConfirmed
|
||||
#if OPENSSL_PQ
|
||||
std::unique_ptr<i2p::crypto::MLKEMKeys> m_PQKeys;
|
||||
#endif
|
||||
std::shared_ptr<const i2p::data::RouterInfo::Address> m_Address;
|
||||
boost::asio::ip::udp::endpoint m_RemoteEndpoint;
|
||||
i2p::data::RouterInfo::CompatibleTransports m_RemoteTransports, m_RemotePeerTestTransports;
|
||||
@@ -403,6 +408,7 @@ namespace transport
|
||||
uint64_t m_LastResendTime, m_LastResendAttemptTime, m_NextRouterInfoResendTime; // in milliseconds
|
||||
int m_NumRanges;
|
||||
uint8_t m_Ranges[SSU2_MAX_NUM_ACK_RANGES*2]; // ranges sent with previous Ack if any
|
||||
uint8_t m_Version;
|
||||
};
|
||||
|
||||
inline uint64_t CreateHeaderMask (const uint8_t * kh, const uint8_t * nonce)
|
||||
|
||||
Reference in New Issue
Block a user