Skip to content

Commit

Permalink
security: avoid brute-force attacks targeting the password. #RBY-01-0…
Browse files Browse the repository at this point in the history
…03 WP1-WP2
  • Loading branch information
richardo2016x committed Oct 15, 2024
1 parent 32b2452 commit 5bb61fc
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 6 deletions.
29 changes: 27 additions & 2 deletions apps/mobile/src/core/apis/lock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ import { RABBY_MOBILE_KR_PWD } from '@/constant/encryptor';
import { BroadcastEvent } from '@/constant/event';
import { keyringService, sessionService } from '../services';
import { makeEEClass } from './event';
import { formatTimeReadable } from '@/utils/time';
import {
resetMultipleFailed,
setMultipleFailed,
shouldRejectUnlockDueToMultipleFailed,
} from '../utils/unlockRateLimit';

export const enum PasswordStatus {
Unknown = -1,
Expand Down Expand Up @@ -198,15 +204,34 @@ export function isUnlocked() {
return keyringService.isUnlocked();
}

export type UnlockResultErrors = {
error: string;
formFieldError?: string;
toastError?: string;
};
async function unlockWallet(password: string) {
const unlockResult = {
error: '',
};
formFieldError: '',
toastError: '',
} as UnlockResultErrors;

const checkReject = shouldRejectUnlockDueToMultipleFailed();
if (checkReject.reject) {
unlockResult.error = ERRORS.INCORRECT_PASSWORD;
unlockResult.formFieldError = 'Too many failed attempts';
unlockResult.toastError = `Too many failed attempts, please try again after ${formatTimeReadable(
Math.floor(checkReject.timeDiff / 1e3),
)}`;
return unlockResult;
}

try {
await keyringService.verifyPassword(password);
resetMultipleFailed();
} catch (err) {
unlockResult.error = 'Incorrect Password';
unlockResult.error = ERRORS.INCORRECT_PASSWORD;
setMultipleFailed();
return unlockResult;
}

Expand Down
58 changes: 58 additions & 0 deletions apps/mobile/src/core/utils/unlockRateLimit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { appStorage } from '../storage/mmkv';

// - if failed 5 times, reject unlock for 5 minutes
const MULTIPLE_FAILED_CONF = {
key: '@failed_unlock',
limit: 5,
duration: (5 * 60 + 30) * 1000,
};

/**
* @description
*
* - `count` means the failed attempts.
* - `time` means the time when the unlocking will be allwed.
*/
type FAILED_LOCK_INFO = { count: number; time: number };
function getMultipleFailed() {
const val = appStorage.getItem(MULTIPLE_FAILED_CONF.key);

return {
count: 0,
time: 0,
...val,
} as FAILED_LOCK_INFO;
}
export function setMultipleFailed() {
const record = getMultipleFailed();

const now = Date.now();
if (record.time >= now) {
record.count = 0;
} else if (record.count >= MULTIPLE_FAILED_CONF.limit) {
record.time = now + MULTIPLE_FAILED_CONF.duration;
} else {
record.count += 1;
}

appStorage.setItem(MULTIPLE_FAILED_CONF.key, record);
}
export function resetMultipleFailed() {
appStorage.setItem(MULTIPLE_FAILED_CONF.key, { count: 0, time: 0 });
}
export function shouldRejectUnlockDueToMultipleFailed() {
const record = getMultipleFailed();
const result = {
reject: false,
timeDiff: 0,
};
if (!record?.time) return result;

const now = Date.now();
if (record.time > now) {
result.timeDiff = record.time - now;
result.reject = true;
}

return result;
}
7 changes: 5 additions & 2 deletions apps/mobile/src/screens/Unlock/Unlock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,11 @@ function useUnlockForm(navigation: ReturnType<typeof useRabbyAppNavigation>) {
try {
const result = await unlockApp(values.password);
if (result.error) {
helpers?.setFieldError('password', t('page.unlock.password.error'));
toast.show(result.error);
helpers?.setFieldError(
'password',
result.formFieldError || t('page.unlock.password.error'),
);
toast.show(result.toastError || result.error);
} else {
updateUnlockTime();
}
Expand Down
5 changes: 3 additions & 2 deletions apps/mobile/src/screens/Unlock/hooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { atomByMMKV } from '@/core/storage/mmkv';
import { useBiometrics } from '@/hooks/biometrics';
import { toast } from '@/components/Toast';
import { Alert } from 'react-native';
import { IS_IOS } from '@/core/native/utils';
import { UnlockResultErrors } from '@/core/apis/lock';

export enum UNLOCK_STATE {
IDLE = 0,
Expand All @@ -25,7 +25,8 @@ export function useUnlockApp() {

const unlockApp = useCallback(
async (password: string) => {
if (stateRef.current.status !== UNLOCK_STATE.IDLE) return { error: '' };
if (stateRef.current.status !== UNLOCK_STATE.IDLE)
return { error: '' } as UnlockResultErrors;

setUnlockState(prev => ({ ...prev, status: UNLOCK_STATE.UNLOCKING }));

Expand Down

0 comments on commit 5bb61fc

Please sign in to comment.