//// Wallet.cpp// interface/src/commerce//// Created by Howard Stearns on 8/4/17.// Copyright 2017 High Fidelity, Inc.//// Distributed under the Apache License, Version 2.0.// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html// #include "Wallet.h" #include <openssl/ssl.h>#include <openssl/err.h>#include <openssl/x509.h>#include <openssl/pem.h>#include <openssl/evp.h>#include <openssl/aes.h>#include <openssl/ecdsa.h>// I know, right? But per https://www.openssl.org/docs/faq.html// this avoids OPENSSL_Uplink(00007FF847238000,08): no OPENSSL_Applink// at runtime.#ifdef Q_OS_WIN#include <openssl/applink.c>#endif #include <QFile>#include <QCryptographicHash>#include <QQmlContext>#include <QBuffer> #include <PathUtils.h>#include <OffscreenUi.h>#include <AccountManager.h>#include <ui/TabletScriptingInterface.h> #include "Application.h"#include "CommerceLogging.h"#include "Ledger.h"#include "ui/SecurityImageProvider.h"#include "scripting/HMDScriptingInterface.h" static const char* KEY_FILE = "hifikey";static const char* INSTRUCTIONS_FILE = "backup_instructions.html";static const char* IMAGE_HEADER = "-----BEGIN SECURITY IMAGE-----\n";static const char* IMAGE_FOOTER = "-----END SECURITY IMAGE-----\n"; void initialize() { static bool initialized = false; if (!initialized) { SSL_load_error_strings(); SSL_library_init(); OpenSSL_add_all_algorithms(); initialized = true; }} QString keyFilePath() { auto accountManager = DependencyManager::get<AccountManager>(); return PathUtils::getAppDataFilePath(QString("%1.%2").arg(accountManager->getAccountInfo().getUsername(), KEY_FILE));}bool Wallet::copyKeyFileFrom(const QString& pathname) { QString existing = getKeyFilePath(); qCDebug(commerce) << "Old keyfile" << existing; if (!existing.isEmpty()) { QString backup = QString(existing).insert(existing.indexOf(KEY_FILE) - 1, QDateTime::currentDateTime().toString(Qt::ISODate).replace(":", "")); qCDebug(commerce) << "Renaming old keyfile to" << backup; if (!QFile::rename(existing, backup)) { qCCritical(commerce) << "Unable to backup" << existing << "to" << backup; return false; } } QString destination = keyFilePath(); bool result = QFile::copy(pathname, destination); qCDebug(commerce) << "copy" << pathname << "to" << destination << "=>" << result; return result;} // use the cached _passphrase if it exists, otherwise we need to promptint passwordCallback(char* password, int maxPasswordSize, int rwFlag, void* u) { // just return a hardcoded pwd for now auto wallet = DependencyManager::get<Wallet>(); auto passphrase = wallet->getPassphrase(); if (passphrase && !passphrase->isEmpty()) { QString saltedPassphrase(*passphrase); saltedPassphrase.append(wallet->getSalt()); strcpy(password, saltedPassphrase.toUtf8().constData()); return static_cast<int>(passphrase->size()); } else { // this shouldn't happen - so lets log it to tell us we have // a problem with the flow... qCCritical(commerce) << "no cached passphrase while decrypting!"; return 0; }} EC_KEY* readKeys(const char* filename) { FILE* fp; EC_KEY *key = NULL; if ((fp = fopen(filename, "rt"))) { // file opened successfully qCDebug(commerce) << "opened key file" << filename; if ((key = PEM_read_EC_PUBKEY(fp, NULL, NULL, NULL))) { // now read private key qCDebug(commerce) << "read public key"; if ((key = PEM_read_ECPrivateKey(fp, &key, passwordCallback, NULL))) { qCDebug(commerce) << "read private key"; fclose(fp); return key; } qCDebug(commerce) << "failed to read private key"; } else { qCDebug(commerce) << "failed to read public key"; } fclose(fp); } else { qCDebug(commerce) << "failed to open key file" << filename; } return key;} bool Wallet::writeBackupInstructions() { QString inputFilename(PathUtils::resourcesPath() + "html/commerce/backup_instructions.html"); QString outputFilename = PathUtils::getAppDataFilePath(INSTRUCTIONS_FILE); QFile inputFile(inputFilename); QFile outputFile(outputFilename); bool retval = false; if (getKeyFilePath().isEmpty()) { return false; } if (QFile::exists(inputFilename) && inputFile.open(QIODevice::ReadOnly)) { if (outputFile.open(QIODevice::ReadWrite)) { // Read the data from the original file, then close it QByteArray fileData = inputFile.readAll(); inputFile.close(); // Translate the data from the original file into a QString QString text(fileData); // Replace the necessary string text.replace(QString("HIFIKEY_PATH_REPLACEME"), keyFilePath()); // Write the new text back to the file outputFile.write(text.toUtf8()); // Close the output file outputFile.close(); retval = true; qCDebug(commerce) << "wrote html file successfully"; } else { qCDebug(commerce) << "failed to open output html file" << outputFilename; } } else { qCDebug(commerce) << "failed to open input html file" << inputFilename; } return retval;} bool writeKeys(const char* filename, EC_KEY* keys) { FILE* fp; bool retval = false; if ((fp = fopen(filename, "wt"))) { if (!PEM_write_EC_PUBKEY(fp, keys)) { fclose(fp); qCCritical(commerce) << "failed to write public key"; return retval; } if (!PEM_write_ECPrivateKey(fp, keys, EVP_des_ede3_cbc(), NULL, 0, passwordCallback, NULL)) { fclose(fp); qCCritical(commerce) << "failed to write private key"; return retval; } retval = true; qCDebug(commerce) << "wrote keys successfully"; fclose(fp); } else { qCDebug(commerce) << "failed to open key file" << filename; } return retval;} bool Wallet::setWallet(const QByteArray& wallet) { QFile file(keyFilePath()); if (!file.open(QIODevice::WriteOnly)) { qCCritical(commerce) << "Unable to open wallet for write in" << keyFilePath(); return false; } if (file.write(wallet) != wallet.count()) { qCCritical(commerce) << "Unable to write wallet in" << keyFilePath(); return false; } file.close(); return true;}QByteArray Wallet::getWallet() { QFile file(keyFilePath()); if (!file.open(QIODevice::ReadOnly)) { qCInfo(commerce) << "No existing wallet in" << keyFilePath(); return QByteArray(); } QByteArray wallet = file.readAll(); file.close(); return wallet;} QPair<QByteArray*, QByteArray*> generateECKeypair() { EC_KEY* keyPair = EC_KEY_new_by_curve_name(NID_secp256k1); QPair<QByteArray*, QByteArray*> retval{}; EC_KEY_set_asn1_flag(keyPair, OPENSSL_EC_NAMED_CURVE); if (!EC_KEY_generate_key(keyPair)) { qCDebug(commerce) << "Error generating EC Keypair -" << ERR_get_error(); return retval; } // grab the public key and private key from the file unsigned char* publicKeyDER = NULL; int publicKeyLength = i2d_EC_PUBKEY(keyPair, &publicKeyDER); unsigned char* privateKeyDER = NULL; int privateKeyLength = i2d_ECPrivateKey(keyPair, &privateKeyDER); if (publicKeyLength <= 0 || privateKeyLength <= 0) { qCDebug(commerce) << "Error getting DER public or private key from EC struct -" << ERR_get_error(); // cleanup the EC struct EC_KEY_free(keyPair); // cleanup the public and private key DER data, if required if (publicKeyLength > 0) { OPENSSL_free(publicKeyDER); } if (privateKeyLength > 0) { OPENSSL_free(privateKeyDER); } return retval; } if (!writeKeys(keyFilePath().toStdString().c_str(), keyPair)) { qCDebug(commerce) << "couldn't save keys!"; return retval; } EC_KEY_free(keyPair); // prepare the return values. TODO: Fix this - we probably don't really even want the // private key at all (better to read it when we need it?). Or maybe we do, when we have // multiple keys? retval.first = new QByteArray(reinterpret_cast<char*>(publicKeyDER), publicKeyLength); retval.second = new QByteArray(reinterpret_cast<char*>(privateKeyDER), privateKeyLength); // cleanup the publicKeyDER and publicKeyDER data OPENSSL_free(publicKeyDER); OPENSSL_free(privateKeyDER); return retval;}// END copied code (which will soon change) // the public key can just go into a byte arrayQByteArray readPublicKey(const char* filename) { FILE* fp; EC_KEY* key = NULL; if ((fp = fopen(filename, "r"))) { // file opened successfully qCDebug(commerce) << "opened key file" << filename; if ((key = PEM_read_EC_PUBKEY(fp, NULL, NULL, NULL))) { // file read successfully unsigned char* publicKeyDER = NULL; int publicKeyLength = i2d_EC_PUBKEY(key, &publicKeyDER); // TODO: check for 0 length? // cleanup EC_KEY_free(key); fclose(fp); qCDebug(commerce) << "parsed public key file successfully"; QByteArray retval((char*)publicKeyDER, publicKeyLength); OPENSSL_free(publicKeyDER); return retval; } else { qCDebug(commerce) << "couldn't parse" << filename; } fclose(fp); } else { qCDebug(commerce) << "couldn't open" << filename; } return QByteArray();} // the private key should be read/copied into heap memory. For now, we need the EC_KEY struct// so I'll return that.EC_KEY* readPrivateKey(const char* filename) { FILE* fp; EC_KEY* key = NULL; if ((fp = fopen(filename, "r"))) { // file opened successfully qCDebug(commerce) << "opened key file" << filename; if ((key = PEM_read_ECPrivateKey(fp, &key, passwordCallback, NULL))) { qCDebug(commerce) << "parsed private key file successfully"; } else { qCDebug(commerce) << "couldn't parse" << filename; // if the passphrase is wrong, then let's not cache it DependencyManager::get<Wallet>()->setPassphrase(""); } fclose(fp); } else { qCDebug(commerce) << "couldn't open" << filename; } return key;} // QT's QByteArray will convert to Base64 without any embedded newlines. This just// writes it with embedded newlines, which is more readable.void outputBase64WithNewlines(QFile& file, const QByteArray& b64Array) { for (int i = 0; i < b64Array.size(); i += 64) { file.write(b64Array.mid(i, 64)); file.write("\n"); }} void initializeAESKeys(unsigned char* ivec, unsigned char* ckey, const QByteArray& salt) { // use the ones in the wallet auto wallet = DependencyManager::get<Wallet>(); memcpy(ivec, wallet->getIv(), 16); memcpy(ckey, wallet->getCKey(), 32);} Wallet::Wallet() { auto nodeList = DependencyManager::get<NodeList>(); auto ledger = DependencyManager::get<Ledger>(); auto& packetReceiver = nodeList->getPacketReceiver(); _passphrase = new QString(""); packetReceiver.registerListener(PacketType::ChallengeOwnership, this, "handleChallengeOwnershipPacket"); packetReceiver.registerListener(PacketType::ChallengeOwnershipRequest, this, "handleChallengeOwnershipPacket"); connect(ledger.data(), &Ledger::accountResult, this, [](QJsonObject result) { auto wallet = DependencyManager::get<Wallet>(); auto walletScriptingInterface = DependencyManager::get<WalletScriptingInterface>(); uint status; QString keyStatus = result.contains("data") ? result["data"].toObject()["keyStatus"].toString() : ""; if (wallet->getKeyFilePath().isEmpty() || !wallet->getSecurityImage()) { if (keyStatus == "preexisting") { status = (uint) WalletStatus::WALLET_STATUS_PREEXISTING; } else{ status = (uint) WalletStatus::WALLET_STATUS_NOT_SET_UP; } } else if (!wallet->walletIsAuthenticatedWithPassphrase()) { status = (uint) WalletStatus::WALLET_STATUS_NOT_AUTHENTICATED; } else if (keyStatus == "conflicting") { status = (uint) WalletStatus::WALLET_STATUS_CONFLICTING; } else { status = (uint) WalletStatus::WALLET_STATUS_READY; } walletScriptingInterface->setWalletStatus(status); }); auto accountManager = DependencyManager::get<AccountManager>(); connect(accountManager.data(), &AccountManager::usernameChanged, this, [&]() { getWalletStatus(); clear(); });} void Wallet::clear() { _publicKeys.clear(); if (_securityImage) { delete _securityImage; } _securityImage = nullptr; // tell the provider we got nothing updateImageProvider(); _passphrase->clear();} Wallet::~Wallet() { if (_securityImage) { delete _securityImage; } if (_passphrase) { delete _passphrase; }} bool Wallet::setPassphrase(const QString& passphrase) { if (_passphrase) { delete _passphrase; } _passphrase = new QString(passphrase); _publicKeys.clear(); writeBackupInstructions(); return true;} bool Wallet::writeSecurityImage(const QPixmap* pixmap, const QString& outputFilePath) { // aes requires a couple 128-bit keys (ckey and ivec). For now, I'll just // use the md5 of the salt as the ckey (md5 is 128-bit), and ivec will be // a constant. We can review this later - there are ways to generate keys // from a password that may be better. unsigned char ivec[16]; unsigned char ckey[32]; initializeAESKeys(ivec, ckey, _salt); int tempSize, outSize; QByteArray inputFileBuffer; QBuffer buffer(&inputFileBuffer); buffer.open(QIODevice::WriteOnly); // another spot where we are assuming only jpgs pixmap->save(&buffer, "jpg"); // reserve enough capacity for encrypted bytes unsigned char* outputFileBuffer = new unsigned char[inputFileBuffer.size() + AES_BLOCK_SIZE]; EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new(); // TODO: add error handling!!! if (!EVP_EncryptInit_ex(ctx, EVP_des_ede3_cbc(), NULL, ckey, ivec)) { qCDebug(commerce) << "encrypt init failure"; delete[] outputFileBuffer; return false; } if (!EVP_EncryptUpdate(ctx, outputFileBuffer, &tempSize, (unsigned char*)inputFileBuffer.data(), inputFileBuffer.size())) { qCDebug(commerce) << "encrypt update failure"; delete[] outputFileBuffer; return false; } outSize = tempSize; if (!EVP_EncryptFinal_ex(ctx, outputFileBuffer + outSize, &tempSize)) { qCDebug(commerce) << "encrypt final failure"; delete[] outputFileBuffer; return false; } outSize += tempSize; EVP_CIPHER_CTX_free(ctx); qCDebug(commerce) << "encrypted buffer size" << outSize; QByteArray output((const char*)outputFileBuffer, outSize); // now APPEND to the file, QByteArray b64output = output.toBase64(); QFile outputFile(outputFilePath); outputFile.open(QIODevice::Append); outputFile.write(IMAGE_HEADER); outputBase64WithNewlines(outputFile, b64output); outputFile.write(IMAGE_FOOTER); outputFile.close(); delete[] outputFileBuffer; return true;} bool Wallet::readSecurityImage(const QString& inputFilePath, unsigned char** outputBufferPtr, int* outputBufferSize) { unsigned char ivec[16]; unsigned char ckey[32]; initializeAESKeys(ivec, ckey, _salt); // read encrypted file QFile inputFile(inputFilePath); if (!inputFile.exists()) { qCDebug(commerce) << "cannot decrypt file" << inputFilePath << "it doesn't exist"; return false; } inputFile.open(QIODevice::ReadOnly | QIODevice::Text); bool foundHeader = false; bool foundFooter = false; QByteArray base64EncryptedBuffer; while (!inputFile.atEnd()) { QString line(inputFile.readLine()); if (!foundHeader) { foundHeader = (line == IMAGE_HEADER);Lines 1–500 of 911. Load more
// // Wallet.cpp // interface/src/commerce // // Created by Howard Stearns on 8/4/17. // Copyright 2017 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "Wallet.h" #include <openssl/ssl.h> #include <openssl/err.h> #include <openssl/x509.h> #include <openssl/pem.h> #include <openssl/evp.h> #include <openssl/aes.h> #include <openssl/ecdsa.h> // I know, right? But per https://www.openssl.org/docs/faq.html // this avoids OPENSSL_Uplink(00007FF847238000,08): no OPENSSL_Applink // at runtime. #ifdef Q_OS_WIN #include <openssl/applink.c> #endif #include <QFile> #include <QCryptographicHash> #include <QQmlContext> #include <QBuffer> #include <PathUtils.h> #include <OffscreenUi.h> #include <AccountManager.h> #include <ui/TabletScriptingInterface.h> #include "Application.h" #include "CommerceLogging.h" #include "Ledger.h" #include "ui/SecurityImageProvider.h" #include "scripting/HMDScriptingInterface.h" static const char* KEY_FILE = "hifikey"; static const char* INSTRUCTIONS_FILE = "backup_instructions.html"; static const char* IMAGE_HEADER = "-----BEGIN SECURITY IMAGE-----\n"; static const char* IMAGE_FOOTER = "-----END SECURITY IMAGE-----\n"; void initialize() { static bool initialized = false; if (!initialized) { SSL_load_error_strings(); SSL_library_init(); OpenSSL_add_all_algorithms(); initialized = true; } } QString keyFilePath() { auto accountManager = DependencyManager::get<AccountManager>(); return PathUtils::getAppDataFilePath(QString("%1.%2").arg(accountManager->getAccountInfo().getUsername(), KEY_FILE)); } bool Wallet::copyKeyFileFrom(const QString& pathname) { QString existing = getKeyFilePath(); qCDebug(commerce) << "Old keyfile" << existing; if (!existing.isEmpty()) { QString backup = QString(existing).insert(existing.indexOf(KEY_FILE) - 1, QDateTime::currentDateTime().toString(Qt::ISODate).replace(":", "")); qCDebug(commerce) << "Renaming old keyfile to" << backup; if (!QFile::rename(existing, backup)) { qCCritical(commerce) << "Unable to backup" << existing << "to" << backup; return false; } } QString destination = keyFilePath(); bool result = QFile::copy(pathname, destination); qCDebug(commerce) << "copy" << pathname << "to" << destination << "=>" << result; return result; } // use the cached _passphrase if it exists, otherwise we need to prompt int passwordCallback(char* password, int maxPasswordSize, int rwFlag, void* u) { // just return a hardcoded pwd for now auto wallet = DependencyManager::get<Wallet>(); auto passphrase = wallet->getPassphrase(); if (passphrase && !passphrase->isEmpty()) { QString saltedPassphrase(*passphrase); saltedPassphrase.append(wallet->getSalt()); strcpy(password, saltedPassphrase.toUtf8().constData()); return static_cast<int>(passphrase->size()); } else { // this shouldn't happen - so lets log it to tell us we have // a problem with the flow... qCCritical(commerce) << "no cached passphrase while decrypting!"; return 0; } } EC_KEY* readKeys(const char* filename) { FILE* fp; EC_KEY *key = NULL; if ((fp = fopen(filename, "rt"))) { // file opened successfully qCDebug(commerce) << "opened key file" << filename; if ((key = PEM_read_EC_PUBKEY(fp, NULL, NULL, NULL))) { // now read private key qCDebug(commerce) << "read public key"; if ((key = PEM_read_ECPrivateKey(fp, &key, passwordCallback, NULL))) { qCDebug(commerce) << "read private key"; fclose(fp); return key; } qCDebug(commerce) << "failed to read private key"; } else { qCDebug(commerce) << "failed to read public key"; } fclose(fp); } else { qCDebug(commerce) << "failed to open key file" << filename; } return key; } bool Wallet::writeBackupInstructions() { QString inputFilename(PathUtils::resourcesPath() + "html/commerce/backup_instructions.html"); QString outputFilename = PathUtils::getAppDataFilePath(INSTRUCTIONS_FILE); QFile inputFile(inputFilename); QFile outputFile(outputFilename); bool retval = false; if (getKeyFilePath().isEmpty()) { return false; } if (QFile::exists(inputFilename) && inputFile.open(QIODevice::ReadOnly)) { if (outputFile.open(QIODevice::ReadWrite)) { // Read the data from the original file, then close it QByteArray fileData = inputFile.readAll(); inputFile.close(); // Translate the data from the original file into a QString QString text(fileData); // Replace the necessary string text.replace(QString("HIFIKEY_PATH_REPLACEME"), keyFilePath()); // Write the new text back to the file outputFile.write(text.toUtf8()); // Close the output file outputFile.close(); retval = true; qCDebug(commerce) << "wrote html file successfully"; } else { qCDebug(commerce) << "failed to open output html file" << outputFilename; } } else { qCDebug(commerce) << "failed to open input html file" << inputFilename; } return retval; } bool writeKeys(const char* filename, EC_KEY* keys) { FILE* fp; bool retval = false; if ((fp = fopen(filename, "wt"))) { if (!PEM_write_EC_PUBKEY(fp, keys)) { fclose(fp); qCCritical(commerce) << "failed to write public key"; return retval; } if (!PEM_write_ECPrivateKey(fp, keys, EVP_des_ede3_cbc(), NULL, 0, passwordCallback, NULL)) { fclose(fp); qCCritical(commerce) << "failed to write private key"; return retval; } retval = true; qCDebug(commerce) << "wrote keys successfully"; fclose(fp); } else { qCDebug(commerce) << "failed to open key file" << filename; } return retval; } bool Wallet::setWallet(const QByteArray& wallet) { QFile file(keyFilePath()); if (!file.open(QIODevice::WriteOnly)) { qCCritical(commerce) << "Unable to open wallet for write in" << keyFilePath(); return false; } if (file.write(wallet) != wallet.count()) { qCCritical(commerce) << "Unable to write wallet in" << keyFilePath(); return false; } file.close(); return true; } QByteArray Wallet::getWallet() { QFile file(keyFilePath()); if (!file.open(QIODevice::ReadOnly)) { qCInfo(commerce) << "No existing wallet in" << keyFilePath(); return QByteArray(); } QByteArray wallet = file.readAll(); file.close(); return wallet; } QPair<QByteArray*, QByteArray*> generateECKeypair() { EC_KEY* keyPair = EC_KEY_new_by_curve_name(NID_secp256k1); QPair<QByteArray*, QByteArray*> retval{}; EC_KEY_set_asn1_flag(keyPair, OPENSSL_EC_NAMED_CURVE); if (!EC_KEY_generate_key(keyPair)) { qCDebug(commerce) << "Error generating EC Keypair -" << ERR_get_error(); return retval; } // grab the public key and private key from the file unsigned char* publicKeyDER = NULL; int publicKeyLength = i2d_EC_PUBKEY(keyPair, &publicKeyDER); unsigned char* privateKeyDER = NULL; int privateKeyLength = i2d_ECPrivateKey(keyPair, &privateKeyDER); if (publicKeyLength <= 0 || privateKeyLength <= 0) { qCDebug(commerce) << "Error getting DER public or private key from EC struct -" << ERR_get_error(); // cleanup the EC struct EC_KEY_free(keyPair); // cleanup the public and private key DER data, if required if (publicKeyLength > 0) { OPENSSL_free(publicKeyDER); } if (privateKeyLength > 0) { OPENSSL_free(privateKeyDER); } return retval; } if (!writeKeys(keyFilePath().toStdString().c_str(), keyPair)) { qCDebug(commerce) << "couldn't save keys!"; return retval; } EC_KEY_free(keyPair); // prepare the return values. TODO: Fix this - we probably don't really even want the // private key at all (better to read it when we need it?). Or maybe we do, when we have // multiple keys? retval.first = new QByteArray(reinterpret_cast<char*>(publicKeyDER), publicKeyLength); retval.second = new QByteArray(reinterpret_cast<char*>(privateKeyDER), privateKeyLength); // cleanup the publicKeyDER and publicKeyDER data OPENSSL_free(publicKeyDER); OPENSSL_free(privateKeyDER); return retval; } // END copied code (which will soon change) // the public key can just go into a byte array QByteArray readPublicKey(const char* filename) { FILE* fp; EC_KEY* key = NULL; if ((fp = fopen(filename, "r"))) { // file opened successfully qCDebug(commerce) << "opened key file" << filename; if ((key = PEM_read_EC_PUBKEY(fp, NULL, NULL, NULL))) { // file read successfully unsigned char* publicKeyDER = NULL; int publicKeyLength = i2d_EC_PUBKEY(key, &publicKeyDER); // TODO: check for 0 length? // cleanup EC_KEY_free(key); fclose(fp); qCDebug(commerce) << "parsed public key file successfully"; QByteArray retval((char*)publicKeyDER, publicKeyLength); OPENSSL_free(publicKeyDER); return retval; } else { qCDebug(commerce) << "couldn't parse" << filename; } fclose(fp); } else { qCDebug(commerce) << "couldn't open" << filename; } return QByteArray(); } // the private key should be read/copied into heap memory. For now, we need the EC_KEY struct // so I'll return that. EC_KEY* readPrivateKey(const char* filename) { FILE* fp; EC_KEY* key = NULL; if ((fp = fopen(filename, "r"))) { // file opened successfully qCDebug(commerce) << "opened key file" << filename; if ((key = PEM_read_ECPrivateKey(fp, &key, passwordCallback, NULL))) { qCDebug(commerce) << "parsed private key file successfully"; } else { qCDebug(commerce) << "couldn't parse" << filename; // if the passphrase is wrong, then let's not cache it DependencyManager::get<Wallet>()->setPassphrase(""); } fclose(fp); } else { qCDebug(commerce) << "couldn't open" << filename; } return key; } // QT's QByteArray will convert to Base64 without any embedded newlines. This just // writes it with embedded newlines, which is more readable. void outputBase64WithNewlines(QFile& file, const QByteArray& b64Array) { for (int i = 0; i < b64Array.size(); i += 64) { file.write(b64Array.mid(i, 64)); file.write("\n"); } } void initializeAESKeys(unsigned char* ivec, unsigned char* ckey, const QByteArray& salt) { // use the ones in the wallet auto wallet = DependencyManager::get<Wallet>(); memcpy(ivec, wallet->getIv(), 16); memcpy(ckey, wallet->getCKey(), 32); } Wallet::Wallet() { auto nodeList = DependencyManager::get<NodeList>(); auto ledger = DependencyManager::get<Ledger>(); auto& packetReceiver = nodeList->getPacketReceiver(); _passphrase = new QString(""); packetReceiver.registerListener(PacketType::ChallengeOwnership, this, "handleChallengeOwnershipPacket"); packetReceiver.registerListener(PacketType::ChallengeOwnershipRequest, this, "handleChallengeOwnershipPacket"); connect(ledger.data(), &Ledger::accountResult, this, [](QJsonObject result) { auto wallet = DependencyManager::get<Wallet>(); auto walletScriptingInterface = DependencyManager::get<WalletScriptingInterface>(); uint status; QString keyStatus = result.contains("data") ? result["data"].toObject()["keyStatus"].toString() : ""; if (wallet->getKeyFilePath().isEmpty() || !wallet->getSecurityImage()) { if (keyStatus == "preexisting") { status = (uint) WalletStatus::WALLET_STATUS_PREEXISTING; } else{ status = (uint) WalletStatus::WALLET_STATUS_NOT_SET_UP; } } else if (!wallet->walletIsAuthenticatedWithPassphrase()) { status = (uint) WalletStatus::WALLET_STATUS_NOT_AUTHENTICATED; } else if (keyStatus == "conflicting") { status = (uint) WalletStatus::WALLET_STATUS_CONFLICTING; } else { status = (uint) WalletStatus::WALLET_STATUS_READY; } walletScriptingInterface->setWalletStatus(status); }); auto accountManager = DependencyManager::get<AccountManager>(); connect(accountManager.data(), &AccountManager::usernameChanged, this, [&]() { getWalletStatus(); clear(); }); } void Wallet::clear() { _publicKeys.clear(); if (_securityImage) { delete _securityImage; } _securityImage = nullptr; // tell the provider we got nothing updateImageProvider(); _passphrase->clear(); } Wallet::~Wallet() { if (_securityImage) { delete _securityImage; } if (_passphrase) { delete _passphrase; } } bool Wallet::setPassphrase(const QString& passphrase) { if (_passphrase) { delete _passphrase; } _passphrase = new QString(passphrase); _publicKeys.clear(); writeBackupInstructions(); return true; } bool Wallet::writeSecurityImage(const QPixmap* pixmap, const QString& outputFilePath) { // aes requires a couple 128-bit keys (ckey and ivec). For now, I'll just // use the md5 of the salt as the ckey (md5 is 128-bit), and ivec will be // a constant. We can review this later - there are ways to generate keys // from a password that may be better. unsigned char ivec[16]; unsigned char ckey[32]; initializeAESKeys(ivec, ckey, _salt); int tempSize, outSize; QByteArray inputFileBuffer; QBuffer buffer(&inputFileBuffer); buffer.open(QIODevice::WriteOnly); // another spot where we are assuming only jpgs pixmap->save(&buffer, "jpg"); // reserve enough capacity for encrypted bytes unsigned char* outputFileBuffer = new unsigned char[inputFileBuffer.size() + AES_BLOCK_SIZE]; EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new(); // TODO: add error handling!!! if (!EVP_EncryptInit_ex(ctx, EVP_des_ede3_cbc(), NULL, ckey, ivec)) { qCDebug(commerce) << "encrypt init failure"; delete[] outputFileBuffer; return false; } if (!EVP_EncryptUpdate(ctx, outputFileBuffer, &tempSize, (unsigned char*)inputFileBuffer.data(), inputFileBuffer.size())) { qCDebug(commerce) << "encrypt update failure"; delete[] outputFileBuffer; return false; } outSize = tempSize; if (!EVP_EncryptFinal_ex(ctx, outputFileBuffer + outSize, &tempSize)) { qCDebug(commerce) << "encrypt final failure"; delete[] outputFileBuffer; return false; } outSize += tempSize; EVP_CIPHER_CTX_free(ctx); qCDebug(commerce) << "encrypted buffer size" << outSize; QByteArray output((const char*)outputFileBuffer, outSize); // now APPEND to the file, QByteArray b64output = output.toBase64(); QFile outputFile(outputFilePath); outputFile.open(QIODevice::Append); outputFile.write(IMAGE_HEADER); outputBase64WithNewlines(outputFile, b64output); outputFile.write(IMAGE_FOOTER); outputFile.close(); delete[] outputFileBuffer; return true; } bool Wallet::readSecurityImage(const QString& inputFilePath, unsigned char** outputBufferPtr, int* outputBufferSize) { unsigned char ivec[16]; unsigned char ckey[32]; initializeAESKeys(ivec, ckey, _salt); // read encrypted file QFile inputFile(inputFilePath); if (!inputFile.exists()) { qCDebug(commerce) << "cannot decrypt file" << inputFilePath << "it doesn't exist"; return false; } inputFile.open(QIODevice::ReadOnly | QIODevice::Text); bool foundHeader = false; bool foundFooter = false; QByteArray base64EncryptedBuffer; while (!inputFile.atEnd()) { QString line(inputFile.readLine()); if (!foundHeader) { foundHeader = (line == IMAGE_HEADER);