diff --git a/src/Makefile.am b/src/Makefile.am index e8b3185b3518..de6a47d214c8 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -46,6 +46,7 @@ BITCOIN_CORE_H = \ init.h \ instantx.h \ key.h \ + keepass.h \ keystore.h \ leveldbwrapper.h \ limitedmap.h \ @@ -138,6 +139,7 @@ libdarkcoin_wallet_a_SOURCES = \ rpcwallet.cpp \ wallet.cpp \ walletdb.cpp \ + keepass.cpp \ $(BITCOIN_CORE_H) libdarkcoin_common_a_SOURCES = \ @@ -148,7 +150,7 @@ libdarkcoin_common_a_SOURCES = \ core.cpp \ darksend.cpp \ masternode.cpp \ - masternodeconfig.cpp \ + masternodeconfig.cpp \ instantx.cpp \ hash.cpp \ key.cpp \ diff --git a/src/crypter.cpp b/src/crypter.cpp index 4c43e3a7986d..be8137234c7e 100644 --- a/src/crypter.cpp +++ b/src/crypter.cpp @@ -110,6 +110,43 @@ bool EncryptSecret(const CKeyingMaterial& vMasterKey, const CKeyingMaterial &vch return cKeyCrypter.Encrypt(*((const CKeyingMaterial*)&vchPlaintext), vchCiphertext); } + +// General secure AES 256 CBC encryption routine +bool EncryptAES256(const SecureString& sKey, const SecureString& sPlaintext, const std::string& sIV, std::string& sCiphertext) +{ + // max ciphertext len for a n bytes of plaintext is + // n + AES_BLOCK_SIZE - 1 bytes + int nLen = sPlaintext.size(); + int nCLen = nLen + AES_BLOCK_SIZE; + int nFLen = 0; + + // Verify key sizes + if(sKey.size() != 32 || sIV.size() != AES_BLOCK_SIZE) { + LogPrintf("crypter EncryptAES256 - Invalid key or block size: Key: %d sIV:%d\n", sKey.size(), sIV.size()); + return false; + } + + // Prepare output buffer + sCiphertext.resize(nCLen); + + // Perform the encryption + EVP_CIPHER_CTX ctx; + + bool fOk = true; + + EVP_CIPHER_CTX_init(&ctx); + if (fOk) fOk = EVP_EncryptInit_ex(&ctx, EVP_aes_256_cbc(), NULL, (const unsigned char*) &sKey[0], (const unsigned char*) &sIV[0]); + if (fOk) fOk = EVP_EncryptUpdate(&ctx, (unsigned char*) &sCiphertext[0], &nCLen, (const unsigned char*) &sPlaintext[0], nLen); + if (fOk) fOk = EVP_EncryptFinal_ex(&ctx, (unsigned char*) (&sCiphertext[0])+nCLen, &nFLen); + EVP_CIPHER_CTX_cleanup(&ctx); + + if (!fOk) return false; + + sCiphertext.resize(nCLen + nFLen); + return true; +} + + bool DecryptSecret(const CKeyingMaterial& vMasterKey, const std::vector& vchCiphertext, const uint256& nIV, CKeyingMaterial& vchPlaintext) { CCrypter cKeyCrypter; @@ -120,6 +157,37 @@ bool DecryptSecret(const CKeyingMaterial& vMasterKey, const std::vector &vchCiphertext); bool DecryptSecret(const CKeyingMaterial& vMasterKey, const std::vector& vchCiphertext, const uint256& nIV, CKeyingMaterial& vchPlaintext); +bool EncryptAES256(const SecureString& sKey, const SecureString& sPlaintext, const std::string& sIV, std::string& sCiphertext); +bool DecryptAES256(const SecureString& sKey, const std::string& sCiphertext, const std::string& sIV, SecureString& sPlaintext); + + /** Keystore which keeps the private keys encrypted. * It derives from the basic key store, which is used if no encryption is active. */ diff --git a/src/init.cpp b/src/init.cpp index 541d497a0279..f25711e3d34b 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -25,6 +25,7 @@ #include "db.h" #include "wallet.h" #include "walletdb.h" +#include "keepass.h" #endif #include @@ -248,6 +249,11 @@ std::string HelpMessage(HelpMessageMode hmm) #ifdef ENABLE_WALLET strUsage += "\n" + _("Wallet options:") + "\n"; strUsage += " -disablewallet " + _("Do not load the wallet and disable wallet RPC calls") + "\n"; + strUsage += " -keepass " + _("Use KeePass 2 integration using KeePassHttp plugin (default: 0)") + "\n"; + strUsage += " -keepassport= " + _("Connect to KeePassHttp on port (default: 19455)") + "\n"; + strUsage += " -keepasskey= " + _("KeePassHttp key for AES encrypted communication with KeePass") + "\n"; + strUsage += " -keepassid= " + _("KeePassHttp id for the established association") + "\n"; + strUsage += " -keepassname= " + _("Name to construct url for KeePass entry that stores the wallet passphrase") + "\n"; strUsage += " -keypool= " + _("Set key pool size to (default: 100)") + "\n"; strUsage += " -paytxfee= " + _("Fee per kB to add to transactions you send") + "\n"; strUsage += " -rescan " + _("Rescan the block chain for missing wallet transactions") + " " + _("on startup") + "\n"; @@ -707,6 +713,10 @@ bool AppInit2(boost::thread_group& threadGroup) if (r == CDBEnv::RECOVER_FAIL) return InitError(_("wallet.dat corrupt, salvage failed")); } + + // Initialize KeePass Integration + keePassInt.init(); + } // (!fDisableWallet) #endif // ENABLE_WALLET // ********************************************************* Step 6: network initialization diff --git a/src/keepass.cpp b/src/keepass.cpp new file mode 100644 index 000000000000..b3fbc7fdabca --- /dev/null +++ b/src/keepass.cpp @@ -0,0 +1,439 @@ +// Copyright (c) 2014 The Darkcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "keepass.h" + +#include +#include +#include +#include +#include + +#include "util.h" +#include "json/json_spirit_writer_template.h" +#include "json/json_spirit_reader_template.h" +#include "rpcprotocol.h" +#include "script.h" // Necessary to prevent compile errors due to forward declaration of +//CScript in serialize.h (included from crypter.h) +#include "crypter.h" + +using boost::asio::ip::tcp; + +CKeePassIntegrator keePassInt; + +CKeePassIntegrator::CKeePassIntegrator() + :sKeyBase64(" "), sKey(" "), sUrl(" ") // Prevent LockedPageManagerBase complaints +{ + sKeyBase64.clear(); // Prevent LockedPageManagerBase complaints + sKey.clear(); // Prevent LockedPageManagerBase complaints + sUrl.clear(); // Prevent LockedPageManagerBase complaints + bIsActive = false; + nPort = KEEPASS_KEEPASSHTTP_PORT; +} + +// Initialze from application context +void CKeePassIntegrator::init() { + bIsActive = GetBoolArg("-keepass", false); + nPort = boost::lexical_cast(GetArg("-keepassport", KEEPASS_KEEPASSHTTP_PORT)); + sKeyBase64 = SecureString(GetArg("-keepasskey", "").c_str()); + sKeePassId = GetArg("-keepassid", ""); + sKeePassEntryName = GetArg("-keepassname", ""); + // Convert key if available + if(sKeyBase64.size() > 0) { + sKey = DecodeBase64Secure(sKeyBase64); + } + // Construct url if available + if(sKeePassEntryName.size() > 0) { + sUrl = SecureString("http://"); + sUrl += SecureString(sKeePassEntryName.c_str()); + sUrl += SecureString("/"); + //sSubmitUrl = "http://"; + //sSubmitUrl += SecureString(sKeePassEntryName.c_str()); + } +} + +void CKeePassIntegrator::CKeePassRequest::addStrParameter(std::string sName, std::string sValue) { + requestObj.push_back(json_spirit::Pair(sName, sValue)); +} + +void CKeePassIntegrator::CKeePassRequest::addStrParameter(std::string sName, SecureString sValue) { + std::string sCipherValue; + + if(!EncryptAES256(sKey, sValue, sIV, sCipherValue)) { + throw std::runtime_error("Unable to encrypt Verifier"); + } + + addStrParameter(sName, EncodeBase64(sCipherValue)); +} + +std::string CKeePassIntegrator::CKeePassRequest::getJson() { + return json_spirit::write_string(json_spirit::Value(requestObj), false); +} + +void CKeePassIntegrator::CKeePassRequest::init() { + SecureString sIVSecure = generateRandomKey(KEEPASS_CRYPTO_BLOCK_SIZE); + sIV = std::string(&sIVSecure[0], sIVSecure.size()); + // Generate Nonce, Verifier and RequestType + SecureString sNonceBase64Secure = EncodeBase64Secure(sIVSecure); + addStrParameter("Nonce", std::string(&sNonceBase64Secure[0], sNonceBase64Secure.size())); // Plain + addStrParameter("Verifier", sNonceBase64Secure); // Encoded + addStrParameter("RequestType", sType); +} + +void CKeePassIntegrator::CKeePassResponse::parseResponse(std::string sResponse) { + json_spirit::Value responseValue; + if(!json_spirit::read_string(sResponse, responseValue)) { + throw std::runtime_error("Unable to parse KeePassHttp response"); + } + + responseObj = responseValue.get_obj(); + + // retrieve main values + bSuccess = json_spirit::find_value(responseObj, "Success").get_bool(); + sType = getStr("RequestType"); + sIV = DecodeBase64(getStr("Nonce")); +} + +std::string CKeePassIntegrator::CKeePassResponse::getStr(std::string sName) { + //LogPrintf("CKeePassResponse::getStr sName: [%s]\n", sName.c_str()); + std::string sValue(json_spirit::find_value(responseObj, sName).get_str()); + //LogPrintf("CKeePassResponse::getStr sValue: [%s]\n", sValue.c_str()); + return sValue; +} + +SecureString CKeePassIntegrator::CKeePassResponse::getSecureStr(std::string sName) { + LogPrintf("CKeePassResponse::getSecureStr sName: [%s]\n", sName.c_str()); + std::string sValueBase64Encrypted(json_spirit::find_value(responseObj, sName).get_str()); + SecureString sValue; + try { + sValue = decrypt(sValueBase64Encrypted); + } catch (std::exception &e) { + std::string sErrorMessage = "Exception occured while decrypting "; + sErrorMessage += sName + ": " + e.what(); + throw std::runtime_error(sErrorMessage); + } + return sValue; +} + +SecureString CKeePassIntegrator::CKeePassResponse::decrypt(std::string sValueBase64Encrypted) { + std::string sValueEncrypted = DecodeBase64(sValueBase64Encrypted); + SecureString sValue; + if(!DecryptAES256(sKey, sValueEncrypted, sIV, sValue)) { + throw std::runtime_error("Unable to decrypt value."); + } + return sValue; +} + +std::vector CKeePassIntegrator::CKeePassResponse::getEntries() { + + std::vector vEntries; + + json_spirit::Array aEntries = json_spirit::find_value(responseObj, "Entries").get_array(); + for(json_spirit::Array::iterator it = aEntries.begin(); it != aEntries.end(); ++it) { + SecureString sEntryUuid(decrypt(json_spirit::find_value((*it).get_obj(), "Uuid").get_str().c_str())); + SecureString sEntryName(decrypt(json_spirit::find_value((*it).get_obj(), "Name").get_str().c_str())); + SecureString sEntryLogin(decrypt(json_spirit::find_value((*it).get_obj(), "Login").get_str().c_str())); + SecureString sEntryPassword(decrypt(json_spirit::find_value((*it).get_obj(), "Password").get_str().c_str())); + CKeePassEntry entry(sEntryUuid, sEntryUuid, sEntryLogin, sEntryPassword); + vEntries.push_back(entry); + } + + return vEntries; + +} + +SecureString CKeePassIntegrator::generateRandomKey(size_t nSize) { + // Generates random key + SecureString key; + key.resize(nSize); + + RandAddSeedPerfmon(); + RAND_bytes((unsigned char *) &key[0], nSize); + + return key; +} + +// Construct POST body for RPC JSON call +std::string CKeePassIntegrator::constructHTTPPost(const std::string& strMsg, const std::map& mapRequestHeaders) +{ + std::ostringstream s; + s << "POST / HTTP/1.1\r\n" + << "User-Agent: darkcoin-json-rpc/" << FormatFullVersion() << "\r\n" + << "Host: localhost\r\n" + << "Content-Type: application/json\r\n" + << "Content-Length: " << strMsg.size() << "\r\n" + << "Connection: close\r\n" + << "Accept: application/json\r\n"; + BOOST_FOREACH(const PAIRTYPE(std::string, std::string)& item, mapRequestHeaders) + s << item.first << ": " << item.second << "\r\n"; + s << "\r\n" << strMsg; + + return s.str(); +} + +// Send RPC message to KeePassHttp +void CKeePassIntegrator::doHTTPPost(const std::string& sRequest, int& nStatus, std::string& sResponse) { + // Prepare communication + boost::asio::io_service io_service; + + // Get a list of endpoints corresponding to the server name. + tcp::resolver resolver(io_service); + tcp::resolver::query query(KEEPASS_KEEPASSHTTP_HOST, boost::lexical_cast(nPort)); + tcp::resolver::iterator endpoint_iterator = resolver.resolve(query); + tcp::resolver::iterator end; + + // Try each endpoint until we successfully establish a connection. + tcp::socket socket(io_service); + boost::system::error_code error = boost::asio::error::host_not_found; + while (error && endpoint_iterator != end) + { + socket.close(); + socket.connect(*endpoint_iterator++, error); + } + + if(error) { + throw boost::system::system_error(error); + } + + // Form the request. + std::map mapRequestHeaders; + std::string strPost = constructHTTPPost(sRequest, mapRequestHeaders); + + LogPrintf("CKeePassIntegrator::send - POST data: %s\n", strPost.c_str()); + + boost::asio::streambuf request; + std::ostream request_stream(&request); + request_stream << strPost; + + // Send the request. + boost::asio::write(socket, request); + + LogPrintf("CKeePassIntegrator::send - request written\n"); + + // Read the response status line. The response streambuf will automatically + // grow to accommodate the entire line. The growth may be limited by passing + // a maximum size to the streambuf constructor. + boost::asio::streambuf response; + boost::asio::read_until(socket, response, "\r\n"); + + LogPrintf("CKeePassIntegrator::send - request status line read\n"); + + // Receive HTTP reply status + int nProto = 0; + std::istream response_stream(&response); + nStatus = ReadHTTPStatus(response_stream, nProto); + + LogPrintf("CKeePassIntegrator::send - reading body start\n"); + // Read until EOF, writing data to output as we go. + while (boost::asio::read(socket, response, boost::asio::transfer_at_least(1), error)) + { + if (error != boost::asio::error::eof) { + if (error != 0) { // 0 is success + throw boost::system::system_error(error); + } + } + } + LogPrintf("CKeePassIntegrator::send - reading body end\n"); + + // Receive HTTP reply message headers and body + std::map mapHeaders; + ReadHTTPMessage(response_stream, mapHeaders, sResponse, nProto); + LogPrintf("CKeePassIntegrator::send - Processed body\n"); +} + +void CKeePassIntegrator::rpcTestAssociation(bool bTriggerUnlock) { + CKeePassRequest request(sKey, "test-associate"); + request.addStrParameter("TriggerUnlock", std::string(bTriggerUnlock ? "true" : "false")); + + int nStatus; + std::string sResponse; + + doHTTPPost(request.getJson(), nStatus, sResponse); + + LogPrintf("CKeePassIntegrator::testAssociation - send result: status: %d response: %s\n", nStatus, sResponse.c_str()); +} + +std::vector CKeePassIntegrator::rpcGetLogins() { + + // Convert key format + SecureString sKey = DecodeBase64Secure(sKeyBase64); + + CKeePassRequest request(sKey, "get-logins"); + request.addStrParameter("addStrParameter", std::string("true")); + request.addStrParameter("TriggerUnlock", std::string("true")); + request.addStrParameter("Id", sKeePassId); + request.addStrParameter("Url", sUrl); + + int nStatus; + std::string sResponse; + + doHTTPPost(request.getJson(), nStatus, sResponse); + + LogPrintf("CKeePassIntegrator::getLogin - send result: status: %d response: %s\n", nStatus, sResponse.c_str()); + + if(nStatus != 200) { + std::string sErrorMessage = "Error returned by KeePassHttp: HTTP code "; + sErrorMessage += boost::lexical_cast(nStatus); + sErrorMessage += " - Response: "; + sErrorMessage += " response: ["; + sErrorMessage += sResponse; + sErrorMessage += "]"; + throw std::runtime_error(sErrorMessage); + } + + // Parse the response + CKeePassResponse response(sKey, sResponse); + + if(!response.getSuccess()) { + std::string sErrorMessage = "KeePassHttp returned failure status"; + throw std::runtime_error(sErrorMessage); + } + + return response.getEntries(); +} + +void CKeePassIntegrator::rpcSetLogin(const SecureString& strWalletPass, const SecureString& sEntryId) { + + // Convert key format + SecureString sKey = DecodeBase64Secure(sKeyBase64); + + CKeePassRequest request(sKey, "set-login"); + request.addStrParameter("Id", sKeePassId); + request.addStrParameter("Url", sUrl); + LogPrintf("CKeePassIntegrator::setLogin - send Url: %s\n", sUrl.c_str()); + + //request.addStrParameter("SubmitUrl", sSubmitUrl); // Is used to construct the entry title + request.addStrParameter("Login", SecureString("darkcoin")); + request.addStrParameter("Password", strWalletPass); + if(sEntryId.size() != 0) { + request.addStrParameter("Uuid", sEntryId); // Update existing + } + + int nStatus; + std::string sResponse; + + doHTTPPost(request.getJson(), nStatus, sResponse); + + LogPrintf("CKeePassIntegrator::setLogin - send result: status: %d response: %s\n", nStatus, sResponse.c_str()); + + if(nStatus != 200) { + std::string sErrorMessage = "Error returned: HTTP code "; + sErrorMessage += boost::lexical_cast(nStatus); + sErrorMessage += " - Response: "; + sErrorMessage += " response: ["; + sErrorMessage += sResponse; + sErrorMessage += "]"; + throw std::runtime_error(sErrorMessage); + } + + // Parse the response + CKeePassResponse response(sKey, sResponse); + + if(!response.getSuccess()) { + throw std::runtime_error("KeePassHttp returned failure status"); + } +} + + +SecureString CKeePassIntegrator::generateKeePassKey() { + SecureString sKey = generateRandomKey(KEEPASS_CRYPTO_KEY_SIZE); + SecureString sKeyBase64 = EncodeBase64Secure(sKey); + return sKeyBase64; +} + +void CKeePassIntegrator::rpcAssociate(std::string& sId, SecureString& sKeyBase64) { + sKey = generateRandomKey(KEEPASS_CRYPTO_KEY_SIZE); + CKeePassRequest request(sKey, "associate"); + + sKeyBase64 = EncodeBase64Secure(sKey); + request.addStrParameter("Key", std::string(&sKeyBase64[0], sKeyBase64.size())); + + int nStatus; + std::string sResponse; + + doHTTPPost(request.getJson(), nStatus, sResponse); + + LogPrintf("CKeePassIntegrator::associate_new - send result: status: %d response: %s\n", nStatus, sResponse.c_str()); + + if(nStatus != 200) { + std::string sErrorMessage = "Error returned: HTTP code "; + sErrorMessage += boost::lexical_cast(nStatus); + sErrorMessage += " - Response: "; + sErrorMessage += " response: ["; + sErrorMessage += sResponse; + sErrorMessage += "]"; + throw std::runtime_error(sErrorMessage); + } + + // Parse the response + CKeePassResponse response(sKey, sResponse); + + if(!response.getSuccess()) { + throw std::runtime_error("KeePassHttp returned failure status"); + } + + // If we got here, we were successful. Return the information + sId = response.getStr("Id"); +} + +// Retrieve wallet passphrase from KeePass +SecureString CKeePassIntegrator::retrievePassphrase() { + + // Check we have all required information + if(sKey.size() == 0) { + throw std::runtime_error("keepasskey parameter is not defined. Please specify the configuration parameter."); + } + if(sKeePassId.size() == 0) { + throw std::runtime_error("keepassid parameter is not defined. Please specify the configuration parameter."); + } + if(sKeePassEntryName == "") { + throw std::runtime_error("keepassname parameter is not defined. Please specify the configuration parameter."); + } + + // Retrieve matching logins from KeePass + std::vector entries = rpcGetLogins(); + + // Only accept one unique match + if(entries.size() == 0) { + throw std::runtime_error("KeePassHttp returned 0 matches, please verify the keepassurl setting."); + } + if(entries.size() > 1) { + throw std::runtime_error("KeePassHttp returned multiple matches, bailing out."); + } + + return entries[0].getPassword(); +} + +// Update wallet passphrase in keepass +void CKeePassIntegrator::updatePassphrase(const SecureString& sWalletPassphrase) { + + // Check we have all required information + if(sKey.size() == 0) { + throw std::runtime_error("keepasskey parameter is not defined. Please specify the configuration parameter."); + } + if(sKeePassId.size() == 0) { + throw std::runtime_error("keepassid parameter is not defined. Please specify the configuration parameter."); + } + if(sKeePassEntryName == "") { + throw std::runtime_error("keepassname parameter is not defined. Please specify the configuration parameter."); + } + + SecureString sEntryId(""); + + std::string sErrorMessage; + + // Lookup existing entry + std::vector vEntries = rpcGetLogins(); + + if(vEntries.size() > 1) { + throw std::runtime_error("KeePassHttp returned multiple matches, bailing out."); + } + + if(vEntries.size() == 1) { + sEntryId = vEntries[0].getUuid(); + } + + // Update wallet passphrase in KeePass + rpcSetLogin(sWalletPassphrase, sEntryId); +} diff --git a/src/keepass.h b/src/keepass.h new file mode 100644 index 000000000000..60f8f0127565 --- /dev/null +++ b/src/keepass.h @@ -0,0 +1,133 @@ +// Copyright (c) 2014 The Darkcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef _KEEPASS_H_ +#define _KEEPASS_H_ + +#define KEEPASS_CRYPTO_KEY_SIZE 32 +#define KEEPASS_CRYPTO_BLOCK_SIZE 16 +#define KEEPASS_KEEPASSHTTP_HOST "localhost" +#define KEEPASS_KEEPASSHTTP_PORT 19455 + +#include +#include +#include + +#include "json/json_spirit_value.h" +#include "crypter.h" +#include "allocators.h" + +class CKeePassIntegrator { + + bool bIsActive; + unsigned int nPort; + SecureString sKeyBase64; + SecureString sKey; + SecureString sUrl; + //SecureString sSubmitUrl; + std::string sKeePassId; + std::string sKeePassEntryName; + + class CKeePassRequest { + + json_spirit::Object requestObj; + std::string sType; + std::string sIV; + SecureString sKey; + + void init(); + + public: + void addStrParameter(std::string sName, std::string sValue); // Regular + void addStrParameter(std::string sName, SecureString sValue); // Encrypt + std::string getJson(); + + CKeePassRequest(SecureString sKey, std::string sType) + { + this->sKey = sKey; + this->sType = sType; + init(); + }; + }; + + + class CKeePassEntry { + + SecureString uuid; + SecureString name; + SecureString login; + SecureString password; + + public: + CKeePassEntry(SecureString uuid, SecureString name, SecureString login, SecureString password) : + uuid(uuid), name(name), login(login), password(password) { + } + + SecureString getUuid() { + return uuid; + } + + SecureString getName() { + return name; + } + + SecureString getLogin() { + return login; + } + + SecureString getPassword() { + return password; + } + + }; + + + class CKeePassResponse { + + bool bSuccess; + std::string sType; + std::string sIV; + SecureString sKey; + + void parseResponse(std::string sResponse); + + public: + json_spirit::Object responseObj; + CKeePassResponse(SecureString sKey, std::string sResponse) { + this->sKey = sKey; + parseResponse(sResponse); + } + + bool getSuccess() { + return bSuccess; + } + + SecureString getSecureStr(std::string sName); + std::string getStr(std::string sName); + std::vector getEntries(); + + SecureString decrypt(std::string sValue); // DecodeBase64 and decrypt arbitrary string value + + }; + + static SecureString generateRandomKey(size_t nSize); + static std::string constructHTTPPost(const std::string& strMsg, const std::map& mapRequestHeaders); + void doHTTPPost(const std::string& sRequest, int& nStatus, std::string& sResponse); + void rpcTestAssociation(bool bTriggerUnlock); + std::vector rpcGetLogins(); + void rpcSetLogin(const SecureString& strWalletPass, const SecureString& sEntryId); + +public: + CKeePassIntegrator(); + void init(); + static SecureString generateKeePassKey(); + void rpcAssociate(std::string& sId, SecureString& sKeyBase64); + SecureString retrievePassphrase(); + void updatePassphrase(const SecureString& sWalletPassphrase); + +}; + +extern CKeePassIntegrator keePassInt; + +#endif diff --git a/src/rpcprotocol.cpp b/src/rpcprotocol.cpp index 99e3febd0938..e5ebc2a754ea 100644 --- a/src/rpcprotocol.cpp +++ b/src/rpcprotocol.cpp @@ -138,6 +138,7 @@ int ReadHTTPStatus(std::basic_istream& stream, int &proto) { string str; getline(stream, str); + //LogPrintf("ReadHTTPStatus - getline string: %s\n",str.c_str()); vector vWords; boost::split(vWords, str, boost::is_any_of(" ")); if (vWords.size() < 2) diff --git a/src/rpcserver.cpp b/src/rpcserver.cpp index 22aedb585ca4..e992db9f2109 100644 --- a/src/rpcserver.cpp +++ b/src/rpcserver.cpp @@ -293,6 +293,7 @@ static const CRPCCommand vRPCCommands[] = { "getwalletinfo", &getwalletinfo, true, false, true }, { "importprivkey", &importprivkey, false, false, true }, { "importwallet", &importwallet, false, false, true }, + { "keepass", &keepass, false, false, true }, { "keypoolrefill", &keypoolrefill, true, false, true }, { "listaccounts", &listaccounts, false, false, true }, { "listaddressgroupings", &listaddressgroupings, false, false, true }, diff --git a/src/rpcserver.h b/src/rpcserver.h index aa2f0037a0fb..046a8a21bc09 100644 --- a/src/rpcserver.h +++ b/src/rpcserver.h @@ -166,6 +166,7 @@ extern json_spirit::Value getinfo(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value getwalletinfo(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value getblockchaininfo(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value getnetworkinfo(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value keepass(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value getrawtransaction(const json_spirit::Array& params, bool fHelp); // in rcprawtransaction.cpp extern json_spirit::Value listunspent(const json_spirit::Array& params, bool fHelp); diff --git a/src/rpcwallet.cpp b/src/rpcwallet.cpp index 23a8772ffad7..a70b290e71a1 100644 --- a/src/rpcwallet.cpp +++ b/src/rpcwallet.cpp @@ -12,6 +12,7 @@ #include "util.h" #include "wallet.h" #include "walletdb.h" +#include "keepass.h" #include @@ -1921,3 +1922,56 @@ Value getwalletinfo(const Array& params, bool fHelp) obj.push_back(Pair("unlocked_until", nWalletUnlockTime)); return obj; } + +Value keepass(const Array& params, bool fHelp) { + string strCommand; + + if (params.size() >= 1) + strCommand = params[0].get_str(); + + if (fHelp || + (strCommand != "genkey" && strCommand != "init" && strCommand != "setpassphrase")) + throw runtime_error( + "keepass \n"); + + if (strCommand == "genkey") + { + SecureString result; + // Generate RSA key + //std::string keePassKey = CKeePassIntegrator::generateKey(); + //return keePassKey; + SecureString sKey = CKeePassIntegrator::generateKeePassKey(); + result = "Generated Key: "; + result += sKey; + return result.c_str(); + } + else if(strCommand == "init") + { + // Generate base64 encoded 256 bit RSA key and associate with KeePassHttp + SecureString result; + SecureString sKey; + std::string sId; + std::string sErrorMessage; + keePassInt.rpcAssociate(sId, sKey); + result = "Association successful. Id: "; + result += sId.c_str(); + result += " - Key: "; + result += sKey.c_str(); + return result.c_str(); + } + else if(strCommand == "setpassphrase") + { + if(params.size() != 2) { + return "setlogin: invalid number of parameters. Requires a passphrase"; + } + + SecureString sPassphrase = SecureString(params[1].get_str().c_str()); + + keePassInt.updatePassphrase(sPassphrase); + + return "setlogin: Updated credentials."; + } + + return "Invalid command"; + +} diff --git a/src/util.cpp b/src/util.cpp index 4ff1a9db7ed1..ae5ebc1520f8 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -12,10 +12,16 @@ #include "ui_interface.h" #include "uint256.h" #include "version.h" +#include "allocators.h" #include #include +#include +#include +#include +#include // for OPENSSL_cleanse() + #ifndef WIN32 // for posix_fallocate @@ -612,6 +618,33 @@ string EncodeBase64(const string& str) return EncodeBase64((const unsigned char*)str.c_str(), str.size()); } +// Base64 encoding with secure memory allocation +SecureString EncodeBase64Secure(const SecureString& input) +{ + // Init openssl BIO with base64 filter and memory output + BIO *b64, *mem; + b64 = BIO_new(BIO_f_base64()); + BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); // No newlines in output + mem = BIO_new(BIO_s_mem()); + BIO_push(b64, mem); + + // Decode the string + BIO_write(b64, &input[0], input.size()); + (void) BIO_flush(b64); + + // Create output variable from buffer mem ptr + BUF_MEM *bptr; + BIO_get_mem_ptr(b64, &bptr); + SecureString output(bptr->data, bptr->length); + + // Cleanse secure data buffer from memory + OPENSSL_cleanse((void *) bptr->data, bptr->length); + + // Free memory + BIO_free_all(b64); + return output; +} + vector DecodeBase64(const char* p, bool* pfInvalid) { static const int decode64_table[256] = @@ -701,6 +734,35 @@ string DecodeBase64(const string& str) return string((const char*)&vchRet[0], vchRet.size()); } +// Base64 decoding with secure memory allocation +SecureString DecodeBase64Secure(const SecureString& input) +{ + SecureString output; + + // Init openssl BIO with base64 filter and memory input + BIO *b64, *mem; + b64 = BIO_new(BIO_f_base64()); + BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); //Do not use newlines to flush buffer + mem = BIO_new_mem_buf((void *) &input[0], input.size()); + BIO_push(b64, mem); + + // Prepare buffer to receive decoded data + if(input.size() % 4 != 0) { + throw runtime_error("Input length should be a multiple of 4"); + } + size_t nMaxLen = input.size() / 4 * 3; // upper bound, guaranteed divisible by 4 + output.resize(nMaxLen); + + // Decode the string + size_t nLen; + nLen = BIO_read(b64, (void *) &output[0], input.size()); + output.resize(nLen); + + // Free memory + BIO_free_all(b64); + return output; +} + string EncodeBase32(const unsigned char* pch, size_t len) { static const char *pbase32 = "abcdefghijklmnopqrstuvwxyz234567"; @@ -888,7 +950,6 @@ string DecodeBase32(const string& str) return string((const char*)&vchRet[0], vchRet.size()); } - bool WildcardMatch(const char* psz, const char* mask) { while (true) diff --git a/src/util.h b/src/util.h index 746242262de4..54fba80c7f7f 100644 --- a/src/util.h +++ b/src/util.h @@ -177,8 +177,10 @@ std::vector ParseHex(const std::string& str); bool IsHex(const std::string& str); std::vector DecodeBase64(const char* p, bool* pfInvalid = NULL); std::string DecodeBase64(const std::string& str); +SecureString DecodeBase64Secure(const SecureString& input); std::string EncodeBase64(const unsigned char* pch, size_t len); std::string EncodeBase64(const std::string& str); +SecureString EncodeBase64Secure(const SecureString& input); std::vector DecodeBase32(const char* p, bool* pfInvalid = NULL); std::string DecodeBase32(const std::string& str); std::string EncodeBase32(const unsigned char* pch, size_t len); diff --git a/src/wallet.cpp b/src/wallet.cpp index 7ff8b0046b66..c3398587079b 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -11,6 +11,7 @@ #include "coincontrol.h" #include "net.h" #include "darksend.h" +#include "keepass.h" #include #include @@ -149,12 +150,26 @@ bool CWallet::LoadCScript(const CScript& redeemScript) bool CWallet::Unlock(const SecureString& strWalletPassphrase, bool anonymizeOnly) { + SecureString strWalletPassphraseFinal; + if (!IsLocked()) { fWalletUnlockAnonymizeOnly = anonymizeOnly; return true; } + // Verify KeePassIntegration + if(strWalletPassphrase == "keepass" && GetBoolArg("-keepass", false)) { + try { + strWalletPassphraseFinal = keePassInt.retrievePassphrase(); + } catch (std::exception& e) { + LogPrintf("CWallet::Unlock could not retrieve passphrase from KeePass: Error: %s\n", e.what()); + return false; + } + } else { + strWalletPassphraseFinal = strWalletPassphrase; + } + CCrypter crypter; CKeyingMaterial vMasterKey; @@ -162,7 +177,7 @@ bool CWallet::Unlock(const SecureString& strWalletPassphrase, bool anonymizeOnly LOCK(cs_wallet); BOOST_FOREACH(const MasterKeyMap::value_type& pMasterKey, mapMasterKeys) { - if(!crypter.SetKeyFromPassphrase(strWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod)) + if(!crypter.SetKeyFromPassphrase(strWalletPassphraseFinal, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod)) return false; if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, vMasterKey)) continue; // try another master key @@ -179,6 +194,22 @@ bool CWallet::Unlock(const SecureString& strWalletPassphrase, bool anonymizeOnly bool CWallet::ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, const SecureString& strNewWalletPassphrase) { bool fWasLocked = IsLocked(); + bool bUseKeePass = false; + + SecureString strOldWalletPassphraseFinal; + + // Verify KeePassIntegration + if(strOldWalletPassphrase == "keepass" && GetBoolArg("-keepass", false)) { + bUseKeePass = true; + try { + strOldWalletPassphraseFinal = keePassInt.retrievePassphrase(); + } catch (std::exception& e) { + LogPrintf("CWallet::ChangeWalletPassphrase could not retrieve passphrase from KeePass: Error: %s\n", e.what()); + return false; + } + } else { + strOldWalletPassphraseFinal = strOldWalletPassphrase; + } { LOCK(cs_wallet); @@ -188,7 +219,7 @@ bool CWallet::ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, CKeyingMaterial vMasterKey; BOOST_FOREACH(MasterKeyMap::value_type& pMasterKey, mapMasterKeys) { - if(!crypter.SetKeyFromPassphrase(strOldWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod)) + if(!crypter.SetKeyFromPassphrase(strOldWalletPassphraseFinal, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod)) return false; if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, vMasterKey)) return false; @@ -214,6 +245,18 @@ bool CWallet::ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, CWalletDB(strWalletFile).WriteMasterKey(pMasterKey.first, pMasterKey.second); if (fWasLocked) Lock(); + + // Update KeePass if necessary + if(bUseKeePass) { + LogPrintf("CWallet::ChangeWalletPassphrase - Updating KeePass with new passphrase"); + try { + keePassInt.updatePassphrase(strNewWalletPassphrase); + } catch (std::exception& e) { + LogPrintf("CWallet::ChangeWalletPassphrase - could not update passphrase in KeePass: Error: %s\n", e.what()); + return false; + } + } + return true; } } @@ -440,6 +483,16 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase) // bits of the unencrypted private key in slack space in the database file. CDB::Rewrite(strWalletFile); + // Update KeePass if necessary + if(GetBoolArg("-keepass", false)) { + LogPrintf("CWallet::EncryptWallet - Updating KeePass with new passphrase"); + try { + keePassInt.updatePassphrase(strWalletPassphrase); + } catch (std::exception& e) { + LogPrintf("CWallet::EncryptWallet - could not update passphrase in KeePass: Error: %s\n", e.what()); + } + } + } NotifyStatusChanged(this);