From 0e5bcad9fa8c028abdac233b9db6a55d97013900 Mon Sep 17 00:00:00 2001 From: inso <insomniak.fr@gmaiL.com> Date: Wed, 7 Dec 2016 20:53:16 +0100 Subject: [PATCH] Add support for scrypt_params and bmas endpoints --- src/sakia/data/connectors/bma.py | 11 +- src/sakia/data/connectors/node.py | 7 +- src/sakia/data/entities/connection.py | 3 + src/sakia/data/repositories/certifications.py | 1 - src/sakia/data/repositories/meta.sql | 3 + .../dialogs/connection_cfg/connection_cfg.ui | 168 ++++++++++++++---- .../gui/dialogs/connection_cfg/controller.py | 12 +- src/sakia/gui/dialogs/connection_cfg/model.py | 11 +- src/sakia/gui/dialogs/connection_cfg/view.py | 58 +++++- .../gui/navigation/network/table_model.py | 16 +- src/sakia/options.py | 4 +- 11 files changed, 237 insertions(+), 57 deletions(-) diff --git a/src/sakia/data/connectors/bma.py b/src/sakia/data/connectors/bma.py index 10eba5d6..ea60a3e6 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 303240b4..6c3f8bbb 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 a118ba3c..58656413 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 10624202..daffd318 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 aeff02cf..90231799 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 30691e41..6f0e3474 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 7b3a3881..146d45b8 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 ecf115db..21890963 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 0db0ee81..b58cd233 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 938ac13d..26d4716b 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 b85c7f72..1952c0f1 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): -- GitLab