Upstream tarball 20080418
[amule.git] / src / ClientCreditsList.cpp
blob51543fc5f104e135b18827f0d95928a3d5a1fb9c
1 //
2 // This file is part of the aMule Project.
3 //
4 // Copyright (c) 2003-2008 aMule Team ( admin@amule.org / http://www.amule.org )
5 // Copyright (c) 2002 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.
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.
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
26 #include "ClientCreditsList.h" // Interface declarations
29 #include <protocol/ed2k/Constants.h>
30 #include <common/Macros.h>
31 #include <common/DataFileVersion.h>
32 #include <common/FileFunctions.h> // Needed for GetFileSize
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
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")
49 CClientCreditsList::CClientCreditsList()
51 m_nLastSaved = ::GetTickCount();
52 LoadList();
54 InitalizeCrypting();
58 CClientCreditsList::~CClientCreditsList()
60 ClientMap::iterator it = m_mapClients.begin();
61 for ( ; it != m_mapClients.end(); ++it ){
62 delete it->second;
64 m_mapClients.clear();
65 delete static_cast<CryptoPP::RSASSA_PKCS1v15_SHA_Signer *>(m_pSignkey);
69 void CClientCreditsList::LoadList()
71 CFile file;
72 CPath fileName = CPath(theApp->ConfigDir + CLIENTS_MET_FILENAME);
74 if (!fileName.FileExists()) {
75 return;
78 try {
79 file.Open(fileName, CFile::read);
81 if (file.ReadUInt8() != CREDITFILE_VERSION) {
82 AddDebugLogLineM( true, logCredits,
83 wxT("Creditfile is out of date and will be replaced") );
84 file.Close();
85 return;
88 // everything is ok, lets see if the backup exist...
89 CPath bakFileName = CPath(theApp->ConfigDir + CLIENTS_MET_BAK_FILENAME);
91 bool bCreateBackup = TRUE;
92 if (bakFileName.FileExists()) {
93 // Ok, the backup exist, get the size
94 CFile hBakFile(bakFileName);
95 if ( hBakFile.GetLength() > file.GetLength()) {
96 // the size of the backup was larger then the
97 // org. file, something is wrong here, don't
98 // overwrite old backup..
99 bCreateBackup = FALSE;
101 // else: backup is smaller or the same size as org.
102 // file, proceed with copying of file
105 //else: the backup doesn't exist, create it
106 if (bCreateBackup) {
107 file.Close(); // close the file before copying
108 if (!CPath::CloneFile(fileName, bakFileName, true)) {
109 AddDebugLogLineM(true, logCredits,
110 CFormat(wxT("Could not create backup file '%s'")) % fileName);
112 // reopen file
113 if (!file.Open(fileName, CFile::read)) {
114 AddDebugLogLineM( true, logCredits,
115 wxT("Failed to load creditfile") );
116 return;
119 file.Seek(1);
123 uint32 count = file.ReadUInt32();
125 const uint32 dwExpired = time(NULL) - 12960000; // today - 150 day
126 uint32 cDeleted = 0;
127 for (uint32 i = 0; i < count; i++){
128 CreditStruct* newcstruct = new CreditStruct();
130 newcstruct->key = file.ReadHash();
131 newcstruct->uploaded = file.ReadUInt32();
132 newcstruct->downloaded = file.ReadUInt32();
133 newcstruct->nLastSeen = file.ReadUInt32();
134 newcstruct->uploaded += static_cast<uint64>(file.ReadUInt32()) << 32;
135 newcstruct->downloaded += static_cast<uint64>(file.ReadUInt32()) << 32;
136 newcstruct->nReserved3 = file.ReadUInt16();
137 newcstruct->nKeySize = file.ReadUInt8();
138 file.Read(newcstruct->abySecureIdent, MAXPUBKEYSIZE);
140 if ( newcstruct->nKeySize > MAXPUBKEYSIZE ) {
141 // Oh dear, this is bad mojo, the file is most likely corrupt
142 // We can no longer assume that any of the clients in the file are valid
143 // and will have to discard it.
144 delete newcstruct;
146 // Remove already read, and possibly invalid, entries
147 ClientMap::iterator it = m_mapClients.begin();
148 for ( ; it != m_mapClients.end(); ++it ){
149 delete it->second;
151 m_mapClients.clear();
153 AddDebugLogLineM( true, logCredits,
154 wxT("WARNING: Corruptions found while reading Creditfile!") );
155 return;
158 if (newcstruct->nLastSeen < dwExpired){
159 cDeleted++;
160 delete newcstruct;
161 continue;
164 CClientCredits* newcredits = new CClientCredits(newcstruct);
165 m_mapClients[newcredits->GetKey()] = newcredits;
168 AddLogLineM(false, wxString::Format(wxPLURAL("Creditfile loaded, %u client is known", "Creditfile loaded, %u clients are known", count - cDeleted), count - cDeleted));
170 if (cDeleted) {
171 AddLogLineM(false, wxString::Format(wxPLURAL(" - Credits expired for %u client!", " - Credits expired for %u clients!", cDeleted), cDeleted));
173 } catch (const CSafeIOException& e) {
174 AddDebugLogLineM(true, logCredits, wxT("IO error while loading clients.met file: ") + e.what());
179 void CClientCreditsList::SaveList()
181 AddDebugLogLineM( false, logCredits, wxT("Saved Credit list"));
182 m_nLastSaved = ::GetTickCount();
184 wxString name(theApp->ConfigDir + CLIENTS_MET_FILENAME);
185 CFile file;
187 if ( !file.Create(name, true) ) {
188 AddDebugLogLineM( true, logCredits, wxT("Failed to create creditfile") );
189 return;
192 if ( file.Open(name, CFile::write) ) {
193 try {
194 uint32 count = 0;
196 file.WriteUInt8( CREDITFILE_VERSION );
197 // Temporary place-holder for number of stucts
198 file.WriteUInt32( 0 );
200 ClientMap::iterator it = m_mapClients.begin();
201 for ( ; it != m_mapClients.end(); ++it ) {
202 CClientCredits* cur_credit = it->second;
204 if ( cur_credit->GetUploadedTotal() || cur_credit->GetDownloadedTotal() ) {
205 const CreditStruct* const cstruct = cur_credit->GetDataStruct();
206 file.WriteHash(cstruct->key);
207 file.WriteUInt32(static_cast<uint32>(cstruct->uploaded));
208 file.WriteUInt32(static_cast<uint32>(cstruct->downloaded));
209 file.WriteUInt32(cstruct->nLastSeen);
210 file.WriteUInt32(static_cast<uint32>(cstruct->uploaded >> 32));
211 file.WriteUInt32(static_cast<uint32>(cstruct->downloaded >> 32));
212 file.WriteUInt16(cstruct->nReserved3);
213 file.WriteUInt8(cstruct->nKeySize);
214 // Doesn't matter if this saves garbage, will be fixed on load.
215 file.Write(cstruct->abySecureIdent, MAXPUBKEYSIZE);
216 count++;
220 // Write the actual number of structs
221 file.Seek( 1 );
222 file.WriteUInt32( count );
223 } catch (const CIOFailureException& e) {
224 AddDebugLogLineM(true, logCredits, wxT("IO failure while saving clients.met: ") + e.what());
226 } else {
227 AddDebugLogLineM(true, logCredits, wxT("Failed to open existing creditfile!"));
232 CClientCredits* CClientCreditsList::GetCredit(const CMD4Hash& key)
234 CClientCredits* result;
236 ClientMap::iterator it = m_mapClients.find( key );
239 if ( it == m_mapClients.end() ){
240 result = new CClientCredits(key);
241 m_mapClients[result->GetKey()] = result;
242 } else {
243 result = it->second;
246 result->SetLastSeen();
248 return result;
252 void CClientCreditsList::Process()
254 if (::GetTickCount() - m_nLastSaved > MIN2MS(13))
255 SaveList();
259 bool CClientCreditsList::CreateKeyPair()
261 try{
262 CryptoPP::AutoSeededX917RNG<CryptoPP::DES_EDE3> rng;
263 CryptoPP::InvertibleRSAFunction privkey;
264 privkey.Initialize(rng, RSAKEYSIZE);
266 // Nothing we can do against this filename2char :/
267 wxCharBuffer filename = filename2char(theApp->ConfigDir + CRYPTKEY_FILENAME);
268 CryptoPP::FileSink *fileSink = new CryptoPP::FileSink(filename);
269 CryptoPP::Base64Encoder *privkeysink = new CryptoPP::Base64Encoder(fileSink);
270 privkey.DEREncode(*privkeysink);
271 privkeysink->MessageEnd();
273 // Do not delete these pointers or it will blow in your face.
274 // cryptopp semantics is giving ownership of these objects.
276 // delete privkeysink;
277 // delete fileSink;
279 AddDebugLogLineM( true, logCredits, wxT("Created new RSA keypair"));
280 } catch(const CryptoPP::Exception& e) {
281 AddDebugLogLineM(true, logCredits,
282 wxString(wxT("Failed to create new RSA keypair: ")) +
283 char2unicode(e.what()));
284 wxASSERT(false);
285 return false;
288 return true;
292 void CClientCreditsList::InitalizeCrypting()
294 m_nMyPublicKeyLen = 0;
295 memset(m_abyMyPublicKey,0,80); // not really needed; better for debugging tho
296 m_pSignkey = NULL;
298 if (!thePrefs::IsSecureIdentEnabled()) {
299 return;
302 try {
303 // check if keyfile is there
304 if (wxFileExists(theApp->ConfigDir + CRYPTKEY_FILENAME)) {
305 off_t keySize = CPath::GetFileSize(theApp->ConfigDir + CRYPTKEY_FILENAME);
307 if (keySize == wxInvalidOffset) {
308 AddDebugLogLineM(true, logCredits, wxT("Cannot access 'cryptkey.dat', please check permissions."));
309 return;
310 } else if (keySize == 0) {
311 AddDebugLogLineM(true, logCredits, wxT("'cryptkey.dat' is empty, recreating keypair."));
312 CreateKeyPair();
314 } else {
315 AddLogLineM( false, _("No 'cryptkey.dat' file found, creating.") );
316 CreateKeyPair();
319 // load private key
320 CryptoPP::FileSource *filesource = new CryptoPP::FileSource(
321 filename2char(theApp->ConfigDir + CRYPTKEY_FILENAME),
322 true, new CryptoPP::Base64Decoder);
323 m_pSignkey = new CryptoPP::RSASSA_PKCS1v15_SHA_Signer(*filesource);
324 // calculate and store public key
325 CryptoPP::RSASSA_PKCS1v15_SHA_Verifier pubkey(
326 *static_cast<CryptoPP::RSASSA_PKCS1v15_SHA_Signer *>(m_pSignkey));
327 CryptoPP::ArraySink *asink = new CryptoPP::ArraySink(m_abyMyPublicKey, 80);
328 pubkey.DEREncode(*asink);
329 m_nMyPublicKeyLen = asink->TotalPutLength();
330 asink->MessageEnd();
331 } catch (const CryptoPP::Exception& e) {
332 delete static_cast<CryptoPP::RSASSA_PKCS1v15_SHA_Signer *>(m_pSignkey);
333 m_pSignkey = NULL;
335 AddDebugLogLineM(true, logCredits,
336 wxString(wxT("Error while initializing encryption keys: ")) +
337 char2unicode(e.what()));
342 uint8 CClientCreditsList::CreateSignature(CClientCredits* pTarget, byte* pachOutput, uint8 nMaxSize, uint32 ChallengeIP, uint8 byChaIPKind, void* sigkey)
344 CryptoPP::RSASSA_PKCS1v15_SHA_Signer* signer =
345 static_cast<CryptoPP::RSASSA_PKCS1v15_SHA_Signer *>(sigkey);
346 // signer param is used for debug only
347 if (signer == NULL)
348 signer = static_cast<CryptoPP::RSASSA_PKCS1v15_SHA_Signer *>(m_pSignkey);
350 // create a signature of the public key from pTarget
351 wxASSERT( pTarget );
352 wxASSERT( pachOutput );
354 if ( !CryptoAvailable() ) {
355 return 0;
358 try {
359 CryptoPP::SecByteBlock sbbSignature(signer->SignatureLength());
360 CryptoPP::AutoSeededX917RNG<CryptoPP::DES_EDE3> rng;
361 byte abyBuffer[MAXPUBKEYSIZE+9];
362 uint32 keylen = pTarget->GetSecIDKeyLen();
363 memcpy(abyBuffer,pTarget->GetSecureIdent(),keylen);
364 // 4 additional bytes random data send from this client
365 uint32 challenge = pTarget->m_dwCryptRndChallengeFrom;
366 wxASSERT ( challenge != 0 );
367 PokeUInt32(abyBuffer+keylen,challenge);
369 uint16 ChIpLen = 0;
370 if ( byChaIPKind != 0){
371 ChIpLen = 5;
372 PokeUInt32(abyBuffer+keylen+4, ChallengeIP);
373 PokeUInt8(abyBuffer+keylen+4+4,byChaIPKind);
375 signer->SignMessage(rng, abyBuffer ,keylen+4+ChIpLen , sbbSignature.begin());
376 CryptoPP::ArraySink asink(pachOutput, nMaxSize);
377 asink.Put(sbbSignature.begin(), sbbSignature.size());
379 return asink.TotalPutLength();
380 } catch (const CryptoPP::Exception& e) {
381 AddDebugLogLineM(true, logCredits, wxString(wxT("Error while creating signature: ")) + char2unicode(e.what()));
382 wxASSERT(false);
384 return 0;
389 bool CClientCreditsList::VerifyIdent(CClientCredits* pTarget, const byte* pachSignature, uint8 nInputSize, uint32 dwForIP, uint8 byChaIPKind)
391 wxASSERT( pTarget );
392 wxASSERT( pachSignature );
393 if ( !CryptoAvailable() ){
394 pTarget->SetIdentState(IS_NOTAVAILABLE);
395 return false;
397 bool bResult;
398 try {
399 CryptoPP::StringSource ss_Pubkey((byte*)pTarget->GetSecureIdent(),pTarget->GetSecIDKeyLen(),true,0);
400 CryptoPP::RSASSA_PKCS1v15_SHA_Verifier pubkey(ss_Pubkey);
401 // 4 additional bytes random data send from this client +5 bytes v2
402 byte abyBuffer[MAXPUBKEYSIZE+9];
403 memcpy(abyBuffer,m_abyMyPublicKey,m_nMyPublicKeyLen);
404 uint32 challenge = pTarget->m_dwCryptRndChallengeFor;
405 wxASSERT ( challenge != 0 );
406 PokeUInt32(abyBuffer+m_nMyPublicKeyLen, challenge);
408 // v2 security improvments (not supported by 29b, not used as default by 29c)
409 uint8 nChIpSize = 0;
410 if (byChaIPKind != 0){
411 nChIpSize = 5;
412 uint32 ChallengeIP = 0;
413 switch (byChaIPKind){
414 case CRYPT_CIP_LOCALCLIENT:
415 ChallengeIP = dwForIP;
416 break;
417 case CRYPT_CIP_REMOTECLIENT:
418 // Ignore local ip...
419 if (!theApp->GetPublicIP(true)) {
420 if (::IsLowID(theApp->GetED2KID())){
421 AddDebugLogLineM( false, logCredits, wxT("Warning: Maybe SecureHash Ident fails because LocalIP is unknown"));
422 // Fallback to local ip...
423 ChallengeIP = theApp->GetPublicIP();
424 } else {
425 ChallengeIP = theApp->GetED2KID();
427 } else {
428 ChallengeIP = theApp->GetPublicIP();
430 break;
431 case CRYPT_CIP_NONECLIENT: // maybe not supported in future versions
432 ChallengeIP = 0;
433 break;
435 PokeUInt32(abyBuffer+m_nMyPublicKeyLen+4, ChallengeIP);
436 PokeUInt8(abyBuffer+m_nMyPublicKeyLen+4+4, byChaIPKind);
438 //v2 end
440 bResult = pubkey.VerifyMessage(abyBuffer, m_nMyPublicKeyLen+4+nChIpSize, pachSignature, nInputSize);
441 } catch (const CryptoPP::Exception& e) {
442 AddDebugLogLineM(true, logCredits, wxString(wxT("Error while verifying identity: ")) + char2unicode(e.what()));
443 bResult = false;
446 if (!bResult){
447 if (pTarget->GetIdentState() == IS_IDNEEDED)
448 pTarget->SetIdentState(IS_IDFAILED);
449 } else {
450 pTarget->Verified(dwForIP);
453 return bResult;
457 bool CClientCreditsList::CryptoAvailable() const
459 return m_nMyPublicKeyLen > 0 && m_pSignkey != NULL;
463 #ifdef _DEBUG
464 bool CClientCreditsList::Debug_CheckCrypting(){
465 // create random key
466 CryptoPP::AutoSeededX917RNG<CryptoPP::DES_EDE3> rng;
468 CryptoPP::RSASSA_PKCS1v15_SHA_Signer priv(rng, 384);
469 CryptoPP::RSASSA_PKCS1v15_SHA_Verifier pub(priv);
471 byte abyPublicKey[80];
472 CryptoPP::ArraySink asink(abyPublicKey, 80);
473 pub.DEREncode(asink);
474 int8 PublicKeyLen = asink.TotalPutLength();
475 asink.MessageEnd();
476 uint32 challenge = rand();
477 // create fake client which pretends to be this emule
478 CreditStruct* newcstruct = new CreditStruct();
479 CClientCredits newcredits(newcstruct);
480 newcredits.SetSecureIdent(m_abyMyPublicKey,m_nMyPublicKeyLen);
481 newcredits.m_dwCryptRndChallengeFrom = challenge;
482 // create signature with fake priv key
483 byte pachSignature[200];
484 memset(pachSignature,0,200);
485 uint8 sigsize = CreateSignature(&newcredits,pachSignature,200,0,false, &priv);
488 // next fake client uses the random created public key
489 CreditStruct* newcstruct2 = new CreditStruct();
490 CClientCredits newcredits2(newcstruct2);
491 newcredits2.m_dwCryptRndChallengeFor = challenge;
493 // if you uncomment one of the following lines the check has to fail
494 //abyPublicKey[5] = 34;
495 //m_abyMyPublicKey[5] = 22;
496 //pachSignature[5] = 232;
498 newcredits2.SetSecureIdent(abyPublicKey,PublicKeyLen);
500 //now verify this signature - if it's true everything is fine
501 return VerifyIdent(&newcredits2,pachSignature,sigsize,0,0);
503 #endif
504 // File_checked_for_headers