From fdc8f09dbd4599663a3bfc5808e097ab92e3f734 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Sat, 13 Jul 2024 12:36:43 +0200 Subject: [PATCH 1/3] feat: react native keychain setup Signed-off-by: Timo Glastra --- apps/funke/app.config.js | 5 +- apps/funke/app/_layout.tsx | 4 +- apps/funke/package.json | 3 +- apps/paradym/app.config.js | 5 +- apps/paradym/app/_layout.tsx | 4 +- packages/secure-store/README.md | 19 ++++ packages/secure-store/error/KeychainError.ts | 1 + .../secure-store/error/WalletUnlockError.ts | 1 + packages/secure-store/keychain.ts | 47 +++++++++ packages/secure-store/legacyUnlock.ts | 41 ++++++++ packages/secure-store/package.json | 24 +++++ .../secure-unlock/react-native-argon2.d.ts | 18 ++++ .../secure-store/secure-unlock/saltStore.ts | 35 +++++++ .../secure-unlock/walletKeyDerivation.ts | 29 ++++++ .../secure-unlock/walletKeyStore.ts | 98 +++++++++++++++++++ packages/secure-store/secureUnlock.ts | 47 +++++++++ packages/utils/package.json | 7 +- packages/utils/src/index.ts | 1 - packages/utils/src/walletKeyStore.ts | 51 ---------- pnpm-lock.yaml | 60 ++++++++---- 20 files changed, 415 insertions(+), 85 deletions(-) create mode 100644 packages/secure-store/README.md create mode 100644 packages/secure-store/error/KeychainError.ts create mode 100644 packages/secure-store/error/WalletUnlockError.ts create mode 100644 packages/secure-store/keychain.ts create mode 100644 packages/secure-store/legacyUnlock.ts create mode 100644 packages/secure-store/package.json create mode 100644 packages/secure-store/secure-unlock/react-native-argon2.d.ts create mode 100644 packages/secure-store/secure-unlock/saltStore.ts create mode 100644 packages/secure-store/secure-unlock/walletKeyDerivation.ts create mode 100644 packages/secure-store/secure-unlock/walletKeyStore.ts create mode 100644 packages/secure-store/secureUnlock.ts delete mode 100644 packages/utils/src/walletKeyStore.ts diff --git a/apps/funke/app.config.js b/apps/funke/app.config.js index 5398466d..ba202f67 100644 --- a/apps/funke/app.config.js +++ b/apps/funke/app.config.js @@ -52,13 +52,14 @@ const config = { updates: { fallbackToCacheTimeout: 0, }, - plugins: ['expo-font', 'expo-secure-store', 'expo-router'], + plugins: ['expo-font', 'expo-router'], assetBundlePatterns: ['**/*'], ios: { supportsTablet: false, bundleIdentifier: `id.animo.funke.wallet${variant.bundle}`, infoPlist: { - NSCameraUsageDescription: 'This app uses the camera to scan QR-codes.', + NSCameraUsageDescription: 'Funke Wallet uses the camera to initiate receiving and sharing of credentials.', + NSFaceIDUsageDescription: 'Funke Wallet uses FaceID to securely unlock the wallet and share credentials.', ITSAppUsesNonExemptEncryption: false, // Add schemes for deep linking CFBundleURLTypes: [ diff --git a/apps/funke/app/_layout.tsx b/apps/funke/app/_layout.tsx index cac8a003..96ff2bb3 100644 --- a/apps/funke/app/_layout.tsx +++ b/apps/funke/app/_layout.tsx @@ -9,8 +9,8 @@ import { useFonts, useTransparentNavigationBar, } from '@package/app' +import { getLegacySecureWalletKey } from '@package/secure-store/legacyUnlock' import { Heading, Page, Paragraph, XStack, YStack, config, useToastController } from '@package/ui' -import { getSecureWalletKey } from '@package/utils' import { DefaultTheme, ThemeProvider } from '@react-navigation/native' import { Stack } from 'expo-router' import * as SplashScreen from 'expo-splash-screen' @@ -40,7 +40,7 @@ export default function HomeLayout() { if (agent) return const startAgent = async () => { - const walletKey = await getSecureWalletKey().catch(() => { + const walletKey = await getLegacySecureWalletKey().catch(() => { toast.show('Could not load wallet key from secure storage.') setAgentInitializationFailed(true) }) diff --git a/apps/funke/package.json b/apps/funke/package.json index 70ae84b3..9439f7a3 100644 --- a/apps/funke/package.json +++ b/apps/funke/package.json @@ -33,16 +33,17 @@ "expo-linking": "~6.3.1", "expo-navigation-bar": "~3.0.6", "expo-router": "~3.5.16", - "expo-secure-store": "~13.0.1", "expo-splash-screen": "~0.27.5", "expo-status-bar": "~1.12.1", "expo-system-ui": "~3.0.6", "expo-updates": "~0.25.16", "react": "*", "react-native": "0.74.2", + "react-native-argon2": "^2.0.1", "react-native-fs": "^2.20.0", "react-native-gesture-handler": "~2.16.2", "react-native-get-random-values": "~1.11.0", + "react-native-keychain": "^8.2.0", "react-native-safe-area-context": "4.10.1", "react-native-screens": "~3.31.1", "react-native-svg": "15.2.0" diff --git a/apps/paradym/app.config.js b/apps/paradym/app.config.js index 0da51254..ac6a4e58 100644 --- a/apps/paradym/app.config.js +++ b/apps/paradym/app.config.js @@ -66,13 +66,14 @@ const config = { updates: { fallbackToCacheTimeout: 0, }, - plugins: ['expo-font', 'expo-secure-store'], + plugins: ['expo-font'], assetBundlePatterns: ['**/*'], ios: { supportsTablet: false, bundleIdentifier: `id.paradym.wallet${variant.bundle}`, infoPlist: { - NSCameraUsageDescription: 'This app uses the camera to scan QR-codes.', + NSCameraUsageDescription: 'Paradym Wallet uses the camera to initiate receiving and sharing of credentials.', + NSFaceIDUsageDescription: 'Paradym Wallet uses FaceID to securely unlock the wallet and share credentials.', ITSAppUsesNonExemptEncryption: false, // Add schemes for deep linking CFBundleURLTypes: [ diff --git a/apps/paradym/app/_layout.tsx b/apps/paradym/app/_layout.tsx index 5411aee7..37ea56a2 100644 --- a/apps/paradym/app/_layout.tsx +++ b/apps/paradym/app/_layout.tsx @@ -9,8 +9,8 @@ import { useHasInternetConnection, useTransparentNavigationBar, } from '@package/app' +import { getLegacySecureWalletKey } from '@package/secure-store/legacyUnlock' import { Heading, Page, Paragraph, XStack, YStack, config, useToastController } from '@package/ui' -import { getSecureWalletKey } from '@package/utils' import { DefaultTheme, ThemeProvider } from '@react-navigation/native' import { useFonts } from 'expo-font' import { Stack } from 'expo-router' @@ -60,7 +60,7 @@ export default function HomeLayout() { if (agent) return const startAgent = async () => { - const walletKey = await getSecureWalletKey().catch(() => { + const walletKey = await getLegacySecureWalletKey().catch(() => { toast.show('Could not load wallet key from secure storage.') setAgentInitializationFailed(true) }) diff --git a/packages/secure-store/README.md b/packages/secure-store/README.md new file mode 100644 index 00000000..9e03d402 --- /dev/null +++ b/packages/secure-store/README.md @@ -0,0 +1,19 @@ +
+ Paradym Logo +
+ +

Secure Store

+ +This package contains methods to securely store, derive and retrieve the wallet key. It contains functionality using both React Native Keychain and Expo Secure Store. For this reason, there's no generic package export, but you should import from the specific files to only import the needed dependencies. + +## Using React Native Keychain + +Using `react-native-keychain` is the recommended approach and provides the best security. If you want to use this in an app, you need to make sure `react-native-keychain` and `react-native-argon2` are installed in the app. + +You should import from `@package/secure-store/secureUnlock`. + +## Using Expo Secure Store + +Expo Secure Store was used previously to store the wallet key, however no PIN or integration with device biometrics has been implemented. This is kept as legacy behavior until the Paradym Wallet can be updated to use the new (more secure) approach. You need to make sure `expo-secure-store` is installed in the app. + +You should import from `@package/secure-store/legacyUnlock` \ No newline at end of file diff --git a/packages/secure-store/error/KeychainError.ts b/packages/secure-store/error/KeychainError.ts new file mode 100644 index 00000000..75e79c21 --- /dev/null +++ b/packages/secure-store/error/KeychainError.ts @@ -0,0 +1 @@ +export class KeychainError extends Error {} diff --git a/packages/secure-store/error/WalletUnlockError.ts b/packages/secure-store/error/WalletUnlockError.ts new file mode 100644 index 00000000..5f14db46 --- /dev/null +++ b/packages/secure-store/error/WalletUnlockError.ts @@ -0,0 +1 @@ +export class WalletUnlockError extends Error {} diff --git a/packages/secure-store/keychain.ts b/packages/secure-store/keychain.ts new file mode 100644 index 00000000..b740ee16 --- /dev/null +++ b/packages/secure-store/keychain.ts @@ -0,0 +1,47 @@ +import * as Keychain from 'react-native-keychain' +import { KeychainError } from './error/KeychainError' + +export type KeychainOptions = Omit + +/** + * Store the value with id in the keychain. + * + * @throws {KeychainError} if an unexpected error occurs + */ +export async function storeKeychainItem(id: string, value: string, options: KeychainOptions): Promise { + const result = await Keychain.setGenericPassword(id, value, { + ...options, + service: value, + }).catch((error) => { + throw new KeychainError(`Error storing value for id '${id}' in keychain`, { + cause: error, + }) + }) + + if (!result) { + throw new KeychainError(`Error storing value for id '${id}' in keychain`) + } +} + +/** + * Retrieve a value by id from the keychcain + * + * @returns {string | null} the value or null if it doesn't exist + * @throws {KeychainError} if an unexpected error occurs + */ +export async function getKeychainItemById(id: string, options: KeychainOptions): Promise { + const result = await Keychain.getGenericPassword({ + ...options, + service: id, + }).catch((error) => { + throw new KeychainError(`Error retrieving value with id '${id}' from keychain`, { + cause: error, + }) + }) + + if (!result) { + return null + } + + return result.password +} diff --git a/packages/secure-store/legacyUnlock.ts b/packages/secure-store/legacyUnlock.ts new file mode 100644 index 00000000..8a45c6f6 --- /dev/null +++ b/packages/secure-store/legacyUnlock.ts @@ -0,0 +1,41 @@ +import { ariesAskar } from '@hyperledger/aries-askar-shared' +import * as SecureStore from 'expo-secure-store' + +const STORE_KEY_LEGACY = 'wallet-key' as const +const STORE_KEY_RAW = 'paradym-wallet-key-raw' as const + +function generateNewWalletKey(): string { + return ariesAskar.storeGenerateRawKey({}) +} + +/** + * Retrieve the wallet key from expo secure store. This is the legacy method as we now have a more secure way to unlock the wallet + * using React Native Keychain, protected by biometrics and a deriving the wallet key from a PIN. + * + * This methods allows us to keep the Paradym Wallet as before, until we can update it to also use the PIN and biometric unlock capabilities. + * + * Once we are ready to upgrade we should ask the user to set up a PIN, and derive a new wallet key from it, and rotate the wallet key to this + * new key. That will be able to overwrite both the raw wallet key and derived wallet key methods used in this function. + */ +export async function getLegacySecureWalletKey(): Promise<{ + walletKey: string + keyDerivation: 'raw' | 'derive' +}> { + const secureStoreAvailable = await SecureStore.isAvailableAsync() + if (!secureStoreAvailable) throw new Error('SecureStore is not available on this device.') + + // New method: raw wallet key + let walletKey = await SecureStore.getItemAsync(STORE_KEY_RAW) + if (walletKey) return { walletKey, keyDerivation: 'raw' } + + // TODO: rotate the old wallet key to a new raw key + // Old method: derived wallet key + walletKey = await SecureStore.getItemAsync(STORE_KEY_LEGACY) + if (walletKey) return { walletKey, keyDerivation: 'derive' } + + // No wallet key found, generate new method: raw wallet key + const newWalletKey = generateNewWalletKey() + await SecureStore.setItemAsync(STORE_KEY_RAW, newWalletKey) + + return { walletKey: newWalletKey, keyDerivation: 'raw' } +} diff --git a/packages/secure-store/package.json b/packages/secure-store/package.json new file mode 100644 index 00000000..591f79bc --- /dev/null +++ b/packages/secure-store/package.json @@ -0,0 +1,24 @@ +{ + "name": "@package/secure-store", + "version": "0.0.0", + "private": true, + "peerDependencies": { + "@hyperledger/aries-askar-react-native": "^0.2.0", + "expo-secure-store": "~13.0.1", + "react-native": "0.74.2", + "react-native-argon2": "^2.0.1", + "react-native-get-random-values": "~1.11.0", + "react-native-keychain": "^8.2.0" + }, + "peerDependenciesMeta": { + "expo-secure-store": { + "optional": true + }, + "react-native-keychain": { + "optional": true + }, + "react-native-argon2": { + "optional": true + } + } +} diff --git a/packages/secure-store/secure-unlock/react-native-argon2.d.ts b/packages/secure-store/secure-unlock/react-native-argon2.d.ts new file mode 100644 index 00000000..9eb0c2d6 --- /dev/null +++ b/packages/secure-store/secure-unlock/react-native-argon2.d.ts @@ -0,0 +1,18 @@ +declare module 'react-native-argon2' { + export interface Argon2Config { + iterations?: number + memory?: number + parallelism?: number + hashLength?: number + mode?: string + } + + export interface Argon2Result { + rawHash: string + encodedHash: string + } + + function argon2(password: string, salt: string, config: Argon2Config): Promise + + export default argon2 +} diff --git a/packages/secure-store/secure-unlock/saltStore.ts b/packages/secure-store/secure-unlock/saltStore.ts new file mode 100644 index 00000000..fb9d3a41 --- /dev/null +++ b/packages/secure-store/secure-unlock/saltStore.ts @@ -0,0 +1,35 @@ +import * as Keychain from 'react-native-keychain' +import { type KeychainOptions, getKeychainItemById, storeKeychainItem } from '../keychain' + +const saltStoreBaseOptions: KeychainOptions = { + /* Salt can be accessed on this device */ + accessible: Keychain.ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY, +} + +const SALT_ID = (version: number) => `PARADYM_WALLET_SALT_${version}` + +/** + * Store the salt in the keychain. + * + * @throws {KeychainError} if an unexpected error occurs + */ +export async function storeSalt(salt: string, version = 1): Promise { + const saltId = SALT_ID(version) + + await storeKeychainItem(saltId, salt, saltStoreBaseOptions) +} + +/** + * Retrieve the salt from the keychain. + * + * @returns {string | null} the salt or null if it doesn't exist + * @throws {KeychainError} if an unexpected error occurs + */ +export async function getSalt(version = 1): Promise { + const saltId = SALT_ID(version) + + // TODO: should probably throw error if not found + const salt = await getKeychainItemById(saltId, saltStoreBaseOptions) + + return salt +} diff --git a/packages/secure-store/secure-unlock/walletKeyDerivation.ts b/packages/secure-store/secure-unlock/walletKeyDerivation.ts new file mode 100644 index 00000000..aa183c0b --- /dev/null +++ b/packages/secure-store/secure-unlock/walletKeyDerivation.ts @@ -0,0 +1,29 @@ +import argon2 from 'react-native-argon2' + +/** + * Derive key from pin and salt. + * + * Configuration based on recommended parameters defined in RFC 9106 + * @see https://www.rfc-editor.org/rfc/rfc9106.html#name-parameter-choice + */ +export const deriveWalletKey = async (pin: string, salt: string) => { + const { rawHash } = await argon2(pin, salt, { + hashLength: 32, + mode: 'argon2id', + parallelism: 4, + iterations: 1, + memory: 21, + }) + + return rawHash +} + +/** + * Generate 32 byte key crypto getRandomValues. + * + * @see https://github.com/LinusU/react-native-get-random-values + * @see https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues + */ +export function generateSalt(): string { + return crypto.getRandomValues(new Uint8Array(32)).join('') +} diff --git a/packages/secure-store/secure-unlock/walletKeyStore.ts b/packages/secure-store/secure-unlock/walletKeyStore.ts new file mode 100644 index 00000000..3a22842e --- /dev/null +++ b/packages/secure-store/secure-unlock/walletKeyStore.ts @@ -0,0 +1,98 @@ +import { Platform } from 'react-native' +import * as Keychain from 'react-native-keychain' +import { type KeychainOptions, getKeychainItemById, storeKeychainItem } from '../keychain' + +const walletKeyStoreBaseOptions: KeychainOptions = { + /* Only allow the current set of enrolled biometrics to access the wallet key */ + accessControl: Keychain.ACCESS_CONTROL.BIOMETRY_CURRENT_SET, + + // TODO: might want to use WHEN_UNLOCKED_THIS_DEVICE_ONLY, to allow devices without a passcode (as we have our own passcode and extra biometrics check) + // Not sure how it works if you have no passcode, but you do have biometrics (i think that is not possible?) + /* Only allow access to the wallet key on the device is was created on, is unlocked, and has a passcode set */ + accessible: Keychain.ACCESSIBLE.WHEN_PASSCODE_SET_THIS_DEVICE_ONLY, + + // TODO: internationalization + authenticationPrompt: { + title: 'Unlock wallet', + description: 'Access to your wallet is locked behind a biometric verification.', + }, + + /* Android Only. Ensure wallet key is protected by hardware. Wil results in error if hardware is not available. Hardware is either StrongBox or TEE */ + securityLevel: Keychain.SECURITY_LEVEL.SECURE_HARDWARE, + + /* Android Only. Ensure wallet key is protected using RSA storage type, this is the only storage type supporting biometrics. */ + storage: Keychain.STORAGE_TYPE.RSA, + + /* Ensure wallet key is protected by biometrics. It is not possible to fallback to the device passcode if the biometric authentication failed. */ + authenticationType: Keychain.AUTHENTICATION_TYPE.BIOMETRICS, +} + +const WALLET_KEY_ID = (version: number) => `PARADYM_WALLET_KEY_${version}` + +/** + * Returns whether biometry backed wallet key can be used. Can be called before trying to access + * or store the wallet key in the keychain. + */ +export async function canUseBiometryBackedWalletKey(): Promise { + if (Platform.OS === 'android') { + /** + * `setUserAuthenticationParameters` is only available on Android API 30+, and is needed to ensure + * the key can only be accessed using biometry. React Native Keychain will fallback to allowing keys + * to be accessed by the device passcode. For this reason we only allow biometry to be used on devices + * running Android API 30 or higher. + */ + if (Platform.Version < 30) { + return false + } + + /** + * Android Only API. We only allow hardware secured key storage for unlocking with biometrics + */ + const securityLevel = await Keychain.getSecurityLevel(walletKeyStoreBaseOptions) + if (!securityLevel || securityLevel !== Keychain.SECURITY_LEVEL.SECURE_HARDWARE) { + return false + } + } + + if (Platform.OS === 'ios') { + /** + * Checks whether the key can be authenticated using only biometrics (no passcode fallback) + */ + const canUseAuthentication = await Keychain.canImplyAuthentication(walletKeyStoreBaseOptions) + if (!canUseAuthentication) return false + } + + /** + * We only support biometrics secured storage of the wallet key + */ + const supportedBiometryType = await Keychain.getSupportedBiometryType(walletKeyStoreBaseOptions) + if (!supportedBiometryType) { + return false + } + + return true +} + +/** + * Store the wallet key in hardware backed, biometric protected storage. + * + * Will use Secure Enclave on iOS and StrongBox/TEE on Android. If biometrics is not enabled/available, + * or when hardware backed storage. + * + * @throws {KeychainError} if an unexpected error occurs + */ +export async function storeWalletKey(walletKey: string, version = 1): Promise { + const walletKeyId = WALLET_KEY_ID(version) + await storeKeychainItem(walletKeyId, walletKey, walletKeyStoreBaseOptions) +} + +/** + * Retrieve the wallet key from hardware backed, biometric protected storage. + * + * @returns {string | null} the wallet key or null if it doesn't exist + * @throws {KeychainError} if an unexpected error occurs + */ +export async function getWalletKeyUsingBiometrics(version = 1): Promise { + const walletKeyId = WALLET_KEY_ID(version) + return await getKeychainItemById(walletKeyId, walletKeyStoreBaseOptions) +} diff --git a/packages/secure-store/secureUnlock.ts b/packages/secure-store/secureUnlock.ts new file mode 100644 index 00000000..8ab78197 --- /dev/null +++ b/packages/secure-store/secureUnlock.ts @@ -0,0 +1,47 @@ +import { WalletUnlockError } from './error/WalletUnlockError' +import { getSalt, storeSalt } from './secure-unlock/saltStore' +import { deriveWalletKey, generateSalt } from './secure-unlock/walletKeyDerivation' +import { + canUseBiometryBackedWalletKey, + getWalletKeyUsingBiometrics, + storeWalletKey, +} from './secure-unlock/walletKeyStore' + +// TODO: how to version? +const version = 1 + +export { canUseBiometryBackedWalletKey } + +export async function enableBiometricsForWalletKey(walletKey: string) { + await storeWalletKey(walletKey, version) +} + +/** + * Generate and store a salt. Optionally returning the existing one if it exists + */ +export async function createSaltForPin(returnExisting = false) { + if (returnExisting) { + const existingSalt = await getSalt() + if (existingSalt) return existingSalt + } + + const salt = generateSalt() + await storeSalt(salt, version) + + return salt +} + +export async function getWalletKeyUsingPin(pin: string) { + const salt = await getSalt(version) + if (!salt) { + throw new WalletUnlockError('Error unlocking wallet. No salt configured') + } + + const walletKey = await deriveWalletKey(pin, salt) + return walletKey +} + +export async function tryUnlockingWalletUsingBiometrics() { + const walletKey = await getWalletKeyUsingBiometrics(version) + return walletKey +} diff --git a/packages/utils/package.json b/packages/utils/package.json index 3bbcff29..76490d89 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -2,10 +2,5 @@ "name": "@package/utils", "version": "0.0.0", "private": true, - "main": "src/index.ts", - "devDependencies": { - "@credo-ts/react-native": "0.5.1-alpha.51", - "@hyperledger/aries-askar-react-native": "^0.2.0", - "expo-secure-store": "~13.0.1" - } + "main": "src/index.ts" } diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index d34f8f31..a1025d7f 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -1,3 +1,2 @@ export * from './format' export * from './url' -export * from './walletKeyStore' diff --git a/packages/utils/src/walletKeyStore.ts b/packages/utils/src/walletKeyStore.ts deleted file mode 100644 index 71f0b9a2..00000000 --- a/packages/utils/src/walletKeyStore.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { agentDependencies } from '@credo-ts/react-native' -import { ariesAskar } from '@hyperledger/aries-askar-react-native' -import * as SecureStore from 'expo-secure-store' - -const STORE_KEY_LEGACY = 'wallet-key' as const -const STORE_KEY_RAW = 'paradym-wallet-key-raw' as const - -const generateNewWalletKey = (): { walletKey: string; keyDerivation: 'raw' } => { - return { walletKey: ariesAskar.storeGenerateRawKey({}), keyDerivation: 'raw' } -} - -export const getSecureWalletKey = async (): Promise<{ - walletKey: string - keyDerivation: 'raw' | 'derive' -}> => { - const secureStoreAvailable = await SecureStore.isAvailableAsync() - if (!secureStoreAvailable) throw new Error('SecureStore is not available on this device.') - - // New method: raw wallet key - let walletKey = await SecureStore.getItemAsync(STORE_KEY_RAW) - // Fix for a period when the key was stored as 'raw' so it has to be regenerated - if (walletKey === 'raw') { - await fixInvalidWalletKey() - walletKey = null - } - if (walletKey) return { walletKey, keyDerivation: 'raw' } - - // TODO: rotate the old wallet key to a new raw key - // Old method: derived wallet key - walletKey = await SecureStore.getItemAsync(STORE_KEY_LEGACY) - if (walletKey) return { walletKey, keyDerivation: 'derive' } - - // No wallet key found, generate new method: raw wallet key - const newWalletKey = generateNewWalletKey() - await SecureStore.setItemAsync(STORE_KEY_RAW, newWalletKey.walletKey) - - return newWalletKey -} - -/** - * Fix for a period when the key was stored as 'raw' so the whole wallet needs to be regenerated because we don't have the original key anymore. - */ -const fixInvalidWalletKey = async () => { - const fileSystem = new agentDependencies.FileSystem() - - const walletPath = `${fileSystem.dataPath}/wallet` - - if (!(await fileSystem.exists(walletPath))) return - - await fileSystem.delete(walletPath) -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8bdc9ba2..000b75fe 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -96,9 +96,6 @@ importers: expo-router: specifier: ~3.5.16 version: 3.5.16(expo-constants@16.0.2(expo@51.0.12(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))))(expo-linking@6.3.1(expo@51.0.12(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))))(expo-modules-autolinking@1.11.1)(expo-status-bar@1.12.1)(expo@51.0.12(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7)))(react-native-safe-area-context@4.10.1(react-native@0.74.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0))(react-native-screens@3.31.1(react-native@0.74.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0))(react-native@0.74.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)(typescript@5.3.3) - expo-secure-store: - specifier: ~13.0.1 - version: 13.0.1(expo@51.0.12(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))) expo-splash-screen: specifier: ~0.27.5 version: 0.27.5(expo-modules-autolinking@1.11.1)(expo@51.0.12(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))) @@ -117,6 +114,9 @@ importers: react-native: specifier: 0.74.2 version: 0.74.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(@types/react@18.2.79)(react@18.2.0) + react-native-argon2: + specifier: ^2.0.1 + version: 2.0.1 react-native-fs: specifier: ^2.20.0 version: 2.20.0(react-native@0.74.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(@types/react@18.2.79)(react@18.2.0)) @@ -126,6 +126,9 @@ importers: react-native-get-random-values: specifier: ~1.11.0 version: 1.11.0(react-native@0.74.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(@types/react@18.2.79)(react@18.2.0)) + react-native-keychain: + specifier: ^8.2.0 + version: 8.2.0 react-native-safe-area-context: specifier: 4.10.1 version: 4.10.1(react-native@0.74.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0) @@ -219,7 +222,7 @@ importers: version: 3.5.16(expo-constants@16.0.2(expo@51.0.12(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))))(expo-linking@6.3.1(expo@51.0.12(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))))(expo-modules-autolinking@1.11.1)(expo-status-bar@1.12.1)(expo@51.0.12(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7)))(react-native-safe-area-context@4.10.1(react-native@0.74.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0))(react-native-screens@3.31.1(react-native@0.74.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0))(react-native@0.74.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)(typescript@5.3.3) expo-secure-store: specifier: ~13.0.1 - version: 13.0.1(expo@51.0.12(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))) + version: 13.0.2(expo@51.0.12(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))) expo-splash-screen: specifier: ~0.27.5 version: 0.27.5(expo-modules-autolinking@1.11.1)(expo@51.0.12(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))) @@ -413,6 +416,27 @@ importers: specifier: 0.74.2 version: 0.74.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(@types/react@18.2.79)(react@18.2.0) + packages/secure-store: + dependencies: + '@hyperledger/aries-askar-react-native': + specifier: ^0.2.0 + version: 0.2.1(react-native@0.74.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0) + expo-secure-store: + specifier: ~13.0.1 + version: 13.0.2(expo@51.0.12(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))) + react-native: + specifier: 0.74.2 + version: 0.74.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(@types/react@18.2.79)(react@18.2.0) + react-native-argon2: + specifier: ^2.0.1 + version: 2.0.1 + react-native-get-random-values: + specifier: ~1.11.0 + version: 1.11.0(react-native@0.74.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(@types/react@18.2.79)(react@18.2.0)) + react-native-keychain: + specifier: ^8.2.0 + version: 8.2.0 + packages/ui: dependencies: '@tamagui/animations-react-native': @@ -441,17 +465,7 @@ importers: specifier: ^1.91.4 version: 1.100.3 - packages/utils: - devDependencies: - '@credo-ts/react-native': - specifier: 0.5.1-alpha.51 - version: 0.5.1-alpha.51(expo@51.0.12(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7)))(react-native-fs@2.20.0(react-native@0.74.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(@types/react@18.2.79)(react@18.2.0)))(react-native-get-random-values@1.11.0(react-native@0.74.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(@types/react@18.2.79)(react@18.2.0)))(react-native@0.74.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(@types/react@18.2.79)(react@18.2.0))(web-streams-polyfill@3.3.3) - '@hyperledger/aries-askar-react-native': - specifier: ^0.2.0 - version: 0.2.1(react-native@0.74.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0) - expo-secure-store: - specifier: ~13.0.1 - version: 13.0.1(expo@51.0.12(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))) + packages/utils: {} packages: @@ -3891,8 +3905,8 @@ packages: react-native-reanimated: optional: true - expo-secure-store@13.0.1: - resolution: {integrity: sha512-5DTKjbv98X7yPbm+1jER/sOEIlt2Ih7qwabTvkWDXry5bPcQGoulxH5zIX9+JvVH7of8GI4t7NSEbpAO3P7FZA==} + expo-secure-store@13.0.2: + resolution: {integrity: sha512-3QYgoneo8p8yeeBPBiAfokNNc2xq6+n8+Ob4fAlErEcf4H7Y72LH+K/dx0nQyWau2ZKZUXBxyyfuHFyVKrEVLg==} peerDependencies: expo: '*' @@ -5511,6 +5525,9 @@ packages: react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + react-native-argon2@2.0.1: + resolution: {integrity: sha512-/iOi0S+VVgS1gQGtQgL4ZxUVS4gz6Lav3bgIbtNmr9KbOunnBYzP6/yBe/XxkbpXvasHDwdQnuppOH/nuOBn7w==} + react-native-fs@2.20.0: resolution: {integrity: sha512-VkTBzs7fIDUiy/XajOSNk0XazFE9l+QlMAce7lGuebZcag5CnjszB+u4BdqzwaQOdcYb5wsJIsqq4kxInIRpJQ==} peerDependencies: @@ -5536,6 +5553,9 @@ packages: peerDependencies: react: 18.2.0 + react-native-keychain@8.2.0: + resolution: {integrity: sha512-SkRtd9McIl1Ss2XSWNLorG+KMEbgeVqX+gV+t3u1EAAqT8q2/OpRmRbxpneT2vnb/dMhiU7g6K/pf3nxLUXRvA==} + react-native-safe-area-context@4.10.1: resolution: {integrity: sha512-w8tCuowDorUkPoWPXmhqosovBr33YsukkwYCDERZFHAxIkx6qBadYxfeoaJ91nCQKjkNzGrK5qhoNOeSIcYSpA==} peerDependencies: @@ -11764,7 +11784,7 @@ snapshots: - supports-color - typescript - expo-secure-store@13.0.1(expo@51.0.12(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))): + expo-secure-store@13.0.2(expo@51.0.12(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))): dependencies: expo: 51.0.12(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7)) @@ -13548,6 +13568,8 @@ snapshots: react-is@18.3.1: {} + react-native-argon2@2.0.1: {} + react-native-fs@2.20.0(react-native@0.74.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(@types/react@18.2.79)(react@18.2.0)): dependencies: base-64: 0.1.0 @@ -13576,6 +13598,8 @@ snapshots: react-fast-compare: 3.2.2 shallowequal: 1.1.0 + react-native-keychain@8.2.0: {} + react-native-safe-area-context@4.10.1(react-native@0.74.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0): dependencies: react: 18.2.0 From 6e176289883a9b8f8f32c96ba35e8cbd89ef352d Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Tue, 16 Jul 2024 11:32:11 +0200 Subject: [PATCH 2/3] Update packages/secure-store/secure-unlock/walletKeyStore.ts Co-authored-by: Berend Sliedrecht <61358536+berendsliedrecht@users.noreply.github.com> --- packages/secure-store/secure-unlock/walletKeyStore.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/secure-store/secure-unlock/walletKeyStore.ts b/packages/secure-store/secure-unlock/walletKeyStore.ts index 3a22842e..8b60ccdd 100644 --- a/packages/secure-store/secure-unlock/walletKeyStore.ts +++ b/packages/secure-store/secure-unlock/walletKeyStore.ts @@ -65,12 +65,7 @@ export async function canUseBiometryBackedWalletKey(): Promise { /** * We only support biometrics secured storage of the wallet key */ - const supportedBiometryType = await Keychain.getSupportedBiometryType(walletKeyStoreBaseOptions) - if (!supportedBiometryType) { - return false - } - - return true + return Keychain.getSupportedBiometryType(walletKeyStoreBaseOptions) } /** From e4e8b8c7cef5a48dc92f7fb487193e78482f3901 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Thu, 25 Jul 2024 13:30:48 +0200 Subject: [PATCH 3/3] fix Signed-off-by: Timo Glastra --- packages/secure-store/secure-unlock/walletKeyStore.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/secure-store/secure-unlock/walletKeyStore.ts b/packages/secure-store/secure-unlock/walletKeyStore.ts index 8b60ccdd..4b56fefd 100644 --- a/packages/secure-store/secure-unlock/walletKeyStore.ts +++ b/packages/secure-store/secure-unlock/walletKeyStore.ts @@ -62,10 +62,12 @@ export async function canUseBiometryBackedWalletKey(): Promise { if (!canUseAuthentication) return false } + const supportedBiometryType = await Keychain.getSupportedBiometryType(walletKeyStoreBaseOptions) + /** * We only support biometrics secured storage of the wallet key */ - return Keychain.getSupportedBiometryType(walletKeyStoreBaseOptions) + return supportedBiometryType !== null } /**