Skip to content

Commit

Permalink
Protect the seed by refactoring the application as two independent pr…
Browse files Browse the repository at this point in the history
…ocesses running on a microkernel.
  • Loading branch information
Empowerful committed Sep 9, 2019
1 parent 1331093 commit 6401225
Show file tree
Hide file tree
Showing 158 changed files with 3,748 additions and 2,429 deletions.
2 changes: 2 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
},
"rules": {
"camelcase": 0,
"no-undef": "error",
"no-unused-vars": "warn",
"generator-star-spacing": ["error", {"before": true, "after": true}],
"jest/no-disabled-tests": 1,
"jest/no-focused-tests": 2,
Expand Down
2 changes: 1 addition & 1 deletion config/env/production.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ module.exports = {
ROOT_URL: 'https://blockchain.info',
THE_PIT_URL: 'https://pit.blockchain.com',
WEB_SOCKET_URL: 'wss://ws.blockchain.info',
WALLET_HELPER_DOMAIN: 'https://wallet-helper.blockchain.info',
WALLET_HELPER_DOMAIN: 'https://wallet-helper.blockchain.com',
VERIFF_URL: 'https://magic.veriff.me'
}
1 change: 0 additions & 1 deletion config/paths.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ const resolveRoot = relativePath => path.resolve(appDirectory, relativePath)
module.exports = {
appBuild: resolveRoot('lib'),
ciBuild: resolveRoot('dist'),
src: resolveRoot('packages/blockchain-wallet-v4-frontend/src'),
pkgJson: resolveRoot('package.json'),
envConfig: resolveRoot('config/env'),
sslConfig: resolveRoot('config/ssl')
Expand Down
53 changes: 32 additions & 21 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,42 +57,49 @@
]
},
"scripts": {
"analyze": "yarn workspace blockchain-wallet-v4-frontend analyze",
"build": "yarn workspace blockchain-wallet-v4-frontend build:dev",
"build:prod": "yarn workspace blockchain-wallet-v4-frontend build:prod",
"ci:compile": "yarn workspace blockchain-wallet-v4-frontend ci:compile",
"analyze": "cross-env-shell ANALYZE=true NODE_ENV=production webpack-cli --config webpack.config.ci.js",
"build:dev": "cross-env-shell NODE_ENV=development webpack-cli --config webpack.config.dev.js --progress --colors",
"build:prod": "cross-env-shell NODE_ENV=production webpack-cli --config webpack.config.dev.js --progress --colors",
"build:staging": "cross-env-shell NODE_ENV=staging webpack-cli --config webpack.config.dev.js --progress --colors",
"build:testnet": "cross-env-shell NODE_ENV=testnet webpack-cli --config webpack.config.dev.js --progress --colors",
"ci:compile": "cross-env-shell NODE_ENV=production webpack-cli --config webpack.config.ci.js --display-error-details",
"ci:coverage:components": "yarn workspace blockchain-info-components ci:coverage:components",
"ci:coverage:core": "yarn workspace blockchain-wallet-v4 ci:coverage:core",
"ci:coverage:frontend": "yarn workspace blockchain-wallet-v4-frontend ci:coverage:frontend",
"ci:coverage:main-process": "yarn workspace main-process ci:coverage:frontend",
"ci:coverage:security-process": "yarn workspace security-process ci:coverage:frontend",
"ci:coverage:report": "istanbul report --root ./coverage --dir ./coverage/ lcov && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js",
"ci:lint": "prettier './packages/*/src/**/*.js' --loglevel error --write && eslint './packages/*/src/**/*.js' --fix && stylelint './packages/*/src/**/*.js'",
"ci:test:build": "yarn wsrun test:build --serial",
"ci:test:core:components": "yarn wsrun ci:test --serial --exclude-missing",
"ci:test:frontend": "yarn workspace blockchain-wallet-v4-frontend ci:test:frontend",
"ci:test:main-process": "yarn workspace main-process ci:test:frontend",
"ci:test:security-process": "yarn workspace security-process ci:test:frontend",
"clean": "cross-env yarn wsrun clean && rimraf build && rimraf coverage && rimraf dist && rimraf *.log && rimraf node_modules",
"coverage": "cross-env rimraf coverage && yarn wsrun coverage --fast-exit && istanbul report --root ./coverage --dir ./coverage/ text-summary html",
"coverage:components": "yarn workspace blockchain-info-components coverage",
"coverage:core": "yarn workspace blockchain-wallet-v4 coverage",
"coverage:frontend": "yarn workspace blockchain-wallet-v4-frontend coverage",
"debug:prod": "yarn workspace blockchain-wallet-v4-frontend debug:prod",
"coverage:main-process": "yarn workspace main-process coverage",
"coverage:security-process": "yarn workspace security-process coverage",
"debug:prod": "cross-env-shell NODE_ENV=production webpack-dev-server --config webpack.debug.js --progress --colors",
"fix": "cross-env yarn prettier && yarn lint:fix && yarn test:components:update && yarn test:frontend:update",
"link:resolved:paths": "yarn wsrun link:resolved:paths --exclude-missing",
"lint": "eslint --cache './packages/*/src/**/*.js'",
"lint:components": "eslint './packages/blockchain-info-components/src/**/*.js'",
"lint:core": "eslint './packages/blockchain-wallet-v4/src/**/*.js'",
"lint:css": "stylelint './packages/*/src/**/*.js'",
"lint:fix": "eslint './packages/*/src/**/*.js' --fix",
"lint:frontend": "eslint './packages/blockchain-wallet-v4-frontend/src/**/*.js'",
"manage:translations": "yarn workspace blockchain-wallet-v4-frontend manage:translations",
"lint:main-process": "eslint './packages/main-process/src/**/*.js'",
"lint:security-process": "eslint './packages/security-process/src/**/*.js'",
"manage:translations": "yarn build:prod && node ./translationRunner.js",
"prettier": "prettier './packages/*/src/**/*.js' --loglevel error --write",
"prettier:components": "prettier './packages/blockchain-info-components/src/**/*.js' --list-different --loglevel error --write",
"prettier:core": "prettier './packages/blockchain-wallet-v4/src/**/*.js' --list-different --loglevel error --write",
"prettier:frontend": "prettier './packages/blockchain-wallet-v4-frontend/src/**/*.js' --list-different --loglevel error --write",
"start": "yarn workspace blockchain-wallet-v4-frontend start:dev",
"start:dev": "yarn workspace blockchain-wallet-v4-frontend start:dev",
"start:prod": "yarn workspace blockchain-wallet-v4-frontend start:prod",
"start:staging": "yarn workspace blockchain-wallet-v4-frontend start:staging",
"start:testnet": "yarn workspace blockchain-wallet-v4-frontend start:testnet",
"prettier:main-process": "prettier './packages/main-process/src/**/*.js' --list-different --loglevel error --write",
"prettier:security-process": "prettier './packages/security-process/src/**/*.js' --list-different --loglevel error --write",
"start": "yarn start:dev",
"start:dev": "cross-env-shell NODE_ENV=development webpack-dev-server --config webpack.config.dev.js --progress --colors --watch --devtool cheap-module-source-map",
"start:prod": "cross-env-shell DISABLE_SSL=true NODE_ENV=production webpack-dev-server --config webpack.config.dev.js --progress --colors --watch --devtool cheap-module-source-map",
"start:staging": "cross-env-shell NODE_ENV=staging webpack-dev-server --config webpack.config.dev.js --progress --colors --watch --devtool cheap-module-source-map",
"start:testnet": "cross-env-shell NODE_ENV=testnet webpack-dev-server --config webpack.config.dev.js --progress --colors --watch --devtool cheap-module-source-map",
"storybook:build": "yarn workspace blockchain-info-components storybook:build",
"storybook:serve": "yarn workspace blockchain-info-components storybook:serve",
"storybook:deploy": "yarn workspace blockchain-info-components storybook:build && yarn workspace blockchain-info-components storybook:deploy",
Expand All @@ -104,10 +111,14 @@
"test:core": "yarn workspace blockchain-wallet-v4 test",
"test:core:debug": "yarn workspace blockchain-wallet-v4 test:debug",
"test:core:watch": "yarn workspace blockchain-wallet-v4 test:watch",
"test:frontend": "yarn workspace blockchain-wallet-v4-frontend test",
"test:frontend:debug": "yarn workspace blockchain-wallet-v4-frontend test:debug",
"test:frontend:update": "yarn workspace blockchain-wallet-v4-frontend test:update",
"test:frontend:watch": "yarn workspace blockchain-wallet-v4-frontend test:watch",
"test:main-process": "yarn workspace main-process test",
"test:main-process:debug": "yarn workspace main-process test:debug",
"test:main-process:update": "yarn workspace main-process test:update",
"test:main-process:watch": "yarn workspace main-process test:watch",
"test:security-process": "yarn workspace security-process test",
"test:security-process:debug": "yarn workspace security-process test:debug",
"test:security-process:update": "yarn workspace security-process test:update",
"test:security-process:watch": "yarn workspace security-process test:watch",
"release": "release-it"
},
"dependencies": {
Expand Down Expand Up @@ -166,7 +177,7 @@
"express": "4.16.4",
"file-loader": "3.0.1",
"generate-changelog": "1.7.1",
"html-webpack-plugin": "3.2.0",
"html-webpack-plugin": "4.0.0-beta.8",
"husky": "2.3.0",
"identity-obj-proxy": "3.0.0",
"istanbul": "github:Xesenix/istanbul",
Expand Down
36 changes: 36 additions & 0 deletions packages/blockchain-wallet-v4/src/SecurityModule/core.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
export default ({ BIP39, Bitcoin, crypto, ed25519, EthHd }) => {
const credentialsEntropy = ({ guid, password, sharedKey }) =>
crypto.sha256(Buffer.from(guid + sharedKey + password))

const entropyToSeed = entropy =>
BIP39.mnemonicToSeed(BIP39.entropyToMnemonic(entropy))

const deriveBIP32KeyFromSeedHex = ({ entropy, network }, path) => {
const seed = entropyToSeed(entropy)

return Bitcoin.HDNode.fromSeedBuffer(seed, network)
.derivePath(path)
.toBase58()
}

// Derivation error using seedHex directly instead of seed derived from
// mnemonic derived from seedHex
const deriveLegacyEthereumKey = ({ entropy }) =>
EthHd.fromMasterSeed(entropy)
.derivePath(`m/44'/60'/0'/0/0`)
.getWallet()
.getPrivateKey()

const deriveSLIP10ed25519Key = async ({ entropy }, path) => {
const seed = entropyToSeed(entropy)
return ed25519.derivePath(path, seed.toString(`hex`))
}

return {
credentialsEntropy,
deriveBIP32KeyFromSeedHex,
deriveLegacyEthereumKey,
deriveSLIP10ed25519Key,
entropyToSeed
}
}
94 changes: 94 additions & 0 deletions packages/blockchain-wallet-v4/src/SecurityModule/core.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import BIP39 from 'bip39'
import Bitcoin from 'bitcoinjs-lib'
import * as ed25519 from 'ed25519-hd-key'
import EthHd from 'ethereumjs-wallet/hdkey'
import * as StellarSdk from 'stellar-sdk'

import Core from './core'
import * as crypto from '../walletCrypto'
import { taskToPromise } from '../utils/functional'

const core = Core({ BIP39, Bitcoin, crypto, ed25519, EthHd, taskToPromise })

it(`generates entropy from the user's credentials`, () => {
expect(
core
.credentialsEntropy({
guid: `50dae286-e42e-4d67-8419-d5dcc563746c`,
password: `password`,
sharedKey: `b91c904b-53ab-44b1-bf79-5b60c018da15`
})
.toString(`base64`)
).toEqual(`jqdTiIA0jYETn9EjAGljE5697lc8kSkxod79srxfLug=`)
})

it(`entropyToSeed`, () => {
expect(
core.entropyToSeed(`713a3ae074e60e56c6bd0557c4984af1`).toString(`base64`)
).toEqual(
`5KWmMucJQ65/B2Wd8TMhYJN/rYJYchakxkMVoPs5SX7koB923atMumgUeXfzoUe2rVhMQYCOgjigf2zEtYLxhg==`
)
})

it(`derives a BIP32 key from seedHex`, async () => {
expect(
await core.deriveBIP32KeyFromSeedHex(
{
network: Bitcoin.networks.bitcoin,
entropy: `713a3ae074e60e56c6bd0557c4984af1`
},
`m/0`
)
).toEqual(
`xprv9vJpjafE9tbBCPBrcv5hBq1tUP4s4d3kZRHewAkGwzjvPZ3Jm8nt9eYwoLUcjnBKdB46WZmzuoEqWLJNB2GwyfShQ1y3Pn7AoVsGYXgzabG`
)
})

// Derivation error using seedHex directly instead of seed derived from
// mnemonic derived from seedHex
it(`derives a legacy Ethereum key from seedHex`, async () => {
expect(
(await core.deriveLegacyEthereumKey({
entropy: `e39c77ed95097f9006c34e1a843aa151`
})).toString(`hex`)
).toEqual(`bb9c3e500b9c41ce9836619fb840436c2d98695d6dc43fb73e6e02df7ee7fc5c`)
})

describe(`derives a SLIP-10 ed25519 key from the seed`, () => {
const testVectors = [
{
seedHex: '713a3ae074e60e56c6bd0557c4984af1',
publicKey: 'GDRXE2BQUC3AZNPVFSCEZ76NJ3WWL25FYFK6RGZGIEKWE4SOOHSUJUJ6',
secret: 'SBGWSG6BTNCKCOB3DIFBGCVMUPQFYPA2G4O34RMTB343OYPXU5DJDVMN'
},
{
seedHex: 'b781c27351c7024355cf7f0b0efdc7f85e046cf9',
publicKey: 'GAVXVW5MCK7Q66RIBWZZKZEDQTRXWCZUP4DIIFXCCENGW2P6W4OA34RH',
secret: 'SAKS7I2PNDBE5SJSUSU2XLJ7K5XJ3V3K4UDFAHMSBQYPOKE247VHAGDB'
},
{
seedHex:
'150df9e3ab10f3f8f1428d723a6539662e181ec8781355396cec5fc2ce08d760',
publicKey: 'GC3MMSXBWHL6CPOAVERSJITX7BH76YU252WGLUOM5CJX3E7UCYZBTPJQ',
secret: 'SAEWIVK3VLNEJ3WEJRZXQGDAS5NVG2BYSYDFRSH4GKVTS5RXNVED5AX7'
},
{
seedHex: '00000000000000000000000000000000',
publicKey: 'GB3JDWCQJCWMJ3IILWIGDTQJJC5567PGVEVXSCVPEQOTDN64VJBDQBYX',
secret: 'SBUV3MRWKNS6AYKZ6E6MOUVF2OYMON3MIUASWL3JLY5E3ISDJFELYBRZ'
}
]

testVectors.forEach(({ publicKey, secret, seedHex }, index) => {
it(`test vector ${index}`, async () => {
const { key } = await core.deriveSLIP10ed25519Key(
{ entropy: Buffer.from(seedHex, `hex`) },
`m/44'/148'/0'`
)

const keypair = StellarSdk.Keypair.fromRawEd25519Seed(key)
expect(keypair.publicKey()).toEqual(publicKey)
expect(keypair.secret()).toEqual(secret)
})
})
})
53 changes: 53 additions & 0 deletions packages/blockchain-wallet-v4/src/SecurityModule/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Functions that require sensitive information to perform (e.g., password,
// seed, and sharedKey). Think of this module as similar to a Hardware Security
// Module.

import BIP39 from 'bip39'
import Bitcoin from 'bitcoinjs-lib'
import * as ed25519 from 'ed25519-hd-key'

import * as selectors from '../redux/wallet/selectors'
import Core from './core'
import * as types from '../types'
import { taskToPromise } from '../utils/functional'
import * as crypto from '../walletCrypto'

const core = Core({ BIP39, Bitcoin, crypto, ed25519 })

export default ({ store }) => {
const getSeedHex = ({ secondPassword }) => {
const state = store.getState()
const wallet = selectors.getWallet(state)
return taskToPromise(types.Wallet.getSeedHex(secondPassword, wallet))
}

const credentialsEntropy = ({ guid, sharedKey }) => {
const state = store.getState()
const password = selectors.getMainPassword(state)
return core.credentialsEntropy({ guid, password, sharedKey })
}

const deriveBIP32Key = async ({ network, secondPassword }, path) => {
const entropy = await getSeedHex({ secondPassword })
return core.deriveBIP32KeyFromSeedHex({ entropy, network }, path)
}

// Derivation error using seedHex directly instead of seed derived from
// mnemonic derived from seedHex
const deriveLegacyEthereumKey = async ({ secondPassword }) => {
const entropy = await getSeedHex({ secondPassword })
return core.deriveLegacyEthereumKey({ entropy })
}

const deriveSLIP10ed25519Key = async ({ secondPassword }, path) => {
const entropy = await getSeedHex({ secondPassword })
return core.deriveSLIP10ed25519Key({ entropy }, path)
}

return {
credentialsEntropy,
deriveBIP32Key,
deriveLegacyEthereumKey,
deriveSLIP10ed25519Key
}
}
5 changes: 4 additions & 1 deletion packages/blockchain-wallet-v4/src/network/api/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ import axios from 'axios'
import queryString from 'query-string'
import { prop, path, pathOr, merge } from 'ramda'

import * as kernel from 'web-microkernel/src'

axios.defaults.withCredentials = false
axios.defaults.timeout = Infinity

export default ({ apiKey }) => {
export default ({ apiKey, imports }) => {
const encodeData = (data, contentType) => {
const defaultData = {
api_code: apiKey,
Expand Down Expand Up @@ -39,6 +41,7 @@ export default ({ apiKey }) => {
...options
}) =>
axios({
adapter: kernel.sanitizeFunction(imports.axios),
url: `${url}${endPoint}`,
method,
data: encodeData(data, contentType),
Expand Down
4 changes: 1 addition & 3 deletions packages/blockchain-wallet-v4/src/network/api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,15 @@ import sfox from './sfox'
import trades from './trades'
import wallet from './wallet'
import xlm from './xlm'
import httpService from './http'
import apiAuthorize from './apiAuthorize'

export default ({
http,
options,
apiKey,
getAuthCredentials,
reauthenticate,
networks
} = {}) => {
const http = httpService({ apiKey })
const authorizedHttp = apiAuthorize(http, getAuthCredentials, reauthenticate)
const apiUrl = options.domains.api
const coinifyUrl = options.domains.coinify
Expand Down
13 changes: 2 additions & 11 deletions packages/blockchain-wallet-v4/src/network/walletApi.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,9 @@ import { futurizeP } from 'futurize'
import createApi from './api'
import * as Coin from '../coinSelection/coin.js'

const createWalletApi = (
{ options, apiKey, getAuthCredentials, reauthenticate, networks } = {},
returnType
) => {
const createWalletApi = (options = {}, returnType) => {
// ////////////////////////////////////////////////////////////////
const ApiPromise = createApi({
options,
apiKey,
getAuthCredentials,
reauthenticate,
networks
})
const ApiPromise = createApi(options)
const eitherToTask = e => e.fold(Task.rejected, Task.of)
const taskToPromise = t =>
new Promise((resolve, reject) => t.fork(reject, resolve))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { takeLatest } from 'redux-saga/effects'
import * as AT from './actionTypes'
import sagas from './sagas'

export default ({ api, networks }) => {
const kvStoreEthSagas = sagas({ api, networks })
export default (...args) => {
const kvStoreEthSagas = sagas(...args)

return function * coreKvStoreEthSaga () {
yield takeLatest(AT.FETCH_METADATA_ETH, kvStoreEthSagas.fetchMetadataEth)
Expand Down

0 comments on commit 6401225

Please sign in to comment.