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