diff --git a/src/sakia/data/connectors/bma.py b/src/sakia/data/connectors/bma.py index 10eba5d6e4f089cf5df0ccfee5d99f280949ab3b..ea60a3e6b2f11618df70de72927d3cdfbcabee9e 100644 --- a/src/sakia/data/connectors/bma.py +++ b/src/sakia/data/connectors/bma.py @@ -1,14 +1,15 @@ -from duniterpy.api import bma import logging import aiohttp from aiohttp.errors import ClientError, ServerDisconnectedError +from duniterpy.api import bma +from duniterpy.documents import BMAEndpoint, SecuredBMAEndpoint +from sakia.errors import NoPeerAvailable +from pkg_resources import parse_version +from socket import gaierror import asyncio import random -from socket import gaierror import jsonschema -from pkg_resources import parse_version import attr -from sakia.errors import NoPeerAvailable @attr.s() @@ -37,7 +38,7 @@ class BmaConnector: nodes = [n for n in nodes if filters[request](n)] endpoints = [] for n in nodes: - endpoints += n.endpoints + endpoints += [e for e in n.endpoints if type(e) in (BMAEndpoint, SecuredBMAEndpoint)] return endpoints async def get(self, currency, request, req_args={}, get_args={}): diff --git a/src/sakia/data/connectors/node.py b/src/sakia/data/connectors/node.py index 303240b4ee1710900435fb322781a32e954d1ec0..6c3f8bbb18984018055fe6236fc7eb3cdeb6cba1 100644 --- a/src/sakia/data/connectors/node.py +++ b/src/sakia/data/connectors/node.py @@ -50,18 +50,21 @@ class NodeConnector(QObject): return self._session @classmethod - async def from_address(cls, currency, address, port, session): + async def from_address(cls, currency, secured, address, port, session): """ Factory method to get a node from a given address :param str currency: The node currency. None if we don't know\ the currency it should have, for example if its the first one we add + :param bool secured: True if the node uses https :param str address: The node address :param int port: The node port :param aiohttp.ClientSession session: The client session :return: A new node :rtype: sakia.core.net.Node """ - peer_data = await bma.network.peering(ConnectionHandler(address, port, session)) + http_scheme = "https" if secured else "http" + ws_scheme = "ws" if secured else "wss" + peer_data = await bma.network.peering(ConnectionHandler(http_scheme, ws_scheme, address, port, session)) peer = Peer.from_signed_raw("{0}{1}\n".format(peer_data['raw'], peer_data['signature'])) diff --git a/src/sakia/data/entities/connection.py b/src/sakia/data/entities/connection.py index a118ba3caada2526d3e04df7541dd93cc1792697..586564138761e6db77ac4981b1a21102e4489089 100644 --- a/src/sakia/data/entities/connection.py +++ b/src/sakia/data/entities/connection.py @@ -13,6 +13,9 @@ class Connection: pubkey = attr.ib(convert=str) salt = attr.ib(convert=str) uid = attr.ib(convert=str, default="", cmp=False, hash=False) + scrypt_N = attr.ib(convert=int, default=4096) + scrypt_r = attr.ib(convert=int, default=16) + scrypt_p = attr.ib(convert=int, default=1) blockstamp = attr.ib(convert=block_uid, default=BlockUID.empty(), cmp=False, hash=False) password = attr.ib(convert=str, default="", cmp=False, hash=False) diff --git a/src/sakia/data/repositories/certifications.py b/src/sakia/data/repositories/certifications.py index 1062420213a12d70be0ac3eb28773e510639de2f..daffd31820e50aa2520032a466123ca896593d88 100644 --- a/src/sakia/data/repositories/certifications.py +++ b/src/sakia/data/repositories/certifications.py @@ -107,4 +107,3 @@ class CertificationsRepo: certifier=? AND certified=? AND block=?""", where_fields) - diff --git a/src/sakia/data/repositories/meta.sql b/src/sakia/data/repositories/meta.sql index aeff02cf2eabd17ffb8a066b959a6d7abc4afbd9..902317994e6371af8a445750ded3d36d2cc8dcba 100644 --- a/src/sakia/data/repositories/meta.sql +++ b/src/sakia/data/repositories/meta.sql @@ -109,6 +109,9 @@ CREATE TABLE IF NOT EXISTS connections( pubkey VARCHAR(50), salt VARCHAR(50), uid VARCHAR(255), + scrypt_N INT, + scrypt_p INT, + scrypt_r INT, blockstamp VARCHAR(100), PRIMARY KEY (currency, pubkey) ); diff --git a/src/sakia/gui/dialogs/connection_cfg/connection_cfg.ui b/src/sakia/gui/dialogs/connection_cfg/connection_cfg.ui index 30691e419f859c1b75b70d75fbbc48d5955c3f36..6f0e3474951d39c67bc46878cda9ebe91ee5af50 100644 --- a/src/sakia/gui/dialogs/connection_cfg/connection_cfg.ui +++ b/src/sakia/gui/dialogs/connection_cfg/connection_cfg.ui @@ -6,8 +6,8 @@ <rect> <x>0</x> <y>0</y> - <width>639</width> - <height>447</height> + <width>623</width> + <height>545</height> </rect> </property> <property name="windowTitle"> @@ -31,19 +31,6 @@ </property> </widget> </item> - <item> - <spacer name="verticalSpacer"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>40</height> - </size> - </property> - </spacer> - </item> <item> <layout class="QHBoxLayout" name="horizontalLayout_10"> <property name="rightMargin"> @@ -69,6 +56,13 @@ </property> </widget> </item> + <item> + <widget class="QCheckBox" name="checkbox_secured"> + <property name="text"> + <string>SSL/TLS</string> + </property> + </widget> + </item> </layout> </item> <item> @@ -76,23 +70,10 @@ <property name="topMargin"> <number>6</number> </property> - <item> - <spacer name="verticalSpacer_7"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>40</height> - </size> - </property> - </spacer> - </item> <item> <widget class="QPushButton" name="button_register"> <property name="text"> - <string>Register your account</string> + <string>Register a new account</string> </property> <property name="icon"> <iconset resource="../../../../../res/icons/icons.qrc"> @@ -109,7 +90,7 @@ <item> <widget class="QPushButton" name="button_connect"> <property name="text"> - <string>Connect using your account</string> + <string>Connect with an existing account</string> </property> <property name="icon"> <iconset resource="../../../../../res/icons/icons.qrc"> @@ -257,6 +238,20 @@ </property> </widget> </item> + <item> + <widget class="QLineEdit" name="edit_salt_bis"> + <property name="placeholderText"> + <string>Please repeat your secret key</string> + </property> + </widget> + </item> + <item> + <widget class="Line" name="line"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> <item> <widget class="QLineEdit" name="edit_password"> <property name="echoMode"> @@ -280,6 +275,106 @@ </property> </widget> </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <property name="topMargin"> + <number>6</number> + </property> + <item> + <widget class="QLabel" name="label_6"> + <property name="text"> + <string>Scrypt parameters</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="combo_scrypt_params"> + <item> + <property name="text"> + <string>Simple</string> + </property> + </item> + <item> + <property name="text"> + <string>Secure</string> + </property> + </item> + <item> + <property name="text"> + <string>Hardest</string> + </property> + </item> + <item> + <property name="text"> + <string>Extreme</string> + </property> + </item> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLabel" name="label"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>N :</string> + </property> + </widget> + </item> + <item> + <widget class="QSpinBox" name="spin_n"/> + </item> + <item> + <widget class="QLabel" name="label_4"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>r :</string> + </property> + </widget> + </item> + <item> + <widget class="QSpinBox" name="spin_r"/> + </item> + <item> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string>p :</string> + </property> + </widget> + </item> + <item> + <widget class="QSpinBox" name="spin_p"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + </layout> + </item> <item> <layout class="QHBoxLayout" name="horizontalLayout_6"> <property name="topMargin"> @@ -344,6 +439,19 @@ </widget> </widget> </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> <item> <widget class="QLabel" name="label_currency"> <property name="text"> diff --git a/src/sakia/gui/dialogs/connection_cfg/controller.py b/src/sakia/gui/dialogs/connection_cfg/controller.py index 7b3a38819b5042a6d36a6e1d060c0c91baaf4d0a..146d45b8c065478bc458ef3b7c2ee93389e94e47 100644 --- a/src/sakia/gui/dialogs/connection_cfg/controller.py +++ b/src/sakia/gui/dialogs/connection_cfg/controller.py @@ -118,7 +118,8 @@ class ConnectionConfigController(ComponentController): self.view.button_connect.setEnabled(False) self.view.button_register.setEnabled(False) await self.model.create_connection(self.view.lineedit_server.text(), - self.view.spinbox_port.value()) + self.view.spinbox_port.value(), + self.view.checkbox_secured.isChecked()) self.password_asker = PasswordAskerDialog(self.model.connection) except (DisconnectedError, ClientError, MalformedDocumentError, ValueError, TimeoutError) as e: self._logger.debug(str(e)) @@ -201,6 +202,11 @@ class ConnectionConfigController(ComponentController): self.view.label_info.setText(self.tr("Error : passwords are different")) return False + if self.view.edit_salt.text() != \ + self.view.edit_salt_bis.text(): + self.view.label_info.setText(self.tr("Error : secret keys are different")) + return False + self.view.label_info.setText("") return True @@ -211,7 +217,7 @@ class ConnectionConfigController(ComponentController): try: salt = self.view.edit_salt.text() password = self.view.edit_password.text() - self.model.set_scrypt_infos(salt, password) + self.model.set_scrypt_infos(salt, password, self.view.scrypt_params) self.model.set_uid(self.view.edit_account_name.text()) registered, found_identity = await self.model.check_registered() self.view.button_connect.setEnabled(True) @@ -233,7 +239,7 @@ Yours : {0}, the network : {1}""".format(registered[1], registered[2]))) try: salt = self.view.edit_salt.text() password = self.view.edit_password.text() - self.model.set_scrypt_infos(salt, password) + self.model.set_scrypt_infos(salt, password, self.view.scrypt_params) self.model.set_uid(self.view.edit_account_name.text()) registered, found_identity = await self.model.check_registered() if registered[0] is False and registered[2] is None: diff --git a/src/sakia/gui/dialogs/connection_cfg/model.py b/src/sakia/gui/dialogs/connection_cfg/model.py index ecf115db90b0c15ec9c95f9c1447b0a51fbcb989..2189096352a05b4bfc2a4af90a04954c9b99bdb4 100644 --- a/src/sakia/gui/dialogs/connection_cfg/model.py +++ b/src/sakia/gui/dialogs/connection_cfg/model.py @@ -30,10 +30,10 @@ class ConnectionConfigModel(ComponentModel): self.node_connector = node_connector self.identities_processor = identities_processor - async def create_connection(self, server, port): + async def create_connection(self, server, port, secured): session = aiohttp.ClientSession() try: - self.node_connector = await NodeConnector.from_address(None, server, port, session) + self.node_connector = await NodeConnector.from_address(None, secured, server, port, session) self.connection = Connection(self.node_connector.node.currency, "", "") self.node_connector.node.state = Node.ONLINE except: @@ -46,9 +46,12 @@ class ConnectionConfigModel(ComponentModel): def set_uid(self, uid): self.connection.uid = uid - def set_scrypt_infos(self, salt, password): + def set_scrypt_infos(self, salt, password, scrypt_params): self.connection.salt = salt - self.connection.pubkey = SigningKey(self.connection.salt, password).pubkey + self.connection.N = scrypt_params.N + self.connection.r = scrypt_params.r + self.connection.p = scrypt_params.p + self.connection.pubkey = SigningKey(self.connection.salt, password, scrypt_params).pubkey def insert_or_update_connection(self): ConnectionsProcessor(self.app.db.connections_repo).commit_connection(self.connection) diff --git a/src/sakia/gui/dialogs/connection_cfg/view.py b/src/sakia/gui/dialogs/connection_cfg/view.py index 0db0ee81c72887ff587829ec8e9f992f73d12714..b58cd233dada1231461a6c175f3bc792a8aa1f1b 100644 --- a/src/sakia/gui/dialogs/connection_cfg/view.py +++ b/src/sakia/gui/dialogs/connection_cfg/view.py @@ -1,7 +1,8 @@ from PyQt5.QtWidgets import QDialog from PyQt5.QtCore import pyqtSignal from .connection_cfg_uic import Ui_ConnectionConfigurationDialog -from duniterpy.key import SigningKey +from duniterpy.key import SigningKey, ScryptParams +from math import ceil, log from ...widgets import toast from ...widgets.dialogs import QAsyncMessageBox @@ -24,6 +25,58 @@ class ConnectionConfigView(QDialog, Ui_ConnectionConfigurationDialog): self.edit_salt.textChanged.connect(self.values_changed) self.button_generate.clicked.connect(self.action_show_pubkey) + self.combo_scrypt_params.currentIndexChanged.connect(self.handle_combo_change) + self.scrypt_params = ScryptParams(4096, 16, 1) + self.spin_n.setMaximum(2 ** 20) + self.spin_n.setValue(self.scrypt_params.N) + self.spin_n.valueChanged.connect(self.handle_n_change) + self.spin_r.setMaximum(128) + self.spin_r.setValue(self.scrypt_params.r) + self.spin_r.valueChanged.connect(self.handle_r_change) + self.spin_p.setMaximum(128) + self.spin_p.setValue(self.scrypt_params.p) + self.spin_p.valueChanged.connect(self.handle_p_change) + + def handle_combo_change(self, index): + strengths = [ + (2 ** 12, 16, 1), + (2 ** 14, 32, 2), + (2 ** 16, 32, 4), + (2 ** 18, 64, 8), + ] + self.spin_n.setValue(strengths[index][0]) + self.spin_r.setValue(strengths[index][1]) + self.spin_p.setValue(strengths[index][2]) + + def handle_n_change(self, value): + spinbox = self.sender() + self.scrypt_params.N = ConnectionConfigView.compute_power_of_2(spinbox, value, self.scrypt_params.N) + + def handle_r_change(self, value): + spinbox = self.sender() + self.scrypt_params.r = ConnectionConfigView.compute_power_of_2(spinbox, value, self.scrypt_params.r) + + def handle_p_change(self, value): + spinbox = self.sender() + self.scrypt_params.p = ConnectionConfigView.compute_power_of_2(spinbox, value, self.scrypt_params.p) + + @staticmethod + def compute_power_of_2(spinbox, value, param): + if value > 1: + if value > param: + value = pow(2, ceil(log(value) / log(2))) + else: + value -= 1 + value = 2 ** int(log(value, 2)) + else: + value = 1 + + spinbox.blockSignals(True) + spinbox.setValue(value) + spinbox.blockSignals(False) + + return value + def display_info(self, info): self.label_info.setText(info) @@ -50,7 +103,6 @@ class ConnectionConfigView(QDialog, Ui_ConnectionConfigurationDialog): def set_nodes_model(self, model): self.tree_peers.setModel(model) - def set_creation_layout(self): """ Hide unecessary buttons and display correct title @@ -72,7 +124,7 @@ class ConnectionConfigView(QDialog, Ui_ConnectionConfigurationDialog): def action_show_pubkey(self): salt = self.edit_salt.text() password = self.edit_password.text() - pubkey = SigningKey(salt, password).pubkey + pubkey = SigningKey(salt, password, self.scrypt_params).pubkey self.label_info.setText(pubkey) def account_name(self): diff --git a/src/sakia/gui/navigation/network/table_model.py b/src/sakia/gui/navigation/network/table_model.py index 938ac13d41a39a48af568f9c246341cf89e32d97..26d4716b67648678ad6f16366adef2a014e00048 100644 --- a/src/sakia/gui/navigation/network/table_model.py +++ b/src/sakia/gui/navigation/network/table_model.py @@ -10,6 +10,7 @@ import logging from PyQt5.QtCore import QAbstractTableModel, Qt, QVariant, QSortFilterProxyModel, QDateTime, QLocale from PyQt5.QtGui import QColor, QFont, QIcon from sakia.data.entities import Node +from duniterpy.documents import BMAEndpoint, SecuredBMAEndpoint from sakia.errors import NoPeerAvailable from sakia.decorators import asyncify, once_at_a_time @@ -163,13 +164,14 @@ class NetworkTableModel(QAbstractTableModel): addresses = [] ports = [] for e in node.endpoints: - if e.server: - addresses.append(e.server) - elif e.ipv4: - addresses.append(e.ipv4) - elif e.ipv6: - addresses.append(e.ipv6) - ports.append(str(e.port)) + if type(e) in (BMAEndpoint, SecuredBMAEndpoint): + if e.server: + addresses.append(e.server) + elif e.ipv4: + addresses.append(e.ipv4) + elif e.ipv6: + addresses.append(e.ipv6) + ports.append(str(e.port)) address = "\n".join(addresses) port = "\n".join(ports) diff --git a/src/sakia/options.py b/src/sakia/options.py index b85c7f7294766938e7b4d596c29f556e26026f13..1952c0f168ee53295f7d7f235bc170d928071144 100644 --- a/src/sakia/options.py +++ b/src/sakia/options.py @@ -32,12 +32,12 @@ class SakiaOptions: @classmethod def from_arguments(cls, argv): options = cls() - options._parse_arguments(argv) if not path.exists(options.config_path): - cls._logger.info("Creating home directory") makedirs(options.config_path) + options._parse_arguments(argv) + return options def _parse_arguments(self, argv):