Implemented KeePass Integration
More info regarding KeePass: http://keepass.info/ KeePass integration will use KeePassHttp (https://github.com/pfn/keepasshttp/) to facilitate communications between the client and KeePass. KeePassHttp is a plugin for KeePass 2.x and provides a secure means of exposing KeePass entries via HTTP for clients to consume. The implementation is dependent on the following: - crypter.h for AES encryption helper functions. - rpcprotocol.h for handling RPC communications. Could only be used partially however due some static values in the code. - OpenSSL for base64 encoding. regular util.h libraries were not used for base64 encoding/decoding since they do not use secure allocation. - JSON Spirit for reading / writing RPC communications The following changes were made: - Added CLI options in help - Added RPC commands: keepass <genkey|init|setpassphrase> - Added keepass.h and keepass.cpp which hold the integration routines - Modified rpcwallet.cpp to support RPC commands The following new options are available for darkcoind and darkcoin-qt: -keepass Use KeePass 2 integration using KeePassHttp plugin (default: 0) -keepassport=<port> Connect to KeePassHttp on port <port> (default: 19455) -keepasskey=<key> KeePassHttp key for AES encrypted communication with KeePass -keepassid=<name> KeePassHttp id for the established association -keepassname=<name> Name to construct url for KeePass entry that stores the wallet passphrase The following rpc commands are available: - keepass genkey: generates a base64 encoded 256 bit AES key that can be used for the communication with KeePassHttp. Only necessary for manual configuration. Use init for automatic configuration. - keepass init: sets up the association between darkcoind and keepass by generating an AES key and sending an association message to KeePassHttp. This will trigger KeePass to ask for an Id for the association. Returns the association and the base64 encoded string for the AES key. - keepass setpassphrase <passphrase>: updates the passphrase in KeePassHttp to a new value. This should match the passphrase you intend to use for the wallet. Please note that the standard RPC commands walletpassphrasechange and the wallet encrption from the QT GUI already send the updates to KeePassHttp, so this is only necessary for manual manipulation of the password. Sample initialization flow from darkcoin-qt console (this needs to be done only once to set up the association): - Have KeePass running with an open database - Start darkcoin-qt - Open console - type: "keepass init" in darkcoin-qt console - (keepass pops up and asks for an association id, fill that in). Example: mydrkwallet - response: Association successful. Id: mydrkwalletdarkcoin - Key: AgQkcs6cI7v9tlSYKjG/+s8wJrGALHl3jLosJpPLzUE= - Edit darkcoin.conf and fill in these values keepass=1 keepasskey=AgQkcs6cI7v9tlSYKjG/+s8wJrGALHl3jLosJpPLzUE= keepassid=mydrkwallet keepassname=testwallet - Restart darkcoin-qt At this point, the association is made. The next action depends on your particular situation: - current wallet is not yet encrypted. Encrypting the wallet will trigger the integration and stores the password in KeePass (Under the 'KeePassHttp Passwords' group, named after keepassname. - current wallet is already encrypted: use "keepass setpassphrase <passphrase>" to store the passphrase in KeePass. At this point, the passphrase is stored in KeePassHttp. When Unlocking the wallet, one can use keepass as the passphrase to trigger retrieval of the password. This works from the RPC commands as well as the GUI.
This commit is contained in:
parent
9cb9b166a9
commit
bdb50539de
@ -46,6 +46,7 @@ BITCOIN_CORE_H = \
|
|||||||
init.h \
|
init.h \
|
||||||
instantx.h \
|
instantx.h \
|
||||||
key.h \
|
key.h \
|
||||||
|
keepass.h \
|
||||||
keystore.h \
|
keystore.h \
|
||||||
leveldbwrapper.h \
|
leveldbwrapper.h \
|
||||||
limitedmap.h \
|
limitedmap.h \
|
||||||
@ -138,6 +139,7 @@ libdarkcoin_wallet_a_SOURCES = \
|
|||||||
rpcwallet.cpp \
|
rpcwallet.cpp \
|
||||||
wallet.cpp \
|
wallet.cpp \
|
||||||
walletdb.cpp \
|
walletdb.cpp \
|
||||||
|
keepass.cpp \
|
||||||
$(BITCOIN_CORE_H)
|
$(BITCOIN_CORE_H)
|
||||||
|
|
||||||
libdarkcoin_common_a_SOURCES = \
|
libdarkcoin_common_a_SOURCES = \
|
||||||
|
@ -110,6 +110,43 @@ bool EncryptSecret(const CKeyingMaterial& vMasterKey, const CKeyingMaterial &vch
|
|||||||
return cKeyCrypter.Encrypt(*((const CKeyingMaterial*)&vchPlaintext), vchCiphertext);
|
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<unsigned char>& vchCiphertext, const uint256& nIV, CKeyingMaterial& vchPlaintext)
|
bool DecryptSecret(const CKeyingMaterial& vMasterKey, const std::vector<unsigned char>& vchCiphertext, const uint256& nIV, CKeyingMaterial& vchPlaintext)
|
||||||
{
|
{
|
||||||
CCrypter cKeyCrypter;
|
CCrypter cKeyCrypter;
|
||||||
@ -120,6 +157,37 @@ bool DecryptSecret(const CKeyingMaterial& vMasterKey, const std::vector<unsigned
|
|||||||
return cKeyCrypter.Decrypt(vchCiphertext, *((CKeyingMaterial*)&vchPlaintext));
|
return cKeyCrypter.Decrypt(vchCiphertext, *((CKeyingMaterial*)&vchPlaintext));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool DecryptAES256(const SecureString& sKey, const std::string& sCiphertext, const std::string& sIV, SecureString& sPlaintext)
|
||||||
|
{
|
||||||
|
// plaintext will always be equal to or lesser than length of ciphertext
|
||||||
|
int nLen = sCiphertext.size();
|
||||||
|
int nPLen = nLen, nFLen = 0;
|
||||||
|
|
||||||
|
// Verify key sizes
|
||||||
|
if(sKey.size() != 32 || sIV.size() != AES_BLOCK_SIZE) {
|
||||||
|
LogPrintf("crypter DecryptAES256 - Invalid key or block size\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
sPlaintext.resize(nPLen);
|
||||||
|
|
||||||
|
EVP_CIPHER_CTX ctx;
|
||||||
|
|
||||||
|
bool fOk = true;
|
||||||
|
|
||||||
|
EVP_CIPHER_CTX_init(&ctx);
|
||||||
|
if (fOk) fOk = EVP_DecryptInit_ex(&ctx, EVP_aes_256_cbc(), NULL, (const unsigned char*) &sKey[0], (const unsigned char*) &sIV[0]);
|
||||||
|
if (fOk) fOk = EVP_DecryptUpdate(&ctx, (unsigned char *) &sPlaintext[0], &nPLen, (const unsigned char *) &sCiphertext[0], nLen);
|
||||||
|
if (fOk) fOk = EVP_DecryptFinal_ex(&ctx, (unsigned char *) (&sPlaintext[0])+nPLen, &nFLen);
|
||||||
|
EVP_CIPHER_CTX_cleanup(&ctx);
|
||||||
|
|
||||||
|
if (!fOk) return false;
|
||||||
|
|
||||||
|
sPlaintext.resize(nPLen + nFLen);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
bool CCryptoKeyStore::SetCrypted()
|
bool CCryptoKeyStore::SetCrypted()
|
||||||
{
|
{
|
||||||
LOCK(cs_KeyStore);
|
LOCK(cs_KeyStore);
|
||||||
|
@ -107,6 +107,10 @@ public:
|
|||||||
bool EncryptSecret(const CKeyingMaterial& vMasterKey, const CKeyingMaterial &vchPlaintext, const uint256& nIV, std::vector<unsigned char> &vchCiphertext);
|
bool EncryptSecret(const CKeyingMaterial& vMasterKey, const CKeyingMaterial &vchPlaintext, const uint256& nIV, std::vector<unsigned char> &vchCiphertext);
|
||||||
bool DecryptSecret(const CKeyingMaterial& vMasterKey, const std::vector<unsigned char>& vchCiphertext, const uint256& nIV, CKeyingMaterial& vchPlaintext);
|
bool DecryptSecret(const CKeyingMaterial& vMasterKey, const std::vector<unsigned char>& 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.
|
/** Keystore which keeps the private keys encrypted.
|
||||||
* It derives from the basic key store, which is used if no encryption is active.
|
* It derives from the basic key store, which is used if no encryption is active.
|
||||||
*/
|
*/
|
||||||
|
10
src/init.cpp
10
src/init.cpp
@ -25,6 +25,7 @@
|
|||||||
#include "db.h"
|
#include "db.h"
|
||||||
#include "wallet.h"
|
#include "wallet.h"
|
||||||
#include "walletdb.h"
|
#include "walletdb.h"
|
||||||
|
#include "keepass.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
@ -248,6 +249,11 @@ std::string HelpMessage(HelpMessageMode hmm)
|
|||||||
#ifdef ENABLE_WALLET
|
#ifdef ENABLE_WALLET
|
||||||
strUsage += "\n" + _("Wallet options:") + "\n";
|
strUsage += "\n" + _("Wallet options:") + "\n";
|
||||||
strUsage += " -disablewallet " + _("Do not load the wallet and disable wallet RPC calls") + "\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=<port> " + _("Connect to KeePassHttp on port <port> (default: 19455)") + "\n";
|
||||||
|
strUsage += " -keepasskey=<key> " + _("KeePassHttp key for AES encrypted communication with KeePass") + "\n";
|
||||||
|
strUsage += " -keepassid=<name> " + _("KeePassHttp id for the established association") + "\n";
|
||||||
|
strUsage += " -keepassname=<name> " + _("Name to construct url for KeePass entry that stores the wallet passphrase") + "\n";
|
||||||
strUsage += " -keypool=<n> " + _("Set key pool size to <n> (default: 100)") + "\n";
|
strUsage += " -keypool=<n> " + _("Set key pool size to <n> (default: 100)") + "\n";
|
||||||
strUsage += " -paytxfee=<amt> " + _("Fee per kB to add to transactions you send") + "\n";
|
strUsage += " -paytxfee=<amt> " + _("Fee per kB to add to transactions you send") + "\n";
|
||||||
strUsage += " -rescan " + _("Rescan the block chain for missing wallet transactions") + " " + _("on startup") + "\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)
|
if (r == CDBEnv::RECOVER_FAIL)
|
||||||
return InitError(_("wallet.dat corrupt, salvage failed"));
|
return InitError(_("wallet.dat corrupt, salvage failed"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize KeePass Integration
|
||||||
|
keePassInt.init();
|
||||||
|
|
||||||
} // (!fDisableWallet)
|
} // (!fDisableWallet)
|
||||||
#endif // ENABLE_WALLET
|
#endif // ENABLE_WALLET
|
||||||
// ********************************************************* Step 6: network initialization
|
// ********************************************************* Step 6: network initialization
|
||||||
|
439
src/keepass.cpp
Normal file
439
src/keepass.cpp
Normal file
@ -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 <exception>
|
||||||
|
#include <openssl/rand.h>
|
||||||
|
#include <boost/lexical_cast.hpp>
|
||||||
|
#include <boost/foreach.hpp>
|
||||||
|
#include <boost/asio.hpp>
|
||||||
|
|
||||||
|
#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<unsigned int>(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::CKeePassEntry> CKeePassIntegrator::CKeePassResponse::getEntries() {
|
||||||
|
|
||||||
|
std::vector<CKeePassEntry> 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<std::string,std::string>& 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<std::string>(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<std::string, std::string> 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<std::string, std::string> 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::CKeePassEntry> 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<std::string>(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<std::string>(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<std::string>(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<CKeePassIntegrator::CKeePassEntry> 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<CKeePassIntegrator::CKeePassEntry> 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);
|
||||||
|
}
|
133
src/keepass.h
Normal file
133
src/keepass.h
Normal file
@ -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 <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
#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<CKeePassEntry> 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<std::string,std::string>& mapRequestHeaders);
|
||||||
|
void doHTTPPost(const std::string& sRequest, int& nStatus, std::string& sResponse);
|
||||||
|
void rpcTestAssociation(bool bTriggerUnlock);
|
||||||
|
std::vector<CKeePassEntry> 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
|
@ -138,6 +138,7 @@ int ReadHTTPStatus(std::basic_istream<char>& stream, int &proto)
|
|||||||
{
|
{
|
||||||
string str;
|
string str;
|
||||||
getline(stream, str);
|
getline(stream, str);
|
||||||
|
//LogPrintf("ReadHTTPStatus - getline string: %s\n",str.c_str());
|
||||||
vector<string> vWords;
|
vector<string> vWords;
|
||||||
boost::split(vWords, str, boost::is_any_of(" "));
|
boost::split(vWords, str, boost::is_any_of(" "));
|
||||||
if (vWords.size() < 2)
|
if (vWords.size() < 2)
|
||||||
|
@ -293,6 +293,7 @@ static const CRPCCommand vRPCCommands[] =
|
|||||||
{ "getwalletinfo", &getwalletinfo, true, false, true },
|
{ "getwalletinfo", &getwalletinfo, true, false, true },
|
||||||
{ "importprivkey", &importprivkey, false, false, true },
|
{ "importprivkey", &importprivkey, false, false, true },
|
||||||
{ "importwallet", &importwallet, false, false, true },
|
{ "importwallet", &importwallet, false, false, true },
|
||||||
|
{ "keepass", &keepass, false, false, true },
|
||||||
{ "keypoolrefill", &keypoolrefill, true, false, true },
|
{ "keypoolrefill", &keypoolrefill, true, false, true },
|
||||||
{ "listaccounts", &listaccounts, false, false, true },
|
{ "listaccounts", &listaccounts, false, false, true },
|
||||||
{ "listaddressgroupings", &listaddressgroupings, false, false, true },
|
{ "listaddressgroupings", &listaddressgroupings, false, false, true },
|
||||||
|
@ -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 getwalletinfo(const json_spirit::Array& params, bool fHelp);
|
||||||
extern json_spirit::Value getblockchaininfo(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 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 getrawtransaction(const json_spirit::Array& params, bool fHelp); // in rcprawtransaction.cpp
|
||||||
extern json_spirit::Value listunspent(const json_spirit::Array& params, bool fHelp);
|
extern json_spirit::Value listunspent(const json_spirit::Array& params, bool fHelp);
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "wallet.h"
|
#include "wallet.h"
|
||||||
#include "walletdb.h"
|
#include "walletdb.h"
|
||||||
|
#include "keepass.h"
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
@ -1921,3 +1922,56 @@ Value getwalletinfo(const Array& params, bool fHelp)
|
|||||||
obj.push_back(Pair("unlocked_until", nWalletUnlockTime));
|
obj.push_back(Pair("unlocked_until", nWalletUnlockTime));
|
||||||
return obj;
|
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 <genkey|init|setpassphrase>\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";
|
||||||
|
|
||||||
|
}
|
||||||
|
63
src/util.cpp
63
src/util.cpp
@ -12,10 +12,16 @@
|
|||||||
#include "ui_interface.h"
|
#include "ui_interface.h"
|
||||||
#include "uint256.h"
|
#include "uint256.h"
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
|
#include "allocators.h"
|
||||||
|
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
|
|
||||||
#include <boost/date_time/posix_time/posix_time.hpp>
|
#include <boost/date_time/posix_time/posix_time.hpp>
|
||||||
|
#include <openssl/bio.h>
|
||||||
|
#include <openssl/evp.h>
|
||||||
|
#include <openssl/buffer.h>
|
||||||
|
#include <openssl/crypto.h> // for OPENSSL_cleanse()
|
||||||
|
|
||||||
|
|
||||||
#ifndef WIN32
|
#ifndef WIN32
|
||||||
// for posix_fallocate
|
// for posix_fallocate
|
||||||
@ -612,6 +618,33 @@ string EncodeBase64(const string& str)
|
|||||||
return EncodeBase64((const unsigned char*)str.c_str(), str.size());
|
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<unsigned char> DecodeBase64(const char* p, bool* pfInvalid)
|
vector<unsigned char> DecodeBase64(const char* p, bool* pfInvalid)
|
||||||
{
|
{
|
||||||
static const int decode64_table[256] =
|
static const int decode64_table[256] =
|
||||||
@ -701,6 +734,35 @@ string DecodeBase64(const string& str)
|
|||||||
return string((const char*)&vchRet[0], vchRet.size());
|
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)
|
string EncodeBase32(const unsigned char* pch, size_t len)
|
||||||
{
|
{
|
||||||
static const char *pbase32 = "abcdefghijklmnopqrstuvwxyz234567";
|
static const char *pbase32 = "abcdefghijklmnopqrstuvwxyz234567";
|
||||||
@ -888,7 +950,6 @@ string DecodeBase32(const string& str)
|
|||||||
return string((const char*)&vchRet[0], vchRet.size());
|
return string((const char*)&vchRet[0], vchRet.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool WildcardMatch(const char* psz, const char* mask)
|
bool WildcardMatch(const char* psz, const char* mask)
|
||||||
{
|
{
|
||||||
while (true)
|
while (true)
|
||||||
|
@ -177,8 +177,10 @@ std::vector<unsigned char> ParseHex(const std::string& str);
|
|||||||
bool IsHex(const std::string& str);
|
bool IsHex(const std::string& str);
|
||||||
std::vector<unsigned char> DecodeBase64(const char* p, bool* pfInvalid = NULL);
|
std::vector<unsigned char> DecodeBase64(const char* p, bool* pfInvalid = NULL);
|
||||||
std::string DecodeBase64(const std::string& str);
|
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 unsigned char* pch, size_t len);
|
||||||
std::string EncodeBase64(const std::string& str);
|
std::string EncodeBase64(const std::string& str);
|
||||||
|
SecureString EncodeBase64Secure(const SecureString& input);
|
||||||
std::vector<unsigned char> DecodeBase32(const char* p, bool* pfInvalid = NULL);
|
std::vector<unsigned char> DecodeBase32(const char* p, bool* pfInvalid = NULL);
|
||||||
std::string DecodeBase32(const std::string& str);
|
std::string DecodeBase32(const std::string& str);
|
||||||
std::string EncodeBase32(const unsigned char* pch, size_t len);
|
std::string EncodeBase32(const unsigned char* pch, size_t len);
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
#include "coincontrol.h"
|
#include "coincontrol.h"
|
||||||
#include "net.h"
|
#include "net.h"
|
||||||
#include "darksend.h"
|
#include "darksend.h"
|
||||||
|
#include "keepass.h"
|
||||||
|
|
||||||
#include <boost/algorithm/string/replace.hpp>
|
#include <boost/algorithm/string/replace.hpp>
|
||||||
#include <openssl/rand.h>
|
#include <openssl/rand.h>
|
||||||
@ -149,12 +150,26 @@ bool CWallet::LoadCScript(const CScript& redeemScript)
|
|||||||
|
|
||||||
bool CWallet::Unlock(const SecureString& strWalletPassphrase, bool anonymizeOnly)
|
bool CWallet::Unlock(const SecureString& strWalletPassphrase, bool anonymizeOnly)
|
||||||
{
|
{
|
||||||
|
SecureString strWalletPassphraseFinal;
|
||||||
|
|
||||||
if (!IsLocked())
|
if (!IsLocked())
|
||||||
{
|
{
|
||||||
fWalletUnlockAnonymizeOnly = anonymizeOnly;
|
fWalletUnlockAnonymizeOnly = anonymizeOnly;
|
||||||
return true;
|
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;
|
CCrypter crypter;
|
||||||
CKeyingMaterial vMasterKey;
|
CKeyingMaterial vMasterKey;
|
||||||
|
|
||||||
@ -162,7 +177,7 @@ bool CWallet::Unlock(const SecureString& strWalletPassphrase, bool anonymizeOnly
|
|||||||
LOCK(cs_wallet);
|
LOCK(cs_wallet);
|
||||||
BOOST_FOREACH(const MasterKeyMap::value_type& pMasterKey, mapMasterKeys)
|
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;
|
return false;
|
||||||
if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, vMasterKey))
|
if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, vMasterKey))
|
||||||
continue; // try another master key
|
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 CWallet::ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, const SecureString& strNewWalletPassphrase)
|
||||||
{
|
{
|
||||||
bool fWasLocked = IsLocked();
|
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);
|
LOCK(cs_wallet);
|
||||||
@ -188,7 +219,7 @@ bool CWallet::ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase,
|
|||||||
CKeyingMaterial vMasterKey;
|
CKeyingMaterial vMasterKey;
|
||||||
BOOST_FOREACH(MasterKeyMap::value_type& pMasterKey, mapMasterKeys)
|
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;
|
return false;
|
||||||
if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, vMasterKey))
|
if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, vMasterKey))
|
||||||
return false;
|
return false;
|
||||||
@ -214,6 +245,18 @@ bool CWallet::ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase,
|
|||||||
CWalletDB(strWalletFile).WriteMasterKey(pMasterKey.first, pMasterKey.second);
|
CWalletDB(strWalletFile).WriteMasterKey(pMasterKey.first, pMasterKey.second);
|
||||||
if (fWasLocked)
|
if (fWasLocked)
|
||||||
Lock();
|
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;
|
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.
|
// bits of the unencrypted private key in slack space in the database file.
|
||||||
CDB::Rewrite(strWalletFile);
|
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);
|
NotifyStatusChanged(this);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user