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
CClientCreditsList()49 CClientCreditsList::CClientCreditsList()
50 {
51 m_nLastSaved = ::GetTickCount();
52 LoadList();
53
54 InitalizeCrypting();
55 }
56
57
~CClientCreditsList()58 CClientCreditsList::~CClientCreditsList()
59 {
60 DeleteContents(m_mapClients);
61 delete static_cast<CryptoPP::RSASSA_PKCS1v15_SHA_Signer *>(m_pSignkey);
62 }
63
64
LoadList()65 void CClientCreditsList::LoadList()
66 {
67 CFile file;
68 CPath fileName = CPath(thePrefs::GetConfigDir() + 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(thePrefs::GetConfigDir() + 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
SaveList()169 void CClientCreditsList::SaveList()
170 {
171 AddDebugLogLineN( logCredits, wxT("Saved Credit list"));
172 m_nLastSaved = ::GetTickCount();
173
174 wxString name(thePrefs::GetConfigDir() + 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
GetCredit(const CMD4Hash & key)222 CClientCredits* 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
Process()242 void CClientCreditsList::Process()
243 {
244 if (::GetTickCount() - m_nLastSaved > MIN2MS(13))
245 SaveList();
246 }
247
248
CreateKeyPair()249 bool 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(thePrefs::GetConfigDir() + 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
InitalizeCrypting()282 void 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(thePrefs::GetConfigDir() + CRYPTKEY_FILENAME)) {
295 off_t keySize = CPath::GetFileSize(thePrefs::GetConfigDir() + 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(thePrefs::GetConfigDir() + 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.GetMaterial().Save(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
CreateSignature(CClientCredits * pTarget,byte * pachOutput,uint8 nMaxSize,uint32 ChallengeIP,uint8 byChaIPKind,void * sigkey)329 uint8 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
VerifyIdent(CClientCredits * pTarget,const byte * pachSignature,uint8 nInputSize,uint32 dwForIP,uint8 byChaIPKind)376 bool 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
CryptoAvailable() const444 bool CClientCreditsList::CryptoAvailable() const
445 {
446 return m_nMyPublicKeyLen > 0 && m_pSignkey != NULL;
447 }
448
449
450 #ifdef _DEBUG
Debug_CheckCrypting()451 bool 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