creditsdb.cpp

Go to the documentation of this file.
00001 /**
00002  *  Copyright (C) 2004-2005 Alo Sarv <madcat_@users.sourceforge.net>
00003  *
00004  *  This program is free software; you can redistribute it and/or modify
00005  *  it under the terms of the GNU General Public License as published by
00006  *  the Free Software Foundation; either version 2 of the License, or
00007  *  (at your option) any later version.
00008  *
00009  *  This program is distributed in the hope that it will be useful,
00010  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00011  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00012  *  GNU General Public License for more details.
00013  *
00014  *  You should have received a copy of the GNU General Public License
00015  *  along with this program; if not, write to the Free Software
00016  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
00017  */
00018 
00019 /** @file creditsdb.cpp Implementation of CreditsDb and Credits classes */
00020 
00021 #include <hn/hnprec.h>
00022 #include "creditsdb.h"
00023 #include "ed2k.h"
00024 #include <hn/utils.h>
00025 
00026 #include "cryptopp.h"
00027 
00028 #include <boost/filesystem/operations.hpp>
00029 #include <boost/multi_index_container.hpp>
00030 #include <boost/multi_index/key_extractors.hpp>
00031 #include <boost/multi_index/ordered_index.hpp>
00032 
00033 //! Tags used in clients.met file
00034 enum ED2K_ClientsMet {
00035         CM_VER   = 0x11,          //!< Old version - no RSA pub key
00036         CM_VER29 = 0x12           //!< New version (eMule 0.29+), with RSA key
00037 };
00038 
00039 //! The constant-size space alloted to saving keys in credits.met files (v29+).
00040 const unsigned int ED2K_MaxKeySize = 80;
00041 
00042 // Credits class
00043 // -------------
00044 Credits::Credits(PublicKey key, const Hash<MD4Hash> &h) : m_hash(h),
00045 m_uploaded(), m_downloaded(), m_lastSeen(), m_pubKey(key) {}
00046 
00047 // Dummy destructor
00048 Credits::~Credits() {}
00049 
00050 // Construct and load
00051 Credits::Credits(std::istream &i, uint8_t ver) {
00052         m_hash = Utils::getVal<std::string>(i, 16);
00053         uint32_t uploadLow = Utils::getVal<uint32_t>(i);
00054         uint32_t downloadLow = Utils::getVal<uint32_t>(i);
00055         m_lastSeen = Utils::getVal<uint32_t>(i);
00056         uint32_t uploadHigh = Utils::getVal<uint32_t>(i);
00057         uint32_t downloadHigh = Utils::getVal<uint32_t>(i);
00058 
00059         // We prefer storing things as 64-bit values internally
00060         m_uploaded = uploadHigh;
00061         m_uploaded <<= 32;
00062         m_uploaded += uploadLow;
00063         m_downloaded = downloadHigh;
00064         m_downloaded <<= 32;
00065         m_downloaded += downloadLow;
00066         i.seekg(2, std::ios::cur); // Reserved bytes
00067 
00068         if (ver == CM_VER29) {
00069                 uint8_t keySize = Utils::getVal<uint8_t>(i);
00070                 CHECK_THROW(keySize <= ED2K_MaxKeySize);
00071 
00072                 m_pubKey = Utils::getVal<std::string>(i, keySize);
00073 
00074                 // key may be smaller than ED2K_MaxKeySize - seek forward then
00075                 i.seekg(ED2K_MaxKeySize - keySize, std::ios::cur);
00076         }
00077 }
00078 
00079 // Write to stream
00080 std::ostream& operator<<(std::ostream &o, const Credits &c) {
00081         Utils::putVal(o, c.m_hash.getData(), 16);
00082         Utils::putVal<uint32_t>(o, c.m_uploaded);
00083         Utils::putVal<uint32_t>(o, c.m_downloaded);
00084         Utils::putVal<uint32_t>(o, c.m_lastSeen);
00085         Utils::putVal<uint32_t>(o, c.m_uploaded >> 32);
00086         Utils::putVal<uint32_t>(o, c.m_downloaded >> 32);
00087         Utils::putVal<uint16_t>(o, 0); // 2 reserved bytes
00088 
00089         Utils::putVal<uint8_t>(o, c.m_pubKey.size());
00090         Utils::putVal(o, c.m_pubKey.c_str(), c.m_pubKey.size());
00091 
00092         // add padding if keysize is < 80 (required for compatibility)
00093         if ( c.m_pubKey.size() < ED2K_MaxKeySize ) {
00094                 std::string dummyStr(ED2K_MaxKeySize - c.m_pubKey.size(), '\0');
00095                 Utils::putVal(o, dummyStr, dummyStr.size());
00096         }
00097 
00098         return o;
00099 }
00100 
00101 namespace Detail {
00102         struct CreditsDbIndices : public boost::multi_index::indexed_by<
00103                 boost::multi_index::ordered_unique<
00104                         boost::multi_index::identity<Credits*>
00105                 >,
00106                 boost::multi_index::ordered_non_unique<
00107                         boost::multi_index::const_mem_fun<
00108                                 Credits, Hash<MD4Hash>, &Credits::getHash
00109                         >
00110                 >,
00111                 boost::multi_index::ordered_non_unique<
00112                         boost::multi_index::const_mem_fun<
00113                                 Credits, PublicKey, &Credits::getPubKey
00114                         >
00115                 >
00116         > {};
00117         struct CreditsList : boost::multi_index_container<
00118                 Credits*, CreditsDbIndices
00119         > {};
00120         enum CreditsListKeys { ID_Id, ID_Hash, ID_PubKey, ID_LastSeen };
00121         typedef CreditsList::nth_index<ID_Id    >::type::iterator IDIter;
00122         typedef CreditsList::nth_index<ID_Hash  >::type::iterator HashIter;
00123         typedef CreditsList::nth_index<ID_PubKey>::type::iterator KeyIter;
00124 }
00125 using namespace Detail;
00126 
00127 // CreditsDb class
00128 // ---------------
00129 // Defined here to avoid including Crypto headers from creditsdb.h
00130 typedef CryptoPP::RSASSA_PKCS1v15_SHA_Signer   Signer;
00131 typedef CryptoPP::RSASSA_PKCS1v15_SHA_Verifier Verifier;
00132 boost::shared_ptr<Signer>   s_signer;   //!< SecIdent: Signer
00133 boost::shared_ptr<Verifier> s_verifier; //!< SecIdent: Verifier
00134 
00135 // construction/destruction
00136 CreditsDb::CreditsDb() : m_list(new CreditsList) {}
00137 CreditsDb::~CreditsDb() {}
00138 
00139 void CreditsDb::load(const std::string &file) try {
00140         std::ifstream ifs(file.c_str(), std::ios::binary);
00141         if (!ifs) {
00142                 return; // Nothing to do
00143         }
00144         uint8_t ver = Utils::getVal<uint8_t>(ifs);
00145         if (ver != CM_VER && ver != CM_VER29) {
00146                 logError(
00147                         boost::format("Corruption found in credits database.")
00148                 );
00149                 return;
00150         }
00151         uint32_t count = Utils::getVal<uint32_t>(ifs);
00152         Utils::StopWatch t;
00153         while (ifs && count--) {
00154                 m_list->insert(new Credits(ifs, ver));
00155         }
00156         logMsg(
00157                 boost::format("CreditsDb loaded, %d clients are known (%dms)")
00158                 % m_list->size() % t
00159         );
00160 } catch (std::exception &e) {
00161         logError(boost::format("Error loading CreditsDb: %s") % e.what());
00162         logMsg("Attempting to load from backup...");
00163         try {
00164                 load(file + ".bak");
00165                 logMsg("Loading CreditsDb from backup succeeded.");
00166         } catch (std::exception &) {
00167                 logError("Fatal: Failed to load CreditsDb from backup.");
00168         }
00169 }
00170 MSVC_ONLY(;)
00171 
00172 void CreditsDb::save(const std::string &file) const {
00173         std::ofstream ofs(file.c_str());
00174         if (!ofs) {
00175                 logWarning(
00176                         boost::format("Failed to open %s for saving CreditsDb.")
00177                         % file
00178                 );
00179                 return;
00180         }
00181         Utils::putVal<uint8_t>(ofs, CM_VER29);
00182         Utils::putVal<uint32_t>(ofs, m_list->size());
00183         Utils::StopWatch t;
00184         for (IDIter i = m_list->begin(); i != m_list->end(); ++i) {
00185                 ofs << *(*i);
00186         }
00187 
00188         logMsg(
00189                 boost::format("CreditsDb saved, %d clients written (%sms)")
00190                 % m_list->size() % t
00191         );
00192 }
00193 
00194 Credits* CreditsDb::find(const PublicKey &key) const {
00195         CHECK_THROW(key.size());
00196         KeyIter it = m_list->get<ID_PubKey>().find(key);
00197         return it == m_list->get<ID_PubKey>().end() ? 0 : *it;
00198 }
00199 
00200 Credits* CreditsDb::find(const Hash<MD4Hash> &hash) const {
00201         CHECK_THROW(hash);
00202         HashIter it = m_list->get<ID_Hash>().find(hash);
00203         return it == m_list->get<ID_Hash>().end() ? 0 : *it;
00204 }
00205 
00206 void CreditsDb::initCrypting() {
00207         using namespace boost::filesystem;
00208 
00209         path keyPath = ED2K::instance().getConfigDir()/"cryptkey.dat";
00210 
00211         if (!exists(keyPath)) {
00212                 createCryptKey(keyPath.native_file_string());
00213         }
00214 
00215         try {
00216                 loadCryptKey(keyPath.native_file_string());
00217         } catch (std::exception &) {
00218                 logWarning(
00219                         "Failed to load personal RSA key; creating new one."
00220                 );
00221                 createCryptKey(keyPath.native_file_string());
00222                 loadCryptKey(keyPath.native_file_string());
00223         }
00224 }
00225 
00226 void CreditsDb::loadCryptKey(const std::string &where) {
00227         using namespace CryptoPP;
00228 
00229         // load private key
00230         FileSource src(where.c_str(), true, new Base64Decoder);
00231         s_signer.reset(new Signer(src));
00232         s_verifier.reset(new Verifier(*s_signer));
00233 
00234         // pubkey itself
00235         boost::scoped_array<uint8_t> tmp(new uint8_t[80]);
00236         ArraySink sink(tmp.get(), 80);
00237         s_verifier->DEREncode(sink);
00238         sink.MessageEnd();
00239 
00240         uint8_t keySize = sink.TotalPutLength();
00241         m_pubKey = std::string(reinterpret_cast<char*>(tmp.get()), keySize);
00242 
00243         logMsg("RSA keypair loaded successfully.");
00244 }
00245 
00246 void CreditsDb::createCryptKey(const std::string &where) {
00247         using namespace CryptoPP;
00248 
00249         AutoSeededRandomPool rng;
00250         InvertibleRSAFunction privKey;
00251         privKey.Initialize(rng, 384); // 384-bit encryption
00252         Base64Encoder privKeySink(new FileSink(where.c_str()));
00253         privKey.DEREncode(privKeySink);
00254         privKeySink.MessageEnd();
00255 
00256         logMsg("Created new RSA keypair");
00257 }
00258 
00259 // creates a signature to be sent to remote client
00260 // All the typecasting in here is needed, 'cos CryptoPP library wants uint8_t*
00261 // type input, but we are always working with std::string, which only accepts
00262 // sint8_t type input.
00263 std::string CreditsDb::createSignature(
00264         PublicKey key, uint32_t challenge, IpType ipType, uint32_t ip
00265 ) {
00266         CHECK_THROW(key);
00267 
00268         // construct the message
00269         std::ostringstream tmp;
00270         Utils::putVal(tmp, key.c_str(), key.size());
00271         Utils::putVal<uint32_t>(tmp, challenge);
00272         if (ipType) {
00273                 Utils::putVal<uint32_t>(tmp, ip);
00274                 Utils::putVal<uint8_t>(tmp, ipType);
00275         }
00276 
00277         std::string msg(tmp.str());
00278         const uint8_t *msgPtr = reinterpret_cast<const uint8_t*>(msg.c_str());
00279 
00280         // sign the message
00281         CryptoPP::SecByteBlock sign(s_signer->SignatureLength());
00282         CryptoPP::AutoSeededRandomPool rng;
00283         s_signer->SignMessage(rng, msgPtr, msg.size(), sign.begin());
00284 
00285         boost::scoped_array<uint8_t> out(new uint8_t[200]);
00286         CryptoPP::ArraySink asink(out.get(), 200);
00287         asink.Put(sign.begin(), sign.size());
00288 
00289         const char *retPtr = reinterpret_cast<const char*>(out.get());
00290         return std::string(retPtr, asink.TotalPutLength());
00291 }
00292 
00293 bool CreditsDb::verifySignature(
00294         PublicKey key, uint32_t challenge, const std::string &sign,
00295         IpType ipType, uint32_t ip
00296 ) {
00297         CHECK_THROW(key);
00298         CHECK_THROW(challenge);
00299         CHECK_THROW(sign.size());
00300 
00301         CryptoPP::StringSource sPKey(key.c_str(), key.size(), true, 0);
00302         Verifier verifier(sPKey);
00303 
00304         std::ostringstream tmp;
00305         Utils::putVal(
00306                 tmp, instance().m_pubKey.c_str(), instance().m_pubKey.size()
00307         );
00308         Utils::putVal<uint32_t>(tmp, challenge);
00309         if (ipType) {
00310                 Utils::putVal<uint32_t>(tmp, ip);
00311                 Utils::putVal<uint8_t>(tmp, ipType);
00312         }
00313         std::string msg(tmp.str());
00314 
00315         const uint8_t *msgPtr = reinterpret_cast<const uint8_t*>(msg.c_str());
00316         const uint8_t *sigPtr = reinterpret_cast<const uint8_t*>(sign.c_str());
00317         return verifier.VerifyMessage(msgPtr, msg.size(), sigPtr, sign.size());
00318 }
00319 
00320 Credits* CreditsDb::create(PublicKey key, const Hash<MD4Hash> &hash) {
00321         KeyIter it = m_list->get<ID_PubKey>().find(key);
00322         if (it != m_list->get<ID_PubKey>().end()) {
00323                 return *it;
00324         } else {
00325                 Credits *c = new Credits(key, hash);
00326                 m_list->insert(c);
00327                 return c;
00328         }
00329 }