1// 2// This file is part of the aMule Project. 3// 4// Copyright (c) 2003-2011 aMule Team ( admin@amule.org / http://www.amule.org ) 5// Copyright (c) 2002-2011 Merkur ( devs@emule-project.net / http://www.emule-project.net ) 6// 7// Any parts of this program derived from the xMule, lMule or eMule project, 8// or contributed by third-party developers are copyrighted by their 9// respective authors. 10// 11// This program is free software; you can redistribute it and/or modify 12// it under the terms of the GNU General Public License as published by 13// the Free Software Foundation; either version 2 of the License, or 14// (at your option) any later version. 15// 16// This program is distributed in the hope that it will be useful, 17// but WITHOUT ANY WARRANTY; without even the implied warranty of 18// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19// GNU General Public License for more details. 20// 21// You should have received a copy of the GNU General Public License 22// along with this program; if not, write to the Free Software 23// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA 24// 25 26#include "ClientCreditsList.h" // Interface declarations 27 28 29#include <protocol/ed2k/Constants.h> 30#include <common/Macros.h> 31#include <common/DataFileVersion.h> 32#include <common/FileFunctions.h> // Needed for GetFileSize 33 34 35#include "GetTickCount.h" // Needed for GetTickCount 36#include "Preferences.h" // Needed for thePrefs 37#include "ClientCredits.h" // Needed for CClientCredits 38#include "amule.h" // Needed for theApp 39#include "CFile.h" // Needed for CFile 40#include "Logger.h" // Needed for Add(Debug)LogLine 41#include "CryptoPP_Inc.h" // Needed for Crypto functions 42 43 44#define CLIENTS_MET_FILENAME wxT("clients.met") 45#define CLIENTS_MET_BAK_FILENAME wxT("clients.met.bak") 46#define CRYPTKEY_FILENAME wxT("cryptkey.dat") 47 48 49CClientCreditsList::CClientCreditsList() 50{ 51 m_nLastSaved = ::GetTickCount(); 52 LoadList(); 53 54 InitalizeCrypting(); 55} 56 57 58CClientCreditsList::~CClientCreditsList() 59{ 60 DeleteContents(m_mapClients); 61 delete static_cast<CryptoPP::RSASSA_PKCS1v15_SHA_Signer *>(m_pSignkey); 62} 63 64 65void CClientCreditsList::LoadList() 66{ 67 CFile file; 68 CPath fileName = CPath(theApp->ConfigDir + CLIENTS_MET_FILENAME); 69 70 if (!fileName.FileExists()) { 71 return; 72 } 73 74 try { 75 file.Open(fileName, CFile::read); 76 77 if (file.ReadUInt8() != CREDITFILE_VERSION) { 78 AddDebugLogLineC( logCredits, wxT("Creditfile is outdated and will be replaced") ); 79 file.Close(); 80 return; 81 } 82 83 // everything is ok, lets see if the backup exist... 84 CPath bakFileName = CPath(theApp->ConfigDir + CLIENTS_MET_BAK_FILENAME); 85 86 bool bCreateBackup = TRUE; 87 if (bakFileName.FileExists()) { 88 // Ok, the backup exist, get the size 89 CFile hBakFile(bakFileName); 90 if ( hBakFile.GetLength() > file.GetLength()) { 91 // the size of the backup was larger then the 92 // org. file, something is wrong here, don't 93 // overwrite old backup.. 94 bCreateBackup = FALSE; 95 } 96 // else: backup is smaller or the same size as org. 97 // file, proceed with copying of file 98 } 99 100 //else: the backup doesn't exist, create it 101 if (bCreateBackup) { 102 file.Close(); // close the file before copying 103 if (!CPath::CloneFile(fileName, bakFileName, true)) { 104 AddDebugLogLineC(logCredits, 105 CFormat(wxT("Could not create backup file '%s'")) % fileName); 106 } 107 // reopen file 108 if (!file.Open(fileName, CFile::read)) { 109 AddDebugLogLineC( logCredits, 110 wxT("Failed to load creditfile") ); 111 return; 112 } 113 114 file.Seek(1); 115 } 116 117 118 uint32 count = file.ReadUInt32(); 119 120 const uint32 dwExpired = time(NULL) - 12960000; // today - 150 day 121 uint32 cDeleted = 0; 122 for (uint32 i = 0; i < count; i++){ 123 CreditStruct* newcstruct = new CreditStruct(); 124 125 newcstruct->key = file.ReadHash(); 126 newcstruct->uploaded = file.ReadUInt32(); 127 newcstruct->downloaded = file.ReadUInt32(); 128 newcstruct->nLastSeen = file.ReadUInt32(); 129 newcstruct->uploaded += static_cast<uint64>(file.ReadUInt32()) << 32; 130 newcstruct->downloaded += static_cast<uint64>(file.ReadUInt32()) << 32; 131 newcstruct->nReserved3 = file.ReadUInt16(); 132 newcstruct->nKeySize = file.ReadUInt8(); 133 file.Read(newcstruct->abySecureIdent, MAXPUBKEYSIZE); 134 135 if ( newcstruct->nKeySize > MAXPUBKEYSIZE ) { 136 // Oh dear, this is bad mojo, the file is most likely corrupt 137 // We can no longer assume that any of the clients in the file are valid 138 // and will have to discard it. 139 delete newcstruct; 140 141 DeleteContents(m_mapClients); 142 143 AddDebugLogLineC( logCredits, 144 wxT("WARNING: Corruptions found while reading Creditfile!") ); 145 return; 146 } 147 148 if (newcstruct->nLastSeen < dwExpired){ 149 cDeleted++; 150 delete newcstruct; 151 continue; 152 } 153 154 CClientCredits* newcredits = new CClientCredits(newcstruct); 155 m_mapClients[newcredits->GetKey()] = newcredits; 156 } 157 158 AddLogLineN(CFormat(wxPLURAL("Creditfile loaded, %u client is known", "Creditfile loaded, %u clients are known", count - cDeleted)) % (count - cDeleted)); 159 160 if (cDeleted) { 161 AddLogLineN(CFormat(wxPLURAL(" - Credits expired for %u client!", " - Credits expired for %u clients!", cDeleted)) % cDeleted); 162 } 163 } catch (const CSafeIOException& e) { 164 AddDebugLogLineC(logCredits, wxT("IO error while loading clients.met file: ") + e.what()); 165 } 166} 167 168 169void CClientCreditsList::SaveList() 170{ 171 AddDebugLogLineN( logCredits, wxT("Saved Credit list")); 172 m_nLastSaved = ::GetTickCount(); 173 174 wxString name(theApp->ConfigDir + CLIENTS_MET_FILENAME); 175 CFile file; 176 177 if ( !file.Create(name, true) ) { 178 AddDebugLogLineC( logCredits, wxT("Failed to create creditfile") ); 179 return; 180 } 181 182 if ( file.Open(name, CFile::write) ) { 183 try { 184 uint32 count = 0; 185 186 file.WriteUInt8( CREDITFILE_VERSION ); 187 // Temporary place-holder for number of stucts 188 file.WriteUInt32( 0 ); 189 190 ClientMap::iterator it = m_mapClients.begin(); 191 for ( ; it != m_mapClients.end(); ++it ) { 192 CClientCredits* cur_credit = it->second; 193 194 if ( cur_credit->GetUploadedTotal() || cur_credit->GetDownloadedTotal() ) { 195 const CreditStruct* const cstruct = cur_credit->GetDataStruct(); 196 file.WriteHash(cstruct->key); 197 file.WriteUInt32(static_cast<uint32>(cstruct->uploaded)); 198 file.WriteUInt32(static_cast<uint32>(cstruct->downloaded)); 199 file.WriteUInt32(cstruct->nLastSeen); 200 file.WriteUInt32(static_cast<uint32>(cstruct->uploaded >> 32)); 201 file.WriteUInt32(static_cast<uint32>(cstruct->downloaded >> 32)); 202 file.WriteUInt16(cstruct->nReserved3); 203 file.WriteUInt8(cstruct->nKeySize); 204 // Doesn't matter if this saves garbage, will be fixed on load. 205 file.Write(cstruct->abySecureIdent, MAXPUBKEYSIZE); 206 count++; 207 } 208 } 209 210 // Write the actual number of structs 211 file.Seek( 1 ); 212 file.WriteUInt32( count ); 213 } catch (const CIOFailureException& e) { 214 AddDebugLogLineC(logCredits, wxT("IO failure while saving clients.met: ") + e.what()); 215 } 216 } else { 217 AddDebugLogLineC(logCredits, wxT("Failed to open existing creditfile!")); 218 } 219} 220 221 222CClientCredits* CClientCreditsList::GetCredit(const CMD4Hash& key) 223{ 224 CClientCredits* result; 225 226 ClientMap::iterator it = m_mapClients.find( key ); 227 228 229 if ( it == m_mapClients.end() ){ 230 result = new CClientCredits(key); 231 m_mapClients[result->GetKey()] = result; 232 } else { 233 result = it->second; 234 } 235 236 result->SetLastSeen(); 237 238 return result; 239} 240 241 242void CClientCreditsList::Process() 243{ 244 if (::GetTickCount() - m_nLastSaved > MIN2MS(13)) 245 SaveList(); 246} 247 248 249bool CClientCreditsList::CreateKeyPair() 250{ 251 try { 252 CryptoPP::AutoSeededX917RNG<CryptoPP::DES_EDE3> rng; 253 CryptoPP::InvertibleRSAFunction privkey; 254 privkey.Initialize(rng, RSAKEYSIZE); 255 256 // Nothing we can do against this filename2char :/ 257 wxCharBuffer filename = filename2char(theApp->ConfigDir + CRYPTKEY_FILENAME); 258 CryptoPP::FileSink *fileSink = new CryptoPP::FileSink(filename); 259 CryptoPP::Base64Encoder *privkeysink = new CryptoPP::Base64Encoder(fileSink); 260 privkey.DEREncode(*privkeysink); 261 privkeysink->MessageEnd(); 262 263 // Do not delete these pointers or it will blow in your face. 264 // cryptopp semantics is giving ownership of these objects. 265 // 266 // delete privkeysink; 267 // delete fileSink; 268 269 AddDebugLogLineN(logCredits, wxT("Created new RSA keypair")); 270 } catch(const CryptoPP::Exception& e) { 271 AddDebugLogLineC(logCredits, 272 wxString(wxT("Failed to create new RSA keypair: ")) + 273 wxString(char2unicode(e.what()))); 274 wxFAIL; 275 return false; 276 } 277 278 return true; 279} 280 281 282void CClientCreditsList::InitalizeCrypting() 283{ 284 m_nMyPublicKeyLen = 0; 285 memset(m_abyMyPublicKey,0,80); // not really needed; better for debugging tho 286 m_pSignkey = NULL; 287 288 if (!thePrefs::IsSecureIdentEnabled()) { 289 return; 290 } 291 292 try { 293 // check if keyfile is there 294 if (wxFileExists(theApp->ConfigDir + CRYPTKEY_FILENAME)) { 295 off_t keySize = CPath::GetFileSize(theApp->ConfigDir + CRYPTKEY_FILENAME); 296 297 if (keySize == wxInvalidOffset) { 298 AddDebugLogLineC(logCredits, wxT("Cannot access 'cryptkey.dat', please check permissions.")); 299 return; 300 } else if (keySize == 0) { 301 AddDebugLogLineC(logCredits, wxT("'cryptkey.dat' is empty, recreating keypair.")); 302 CreateKeyPair(); 303 } 304 } else { 305 AddLogLineN(_("No 'cryptkey.dat' file found, creating.") ); 306 CreateKeyPair(); 307 } 308 309 // load private key 310 CryptoPP::FileSource filesource(filename2char(theApp->ConfigDir + CRYPTKEY_FILENAME), true, new CryptoPP::Base64Decoder); 311 m_pSignkey = new CryptoPP::RSASSA_PKCS1v15_SHA_Signer(filesource); 312 // calculate and store public key 313 CryptoPP::RSASSA_PKCS1v15_SHA_Verifier pubkey(*static_cast<CryptoPP::RSASSA_PKCS1v15_SHA_Signer *>(m_pSignkey)); 314 CryptoPP::ArraySink asink(m_abyMyPublicKey, 80); 315 pubkey.DEREncode(asink); 316 m_nMyPublicKeyLen = asink.TotalPutLength(); 317 asink.MessageEnd(); 318 } catch (const CryptoPP::Exception& e) { 319 delete static_cast<CryptoPP::RSASSA_PKCS1v15_SHA_Signer *>(m_pSignkey); 320 m_pSignkey = NULL; 321 322 AddDebugLogLineC(logCredits, 323 wxString(wxT("Error while initializing encryption keys: ")) + 324 wxString(char2unicode(e.what()))); 325 } 326} 327 328 329uint8 CClientCreditsList::CreateSignature(CClientCredits* pTarget, byte* pachOutput, uint8 nMaxSize, uint32 ChallengeIP, uint8 byChaIPKind, void* sigkey) 330{ 331 CryptoPP::RSASSA_PKCS1v15_SHA_Signer* signer = 332 static_cast<CryptoPP::RSASSA_PKCS1v15_SHA_Signer *>(sigkey); 333 // signer param is used for debug only 334 if (signer == NULL) 335 signer = static_cast<CryptoPP::RSASSA_PKCS1v15_SHA_Signer *>(m_pSignkey); 336 337 // create a signature of the public key from pTarget 338 wxASSERT( pTarget ); 339 wxASSERT( pachOutput ); 340 341 if ( !CryptoAvailable() ) { 342 return 0; 343 } 344 345 try { 346 CryptoPP::SecByteBlock sbbSignature(signer->SignatureLength()); 347 CryptoPP::AutoSeededX917RNG<CryptoPP::DES_EDE3> rng; 348 byte abyBuffer[MAXPUBKEYSIZE+9]; 349 uint32 keylen = pTarget->GetSecIDKeyLen(); 350 memcpy(abyBuffer,pTarget->GetSecureIdent(),keylen); 351 // 4 additional bytes random data send from this client 352 uint32 challenge = pTarget->m_dwCryptRndChallengeFrom; 353 wxASSERT ( challenge != 0 ); 354 PokeUInt32(abyBuffer+keylen,challenge); 355 356 uint16 ChIpLen = 0; 357 if ( byChaIPKind != 0){ 358 ChIpLen = 5; 359 PokeUInt32(abyBuffer+keylen+4, ChallengeIP); 360 PokeUInt8(abyBuffer+keylen+4+4,byChaIPKind); 361 } 362 signer->SignMessage(rng, abyBuffer ,keylen+4+ChIpLen , sbbSignature.begin()); 363 CryptoPP::ArraySink asink(pachOutput, nMaxSize); 364 asink.Put(sbbSignature.begin(), sbbSignature.size()); 365 366 return asink.TotalPutLength(); 367 } catch (const CryptoPP::Exception& e) { 368 AddDebugLogLineC(logCredits, wxString(wxT("Error while creating signature: ")) + wxString(char2unicode(e.what()))); 369 wxFAIL; 370 371 return 0; 372 } 373} 374 375 376bool CClientCreditsList::VerifyIdent(CClientCredits* pTarget, const byte* pachSignature, uint8 nInputSize, uint32 dwForIP, uint8 byChaIPKind) 377{ 378 wxASSERT( pTarget ); 379 wxASSERT( pachSignature ); 380 if ( !CryptoAvailable() ){ 381 pTarget->SetIdentState(IS_NOTAVAILABLE); 382 return false; 383 } 384 bool bResult; 385 try { 386 CryptoPP::StringSource ss_Pubkey((byte*)pTarget->GetSecureIdent(),pTarget->GetSecIDKeyLen(),true,0); 387 CryptoPP::RSASSA_PKCS1v15_SHA_Verifier pubkey(ss_Pubkey); 388 // 4 additional bytes random data send from this client +5 bytes v2 389 byte abyBuffer[MAXPUBKEYSIZE+9]; 390 memcpy(abyBuffer,m_abyMyPublicKey,m_nMyPublicKeyLen); 391 uint32 challenge = pTarget->m_dwCryptRndChallengeFor; 392 wxASSERT ( challenge != 0 ); 393 PokeUInt32(abyBuffer+m_nMyPublicKeyLen, challenge); 394 395 // v2 security improvments (not supported by 29b, not used as default by 29c) 396 uint8 nChIpSize = 0; 397 if (byChaIPKind != 0){ 398 nChIpSize = 5; 399 uint32 ChallengeIP = 0; 400 switch (byChaIPKind) { 401 case CRYPT_CIP_LOCALCLIENT: 402 ChallengeIP = dwForIP; 403 break; 404 case CRYPT_CIP_REMOTECLIENT: 405 // Ignore local ip... 406 if (!theApp->GetPublicIP(true)) { 407 if (::IsLowID(theApp->GetED2KID())){ 408 AddDebugLogLineN(logCredits, wxT("Warning: Maybe SecureHash Ident fails because LocalIP is unknown")); 409 // Fallback to local ip... 410 ChallengeIP = theApp->GetPublicIP(); 411 } else { 412 ChallengeIP = theApp->GetED2KID(); 413 } 414 } else { 415 ChallengeIP = theApp->GetPublicIP(); 416 } 417 break; 418 case CRYPT_CIP_NONECLIENT: // maybe not supported in future versions 419 ChallengeIP = 0; 420 break; 421 } 422 PokeUInt32(abyBuffer+m_nMyPublicKeyLen+4, ChallengeIP); 423 PokeUInt8(abyBuffer+m_nMyPublicKeyLen+4+4, byChaIPKind); 424 } 425 //v2 end 426 427 bResult = pubkey.VerifyMessage(abyBuffer, m_nMyPublicKeyLen+4+nChIpSize, pachSignature, nInputSize); 428 } catch (const CryptoPP::Exception& e) { 429 AddDebugLogLineC(logCredits, wxString(wxT("Error while verifying identity: ")) + wxString(char2unicode(e.what()))); 430 bResult = false; 431 } 432 433 if (!bResult){ 434 if (pTarget->GetIdentState() == IS_IDNEEDED) 435 pTarget->SetIdentState(IS_IDFAILED); 436 } else { 437 pTarget->Verified(dwForIP); 438 } 439 440 return bResult; 441} 442 443 444bool CClientCreditsList::CryptoAvailable() const 445{ 446 return m_nMyPublicKeyLen > 0 && m_pSignkey != NULL; 447} 448 449 450#ifdef _DEBUG 451bool CClientCreditsList::Debug_CheckCrypting(){ 452 // create random key 453 CryptoPP::AutoSeededX917RNG<CryptoPP::DES_EDE3> rng; 454 455 CryptoPP::RSASSA_PKCS1v15_SHA_Signer priv(rng, 384); 456 CryptoPP::RSASSA_PKCS1v15_SHA_Verifier pub(priv); 457 458 byte abyPublicKey[80]; 459 CryptoPP::ArraySink asink(abyPublicKey, 80); 460 pub.DEREncode(asink); 461 int8 PublicKeyLen = asink.TotalPutLength(); 462 asink.MessageEnd(); 463 uint32 challenge = rand(); 464 // create fake client which pretends to be this emule 465 CreditStruct* newcstruct = new CreditStruct(); 466 CClientCredits newcredits(newcstruct); 467 newcredits.SetSecureIdent(m_abyMyPublicKey,m_nMyPublicKeyLen); 468 newcredits.m_dwCryptRndChallengeFrom = challenge; 469 // create signature with fake priv key 470 byte pachSignature[200]; 471 memset(pachSignature,0,200); 472 uint8 sigsize = CreateSignature(&newcredits,pachSignature,200,0,false, &priv); 473 474 475 // next fake client uses the random created public key 476 CreditStruct* newcstruct2 = new CreditStruct(); 477 CClientCredits newcredits2(newcstruct2); 478 newcredits2.m_dwCryptRndChallengeFor = challenge; 479 480 // if you uncomment one of the following lines the check has to fail 481 //abyPublicKey[5] = 34; 482 //m_abyMyPublicKey[5] = 22; 483 //pachSignature[5] = 232; 484 485 newcredits2.SetSecureIdent(abyPublicKey,PublicKeyLen); 486 487 //now verify this signature - if it's true everything is fine 488 return VerifyIdent(&newcredits2,pachSignature,sigsize,0,0); 489} 490#endif 491// File_checked_for_headers 492