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 }