mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-03-20 15:41:40 -04:00
Add native react argon2id implementation, add SRP polyfill (#771)
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { Image, StyleSheet, Platform, Button, View, FlatList, Text, SafeAreaView, AppState, TextInput, TouchableOpacity, ActivityIndicator } from 'react-native';
|
||||
import { NativeModules } from 'react-native';
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { Buffer } from 'buffer';
|
||||
|
||||
import { ThemedText } from '@/components/ThemedText';
|
||||
import { ThemedView } from '@/components/ThemedView';
|
||||
@@ -60,12 +61,16 @@ export default function HomeScreen() {
|
||||
loginResponse.encryptionSettings
|
||||
);
|
||||
|
||||
console.log('passwordHash', passwordHash);
|
||||
|
||||
// Convert uint8 array to uppercase hex string which is expected by the server.
|
||||
const passwordHashString = Buffer.from(passwordHash).toString('hex').toUpperCase();
|
||||
|
||||
// Get the derived key as base64 string required for decryption.
|
||||
const passwordHashBase64 = Buffer.from(passwordHash).toString('base64');
|
||||
|
||||
console.log('passwordHashString', passwordHashString);
|
||||
|
||||
// 2. Validate login with SRP protocol
|
||||
const validationResponse = await srpUtil.validateLogin(
|
||||
credentials.username,
|
||||
@@ -74,6 +79,8 @@ export default function HomeScreen() {
|
||||
loginResponse
|
||||
);
|
||||
|
||||
console.log('validationResponse', validationResponse);
|
||||
|
||||
// 3. Handle 2FA if required
|
||||
if (validationResponse.requiresTwoFactor) {
|
||||
// Store login response as we need it for 2FA validation
|
||||
|
||||
@@ -5,6 +5,9 @@ import * as SplashScreen from 'expo-splash-screen';
|
||||
import { StatusBar } from 'expo-status-bar';
|
||||
import { useEffect } from 'react';
|
||||
import 'react-native-reanimated';
|
||||
// Required for certain modules such as secure-remote-password which relies on crypto.getRandomValues
|
||||
// and this is not available in react-native without this polyfill
|
||||
import 'react-native-get-random-values';
|
||||
|
||||
import { useColorScheme } from '@/hooks/useColorScheme';
|
||||
import { LoadingProvider } from '@/context/LoadingContext';
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
PODS:
|
||||
- boost (1.84.0)
|
||||
- CatCrypto (0.3.2)
|
||||
- DoubleConversion (1.1.6)
|
||||
- EXConstants (17.0.8):
|
||||
- ExpoModulesCore
|
||||
@@ -1552,6 +1553,8 @@ PODS:
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- Yoga
|
||||
- react-native-get-random-values (1.11.0):
|
||||
- React-Core
|
||||
- react-native-safe-area-context (4.12.0):
|
||||
- DoubleConversion
|
||||
- glog
|
||||
@@ -1911,6 +1914,9 @@ PODS:
|
||||
- React-logger
|
||||
- React-perflogger
|
||||
- React-utils (= 0.76.9)
|
||||
- RNArgon2 (2.0.1):
|
||||
- CatCrypto
|
||||
- React-Core
|
||||
- RNCAsyncStorage (2.1.2):
|
||||
- DoubleConversion
|
||||
- glog
|
||||
@@ -2151,6 +2157,7 @@ DEPENDENCIES:
|
||||
- React-logger (from `../node_modules/react-native/ReactCommon/logger`)
|
||||
- React-Mapbuffer (from `../node_modules/react-native/ReactCommon`)
|
||||
- React-microtasksnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/microtasks`)
|
||||
- react-native-get-random-values (from `../node_modules/react-native-get-random-values`)
|
||||
- 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`)
|
||||
@@ -2180,6 +2187,7 @@ DEPENDENCIES:
|
||||
- React-utils (from `../node_modules/react-native/ReactCommon/react/utils`)
|
||||
- ReactCodegen (from `build/generated/ios`)
|
||||
- ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`)
|
||||
- RNArgon2 (from `../node_modules/react-native-argon2`)
|
||||
- "RNCAsyncStorage (from `../node_modules/@react-native-async-storage/async-storage`)"
|
||||
- RNGestureHandler (from `../node_modules/react-native-gesture-handler`)
|
||||
- RNReanimated (from `../node_modules/react-native-reanimated`)
|
||||
@@ -2188,6 +2196,7 @@ DEPENDENCIES:
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
- CatCrypto
|
||||
- KeychainAccess
|
||||
- SocketRocket
|
||||
|
||||
@@ -2311,6 +2320,8 @@ EXTERNAL SOURCES:
|
||||
:path: "../node_modules/react-native/ReactCommon"
|
||||
React-microtasksnativemodule:
|
||||
:path: "../node_modules/react-native/ReactCommon/react/nativemodule/microtasks"
|
||||
react-native-get-random-values:
|
||||
:path: "../node_modules/react-native-get-random-values"
|
||||
react-native-safe-area-context:
|
||||
:path: "../node_modules/react-native-safe-area-context"
|
||||
react-native-webview:
|
||||
@@ -2369,6 +2380,8 @@ EXTERNAL SOURCES:
|
||||
:path: build/generated/ios
|
||||
ReactCommon:
|
||||
:path: "../node_modules/react-native/ReactCommon"
|
||||
RNArgon2:
|
||||
:path: "../node_modules/react-native-argon2"
|
||||
RNCAsyncStorage:
|
||||
:path: "../node_modules/@react-native-async-storage/async-storage"
|
||||
RNGestureHandler:
|
||||
@@ -2382,6 +2395,7 @@ EXTERNAL SOURCES:
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
boost: 1dca942403ed9342f98334bf4c3621f011aa7946
|
||||
CatCrypto: a477899b6be4954e75be4897e732da098cc0a5a8
|
||||
DoubleConversion: f16ae600a246532c4020132d54af21d0ddb2a385
|
||||
EXConstants: fcfc75800824ac2d5c592b5bc74130bad17b146b
|
||||
EXJSONUtils: 01fc7492b66c234e395dcffdd5f53439c5c29c93
|
||||
@@ -2441,6 +2455,7 @@ SPEC CHECKSUMS:
|
||||
React-logger: c4052eb941cca9a097ef01b59543a656dc088559
|
||||
React-Mapbuffer: 33546a3ebefbccb8770c33a1f8a5554fa96a54de
|
||||
React-microtasksnativemodule: d80ff86c8902872d397d9622f1a97aadcc12cead
|
||||
react-native-get-random-values: d16467cf726c618e9c7a8c3c39c31faa2244bbba
|
||||
react-native-safe-area-context: cd916088cac5300c3266876218377518987b995e
|
||||
react-native-webview: 6b9fc65c1951203a3e958ff3cc0a858d4b6be901
|
||||
React-nativeconfig: 8efdb1ef1e9158c77098a93085438f7e7b463678
|
||||
@@ -2470,6 +2485,7 @@ SPEC CHECKSUMS:
|
||||
React-utils: ed818f19ab445000d6b5c4efa9d462449326cc9f
|
||||
ReactCodegen: f853a20cc9125c5521c8766b4b49375fec20648b
|
||||
ReactCommon: 300d8d9c5cb1a6cd79a67cf5d8f91e4d477195f9
|
||||
RNArgon2: 708e188b7a4d4ec8baf62463927c47abef453a94
|
||||
RNCAsyncStorage: ab0ad9a78ead8b9f143f0238a5aa535777b12e65
|
||||
RNGestureHandler: fffddeb8af59709c6d8de11b6461a6af63cad532
|
||||
RNReanimated: 2e5069649cbab2c946652d3b97589b2ae0526220
|
||||
|
||||
33
mobile-app/package-lock.json
generated
33
mobile-app/package-lock.json
generated
@@ -12,7 +12,6 @@
|
||||
"@react-native-async-storage/async-storage": "^2.1.2",
|
||||
"@react-navigation/bottom-tabs": "^7.2.0",
|
||||
"@react-navigation/native": "^7.0.14",
|
||||
"argon2-browser": "^1.18.0",
|
||||
"expo": "~52.0.43",
|
||||
"expo-blur": "~14.0.3",
|
||||
"expo-constants": "~17.0.8",
|
||||
@@ -30,7 +29,9 @@
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react-native": "0.76.9",
|
||||
"react-native-argon2": "^2.0.1",
|
||||
"react-native-gesture-handler": "~2.20.2",
|
||||
"react-native-get-random-values": "^1.11.0",
|
||||
"react-native-reanimated": "~3.16.1",
|
||||
"react-native-safe-area-context": "4.12.0",
|
||||
"react-native-screens": "~4.4.0",
|
||||
@@ -4771,12 +4772,6 @@
|
||||
"integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/argon2-browser": {
|
||||
"version": "1.18.0",
|
||||
"resolved": "https://registry.npmjs.org/argon2-browser/-/argon2-browser-1.18.0.tgz",
|
||||
"integrity": "sha512-ImVAGIItnFnvET1exhsQB7apRztcoC5TnlSqernMJDUjbc/DLq3UEYeXFrLPrlaIl8cVfwnXb6wX2KpFf2zxHw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/argparse": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
|
||||
@@ -7127,6 +7122,12 @@
|
||||
"integrity": "sha512-8QxYTVXUkuy7fIIoitQkPwGonB8F3Zj8eEO8Sqg9Zv/bkI7RJAzowee4gr81Hak/dUTpA2Z7VfQgoijjPNlUZA==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/fast-base64-decode": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-base64-decode/-/fast-base64-decode-1.0.0.tgz",
|
||||
"integrity": "sha512-qwaScUgUGBYeDNRnbc/KyllVU88Jk1pRHPStuF/lO7B0/RTRLj7U0lkdTAutlBblY08rwZDff6tNU9cjv6j//Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
@@ -11882,6 +11883,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-argon2": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-argon2/-/react-native-argon2-2.0.1.tgz",
|
||||
"integrity": "sha512-/iOi0S+VVgS1gQGtQgL4ZxUVS4gz6Lav3bgIbtNmr9KbOunnBYzP6/yBe/XxkbpXvasHDwdQnuppOH/nuOBn7w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-native-gesture-handler": {
|
||||
"version": "2.20.2",
|
||||
"resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.20.2.tgz",
|
||||
@@ -11898,6 +11905,18 @@
|
||||
"react-native": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-get-random-values": {
|
||||
"version": "1.11.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-get-random-values/-/react-native-get-random-values-1.11.0.tgz",
|
||||
"integrity": "sha512-4BTbDbRmS7iPdhYLRcz3PGFIpFJBwNZg9g42iwa2P6FOv9vZj/xJc678RZXnLNZzd0qd7Q3CCF6Yd+CU2eoXKQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-base64-decode": "^1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react-native": ">=0.56"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-helmet-async": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/react-native-helmet-async/-/react-native-helmet-async-2.0.4.tgz",
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
"@react-native-async-storage/async-storage": "^2.1.2",
|
||||
"@react-navigation/bottom-tabs": "^7.2.0",
|
||||
"@react-navigation/native": "^7.0.14",
|
||||
"argon2-browser": "^1.18.0",
|
||||
"expo": "~52.0.43",
|
||||
"expo-blur": "~14.0.3",
|
||||
"expo-constants": "~17.0.8",
|
||||
@@ -37,7 +36,9 @@
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react-native": "0.76.9",
|
||||
"react-native-argon2": "^2.0.1",
|
||||
"react-native-gesture-handler": "~2.20.2",
|
||||
"react-native-get-random-values": "^1.11.0",
|
||||
"react-native-reanimated": "~3.16.1",
|
||||
"react-native-safe-area-context": "4.12.0",
|
||||
"react-native-screens": "~4.4.0",
|
||||
|
||||
22
mobile-app/types/react-native-argon2.d.ts
vendored
Normal file
22
mobile-app/types/react-native-argon2.d.ts
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
declare module 'react-native-argon2' {
|
||||
interface Argon2Options {
|
||||
iterations?: number;
|
||||
memory?: number;
|
||||
parallelism?: number;
|
||||
hashLength?: number;
|
||||
mode?: 'argon2i' | 'argon2d' | 'argon2id';
|
||||
}
|
||||
|
||||
interface Argon2Result {
|
||||
encodedHash: string;
|
||||
rawHash: string;
|
||||
}
|
||||
|
||||
function argon2(
|
||||
password: string,
|
||||
salt: string,
|
||||
options?: Argon2Options
|
||||
): Promise<Argon2Result>;
|
||||
|
||||
export default argon2;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import argon2 from 'argon2-browser/dist/argon2-bundled.min.js';
|
||||
import argon2 from 'react-native-argon2';
|
||||
import { Email } from './types/webapi/Email';
|
||||
import { EncryptionKey } from './types/EncryptionKey';
|
||||
import { MailboxEmail } from './types/webapi/MailboxEmail';
|
||||
@@ -27,18 +27,26 @@ class EncryptionUtility {
|
||||
throw new Error('Unsupported encryption type');
|
||||
}
|
||||
|
||||
const hash = await argon2.hash({
|
||||
pass: password,
|
||||
salt: salt,
|
||||
time: settings.Iterations,
|
||||
mem: settings.MemorySize,
|
||||
parallelism: settings.DegreeOfParallelism,
|
||||
hashLen: 32,
|
||||
type: 2, // 0 = Argon2d, 1 = Argon2i, 2 = Argon2id
|
||||
});
|
||||
console.log('trying to hash password');
|
||||
const result = await argon2(
|
||||
password,
|
||||
salt,
|
||||
{
|
||||
iterations: settings.Iterations,
|
||||
memory: settings.MemorySize,
|
||||
parallelism: settings.DegreeOfParallelism,
|
||||
hashLength: 32,
|
||||
mode: 'argon2id'
|
||||
}
|
||||
);
|
||||
console.log('result', result);
|
||||
|
||||
// Return bytes
|
||||
return hash.hash;
|
||||
// Convert the hex string to Uint8Array
|
||||
const bytes = new Uint8Array(32);
|
||||
for (let i = 0; i < 32; i++) {
|
||||
bytes[i] = parseInt(result.rawHash.substring(i * 2, i * 2 + 2), 16);
|
||||
}
|
||||
return bytes;
|
||||
} catch (error) {
|
||||
console.error('Argon2 hashing failed:', error);
|
||||
throw error;
|
||||
|
||||
@@ -53,12 +53,17 @@ export class SrpUtility {
|
||||
loginResponse: LoginResponse
|
||||
): Promise<ValidateLoginResponse> {
|
||||
try {
|
||||
console.log('validateLogin');
|
||||
// Generate client ephemeral
|
||||
const clientEphemeral = srp.generateEphemeral();
|
||||
|
||||
console.log('clientEphemeral', clientEphemeral);
|
||||
|
||||
// Derive private key
|
||||
const privateKey = srp.derivePrivateKey(loginResponse.salt, username, passwordHash);
|
||||
|
||||
console.log('privateKey', privateKey);
|
||||
|
||||
// Derive session
|
||||
const sessionProof = srp.deriveSession(
|
||||
clientEphemeral.secret,
|
||||
@@ -68,7 +73,9 @@ export class SrpUtility {
|
||||
privateKey
|
||||
);
|
||||
|
||||
const response = await this.webApiService.rawFetch('Login/Validate', {
|
||||
console.log('sessionProof', sessionProof);
|
||||
|
||||
const response = await this.webApiService.rawFetch('Auth/validate', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -92,6 +99,7 @@ export class SrpUtility {
|
||||
if (error instanceof ApiAuthError) {
|
||||
throw error;
|
||||
}
|
||||
console.error('validateLogin error', error);
|
||||
throw new ApiAuthError('Could not reach AliasVault server. Please try again later or contact support if the problem persists.');
|
||||
}
|
||||
}
|
||||
@@ -122,7 +130,7 @@ export class SrpUtility {
|
||||
privateKey
|
||||
);
|
||||
|
||||
const response = await this.webApiService.rawFetch('Login/Validate2Fa', {
|
||||
const response = await this.webApiService.rawFetch('Auth/validate-2fa', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
||||
Reference in New Issue
Block a user