// Copyright (c) 2014-2017 The Dash Core 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 "wallet/crypter.h" #include "clientversion.h" #include "protocol.h" #include "random.h" #include "rpc/protocol.h" // Necessary to prevent compile errors due to forward declaration of //CScript in serialize.h (included from crypter.h) #include "script/script.h" #include "script/standard.h" #include "util.h" #include "utilstrencodings.h" #include #include #include #include #include #include #include #include "support/cleanse.h" // for OPENSSL_cleanse() const char* CKeePassIntegrator::KEEPASS_HTTP_HOST = "localhost"; CKeePassIntegrator keePassInt; // Base64 decoding with secure memory allocation SecureString DecodeBase64Secure(const SecureString& sInput) { 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 *) &sInput[0], sInput.size()); BIO_push(b64, mem); // Prepare buffer to receive decoded data if(sInput.size() % 4 != 0) { throw std::runtime_error("Input length should be a multiple of 4"); } size_t nMaxLen = sInput.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], sInput.size()); output.resize(nLen); // Free memory BIO_free_all(b64); return output; } // Base64 encoding with secure memory allocation SecureString EncodeBase64Secure(const SecureString& sInput) { // 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, &sInput[0], sInput.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 memory_cleanse((void *) bptr->data, bptr->length); // Free memory BIO_free_all(b64); return output; } 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 = DEFAULT_KEEPASS_HTTP_PORT; } // Initialze from application context void CKeePassIntegrator::init() { bIsActive = GetBoolArg("-keepass", false); nPort = GetArg("-keepassport", DEFAULT_KEEPASS_HTTP_PORT); sKeyBase64 = SecureString(GetArg("-keepasskey", "").c_str()); strKeePassId = GetArg("-keepassid", ""); strKeePassEntryName = GetArg("-keepassname", ""); // Convert key if available if(sKeyBase64.size() > 0) { sKey = DecodeBase64Secure(sKeyBase64); } // Construct url if available if(strKeePassEntryName.size() > 0) { sUrl = SecureString("http://"); sUrl += SecureString(strKeePassEntryName.c_str()); sUrl += SecureString("/"); //sSubmitUrl = "http://"; //sSubmitUrl += SecureString(strKeePassEntryName.c_str()); } } void CKeePassIntegrator::CKeePassRequest::addStrParameter(std::string strName, std::string strValue) { requestObj.push_back(Pair(strName, strValue)); } void CKeePassIntegrator::CKeePassRequest::addStrParameter(std::string strName, SecureString sValue) { std::string sCipherValue; if(!EncryptAES256(sKey, sValue, strIV, sCipherValue)) { throw std::runtime_error("Unable to encrypt Verifier"); } addStrParameter(strName, EncodeBase64(sCipherValue)); } std::string CKeePassIntegrator::CKeePassRequest::getJson() { return requestObj.write(); } void CKeePassIntegrator::CKeePassRequest::init() { SecureString sIVSecure = generateRandomKey(KEEPASS_CRYPTO_BLOCK_SIZE); strIV = 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", strType); } void CKeePassIntegrator::CKeePassResponse::parseResponse(std::string strResponse) { UniValue responseValue; if(!responseValue.read(strResponse)) { throw std::runtime_error("Unable to parse KeePassHttp response"); } responseObj = responseValue; // retrieve main values bSuccess = responseObj["Success"].get_bool(); strType = getStr("RequestType"); strIV = DecodeBase64(getStr("Nonce")); } std::string CKeePassIntegrator::CKeePassResponse::getStr(std::string strName) { return responseObj[strName].get_str(); } SecureString CKeePassIntegrator::CKeePassResponse::getSecureStr(std::string strName) { std::string strValueBase64Encrypted(responseObj[strName].get_str()); SecureString sValue; try { sValue = decrypt(strValueBase64Encrypted); } catch (std::exception &e) { std::string strError = "Exception occured while decrypting "; strError += strName + ": " + e.what(); throw std::runtime_error(strError); } return sValue; } SecureString CKeePassIntegrator::CKeePassResponse::decrypt(std::string strValueBase64Encrypted) { std::string strValueEncrypted = DecodeBase64(strValueBase64Encrypted); SecureString sValue; if(!DecryptAES256(sKey, strValueEncrypted, strIV, sValue)) { throw std::runtime_error("Unable to decrypt value."); } return sValue; } std::vector CKeePassIntegrator::CKeePassResponse::getEntries() { std::vector vEntries; UniValue aEntries = responseObj["Entries"].get_array(); for(size_t i = 0; i < aEntries.size(); i++) { SecureString sEntryUuid(decrypt(aEntries[i]["Uuid"].get_str().c_str())); SecureString sEntryName(decrypt(aEntries[i]["Name"].get_str().c_str())); SecureString sEntryLogin(decrypt(aEntries[i]["Login"].get_str().c_str())); SecureString sEntryPassword(decrypt(aEntries[i]["Password"].get_str().c_str())); CKeePassEntry entry(sEntryUuid, sEntryName, sEntryLogin, sEntryPassword); vEntries.push_back(entry); } return vEntries; } SecureString CKeePassIntegrator::generateRandomKey(size_t nSize) { // Generates random key SecureString sKey; sKey.resize(nSize); GetStrongRandBytes((unsigned char *) &sKey[0], nSize); return sKey; } // Construct POST body for RPC JSON call std::string CKeePassIntegrator::constructHTTPPost(const std::string& strMsg, const std::map& mapRequestHeaders) { std::ostringstream streamOut; streamOut << "POST / HTTP/1.1\r\n" << "User-Agent: dash-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"; for (const auto& item : mapRequestHeaders) streamOut << item.first << ": " << item.second << "\r\n"; streamOut << "\r\n" << strMsg; return streamOut.str(); } /** Reply structure for request_done to fill in */ struct HTTPReply { int nStatus; std::string strBody; }; static void http_request_done(struct evhttp_request *req, void *ctx) { HTTPReply *reply = static_cast(ctx); if (req == NULL) { /* If req is NULL, it means an error occurred while connecting, but * I'm not sure how to find out which one. We also don't really care. */ reply->nStatus = 0; return; } reply->nStatus = evhttp_request_get_response_code(req); struct evbuffer *buf = evhttp_request_get_input_buffer(req); if (buf) { size_t size = evbuffer_get_length(buf); const char *data = (const char*)evbuffer_pullup(buf, size); if (data) reply->strBody = std::string(data, size); evbuffer_drain(buf, size); } } // Send RPC message to KeePassHttp void CKeePassIntegrator::doHTTPPost(const std::string& sRequest, int& nStatus, std::string& strResponse) { // // 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_HTTP_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); // } // Create event base struct event_base *base = event_base_new(); // TODO RAII if (!base) throw std::runtime_error("cannot create event_base"); // Synchronously look up hostname struct evhttp_connection *evcon = evhttp_connection_base_new(base, NULL, KEEPASS_HTTP_HOST, DEFAULT_KEEPASS_HTTP_PORT); // TODO RAII if (evcon == NULL) throw std::runtime_error("create connection failed"); evhttp_connection_set_timeout(evcon, KEEPASS_HTTP_CONNECT_TIMEOUT); // Form the request. // std::map mapRequestHeaders; // std::string strPost = constructHTTPPost(sRequest, mapRequestHeaders); HTTPReply response; struct evhttp_request *req = evhttp_request_new(http_request_done, (void*)&response); // TODO RAII if (req == NULL) throw std::runtime_error("create http request failed"); struct evkeyvalq *output_headers = evhttp_request_get_output_headers(req); assert(output_headers); // s << "POST / HTTP/1.1\r\n" evhttp_add_header(output_headers, "User-Agent", ("dash-json-rpc/" + FormatFullVersion()).c_str()); evhttp_add_header(output_headers, "Host", KEEPASS_HTTP_HOST); evhttp_add_header(output_headers, "Accept", "application/json"); evhttp_add_header(output_headers, "Content-Type", "application/json"); // evhttp_add_header(output_headers, "Content-Length", itostr(strMsg.size()).c_str()); evhttp_add_header(output_headers, "Connection", "close"); // Logging of actual post data disabled as to not write passphrase in debug.log. Only enable temporarily when needed //LogPrint("keepass", "CKeePassIntegrator::doHTTPPost -- send POST data: %s\n", strPost); LogPrint("keepass", "CKeePassIntegrator::doHTTPPost -- send POST data\n"); // boost::asio::streambuf request; // std::ostream request_stream(&request); // request_stream << strPost; // // Send the request. // boost::asio::write(socket, request); // LogPrint("keepass", "CKeePassIntegrator::doHTTPPost -- 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"); // LogPrint("keepass", "CKeePassIntegrator::doHTTPPost -- request status line read\n"); // // Receive HTTP reply status // int nProto = 0; // std::istream response_stream(&response); // nStatus = ReadHTTPStatus(response_stream, nProto); // Attach request data // std::string sRequest = JSONRPCRequest(strMethod, params, 1); struct evbuffer * output_buffer = evhttp_request_get_output_buffer(req); assert(output_buffer); evbuffer_add(output_buffer, sRequest.data(), sRequest.size()); int r = evhttp_make_request(evcon, req, EVHTTP_REQ_POST, "/"); if (r != 0) { evhttp_connection_free(evcon); event_base_free(base); throw std::runtime_error("send http request failed"); } event_base_dispatch(base); evhttp_connection_free(evcon); event_base_free(base); // LogPrint("keepass", "CKeePassIntegrator::doHTTPPost -- reading response 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); // } // } // } // LogPrint("keepass", "CKeePassIntegrator::doHTTPPost -- reading response body end\n"); // // // Receive HTTP reply message headers and body // std::map mapHeaders; // ReadHTTPMessage(response_stream, mapHeaders, strResponse, nProto, std::numeric_limits::max()); // LogPrint("keepass", "CKeePassIntegrator::doHTTPPost -- Processed body\n"); nStatus = response.nStatus; if (response.nStatus == 0) throw std::runtime_error("couldn't connect to server"); else if (response.nStatus >= 400 && response.nStatus != HTTP_BAD_REQUEST && response.nStatus != HTTP_NOT_FOUND && response.nStatus != HTTP_INTERNAL_SERVER_ERROR) throw std::runtime_error(strprintf("server returned HTTP error %d", response.nStatus)); else if (response.strBody.empty()) throw std::runtime_error("no response from server"); // Parse reply UniValue valReply(UniValue::VSTR); if (!valReply.read(response.strBody)) throw std::runtime_error("couldn't parse reply from server"); const UniValue& reply = valReply.get_obj(); if (reply.empty()) throw std::runtime_error("expected reply to have result, error and id properties"); strResponse = valReply.get_str(); } void CKeePassIntegrator::rpcTestAssociation(bool bTriggerUnlock) { CKeePassRequest request(sKey, "test-associate"); request.addStrParameter("TriggerUnlock", std::string(bTriggerUnlock ? "true" : "false")); int nStatus; std::string strResponse; doHTTPPost(request.getJson(), nStatus, strResponse); LogPrint("keepass", "CKeePassIntegrator::rpcTestAssociation -- send result: status: %d response: %s\n", nStatus, strResponse); } 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", strKeePassId); request.addStrParameter("Url", sUrl); int nStatus; std::string strResponse; doHTTPPost(request.getJson(), nStatus, strResponse); // Logging of actual response data disabled as to not write passphrase in debug.log. Only enable temporarily when needed //LogPrint("keepass", "CKeePassIntegrator::rpcGetLogins -- send result: status: %d response: %s\n", nStatus, strResponse); LogPrint("keepass", "CKeePassIntegrator::rpcGetLogins -- send result: status: %d\n", nStatus); if(nStatus != 200) { std::string strError = "Error returned by KeePassHttp: HTTP code "; strError += itostr(nStatus); strError += " - Response: "; strError += " response: ["; strError += strResponse; strError += "]"; throw std::runtime_error(strError); } // Parse the response CKeePassResponse response(sKey, strResponse); if(!response.getSuccess()) { std::string strError = "KeePassHttp returned failure status"; throw std::runtime_error(strError); } return response.getEntries(); } void CKeePassIntegrator::rpcSetLogin(const SecureString& sWalletPass, const SecureString& sEntryId) { // Convert key format SecureString sKey = DecodeBase64Secure(sKeyBase64); CKeePassRequest request(sKey, "set-login"); request.addStrParameter("Id", strKeePassId); request.addStrParameter("Url", sUrl); LogPrint("keepass", "CKeePassIntegrator::rpcSetLogin -- send Url: %s\n", sUrl); //request.addStrParameter("SubmitUrl", sSubmitUrl); // Is used to construct the entry title request.addStrParameter("Login", SecureString("dash")); request.addStrParameter("Password", sWalletPass); if(sEntryId.size() != 0) { request.addStrParameter("Uuid", sEntryId); // Update existing } int nStatus; std::string strResponse; doHTTPPost(request.getJson(), nStatus, strResponse); LogPrint("keepass", "CKeePassIntegrator::rpcSetLogin -- send result: status: %d response: %s\n", nStatus, strResponse); if(nStatus != 200) { std::string strError = "Error returned: HTTP code "; strError += itostr(nStatus); strError += " - Response: "; strError += " response: ["; strError += strResponse; strError += "]"; throw std::runtime_error(strError); } // Parse the response CKeePassResponse response(sKey, strResponse); 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& strId, 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 strResponse; doHTTPPost(request.getJson(), nStatus, strResponse); LogPrint("keepass", "CKeePassIntegrator::rpcAssociate -- send result: status: %d response: %s\n", nStatus, strResponse); if(nStatus != 200) { std::string strError = "Error returned: HTTP code "; strError += itostr(nStatus); strError += " - Response: "; strError += " response: ["; strError += strResponse; strError += "]"; throw std::runtime_error(strError); } // Parse the response CKeePassResponse response(sKey, strResponse); if(!response.getSuccess()) { throw std::runtime_error("KeePassHttp returned failure status"); } // If we got here, we were successful. Return the information strId = 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(strKeePassId.size() == 0) { throw std::runtime_error("keepassid parameter is not defined. Please specify the configuration parameter."); } if(strKeePassEntryName == "") { throw std::runtime_error("keepassname parameter is not defined. Please specify the configuration parameter."); } // Retrieve matching logins from KeePass std::vector vecEntries = rpcGetLogins(); // Only accept one unique match if(vecEntries.size() == 0) { throw std::runtime_error("KeePassHttp returned 0 matches, please verify the keepassurl setting."); } if(vecEntries.size() > 1) { throw std::runtime_error("KeePassHttp returned multiple matches, bailing out."); } return vecEntries[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(strKeePassId.size() == 0) { throw std::runtime_error("keepassid parameter is not defined. Please specify the configuration parameter."); } if(strKeePassEntryName == "") { throw std::runtime_error("keepassname parameter is not defined. Please specify the configuration parameter."); } SecureString sEntryId(""); std::string strError; // Lookup existing entry std::vector vecEntries = rpcGetLogins(); if(vecEntries.size() > 1) { throw std::runtime_error("KeePassHttp returned multiple matches, bailing out."); } if(vecEntries.size() == 1) { sEntryId = vecEntries[0].getUuid(); } // Update wallet passphrase in KeePass rpcSetLogin(sWalletPassphrase, sEntryId); }