Skip to content

Commit

Permalink
[wallet]Store encrypted local signing key
Browse files Browse the repository at this point in the history
Store encrypted local signing key, the encryption password is coming
from the Android keystore.
  • Loading branch information
ashishb committed Oct 3, 2019
1 parent 21cbb94 commit 6b587f4
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 6 deletions.
14 changes: 14 additions & 0 deletions packages/mobile/src/web3/saga.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { navigateToError } from 'src/navigator/NavigationService'
import { setLatestBlockNumber, updateWeb3SyncProgress } from 'src/web3/actions'
import {
checkWeb3SyncProgress,
getDecryptedData,
getEncryptedData,
getOrCreateAccount,
SYNC_TIMEOUT,
waitForWeb3Sync,
Expand Down Expand Up @@ -97,3 +99,15 @@ describe(checkWeb3SyncProgress, () => {
.run()
})
})

describe(getEncryptedData, () => {
it('encrypts and decrypts correctly', () => {
const data = 'testing data'
const password = 'a random password'
const encryptedBuffer: Buffer = getEncryptedData(data, password)
console.debug(`Encrypted data is ${encryptedBuffer.toString('hex')}`)
const decryptedData: string = getDecryptedData(encryptedBuffer, password)
console.debug(`Decrypted data is \"${decryptedData}\"`)
expect(decryptedData).toBe(data)
})
})
43 changes: 37 additions & 6 deletions packages/mobile/src/web3/saga.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { deriveCEK } from '@celo/utils/src/commentEncryption'
import { getAccountAddressFromPrivateKey } from '@celo/walletkit'
import * as Crypto from 'crypto'
import { generateMnemonic, mnemonicToSeedHex } from 'react-native-bip39'
import * as RNFS from 'react-native-fs'
import { REHYDRATE } from 'redux-persist/es/constants'
Expand Down Expand Up @@ -203,12 +204,15 @@ function getPrivateKeyFilePath(account: string): string {

function ensureAddressAndKeyMatch(address: string, privateKey: string) {
const generatedAddress = getAccountAddressFromPrivateKey(privateKey)
if (!generatedAddress) {
throw new Error(`Failed to generate address from private key`)
}
if (address.toLowerCase() !== generatedAddress.toLowerCase()) {
throw new Error(
`Address from private key: ${generatedAddress}, ` + `address of sender ${address}`
)
}
console.debug(`signing-utils@ensureCorrectSigner: sender and private key match`)
Logger.debug(TAG + '@ensureAddressAndKeyMatch', `Sender and private key match`)
}

async function savePrivateKeyToLocalDisk(
Expand All @@ -218,9 +222,10 @@ async function savePrivateKeyToLocalDisk(
) {
ensureAddressAndKeyMatch(account, privateKey)
const filePath = getPrivateKeyFilePath(account)
Logger.debug('savePrivateKeyToLocalDisk', `Writing private key to ${filePath}`)
// TODO(ashishb): Store encrypted private key instead
await RNFS.writeFile(getPrivateKeyFilePath(account), privateKey)
const plainTextData = privateKey
const encryptedData: Buffer = getEncryptedData(plainTextData, encryptionPassword)
Logger.debug('savePrivateKeyToLocalDisk', `Writing encrypted private key to ${filePath}`)
await RNFS.writeFile(getPrivateKeyFilePath(account), encryptedData.toString('hex'))
}

// Reads and returns unencrypted private key
Expand All @@ -230,8 +235,34 @@ export async function readPrivateKeyFromLocalDisk(
): Promise<string> {
const filePath = getPrivateKeyFilePath(account)
Logger.debug('readPrivateKeyFromLocalDisk', `Reading private key from ${filePath}`)
// TODO(ashishb): Read and decrypt private key instead
return RNFS.readFile(getPrivateKeyFilePath(account))
const hexEncodedEncryptedData: string = await RNFS.readFile(filePath)
const encryptedDataBuffer: Buffer = new Buffer(hexEncodedEncryptedData, 'hex')
const privateKey: string = getDecryptedData(encryptedDataBuffer, encryptionPassword)
ensureAddressAndKeyMatch(account, privateKey)
return privateKey
}

// Exported for testing
export function getEncryptedData(plainTextData: string, password: string): Buffer {
try {
const cipher = Crypto.createCipher('aes-256-cbc', password)
return Buffer.concat([cipher.update(new Buffer(plainTextData, 'utf8')), cipher.final()])
} catch (e) {
Logger.error(TAG + '@getEncryptedData', 'Failed to write private key', e)
throw e // Re-throw
}
}

// Exported for testing
export function getDecryptedData(encryptedData: Buffer, password: string): string {
try {
const decipher = Crypto.createDecipher('aes-256-cbc', password)
const decrypted = Buffer.concat([decipher.update(encryptedData), decipher.final()])
return decrypted.toString('utf8')
} catch (e) {
Logger.error(TAG + '@getDecryptedData', 'Failed to read private key', e)
throw e // Re-throw
}
}

// Wait for account to exist and then return it
Expand Down

0 comments on commit 6b587f4

Please sign in to comment.