From ded2992e44ffc4ec8f19ea9aba6d8c4c3984163d Mon Sep 17 00:00:00 2001 From: zeppi Date: Sun, 18 Sep 2022 14:12:32 -0400 Subject: [PATCH] wip --- electron/index.js | 39 ++++ electron/sync/package.json | 12 ++ electron/sync/sync.js | 58 ++++++ electron/sync/testsync.js | 58 ++++++ package.json | 1 + static/app-strings.json | 16 ++ ui/component/router/view.jsx | 3 +- ui/component/settingSystem/index.js | 2 + ui/component/settingSystem/view.jsx | 27 ++- ui/constants/action_types.js | 6 + ui/constants/modal_types.js | 2 + ui/constants/pageTitles.js | 1 + ui/constants/pages.js | 1 + ui/lbrysync.js | 114 +++++++---- ui/page/settingsSync/index.js | 61 ++++++ ui/page/settingsSync/view.jsx | 296 ++++++++++++++++++++++++++++ ui/redux/actions/lbrysync.js | 271 ++++++++++++++++++++++++- ui/redux/actions/sync.js | 2 +- ui/redux/reducers/lbrysync.js | 57 +++++- ui/redux/selectors/lbrysync.js | 13 +- ui/util/saved-passwords.js | 15 ++ ui/util/wallet-preferences.js | 32 +++ yarn.lock | 274 +++++++++++++++++++++++-- 23 files changed, 1278 insertions(+), 83 deletions(-) create mode 100644 electron/sync/package.json create mode 100644 electron/sync/sync.js create mode 100644 electron/sync/testsync.js create mode 100644 ui/page/settingsSync/index.js create mode 100644 ui/page/settingsSync/view.jsx create mode 100644 ui/util/wallet-preferences.js diff --git a/electron/index.js b/electron/index.js index db0a27cb8..74866087e 100644 --- a/electron/index.js +++ b/electron/index.js @@ -17,6 +17,7 @@ import installDevtools from './installDevtools'; import fs from 'fs'; import path from 'path'; import { diskSpaceLinux, diskSpaceWindows, diskSpaceMac } from '../ui/util/diskspace'; +import { generateSalt, generateSaltSeed, deriveSecrets, walletHmac } from './sync/sync.js'; const { download } = require('electron-dl'); const mime = require('mime'); @@ -326,6 +327,42 @@ ipcMain.on('get-disk-space', async (event) => { } }); +// Sync cryptography +ipcMain.on('get-salt-seed', () => { + const saltSeed = generateSaltSeed(); + rendererWindow.webContents.send('got-salt-seed', saltSeed); +}); + +ipcMain.on('get-secrets', (event, password, email, saltseed) => { + console.log('password, salt', password, email, saltseed); + const callback = (result) => { + console.log('callback result', result); + rendererWindow.webContents.send('got-secrets', result); + }; + deriveSecrets(password, email, saltseed, callback); +}); + +ipcMain.handle('invoke-get-secrets', (event, password, email, saltseed) => { + return new Promise((resolve, reject) => { + const callback = (err, result) => { + console.log('callback result', result); + if (err) { + return reject(err); + } + return resolve(result); + }; + console.log('password, salt', password, email, saltseed); + deriveSecrets(password, email, saltseed, callback); + }); +}); + +ipcMain.handle('invoke-get-salt-seed', (event) => { + return new Promise((resolve, reject) => { + const saltSeed = generateSaltSeed(); + return resolve(saltSeed); + }); +}); + ipcMain.on('version-info-requested', () => { function formatRc(ver) { // Adds dash if needed to make RC suffix SemVer friendly @@ -568,3 +605,5 @@ ipcMain.on('upgrade', (event, installerPath) => { }); app.quit(); }); + + diff --git a/electron/sync/package.json b/electron/sync/package.json new file mode 100644 index 000000000..b26d1b454 --- /dev/null +++ b/electron/sync/package.json @@ -0,0 +1,12 @@ +{ + "name": "test", + "version": "1.0.0", + "description": "", + "main": "testsync.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "type": "module" +} diff --git a/electron/sync/sync.js b/electron/sync/sync.js new file mode 100644 index 000000000..c9cfa64ce --- /dev/null +++ b/electron/sync/sync.js @@ -0,0 +1,58 @@ +import crypto from 'crypto'; + +export function generateSalt(email, seed) { + const hashInput = (email + ':' + seed).toString('utf8'); + const hash = crypto.createHash('sha256'); + hash.update(hashInput); + const hash_output = hash.digest('hex').toString('utf8'); + return hash_output; +} + +export function generateSaltSeed() { + return crypto.randomBytes(32).toString('hex'); +} + +export function createHmac(serverWalletState, hmacKey) { + + return crypto.randomBytes(32).toString('hex'); +} + +export function checkHmac(serverWalletState, hmacKey, hmac) { + return crypto.randomBytes(32).toString('hex'); +} + +export function deriveSecrets(rootPassword, email, saltSeed, callback) { + const encodedPassword = Buffer.from(rootPassword.normalize('NFKC')) + const encodedEmail = Buffer.from(email) + const SCRYPT_N = 1 << 20; + const SCRYPT_R = 8; + const SCRYPT_P = 1; + const KEY_LENGTH = 32; + const NUM_KEYS = 2; + const MAXMEM_MULTIPLIER = 256; + const DEFAULT_MAXMEM = MAXMEM_MULTIPLIER * SCRYPT_N * SCRYPT_R; + + function getKeyParts(key) { + const lbryIdPassword = key.slice(0, KEY_LENGTH).toString('base64'); + const hmacKey = key.slice(KEY_LENGTH).toString('base64'); + return { lbryIdPassword, hmacKey }; // Buffer aa bb cc 6c + } + + const salt = generateSalt(encodedEmail, saltSeed); + + const scryptCallback = (err, key) => { + if (err) { + // handle error + } + + callback(err, getKeyParts(key)); + }; + + crypto.scrypt(encodedPassword, salt, KEY_LENGTH * NUM_KEYS, { N: SCRYPT_N, r: SCRYPT_R, p: SCRYPT_P, maxmem: DEFAULT_MAXMEM }, scryptCallback); +} + +export function walletHmac(inputString) { + const hmac = crypto.createHmac('sha256', inputString.toString('utf8')); + const res = hmac.digest('hex'); + return res; +} diff --git a/electron/sync/testsync.js b/electron/sync/testsync.js new file mode 100644 index 000000000..668fa585f --- /dev/null +++ b/electron/sync/testsync.js @@ -0,0 +1,58 @@ +import test from 'tape'; +// import sync from '../sync.js'; +import { generateSalt, generateSaltSeed, deriveSecrets, walletHmac } from './sync.js'; +export default function doTest () { + + test('Generate sync seed', (assert) => { + const seed = generateSaltSeed(); + console.log('seed', seed); + assert.pass(seed.length === 64); + assert.end(); + }); + + test('Generate salt', (assert) => { + const seed = '80b3aff252588c4097df8f79ad42c8a5cf8d7c9e5339efec8bdb8bc7c5cf25ca'; + const email = 'example@example.com'; + const salt = generateSalt(email, seed); + console.log('salt', salt); + const expected = 'be6074a96f3ce812ea51b95d48e4b10493e14f6a329df7c0816018c6239661fe'; + const actual = salt; + + assert.equal(actual, expected, + 'salt is expected value.'); + assert.end(); + }); + + test('Derive Keys', (assert) => { + const seed = '80b3aff252588c4097df8f79ad42c8a5cf8d7c9e5339efec8bdb8bc7c5cf25ca'; + const email = 'example@example.com'; + + const expectedHmacKey = 'bCxUIryLK0Lf9nKg9yiZDlGleMuGJkadLzTje1PAI+8='; //base64 + const expectedLbryIdPassword = 'HKo/J+x4Hsy2NkMvj2JB9RI0yrvEiB4QSA/NHPaT/cA='; + let result; + + function cb(e, r) { + console.log('result', r) + assert.equal(r.keys.hmacKey, expectedHmacKey, 'hmac is expected value'); + assert.equal(r.keys.lbryIdPassword, expectedLbryIdPassword, 'lbryid password is expected value'); + assert.end(); + } + + deriveSecrets('pass', email, seed, cb); + }); + + test('CreateHmac', (assert) => { + const hmacKey = 'bCxUIryLK0Lf9nKg9yiZDlGleMuGJkadLzTje1PAI+8='; + const sequence = 1; + const walletState = `zo4MTkyOjE2OjE68QlIU76+W91/v/F1tu8h+kGB0Ee`; + const expectedHmacHex = '52edbad5b0f9d8cf6189795702790cc2cb92060be24672913ab3e4b69c03698b'; + + const input_str = `${sequence}:${walletState}`; + const hmacHex = walletHmac(input_str); + assert.equal(hmacHex, expectedHmacHex); + assert.end(); + + }); +} + +doTest() diff --git a/package.json b/package.json index e2cce7d90..04fa54538 100644 --- a/package.json +++ b/package.json @@ -192,6 +192,7 @@ "semver": "^5.3.0", "strip-markdown": "^3.0.3", "style-loader": "^0.23.1", + "tape": "^5.6.0", "terser-webpack-plugin": "^4.2.3", "three-full": "^28.0.2", "unist-util-visit": "^2.0.3", diff --git a/static/app-strings.json b/static/app-strings.json index 2b84ec35a..a05f3edeb 100644 --- a/static/app-strings.json +++ b/static/app-strings.json @@ -2318,5 +2318,21 @@ "Odysee Connect --[Section in Help Page]--": "Odysee Connect", "Your hub has blocked this content because it subscribes to the following blocking channel:": "Your hub has blocked this content because it subscribes to the following blocking channel:", "Your hub has blocked access to this content do to a complaint received under the US Digital Millennium Copyright Act.": "Your hub has blocked access to this content do to a complaint received under the US Digital Millennium Copyright Act.", + "Creator Comment settings": "Creator Comment settings", + "Remote Sync Settings": "Remote Sync Settings", + "Wallet Sync": "Wallet Sync", + "Manage cross device sync for your wallet": "Manage cross device sync for your wallet", + "With your email and wallet encryption password, we will register you with sync. If your wallet is not currentlyencrypted, it will be after this. Bitch.": "With your email and wallet encryption password, we will register you with sync. If your wallet is not currentlyencrypted, it will be after this. Bitch.", + "Password Again": "Password Again", + "Enter Email": "Enter Email", + "Sign up": "Sign up", + "Let's get you set up. This will require you to encrypt your wallet and remember your password.": "Let's get you set up. This will require you to encrypt your wallet and remember your password.", + "Sign in to your sync account. Or %sign_up%.": "Sign in to your sync account. Or %sign_up%.", + "Sign up for a sync account. Or %sign_in%.": "Sign up for a sync account. Or %sign_in%.", + "Verifying": "Verifying", + "We have sent you an email to verify your account.": "We have sent you an email to verify your account.", + "Doing Math": "Doing Math", + "Hold on, doing some math.": "Hold on, doing some math.", + "You are signed in as %email%.": "You are signed in as %email%.", "--end--": "--end--" } diff --git a/ui/component/router/view.jsx b/ui/component/router/view.jsx index 497b76b1a..ddf690de3 100644 --- a/ui/component/router/view.jsx +++ b/ui/component/router/view.jsx @@ -46,6 +46,7 @@ import SearchPage from 'page/search'; import SettingsCreatorPage from 'page/settingsCreator'; import SettingsNotificationsPage from 'page/settingsNotifications'; +import SettingsSyncPage from 'page/settingsSync'; import SettingsPage from 'page/settings'; import ShowPage from 'page/show'; @@ -203,7 +204,7 @@ function AppRouter(props: Props) { {/* Odysee signin */} - + diff --git a/ui/component/settingSystem/index.js b/ui/component/settingSystem/index.js index e25240c23..b2ea039a2 100644 --- a/ui/component/settingSystem/index.js +++ b/ui/component/settingSystem/index.js @@ -6,6 +6,7 @@ import { doNotifyDecryptWallet, doNotifyEncryptWallet, doNotifyForgetPassword, + doOpenModal, doToggle3PAnalytics, } from 'redux/actions/app'; import { doSetDaemonSetting, doClearDaemonSetting, doFindFFmpeg } from 'redux/actions/settings'; @@ -32,6 +33,7 @@ const perform = (dispatch) => ({ updateWalletStatus: () => dispatch(doWalletStatus()), confirmForgetPassword: (modalProps) => dispatch(doNotifyForgetPassword(modalProps)), toggle3PAnalytics: (allow) => dispatch(doToggle3PAnalytics(allow)), + openModal: (modal, props) => dispatch(doOpenModal(modal, props)), }); export default connect(select, perform)(SettingSystem); diff --git a/ui/component/settingSystem/view.jsx b/ui/component/settingSystem/view.jsx index 36033ed23..a27226fd4 100644 --- a/ui/component/settingSystem/view.jsx +++ b/ui/component/settingSystem/view.jsx @@ -18,6 +18,8 @@ import { getPasswordFromCookie } from 'util/saved-passwords'; import * as DAEMON_SETTINGS from 'constants/daemon_settings'; import SettingEnablePrereleases from 'component/settingEnablePrereleases'; import SettingDisableAutoUpdates from 'component/settingDisableAutoUpdates'; +import * as ICONS from 'constants/icons'; +import * as PAGES from 'constants/pages'; const IS_MAC = process.platform === 'darwin'; @@ -148,6 +150,14 @@ export default function SettingSystem(props: Props) { checked={daemonSettings.save_files} /> + +