Make RSA decryption for emails work in react native (#771)

This commit is contained in:
Leendert de Borst
2025-04-22 11:26:19 +02:00
parent c2b313c272
commit 38b3b242d9
9 changed files with 967 additions and 91 deletions

View File

@@ -122,6 +122,8 @@ class EncryptionUtility {
* Generates a new RSA key pair for asymmetric encryption
*/
public static async generateRsaKeyPair(): Promise<{ publicKey: string, privateKey: string }> {
// TODO: ensure the key pair is generated in the correct format where private key is in expected
// JWK format that the WASM app already outputs.
const keyPair = await crypto.subtle.generateKey(
{
name: "RSA-OAEP",

View File

@@ -1,102 +1,253 @@
import { StyleSheet, Image, Platform } from 'react-native';
import { Collapsible } from '@/components/Collapsible';
import { ExternalLink } from '@/components/ExternalLink';
import ParallaxScrollView from '@/components/ParallaxScrollView';
import React, { useEffect, useState, useCallback } from 'react';
import { StyleSheet, View, TouchableOpacity, RefreshControl, ActivityIndicator } from 'react-native';
import { useRouter } from 'expo-router';
import { MailboxEmail } from '@/utils/types/webapi/MailboxEmail';
import { useDb } from '@/context/DbContext';
import { useWebApi } from '@/context/WebApiContext';
import { ThemedText } from '@/components/ThemedText';
import { IconSymbol } from '@/components/ui/IconSymbol';
import { TitleContainer } from '@/components/TitleContainer';
import ParallaxScrollView from '@/components/ParallaxScrollView';
import { IconSymbol } from '@/components/ui/IconSymbol';
import { MailboxBulkRequest, MailboxBulkResponse } from '@/utils/types/webapi/MailboxBulk';
import EncryptionUtility from '@/utils/EncryptionUtility';
export default function TabTwoScreen() {
return (
<ParallaxScrollView
headerBackgroundColor={{ light: '#D0D0D0', dark: '#353636' }}
headerImage={
<IconSymbol
size={310}
color="#808080"
name="chevron.left.forwardslash.chevron.right"
style={styles.headerImage}
/>
}>
<TitleContainer title="Emails" />
<ThemedText>This app includes example code to help you get started.</ThemedText>
<Collapsible title="File-based routing">
<ThemedText>
This app has two screens:{' '}
<ThemedText type="defaultSemiBold">app/(tabs)/index.tsx</ThemedText> and{' '}
<ThemedText type="defaultSemiBold">app/(tabs)/explore.tsx</ThemedText>
</ThemedText>
<ThemedText>
The layout file in <ThemedText type="defaultSemiBold">app/(tabs)/_layout.tsx</ThemedText>{' '}
sets up the tab navigator.
</ThemedText>
<ExternalLink href="https://docs.expo.dev/router/introduction">
<ThemedText type="link">Learn more</ThemedText>
</ExternalLink>
</Collapsible>
<Collapsible title="Android, iOS, and web support">
<ThemedText>
You can open this project on Android, iOS, and the web. To open the web version, press{' '}
<ThemedText type="defaultSemiBold">w</ThemedText> in the terminal running this project.
</ThemedText>
</Collapsible>
<Collapsible title="Images">
<ThemedText>
For static images, you can use the <ThemedText type="defaultSemiBold">@2x</ThemedText> and{' '}
<ThemedText type="defaultSemiBold">@3x</ThemedText> suffixes to provide files for
different screen densities
</ThemedText>
<ExternalLink href="https://reactnative.dev/docs/images">
<ThemedText type="link">Learn more</ThemedText>
</ExternalLink>
</Collapsible>
<Collapsible title="Custom fonts">
<ThemedText>
Open <ThemedText type="defaultSemiBold">app/_layout.tsx</ThemedText> to see how to load{' '}
<ThemedText style={{ fontFamily: 'SpaceMono' }}>
custom fonts such as this one.
// Simple hook for minimum duration loading state
const useMinDurationLoading = (initialState: boolean, minDuration: number): [boolean, (newState: boolean) => void] => {
const [state, setState] = useState(initialState);
const [timer, setTimer] = useState<NodeJS.Timeout | null>(null);
const setStateWithMinDuration = useCallback((newState: boolean) => {
if (newState) {
setState(true);
} else {
if (timer) clearTimeout(timer);
const newTimer = setTimeout(() => setState(false), minDuration);
setTimer(newTimer);
}
}, [minDuration, timer]);
useEffect(() => {
return () => {
if (timer) clearTimeout(timer);
};
}, [timer]);
return [state, setStateWithMinDuration];
};
export default function EmailsScreen() {
const router = useRouter();
const dbContext = useDb();
const webApi = useWebApi();
const [error, setError] = useState<string | null>(null);
const [emails, setEmails] = useState<MailboxEmail[]>([]);
const [isLoading, setIsLoading] = useMinDurationLoading(true, 100);
const [isRefreshing, setIsRefreshing] = useState(false);
const loadEmails = useCallback(async () : Promise<void> => {
try {
setError(null);
if (!dbContext?.sqliteClient) {
return;
}
// Get unique email addresses from all credentials
const emailAddresses = await dbContext.sqliteClient.getAllEmailAddresses();
try {
// For now we only show the latest 50 emails. No pagination.
const data = await webApi.post<MailboxBulkRequest, MailboxBulkResponse>('EmailBox/bulk', {
addresses: emailAddresses,
page: 1,
pageSize: 50,
});
// Decrypt emails locally using private key associated with the email address
const encryptionKeys = await dbContext.sqliteClient.getAllEncryptionKeys();
// Decrypt emails locally using public/private key pairs
const decryptedEmails = await EncryptionUtility.decryptEmailList(data.mails, encryptionKeys);
setEmails(decryptedEmails);
setIsLoading(false);
} catch (error) {
console.error(error);
throw new Error('Failed to load emails');
}
} catch (err) {
setError(err instanceof Error ? err.message : 'An error occurred');
}
}, [dbContext?.sqliteClient, webApi]);
useEffect(() => {
loadEmails();
}, [loadEmails]);
const onRefresh = useCallback(async () => {
setIsRefreshing(true);
await loadEmails();
setIsRefreshing(false);
}, [loadEmails]);
const formatEmailDate = (dateSystem: string): string => {
const now = new Date();
const emailDate = new Date(dateSystem);
const secondsAgo = Math.floor((now.getTime() - emailDate.getTime()) / 1000);
if (secondsAgo < 60) {
return 'just now';
} else if (secondsAgo < 3600) {
const minutes = Math.floor(secondsAgo / 60);
return `${minutes} ${minutes === 1 ? 'min' : 'mins'} ago`;
} else if (secondsAgo < 86400) {
const hours = Math.floor(secondsAgo / 3600);
return `${hours} ${hours === 1 ? 'hr' : 'hrs'} ago`;
} else if (secondsAgo < 172800) {
return 'yesterday';
} else {
return emailDate.toLocaleDateString('en-GB', {
day: '2-digit',
month: '2-digit'
});
}
};
const renderContent = () => {
if (isLoading) {
return (
<View style={styles.centerContainer}>
<ActivityIndicator size="large" />
</View>
);
}
if (error) {
return (
<View style={styles.centerContainer}>
<ThemedText style={styles.errorText}>Error: {error}</ThemedText>
</View>
);
}
if (emails.length === 0) {
return (
<View style={styles.centerContainer}>
<ThemedText style={styles.emptyText}>
You have not received any emails at your private email addresses yet. When you receive a new email, it will appear here.
</ThemedText>
</View>
);
}
return emails.map((email) => (
<TouchableOpacity
key={email.id}
style={styles.emailCard}
onPress={() => router.push(`/emails/${email.id}`)}
>
<View style={styles.emailHeader}>
<ThemedText style={styles.emailSubject} numberOfLines={1}>
{email.subject}
</ThemedText>
<ThemedText style={styles.emailDate}>
{formatEmailDate(email.dateSystem)}
</ThemedText>
</View>
<ThemedText style={styles.emailPreview} numberOfLines={2}>
{email.messagePreview}
</ThemedText>
<ExternalLink href="https://docs.expo.dev/versions/latest/sdk/font">
<ThemedText type="link">Learn more</ThemedText>
</ExternalLink>
</Collapsible>
<Collapsible title="Light and dark mode components">
<ThemedText>
This template has light and dark mode support. The{' '}
<ThemedText type="defaultSemiBold">useColorScheme()</ThemedText> hook lets you inspect
what the user's current color scheme is, and so you can adjust UI colors accordingly.
</ThemedText>
<ExternalLink href="https://docs.expo.dev/develop/user-interface/color-themes/">
<ThemedText type="link">Learn more</ThemedText>
</ExternalLink>
</Collapsible>
<Collapsible title="Animations">
<ThemedText>
This template includes an example of an animated component. The{' '}
<ThemedText type="defaultSemiBold">components/HelloWave.tsx</ThemedText> component uses
the powerful <ThemedText type="defaultSemiBold">react-native-reanimated</ThemedText>{' '}
library to create a waving hand animation.
</ThemedText>
{Platform.select({
ios: (
<ThemedText>
The <ThemedText type="defaultSemiBold">components/ParallaxScrollView.tsx</ThemedText>{' '}
component provides a parallax effect for the header image.
</ThemedText>
),
})}
</Collapsible>
</ParallaxScrollView>
</TouchableOpacity>
));
};
return (
<View style={styles.container}>
<ParallaxScrollView
headerBackgroundColor={{ light: '#D0D0D0', dark: '#353636' }}
headerImage={
<IconSymbol
size={310}
color="#808080"
name="envelope.fill"
style={styles.headerImage}
/>
}
>
<TitleContainer title="Emails" />
{renderContent()}
</ParallaxScrollView>
{isRefreshing && (
<View style={styles.refreshIndicator}>
<ActivityIndicator size="large" />
</View>
)}
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
headerImage: {
color: '#808080',
bottom: -90,
left: -35,
position: 'absolute',
},
centerContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 20,
},
errorText: {
color: '#ef4444',
textAlign: 'center',
},
emptyText: {
textAlign: 'center',
opacity: 0.7,
},
emailCard: {
backgroundColor: '#ffffff',
borderRadius: 8,
padding: 16,
marginHorizontal: 16,
marginBottom: 12,
shadowColor: '#000',
shadowOffset: {
width: 0,
height: 2,
},
shadowOpacity: 0.1,
shadowRadius: 3,
elevation: 3,
},
emailHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'flex-start',
marginBottom: 8,
},
emailSubject: {
flex: 1,
fontSize: 16,
fontWeight: 'bold',
marginRight: 8,
},
emailDate: {
fontSize: 12,
opacity: 0.6,
},
emailPreview: {
fontSize: 14,
opacity: 0.8,
},
refreshIndicator: {
position: 'absolute',
top: 20,
alignSelf: 'center',
},
});

View File

@@ -0,0 +1,290 @@
import React, { useEffect, useState } from 'react';
import { StyleSheet, View, ScrollView, TouchableOpacity, ActivityIndicator, Alert, Share } from 'react-native';
import { useLocalSearchParams, useRouter } from 'expo-router';
import { Email } from '@/utils/types/webapi/Email';
import { useDb } from '@/context/DbContext';
import { useWebApi } from '@/context/WebApiContext';
import { ThemedText } from '@/components/ThemedText';
import { IconSymbol } from '@/components/ui/IconSymbol';
import EncryptionUtility from '@/utils/EncryptionUtility';
import WebView from 'react-native-webview';
import * as FileSystem from 'expo-file-system';
import { EncryptionKey } from '@/utils/types/EncryptionKey';
export default function EmailDetailsScreen() {
const { id } = useLocalSearchParams();
const router = useRouter();
const dbContext = useDb();
const webApi = useWebApi();
const [error, setError] = useState<string | null>(null);
const [email, setEmail] = useState<Email | null>(null);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
loadEmail();
}, [id]);
const loadEmail = async () => {
try {
setIsLoading(true);
setError(null);
if (!dbContext?.sqliteClient || !id) {
return;
}
const response = await webApi.get<Email>(`Email/${id}`);
// Decrypt email locally using public/private key pairs
const encryptionKeys = await dbContext.sqliteClient.getAllEncryptionKeys();
const decryptedEmail = await EncryptionUtility.decryptEmail(response, encryptionKeys);
setEmail(decryptedEmail);
} catch (err) {
setError(err instanceof Error ? err.message : 'An error occurred');
} finally {
setIsLoading(false);
}
};
const handleDelete = async () => {
Alert.alert(
'Delete Email',
'Are you sure you want to delete this email?',
[
{
text: 'Cancel',
style: 'cancel',
},
{
text: 'Delete',
style: 'destructive',
onPress: async () => {
try {
await webApi.delete(`Email/${id}`);
router.back();
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to delete email');
}
},
},
]
);
};
const handleDownloadAttachment = async (attachment: Email['attachments'][0]) => {
try {
// Get the encrypted attachment bytes from the API
const base64EncryptedAttachment = await webApi.downloadBlobAndConvertToBase64(
`Email/${id}/attachments/${attachment.id}`
);
if (!dbContext?.sqliteClient || !email) {
setError('Database context or email not available');
return;
}
// Get encryption keys for decryption
const encryptionKeys = await dbContext.sqliteClient.getAllEncryptionKeys();
// Decrypt the attachment
const decryptedBytes = await EncryptionUtility.decryptAttachment(
base64EncryptedAttachment,
email,
encryptionKeys
);
if (!decryptedBytes) {
setError('Failed to decrypt attachment');
return;
}
// Save to temporary file
const tempFile = `${FileSystem.cacheDirectory}${attachment.filename}`;
await FileSystem.writeAsStringAsync(tempFile, decryptedBytes, {
encoding: FileSystem.EncodingType.Base64,
});
// Share the file using the native share API
await Share.share({
url: tempFile,
title: attachment.filename,
});
// Clean up temp file
await FileSystem.deleteAsync(tempFile);
} catch (err) {
console.error('handleDownloadAttachment error', err);
setError(err instanceof Error ? err.message : 'Failed to download attachment');
}
};
if (isLoading) {
return (
<View style={styles.centerContainer}>
<ActivityIndicator size="large" />
</View>
);
}
if (error) {
return (
<View style={styles.centerContainer}>
<ThemedText style={styles.errorText}>Error: {error}</ThemedText>
</View>
);
}
if (!email) {
return (
<View style={styles.centerContainer}>
<ThemedText style={styles.emptyText}>Email not found</ThemedText>
</View>
);
}
return (
<ScrollView style={styles.container}>
{/* Header */}
<View style={styles.header}>
<View style={styles.headerTop}>
<ThemedText style={styles.subject}>{email.subject}</ThemedText>
<TouchableOpacity onPress={handleDelete} style={styles.deleteButton}>
<IconSymbol name="trash" size={24} color="#ef4444" />
</TouchableOpacity>
</View>
<View style={styles.metadata}>
<ThemedText style={styles.metadataText}>
From: {email.fromDisplay} ({email.fromLocal}@{email.fromDomain})
</ThemedText>
<ThemedText style={styles.metadataText}>
To: {email.toLocal}@{email.toDomain}
</ThemedText>
<ThemedText style={styles.metadataText}>
Date: {new Date(email.dateSystem).toLocaleString()}
</ThemedText>
</View>
</View>
{/* Email Body */}
<View style={styles.body}>
{email.messageHtml ? (
<WebView
style={styles.webView}
source={{ html: email.messageHtml }}
scrollEnabled={false}
onNavigationStateChange={(event) => {
if (event.url !== 'about:blank') {
Share.share({
url: event.url,
});
}
}}
/>
) : (
<ThemedText style={styles.plainText}>{email.messagePlain}</ThemedText>
)}
</View>
{/* Attachments */}
{email.attachments && email.attachments.length > 0 && (
<View style={styles.attachments}>
<ThemedText style={styles.attachmentsTitle}>Attachments</ThemedText>
{email.attachments.map((attachment) => (
<TouchableOpacity
key={attachment.id}
style={styles.attachment}
onPress={() => handleDownloadAttachment(attachment)}
>
<IconSymbol name="paperclip" size={20} color="#666" />
<ThemedText style={styles.attachmentName}>
{attachment.filename} ({Math.ceil(attachment.filesize / 1024)} KB)
</ThemedText>
</TouchableOpacity>
))}
</View>
)}
</ScrollView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
},
centerContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 20,
},
errorText: {
color: '#ef4444',
textAlign: 'center',
},
emptyText: {
textAlign: 'center',
opacity: 0.7,
},
header: {
padding: 16,
borderBottomWidth: 1,
borderBottomColor: '#e5e5e5',
},
headerTop: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'flex-start',
marginBottom: 12,
},
subject: {
fontSize: 20,
fontWeight: 'bold',
flex: 1,
marginRight: 16,
},
deleteButton: {
padding: 8,
},
metadata: {
marginTop: 8,
},
metadataText: {
fontSize: 14,
color: '#666',
marginBottom: 4,
},
body: {
padding: 16,
},
webView: {
minHeight: 200,
},
plainText: {
fontSize: 16,
lineHeight: 24,
},
attachments: {
padding: 16,
borderTopWidth: 1,
borderTopColor: '#e5e5e5',
},
attachmentsTitle: {
fontSize: 18,
fontWeight: 'bold',
marginBottom: 12,
},
attachment: {
flexDirection: 'row',
alignItems: 'center',
padding: 12,
backgroundColor: '#f5f5f5',
borderRadius: 8,
marginBottom: 8,
},
attachmentName: {
marginLeft: 8,
fontSize: 14,
color: '#666',
},
});

View File

@@ -1,11 +1,11 @@
import { useEffect, useRef, useState } from 'react';
import { View, ActivityIndicator } from 'react-native';
import { router } from 'expo-router';
import { useAuth } from '@/context/AuthContext';
import { useDb } from '@/context/DbContext';
import { useVaultSync } from '@/hooks/useVaultSync';
import { ThemedView } from '@/components/ThemedView';
import LoadingIndicator from '@/components/LoadingIndicator';
import { install } from 'react-native-quick-crypto';
export default function InitialLoadingScreen() {
const { isInitialized: isAuthInitialized, isLoggedIn } = useAuth();
@@ -17,6 +17,10 @@ export default function InitialLoadingScreen() {
const isFullyInitialized = isAuthInitialized && dbInitialized;
const requireLoginOrUnlock = isFullyInitialized && (!isLoggedIn || !dbAvailable);
// Install the react-native-quick-crypto library which is used by the EncryptionUtility
// which acts as a drop-in replacement for the subtle crypto API.
install();
useEffect(() => {
async function initialize() {
if (hasInitialized.current) {

View File

@@ -257,6 +257,8 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- Yoga
- ExpoSharing (13.0.1):
- ExpoModulesCore
- ExpoSplashScreen (0.29.22):
- ExpoModulesCore
- ExpoSQLite (15.1.4):
@@ -277,6 +279,7 @@ PODS:
- hermes-engine/Pre-built (= 0.76.9)
- hermes-engine/Pre-built (0.76.9)
- KeychainAccess (4.2.2)
- OpenSSL-Universal (3.3.3001)
- RCT-Folly (2024.10.14.00):
- boost
- DoubleConversion
@@ -1559,6 +1562,29 @@ PODS:
- React-Core
- react-native-get-random-values (1.11.0):
- React-Core
- react-native-quick-crypto (0.7.13):
- DoubleConversion
- glog
- hermes-engine
- OpenSSL-Universal
- RCT-Folly (= 2024.10.14.00)
- RCTRequired
- RCTTypeSafety
- React
- React-Core
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- React-ImageManager
- React-NativeModulesApple
- React-RCTFabric
- React-rendererdebug
- React-utils
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- Yoga
- react-native-safe-area-context (4.12.0):
- DoubleConversion
- glog
@@ -2165,6 +2191,7 @@ DEPENDENCIES:
- ExpoKeepAwake (from `../node_modules/expo-keep-awake/ios`)
- ExpoLinking (from `../node_modules/expo-linking/ios`)
- ExpoModulesCore (from `../node_modules/expo-modules-core`)
- ExpoSharing (from `../node_modules/expo-sharing/ios`)
- ExpoSplashScreen (from `../node_modules/expo-splash-screen/ios`)
- ExpoSQLite (from `../node_modules/expo-sqlite/ios`)
- ExpoSymbols (from `../node_modules/expo-symbols/ios`)
@@ -2210,6 +2237,7 @@ DEPENDENCIES:
- React-microtasksnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/microtasks`)
- react-native-aes-gcm-crypto (from `../node_modules/react-native-aes-gcm-crypto`)
- react-native-get-random-values (from `../node_modules/react-native-get-random-values`)
- react-native-quick-crypto (from `../node_modules/react-native-quick-crypto`)
- react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
- react-native-webview (from `../node_modules/react-native-webview`)
- React-nativeconfig (from `../node_modules/react-native/ReactCommon`)
@@ -2252,6 +2280,7 @@ SPEC REPOS:
trunk:
- CatCrypto
- KeychainAccess
- OpenSSL-Universal
- SocketRocket
- SQLite.swift
@@ -2296,6 +2325,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/expo-linking/ios"
ExpoModulesCore:
:path: "../node_modules/expo-modules-core"
ExpoSharing:
:path: "../node_modules/expo-sharing/ios"
ExpoSplashScreen:
:path: "../node_modules/expo-splash-screen/ios"
ExpoSQLite:
@@ -2381,6 +2412,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-aes-gcm-crypto"
react-native-get-random-values:
:path: "../node_modules/react-native-get-random-values"
react-native-quick-crypto:
:path: "../node_modules/react-native-quick-crypto"
react-native-safe-area-context:
:path: "../node_modules/react-native-safe-area-context"
react-native-webview:
@@ -2476,6 +2509,7 @@ SPEC CHECKSUMS:
ExpoKeepAwake: b0171a73665bfcefcfcc311742a72a956e6aa680
ExpoLinking: 8d12bee174ba0cdf31239706578e29e74a417402
ExpoModulesCore: c25d77625038b1968ea1afefc719862c0d8dd993
ExpoSharing: 849a5ce9985c22598c16ec027e32969be8062e8e
ExpoSplashScreen: 0f281e3c2ded4757d2309276c682d023c6299c77
ExpoSQLite: 10fceac6748e9e8f010c70733e0e704fa67399ab
ExpoSymbols: f3002db15156cd4e505c77b6ea1df5c984db9965
@@ -2488,6 +2522,7 @@ SPEC CHECKSUMS:
glog: 08b301085f15bcbb6ff8632a8ebaf239aae04e6a
hermes-engine: 9e868dc7be781364296d6ee2f56d0c1a9ef0bb11
KeychainAccess: c0c4f7f38f6fc7bbe58f5702e25f7bd2f65abf51
OpenSSL-Universal: 6082b0bf950e5636fe0d78def171184e2b3899c2
RCT-Folly: ea9d9256ba7f9322ef911169a9f696e5857b9e17
RCTDeprecation: ebe712bb05077934b16c6bf25228bdec34b64f83
RCTRequired: ca91e5dd26b64f577b528044c962baf171c6b716
@@ -2519,6 +2554,7 @@ SPEC CHECKSUMS:
React-microtasksnativemodule: d80ff86c8902872d397d9622f1a97aadcc12cead
react-native-aes-gcm-crypto: d572dd7a69f31c539bb8309b3a829bfa3bfad244
react-native-get-random-values: d16467cf726c618e9c7a8c3c39c31faa2244bbba
react-native-quick-crypto: 361ecda861e23138ce68fea4b820cbc058688fb5
react-native-safe-area-context: cd916088cac5300c3266876218377518987b995e
react-native-webview: 6b9fc65c1951203a3e958ff3cc0a858d4b6be901
React-nativeconfig: 8efdb1ef1e9158c77098a93085438f7e7b463678

View File

@@ -449,11 +449,13 @@
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-AliasVault/Pods-AliasVault-frameworks.sh",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/ExpoSQLite/crsqlite.framework/crsqlite",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/OpenSSL-Universal/OpenSSL.framework/OpenSSL",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes",
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/crsqlite.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OpenSSL.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework",
);
runOnlyForDeploymentPostprocessing = 0;

View File

@@ -12,6 +12,7 @@
"@react-native-async-storage/async-storage": "^2.1.2",
"@react-navigation/bottom-tabs": "^7.2.0",
"@react-navigation/native": "^7.0.14",
"@types/jsrsasign": "^10.5.15",
"expo": "~52.0.43",
"expo-blur": "~14.0.3",
"expo-clipboard": "^7.0.1",
@@ -21,6 +22,7 @@
"expo-haptics": "~14.0.1",
"expo-linking": "~7.0.5",
"expo-router": "~4.0.20",
"expo-sharing": "~13.0.1",
"expo-splash-screen": "~0.29.22",
"expo-sqlite": "~15.1.4",
"expo-status-bar": "~2.0.1",
@@ -35,6 +37,7 @@
"react-native-argon2": "^2.0.1",
"react-native-gesture-handler": "~2.20.2",
"react-native-get-random-values": "^1.11.0",
"react-native-quick-crypto": "^0.7.13",
"react-native-reanimated": "~3.16.1",
"react-native-safe-area-context": "4.12.0",
"react-native-screens": "~4.4.0",
@@ -2233,6 +2236,30 @@
"dev": true,
"license": "MIT"
},
"node_modules/@craftzdog/react-native-buffer": {
"version": "6.0.5",
"resolved": "https://registry.npmjs.org/@craftzdog/react-native-buffer/-/react-native-buffer-6.0.5.tgz",
"integrity": "sha512-Av+YqfwA9e7jhgI9GFE/gTpwl/H+dRRLmZyJPOpKTy107j9Oj7oXlm3/YiMNz+C/CEGqcKAOqnXDLs4OL6AAFw==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT",
"dependencies": {
"ieee754": "^1.2.1",
"react-native-quick-base64": "^2.0.5"
}
},
"node_modules/@egjs/hammerjs": {
"version": "2.0.17",
"resolved": "https://registry.npmjs.org/@egjs/hammerjs/-/hammerjs-2.0.17.tgz",
@@ -4659,6 +4686,12 @@
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
"license": "MIT"
},
"node_modules/@types/jsrsasign": {
"version": "10.5.15",
"resolved": "https://registry.npmjs.org/@types/jsrsasign/-/jsrsasign-10.5.15.tgz",
"integrity": "sha512-3stUTaSRtN09PPzVWR6aySD9gNnuymz+WviNHoTb85dKu+BjaV4uBbWWGykBBJkfwPtcNZVfTn2lbX00U+yhpQ==",
"license": "MIT"
},
"node_modules/@types/node": {
"version": "22.14.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.0.tgz",
@@ -5267,6 +5300,21 @@
"node": ">= 4.0.0"
}
},
"node_modules/available-typed-arrays": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
"integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==",
"license": "MIT",
"dependencies": {
"possible-typed-array-names": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/babel-core": {
"version": "7.0.0-bridge.0",
"resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz",
@@ -5737,6 +5785,24 @@
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
"license": "ISC"
},
"node_modules/call-bind": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
"integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.0",
"es-define-property": "^1.0.0",
"get-intrinsic": "^1.2.4",
"set-function-length": "^1.2.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/call-bind-apply-helpers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
@@ -5750,6 +5816,22 @@
"node": ">= 0.4"
}
},
"node_modules/call-bound": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"get-intrinsic": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/caller-callsite": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz",
@@ -6587,6 +6669,23 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/define-data-property": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
"license": "MIT",
"dependencies": {
"es-define-property": "^1.0.0",
"es-errors": "^1.3.0",
"gopd": "^1.0.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/define-lazy-prop": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",
@@ -7122,9 +7221,7 @@
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
"integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.8.x"
}
@@ -7617,6 +7714,15 @@
"node": ">=10"
}
},
"node_modules/expo-sharing": {
"version": "13.0.1",
"resolved": "https://registry.npmjs.org/expo-sharing/-/expo-sharing-13.0.1.tgz",
"integrity": "sha512-qych3Nw65wlFcnzE/gRrsdtvmdV0uF4U4qVMZBJYPG90vYyWh2QM9rp1gVu0KWOBc7N8CC2dSVYn4/BXqJy6Xw==",
"license": "MIT",
"peerDependencies": {
"expo": "*"
}
},
"node_modules/expo-splash-screen": {
"version": "0.29.22",
"resolved": "https://registry.npmjs.org/expo-splash-screen/-/expo-splash-screen-0.29.22.tgz",
@@ -8023,6 +8129,21 @@
"integrity": "sha512-6FPvD/IVyT4ZlNe7Wcn5Fb/4ChigpucKYSvD6a+0iMoLn2inpo711eyIcKjmDtE5XNcgAkSH9uN/nfAeZzHEfg==",
"license": "BSD-2-Clause"
},
"node_modules/for-each": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
"integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==",
"license": "MIT",
"dependencies": {
"is-callable": "^1.2.7"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/foreground-child": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
@@ -8332,6 +8453,18 @@
"node": ">=8"
}
},
"node_modules/has-property-descriptors": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
"license": "MIT",
"dependencies": {
"es-define-property": "^1.0.0"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
@@ -8704,6 +8837,22 @@
"node": ">= 0.10"
}
},
"node_modules/is-arguments": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz",
"integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==",
"license": "MIT",
"dependencies": {
"call-bound": "^1.0.2",
"has-tostringtag": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-arrayish": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
@@ -8716,6 +8865,18 @@
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
"license": "MIT"
},
"node_modules/is-callable": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
"integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-core-module": {
"version": "2.16.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
@@ -8783,6 +8944,24 @@
"node": ">=6"
}
},
"node_modules/is-generator-function": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz",
"integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==",
"license": "MIT",
"dependencies": {
"call-bound": "^1.0.3",
"get-proto": "^1.0.0",
"has-tostringtag": "^1.0.2",
"safe-regex-test": "^1.1.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-glob": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
@@ -8850,6 +9029,24 @@
"dev": true,
"license": "MIT"
},
"node_modules/is-regex": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
"integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==",
"license": "MIT",
"dependencies": {
"call-bound": "^1.0.2",
"gopd": "^1.2.0",
"has-tostringtag": "^1.0.2",
"hasown": "^2.0.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-stream": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
@@ -8859,6 +9056,21 @@
"node": ">=0.10.0"
}
},
"node_modules/is-typed-array": {
"version": "1.1.15",
"resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz",
"integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==",
"license": "MIT",
"dependencies": {
"which-typed-array": "^1.1.16"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-wsl": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
@@ -12091,6 +12303,15 @@
"node": ">=4.0.0"
}
},
"node_modules/possible-typed-array-names": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
"integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/postcss": {
"version": "8.4.49",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz",
@@ -12172,6 +12393,15 @@
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
}
},
"node_modules/process": {
"version": "0.11.10",
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
"integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==",
"license": "MIT",
"engines": {
"node": ">= 0.6.0"
}
},
"node_modules/progress": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
@@ -12601,6 +12831,36 @@
"react-native": "*"
}
},
"node_modules/react-native-quick-base64": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/react-native-quick-base64/-/react-native-quick-base64-2.1.2.tgz",
"integrity": "sha512-xghaXpWdB0ji8OwYyo0fWezRroNxiNFCNFpGUIyE7+qc4gA/IGWnysIG5L0MbdoORv8FkTKUvfd6yCUN5R2VFA==",
"license": "MIT",
"dependencies": {
"base64-js": "^1.5.1"
},
"peerDependencies": {
"react": "*",
"react-native": "*"
}
},
"node_modules/react-native-quick-crypto": {
"version": "0.7.13",
"resolved": "https://registry.npmjs.org/react-native-quick-crypto/-/react-native-quick-crypto-0.7.13.tgz",
"integrity": "sha512-9EmXvvBW64gt/hUgeDUGl6KHjDVmtEdC/CbmlyLOTNXxvkOhNI/LhLK9ss6jDqRX3tYTM/DL0uk/+kXSTZQeKA==",
"license": "MIT",
"workspaces": [
".",
"example"
],
"dependencies": {
"@craftzdog/react-native-buffer": "^6.0.5",
"events": "^3.3.0",
"readable-stream": "^4.5.2",
"string_decoder": "^1.3.0",
"util": "^0.12.5"
}
},
"node_modules/react-native-reanimated": {
"version": "3.16.7",
"resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-3.16.7.tgz",
@@ -12859,6 +13119,46 @@
"react": "^18.3.1"
}
},
"node_modules/readable-stream": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz",
"integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==",
"license": "MIT",
"dependencies": {
"abort-controller": "^3.0.0",
"buffer": "^6.0.3",
"events": "^3.3.0",
"process": "^0.11.10",
"string_decoder": "^1.3.0"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
"node_modules/readable-stream/node_modules/buffer": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT",
"dependencies": {
"base64-js": "^1.3.1",
"ieee754": "^1.2.1"
}
},
"node_modules/readline": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/readline/-/readline-1.3.0.tgz",
@@ -13179,6 +13479,23 @@
],
"license": "MIT"
},
"node_modules/safe-regex-test": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz",
"integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==",
"license": "MIT",
"dependencies": {
"call-bound": "^1.0.2",
"es-errors": "^1.3.0",
"is-regex": "^1.2.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
@@ -13458,6 +13775,23 @@
"integrity": "sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==",
"license": "MIT"
},
"node_modules/set-function-length": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
"integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
"license": "MIT",
"dependencies": {
"define-data-property": "^1.1.4",
"es-errors": "^1.3.0",
"function-bind": "^1.1.2",
"get-intrinsic": "^1.2.4",
"gopd": "^1.0.1",
"has-property-descriptors": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/setimmediate": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
@@ -13809,6 +14143,15 @@
"node": ">=4"
}
},
"node_modules/string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"license": "MIT",
"dependencies": {
"safe-buffer": "~5.2.0"
}
},
"node_modules/string-length": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz",
@@ -14785,6 +15128,19 @@
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/util": {
"version": "0.12.5",
"resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz",
"integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==",
"license": "MIT",
"dependencies": {
"inherits": "^2.0.3",
"is-arguments": "^1.0.4",
"is-generator-function": "^1.0.7",
"is-typed-array": "^1.1.3",
"which-typed-array": "^1.1.2"
}
},
"node_modules/utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
@@ -15053,6 +15409,27 @@
"node": ">= 8"
}
},
"node_modules/which-typed-array": {
"version": "1.1.19",
"resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz",
"integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==",
"license": "MIT",
"dependencies": {
"available-typed-arrays": "^1.0.7",
"call-bind": "^1.0.8",
"call-bound": "^1.0.4",
"for-each": "^0.3.5",
"get-proto": "^1.0.1",
"gopd": "^1.2.0",
"has-tostringtag": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/wonka": {
"version": "6.3.5",
"resolved": "https://registry.npmjs.org/wonka/-/wonka-6.3.5.tgz",

View File

@@ -19,6 +19,7 @@
"@react-native-async-storage/async-storage": "^2.1.2",
"@react-navigation/bottom-tabs": "^7.2.0",
"@react-navigation/native": "^7.0.14",
"@types/jsrsasign": "^10.5.15",
"expo": "~52.0.43",
"expo-blur": "~14.0.3",
"expo-clipboard": "^7.0.1",
@@ -28,6 +29,7 @@
"expo-haptics": "~14.0.1",
"expo-linking": "~7.0.5",
"expo-router": "~4.0.20",
"expo-sharing": "~13.0.1",
"expo-splash-screen": "~0.29.22",
"expo-sqlite": "~15.1.4",
"expo-status-bar": "~2.0.1",
@@ -42,6 +44,7 @@
"react-native-argon2": "^2.0.1",
"react-native-gesture-handler": "~2.20.2",
"react-native-get-random-values": "^1.11.0",
"react-native-quick-crypto": "^0.7.13",
"react-native-reanimated": "~3.16.1",
"react-native-safe-area-context": "4.12.0",
"react-native-screens": "~4.4.0",

View File

@@ -113,6 +113,8 @@ class EncryptionUtility {
* Generates a new RSA key pair for asymmetric encryption
*/
public static async generateRsaKeyPair(): Promise<{ publicKey: string, privateKey: string }> {
// TODO: ensure the key pair is generated in the correct format where private key is in expected
// JWK format that the WASM app already outputs.
const keyPair = await crypto.subtle.generateKey(
{
name: "RSA-OAEP",
@@ -179,7 +181,8 @@ class EncryptionUtility {
const cipherBuffer = Uint8Array.from(atob(ciphertext), c => c.charCodeAt(0));
const plaintextBuffer = await crypto.subtle.decrypt(
{
name: "RSA-OAEP"
name: "RSA-OAEP",
hash: "SHA-256",
},
privateKeyObj,
cipherBuffer
@@ -188,7 +191,7 @@ class EncryptionUtility {
return new Uint8Array(plaintextBuffer);
} catch (error) {
console.error('RSA decryption failed:', error);
throw new Error(`Failed to decrypt: ${error instanceof Error ? error.message : 'Unknown error'}`);
throw new Error(`Failed to decrypt: ${error.message}`);
}
}
@@ -250,13 +253,21 @@ class EncryptionUtility {
throw new Error('Encryption key not found');
}
console.log('email.encryptedSymmetricKey: ', email.encryptedSymmetricKey);
console.log('encryptionKey.PrivateKey: ', encryptionKey.PrivateKey);
// Decrypt symmetric key with asymmetric private key
const symmetricKey = await EncryptionUtility.decryptWithPrivateKey(
email.encryptedSymmetricKey,
encryptionKey.PrivateKey
);
console.log('Decrypted symmetricKey: ', symmetricKey);
const symmetricKeyBase64 = Buffer.from(symmetricKey).toString('base64');
console.log('Decrypted symmetricKeyBase64: ', symmetricKeyBase64);
// Create a new object to avoid mutating the original
const decryptedEmail = { ...email };