mirror of
https://github.com/JulianGro/overte.git
synced 2025-04-10 17:23:15 +02:00
Merge pull request #11343 from davidkelly/dk/oneFileWallet
Wallet updates
This commit is contained in:
commit
030b39050a
3 changed files with 124 additions and 72 deletions
|
@ -18,10 +18,12 @@
|
|||
|
||||
#include <PathUtils.h>
|
||||
#include <OffscreenUi.h>
|
||||
#include <AccountManager.h>
|
||||
|
||||
#include <QFile>
|
||||
#include <QCryptographicHash>
|
||||
#include <QQmlContext>
|
||||
#include <QBuffer>
|
||||
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/err.h>
|
||||
|
@ -39,7 +41,8 @@
|
|||
#endif
|
||||
|
||||
static const char* KEY_FILE = "hifikey";
|
||||
static const char* IMAGE_FILE = "hifi_image"; // eventually this will live in keyfile
|
||||
static const char* IMAGE_HEADER = "-----BEGIN SECURITY IMAGE-----\n";
|
||||
static const char* IMAGE_FOOTER = "-----END SECURITY IMAGE-----\n";
|
||||
|
||||
void initialize() {
|
||||
static bool initialized = false;
|
||||
|
@ -52,11 +55,8 @@ void initialize() {
|
|||
}
|
||||
|
||||
QString keyFilePath() {
|
||||
return PathUtils::getAppDataFilePath(KEY_FILE);
|
||||
}
|
||||
|
||||
QString imageFilePath() {
|
||||
return PathUtils::getAppDataFilePath(IMAGE_FILE);
|
||||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
return PathUtils::getAppDataFilePath(QString("%1.%2").arg(accountManager->getAccountInfo().getUsername(), KEY_FILE));
|
||||
}
|
||||
|
||||
// use the cached _passphrase if it exists, otherwise we need to prompt
|
||||
|
@ -131,13 +131,12 @@ bool writeKeys(const char* filename, RSA* keys) {
|
|||
return retval;
|
||||
}
|
||||
|
||||
|
||||
// BEGIN copied code - this will be removed/changed at some point soon
|
||||
// copied (without emits for various signals) from libraries/networking/src/RSAKeypairGenerator.cpp.
|
||||
// We will have a different implementation in practice, but this gives us a start for now
|
||||
//
|
||||
// NOTE: we don't really use the private keys returned - we can see how this evolves, but probably
|
||||
// TODO: we don't really use the private keys returned - we can see how this evolves, but probably
|
||||
// we should just return a list of public keys?
|
||||
// or perhaps return the RSA* instead?
|
||||
QPair<QByteArray*, QByteArray*> generateRSAKeypair() {
|
||||
|
||||
RSA* keyPair = RSA_new();
|
||||
|
@ -265,6 +264,15 @@ RSA* readPrivateKey(const char* 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>();
|
||||
|
@ -287,8 +295,7 @@ void Wallet::setPassphrase(const QString& passphrase) {
|
|||
_publicKeys.clear();
|
||||
}
|
||||
|
||||
// encrypt some stuff
|
||||
bool Wallet::encryptFile(const QString& inputFilePath, const QString& outputFilePath) {
|
||||
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
|
||||
|
@ -299,16 +306,12 @@ bool Wallet::encryptFile(const QString& inputFilePath, const QString& outputFile
|
|||
initializeAESKeys(ivec, ckey, _salt);
|
||||
|
||||
int tempSize, outSize;
|
||||
QByteArray inputFileBuffer;
|
||||
QBuffer buffer(&inputFileBuffer);
|
||||
buffer.open(QIODevice::WriteOnly);
|
||||
|
||||
// read entire unencrypted file into memory
|
||||
QFile inputFile(inputFilePath);
|
||||
if (!inputFile.exists()) {
|
||||
qCDebug(commerce) << "cannot encrypt" << inputFilePath << "file doesn't exist";
|
||||
return false;
|
||||
}
|
||||
inputFile.open(QIODevice::ReadOnly);
|
||||
QByteArray inputFileBuffer = inputFile.readAll();
|
||||
inputFile.close();
|
||||
// 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];
|
||||
|
@ -337,16 +340,21 @@ bool Wallet::encryptFile(const QString& inputFilePath, const QString& outputFile
|
|||
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::WriteOnly);
|
||||
outputFile.write(output);
|
||||
outputFile.open(QIODevice::Append);
|
||||
outputFile.write(IMAGE_HEADER);
|
||||
outputBase64WithNewlines(outputFile, b64output);
|
||||
outputFile.write(IMAGE_FOOTER);
|
||||
outputFile.close();
|
||||
|
||||
delete[] outputFileBuffer;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Wallet::decryptFile(const QString& inputFilePath, unsigned char** outputBufferPtr, int* outputBufferSize) {
|
||||
bool Wallet::readSecurityImage(const QString& inputFilePath, unsigned char** outputBufferPtr, int* outputBufferSize) {
|
||||
unsigned char ivec[16];
|
||||
unsigned char ckey[32];
|
||||
initializeAESKeys(ivec, ckey, _salt);
|
||||
|
@ -357,9 +365,31 @@ bool Wallet::decryptFile(const QString& inputFilePath, unsigned char** outputBuf
|
|||
qCDebug(commerce) << "cannot decrypt file" << inputFilePath << "it doesn't exist";
|
||||
return false;
|
||||
}
|
||||
inputFile.open(QIODevice::ReadOnly);
|
||||
QByteArray encryptedBuffer = inputFile.readAll();
|
||||
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);
|
||||
} else {
|
||||
foundFooter = (line == IMAGE_FOOTER);
|
||||
if (!foundFooter) {
|
||||
base64EncryptedBuffer.append(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
inputFile.close();
|
||||
if (! (foundHeader && foundFooter)) {
|
||||
qCDebug(commerce) << "couldn't parse" << inputFilePath << foundHeader << foundFooter;
|
||||
return false;
|
||||
}
|
||||
|
||||
// convert to bytes
|
||||
auto encryptedBuffer = QByteArray::fromBase64(base64EncryptedBuffer);
|
||||
|
||||
// setup decrypted buffer
|
||||
unsigned char* outputBuffer = new unsigned char[encryptedBuffer.size()];
|
||||
|
@ -428,6 +458,9 @@ bool Wallet::generateKeyPair() {
|
|||
|
||||
qCInfo(commerce) << "Generating keypair.";
|
||||
auto keyPair = generateRSAKeypair();
|
||||
|
||||
// TODO: redo this soon -- need error checking and so on
|
||||
writeSecurityImage(_securityImage, keyFilePath());
|
||||
sendKeyFilePathIfExists();
|
||||
QString oldKey = _publicKeys.count() == 0 ? "" : _publicKeys.last();
|
||||
QString key = keyPair.first->toBase64();
|
||||
|
@ -493,27 +526,30 @@ void Wallet::chooseSecurityImage(const QString& filename) {
|
|||
if (_securityImage) {
|
||||
delete _securityImage;
|
||||
}
|
||||
// temporary...
|
||||
QString path = qApp->applicationDirPath();
|
||||
path.append("/resources/qml/hifi/commerce/wallet/");
|
||||
path.append(filename);
|
||||
|
||||
// now create a new security image pixmap
|
||||
_securityImage = new QPixmap();
|
||||
|
||||
qCDebug(commerce) << "loading data for pixmap from" << path;
|
||||
_securityImage->load(path);
|
||||
|
||||
// encrypt it and save.
|
||||
if (encryptFile(path, imageFilePath())) {
|
||||
qCDebug(commerce) << "emitting pixmap";
|
||||
|
||||
updateImageProvider();
|
||||
// update the image now
|
||||
updateImageProvider();
|
||||
|
||||
// we could be choosing the _inital_ security image. If so, there
|
||||
// will be no hifikey file yet. If that is the case, we are done. If
|
||||
// there _is_ a keyfile, we need to update it (similar to changing the
|
||||
// passphrase, we need to do so into a temp file and move it).
|
||||
if (!QFile(keyFilePath()).exists()) {
|
||||
emit securityImageResult(true);
|
||||
} else {
|
||||
qCDebug(commerce) << "failed to encrypt security image";
|
||||
emit securityImageResult(false);
|
||||
return;
|
||||
}
|
||||
|
||||
bool success = writeWallet();
|
||||
emit securityImageResult(success);
|
||||
}
|
||||
|
||||
void Wallet::getSecurityImage() {
|
||||
|
@ -526,10 +562,11 @@ void Wallet::getSecurityImage() {
|
|||
return;
|
||||
}
|
||||
|
||||
// decrypt and return
|
||||
QString filePath(imageFilePath());
|
||||
QFileInfo fileInfo(filePath);
|
||||
if (fileInfo.exists() && decryptFile(filePath, &data, &dataLen)) {
|
||||
bool success = false;
|
||||
// decrypt and return. Don't bother if we have no file to decrypt, or
|
||||
// no salt set yet.
|
||||
QFileInfo fileInfo(keyFilePath());
|
||||
if (fileInfo.exists() && _salt.size() > 0 && readSecurityImage(keyFilePath(), &data, &dataLen)) {
|
||||
// create the pixmap
|
||||
_securityImage = new QPixmap();
|
||||
_securityImage->loadFromData(data, dataLen, "jpg");
|
||||
|
@ -538,11 +575,9 @@ void Wallet::getSecurityImage() {
|
|||
updateImageProvider();
|
||||
|
||||
delete[] data;
|
||||
emit securityImageResult(true);
|
||||
} else {
|
||||
qCDebug(commerce) << "failed to decrypt security image (maybe none saved yet?)";
|
||||
emit securityImageResult(false);
|
||||
success = true;
|
||||
}
|
||||
emit securityImageResult(success);
|
||||
}
|
||||
void Wallet::sendKeyFilePathIfExists() {
|
||||
QString filePath(keyFilePath());
|
||||
|
@ -566,35 +601,47 @@ void Wallet::reset() {
|
|||
|
||||
|
||||
QFile keyFile(keyFilePath());
|
||||
QFile imageFile(imageFilePath());
|
||||
keyFile.remove();
|
||||
imageFile.remove();
|
||||
}
|
||||
bool Wallet::writeWallet(const QString& newPassphrase) {
|
||||
RSA* keys = readKeys(keyFilePath().toStdString().c_str());
|
||||
if (keys) {
|
||||
// we read successfully, so now write to a new temp file
|
||||
QString tempFileName = QString("%1.%2").arg(keyFilePath(), QString("temp"));
|
||||
QString oldPassphrase = *_passphrase;
|
||||
if (!newPassphrase.isEmpty()) {
|
||||
setPassphrase(newPassphrase);
|
||||
}
|
||||
if (writeKeys(tempFileName.toStdString().c_str(), keys)) {
|
||||
if (writeSecurityImage(_securityImage, tempFileName)) {
|
||||
// ok, now move the temp file to the correct spot
|
||||
QFile(QString(keyFilePath())).remove();
|
||||
QFile(tempFileName).rename(QString(keyFilePath()));
|
||||
qCDebug(commerce) << "wallet written successfully";
|
||||
return true;
|
||||
} else {
|
||||
qCDebug(commerce) << "couldn't write security image to temp wallet";
|
||||
}
|
||||
} else {
|
||||
qCDebug(commerce) << "couldn't write keys to temp wallet";
|
||||
}
|
||||
// if we are here, we failed, so cleanup
|
||||
QFile(tempFileName).remove();
|
||||
if (!newPassphrase.isEmpty()) {
|
||||
setPassphrase(oldPassphrase);
|
||||
}
|
||||
|
||||
} else {
|
||||
qCDebug(commerce) << "couldn't read wallet - bad passphrase?";
|
||||
// TODO: review this, but it seems best to reset the passphrase
|
||||
// since we couldn't decrypt the existing wallet (or is doesn't
|
||||
// exist perhaps).
|
||||
setPassphrase("");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Wallet::changePassphrase(const QString& newPassphrase) {
|
||||
qCDebug(commerce) << "changing passphrase";
|
||||
RSA* keys = readKeys(keyFilePath().toStdString().c_str());
|
||||
if (keys) {
|
||||
// we read successfully, so now write to a new temp file
|
||||
// save old passphrase just in case
|
||||
// TODO: force re-enter?
|
||||
QString oldPassphrase = *_passphrase;
|
||||
setPassphrase(newPassphrase);
|
||||
QString tempFileName = QString("%1.%2").arg(keyFilePath(), QString("temp"));
|
||||
if (writeKeys(tempFileName.toStdString().c_str(), keys)) {
|
||||
// ok, now move the temp file to the correct spot
|
||||
QFile(QString(keyFilePath())).remove();
|
||||
QFile(tempFileName).rename(QString(keyFilePath()));
|
||||
qCDebug(commerce) << "passphrase changed successfully";
|
||||
return true;
|
||||
} else {
|
||||
qCDebug(commerce) << "couldn't write keys";
|
||||
QFile(tempFileName).remove();
|
||||
setPassphrase(oldPassphrase);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
qCDebug(commerce) << "couldn't decrypt keys with current passphrase, clearing";
|
||||
setPassphrase(QString(""));
|
||||
return false;
|
||||
return writeWallet(newPassphrase);
|
||||
}
|
||||
|
|
|
@ -55,14 +55,15 @@ signals:
|
|||
private:
|
||||
QStringList _publicKeys{};
|
||||
QPixmap* _securityImage { nullptr };
|
||||
QByteArray _salt {"iamsalt!"};
|
||||
QByteArray _salt;
|
||||
QByteArray _iv;
|
||||
QByteArray _ckey;
|
||||
QString* _passphrase { new QString("") };
|
||||
|
||||
bool writeWallet(const QString& newPassphrase = QString(""));
|
||||
void updateImageProvider();
|
||||
bool encryptFile(const QString& inputFilePath, const QString& outputFilePath);
|
||||
bool decryptFile(const QString& inputFilePath, unsigned char** outputBufferPtr, int* outputBufferLen);
|
||||
bool writeSecurityImage(const QPixmap* pixmap, const QString& outputFilePath);
|
||||
bool readSecurityImage(const QString& inputFilePath, unsigned char** outputBufferPtr, int* outputBufferLen);
|
||||
};
|
||||
|
||||
#endif // hifi_Wallet_h
|
||||
|
|
|
@ -51,5 +51,9 @@ QPixmap ImageProvider::requestPixmap(const QString& id, QSize* size, const QSize
|
|||
return _securityImage->copy();
|
||||
}
|
||||
}
|
||||
return QPixmap();
|
||||
// otherwise just return a grey pixmap. This avoids annoying error messages in qml we would get
|
||||
// when sending a 'null' pixmap (QPixmap())
|
||||
QPixmap greyPixmap(200, 200);
|
||||
greyPixmap.fill(QColor("darkGrey"));
|
||||
return greyPixmap;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue