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/resources/qml/hifi/commerce/Checkout.qml b/interface/resources/qml/hifi/commerce/Checkout.qml
index 865bb72921..b9d15b61e4 100644
--- a/interface/resources/qml/hifi/commerce/Checkout.qml
+++ b/interface/resources/qml/hifi/commerce/Checkout.qml
@@ -66,6 +66,14 @@ Rectangle {
}
}
}
+ onSecurityImageResult: {
+ securityImage.source = securityImageSelection.getImagePathFromImageID(imageID);
+ }
+ }
+
+ SecurityImageSelection {
+ id: securityImageSelection;
+ referrerURL: checkoutRoot.itemHref;
}
//
@@ -80,6 +88,20 @@ Rectangle {
anchors.left: parent.left;
anchors.top: parent.top;
+ // Security Image
+ Image {
+ id: securityImage;
+ // Anchors
+ anchors.top: parent.top;
+ anchors.left: parent.left;
+ anchors.leftMargin: 16;
+ height: parent.height - 5;
+ width: height;
+ anchors.verticalCenter: parent.verticalCenter;
+ fillMode: Image.PreserveAspectFit;
+ mipmap: true;
+ }
+
// Title Bar text
RalewaySemiBold {
id: titleBarText;
@@ -87,8 +109,11 @@ Rectangle {
// Text size
size: hifi.fontSizes.overlayTitle;
// Anchors
- anchors.fill: parent;
+ anchors.top: parent.top;
+ anchors.left: securityImage.right;
anchors.leftMargin: 16;
+ anchors.bottom: parent.bottom;
+ width: paintedWidth;
// Style
color: hifi.colors.lightGrayText;
// Alignment
@@ -381,7 +406,7 @@ Rectangle {
// "Buy" button
HifiControlsUit.Button {
id: buyButton;
- enabled: balanceAfterPurchase >= 0 && !alreadyOwned && inventoryReceived && balanceReceived;
+ enabled: balanceAfterPurchase >= 0 && inventoryReceived && balanceReceived;
color: hifi.buttons.black;
colorScheme: hifi.colorSchemes.dark;
anchors.top: parent.top;
@@ -391,9 +416,16 @@ Rectangle {
anchors.right: parent.right;
anchors.rightMargin: 20;
width: parent.width/2 - anchors.rightMargin*2;
- text: (inventoryReceived && balanceReceived) ? (alreadyOwned ? "Already Owned" : "Buy") : "--";
+ text: (inventoryReceived && balanceReceived) ? (alreadyOwned ? "Already Owned: Get Item" : "Buy") : "--";
onClicked: {
- commerce.buy(itemId, parseInt(itemPriceText.text));
+ if (!alreadyOwned) {
+ commerce.buy(itemId, parseInt(itemPriceText.text));
+ } else {
+ if (urlHandler.canHandleUrl(itemHref)) {
+ urlHandler.handleUrl(itemHref);
+ }
+ sendToScript({method: 'checkout_buySuccess', itemId: itemId});
+ }
}
}
}
@@ -427,6 +459,7 @@ Rectangle {
itemHref = message.params.itemHref;
commerce.balance();
commerce.inventory();
+ commerce.getSecurityImage();
break;
default:
console.log('Unrecognized message from marketplaces.js:', JSON.stringify(message));
diff --git a/interface/resources/qml/hifi/commerce/Inventory.qml b/interface/resources/qml/hifi/commerce/Inventory.qml
index 298abebdab..d7ffae7c3c 100644
--- a/interface/resources/qml/hifi/commerce/Inventory.qml
+++ b/interface/resources/qml/hifi/commerce/Inventory.qml
@@ -43,6 +43,14 @@ Rectangle {
inventoryContentsList.model = inventory.assets;
}
}
+ onSecurityImageResult: {
+ securityImage.source = securityImageSelection.getImagePathFromImageID(imageID);
+ }
+ }
+
+ SecurityImageSelection {
+ id: securityImageSelection;
+ referrerURL: inventoryRoot.referrerURL;
}
//
@@ -51,12 +59,26 @@ Rectangle {
Item {
id: titleBarContainer;
// Size
- width: inventoryRoot.width;
+ width: parent.width;
height: 50;
// Anchors
anchors.left: parent.left;
anchors.top: parent.top;
+ // Security Image
+ Image {
+ id: securityImage;
+ // Anchors
+ anchors.top: parent.top;
+ anchors.left: parent.left;
+ anchors.leftMargin: 16;
+ height: parent.height - 5;
+ width: height;
+ anchors.verticalCenter: parent.verticalCenter;
+ fillMode: Image.PreserveAspectFit;
+ mipmap: true;
+ }
+
// Title Bar text
RalewaySemiBold {
id: titleBarText;
@@ -64,8 +86,11 @@ Rectangle {
// Text size
size: hifi.fontSizes.overlayTitle;
// Anchors
- anchors.fill: parent;
+ anchors.top: parent.top;
+ anchors.left: securityImage.right;
anchors.leftMargin: 16;
+ anchors.bottom: parent.bottom;
+ width: paintedWidth;
// Style
color: hifi.colors.lightGrayText;
// Alignment
@@ -73,6 +98,25 @@ Rectangle {
verticalAlignment: Text.AlignVCenter;
}
+ // "Change Security Image" button
+ HifiControlsUit.Button {
+ id: changeSecurityImageButton;
+ color: hifi.buttons.black;
+ colorScheme: hifi.colorSchemes.dark;
+ anchors.top: parent.top;
+ anchors.topMargin: 3;
+ anchors.bottom: parent.bottom;
+ anchors.bottomMargin: 3;
+ anchors.right: parent.right;
+ anchors.rightMargin: 20;
+ width: 200;
+ text: "Change Security Image"
+ onClicked: {
+ securityImageSelection.isManuallyChangingSecurityImage = true;
+ securityImageSelection.visible = true;
+ }
+ }
+
// Separator
HifiControlsUit.Separator {
anchors.left: parent.left;
@@ -166,6 +210,7 @@ Rectangle {
}
ListView {
id: inventoryContentsList;
+ clip: true;
// Anchors
anchors.top: inventoryContentsLabel.bottom;
anchors.topMargin: 8;
@@ -262,6 +307,7 @@ Rectangle {
referrerURL = message.referrerURL;
commerce.balance();
commerce.inventory();
+ commerce.getSecurityImage();
break;
default:
console.log('Unrecognized message from marketplaces.js:', JSON.stringify(message));
diff --git a/interface/resources/qml/hifi/commerce/SecurityImageModel.qml b/interface/resources/qml/hifi/commerce/SecurityImageModel.qml
new file mode 100644
index 0000000000..2fbf28683f
--- /dev/null
+++ b/interface/resources/qml/hifi/commerce/SecurityImageModel.qml
@@ -0,0 +1,42 @@
+//
+// SecurityImageModel.qml
+// qml/hifi/commerce
+//
+// SecurityImageModel
+//
+// Created by Zach Fox on 2017-08-15
+// 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
+//
+
+import QtQuick 2.5
+
+ListModel {
+ id: root;
+ ListElement{
+ sourcePath: "images/01cat.jpg"
+ securityImageEnumValue: 1;
+ }
+ ListElement{
+ sourcePath: "images/02car.jpg"
+ securityImageEnumValue: 2;
+ }
+ ListElement{
+ sourcePath: "images/03dog.jpg"
+ securityImageEnumValue: 3;
+ }
+ ListElement{
+ sourcePath: "images/04stars.jpg"
+ securityImageEnumValue: 4;
+ }
+ ListElement{
+ sourcePath: "images/05plane.jpg"
+ securityImageEnumValue: 5;
+ }
+ ListElement{
+ sourcePath: "images/06gingerbread.jpg"
+ securityImageEnumValue: 6;
+ }
+}
diff --git a/interface/resources/qml/hifi/commerce/SecurityImageSelection.qml b/interface/resources/qml/hifi/commerce/SecurityImageSelection.qml
new file mode 100644
index 0000000000..7775f1ff9c
--- /dev/null
+++ b/interface/resources/qml/hifi/commerce/SecurityImageSelection.qml
@@ -0,0 +1,271 @@
+//
+// SecurityImageSelection.qml
+// qml/hifi/commerce
+//
+// SecurityImageSelection
+//
+// Created by Zach Fox on 2017-08-15
+// 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
+//
+
+import Hifi 1.0 as Hifi
+import QtQuick 2.5
+import QtQuick.Controls 1.4
+import "../../styles-uit"
+import "../../controls-uit" as HifiControlsUit
+import "../../controls" as HifiControls
+
+// references XXX from root context
+
+Rectangle {
+ HifiConstants { id: hifi; }
+
+ id: securityImageSelectionRoot;
+ property string referrerURL: "";
+ property bool isManuallyChangingSecurityImage: false;
+ anchors.fill: parent;
+ // Style
+ color: hifi.colors.baseGray;
+ z:999; // On top of everything else
+ visible: false;
+
+ Hifi.QmlCommerce {
+ id: commerce;
+ onSecurityImageResult: {
+ if (!isManuallyChangingSecurityImage) {
+ securityImageSelectionRoot.visible = (imageID == 0);
+ }
+ if (imageID > 0) {
+ for (var itr = 0; itr < gridModel.count; itr++) {
+ var thisValue = gridModel.get(itr).securityImageEnumValue;
+ if (thisValue === imageID) {
+ securityImageGrid.currentIndex = itr;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ Component.onCompleted: {
+ commerce.getSecurityImage();
+ }
+
+ //
+ // TITLE BAR START
+ //
+ Item {
+ id: titleBarContainer;
+ // Size
+ width: securityImageSelectionRoot.width;
+ height: 30;
+ // Anchors
+ anchors.left: parent.left;
+ anchors.top: parent.top;
+
+ // Title Bar text
+ RalewaySemiBold {
+ id: titleBarText;
+ text: "Select a Security Image";
+ // Text size
+ size: hifi.fontSizes.overlayTitle;
+ // Anchors
+ anchors.fill: parent;
+ anchors.leftMargin: 16;
+ // Style
+ color: hifi.colors.lightGrayText;
+ // Alignment
+ horizontalAlignment: Text.AlignHLeft;
+ verticalAlignment: Text.AlignVCenter;
+ }
+
+ // Separator
+ HifiControlsUit.Separator {
+ anchors.left: parent.left;
+ anchors.right: parent.right;
+ anchors.bottom: parent.bottom;
+ }
+ }
+ //
+ // TITLE BAR END
+ //
+
+ //
+ // EXPLANATION START
+ //
+ Item {
+ id: explanationContainer;
+ // Size
+ width: securityImageSelectionRoot.width;
+ height: 85;
+ // Anchors
+ anchors.top: titleBarContainer.bottom;
+ anchors.left: parent.left;
+ anchors.right: parent.right;
+
+ RalewayRegular {
+ id: explanationText;
+ text: "This image will be displayed on secure Inventory and Marketplace Checkout dialogs.
If you don't see your selected image on these dialogs, do not use them!";
+ // Text size
+ size: 16;
+ // Anchors
+ anchors.top: parent.top;
+ anchors.topMargin: 4;
+ anchors.left: parent.left;
+ anchors.leftMargin: 16;
+ anchors.right: parent.right;
+ anchors.rightMargin: 16;
+ // Style
+ color: hifi.colors.lightGrayText;
+ wrapMode: Text.WordWrap;
+ // Alignment
+ horizontalAlignment: Text.AlignHLeft;
+ verticalAlignment: Text.AlignVCenter;
+ }
+
+ // Separator
+ HifiControlsUit.Separator {
+ anchors.left: parent.left;
+ anchors.right: parent.right;
+ anchors.bottom: parent.bottom;
+ }
+ }
+ //
+ // EXPLANATION END
+ //
+
+ //
+ // SECURITY IMAGE GRID START
+ //
+ Item {
+ id: securityImageGridContainer;
+ // Anchors
+ anchors.left: parent.left;
+ anchors.leftMargin: 8;
+ anchors.right: parent.right;
+ anchors.rightMargin: 8;
+ anchors.top: explanationContainer.bottom;
+ anchors.topMargin: 8;
+ anchors.bottom: actionButtonsContainer.top;
+ anchors.bottomMargin: 8;
+
+ SecurityImageModel {
+ id: gridModel;
+ }
+
+ GridView {
+ id: securityImageGrid;
+ clip: true;
+ // Anchors
+ anchors.fill: parent;
+ currentIndex: -1;
+ cellWidth: width / 2;
+ cellHeight: height / 3;
+ model: gridModel;
+ delegate: Item {
+ width: securityImageGrid.cellWidth;
+ height: securityImageGrid.cellHeight;
+ Item {
+ anchors.fill: parent;
+ Image {
+ width: parent.width - 8;
+ height: parent.height - 8;
+ source: sourcePath;
+ anchors.horizontalCenter: parent.horizontalCenter;
+ anchors.verticalCenter: parent.verticalCenter;
+ fillMode: Image.PreserveAspectFit;
+ mipmap: true;
+ }
+ }
+ MouseArea {
+ anchors.fill: parent;
+ onClicked: {
+ securityImageGrid.currentIndex = index;
+ }
+ }
+ }
+ highlight: Rectangle {
+ width: securityImageGrid.cellWidth;
+ height: securityImageGrid.cellHeight;
+ color: hifi.colors.blueHighlight;
+ }
+ }
+ }
+ //
+ // SECURITY IMAGE GRID END
+ //
+
+
+ //
+ // ACTION BUTTONS START
+ //
+ Item {
+ id: actionButtonsContainer;
+ // Size
+ width: securityImageSelectionRoot.width;
+ height: 40;
+ // Anchors
+ anchors.left: parent.left;
+ anchors.bottom: parent.bottom;
+ anchors.bottomMargin: 8;
+
+ // "Cancel" button
+ HifiControlsUit.Button {
+ id: cancelButton;
+ color: hifi.buttons.black;
+ colorScheme: hifi.colorSchemes.dark;
+ anchors.top: parent.top;
+ anchors.topMargin: 3;
+ anchors.bottom: parent.bottom;
+ anchors.bottomMargin: 3;
+ anchors.left: parent.left;
+ anchors.leftMargin: 20;
+ width: parent.width/2 - anchors.leftMargin*2;
+ text: "Cancel"
+ onClicked: {
+ if (!securityImageSelectionRoot.isManuallyChangingSecurityImage) {
+ sendToScript({method: 'securityImageSelection_cancelClicked', referrerURL: securityImageSelectionRoot.referrerURL});
+ } else {
+ securityImageSelectionRoot.visible = false;
+ }
+ }
+ }
+
+ // "Confirm" button
+ HifiControlsUit.Button {
+ id: confirmButton;
+ color: hifi.buttons.black;
+ colorScheme: hifi.colorSchemes.dark;
+ anchors.top: parent.top;
+ anchors.topMargin: 3;
+ anchors.bottom: parent.bottom;
+ anchors.bottomMargin: 3;
+ anchors.right: parent.right;
+ anchors.rightMargin: 20;
+ width: parent.width/2 - anchors.rightMargin*2;
+ text: "Confirm";
+ onClicked: {
+ securityImageSelectionRoot.isManuallyChangingSecurityImage = false;
+ commerce.chooseSecurityImage(gridModel.get(securityImageGrid.currentIndex).securityImageEnumValue);
+ }
+ }
+ }
+ //
+ // ACTION BUTTONS END
+ //
+
+ //
+ // FUNCTION DEFINITIONS START
+ //
+ signal sendToScript(var message);
+
+ function getImagePathFromImageID(imageID) {
+ return (imageID ? gridModel.get(imageID - 1).sourcePath : "");
+ }
+ //
+ // FUNCTION DEFINITIONS END
+ //
+}
diff --git a/interface/resources/qml/hifi/commerce/images/01cat.jpg b/interface/resources/qml/hifi/commerce/images/01cat.jpg
new file mode 100644
index 0000000000..6e7897cb82
Binary files /dev/null and b/interface/resources/qml/hifi/commerce/images/01cat.jpg differ
diff --git a/interface/resources/qml/hifi/commerce/images/02car.jpg b/interface/resources/qml/hifi/commerce/images/02car.jpg
new file mode 100644
index 0000000000..5dd8091e57
Binary files /dev/null and b/interface/resources/qml/hifi/commerce/images/02car.jpg differ
diff --git a/interface/resources/qml/hifi/commerce/images/03dog.jpg b/interface/resources/qml/hifi/commerce/images/03dog.jpg
new file mode 100644
index 0000000000..4a85b80c0c
Binary files /dev/null and b/interface/resources/qml/hifi/commerce/images/03dog.jpg differ
diff --git a/interface/resources/qml/hifi/commerce/images/04stars.jpg b/interface/resources/qml/hifi/commerce/images/04stars.jpg
new file mode 100644
index 0000000000..8f2bf62f83
Binary files /dev/null and b/interface/resources/qml/hifi/commerce/images/04stars.jpg differ
diff --git a/interface/resources/qml/hifi/commerce/images/05plane.jpg b/interface/resources/qml/hifi/commerce/images/05plane.jpg
new file mode 100644
index 0000000000..6504459d8b
Binary files /dev/null and b/interface/resources/qml/hifi/commerce/images/05plane.jpg differ
diff --git a/interface/resources/qml/hifi/commerce/images/06gingerbread.jpg b/interface/resources/qml/hifi/commerce/images/06gingerbread.jpg
new file mode 100644
index 0000000000..54c37faa2f
Binary files /dev/null and b/interface/resources/qml/hifi/commerce/images/06gingerbread.jpg differ
diff --git a/interface/src/commerce/Ledger.cpp b/interface/src/commerce/Ledger.cpp
index ad79a836ad..8d7d47aca0 100644
--- a/interface/src/commerce/Ledger.cpp
+++ b/interface/src/commerce/Ledger.cpp
@@ -24,12 +24,12 @@ void 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);
@@ -78,4 +78,4 @@ void Ledger::inventory(const QStringList& keys) {
inventoryObject.insert("assets", _inventory);
qCInfo(commerce) << "Inventory:" << inventoryObject;
emit inventoryResult(inventoryObject, "");
-}
\ No newline at end of file
+}
diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp
index 63bfca4f31..573740727f 100644
--- a/interface/src/commerce/QmlCommerce.cpp
+++ b/interface/src/commerce/QmlCommerce.cpp
@@ -19,9 +19,11 @@ HIFI_QML_DEF(QmlCommerce)
QmlCommerce::QmlCommerce(QQuickItem* parent) : OffscreenQmlDialog(parent) {
auto ledger = DependencyManager::get();
+ auto wallet = DependencyManager::get();
connect(ledger.data(), &Ledger::buyResult, this, &QmlCommerce::buyResult);
connect(ledger.data(), &Ledger::balanceResult, this, &QmlCommerce::balanceResult);
connect(ledger.data(), &Ledger::inventoryResult, this, &QmlCommerce::inventoryResult);
+ connect(wallet.data(), &Wallet::securityImageResult, this, &QmlCommerce::securityImageResult);
}
void QmlCommerce::buy(const QString& assetId, int cost, const QString& buyerUsername) {
@@ -48,4 +50,13 @@ void QmlCommerce::inventory() {
auto ledger = DependencyManager::get();
auto wallet = DependencyManager::get();
ledger->inventory(wallet->listPublicKeys());
- }
\ No newline at end of file
+}
+
+void QmlCommerce::chooseSecurityImage(uint imageID) {
+ auto wallet = DependencyManager::get();
+ wallet->chooseSecurityImage(imageID);
+}
+void QmlCommerce::getSecurityImage() {
+ auto wallet = DependencyManager::get();
+ wallet->getSecurityImage();
+}
\ No newline at end of file
diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h
index 0b1d232fd7..5b702bfeff 100644
--- a/interface/src/commerce/QmlCommerce.h
+++ b/interface/src/commerce/QmlCommerce.h
@@ -30,11 +30,14 @@ signals:
// because we can't scalably know of out-of-band changes (e.g., another machine interacting with the block chain).
void balanceResult(int balance, const QString& failureMessage);
void inventoryResult(QJsonObject inventory, const QString& failureMessage);
+ void securityImageResult(uint imageID);
protected:
Q_INVOKABLE void buy(const QString& assetId, int cost, const QString& buyerUsername = "");
Q_INVOKABLE void balance();
Q_INVOKABLE void inventory();
+ Q_INVOKABLE void chooseSecurityImage(uint imageID);
+ Q_INVOKABLE void getSecurityImage();
};
#endif // hifi_QmlCommerce_h
diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp
index 34d89b54b0..f47b174d88 100644
--- a/interface/src/commerce/Wallet.cpp
+++ b/interface/src/commerce/Wallet.cpp
@@ -9,30 +9,223 @@
// 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* 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 keyFilePath() {
+ return PathUtils::getAppDataFilePath(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 static_cast(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(keyFilePath().toStdString().c_str(), "wt"))) {
+ if (!PEM_write_RSAPublicKey(fp, keyPair)) {
+ fclose(fp);
+ qCDebug(commerce) << "failed to write public key";
+ return retval;
+ }
+
+ 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";
+
+ } else {
+ qCDebug(commerce) << "couldn't parse" << filename;
+ }
+ fclose(fp);
+ } else {
+ qCDebug(commerce) << "couldn't open" << filename;
+ }
+ return key;
+}
+
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(keyFilePath().toStdString().c_str());
+ if (publicKey.size() > 0) {
+ 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
+ _publicKeys.push_back(QUrl::toPercentEncoding(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();
+ auto keyPair = generateRSAKeypair();
+
+ _publicKeys.push_back(QUrl::toPercentEncoding(keyPair.first->toBase64()));
+ qCDebug(commerce) << "public key:" << _publicKeys.last();
- _publicKeys.push_back(key); // Keep in memory for synchronous speed.
// 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.
// 2. It is maximally private, and we can step back from that later if desired.
// 3. It maximally exercises all the machinery, so we are most likely to surface issues now.
auto ledger = DependencyManager::get();
- return ledger->receiveAt(key);
+ return ledger->receiveAt(_publicKeys.last());
}
QStringList Wallet::listPublicKeys() {
qCInfo(commerce) << "Enumerating public keys.";
@@ -40,7 +233,44 @@ 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(keyFilePath().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) {
+ return QUrl::toPercentEncoding(signature.toBase64());
+ }
+ }
+ return QString();
+}
+
+
+void Wallet::chooseSecurityImage(uint imageID) {
+ _chosenSecurityImage = (SecurityImage)imageID;
+ emit securityImageResult(imageID);
+}
+void Wallet::getSecurityImage() {
+ emit securityImageResult(_chosenSecurityImage);
+}
diff --git a/interface/src/commerce/Wallet.h b/interface/src/commerce/Wallet.h
index 79de5e81da..a1c7c7752b 100644
--- a/interface/src/commerce/Wallet.h
+++ b/interface/src/commerce/Wallet.h
@@ -25,10 +25,30 @@ public:
bool createIfNeeded();
bool generateKeyPair();
QStringList listPublicKeys();
- QString signWithKey(const QString& text, const QString& key);
+ QString signWithKey(const QByteArray& text, const QString& key);
+ void chooseSecurityImage(uint imageID);
+ void getSecurityImage();
+
+signals:
+ void securityImageResult(uint imageID);
+
+protected:
+ // ALWAYS add SecurityImage enum values to the END of the enum.
+ // They must be in the same order as the images are listed in
+ // SecurityImageSelection.qml
+ enum SecurityImage {
+ NONE = 0,
+ Cat,
+ Car,
+ Dog,
+ Stars,
+ Plane,
+ Gingerbread
+ };
private:
QStringList _publicKeys{};
+ SecurityImage _chosenSecurityImage = SecurityImage::NONE;
};
#endif // hifi_Wallet_h
diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp
index 3a31ccd25f..1b91737364 100644
--- a/libraries/animation/src/Rig.cpp
+++ b/libraries/animation/src/Rig.cpp
@@ -168,6 +168,8 @@ void Rig::destroyAnimGraph() {
void Rig::initJointStates(const FBXGeometry& geometry, const glm::mat4& modelOffset) {
_geometryOffset = AnimPose(geometry.offset);
_invGeometryOffset = _geometryOffset.inverse();
+ _geometryToRigTransform = modelOffset * geometry.offset;
+ _rigToGeometryTransform = glm::inverse(_geometryToRigTransform);
setModelOffset(modelOffset);
_animSkeleton = std::make_shared(geometry);
@@ -1761,59 +1763,11 @@ void Rig::computeAvatarBoundingCapsule(
return;
}
- AnimInverseKinematics ikNode("boundingShape");
- ikNode.setSkeleton(_animSkeleton);
-
- ikNode.setTargetVars("LeftHand", "leftHandPosition", "leftHandRotation",
- "leftHandType", "leftHandWeight", 1.0f, {},
- QString(), QString(), QString());
- ikNode.setTargetVars("RightHand", "rightHandPosition", "rightHandRotation",
- "rightHandType", "rightHandWeight", 1.0f, {},
- QString(), QString(), QString());
- ikNode.setTargetVars("LeftFoot", "leftFootPosition", "leftFootRotation",
- "leftFootType", "leftFootWeight", 1.0f, {},
- QString(), QString(), QString());
- ikNode.setTargetVars("RightFoot", "rightFootPosition", "rightFootRotation",
- "rightFootType", "rightFootWeight", 1.0f, {},
- QString(), QString(), QString());
glm::vec3 hipsPosition(0.0f);
int hipsIndex = indexOfJoint("Hips");
if (hipsIndex >= 0) {
hipsPosition = transformPoint(_geometryToRigTransform, _animSkeleton->getAbsoluteDefaultPose(hipsIndex).trans());
}
- AnimVariantMap animVars;
- animVars.setRigToGeometryTransform(_rigToGeometryTransform);
- glm::quat handRotation = glm::angleAxis(PI, Vectors::UNIT_X);
- animVars.set("leftHandPosition", hipsPosition);
- animVars.set("leftHandRotation", handRotation);
- animVars.set("leftHandType", (int)IKTarget::Type::RotationAndPosition);
- animVars.set("rightHandPosition", hipsPosition);
- animVars.set("rightHandRotation", handRotation);
- animVars.set("rightHandType", (int)IKTarget::Type::RotationAndPosition);
-
- int rightFootIndex = indexOfJoint("RightFoot");
- int leftFootIndex = indexOfJoint("LeftFoot");
- if (rightFootIndex != -1 && leftFootIndex != -1) {
- glm::vec3 geomFootPosition = glm::vec3(0.0f, _animSkeleton->getAbsoluteDefaultPose(rightFootIndex).trans().y, 0.0f);
- glm::vec3 footPosition = transformPoint(_geometryToRigTransform, geomFootPosition);
- glm::quat footRotation = glm::angleAxis(0.5f * PI, Vectors::UNIT_X);
- animVars.set("leftFootPosition", footPosition);
- animVars.set("leftFootRotation", footRotation);
- animVars.set("leftFootType", (int)IKTarget::Type::RotationAndPosition);
- animVars.set("rightFootPosition", footPosition);
- animVars.set("rightFootRotation", footRotation);
- animVars.set("rightFootType", (int)IKTarget::Type::RotationAndPosition);
- }
-
- // call overlay twice: once to verify AnimPoseVec joints and again to do the IK
- AnimNode::Triggers triggersOut;
- AnimContext context(false, false, false, _geometryToRigTransform, _rigToGeometryTransform);
- float dt = 1.0f; // the value of this does not matter
- ikNode.overlay(animVars, context, dt, triggersOut, _animSkeleton->getRelativeBindPoses());
- AnimPoseVec finalPoses = ikNode.overlay(animVars, context, dt, triggersOut, _animSkeleton->getRelativeBindPoses());
-
- // convert relative poses to absolute
- _animSkeleton->convertRelativePosesToAbsolute(finalPoses);
// compute bounding box that encloses all points
Extents totalExtents;
@@ -1824,15 +1778,15 @@ void Rig::computeAvatarBoundingCapsule(
// even if they do not have legs (default robot)
totalExtents.addPoint(glm::vec3(0.0f));
- // HACK to reduce the radius of the bounding capsule to be tight with the torso, we only consider joints
+ // To reduce the radius of the bounding capsule to be tight with the torso, we only consider joints
// from the head to the hips when computing the rest of the bounding capsule.
int index = indexOfJoint("Head");
while (index != -1) {
const FBXJointShapeInfo& shapeInfo = geometry.joints.at(index).shapeInfo;
- AnimPose pose = finalPoses[index];
+ AnimPose pose = _animSkeleton->getAbsoluteDefaultPose(index);
if (shapeInfo.points.size() > 0) {
- for (size_t j = 0; j < shapeInfo.points.size(); ++j) {
- totalExtents.addPoint((pose * shapeInfo.points[j]));
+ for (auto& point : shapeInfo.points) {
+ totalExtents.addPoint((pose * point));
}
}
index = _animSkeleton->getParentIndex(index);
@@ -1846,7 +1800,6 @@ void Rig::computeAvatarBoundingCapsule(
radiusOut = 0.5f * sqrtf(0.5f * (diagonal.x * diagonal.x + diagonal.z * diagonal.z));
heightOut = diagonal.y - 2.0f * radiusOut;
- glm::vec3 rootPosition = finalPoses[geometry.rootJointIndex].trans();
- glm::vec3 rigCenter = transformPoint(_geometryToRigTransform, (0.5f * (totalExtents.maximum + totalExtents.minimum)));
- localOffsetOut = rigCenter - transformPoint(_geometryToRigTransform, rootPosition);
+ glm::vec3 capsuleCenter = transformPoint(_geometryToRigTransform, (0.5f * (totalExtents.maximum + totalExtents.minimum)));
+ localOffsetOut = capsuleCenter - hipsPosition;
}
diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js
index 9378a1d95b..84d7d44689 100644
--- a/scripts/system/marketplaces/marketplaces.js
+++ b/scripts/system/marketplaces/marketplaces.js
@@ -21,6 +21,7 @@
var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("../html/js/marketplacesInject.js");
var MARKETPLACE_CHECKOUT_QML_PATH = Script.resourcesPath() + "qml/hifi/commerce/Checkout.qml";
var MARKETPLACE_INVENTORY_QML_PATH = Script.resourcesPath() + "qml/hifi/commerce/Inventory.qml";
+ var MARKETPLACE_SECURITY_QML_PATH = Script.resourcesPath() + "qml/hifi/commerce/SecurityImageSelection.qml";
var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png";
// var HOME_BUTTON_TEXTURE = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png";
@@ -87,7 +88,7 @@
function onScreenChanged(type, url) {
onMarketplaceScreen = type === "Web" && url === MARKETPLACE_URL_INITIAL;
- wireEventBridge(type === "QML" && (url === MARKETPLACE_CHECKOUT_QML_PATH || url === MARKETPLACE_INVENTORY_QML_PATH));
+ wireEventBridge(type === "QML" && (url === MARKETPLACE_CHECKOUT_QML_PATH || url === MARKETPLACE_INVENTORY_QML_PATH || url === MARKETPLACE_SECURITY_QML_PATH));
// for toolbar mode: change button to active when window is first openend, false otherwise.
marketplaceButton.editProperties({ isActive: onMarketplaceScreen });
if (type === "Web" && url.indexOf(MARKETPLACE_URL) !== -1) {
@@ -217,8 +218,11 @@
case 'inventory_backClicked':
tablet.gotoWebScreen(message.referrerURL, MARKETPLACES_INJECT_SCRIPT_URL);
break;
+ case 'securityImageSelection_cancelClicked':
+ tablet.gotoWebScreen(message.referrerURL, MARKETPLACES_INJECT_SCRIPT_URL);
+ break;
default:
- print('Unrecognized message from Checkout.qml or Inventory.qml: ' + JSON.stringify(message));
+ print('Unrecognized message from Checkout.qml, Inventory.qml, or SecurityImageSelection.qml: ' + JSON.stringify(message));
}
}