PIN authentication error handling

This commit is contained in:
Michele Balistreri 2024-09-24 12:28:16 +02:00
parent de81d72e60
commit e910e0b6c4
No known key found for this signature in database
GPG Key ID: E9567DA33A4F791A
6 changed files with 105 additions and 30 deletions

View File

@ -6,6 +6,7 @@ import DiscoveryScreen from './components/steps/DiscoveryScreen';
import InitializationScreen from './components/steps/InitializationScreen'; import InitializationScreen from './components/steps/InitializationScreen';
import MnemonicScreen from './components/steps/MnemonicScreen'; import MnemonicScreen from './components/steps/MnemonicScreen';
import AuthenticationScreen from './components/steps/AuthenticationScreen'; import AuthenticationScreen from './components/steps/AuthenticationScreen';
import FactoryResetScreen from './components/steps/FactoryResetScreen';
import NFCModal from './NFCModal'; import NFCModal from './NFCModal';
//@ts-ignore //@ts-ignore
@ -20,8 +21,11 @@ enum Step {
} }
const Main = () => { const Main = () => {
const PIN_MAX_RETRY = 3;
const [isModalVisible, setIsModalVisible] = useState<boolean>(false); const [isModalVisible, setIsModalVisible] = useState<boolean>(false);
const [step, setStep] = useState(Step.Discovery); const [step, setStep] = useState(Step.Discovery);
const [pinCounter, setPinCounter] = useState(PIN_MAX_RETRY);
const didMount = useRef(false); const didMount = useRef(false);
const stepRef = useRef(step); const stepRef = useRef(step);
@ -41,14 +45,18 @@ const Main = () => {
} }
const isCardLost = (err) => { const isCardLost = (err) => {
return (err == "Tag was lost.") || err.includes("NFCError:100"); return (err == "Tag was lost.") || /"NFCError:10[02]"/.test(err)
} }
const wrongPINCounter = (err) => { const wrongPINCounter = (err) => {
const matches = /Unexpected error SW, 0x63C(\d+)|wrongPIN\(retryCounter: (\d+)\)/.exec(err) const matches = /Unexpected error SW, 0x63C(\d)|wrongPIN\(retryCounter: (\d+)\)/.exec(err)
if (matches && matches.length == 2) { if (matches) {
return parseInt(matches[1]) if (matches[1] !== undefined) {
return parseInt(matches[1])
} else {
return parseInt(matches[2])
}
} }
return null return null
@ -59,12 +67,18 @@ const Main = () => {
return; return;
} }
var newPinCounter = pinCounter;
try { try {
const appInfo = await Keycard.getApplicationInfo(); const appInfo = await Keycard.getApplicationInfo();
if (appInfo["new-pairing"]) { if (appInfo["new-pairing"]) {
await addPairing(appInfo["instance-uid"], appInfo["new-pairing"]); await addPairing(appInfo["instance-uid"], appInfo["new-pairing"]);
} }
if (appInfo["pin-retry-counter"] !== null) {
newPinCounter = appInfo["pin-retry-counter"];
}
switch (stepRef.current) { switch (stepRef.current) {
case Step.Discovery: case Step.Discovery:
if (appInfo["initialized?"]) { if (appInfo["initialized?"]) {
@ -105,14 +119,20 @@ const Main = () => {
const pinRetryCounter = wrongPINCounter(err.message); const pinRetryCounter = wrongPINCounter(err.message);
if (pinRetryCounter !== null) { if (pinRetryCounter !== null) {
//TODO: better handling pinRef.current = ""
newPinCounter = pinRetryCounter;
console.log("wrong PIN. Retry counter: " + pinRetryCounter); console.log("wrong PIN. Retry counter: " + pinRetryCounter);
return; } else {
console.log(err);
} }
console.log(err);
} }
if (newPinCounter == 0) {
setStep(Step.FactoryReset);
}
setPinCounter(newPinCounter);
await Keycard.stopNFC(""); await Keycard.stopNFC("");
setIsModalVisible(false); setIsModalVisible(false);
} }
@ -155,10 +175,9 @@ const Main = () => {
setIsModalVisible(true); setIsModalVisible(true);
} }
const factoryResetCard = async () => { const startFactoryReset = async () => {
stepRef.current = Step.FactoryReset; stepRef.current = Step.FactoryReset;
setStep(Step.FactoryReset); setStep(Step.FactoryReset);
return connectCard();
} }
const initPin = async (p: string) => { const initPin = async (p: string) => {
@ -180,12 +199,17 @@ const Main = () => {
setStep(Step.Discovery); setStep(Step.Discovery);
} }
const pinDisplayCounter = () => {
return pinCounter == PIN_MAX_RETRY ? -1 : pinCounter;
}
return ( return (
<SafeAreaView style={styles.container}> <SafeAreaView style={styles.container}>
{step == Step.Discovery && <DiscoveryScreen onPressFunc={connectCard} onFactoryResetFunc={factoryResetCard}></DiscoveryScreen>} {step == Step.Discovery && <DiscoveryScreen onPressFunc={connectCard} onFactoryResetFunc={startFactoryReset}></DiscoveryScreen>}
{step == Step.Initialization && <InitializationScreen onPressFunc={initPin} onCancelFunc={cancel}></InitializationScreen>} {step == Step.Initialization && <InitializationScreen onPressFunc={initPin} onCancelFunc={cancel}></InitializationScreen>}
{step == Step.Loading && <MnemonicScreen onPressFunc={loadMnemonic} pinRequired={pinRef.current ? false : true} onCancelFunc={cancel}></MnemonicScreen>} {step == Step.Loading && <MnemonicScreen pinRequired={pinRef.current ? false : true} pinRetryCounter={pinDisplayCounter()} onPressFunc={loadMnemonic} onCancelFunc={cancel}></MnemonicScreen>}
{step == Step.Authentication && <AuthenticationScreen onPressFunc={() => {} } onCancelFunc={cancel}></AuthenticationScreen>} {step == Step.Authentication && <AuthenticationScreen pinRetryCounter={pinDisplayCounter()} onPressFunc={() => {} } onCancelFunc={cancel}></AuthenticationScreen>}
{step == Step.FactoryReset && <FactoryResetScreen pinRetryCounter={pinDisplayCounter()} onPressFunc={connectCard} onCancelFunc={cancel}></FactoryResetScreen>}
<NFCModal isVisible={isModalVisible} onChangeFunc={setIsModalVisible}></NFCModal> <NFCModal isVisible={isModalVisible} onChangeFunc={setIsModalVisible}></NFCModal>
</SafeAreaView> </SafeAreaView>
); );

View File

@ -7,17 +7,19 @@ import DialpadPin from "./DialpadPin";
const { width } = Dimensions.get("window"); const { width } = Dimensions.get("window");
type DialpadProps = { type DialpadProps = {
pinRetryCounter: number;
prompt: string; prompt: string;
onCancelFunc: () => void; onCancelFunc: () => void;
onNextFunc: (p?: any) => boolean; onNextFunc: (p?: any) => boolean;
}; };
const Dialpad: FC<DialpadProps> = props => { const Dialpad: FC<DialpadProps> = props => {
const {prompt, onNextFunc, onCancelFunc} = props; const {pinRetryCounter, prompt, onNextFunc, onCancelFunc} = props;
const dialPadContent = [1, 2, 3, 4, 5, 6, 7, 8, 9, "", 0, "X"]; const dialPadContent = [1, 2, 3, 4, 5, 6, 7, 8, 9, "", 0, "X"];
const dialPadSize = width * 0.2; const dialPadSize = width * 0.2;
const dialPadTextSize = dialPadSize * 0.36; const dialPadTextSize = dialPadSize * 0.36;
const [code, setCode] = useState([]); const [code, setCode] = useState([]);
const [wrongRepeat, setWrongRepeat] = useState(false);
const pinLength = 6; const pinLength = 6;
const pinContainerSize = width / 2; const pinContainerSize = width / 2;
const pinSize = (pinContainerSize / pinLength) + 8; const pinSize = (pinContainerSize / pinLength) + 8;
@ -34,9 +36,8 @@ const Dialpad: FC<DialpadProps> = props => {
} }
const onNext = () => { const onNext = () => {
if (!onNextFunc(code.join(''))) { setWrongRepeat(!onNextFunc(code.join('')));
setCode([]); setCode([]);
}
} }
return ( return (
@ -44,6 +45,8 @@ const Dialpad: FC<DialpadProps> = props => {
<View style={styles.textContainer}> <View style={styles.textContainer}>
<Text style={styles.pinText}>{prompt}</Text> <Text style={styles.pinText}>{prompt}</Text>
<Text style={styles.pinSubText}>Enter your secure six-digit code</Text> <Text style={styles.pinSubText}>Enter your secure six-digit code</Text>
{pinRetryCounter >= 0 && <Text style={styles.pinAttempts}>Remaining attempts: {pinRetryCounter}</Text>}
{wrongRepeat && <Text style={styles.pinAttempts}>The PINs do not match</Text>}
<DialpadPin pinLength={pinLength} pinSize={pinSize} code={code} dialPadContent={dialPadContent} /> <DialpadPin pinLength={pinLength} pinSize={pinSize} code={code} dialPadContent={dialPadContent} />
<DialpadKeypad dialPadContent={dialPadContent} dialPadSize={dialPadSize} dialPadTextSize={dialPadTextSize} updateCodeFunc={updateCode} code={code}/> <DialpadKeypad dialPadContent={dialPadContent} dialPadSize={dialPadSize} dialPadTextSize={dialPadTextSize} updateCodeFunc={updateCode} code={code}/>
</View> </View>
@ -76,6 +79,14 @@ const styles = StyleSheet.create({
color: "white", color: "white",
marginVertical: 30, marginVertical: 30,
}, },
pinAttempts: {
fontSize: 18,
fontFamily: 'Inconsolata Regular',
color: "white",
marginTop: -10,
marginBottom: 30
},
btnContainer: { btnContainer: {
flexDirection: 'row', flexDirection: 'row',
width: '74%', width: '74%',

View File

@ -3,12 +3,13 @@ import { StyleSheet, Text, View } from "react-native";
import Button from "../Button"; import Button from "../Button";
type AuthenticationScreenProps = { type AuthenticationScreenProps = {
pinRetryCounter: number;
onPressFunc: () => void; onPressFunc: () => void;
onCancelFunc: () => void; onCancelFunc: () => void;
}; };
const AuthenticationScreen: FC<AuthenticationScreenProps> = props => { const AuthenticationScreen: FC<AuthenticationScreenProps> = props => {
const {onPressFunc, onCancelFunc} = props; const {pinRetryCounter, onPressFunc, onCancelFunc} = props;
return ( return (
<View> <View>

View File

@ -0,0 +1,42 @@
import {FC } from "react";
import { StyleSheet, Text, View } from "react-native";
import Button from "../Button";
type FactoryResetScreenProps = {
pinRetryCounter: number;
onPressFunc: () => void;
onCancelFunc: () => void;
};
const FactoryResetScreen: FC<FactoryResetScreenProps> = props => {
const {pinRetryCounter, onPressFunc, onCancelFunc} = props;
return (
<View>
<View>
<Text style={styles.heading}>Factory reset</Text>
<Text style={styles.prompt}>This will remove the keys from your card. Are you sure?</Text>
<Button label="Next" disabled={false} btnColor="#199515" btnBorderColor="#199515" btnFontSize={17} btnBorderWidth={1} btnWidth="100%" onChangeFunc={onPressFunc} btnJustifyContent='center'></Button>
<Button label="Cancel" disabled={false} btnColor="#199515" btnBorderColor="#199515" btnFontSize={17} btnBorderWidth={1} btnWidth="100%" onChangeFunc={onCancelFunc} btnJustifyContent='center'></Button>
</View>
</View>
)};
const styles = StyleSheet.create({
heading: {
textAlign: 'center',
fontSize: 30,
fontFamily: 'Inconsolata Medium',
color: '#199515',
marginTop: '50%'
},
prompt: {
textAlign: 'center',
fontSize: 18,
fontFamily: 'Inconsolata Regular',
color: 'white',
marginTop: '5%'
}
});
export default FactoryResetScreen;

View File

@ -24,23 +24,19 @@ const InitializationScreen: FC<InitializationScreenProps> = props => {
return true; return true;
} }
const isSamePin = (p: string) => {
return pin === p;
}
const submitPin = (p: string) => { const submitPin = (p: string) => {
if(!isSamePin(p)) { if(pin == p) {
return false; onPressFunc(pin);
return true;
} }
onPressFunc(pin); return false;
return true;
} }
return ( return (
<View> <View>
{ step == PinSteps.InsertPin && <Dialpad prompt={"Choose PIN"} onCancelFunc={onCancelFunc} onNextFunc={insertPin}></Dialpad> } { step == PinSteps.InsertPin && <Dialpad pinRetryCounter={-1} prompt={"Choose PIN"} onCancelFunc={onCancelFunc} onNextFunc={insertPin}></Dialpad> }
{ step == PinSteps.RepeatPin && <Dialpad prompt={"Repeat PIN"} onCancelFunc={() => setStep(PinSteps.InsertPin)} onNextFunc={submitPin}></Dialpad> } { step == PinSteps.RepeatPin && <Dialpad pinRetryCounter={-1} prompt={"Repeat PIN"} onCancelFunc={() => setStep(PinSteps.InsertPin)} onNextFunc={submitPin}></Dialpad> }
</View> </View>
)}; )};

View File

@ -11,12 +11,13 @@ enum LoadMnemonicSteps {
type MnemonicScreenProps = { type MnemonicScreenProps = {
pinRequired: boolean; pinRequired: boolean;
pinRetryCounter: number;
onPressFunc: (mn: string, pin?: string) => void; onPressFunc: (mn: string, pin?: string) => void;
onCancelFunc: () => void; onCancelFunc: () => void;
}; };
const MnemonicScreen: FC<MnemonicScreenProps> = props => { const MnemonicScreen: FC<MnemonicScreenProps> = props => {
const {pinRequired, onPressFunc, onCancelFunc} = props; const {pinRequired, pinRetryCounter, onPressFunc, onCancelFunc} = props;
const [mnemonic, setMnemonic] = useState(''); const [mnemonic, setMnemonic] = useState('');
const [errMessage, setErrMessage] = useState(''); const [errMessage, setErrMessage] = useState('');
const [step, setStep] = useState(LoadMnemonicSteps.InsertMnemonic); const [step, setStep] = useState(LoadMnemonicSteps.InsertMnemonic);
@ -63,7 +64,7 @@ const MnemonicScreen: FC<MnemonicScreenProps> = props => {
<Text style={styles.errorMessage}>{errMessage}</Text> <Text style={styles.errorMessage}>{errMessage}</Text>
</View> </View>
} }
{ step == LoadMnemonicSteps.InsertPin && <Dialpad prompt={"Enter PIN"} onCancelFunc={() => setStep(LoadMnemonicSteps.InsertMnemonic)} onNextFunc={submitPin}></Dialpad>} { step == LoadMnemonicSteps.InsertPin && <Dialpad pinRetryCounter={pinRetryCounter} prompt={"Enter PIN"} onCancelFunc={() => setStep(LoadMnemonicSteps.InsertMnemonic)} onNextFunc={submitPin}></Dialpad>}
</View> </View>
)}; )};