mirror of
https://github.com/selfxyz/self.git
synced 2026-02-19 02:24:25 -05:00
SELF-1497: add keychain patch (#1607)
* add keychain patch - wip * centralise useStrongbox flag usage * set allowBackup to false * bump to version 2.9.12 * bump android build for 2.9.12 * improve keychain error detection * Disable Strongbox by default --------- Co-authored-by: Justin Hernandez <justin.hernandez@self.xyz>
This commit is contained in:
@@ -25,7 +25,8 @@
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:roundIcon="@mipmap/ic_launcher"
|
||||
android:extractNativeLibs="false"
|
||||
tools:replace="android:icon, android:roundIcon, android:name, android:extractNativeLibs"
|
||||
android:allowBackup="false"
|
||||
tools:replace="android:icon, android:roundIcon, android:name, android:extractNativeLibs, android:allowBackup"
|
||||
android:theme="@style/AppTheme"
|
||||
android:supportsRtl="true"
|
||||
android:usesCleartextTraffic="false"
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2.9.11</string>
|
||||
<string>2.9.12</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
|
||||
@@ -546,7 +546,7 @@
|
||||
"$(PROJECT_DIR)",
|
||||
"$(PROJECT_DIR)/MoproKit/Libs",
|
||||
);
|
||||
MARKETING_VERSION = 2.9.11;
|
||||
MARKETING_VERSION = 2.9.12;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
@@ -686,7 +686,7 @@
|
||||
"$(PROJECT_DIR)",
|
||||
"$(PROJECT_DIR)/MoproKit/Libs",
|
||||
);
|
||||
MARKETING_VERSION = 2.9.11;
|
||||
MARKETING_VERSION = 2.9.12;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@selfxyz/mobile-app",
|
||||
"version": "2.9.11",
|
||||
"version": "2.9.12",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -7,10 +7,12 @@ import type {
|
||||
ACCESSIBLE,
|
||||
GetOptions,
|
||||
SECURITY_LEVEL,
|
||||
SetOptions,
|
||||
} from 'react-native-keychain';
|
||||
import Keychain from 'react-native-keychain';
|
||||
|
||||
import { useSettingStore } from '@/stores/settingStore';
|
||||
import type { ExtendedSetOptions } from '@/types/react-native-keychain';
|
||||
|
||||
/**
|
||||
* Security configuration for keychain operations
|
||||
*/
|
||||
@@ -23,6 +25,8 @@ export interface AdaptiveSecurityConfig {
|
||||
export interface GetSecureOptions {
|
||||
requireAuth?: boolean;
|
||||
promptMessage?: string;
|
||||
/** Whether to use StrongBox-backed key generation on Android. Default: true */
|
||||
useStrongBox?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -61,7 +65,8 @@ export async function checkPasscodeAvailable(): Promise<boolean> {
|
||||
await Keychain.setGenericPassword('test', 'test', {
|
||||
service: testService,
|
||||
accessible: Keychain.ACCESSIBLE.WHEN_PASSCODE_SET_THIS_DEVICE_ONLY,
|
||||
});
|
||||
useStrongBox: false,
|
||||
} as ExtendedSetOptions);
|
||||
// Clean up test entry
|
||||
await Keychain.resetGenericPassword({ service: testService });
|
||||
return true;
|
||||
@@ -78,7 +83,7 @@ export async function createKeychainOptions(
|
||||
options: GetSecureOptions,
|
||||
capabilities?: SecurityCapabilities,
|
||||
): Promise<{
|
||||
setOptions: SetOptions;
|
||||
setOptions: ExtendedSetOptions;
|
||||
getOptions: GetOptions;
|
||||
}> {
|
||||
const config = await getAdaptiveSecurityConfig(
|
||||
@@ -86,10 +91,14 @@ export async function createKeychainOptions(
|
||||
capabilities,
|
||||
);
|
||||
|
||||
const setOptions: SetOptions = {
|
||||
const useStrongBox =
|
||||
options.useStrongBox ?? useSettingStore.getState().useStrongBox;
|
||||
|
||||
const setOptions: ExtendedSetOptions = {
|
||||
accessible: config.accessible,
|
||||
...(config.securityLevel && { securityLevel: config.securityLevel }),
|
||||
...(config.accessControl && { accessControl: config.accessControl }),
|
||||
useStrongBox,
|
||||
};
|
||||
|
||||
const getOptions: GetOptions = {
|
||||
|
||||
@@ -11,7 +11,7 @@ import React, {
|
||||
useState,
|
||||
} from 'react';
|
||||
import type { StyleProp, TextStyle, ViewStyle } from 'react-native';
|
||||
import { Alert, ScrollView, TouchableOpacity } from 'react-native';
|
||||
import { Alert, Platform, ScrollView, TouchableOpacity } from 'react-native';
|
||||
import { Button, Sheet, Text, XStack, YStack } from 'tamagui';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import type { NativeStackScreenProps } from '@react-navigation/native-stack';
|
||||
@@ -399,6 +399,8 @@ const DevSettingsScreen: React.FC<DevSettingsScreenProps> = ({}) => {
|
||||
const subscribedTopics = useSettingStore(state => state.subscribedTopics);
|
||||
const loggingSeverity = useSettingStore(state => state.loggingSeverity);
|
||||
const setLoggingSeverity = useSettingStore(state => state.setLoggingSeverity);
|
||||
const useStrongBox = useSettingStore(state => state.useStrongBox);
|
||||
const setUseStrongBox = useSettingStore(state => state.setUseStrongBox);
|
||||
const [hasNotificationPermission, setHasNotificationPermission] =
|
||||
useState(false);
|
||||
const paddingBottom = useSafeBottomPadding(20);
|
||||
@@ -754,6 +756,34 @@ const DevSettingsScreen: React.FC<DevSettingsScreenProps> = ({}) => {
|
||||
/>
|
||||
</ParameterSection>
|
||||
|
||||
{Platform.OS === 'android' && (
|
||||
<ParameterSection
|
||||
icon={<BugIcon />}
|
||||
title="Android Keystore"
|
||||
description="Configure keystore security options"
|
||||
>
|
||||
<TopicToggleButton
|
||||
label="Use StrongBox"
|
||||
isSubscribed={useStrongBox}
|
||||
onToggle={() => {
|
||||
Alert.alert(
|
||||
useStrongBox ? 'Disable StrongBox' : 'Enable StrongBox',
|
||||
useStrongBox
|
||||
? 'New keys will be generated without StrongBox hardware backing. Existing keys will continue to work.'
|
||||
: 'New keys will attempt to use StrongBox hardware backing for enhanced security.',
|
||||
[
|
||||
{ text: 'Cancel', style: 'cancel' },
|
||||
{
|
||||
text: useStrongBox ? 'Disable' : 'Enable',
|
||||
onPress: () => setUseStrongBox(!useStrongBox),
|
||||
},
|
||||
],
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</ParameterSection>
|
||||
)}
|
||||
|
||||
<ParameterSection
|
||||
icon={<WarningIcon color={yellow500} />}
|
||||
title="Danger Zone"
|
||||
|
||||
@@ -38,11 +38,13 @@ interface PersistedSettingsState {
|
||||
setSkipDocumentSelectorIfSingle: (value: boolean) => void;
|
||||
setSubscribedTopics: (topics: string[]) => void;
|
||||
setTurnkeyBackupEnabled: (turnkeyBackupEnabled: boolean) => void;
|
||||
setUseStrongBox: (useStrongBox: boolean) => void;
|
||||
skipDocumentSelector: boolean;
|
||||
skipDocumentSelectorIfSingle: boolean;
|
||||
subscribedTopics: string[];
|
||||
toggleCloudBackupEnabled: () => void;
|
||||
turnkeyBackupEnabled: boolean;
|
||||
useStrongBox: boolean;
|
||||
}
|
||||
|
||||
interface NonPersistedSettingsState {
|
||||
@@ -147,6 +149,10 @@ export const useSettingStore = create<SettingsState>()(
|
||||
setSkipDocumentSelectorIfSingle: (value: boolean) =>
|
||||
set({ skipDocumentSelectorIfSingle: value }),
|
||||
|
||||
// StrongBox setting for Android keystore (default: false)
|
||||
useStrongBox: false,
|
||||
setUseStrongBox: (useStrongBox: boolean) => set({ useStrongBox }),
|
||||
|
||||
// Non-persisted state (will not be saved to storage)
|
||||
hideNetworkModal: false,
|
||||
setHideNetworkModal: (hideNetworkModal: boolean) => {
|
||||
|
||||
21
app/src/types/react-native-keychain.d.ts
vendored
Normal file
21
app/src/types/react-native-keychain.d.ts
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
|
||||
|
||||
import type { SetOptions } from 'react-native-keychain';
|
||||
|
||||
/**
|
||||
* Extended SetOptions with useStrongBox property added by our patch.
|
||||
* Use this type when you need the useStrongBox option for Android keystore.
|
||||
*/
|
||||
export type ExtendedSetOptions = SetOptions & {
|
||||
/**
|
||||
* Whether to attempt StrongBox-backed key generation on Android.
|
||||
* When true (default), the library will try to use StrongBox hardware
|
||||
* security module if available, falling back to regular secure hardware.
|
||||
* When false, StrongBox is skipped and regular secure hardware is used directly.
|
||||
* @platform Android
|
||||
* @default true
|
||||
*/
|
||||
useStrongBox?: boolean;
|
||||
};
|
||||
@@ -29,6 +29,8 @@ export function isKeychainCryptoError(error: unknown): boolean {
|
||||
err?.name === 'com.oblador.keychain.exceptions.CryptoFailedException' ||
|
||||
err?.message?.includes('CryptoFailedException') ||
|
||||
err?.message?.includes('Decryption failed') ||
|
||||
err?.message?.includes('Could not encrypt data') ||
|
||||
err?.message?.includes('Keystore operation failed') ||
|
||||
err?.message?.includes('Authentication tag verification failed')) &&
|
||||
!isUserCancellation(error),
|
||||
);
|
||||
@@ -41,6 +43,13 @@ export function isUserCancellation(error: unknown): boolean {
|
||||
err?.code === 'USER_CANCELED' ||
|
||||
err?.message?.includes('User canceled') ||
|
||||
err?.message?.includes('Authentication canceled') ||
|
||||
err?.message?.includes('cancelled by user'),
|
||||
err?.message?.includes('cancelled by user') ||
|
||||
err?.message?.includes('Fingerprint operation cancelled') ||
|
||||
err?.message?.includes('operation cancelled') ||
|
||||
err?.message?.includes("Can't verify face") ||
|
||||
err?.message?.includes('code: 5') || // ERROR_CANCELED
|
||||
err?.message?.includes('code: 2') || // ERROR_UNABLE_TO_PROCESS (biometric verification failed)
|
||||
err?.message?.includes('code: 10') || // ERROR_USER_CANCELED
|
||||
err?.message?.includes('code: 13'), // ERROR_NEGATIVE_BUTTON - for ref (https://developer.android.com/reference/androidx/biometric/BiometricPrompt#ERROR_NEGATIVE_BUTTON())
|
||||
);
|
||||
}
|
||||
|
||||
9048
patches/react-native-keychain+10.0.0.patch
Normal file
9048
patches/react-native-keychain+10.0.0.patch
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user