diff --git a/www/i18n/locale-en-GB.json b/www/i18n/locale-en-GB.json index f23838522a8a7bf23ddd751b64ee31e802f6738c..1cf2efc691f08cbf39d10bfb04e53fc560053231 100644 --- a/www/i18n/locale-en-GB.json +++ b/www/i18n/locale-en-GB.json @@ -518,6 +518,9 @@ "BTN_RESET" : "Reset", "DOWNLOAD_REVOKE": "Save a revocation file", "DOWNLOAD_REVOKE_HELP" : "Having a revocation file is important, for example in case of loss of identifiers. It allows you to <b>get this account out of the Web Of Trust</b>, thus becoming a simple wallet.", + "GENERATE_KEYFILE": "Generate my keychain file ...", + "GENERATE_KEYFILE_HELP": "Generate a file allowing you to authenticate without entering your identifiers.<br/><b>Warning:</b> this file will contain your secret key; It is therefore very important to put it in a safe place!", + "KEYFILE_FILENAME": "keychain-{{pubkey|formatPubkey}}-{{currency}}-{{format}}.dunikey", "MEMBERSHIP_IN": "Register as member...", "MEMBERSHIP_IN_HELP": "Allows you to <b>transform </b> a simple wallet account <b>into a member account</b>, by sending a membership request. Useful only if you do not already have another member account.", "SEND_IDENTITY": "Publish identity...", @@ -556,7 +559,23 @@ "SAVE_ID": "Save my credentials...", "SAVE_ID_HELP": "Creating a backup file, to <b>retrieve your password</b> (and the secret identifier) <b> in case of forgetting</b>. The file is <b>secured</ b> (encrypted) using personal questions.", "STRONG_LEVEL": "Strong <span class=\"hidden-xs \">(6 questions minimum)</span>", - "TITLE": "Account and security" + "TITLE": "Account and security", + "KEYFILE": { + "PUBSEC_FORMAT": "PubSec format.", + "PUBSEC_FORMAT_HELP": "This file format is compatible in particular with Cesium and Gannonce. Your keychain is stored <b>without encryption</b>: anyone with a copy of this file will be able to empty your account.", + "WIF_FORMAT": "Wallet Import Format (WIF)", + "WIF_FORMAT_HELP": "This format is used in particular by paper wallets. Your keychain is stored <b>without encryption</b>: anyone with a copy of this file will be able to empty your account.", + "EWIF_FORMAT": "Encrypted Wallet Import Format (WIF)", + "EWIF_FORMAT_HELP": "This format is used in particular by paper wallets. However, <b>the keychain is encrypted</b> from a passphrase of your choice.", + "PASSWORD_POPUP": { + "TITLE": "Keychain file encrypted", + "HELP": "Please enter the passphrase:", + "PASSWORD_HELP": "Passphrase" + }, + "ERROR": { + "BAD_PASSWORD": "Bad passphrase" + } + } }, "FILE_NAME": "{{currency}} - Account statement {{pubkey|formatPubkey}} to {{currentTime|formatDateForFile}}.csv", "HEADERS": { @@ -582,9 +601,12 @@ } }, "ERROR": { + "UNKNOWN_URI_FORMAT": "Unknown URI format", + "PUBKEY_INVALID_CHECKSUM": "Invalid public key (bad checksum).", "POPUP_TITLE": "Error", "UNKNOWN_ERROR": "Unknown error", "CRYPTO_UNKNOWN_ERROR": "Your browser is not compatible with cryptographic features.", + "DOWNLOAD_KEYFILE_FAILED": "Failed to generate the keychain file.", "EQUALS_TO_PSEUDO": "Must be different from pseudonym", "EQUALS_TO_SALT": "Must be different from secret identifier", "FIELD_REQUIRED": "This field is required.", @@ -634,6 +656,7 @@ "INVALID_USER_ID": "Field 'pseudonym' must not contains spaces or special characters.", "INVALID_COMMENT": "Field 'reference' has a bad format.", "INVALID_PUBKEY": "Public key has a bad format.", + "INVALID_PUBKEY_CHECKSUM": "Invalid checksum.", "IDENTITY_REVOKED": "This identity <b>has been revoked {{revocationTime|formatFromNow}}</b> ({{revocationTime|formatDate}}). It can no longer become a member.", "IDENTITY_PENDING_REVOCATION": "The <b>revocation of this identity</b> has been requested and is awaiting processing. Certification is therefore disabled.", "IDENTITY_INVALID_BLOCK_HASH": "This membership application is no longer valid (because it references a block that network peers are cancelled): the person must renew its application for membership <b>before</b> being certified.", diff --git a/www/i18n/locale-en.json b/www/i18n/locale-en.json index ce6a86f8565a36770a375354d66c139e78f77844..48f0eee6ad456675e515222ff63d1ab4a8a36d0d 100644 --- a/www/i18n/locale-en.json +++ b/www/i18n/locale-en.json @@ -518,6 +518,9 @@ "BTN_RESET" : "Reset", "DOWNLOAD_REVOKE": "Save a revocation file", "DOWNLOAD_REVOKE_HELP" : "Having a revocation file is important, for example in case of loss of identifiers. It allows you to <b>get this account out of the Web Of Trust</b>, thus becoming a simple wallet.", + "GENERATE_KEYFILE": "Generate my keychain file ...", + "GENERATE_KEYFILE_HELP": "Generate a file allowing you to authenticate without entering your identifiers.<br/><b>Warning:</b> this file will contain your secret key; It is therefore very important to put it in a safe place!", + "KEYFILE_FILENAME": "keychain-{{pubkey|formatPubkey}}-{{currency}}-{{format}}.dunikey", "MEMBERSHIP_IN": "Register as member...", "MEMBERSHIP_IN_HELP": "Allows you to <b>transform </b> a simple wallet account <b>into a member account</b>, by sending a membership request. Useful only if you do not already have another member account.", "SEND_IDENTITY": "Publish identity...", @@ -556,7 +559,23 @@ "SAVE_ID": "Save my credentials...", "SAVE_ID_HELP": "Creating a backup file, to <b>retrieve your password</b> (and the secret identifier) <b> in case of forgetting</b>. The file is <b>secured</ b> (encrypted) using personal questions.", "STRONG_LEVEL": "Strong <span class=\"hidden-xs \">(6 questions minimum)</span>", - "TITLE": "Account and security" + "TITLE": "Account and security", + "KEYFILE": { + "PUBSEC_FORMAT": "PubSec format.", + "PUBSEC_FORMAT_HELP": "This file format is compatible in particular with Cesium and Gannonce. Your keychain is stored <b>without encryption</b>: anyone with a copy of this file will be able to empty your account.", + "WIF_FORMAT": "Wallet Import Format (WIF)", + "WIF_FORMAT_HELP": "This format is used in particular by paper wallets. Your keychain is stored <b>without encryption</b>: anyone with a copy of this file will be able to empty your account.", + "EWIF_FORMAT": "Encrypted Wallet Import Format (WIF)", + "EWIF_FORMAT_HELP": "This format is used in particular by paper wallets. However, <b>the keychain is encrypted</b> from a passphrase of your choice.", + "PASSWORD_POPUP": { + "TITLE": "Keychain file encrypted", + "HELP": "Please enter the passphrase:", + "PASSWORD_HELP": "Passphrase" + }, + "ERROR": { + "BAD_PASSWORD": "Bad passphrase" + } + } }, "FILE_NAME": "{{currency}} - Account statement {{pubkey|formatPubkey}} to {{currentTime|formatDateForFile}}.csv", "HEADERS": { @@ -582,9 +601,12 @@ } }, "ERROR": { + "UNKNOWN_URI_FORMAT": "Unknown URI format", + "PUBKEY_INVALID_CHECKSUM": "Invalid public key (bad checksum).", "POPUP_TITLE": "Error", "UNKNOWN_ERROR": "Unknown error", "CRYPTO_UNKNOWN_ERROR": "Your browser is not compatible with cryptographic features.", + "DOWNLOAD_KEYFILE_FAILED": "Failed to generate the keychain file.", "EQUALS_TO_PSEUDO": "Must be different from pseudonym", "EQUALS_TO_SALT": "Must be different from secret identifier", "FIELD_REQUIRED": "This field is required.", @@ -634,6 +656,7 @@ "INVALID_USER_ID": "Field 'pseudonym' must not contains spaces or special characters.", "INVALID_COMMENT": "Field 'reference' has a bad format.", "INVALID_PUBKEY": "Public key has a bad format.", + "INVALID_PUBKEY_CHECKSUM": "Invalid checksum.", "IDENTITY_REVOKED": "This identity <b>has been revoked {{revocationTime|formatFromNow}}</b> ({{revocationTime|formatDate}}). It can no longer become a member.", "IDENTITY_PENDING_REVOCATION": "The <b>revocation of this identity</b> has been requested and is awaiting processing. Certification is therefore disabled.", "IDENTITY_INVALID_BLOCK_HASH": "This membership application is no longer valid (because it references a block that network peers are cancelled): the person must renew its application for membership <b>before</b> being certified.", diff --git a/www/i18n/locale-fr-FR.json b/www/i18n/locale-fr-FR.json index 90739250c16c41a33fd5c7937501f14ffb1c9ecf..61a938df059814bb624ab1fd3b6fdb1a26a79644 100644 --- a/www/i18n/locale-fr-FR.json +++ b/www/i18n/locale-fr-FR.json @@ -423,7 +423,7 @@ "TYPE" : "Type :", "SIZE": "Taille :", "VALIDATING": "Validation en cours...", - "HELP": "Format de fichier attendu : <b>.dunikey</b> (type PubSec). D'autres formats sont en cours de développement (EWIF, WIF)." + "HELP": "Format de fichier attendu : <b>.yml</b> ou <b>.dunikey</b> (type PubSec, WIF ou EWIF)." } }, "AUTH": { @@ -519,7 +519,7 @@ "DOWNLOAD_REVOKE": "Sauvegarder mon fichier de révocation", "DOWNLOAD_REVOKE_HELP": "Disposer d'un fichier de révocation est important, par exemple en cas de perte de vos identifiants. Il vous permet de <b>sortir ce compte de la toile de confiance</b>, en redevenant ainsi un simple portefeuille.", "GENERATE_KEYFILE": "Générer mon fichier de trousseau...", - "GENERATE_KEYFILE_HELP": "Génère un fichier permettant de vous authentifier sans saisir vos identifiants.<br/><b>Attention :</b> ce fichier contiendra votre clef secrète : il est donc très important de le mettre en lieu sûr !", + "GENERATE_KEYFILE_HELP": "Génère un fichier permettant de vous authentifier sans saisir vos identifiants.<br/><b>Attention :</b> ce fichier contiendra votre trousseau de compte (clefs publique et secrète) ; Il est donc très important de le mettre en lieu sûr !", "KEYFILE_FILENAME": "trousseau-{{pubkey|formatPubkey}}-{{currency}}-{{format}}.dunikey", "MEMBERSHIP_IN": "Transformer en compte membre...", "MEMBERSHIP_IN_HELP": "Permet de <b>transformer</b> un compte simple portefeuille <b>en compte membre</b>, en envoyant une demande d'adhésion. Utile uniquement si vous n'avez pas déjà une autre compte membre.", @@ -562,15 +562,18 @@ "TITLE": "Compte et sécurité", "KEYFILE": { "PUBSEC_FORMAT": "Format PubSec.", - "PUBSEC_FORMAT_HELP": "Ce format de fichier est compatible notamment avec Cesium et gannonce.", - "WIF_FORMAT": "Format WIF", - "WIF_FORMAT_HELP": "Ce format permet notamment la génération de portefeuilles papier (Paper Wallet).", - "EWIF_FORMAT": "Format EWIF", - "EWIF_FORMAT_HELP": "Ce format protège le trousseau par un mot de passe. Il permet notamment la génération de portefeuilles papier (Paper Wallet).", + "PUBSEC_FORMAT_HELP": "Ce format votre stocke votre trousseau de manière très simple. Il est compatible notamment avec Cesium, ğannonce et Duniter.<br/><b>Attention:</b>Le fichier <b>n'est pas chiffré</b> (la clef secrète y apparait en clair); Veuillez donc le stocker en lieu sûr !", + "WIF_FORMAT": "Format WIF (Wallet Import Format) - v1", + "WIF_FORMAT_HELP": "Ce format stocke votre votre trousseau, en y intégrant une somme de contrôle pour vérifier l'intégrité du fichier. Il est compatible notamment avec les portefeuilles papier (Duniter paper wallet).<br/><b>Attention:</b>Le fichier <b>n'est pas chiffré</b> (la clef secrète y apparait en clair); Veuillez donc le stocker en lieu sûr !", + "EWIF_FORMAT": "Format EWIF (Encrypted Wallet Import Format) - v1", + "EWIF_FORMAT_HELP": "Ce format stocke votre trouseau <b>de manière chiffré</b> à partir d'une phrase secrète de votre choix. Il intègre aussi une somme de contrôle pour vérifier l'intégrité du fichier.<br/><b>Attention:</b> Veuillez à toujours vous rappeller de votre phrase secrète !", "PASSWORD_POPUP": { "TITLE": "Fichier de trousseau chiffré", - "HELP": "Veuillez indiquer le mot de passe:", - "PASSWORD_HELP": "Mot de passe" + "HELP": "Veuillez indiquer la phrase secrète:", + "PASSWORD_HELP": "Phrase secrète" + }, + "ERROR": { + "BAD_PASSWORD": "Phrase secrète incorrecte" } } }, diff --git a/www/js/controllers/app-controllers.js b/www/js/controllers/app-controllers.js index 0ff395cf3ddb4be922e2923e4b46c7be3012bc2c..95e70aa197ee788a29f3e5d0bec074ecf1be2074 100644 --- a/www/js/controllers/app-controllers.js +++ b/www/js/controllers/app-controllers.js @@ -86,37 +86,44 @@ function AppController($scope, $rootScope, $state, $ionicSideMenuDelegate, $q, $ // Device Methods //////////////////////////////////////// - function parseWif(data) { - return CryptoUtils.readWif(data, { - password: function() { - return Modals.showPassword({ + function parseWIF(data, options) { + options = options || {}; + options.withSecret = angular.isDefined(options.withSecret) && options.withSecret || true; + options.password = function() { + return Modals.showPassword({ title: 'ACCOUNT.SECURITY.KEYFILE.PASSWORD_POPUP.TITLE', - subTitle: 'ACCOUNT.SECURITY.KEYFILE.PASSWORD_POPUP.HELP' + subTitle: 'ACCOUNT.SECURITY.KEYFILE.PASSWORD_POPUP.HELP', + error: options.error }) .then(function(password) { - UIUtils.loading.show(); + if (password) UIUtils.loading.show(); return password; }); - } - }) + }; + + return CryptoUtils.parseWIF_or_EWIF(data, options) .catch(function(err) { if (err && err == 'CANCELLED') return; - if (err && err == 'BAD_PASSWORD') return parseWif(); // recursive call + if (err && err.ucode == CryptoUtils.errorCodes.BAD_PASSWORD) { + // recursive call + return parseWIF(data, {withSecret: options.withSecret, error: 'ACCOUNT.SECURITY.KEYFILE.ERROR.BAD_PASSWORD'}); + } console.error("[app] Unable to parse as WIF or EWIF format: " + (err && err.message || err)); throw err; // rethrow }); } $scope.scanQrCodeAndGo = function() { - if (!Device.barcode.enable) { - return; - } - Device.barcode.scan() + + if (!Device.barcode.enable) return; + + // Run scan cordova plugin, on device + return Device.barcode.scan() .then(function(data) { if (!data) return; - // Parse as an URI - BMA.uri.parse(data) + // Try to parse as an URI + return BMA.uri.parse(data) .then(function(res){ if (!res || res.pubkey) throw {message: 'ERROR.SCAN_UNKNOWN_FORMAT'}; // If pubkey: open the identity @@ -131,15 +138,14 @@ function AppController($scope, $rootScope, $state, $ionicSideMenuDelegate, $q, $ console.debug(err && err.message || err); // Try to read as WIF format - return parseWif(data) + return parseWIF(data) .then(function(keypair) { - if (!keypair || !keypair.signPk || !keypair.signSk) throw {message: 'ERROR.SCAN_UNKNOWN_FORMAT'}; - var pubkey = CryptoUtils.base58.encode(keypair.signPk); + if (!keypair || !keypair.signPk || !keypair.signSk) throw err; // rethrow the first error return csWallet.login({ forceAuth: true, minData: false, authData: { - pubkey: pubkey, + pubkey: CryptoUtils.base58.encode(keypair.signPk), keypair: keypair } }) diff --git a/www/js/controllers/login-controllers.js b/www/js/controllers/login-controllers.js index 34e250b18d1577dcb84fb32594d364914b978b24..00ba7f1851a7a177cce4e153277124024f10ed12 100644 --- a/www/js/controllers/login-controllers.js +++ b/www/js/controllers/login-controllers.js @@ -325,10 +325,11 @@ function LoginModalController($scope, $timeout, $q, $ionicPopover, CryptoUtils, options = options || {}; options.password = options.password || $scope.formData.file.password || function() { - $scope.formData.file.password = undefined; + $scope.formData.file.password = undefined; return Modals.showPassword({ title: 'ACCOUNT.SECURITY.KEYFILE.PASSWORD_POPUP.TITLE', - subTitle: 'ACCOUNT.SECURITY.KEYFILE.PASSWORD_POPUP.HELP' + subTitle: 'ACCOUNT.SECURITY.KEYFILE.PASSWORD_POPUP.HELP', + error: options.error }) .then(function (password) { // Remember password (for validation) @@ -339,9 +340,10 @@ function LoginModalController($scope, $timeout, $q, $ionicPopover, CryptoUtils, return CryptoUtils.readKeyFile($scope.formData.file, options) .catch(function(err) { - if (err && err == 'CANCELLED') return; - if (err && err == 'BAD_PASSWORD') { - return $scope.readKeyFile($scope.formData.file, options); // Loop (ask the password again) + $scope.formData.file.password = undefined; + if (err && err.ucode == CryptoUtils.errorCodes.BAD_PASSWORD) { + // Recursive call + return $scope.readKeyFile($scope.formData.file, {withSecret: options.withSecret, error: 'ACCOUNT.SECURITY.KEYFILE.ERROR.BAD_PASSWORD'}); } throw err; }); diff --git a/www/js/services/crypto-services.js b/www/js/services/crypto-services.js index 420cea95303eea69a21eb74787e22417bbed38da..ac15f9a3a1a890f5d6483c7e5fb1722423b87112 100644 --- a/www/js/services/crypto-services.js +++ b/www/js/services/crypto-services.js @@ -135,6 +135,11 @@ angular.module('cesium.crypto.services', ['cesium.utils.services']) } }; + CryptoAbstractService.prototype.errorCodes = { + BAD_PASSWORD: 3001, + BAD_CHECKSUM: 3002 + }; + CryptoAbstractService.prototype.async_load_base58 = function(on_ready) { var that = this; if (Base58 !== null){return on_ready(Base58);} @@ -235,7 +240,8 @@ angular.module('cesium.crypto.services', ['cesium.utils.services']) if (!matches) { return $q.reject('Missing [Data] field in file. This is required for WIF or EWIF format'); } - return that.readWif(matches[1], { + + return that.parseWIF_or_EWIF(matches[1], { type: type, password: options.password }) @@ -253,7 +259,64 @@ angular.module('cesium.crypto.services', ['cesium.utils.services']) } }; - CryptoAbstractService.prototype.readWif_v1 = function(wif_base58) { + + /** + * + * @param data_base58 + * @param options + * @returns {*} + */ + CryptoAbstractService.prototype.parseWIF_or_EWIF = function(data_base58, options) { + var that = this; + options = options || {}; + + var data_int8 = that.base58.decode(data_base58); + if (data_int8.length != that.constants.EWIF.DATA_LENGTH && data_int8.length != that.constants.WIF.DATA_LENGTH) { + return $q.reject('Invalid WIF or EWIF format (invalid bytes count).'); + } + + // Detect the type from the first byte + options.type = options.type || (data_int8[0] == 1 && 'WIF') || (data_int8[0] == 2 && 'EWIF'); + + // Type: WIF + if (options.type == 'WIF') { + return that.parseWIF_v1(data_base58); + } + + // Type: EWIF + if (options.type == 'EWIF') { + + // If not set, resolve password using the given callback + if (typeof options.password == "function") { + console.debug("[crypto] [EWIF] Executing 'options.password()' to resolve the password..."); + options.password = options.password(); + if (!options.password) { + return $q.reject({message: "Invalid callback result for 'options.password()': must return a promise or a string."}); + } + } + + // If password is a promise, get the result then read data + if (typeof options.password == "object" && options.password.then) { + return options.password.then(function(password) { + if (!password) throw 'CANCELLED'; + return that.parseEWIF_v1(data_base58, password); + }); + } + + // If password is a valid string, read data + if (typeof options.password == "string") { + return that.parseEWIF_v1(data_base58, options.password); + } + + return $q.reject({message: 'Invalid EWIF options.password. Waiting a callback function, a promise or a string.'}); + } + + // Unknown type + return $q.reject({message: 'Invalid WIF or EWIF format: unknown first byte identifier.'}); + }; + + + CryptoAbstractService.prototype.parseWIF_v1 = function(wif_base58) { var that = this, wif_int8 = that.util.decode_base58(wif_base58); @@ -281,7 +344,7 @@ angular.module('cesium.crypto.services', ['cesium.utils.services']) return that.seedKeypair(seed); }; - CryptoAbstractService.prototype.readEwif_v1 = function(ewif_base58, password) { + CryptoAbstractService.prototype.parseEWIF_v1 = function(ewif_base58, password) { var that = this, ewif_int8 = that.util.decode_base58(ewif_base58); @@ -341,13 +404,13 @@ angular.module('cesium.crypto.services', ['cesium.utils.services']) // Check salt var expectedSalt = that.util.crypto_hash_sha256(that.util.crypto_hash_sha256(keypair.signPk)).slice(0,4); if(that.util.encode_base58(salt) !== that.util.encode_base58(expectedSalt)) { - throw 'BAD_PASSWORD'; + throw {ucode: that.errorCodes.BAD_PASSWORD, message: 'ERROR.BAD_PASSWORD'}; } // Check checksum var expectedChecksum = that.util.crypto_hash_sha256(that.util.crypto_hash_sha256(ewif_int8_no_checksum)).slice(0,2); if (that.util.encode_base58(checksum) != that.util.encode_base58(expectedChecksum)) { - throw {message: "Invalid EWIF format: bad checksum"}; + throw {ucode: that.errorCodes.BAD_CHECKSUM, message: 'ERROR.BAD_CHECKSUM'}; } return keypair; @@ -355,77 +418,80 @@ angular.module('cesium.crypto.services', ['cesium.utils.services']) }; - /** - * - * @param data_base58 - * @param options - * @returns {*} - */ - CryptoAbstractService.prototype.readWif = function(data_base58, options) { - var that = this; - options = options || {}; + CryptoAbstractService.prototype.pkChecksum = function(pubkey) { + var signPk_int8 = this.util.decode_base58(pubkey); + return this.util.encode_base58(this.util.crypto_hash_sha256(this.util.crypto_hash_sha256(signPk_int8))).substring(0,3); + }; - // Check has password options, if EWIF or autodetect format - if ((!options.type || options.type === "EWIF") && !options.password) { - $q.reject("Missing options.password or options.passwordCallback"); - } + CryptoAbstractService.prototype.seed_from_signSk = function(signSk) { + var that = this; + var seed = new Uint8Array(that.constants.SEED_LENGTH); + for (var i = 0; i < seed.length; i++) seed[i] = signSk[i]; + return seed; + }; - var data_int8 = that.base58.decode(data_base58); - if (data_int8.length != that.constants.EWIF.DATA_LENGTH && data_int8.length != that.constants.WIF.DATA_LENGTH) { - return $q.reject('Invalid WIF or EWIF format (invalid bytes count).'); - } + CryptoAbstractService.prototype.generateKeyFileContent = function(keypair, options) { + var that = this; + options = options || {}; + options.type = options.type || "PubSec"; + + switch(options.type) { + + // PubSec + case "PubSec" : + return $q.resolve( + "Type: PubSec\n" + + "Version: 1\n" + + "pub: " + that.base58.encode(keypair.signPk) + "\n" + + "sec: " + that.base58.encode(keypair.signSk) + "\n"); + + // WIF - v1 + case "WIF" : + return that.wif_v1_from_keypair(keypair) + .then(function(data) { + return "Type: WIF\n" + + "Version: 1\n" + + "Data: " + data + "\n"; + }); - // Detect the type from the first byte - options.type = options.type || (data_int8[0] == 1 && 'WIF') || (data_int8[0] == 2 && 'EWIF'); + // EWIF - v1 + case "EWIF" : - // Type: WIF - if (options.type == 'WIF') { - return that.readWif_v1(data_base58); - } + if (!options.password) return $q.reject({message: 'Missing EWIF options.password.'}); - // Type: EWIF - if (options.type == 'EWIF') { + // If not set, resolve password using the given callback + if (options.password && typeof options.password == "function") { + console.debug("[crypto] [EWIF] Executing 'options.password()' to resolve the password..."); + options.password = options.password(); + if (!options.password) { + return $q.reject({message: "Invalid callback result for 'options.password()': must return a promise or a string."}); + } + } - // If not set, resolve password using the given callback - if (typeof options.password == "function") { - console.debug("[crypto] [EWIF] Executing 'options.password()' to resolve the password..."); - options.password = options.password(); - if (!options.password) { - return $q.reject({message: "Invalid callback result for 'options.password()': must return a promise or a string."}); + // If password is a promise, get the result then read data + if (options.password && typeof options.password == "object" && options.password.then) { + return options.password.then(function(password) { + if (!password) throw 'CANCELLED'; + // Recursive call, with the string password in options + return that.generateKeyFileContent(keypair, angular.merge({}, options, {password: password})); + }); } - } - // If password is a promise, get the result then read data - if (typeof options.password == "object" && options.password.then) { - return options.password.then(function(password) { - if (!password) throw 'CANCELLED'; - return that.readEwif_v1(data_base58, password); - }); - } + // If password is a valid string, read data + if (options.password && typeof options.password == "string") { + return that.ewif_v1_from_keypair(keypair, options.password) + .then(function(data) { + return "Type: EWIF\n" + + "Version: 1\n" + + "Data: " + data + "\n"; + }); + } - // If password is a valid string, read data - if (typeof options.password == "string") { - return that.readEwif_v1(data_base58, options.password); - } + return $q.reject({message: 'Invalid EWIF options.password. Waiting a callback function, a promise or a string.'}); - return $q.reject({message: 'Invalid EWIF options.password. Waiting a callback function, a promise or a string.'}); + default: + return $q.reject({message: "Unknown keyfile format: " + options.type}); } - - // Unknown type - return $q.reject({message: 'Invalid WIF or EWIF format: unknown first byte identifier.'}); - }; - - - CryptoAbstractService.prototype.pkChecksum = function(pubkey) { - var signPk_int8 = this.util.decode_base58(pubkey); - return this.util.encode_base58(this.util.crypto_hash_sha256(this.util.crypto_hash_sha256(signPk_int8))).substring(0,3); - }; - - CryptoAbstractService.prototype.seed_from_signSk = function(signSk) { - var that = this; - var seed = new Uint8Array(that.constants.SEED_LENGTH); - for (var i = 0; i < seed.length; i++) seed[i] = signSk[i]; - return seed; }; CryptoAbstractService.prototype.wif_v1_from_keypair = function(keypair) { @@ -453,44 +519,47 @@ angular.module('cesium.crypto.services', ['cesium.utils.services']) var seed = that.seed_from_signSk(keypair.signSk); if (!seed || seed.byteLength !== that.constants.SEED_LENGTH) - throw "Bad see format. Expected {0} bytes".format(that.constants.SEED_LENGTH); + return $q.reject({message: "Bad see format. Expected {0} bytes".format(that.constants.SEED_LENGTH)}); // salt - var salt = that.nacl.crypto_hash_sha256(that.nacl.crypto_hash_sha256(keypair.signPk)).slice(0,4); + var salt = that.util.crypto_hash_sha256(that.util.crypto_hash_sha256(keypair.signPk)).slice(0,4); // scrypt_seed - var scrypt_seed = that.util.crypto_scrypt( + return that.util.crypto_scrypt( that.util.encode_utf8(password), salt, that.constants.EWIF.SCRYPT_PARAMS.N, that.constants.EWIF.SCRYPT_PARAMS.r, that.constants.EWIF.SCRYPT_PARAMS.p, - 64); - var derivedhalf1 = scrypt_seed.slice(0,32); - var derivedhalf2 = scrypt_seed.slice(32,64); + 64) + .then(function(scrypt_seed) { + var derivedhalf1 = scrypt_seed.slice(0,32); + var derivedhalf2 = scrypt_seed.slice(32,64); + + //XOR & AES + var seed1_xor_derivedhalf1_1 = xor(seed.slice(0,16), derivedhalf1.slice(0,16)); + var seed2_xor_derivedhalf1_2 = xor(seed.slice(16,32), derivedhalf1.slice(16,32)); - //XOR & AES - var seed1_xor_derivedhalf1_1 = xor(seed.slice(0,16), derivedhalf1.slice(0,16)); - var seed2_xor_derivedhalf1_2 = xor(seed.slice(16,32), derivedhalf1.slice(16,32)); + var aesEcb = new aesjs.ModeOfOperation.ecb(derivedhalf2); + var encryptedhalf1 = aesEcb.encrypt(seed1_xor_derivedhalf1_1); + var encryptedhalf2 = aesEcb.encrypt(seed2_xor_derivedhalf1_2); - var aesEcb = new aesjs.ModeOfOperation.ecb(derivedhalf2); - var encryptedhalf1 = aesEcb.encrypt(seed1_xor_derivedhalf1_1); - var encryptedhalf2 = aesEcb.encrypt(seed2_xor_derivedhalf1_2); + encryptedhalf1 = new Uint8Array(encryptedhalf1); + encryptedhalf2 = new Uint8Array(encryptedhalf2); - encryptedhalf1 = new Uint8Array(encryptedhalf1); - encryptedhalf2 = new Uint8Array(encryptedhalf2); + // concatenate ewif + var ewif_int8 = new Uint8Array(1); + ewif_int8[0] = 0x02; + ewif_int8 = concat_Uint8Array(ewif_int8,salt); + ewif_int8 = concat_Uint8Array(ewif_int8,encryptedhalf1); + ewif_int8 = concat_Uint8Array(ewif_int8,encryptedhalf2); - // concatenate ewif - var ewif_int8 = new Uint8Array(1); - ewif_int8[0] = 0x02; - ewif_int8 = concat_Uint8Array(ewif_int8,salt); - ewif_int8 = concat_Uint8Array(ewif_int8,encryptedhalf1); - ewif_int8 = concat_Uint8Array(ewif_int8,encryptedhalf2); + var checksum = that.util.crypto_hash_sha256(that.util.crypto_hash_sha256(ewif_int8)).slice(0,2); + ewif_int8 = concat_Uint8Array(ewif_int8,checksum); - var checksum = that.nacl.crypto_hash_sha256(that.nacl.crypto_hash_sha256(ewif_int8)).slice(0,2); - ewif_int8 = concat_Uint8Array(ewif_int8,checksum); + return that.util.encode_base58(ewif_int8); + }); - return that.util.encode_base58(ewif_int8); }; // Web crypto API - see https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API diff --git a/www/js/services/modal-services.js b/www/js/services/modal-services.js index e6aff2dafde9f21293dd6bf2b263235e34792c3f..61614948306a550cb556795af1d03c9704322e1e 100644 --- a/www/js/services/modal-services.js +++ b/www/js/services/modal-services.js @@ -146,7 +146,7 @@ angular.module('cesium.modal.services', []) }; }) -.factory('Modals', function($rootScope, $translate, $ionicPopup, ModalUtils, UIUtils) { +.factory('Modals', function($rootScope, $translate, $ionicPopup, $timeout, ModalUtils, UIUtils) { 'ngInject'; function showTransfer(parameters) { @@ -235,14 +235,14 @@ angular.module('cesium.modal.services', []) }; scope.submit = function(e) { scope.form.$submitted=true; - if(!scope.form.$valid || !scope.formData.password) { - //don't allow the user to close unless he enters a uid - if (e && e.preventDefault) e.preventDefault(); - } else { + if (e && e.preventDefault) e.preventDefault(); + if(scope.form.$valid && scope.formData.password) { options.popup.close(scope.formData.password); } }; + scope.error = options.error || undefined; + // Choose password popup return $translate([options.title, options.subTitle, 'COMMON.BTN_OK', 'COMMON.BTN_CANCEL']) .then(function (translations) { diff --git a/www/js/services/wallet-services.js b/www/js/services/wallet-services.js index 58105e45af6ebb73a7dd91f9a477fc5b0e221816..82de629a4b670438734824d669fcfc625fb885e9 100644 --- a/www/js/services/wallet-services.js +++ b/www/js/services/wallet-services.js @@ -1409,41 +1409,30 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se }, - getKeyFileDocument =function(format) { - - var document; - switch(format) { - case "PubSec" : - document = "Type: PubSec\n" + - "Version: 1\n" + - "pub: " + data.pubkey + "\n" + - "sec: " + CryptoUtils.base58.encode(data.keypair.signSk) + "\n"; - break; - case "WIF" : - document = "Type: WIF\n" + - "Version: 1\n" + - "Data: " + CryptoUtils.wif_v1_from_keypair(data.keypair)+ "\n"; - break; - case "EWIF" : - document = "Type: EWIF\n" + - "Version: 1\n" + - "Data: " + CryptoUtils.ewif_v1_from_keypair(data.keypair, "bonjour") + "\n"; - break; - default: - return $q.reject("Unknown keyfile format: " + format); - } - if (document) { - return $q.resolve(document); - } - }, downloadKeyFile = function(format){ if (!isAuth()) return $q.reject('user not authenticated'); return $q.all([ csCurrency.get(), - getKeyFileDocument(format) + CryptoUtils.generateKeyFileContent(data.keypair, + { + type: format, + password: function() { + UIUtils.loading.hide(); + return Modals.showPassword({ + title: 'ACCOUNT.SECURITY.KEYFILE.PASSWORD_POPUP.TITLE', + subTitle: 'ACCOUNT.SECURITY.KEYFILE.PASSWORD_POPUP.HELP' + }) + .then(function(password) { + return UIUtils.loading.show(10) + .then(function(){ + return password; + }); + }); + } + }) ]) .then(function(res) { var currency = res[0]; diff --git a/www/templates/common/popup_password.html b/www/templates/common/popup_password.html index 800e93b14f4831e4bab24ccc9acde69facdf0359..f957bc659134dcabecb974e21909b35eeccdcd49 100644 --- a/www/templates/common/popup_password.html +++ b/www/templates/common/popup_password.html @@ -17,5 +17,8 @@ <span translate="ERROR.FIELD_TOO_SHORT"></span> </div> </div> + <div class="form-errors" ng-if="error"> + <div class="form-error" >{{error|translate}}</div> + </div> </div> </form>