From f784bc42def386b6b5c5c4acda81c1548c81eff7 Mon Sep 17 00:00:00 2001 From: David Kelly Date: Fri, 11 Aug 2017 16:54:04 -0700 Subject: [PATCH 1/7] Initial cut using RSA keys for now --- interface/CMakeLists.txt | 6 + interface/src/commerce/Ledger.cpp | 8 +- interface/src/commerce/Wallet.cpp | 253 ++++++++++++++++++++++++++++-- interface/src/commerce/Wallet.h | 3 +- 4 files changed, 256 insertions(+), 14 deletions(-) diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 81c8a44baf..4ed95b59f7 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -183,6 +183,12 @@ if (WIN32) add_dependency_external_projects(steamworks) endif() +# include OPENSSL +include_directories(SYSTEM "${OPENSSL_INCLUDE_DIR}") + +# append OpenSSL to our list of libraries to link +target_link_libraries(${TARGET_NAME} ${OPENSSL_LIBRARIES}) + # disable /OPT:REF and /OPT:ICF for the Debug builds # This will prevent the following linker warnings # LINK : warning LNK4075: ignoring '/INCREMENTAL' due to '/OPT:ICF' specification diff --git a/interface/src/commerce/Ledger.cpp b/interface/src/commerce/Ledger.cpp index 0d9d780743..5973701633 100644 --- a/interface/src/commerce/Ledger.cpp +++ b/interface/src/commerce/Ledger.cpp @@ -24,12 +24,12 @@ bool Ledger::buy(const QString& hfc_key, int cost, const QString& asset_id, cons transaction["inventory_key"] = inventory_key; transaction["inventory_buyer_username"] = buyerUsername; QJsonDocument transactionDoc{ transaction }; - QString transactionString = transactionDoc.toJson(QJsonDocument::Compact); - + auto transactionString = transactionDoc.toJson(QJsonDocument::Compact); + auto wallet = DependencyManager::get(); QString signature = wallet->signWithKey(transactionString, hfc_key); QJsonObject request; - request["transaction"] = transactionString; + request["transaction"] = QString(transactionString); request["signature"] = signature; qCInfo(commerce) << "Transaction:" << QJsonDocument(request).toJson(QJsonDocument::Compact); @@ -45,4 +45,4 @@ bool Ledger::receiveAt(const QString& hfc_key) { auto username = accountManager->getAccountInfo().getUsername(); qCInfo(commerce) << "Setting default receiving key for" << username; return true; // FIXME send to server. -} \ No newline at end of file +} diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index d2a6ae4809..6f0329b8ba 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -9,25 +9,230 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include #include "CommerceLogging.h" #include "Ledger.h" #include "Wallet.h" +#include + +#include + +#include +#include +#include +#include +#include +#include + +static const char* PUBLIC_KEY_FILE = "hifikey.pub"; +static const char* PRIVATE_KEY_FILE = "hifikey"; + +void initialize() { + static bool initialized = false; + if (!initialized) { + SSL_load_error_strings(); + SSL_library_init(); + OpenSSL_add_all_algorithms(); + initialized = true; + } +} + +QString publicKeyFilePath() { + return PathUtils::getAppDataFilePath(PUBLIC_KEY_FILE); +} + +QString privateKeyFilePath() { + return PathUtils::getAppDataFilePath(PRIVATE_KEY_FILE); +} +// for now the callback function just returns the same string. Later we can hook +// this to the gui (some thought required) + +int passwordCallback(char* password, int maxPasswordSize, int rwFlag, void* u) { + // just return a hardcoded pwd for now + static const char* pwd = "pwd"; + strcpy(password, pwd); + return strlen(pwd); +} + +// 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 +QPair generateRSAKeypair() { + + RSA* keyPair = RSA_new(); + BIGNUM* exponent = BN_new(); + QPair retval; + + const unsigned long RSA_KEY_EXPONENT = 65537; + BN_set_word(exponent, RSA_KEY_EXPONENT); + + // seed the random number generator before we call RSA_generate_key_ex + srand(time(NULL)); + + const int RSA_KEY_BITS = 2048; + + if (!RSA_generate_key_ex(keyPair, RSA_KEY_BITS, exponent, NULL)) { + qCDebug(commerce) << "Error generating 2048-bit RSA Keypair -" << ERR_get_error(); + + // we're going to bust out of here but first we cleanup the BIGNUM + BN_free(exponent); + return retval; + } + + // we don't need the BIGNUM anymore so clean that up + BN_free(exponent); + + // grab the public key and private key from the file + unsigned char* publicKeyDER = NULL; + int publicKeyLength = i2d_RSAPublicKey(keyPair, &publicKeyDER); + + unsigned char* privateKeyDER = NULL; + int privateKeyLength = i2d_RSAPrivateKey(keyPair, &privateKeyDER); + + if (publicKeyLength <= 0 || privateKeyLength <= 0) { + qCDebug(commerce) << "Error getting DER public or private key from RSA struct -" << ERR_get_error(); + + + // cleanup the RSA struct + RSA_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; + } + + + + // now lets persist them to files + // TODO: figure out a scheme for multiple keys, etc... + FILE* fp; + if (fp = fopen(publicKeyFilePath().toStdString().c_str(), "wt")) { + if (!PEM_write_RSAPublicKey(fp, keyPair)) { + fclose(fp); + qCDebug(commerce) << "failed to write public key"; + return retval; + } + fclose(fp); + } + + if (fp = fopen(privateKeyFilePath().toStdString().c_str(), "wt")) { + char pwd[] = "pwd"; + if (!PEM_write_RSAPrivateKey(fp, keyPair, EVP_des_ede3_cbc(), NULL, 0, passwordCallback, NULL)) { + fclose(fp); + qCDebug(commerce) << "failed to write private key"; + return retval; + } + fclose(fp); + } + + RSA_free(keyPair); + + // prepare the return values + retval.first = new QByteArray(reinterpret_cast(publicKeyDER), publicKeyLength ), + retval.second = new QByteArray(reinterpret_cast(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; + RSA* key = NULL; + if ( fp = fopen(filename, "r")) { + // file opened successfully + qCDebug(commerce) << "opened key file" << filename; + if (key = PEM_read_RSAPublicKey(fp, NULL, NULL, NULL)) { + // file read successfully + unsigned char* publicKeyDER = NULL; + int publicKeyLength = i2d_RSAPublicKey(key, &publicKeyDER); + // TODO: check for 0 length? + + // cleanup + RSA_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 RSA struct +// so I'll return that. Note we need to RSA_free(key) later!!! +RSA* readPrivateKey(const char* filename) { + FILE* fp; + RSA* key = NULL; + if ( fp = fopen(filename, "r")) { + // file opened successfully + qCDebug(commerce) << "opened key file" << filename; + if (key = PEM_read_RSAPrivateKey(fp, &key, passwordCallback, NULL)) { + // cleanup + fclose(fp); + + qCDebug(commerce) << "parsed private key file successfully"; + return key; + + } else { + qCDebug(commerce) << "couldn't parse" << filename; + } + fclose(fp); + } else { + qCDebug(commerce) << "couldn't open" << filename; + } + return false; +} + bool Wallet::createIfNeeded() { - // FIXME: persist in file between sessions. if (_publicKeys.count() > 0) return false; + + // FIXME: initialize OpenSSL elsewhere soon + initialize(); + + // try to read existing keys if they exist... + auto publicKey = readPublicKey(publicKeyFilePath().toStdString().c_str()); + if (publicKey.size() > 0) { + if (auto key = readPrivateKey(privateKeyFilePath().toStdString().c_str()) ) { + qCDebug(commerce) << "read private key"; + RSA_free(key); + // K -- add the public key since we have a legit private key associated with it + _publicKeys.push_back(publicKey.toBase64()); + return false; + } + } qCInfo(commerce) << "Creating wallet."; return generateKeyPair(); } bool Wallet::generateKeyPair() { - // FIXME: need private key, too, and persist in file. qCInfo(commerce) << "Generating keypair."; - QString key = QUuid::createUuid().toString(); - _publicKeys.push_back(key); + auto keyPair = generateRSAKeypair(); + + // TODO: do we need to pass params in here to make sure this is url-safe base64? + _publicKeys.push_back(keyPair.first->toBase64()); + qCDebug(commerce) << "public key:" << keyPair.first->toBase64(); + auto ledger = DependencyManager::get(); - return ledger->receiveAt(key); + return ledger->receiveAt(_publicKeys.last()); } QStringList Wallet::listPublicKeys() { qCInfo(commerce) << "Enumerating public keys."; @@ -35,7 +240,37 @@ QStringList Wallet::listPublicKeys() { return _publicKeys; } -QString Wallet::signWithKey(const QString& text, const QString& key) { +// for now a copy of how we sign in libraries/networking/src/DataServerAccountInfo - +// we sha256 the text, read the private key from disk (for now!), and return the signed +// sha256. Note later with multiple keys, we may need the key parameter (or something +// similar) so I left it alone for now. Also this will probably change when we move +// away from RSA keys anyways. Note that since this returns a QString, we better avoid +// the horror of code pages and so on (changing the bytes) by just returning a base64 +// encoded string representing the signature (suitable for http, etc...) +QString Wallet::signWithKey(const QByteArray& text, const QString& key) { qCInfo(commerce) << "Signing text."; - return "fixme signed"; -} \ No newline at end of file + RSA* rsaPrivateKey = NULL; + if (rsaPrivateKey = readPrivateKey(privateKeyFilePath().toStdString().c_str())) { + QByteArray signature(RSA_size(rsaPrivateKey), 0); + unsigned int signatureBytes = 0; + + QByteArray hashedPlaintext = QCryptographicHash::hash(text, QCryptographicHash::Sha256); + + int encryptReturn = RSA_sign(NID_sha256, + reinterpret_cast(hashedPlaintext.constData()), + hashedPlaintext.size(), + reinterpret_cast(signature.data()), + &signatureBytes, + rsaPrivateKey); + + // free the private key RSA struct now that we are done with it + RSA_free(rsaPrivateKey); + + if (encryptReturn != -1) { + // TODO: do we need to pass options in here to make sure it is url-safe? + return signature.toBase64(); + } + } + return QString(); +} + diff --git a/interface/src/commerce/Wallet.h b/interface/src/commerce/Wallet.h index 7cfb14c30d..a994f3e5b1 100644 --- a/interface/src/commerce/Wallet.h +++ b/interface/src/commerce/Wallet.h @@ -24,10 +24,11 @@ public: bool createIfNeeded(); bool generateKeyPair(); QStringList listPublicKeys(); - QString signWithKey(const QString& text, const QString& key); + QString signWithKey(const QByteArray& text, const QString& key); private: QStringList _publicKeys{}; + }; #endif // hifi_Wallet_h From b4b592cc00afef617c1204be775243d64771b883 Mon Sep 17 00:00:00 2001 From: David Kelly Date: Mon, 14 Aug 2017 12:53:10 -0700 Subject: [PATCH 2/7] one key file with (for now) both public and private keys in there --- interface/src/commerce/Wallet.cpp | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index 6f0329b8ba..977fe0fdfb 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -24,8 +24,7 @@ #include #include -static const char* PUBLIC_KEY_FILE = "hifikey.pub"; -static const char* PRIVATE_KEY_FILE = "hifikey"; +static const char* KEY_FILE = "hifikey"; void initialize() { static bool initialized = false; @@ -37,16 +36,12 @@ void initialize() { } } -QString publicKeyFilePath() { - return PathUtils::getAppDataFilePath(PUBLIC_KEY_FILE); +QString keyFilePath() { + return PathUtils::getAppDataFilePath(KEY_FILE); } -QString privateKeyFilePath() { - return PathUtils::getAppDataFilePath(PRIVATE_KEY_FILE); -} // for now the callback function just returns the same string. Later we can hook // this to the gui (some thought required) - int passwordCallback(char* password, int maxPasswordSize, int rwFlag, void* u) { // just return a hardcoded pwd for now static const char* pwd = "pwd"; @@ -113,16 +108,13 @@ QPair generateRSAKeypair() { // now lets persist them to files // TODO: figure out a scheme for multiple keys, etc... FILE* fp; - if (fp = fopen(publicKeyFilePath().toStdString().c_str(), "wt")) { + if (fp = fopen(keyFilePath().toStdString().c_str(), "wt")) { if (!PEM_write_RSAPublicKey(fp, keyPair)) { fclose(fp); qCDebug(commerce) << "failed to write public key"; return retval; } - fclose(fp); - } - if (fp = fopen(privateKeyFilePath().toStdString().c_str(), "wt")) { char pwd[] = "pwd"; if (!PEM_write_RSAPrivateKey(fp, keyPair, EVP_des_ede3_cbc(), NULL, 0, passwordCallback, NULL)) { fclose(fp); @@ -209,9 +201,9 @@ bool Wallet::createIfNeeded() { initialize(); // try to read existing keys if they exist... - auto publicKey = readPublicKey(publicKeyFilePath().toStdString().c_str()); + auto publicKey = readPublicKey(keyFilePath().toStdString().c_str()); if (publicKey.size() > 0) { - if (auto key = readPrivateKey(privateKeyFilePath().toStdString().c_str()) ) { + if (auto key = readPrivateKey(keyFilePath().toStdString().c_str()) ) { qCDebug(commerce) << "read private key"; RSA_free(key); // K -- add the public key since we have a legit private key associated with it @@ -250,7 +242,7 @@ QStringList Wallet::listPublicKeys() { QString Wallet::signWithKey(const QByteArray& text, const QString& key) { qCInfo(commerce) << "Signing text."; RSA* rsaPrivateKey = NULL; - if (rsaPrivateKey = readPrivateKey(privateKeyFilePath().toStdString().c_str())) { + if (rsaPrivateKey = readPrivateKey(keyFilePath().toStdString().c_str())) { QByteArray signature(RSA_size(rsaPrivateKey), 0); unsigned int signatureBytes = 0; From 170d2b186b45ce0d22be4517c44362dd41d4d75c Mon Sep 17 00:00:00 2001 From: David Kelly Date: Mon, 14 Aug 2017 13:48:09 -0700 Subject: [PATCH 3/7] url encode the base64 signature/key --- interface/src/commerce/Wallet.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index c7ebc9c96f..dd1a463237 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -207,7 +207,7 @@ bool Wallet::createIfNeeded() { qCDebug(commerce) << "read private key"; RSA_free(key); // K -- add the public key since we have a legit private key associated with it - _publicKeys.push_back(publicKey.toBase64()); + _publicKeys.push_back(QUrl::toPercentEncoding(publicKey.toBase64())); return false; } } @@ -219,9 +219,8 @@ bool Wallet::generateKeyPair() { qCInfo(commerce) << "Generating keypair."; auto keyPair = generateRSAKeypair(); - // TODO: do we need to pass params in here to make sure this is url-safe base64? - _publicKeys.push_back(keyPair.first->toBase64()); - qCDebug(commerce) << "public key:" << keyPair.first->toBase64(); + _publicKeys.push_back(QUrl::toPercentEncoding(keyPair.first->toBase64())); + qCDebug(commerce) << "public key:" << _publicKeys.last; // It's arguable whether we want to change the receiveAt every time, but: // 1. It's certainly needed the first time, when createIfNeeded answers true. @@ -264,7 +263,7 @@ QString Wallet::signWithKey(const QByteArray& text, const QString& key) { if (encryptReturn != -1) { // TODO: do we need to pass options in here to make sure it is url-safe? - return signature.toBase64(); + return QUrl::toPercentEncoding(signature.toBase64()); } } return QString(); From ef5863d169d20a29851c0d86d70b7c1958855527 Mon Sep 17 00:00:00 2001 From: David Kelly Date: Mon, 14 Aug 2017 16:24:50 -0700 Subject: [PATCH 4/7] warnings squashed --- interface/src/commerce/Wallet.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index dd1a463237..04005c37e1 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -108,7 +108,7 @@ QPair generateRSAKeypair() { // now lets persist them to files // TODO: figure out a scheme for multiple keys, etc... FILE* fp; - if (fp = fopen(keyFilePath().toStdString().c_str(), "wt")) { + if ((fp = fopen(keyFilePath().toStdString().c_str(), "wt"))) { if (!PEM_write_RSAPublicKey(fp, keyPair)) { fclose(fp); qCDebug(commerce) << "failed to write public key"; @@ -141,7 +141,7 @@ QPair generateRSAKeypair() { QByteArray readPublicKey(const char* filename) { FILE* fp; RSA* key = NULL; - if ( fp = fopen(filename, "r")) { + if ((fp = fopen(filename, "r"))) { // file opened successfully qCDebug(commerce) << "opened key file" << filename; if (key = PEM_read_RSAPublicKey(fp, NULL, NULL, NULL)) { @@ -174,7 +174,7 @@ QByteArray readPublicKey(const char* filename) { RSA* readPrivateKey(const char* filename) { FILE* fp; RSA* key = NULL; - if ( fp = fopen(filename, "r")) { + if ((fp = fopen(filename, "r"))) { // file opened successfully qCDebug(commerce) << "opened key file" << filename; if (key = PEM_read_RSAPrivateKey(fp, &key, passwordCallback, NULL)) { @@ -220,7 +220,7 @@ bool Wallet::generateKeyPair() { auto keyPair = generateRSAKeypair(); _publicKeys.push_back(QUrl::toPercentEncoding(keyPair.first->toBase64())); - qCDebug(commerce) << "public key:" << _publicKeys.last; + qCDebug(commerce) << "public key:" << _publicKeys.last(); // It's arguable whether we want to change the receiveAt every time, but: // 1. It's certainly needed the first time, when createIfNeeded answers true. @@ -245,7 +245,7 @@ QStringList Wallet::listPublicKeys() { QString Wallet::signWithKey(const QByteArray& text, const QString& key) { qCInfo(commerce) << "Signing text."; RSA* rsaPrivateKey = NULL; - if (rsaPrivateKey = readPrivateKey(keyFilePath().toStdString().c_str())) { + if ((rsaPrivateKey = readPrivateKey(keyFilePath().toStdString().c_str()))) { QByteArray signature(RSA_size(rsaPrivateKey), 0); unsigned int signatureBytes = 0; @@ -262,7 +262,6 @@ QString Wallet::signWithKey(const QByteArray& text, const QString& key) { RSA_free(rsaPrivateKey); if (encryptReturn != -1) { - // TODO: do we need to pass options in here to make sure it is url-safe? return QUrl::toPercentEncoding(signature.toBase64()); } } From c08fce967f4f483c2bb66eb6efdd82843c463c6b Mon Sep 17 00:00:00 2001 From: David Kelly Date: Tue, 15 Aug 2017 08:58:16 -0700 Subject: [PATCH 5/7] forgot a warning --- interface/src/commerce/Wallet.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index 04005c37e1..4b89386c3e 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -46,7 +46,7 @@ int passwordCallback(char* password, int maxPasswordSize, int rwFlag, void* u) { // just return a hardcoded pwd for now static const char* pwd = "pwd"; strcpy(password, pwd); - return strlen(pwd); + return static_cast(strlen(pwd)); } // BEGIN copied code - this will be removed/changed at some point soon From a6390edf4639f8287fe9b8e48c588cee0910f182 Mon Sep 17 00:00:00 2001 From: David Kelly Date: Tue, 15 Aug 2017 10:10:48 -0700 Subject: [PATCH 6/7] ugh --- interface/src/commerce/Wallet.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index 4b89386c3e..8a81faec49 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -15,7 +15,7 @@ #include -#include +#include #include #include From 0f7db74ef12d16debc448016b3ce31ad7cb6917f Mon Sep 17 00:00:00 2001 From: David Kelly Date: Tue, 15 Aug 2017 11:44:15 -0700 Subject: [PATCH 7/7] missed a couple warnings --- interface/src/commerce/Wallet.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index 8a81faec49..7a980528ce 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -115,7 +115,6 @@ QPair generateRSAKeypair() { return retval; } - char pwd[] = "pwd"; if (!PEM_write_RSAPrivateKey(fp, keyPair, EVP_des_ede3_cbc(), NULL, 0, passwordCallback, NULL)) { fclose(fp); qCDebug(commerce) << "failed to write private key"; @@ -144,7 +143,7 @@ QByteArray readPublicKey(const char* filename) { if ((fp = fopen(filename, "r"))) { // file opened successfully qCDebug(commerce) << "opened key file" << filename; - if (key = PEM_read_RSAPublicKey(fp, NULL, NULL, NULL)) { + if ((key = PEM_read_RSAPublicKey(fp, NULL, NULL, NULL))) { // file read successfully unsigned char* publicKeyDER = NULL; int publicKeyLength = i2d_RSAPublicKey(key, &publicKeyDER); @@ -177,12 +176,11 @@ RSA* readPrivateKey(const char* filename) { if ((fp = fopen(filename, "r"))) { // file opened successfully qCDebug(commerce) << "opened key file" << filename; - if (key = PEM_read_RSAPrivateKey(fp, &key, passwordCallback, NULL)) { + if ((key = PEM_read_RSAPrivateKey(fp, &key, passwordCallback, NULL))) { // cleanup fclose(fp); qCDebug(commerce) << "parsed private key file successfully"; - return key; } else { qCDebug(commerce) << "couldn't parse" << filename; @@ -191,7 +189,7 @@ RSA* readPrivateKey(const char* filename) { } else { qCDebug(commerce) << "couldn't open" << filename; } - return false; + return key; } bool Wallet::createIfNeeded() {