440 lines
16 KiB
C++
440 lines
16 KiB
C++
|
// 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);
|
||
|
}
|