/ libi2pd / TunnelConfig.cpp
TunnelConfig.cpp
  1  /*
  2  * Copyright (c) 2013-2025, The PurpleI2P Project
  3  *
  4  * This file is part of Purple i2pd project and licensed under BSD3
  5  *
  6  * See full license text in LICENSE file at top of project tree
  7  *
  8  */
  9  
 10  #include <memory>
 11  #include <openssl/rand.h>
 12  #include <openssl/sha.h>
 13  #include "Log.h"
 14  #include "Transports.h"
 15  #include "Timestamp.h"
 16  #include "I2PEndian.h"
 17  #include "I2NPProtocol.h"
 18  #include "TunnelConfig.h"
 19  
 20  namespace i2p
 21  {
 22  namespace tunnel
 23  {
 24  	TunnelHopConfig::TunnelHopConfig (std::shared_ptr<const i2p::data::IdentityEx> r)
 25  	{
 26  		RAND_bytes ((uint8_t *)&tunnelID, 4);
 27  		if (!tunnelID) tunnelID = 1; // tunnelID can't be zero
 28  		isGateway = true;
 29  		isEndpoint = true;
 30  		ident = r;
 31  		//nextRouter = nullptr;
 32  		nextTunnelID = 0;
 33  
 34  		next = nullptr;
 35  		prev = nullptr;
 36  	}
 37  
 38  	void TunnelHopConfig::SetNextIdent (const i2p::data::IdentHash& ident)
 39  	{
 40  		nextIdent = ident;
 41  		isEndpoint = false;
 42  		RAND_bytes ((uint8_t *)&nextTunnelID, 4);
 43  		if (!nextTunnelID) nextTunnelID = 1; // tunnelID can't be zero
 44  	}
 45  
 46  	void TunnelHopConfig::SetReplyHop (uint32_t replyTunnelID, const i2p::data::IdentHash& replyIdent)
 47  	{
 48  		nextIdent = replyIdent;
 49  		nextTunnelID = replyTunnelID;
 50  		isEndpoint = true;
 51  	}
 52  
 53  	void TunnelHopConfig::SetNext (TunnelHopConfig * n)
 54  	{
 55  		next = n;
 56  		if (next)
 57  		{
 58  			next->prev = this;
 59  			next->isGateway = false;
 60  			isEndpoint = false;
 61  			nextIdent = next->ident->GetIdentHash ();
 62  			nextTunnelID = next->tunnelID;
 63  		}
 64  	}
 65  
 66  	void TunnelHopConfig::SetPrev (TunnelHopConfig * p)
 67  	{
 68  		prev = p;
 69  		if (prev)
 70  		{
 71  			prev->next = this;
 72  			prev->isEndpoint = false;
 73  			isGateway = false;
 74  		}
 75  	}
 76  
 77  	void TunnelHopConfig::DecryptRecord (uint8_t * records, int index) const
 78  	{
 79  		uint8_t * record = records + index*TUNNEL_BUILD_RECORD_SIZE;
 80  		i2p::crypto::CBCDecryption decryption;
 81  		decryption.SetKey (replyKey);
 82  		decryption.Decrypt(record, TUNNEL_BUILD_RECORD_SIZE, replyIV, record);
 83  	}
 84  
 85  	void ECIESTunnelHopConfig::EncryptECIES (const uint8_t * plainText, size_t len, uint8_t * encrypted)
 86  	{
 87  		if (!ident) return;
 88  		i2p::crypto::InitNoiseNState (*this, ident->GetEncryptionPublicKey ());
 89  		auto ephemeralKeys = i2p::transport::transports.GetNextX25519KeysPair ();
 90  		memcpy (encrypted, ephemeralKeys->GetPublicKey (), 32);
 91  		MixHash (encrypted, 32); // h = SHA256(h || sepk)
 92  		encrypted += 32;
 93  		uint8_t sharedSecret[32];
 94  		ephemeralKeys->Agree (ident->GetEncryptionPublicKey (), sharedSecret); // x25519(sesk, hepk)
 95  		MixKey (sharedSecret);
 96  		uint8_t nonce[12];
 97  		memset (nonce, 0, 12);
 98  		if (!i2p::crypto::AEADChaCha20Poly1305 (plainText, len, m_H, 32, m_CK + 32, nonce, encrypted, len + 16, true)) // encrypt
 99  		{
100  			LogPrint (eLogWarning, "Tunnel: Plaintext AEAD encryption failed");
101  			return;
102  		}
103  		MixHash (encrypted, len + 16); // h = SHA256(h || ciphertext)
104  	}
105  
106  	bool ECIESTunnelHopConfig::DecryptECIES (const uint8_t * key, const uint8_t * nonce, const uint8_t * encrypted, size_t len, uint8_t * clearText) const
107  	{
108  		return i2p::crypto::AEADChaCha20Poly1305 (encrypted, len - 16, m_H, 32, key, nonce, clearText, len - 16, false); // decrypt
109  	}
110  
111  	void LongECIESTunnelHopConfig::CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID)
112  	{
113  		// generate keys
114  		RAND_bytes (layerKey, 32);
115  		RAND_bytes (ivKey, 32);
116  		RAND_bytes (replyKey, 32);
117  		RAND_bytes (replyIV, 16);
118  		// fill clear text
119  		uint8_t flag = 0;
120  		if (isGateway) flag |= TUNNEL_BUILD_RECORD_GATEWAY_FLAG;
121  		if (isEndpoint) flag |= TUNNEL_BUILD_RECORD_ENDPOINT_FLAG;
122  		uint8_t clearText[ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE];
123  		htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET, tunnelID);
124  		htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET, nextTunnelID);
125  		memcpy (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, nextIdent, 32);
126  		memcpy (clearText + ECIES_BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET, layerKey, 32);
127  		memcpy (clearText + ECIES_BUILD_REQUEST_RECORD_IV_KEY_OFFSET, ivKey, 32);
128  		memcpy (clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET, replyKey, 32);
129  		memcpy (clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_IV_OFFSET, replyIV, 16);
130  		clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] = flag;
131  		memset (clearText + ECIES_BUILD_REQUEST_RECORD_MORE_FLAGS_OFFSET, 0, 3); // set to 0 for compatibility
132  		htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_REQUEST_TIME_OFFSET, i2p::util::GetMinutesSinceEpoch ());
133  		htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_REQUEST_EXPIRATION_OFFSET, 600); // +10 minutes
134  		htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET, replyMsgID);
135  		memset (clearText + ECIES_BUILD_REQUEST_RECORD_PADDING_OFFSET, 0, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE - ECIES_BUILD_REQUEST_RECORD_PADDING_OFFSET);
136  		// encrypt
137  		uint8_t * record = records + recordIndex*TUNNEL_BUILD_RECORD_SIZE;
138  		EncryptECIES (clearText, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE, record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET);
139  		memcpy (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)ident->GetIdentHash (), 16);
140  	}
141  
142  	bool LongECIESTunnelHopConfig::DecryptBuildResponseRecord (uint8_t * records) const
143  	{
144  		uint8_t * record = records + recordIndex*TUNNEL_BUILD_RECORD_SIZE;
145  		uint8_t nonce[12];
146  		memset (nonce, 0, 12);
147  		if (!DecryptECIES (m_CK, nonce, record, TUNNEL_BUILD_RECORD_SIZE, record))
148  		{
149  			LogPrint (eLogWarning, "Tunnel: Response AEAD decryption failed");
150  			return false;
151  		}
152  		return true;
153  	}
154  
155  	void ShortECIESTunnelHopConfig::CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID)
156  	{
157  		// fill clear text
158  		uint8_t flag = 0;
159  		if (isGateway) flag |= TUNNEL_BUILD_RECORD_GATEWAY_FLAG;
160  		if (isEndpoint) flag |= TUNNEL_BUILD_RECORD_ENDPOINT_FLAG;
161  		uint8_t clearText[SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE ];
162  		htobe32buf (clearText + SHORT_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET, tunnelID);
163  		htobe32buf (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET, nextTunnelID);
164  		memcpy (clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, nextIdent, 32);
165  		clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] = flag;
166  		memset (clearText + SHORT_REQUEST_RECORD_MORE_FLAGS_OFFSET, 0, 2);
167  		clearText[SHORT_REQUEST_RECORD_LAYER_ENCRYPTION_TYPE] = 0; // AES
168  		htobe32buf (clearText + SHORT_REQUEST_RECORD_REQUEST_TIME_OFFSET, i2p::util::GetMinutesSinceEpoch ());
169  		htobe32buf (clearText + SHORT_REQUEST_RECORD_REQUEST_EXPIRATION_OFFSET , 600); // +10 minutes
170  		htobe32buf (clearText + SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET, replyMsgID);
171  		memset (clearText + SHORT_REQUEST_RECORD_PADDING_OFFSET, 0, SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE - SHORT_REQUEST_RECORD_PADDING_OFFSET);
172  		// encrypt
173  		uint8_t * record = records + recordIndex*SHORT_TUNNEL_BUILD_RECORD_SIZE;
174  		EncryptECIES (clearText, SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE, record + SHORT_REQUEST_RECORD_ENCRYPTED_OFFSET);
175  		// derive keys
176  		i2p::crypto::HKDF (m_CK, nullptr, 0, "SMTunnelReplyKey", m_CK);
177  		memcpy (replyKey, m_CK + 32, 32);
178  		i2p::crypto::HKDF (m_CK, nullptr, 0, "SMTunnelLayerKey", m_CK);
179  		memcpy (layerKey, m_CK + 32, 32);
180  		if (isEndpoint)
181  		{
182  			i2p::crypto::HKDF (m_CK, nullptr, 0, "TunnelLayerIVKey", m_CK);
183  			memcpy (ivKey, m_CK + 32, 32);
184  			i2p::crypto::HKDF (m_CK, nullptr, 0, "RGarlicKeyAndTag", m_CK); // OTBRM garlic key m_CK + 32, tag first 8 bytes of m_CK
185  		}
186  		else
187  			memcpy (ivKey, m_CK, 32); // last HKDF
188  		memcpy (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)ident->GetIdentHash (), 16);
189  	}
190  
191  	bool ShortECIESTunnelHopConfig::DecryptBuildResponseRecord (uint8_t * records) const
192  	{
193  		uint8_t * record = records + recordIndex*SHORT_TUNNEL_BUILD_RECORD_SIZE;
194  		uint8_t nonce[12];
195  		memset (nonce, 0, 12);
196  		nonce[4] = recordIndex; // nonce is record index
197  		if (!DecryptECIES (replyKey, nonce, record, SHORT_TUNNEL_BUILD_RECORD_SIZE, record))
198  		{
199  			LogPrint (eLogWarning, "Tunnel: Response AEAD decryption failed");
200  			return false;
201  		}
202  		return true;
203  	}
204  
205  	void ShortECIESTunnelHopConfig::DecryptRecord (uint8_t * records, int index) const
206  	{
207  		uint8_t * record = records + index*SHORT_TUNNEL_BUILD_RECORD_SIZE;
208  		uint8_t nonce[12];
209  		memset (nonce, 0, 12);
210  		nonce[4] = index; // nonce is index
211  		i2p::crypto::ChaCha20 (record, SHORT_TUNNEL_BUILD_RECORD_SIZE, replyKey, nonce, record);
212  	}
213  
214  	uint64_t ShortECIESTunnelHopConfig::GetGarlicKey (uint8_t * key) const
215  	{
216  		uint64_t tag;
217  		memcpy (&tag, m_CK, 8);
218  		memcpy (key, m_CK + 32, 32);
219  		return tag;
220  	}
221  
222  	void LongPhonyTunnelHopConfig::CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID)
223  	{
224  		uint8_t * record = records + recordIndex*TUNNEL_BUILD_RECORD_SIZE;
225  		memcpy (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)i2p::context.GetIdentHash (), 16);
226  		memcpy (record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, i2p::transport::transports.GetNextX25519KeysPair ()->GetPublicKey (), 32);
227  		RAND_bytes (record + 48, TUNNEL_BUILD_RECORD_SIZE - 48);
228  	}
229  
230  	void ShortPhonyTunnelHopConfig::CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID)
231  	{
232  		uint8_t * record = records + recordIndex*SHORT_TUNNEL_BUILD_RECORD_SIZE;
233  		memcpy (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)i2p::context.GetIdentHash (), 16);
234  		memcpy (record + SHORT_REQUEST_RECORD_ENCRYPTED_OFFSET, i2p::transport::transports.GetNextX25519KeysPair ()->GetPublicKey (), 32);
235  		RAND_bytes (record + 48, SHORT_TUNNEL_BUILD_RECORD_SIZE - 48);
236  	}
237  
238  	TunnelConfig::TunnelConfig (const std::vector<std::shared_ptr<const i2p::data::IdentityEx> >& peers,
239  		bool isShort, i2p::data::RouterInfo::CompatibleTransports farEndTransports):
240  		m_IsShort (isShort), m_FarEndTransports (farEndTransports)
241  	{
242  		// inbound
243  		CreatePeers (peers);
244  		m_LastHop->SetNextIdent (i2p::context.GetIdentHash ());
245  	}
246  
247  	TunnelConfig::TunnelConfig (const std::vector<std::shared_ptr<const i2p::data::IdentityEx> >& peers,
248  		uint32_t replyTunnelID, const i2p::data::IdentHash& replyIdent, bool isShort,
249  		i2p::data::RouterInfo::CompatibleTransports farEndTransports):
250  		m_IsShort (isShort), m_FarEndTransports (farEndTransports)
251  	{
252  		// outbound
253  		CreatePeers (peers);
254  		m_FirstHop->isGateway = false;
255  		m_LastHop->SetReplyHop (replyTunnelID, replyIdent);
256  	}
257  	
258  	void TunnelConfig::CreatePeers (const std::vector<std::shared_ptr<const i2p::data::IdentityEx> >& peers)
259  	{
260  		TunnelHopConfig * prev = nullptr;
261  		for (const auto& it: peers)
262  		{
263  			TunnelHopConfig * hop = nullptr;
264  			if (m_IsShort)
265  				hop = new ShortECIESTunnelHopConfig (it);
266  			else
267  			{
268  				if (it->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)
269  					hop = new LongECIESTunnelHopConfig (it);
270  				else
271  					LogPrint (eLogError, "Tunnel: ElGamal router is not supported");
272  			}
273  			if (hop)
274  			{
275  				if (prev)
276  					prev->SetNext (hop);
277  				else
278  					m_FirstHop = hop;
279  				prev = hop;
280  			}
281  		}
282  		m_LastHop = prev;
283  	}
284  
285  	void TunnelConfig::CreatePhonyHop ()
286  	{
287  		if (m_LastHop && m_LastHop->ident)
288  		{	
289  			TunnelHopConfig * hop = nullptr;
290  			if (m_IsShort)
291  				hop = new ShortPhonyTunnelHopConfig ();
292  			else
293  				hop = new LongPhonyTunnelHopConfig ();
294  			if (hop)
295  			{	
296  				hop->prev = m_LastHop;
297  				m_LastHop->next = hop;
298  				m_LastHop = hop;
299  			}	
300  		}	
301  	}	
302  
303  	void TunnelConfig::DeletePhonyHop ()
304  	{
305  		if (m_LastHop && !m_LastHop->ident)
306  		{
307  			if (m_LastHop->prev) m_LastHop->prev->next = nullptr;
308  			else m_FirstHop = nullptr;
309  			auto tmp = m_LastHop;
310  			m_LastHop = m_LastHop->prev;
311  			delete tmp;
312  		}	
313  	}	
314  }
315  }