implement account conversion

Signed-off-by: Michele Balistreri <michele@bitgamma.com>
This commit is contained in:
Michele Balistreri 2021-07-13 15:41:45 +02:00
parent baa96ed22e
commit e8f7ae8f27
No known key found for this signature in database
GPG Key ID: E9567DA33A4F791A
11 changed files with 239 additions and 26 deletions

View File

@ -528,6 +528,22 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL
StatusThreadPoolExecutor.getInstance().execute(r);
}
@ReactMethod
public void verifyDatabasePassword(final String keyUID, final String password, final Callback callback) {
Log.d(TAG, "verifyDatabasePassword");
Runnable r = new Runnable() {
@Override
public void run() {
String result = Statusgo.verifyDatabasePassword(keyUID, password);
callback.invoke(result);
}
};
StatusThreadPoolExecutor.getInstance().execute(r);
}
public String getKeyStorePath(String keyUID) {
final String commonKeydir = pathCombine(this.getNoBackupDirectory(), "/keystore");
final String keydir = pathCombine(commonKeydir, keyUID);
@ -1478,6 +1494,20 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL
StatusThreadPoolExecutor.getInstance().execute(r);
}
@ReactMethod
public void convertToKeycardAccount(final String keyUID, final String accountData, final String options, final String password, final String newPassword, final Callback callback) {
Log.d(TAG, "convertToKeycardAccount");
final String keyStoreDir = this.getKeyStorePath(keyUID);
Runnable r = new Runnable() {
@Override
public void run() {
String result = Statusgo.convertToKeycardAccount(keyStoreDir, accountData, options, password, newPassword);
callback.invoke(result);
}
};
StatusThreadPoolExecutor.getInstance().execute(r);
}
}

View File

@ -570,6 +570,17 @@ RCT_EXPORT_METHOD(verify:(NSString *)address
callback(@[result]);
}
//////////////////////////////////////////////////////////////////// verifyDatabasePassword
RCT_EXPORT_METHOD(verifyDatabasePassword:(NSString *)keyUID
password:(NSString *)password
callback:(RCTResponseSenderBlock)callback) {
#if DEBUG
NSLog(@"VerifyDatabasePassword() method called");
#endif
NSString *result = StatusgoVerifyDatabasePassword(keyUID, password);
callback(@[result]);
}
//////////////////////////////////////////////////////////////////// changeDatabasePassword
RCT_EXPORT_METHOD(reEncryptDbAndKeystore:(NSString *)keyUID
currentPassword:(NSString *)currentPassword
@ -583,6 +594,21 @@ RCT_EXPORT_METHOD(reEncryptDbAndKeystore:(NSString *)keyUID
callback(@[result]);
}
//////////////////////////////////////////////////////////////////// convertToKeycardAccount
RCT_EXPORT_METHOD(convertToKeycardAccount:(NSString *)keyUID
accountData:(NSString *)accountData
settings:(NSString *)settings
currentPassword:(NSString *)currentPassword
newPassword:(NSString *)newPassword
callback:(RCTResponseSenderBlock)callback) {
#if DEBUG
NSLog(@"convertToKeycardAccount() method called");
#endif
NSURL *multiaccountKeystoreDir = [self getKeyStoreDir:keyUID];
NSString *result = StatusgoConvertToKeycardAccount(multiaccountKeystoreDir.path, accountData, settings, currentPassword, newPassword);
callback(@[result]);
}
////////////////////////////////////////////////////////////////////
#pragma mark - SendTransaction
//////////////////////////////////////////////////////////////////// sendTransaction

View File

@ -5,6 +5,7 @@
[status-im.multiaccounts.model :as multiaccounts.model]
[status-im.utils.fx :as fx]
[re-frame.core :as re-frame]
[clojure.string :as string]
[status-im.i18n.i18n :as i18n]
[taoensso.timbre :as log]
[status-im.keycard.common :as common]
@ -13,6 +14,10 @@
[status-im.ethereum.eip55 :as eip55]
[status-im.ethereum.core :as ethereum]
[status-im.bottom-sheet.core :as bottom-sheet]
[status-im.native-module.core :as status]
[status-im.utils.types :as types]
[status-im.utils.security :as security]
[status-im.utils.keychain.core :as keychain]
[status-im.utils.platform :as platform]))
(fx/defn pair* [_ password]
@ -217,13 +222,54 @@
(navigation/set-stack-root :profile-stack [:my-profile :keycard-settings])
(return-to-keycard-login))))
(re-frame/reg-fx
::finish-migration
(fn [[account settings password encryption-pass login-params]]
(status/convert-to-keycard-account
account
settings
password
encryption-pass
#(let [{:keys [error]} (types/json->clj %)]
(if (string/blank? error)
(status/login-with-keycard login-params)
(throw (js/Error. "Please shake the phone to report this error and restart the app. Migration failed unexpectedly.")))))))
(fx/defn migrate-account
[{:keys [db] :as cofx}]
(let [pairing (get-in db [:keycard :secrets :pairing])
paired-on (get-in db [:keycard :secrets :paired-on])
instance-uid (get-in db [:keycard :multiaccount :instance-uid])
account (-> db
:multiaccounts/login
(assoc :keycard-pairing pairing)
(assoc :save-password? false))
key-uid (-> account :key-uid)
settings {:keycard-instance-uid instance-uid
:keycard-paired-on paired-on
:keycard-pairing pairing}
password (ethereum/sha3 (security/safe-unmask-data (get-in db [:keycard :migration-password])))
encryption-pass (get-in db [:keycard :multiaccount :encryption-public-key])
login-params {:key-uid key-uid
:multiaccount-data (types/clj->json account)
:password encryption-pass
:chat-key (get-in db [:keycard :multiaccount :whisper-private-key])}]
{:db (-> db
(assoc-in [:multiaccounts/multiaccounts key-uid :keycard-pairing] pairing)
(assoc :multiaccounts/login account)
(assoc :auth-method keychain/auth-method-none)
(update :keycard dissoc :flow :migration-password)
(dissoc :recovered-account?))
::finish-migration [account settings password encryption-pass login-params]}))
(fx/defn on-generate-and-load-key-success
{:events [:keycard.callback/on-generate-and-load-key-success]
:interceptors [(re-frame/inject-cofx :random-guid-generator)
(re-frame/inject-cofx ::multiaccounts.create/get-signing-phrase)]}
[{:keys [db random-guid-generator] :as cofx} data]
(let [account-data (js->clj data :keywordize-keys true)
backup? (get-in db [:keycard :creating-backup?])]
backup? (get-in db [:keycard :creating-backup?])
migration? (get-in db [:keycard :converting-account?])]
(fx/merge cofx
{:db (-> db
(assoc-in [:keycard :multiaccount]
@ -242,14 +288,14 @@
(assoc-in [:keycard :pin :status] nil)
(assoc-in [:keycard :application-info :key-uid]
(ethereum/normalized-hex (:key-uid account-data)))
(update :keycard dissoc :recovery-phrase)
(update :keycard dissoc :creating-backup?)
(update-in [:keycard :secrets] dissoc :pin :puk :password)
(assoc :multiaccounts/new-installation-id (random-guid-generator))
(update-in [:keycard :secrets] dissoc :mnemonic))}
(update :keycard dissoc :recovery-phrase :creating-backup? :converting-account?)
(update-in [:keycard :secrets] dissoc :pin :puk :password :mnemonic)
(assoc :multiaccounts/new-installation-id (random-guid-generator)))}
(common/remove-listener-to-hardware-back-button)
(common/hide-connection-sheet)
(if backup? (on-backup-success backup?) (create-keycard-multiaccount)))))
(cond backup? (on-backup-success backup?)
migration? (migrate-account)
:else (create-keycard-multiaccount)))))
(fx/defn on-generate-and-load-key-error
{:events [:keycard.callback/on-generate-and-load-key-error]}

View File

@ -10,8 +10,11 @@
[status-im.popover.core :as popover]
[status-im.utils.fx :as fx]
[status-im.utils.security :as security]
[status-im.ethereum.core :as ethereum]
[status-im.i18n.i18n :as i18n]
[status-im.utils.types :as types]
[status-im.keycard.backup-key :as keycard.backup]))
[status-im.keycard.backup-key :as keycard.backup]
[status-im.bottom-sheet.core :as bottom-sheet]))
(fx/defn key-and-storage-management-pressed
"This event can be dispatched before login and from profile and needs to redirect accordingly"
@ -29,6 +32,11 @@
[{:keys [db] :as cofx} checked?]
{:db (assoc-in db [:multiaccounts/key-storage :move-keystore-checked?] checked?)})
(fx/defn reset-db-checked
{:events [::reset-db-checked]}
[{:keys [db] :as cofx} checked?]
{:db (assoc-in db [:multiaccounts/key-storage :reset-db-checked?] checked?)})
(fx/defn navigate-back
{:events [::navigate-back]}
[{:keys [db] :as cofx}]
@ -120,11 +128,6 @@
[{:keys [db]} selected?]
{:db (assoc-in db [:multiaccounts/key-storage :keycard-storage-selected?] selected?)})
(fx/defn warning-popup
{:events [::show-transfer-warning-popup]}
[cofx]
(popover/show-popover cofx {:view :transfer-multiaccount-to-keycard-warning}))
(re-frame/reg-fx
::delete-multiaccount
(fn [{:keys [key-uid on-success on-error]}]
@ -158,13 +161,54 @@ The exact events dispatched for this flow if consumed from the UI are:
We don't need to take the exact steps, just set the required state and redirect to correct screen
"
(fx/defn handle-delete-multiaccount-success
(fx/defn import-multiaccount
{:events [::delete-multiaccount-success]}
[{:keys [db] :as cofx} _]
{::multiaccounts.recover/import-multiaccount {:passphrase (get-in db [:multiaccounts/key-storage :seed-phrase])
[{:keys [db] :as cofx}]
{:dispatch [:bottom-sheet/hide]
::multiaccounts.recover/import-multiaccount {:passphrase (get-in db [:multiaccounts/key-storage :seed-phrase])
:password nil
:success-event ::import-multiaccount-success}})
(fx/defn storage-selected
{:events [::storage-selected]}
[{:keys [db] :as cofx}]
(if (get-in db [:multiaccounts/key-storage :reset-db-checked?])
(popover/show-popover cofx {:view :transfer-multiaccount-to-keycard-warning})
(bottom-sheet/show-bottom-sheet cofx {:view :migrate-account-password})))
(fx/defn skip-password-pressed
{:events [::skip-password-pressed]}
[cofx]
(popover/show-popover cofx {:view :transfer-multiaccount-to-keycard-warning}))
(fx/defn password-changed
{:events [::password-changed]}
[{db :db} password]
(let [unmasked-pass (security/safe-unmask-data password)]
{:db (update db :keycard assoc
:migration-password password
:migration-password-error nil
:migration-password-valid? (and unmasked-pass (> (count unmasked-pass) 5)))}))
(fx/defn verify-password-result
{:events [::verify-password-result]}
[{:keys [db] :as cofx} result]
(let [{:keys [error]} (types/json->clj result)]
(if (string/blank? error)
(fx/merge
cofx
{:db (update db :keycard dissoc :migration-password-error :migration-password-valid?)}
(import-multiaccount))
{:db (assoc-in db [:keycard :migration-password-error] (i18n/label :t/wrong-password))})))
(fx/defn verify-password
{:events [::verify-password]}
[{:keys [db] :as cofx}]
(native-module/verify-database-password
(get-in db [:multiaccounts/login :key-uid])
(ethereum/sha3 (security/safe-unmask-data (get-in db [:keycard :migration-password])))
#(re-frame/dispatch [::verify-password-result %])))
(fx/defn handle-multiaccount-import
{:events [::import-multiaccount-success]}
[{:keys [db] :as cofx} root-data derived-data]
@ -178,6 +222,7 @@ We don't need to take the exact steps, just set the required state and redirect
:selected-storage-type :advanced)
(assoc-in [:keycard :flow] :recovery)
(assoc-in [:keycard :from-key-storage-and-migration?] true)
(assoc-in [:keycard :converting-account?] (not (get-in db [:multiaccounts/key-storage :reset-db-checked?])))
(dissoc :multiaccounts/key-storage))}
(popover/hide-popover)
(navigation/navigate-to-cofx :keycard-onboarding-intro nil)))

View File

@ -337,12 +337,15 @@
(fx/defn login-only-events
[{:keys [db] :as cofx} key-uid password save-password?]
(let [auth-method (:auth-method db)
new-auth-method (get-new-auth-method auth-method save-password?)]
new-auth-method (get-new-auth-method auth-method save-password?)
from-migration? (get-in db [:keycard :from-key-storage-and-migration?])]
(log/debug "[login] login-only-events"
"auth-method" auth-method
"new-auth-method" new-auth-method)
(fx/merge cofx
{:db (assoc db :chats/loading? true)
{:db (-> db
(assoc :chats/loading? true)
(update :keycard dissoc :from-key-storage-and-migration?))
::json-rpc/call
[{:method "browsers_getBrowsers"
:on-success #(re-frame/dispatch [::initialize-browsers %])}
@ -354,6 +357,8 @@
:on-success #(do (re-frame/dispatch [::get-settings-callback %])
(redirect-to-root db))}]}
(notifications/load-notification-preferences)
(when from-migration?
(utils/show-popup (i18n/label :t/migration-successful) (i18n/label :t/migration-successful-text)))
(when save-password?
(keychain/save-user-password key-uid password))
(keychain/save-auth-method key-uid (or new-auth-method auth-method keychain/auth-method-none)))))

View File

@ -192,6 +192,12 @@
(log/debug "[native-module] verify")
(.verify ^js (status) address hashed-password callback))
(defn verify-database-password
"NOTE: beware, the password has to be sha3 hashed"
[key-uid hashed-password callback]
(log/debug "[native-module] verify-database-password")
(.verifyDatabasePassword ^js (status) key-uid hashed-password callback))
(defn login-with-keycard
[{:keys [key-uid multiaccount-data password chat-key]}]
(log/debug "[native-module] login-with-keycard")
@ -414,3 +420,14 @@
(init-keystore
key-uid
#(.reEncryptDbAndKeystore ^js (status) key-uid current-password# new-password# callback)))
(defn convert-to-keycard-account
[{:keys [key-uid] :as multiaccount-data} settings current-password# new-password callback]
(log/debug "[native-module] convert-to-keycard-account")
(.convertToKeycardAccount ^js (status)
key-uid
(types/clj->json multiaccount-data)
(types/clj->json settings)
current-password#
new-password
callback))

View File

@ -3,6 +3,7 @@
[re-frame.core :as re-frame]
[status-im.ui.screens.home.sheet.views :as home.sheet]
[status-im.ui.screens.keycard.views :as keycard]
[status-im.ui.screens.multiaccounts.key-storage.views :as key-storage]
[status-im.ui.screens.about-app.views :as about-app]
[status-im.ui.screens.multiaccounts.recover.views :as recover.views]
[quo.core :as quo]))
@ -33,7 +34,10 @@
(merge about-app/learn-more)
(= view :recover-sheet)
(merge recover.views/bottom-sheet))]
(merge recover.views/bottom-sheet)
(= view :migrate-account-password)
(merge key-storage/migrate-account-password))]
[quo/bottom-sheet opts
(when content
[content])]))

View File

@ -24,3 +24,11 @@
{:color colors/gray
:text-align :center
:line-height 22})
(def header
{:flex-direction :row
:align-items :center
:justify-content :space-between
:padding-top 16
:padding-left 16
:margin-bottom 11})

View File

@ -55,7 +55,7 @@
;; Component to render Key and Storage management screen
(defview actions-base [{:keys [next-title next-event]}]
(letsubs [{:keys [name] :as multiaccount} [:multiaccounts/login]
{:keys [move-keystore-checked?]} [:multiaccounts/key-storage]]
{:keys [move-keystore-checked? reset-db-checked?]} [:multiaccounts/key-storage]]
[react/view {:flex 1}
[local-topbar (i18n/label :t/choose-actions)]
[accordion/section {:title name
@ -77,8 +77,8 @@
[quo/list-item {:title (i18n/label :t/reset-database)
:subtitle (i18n/label :t/reset-database-warning)
:subtitle-max-lines 4
:disabled true
:active move-keystore-checked?
:active reset-db-checked?
:on-press #(re-frame/dispatch [::multiaccounts.key-storage/reset-db-checked (not reset-db-checked?)])
:accessory :checkbox}]]
(when (and next-title next-event)
[toolbar/toolbar {:show-border? true
@ -195,9 +195,37 @@
:right [quo/button
{:type :secondary
:disabled (not keycard-storage-selected?)
:on-press #(re-frame/dispatch [::multiaccounts.key-storage/show-transfer-warning-popup])}
:on-press #(re-frame/dispatch [::multiaccounts.key-storage/storage-selected])}
(i18n/label :t/confirm)]}]]]))
(defview migrate-account-password-view []
(letsubs [{:keys [migration-password-error migration-password-valid?]} [:keycard]]
[react/view {:flex 1}
[react/view styles/header
[react/text {:style {:typography :title-bold}} (i18n/label :t/enter-password)]
[react/view {:padding-horizontal 24}
[quo/button {:type :secondary
:on-press #(re-frame/dispatch [::multiaccounts.key-storage/skip-password-pressed])}
(i18n/label :t/skip)]]]
[quo/separator]
[react/view {:padding-horizontal 16 :padding-vertical 12}
[react/text {:style {:margin-top 6 :margin-bottom 12 :color colors/gray}} (i18n/label :t/enter-password-migration-prompt)]
[quo/text-input
{:secure-text-entry true
:placeholder (i18n/label :t/current-password)
:on-change-text #(re-frame/dispatch [::multiaccounts.key-storage/password-changed (status-im.utils.security/mask-data %)])
:accessibility-label :enter-password-input
:auto-capitalize :none
:error migration-password-error
:show-cancel false}]]
[react/view {:padding-horizontal 16 :padding-vertical 12}
[quo/button {:on-press #(re-frame/dispatch [::multiaccounts.key-storage/verify-password])
:disabled (not migration-password-valid?)}
(i18n/label :t/confirm)]]]))
(def migrate-account-password
{:content migrate-account-password-view})
(defview seed-key-uid-mismatch-popover []
(letsubs [{:keys [name]} [:multiaccounts/login]
{logged-in-name :name} [:multiaccount]]

View File

@ -2,7 +2,7 @@
"_comment": "DO NOT EDIT THIS FILE BY HAND. USE 'scripts/update-status-go.sh <tag>' instead",
"owner": "status-im",
"repo": "status-go",
"version": "v0.82.0",
"commit-sha1": "2f6b32b1f54de5802882c0177bb0b16924d04271",
"src-sha256": "1nlslsbibwnb7s6mbxqsijxrsw3lrs1dqvqy6bhfxsvwi5x97a28"
"version": "v0.83.0",
"commit-sha1": "6c2e9652d0e77a732af8649a1a75961ac9ac820f",
"src-sha256": "14fhj54d46xv3vh5qkbryxq7hdjrp13pabz8012qa592i0lf72fb"
}

View File

@ -521,6 +521,10 @@
"pair-code-placeholder": "Pair code...",
"enter-pair-code-description": "Pairing code can be set from an already paired Status client",
"enter-password": "Enter password",
"enter-password-migration-prompt": "Enter your password to move contacts, chats and settings along with your keys",
"migration-successful": "Migration successful",
"migration-successful-text": "Account succesfully migrated to Keycard",
"skip": "Skip",
"password-placeholder":"Password...",
"confirm-password-placeholder": "Confirm your password...",
"enter-pin": "Enter 6-digit passcode",