2 // This file is part of the aMule Project.
4 // Copyright (c) 2003-2008 aMule Team ( admin@amule.org / http://www.amule.org )
5 // Copyright (c) 2002-2008 Merkur ( devs@emule-project.net / http://www.emule-project.net )
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
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.
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();
58 CClientCreditsList::~CClientCreditsList()
60 ClientMap::iterator it
= m_mapClients
.begin();
61 for ( ; it
!= m_mapClients
.end(); ++it
){
65 delete static_cast<CryptoPP::RSASSA_PKCS1v15_SHA_Signer
*>(m_pSignkey
);
69 void CClientCreditsList::LoadList()
72 CPath fileName
= CPath(theApp
->ConfigDir
+ CLIENTS_MET_FILENAME
);
74 if (!fileName
.FileExists()) {
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") );
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
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
);
113 if (!file
.Open(fileName
, CFile::read
)) {
114 AddDebugLogLineM( true, logCredits
,
115 wxT("Failed to load creditfile") );
123 uint32 count
= file
.ReadUInt32();
125 const uint32 dwExpired
= time(NULL
) - 12960000; // today - 150 day
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.
146 // Remove already read, and possibly invalid, entries
147 ClientMap::iterator it
= m_mapClients
.begin();
148 for ( ; it
!= m_mapClients
.end(); ++it
){
151 m_mapClients
.clear();
153 AddDebugLogLineM( true, logCredits
,
154 wxT("WARNING: Corruptions found while reading Creditfile!") );
158 if (newcstruct
->nLastSeen
< dwExpired
){
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
));
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
);
187 if ( !file
.Create(name
, true) ) {
188 AddDebugLogLineM( true, logCredits
, wxT("Failed to create creditfile") );
192 if ( file
.Open(name
, CFile::write
) ) {
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
);
220 // Write the actual number of structs
222 file
.WriteUInt32( count
);
223 } catch (const CIOFailureException
& e
) {
224 AddDebugLogLineM(true, logCredits
, wxT("IO failure while saving clients.met: ") + e
.what());
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
;
246 result
->SetLastSeen();
252 void CClientCreditsList::Process()
254 if (::GetTickCount() - m_nLastSaved
> MIN2MS(13))
259 bool CClientCreditsList::CreateKeyPair()
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;
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()));
292 void CClientCreditsList::InitalizeCrypting()
294 m_nMyPublicKeyLen
= 0;
295 memset(m_abyMyPublicKey
,0,80); // not really needed; better for debugging tho
298 if (!thePrefs::IsSecureIdentEnabled()) {
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."));
310 } else if (keySize
== 0) {
311 AddDebugLogLineM(true, logCredits
, wxT("'cryptkey.dat' is empty, recreating keypair."));
315 AddLogLineM( false, _("No 'cryptkey.dat' file found, creating.") );
320 CryptoPP::FileSource
filesource(filename2char(theApp
->ConfigDir
+ CRYPTKEY_FILENAME
), true, new CryptoPP::Base64Decoder
);
321 m_pSignkey
= new CryptoPP::RSASSA_PKCS1v15_SHA_Signer(filesource
);
322 // calculate and store public key
323 CryptoPP::RSASSA_PKCS1v15_SHA_Verifier
pubkey(*static_cast<CryptoPP::RSASSA_PKCS1v15_SHA_Signer
*>(m_pSignkey
));
324 CryptoPP::ArraySink
asink(m_abyMyPublicKey
, 80);
325 pubkey
.DEREncode(asink
);
326 m_nMyPublicKeyLen
= asink
.TotalPutLength();
328 } catch (const CryptoPP::Exception
& e
) {
329 delete static_cast<CryptoPP::RSASSA_PKCS1v15_SHA_Signer
*>(m_pSignkey
);
332 AddDebugLogLineM(true, logCredits
,
333 wxString(wxT("Error while initializing encryption keys: ")) +
334 char2unicode(e
.what()));
339 uint8
CClientCreditsList::CreateSignature(CClientCredits
* pTarget
, byte
* pachOutput
, uint8 nMaxSize
, uint32 ChallengeIP
, uint8 byChaIPKind
, void* sigkey
)
341 CryptoPP::RSASSA_PKCS1v15_SHA_Signer
* signer
=
342 static_cast<CryptoPP::RSASSA_PKCS1v15_SHA_Signer
*>(sigkey
);
343 // signer param is used for debug only
345 signer
= static_cast<CryptoPP::RSASSA_PKCS1v15_SHA_Signer
*>(m_pSignkey
);
347 // create a signature of the public key from pTarget
349 wxASSERT( pachOutput
);
351 if ( !CryptoAvailable() ) {
356 CryptoPP::SecByteBlock
sbbSignature(signer
->SignatureLength());
357 CryptoPP::AutoSeededX917RNG
<CryptoPP::DES_EDE3
> rng
;
358 byte abyBuffer
[MAXPUBKEYSIZE
+9];
359 uint32 keylen
= pTarget
->GetSecIDKeyLen();
360 memcpy(abyBuffer
,pTarget
->GetSecureIdent(),keylen
);
361 // 4 additional bytes random data send from this client
362 uint32 challenge
= pTarget
->m_dwCryptRndChallengeFrom
;
363 wxASSERT ( challenge
!= 0 );
364 PokeUInt32(abyBuffer
+keylen
,challenge
);
367 if ( byChaIPKind
!= 0){
369 PokeUInt32(abyBuffer
+keylen
+4, ChallengeIP
);
370 PokeUInt8(abyBuffer
+keylen
+4+4,byChaIPKind
);
372 signer
->SignMessage(rng
, abyBuffer
,keylen
+4+ChIpLen
, sbbSignature
.begin());
373 CryptoPP::ArraySink
asink(pachOutput
, nMaxSize
);
374 asink
.Put(sbbSignature
.begin(), sbbSignature
.size());
376 return asink
.TotalPutLength();
377 } catch (const CryptoPP::Exception
& e
) {
378 AddDebugLogLineM(true, logCredits
, wxString(wxT("Error while creating signature: ")) + char2unicode(e
.what()));
386 bool CClientCreditsList::VerifyIdent(CClientCredits
* pTarget
, const byte
* pachSignature
, uint8 nInputSize
, uint32 dwForIP
, uint8 byChaIPKind
)
389 wxASSERT( pachSignature
);
390 if ( !CryptoAvailable() ){
391 pTarget
->SetIdentState(IS_NOTAVAILABLE
);
396 CryptoPP::StringSource
ss_Pubkey((byte
*)pTarget
->GetSecureIdent(),pTarget
->GetSecIDKeyLen(),true,0);
397 CryptoPP::RSASSA_PKCS1v15_SHA_Verifier
pubkey(ss_Pubkey
);
398 // 4 additional bytes random data send from this client +5 bytes v2
399 byte abyBuffer
[MAXPUBKEYSIZE
+9];
400 memcpy(abyBuffer
,m_abyMyPublicKey
,m_nMyPublicKeyLen
);
401 uint32 challenge
= pTarget
->m_dwCryptRndChallengeFor
;
402 wxASSERT ( challenge
!= 0 );
403 PokeUInt32(abyBuffer
+m_nMyPublicKeyLen
, challenge
);
405 // v2 security improvments (not supported by 29b, not used as default by 29c)
407 if (byChaIPKind
!= 0){
409 uint32 ChallengeIP
= 0;
410 switch (byChaIPKind
){
411 case CRYPT_CIP_LOCALCLIENT
:
412 ChallengeIP
= dwForIP
;
414 case CRYPT_CIP_REMOTECLIENT
:
415 // Ignore local ip...
416 if (!theApp
->GetPublicIP(true)) {
417 if (::IsLowID(theApp
->GetED2KID())){
418 AddDebugLogLineM( false, logCredits
, wxT("Warning: Maybe SecureHash Ident fails because LocalIP is unknown"));
419 // Fallback to local ip...
420 ChallengeIP
= theApp
->GetPublicIP();
422 ChallengeIP
= theApp
->GetED2KID();
425 ChallengeIP
= theApp
->GetPublicIP();
428 case CRYPT_CIP_NONECLIENT
: // maybe not supported in future versions
432 PokeUInt32(abyBuffer
+m_nMyPublicKeyLen
+4, ChallengeIP
);
433 PokeUInt8(abyBuffer
+m_nMyPublicKeyLen
+4+4, byChaIPKind
);
437 bResult
= pubkey
.VerifyMessage(abyBuffer
, m_nMyPublicKeyLen
+4+nChIpSize
, pachSignature
, nInputSize
);
438 } catch (const CryptoPP::Exception
& e
) {
439 AddDebugLogLineM(true, logCredits
, wxString(wxT("Error while verifying identity: ")) + char2unicode(e
.what()));
444 if (pTarget
->GetIdentState() == IS_IDNEEDED
)
445 pTarget
->SetIdentState(IS_IDFAILED
);
447 pTarget
->Verified(dwForIP
);
454 bool CClientCreditsList::CryptoAvailable() const
456 return m_nMyPublicKeyLen
> 0 && m_pSignkey
!= NULL
;
461 bool CClientCreditsList::Debug_CheckCrypting(){
463 CryptoPP::AutoSeededX917RNG
<CryptoPP::DES_EDE3
> rng
;
465 CryptoPP::RSASSA_PKCS1v15_SHA_Signer
priv(rng
, 384);
466 CryptoPP::RSASSA_PKCS1v15_SHA_Verifier
pub(priv
);
468 byte abyPublicKey
[80];
469 CryptoPP::ArraySink
asink(abyPublicKey
, 80);
470 pub
.DEREncode(asink
);
471 int8 PublicKeyLen
= asink
.TotalPutLength();
473 uint32 challenge
= rand();
474 // create fake client which pretends to be this emule
475 CreditStruct
* newcstruct
= new CreditStruct();
476 CClientCredits
newcredits(newcstruct
);
477 newcredits
.SetSecureIdent(m_abyMyPublicKey
,m_nMyPublicKeyLen
);
478 newcredits
.m_dwCryptRndChallengeFrom
= challenge
;
479 // create signature with fake priv key
480 byte pachSignature
[200];
481 memset(pachSignature
,0,200);
482 uint8 sigsize
= CreateSignature(&newcredits
,pachSignature
,200,0,false, &priv
);
485 // next fake client uses the random created public key
486 CreditStruct
* newcstruct2
= new CreditStruct();
487 CClientCredits
newcredits2(newcstruct2
);
488 newcredits2
.m_dwCryptRndChallengeFor
= challenge
;
490 // if you uncomment one of the following lines the check has to fail
491 //abyPublicKey[5] = 34;
492 //m_abyMyPublicKey[5] = 22;
493 //pachSignature[5] = 232;
495 newcredits2
.SetSecureIdent(abyPublicKey
,PublicKeyLen
);
497 //now verify this signature - if it's true everything is fine
498 return VerifyIdent(&newcredits2
,pachSignature
,sigsize
,0,0);
501 // File_checked_for_headers