import 'dart:typed_data'; import 'package:dmrtd/dmrtd.dart'; import 'package:dmrtd/src/proto/can_key.dart'; import 'package:flutter/material.dart'; import 'package:gascom/widgets/custom_app_bar.dart'; class NfcScreen extends StatefulWidget { const NfcScreen({super.key}); @override State createState() => _NfcScreenState(); } class _NfcScreenState extends State { final NfcProvider _nfc = NfcProvider(); MrtdData? _mrtdData = MrtdData(); String _alertMessage = ""; bool _isReading = false; String id = ''; // should change based on user id card (it's the string below the image) DateTime dateOfBirth = DateTime(2000, 1, 1); // should change based on user id card DateTime dateOfExpiry = DateTime(2029, 1, 1); // should change based on user id card @override void initState() { WidgetsBinding.instance.addPostFrameCallback((_) async { try { _buttonPressed(); } catch (e) { print(e); } }); super.initState(); } @override void dispose() { super.dispose(); } @override Widget build(BuildContext context) { return const Scaffold( appBar: CustomAppBar(), body: SafeArea( child: Center( child: Text("NFC Screen, Nfc has ti be turned on."), ), ), ); } String formatProgressMsg(String message, int percentProgress) { final p = (percentProgress / 20).round(); final full = "🟢 " * p; final empty = "⚪️ " * (5 - p); return "$message\n\n$full$empty"; } void _readMRTD({required AccessKey accessKey, bool isPace = false}) async { try { setState(() { _mrtdData = null; _alertMessage = "Waiting for Passport tag ..."; _isReading = true; }); try { bool demo = false; print("-------------- FIRST -------------"); if (!demo) await _nfc.connect( iosAlertMessage: "Hold your phone near Biometric Passport"); print("-------------- SECOND -------------"); final passport = Passport(_nfc); print("-------------- THIRD -------------"); setState(() { _alertMessage = "Reading Passport ..."; }); print("-------------- 4 -------------"); _nfc.setIosAlertMessage("Trying to read EF.CardAccess ..."); final mrtdData = MrtdData(); print("-------------- 5 -------------"); try { mrtdData.cardAccess = await passport.readEfCardAccess(); } on PassportError { print("-------------- 6 -------------"); //if (e.code != StatusWord.fileNotFound) rethrow; } _nfc.setIosAlertMessage("Trying to read EF.CardSecurity ..."); try { //mrtdData.cardSecurity = await passport.readEfCardSecurity(); } on PassportError { //if (e.code != StatusWord.fileNotFound) rethrow; } _nfc.setIosAlertMessage("Initiating session with PACE..."); //set MrtdData mrtdData.isPACE = isPace; mrtdData.isDBA = accessKey.PACE_REF_KEY_TAG == 0x01 ? true : false; if (isPace) { //PACE session print("-------------- 7 -------------"); await passport.startSessionPACE(accessKey, mrtdData.cardAccess!); } else { print("-------------- 8 -------------"); //BAC session await passport.startSession(accessKey as DBAKey); } print("-------------- 9 -------------"); _nfc.setIosAlertMessage(formatProgressMsg("Reading EF.COM ...", 0)); mrtdData.com = await passport.readEfCOM(); _nfc.setIosAlertMessage( formatProgressMsg("Reading Data Groups ...", 20)); print("-------------- 10 -------------"); if (mrtdData.com!.dgTags.contains(EfDG1.TAG)) { mrtdData.dg1 = await passport.readEfDG1(); print("DG1 mrz country: ${mrtdData.dg1?.mrz.country}"); print("DG1 mrz dateOfBirth: ${mrtdData.dg1?.mrz.dateOfBirth}"); print("DG1 mrz dateOfExpiry: ${mrtdData.dg1?.mrz.dateOfExpiry}"); print("DG1 mrz documentCode: ${mrtdData.dg1?.mrz.documentCode}"); print("DG1 mrz documentNumber: ${mrtdData.dg1?.mrz.documentNumber}"); print("DG1 mrz firstName: ${mrtdData.dg1?.mrz.firstName}"); print("DG1 mrz gender: ${mrtdData.dg1?.mrz.gender}"); print("DG1 mrz lastName: ${mrtdData.dg1?.mrz.lastName}"); print("DG1 mrz nationality: ${mrtdData.dg1?.mrz.nationality}"); print("DG1 mrz optionalData: ${mrtdData.dg1?.mrz.optionalData}"); print("DG1 mrz optionalData2: ${mrtdData.dg1?.mrz.optionalData2}"); print("DG1 mrz version: ${mrtdData.dg1?.mrz.version}"); } print("-------------- 11 -------------"); if (mrtdData.com!.dgTags.contains(EfDG2.TAG)) { mrtdData.dg2 = await passport.readEfDG2(); print("DG2 deviceType: ${mrtdData.dg2?.deviceType}"); print("DG2 expression: ${mrtdData.dg2?.expression}"); print("DG2 eyeColor: ${mrtdData.dg2?.eyeColor}"); print("DG2 faceImageType: ${mrtdData.dg2?.faceImageType}"); print("DG2 facialRecordDataLength: ${mrtdData.dg2?.facialRecordDataLength}"); print("DG2 featureMask: ${mrtdData.dg2?.featureMask}"); print("DG2 gender: ${mrtdData.dg2?.gender}"); print("DG2 hairColor: ${mrtdData.dg2?.hairColor}"); print("DG2 imageColorSpace: ${mrtdData.dg2?.imageColorSpace}"); print("DG2 imageData: ${mrtdData.dg2?.imageData}"); print("DG2 imageHeight: ${mrtdData.dg2?.imageHeight}"); print("DG2 imageType: ${mrtdData.dg2?.imageType}"); print("DG2 imageWidth: ${mrtdData.dg2?.imageWidth}"); print("DG2 lengthOfRecord: ${mrtdData.dg2?.lengthOfRecord}"); print("DG2 nrFeaturePoints: ${mrtdData.dg2?.nrFeaturePoints}"); print("DG2 numberOfFacialImages: ${mrtdData.dg2?.numberOfFacialImages}"); print("DG2 poseAngle: ${mrtdData.dg2?.poseAngle}"); print("DG2 poseAngleUncertainty: ${mrtdData.dg2?.poseAngleUncertainty}"); print("DG2 quality: ${mrtdData.dg2?.quality}"); print("DG2 sourceType: ${mrtdData.dg2?.sourceType}"); print("DG2 versionNumber: ${mrtdData.dg2?.versionNumber}"); } // To read DG3 and DG4 session has to be established with CVCA certificate (not supported). // if(mrtdData.com!.dgTags.contains(EfDG3.TAG)) { // mrtdData.dg3 = await passport.readEfDG3(); // } // if(mrtdData.com!.dgTags.contains(EfDG4.TAG)) { // mrtdData.dg4 = await passport.readEfDG4(); // } if (mrtdData.com!.dgTags.contains(EfDG5.TAG)) { mrtdData.dg5 = await passport.readEfDG5(); } if (mrtdData.com!.dgTags.contains(EfDG6.TAG)) { mrtdData.dg6 = await passport.readEfDG6(); } if (mrtdData.com!.dgTags.contains(EfDG7.TAG)) { mrtdData.dg7 = await passport.readEfDG7(); } if (mrtdData.com!.dgTags.contains(EfDG8.TAG)) { mrtdData.dg8 = await passport.readEfDG8(); } if (mrtdData.com!.dgTags.contains(EfDG9.TAG)) { mrtdData.dg9 = await passport.readEfDG9(); } if (mrtdData.com!.dgTags.contains(EfDG10.TAG)) { mrtdData.dg10 = await passport.readEfDG10(); } if (mrtdData.com!.dgTags.contains(EfDG11.TAG)) { mrtdData.dg11 = await passport.readEfDG11(); print("DG11 custodyInformation: ${mrtdData.dg11?.custodyInformation}"); print("DG11 ersonalSummary: ${mrtdData.dg11?.ersonalSummary}"); print("DG11 fullDateOfBirth: ${mrtdData.dg11?.fullDateOfBirth}"); print("DG11 nameOfHolder: ${mrtdData.dg11?.nameOfHolder}"); print("DG11 otherNames: ${mrtdData.dg11?.otherNames}"); print("DG11 otherValidTDNumbers: ${mrtdData.dg11?.otherValidTDNumbers}"); print("DG11 permanentAddress: ${mrtdData.dg11?.permanentAddress}"); print("DG11 personalNumber: ${mrtdData.dg11?.personalNumber}"); print("DG11 placeOfBirth: ${mrtdData.dg11?.placeOfBirth}"); print("DG11 profession: ${mrtdData.dg11?.profession}"); print("DG11 proofOfCitizenship: ${mrtdData.dg11?.proofOfCitizenship}"); print("DG11 telephone: ${mrtdData.dg11?.telephone}"); print("DG11 title: ${mrtdData.dg11?.title}"); } if (mrtdData.com!.dgTags.contains(EfDG12.TAG)) { mrtdData.dg12 = await passport.readEfDG12(); print("DG12 dateOfIssue: ${mrtdData.dg12?.dateOfIssue}"); print("DG12 dateOfIssue: ${mrtdData.dg12?.issuingAuthority}"); } if (mrtdData.com!.dgTags.contains(EfDG13.TAG)) { mrtdData.dg13 = await passport.readEfDG13(); } if (mrtdData.com!.dgTags.contains(EfDG14.TAG)) { mrtdData.dg14 = await passport.readEfDG14(); } if (mrtdData.com!.dgTags.contains(EfDG15.TAG)) { mrtdData.dg15 = await passport.readEfDG15(); _nfc.setIosAlertMessage(formatProgressMsg("Doing AA ...", 60)); print("DG15 aaPublicKey: ${mrtdData.dg15?.aaPublicKey}"); mrtdData.aaSig = await passport.activeAuthenticate(Uint8List(8)); } if (mrtdData.com!.dgTags.contains(EfDG16.TAG)) { mrtdData.dg16 = await passport.readEfDG16(); } print("-------------- 12 -------------"); _nfc.setIosAlertMessage(formatProgressMsg("Reading EF.SOD ...", 80)); mrtdData.sod = await passport.readEfSOD(); setState(() { _mrtdData = mrtdData; }); setState(() { _alertMessage = ""; }); } on Exception catch (e) { final se = e.toString().toLowerCase(); String alertMsg = "An error has occurred while reading Passport!"; if (e is PassportError) { if (se.contains("security status not satisfied")) { alertMsg = "Failed to initiate session with passport.\nCheck input data!"; } print("PassportError: ${e.message}"); } else { print( "An exception was encountered while trying to read Passport: $e"); } if (se.contains('timeout')) { alertMsg = "Timeout while waiting for Passport tag"; } else if (se.contains("tag was lost")) { alertMsg = "Tag was lost. Please try again!"; } else if (se.contains("invalidated by user")) { alertMsg = ""; } setState(() { _alertMessage = alertMsg; }); } finally { if (_alertMessage.isNotEmpty) { await _nfc.disconnect(iosErrorMessage: _alertMessage); } else { await _nfc.disconnect( iosAlertMessage: formatProgressMsg("Finished", 100)); } setState(() { _isReading = false; }); } } on Exception catch (e) { print("Read MRTD error: $e"); } } void _readMRTDOld() async { try { setState(() { _mrtdData = null; _alertMessage = "Waiting for Passport tag ..."; _isReading = true; }); await _nfc.connect( iosAlertMessage: "Hold your phone near Biometric Passport"); final passport = Passport(_nfc); setState(() { _alertMessage = "Reading Passport ..."; }); _nfc.setIosAlertMessage("Trying to read EF.CardAccess ..."); final mrtdData = MrtdData(); try { mrtdData.cardAccess = await passport.readEfCardAccess(); } on PassportError { //if (e.code != StatusWord.fileNotFound) rethrow; } _nfc.setIosAlertMessage("Trying to read EF.CardSecurity ..."); try { mrtdData.cardSecurity = await passport.readEfCardSecurity(); } on PassportError { //if (e.code != StatusWord.fileNotFound) rethrow; } _nfc.setIosAlertMessage("Initiating session ..."); final bacKeySeed = DBAKey(id, dateOfBirth, dateOfExpiry); await passport.startSession(bacKeySeed); _nfc.setIosAlertMessage(formatProgressMsg("Reading EF.COM ...", 0)); mrtdData.com = await passport.readEfCOM(); _nfc.setIosAlertMessage(formatProgressMsg("Reading Data Groups ...", 20)); if (mrtdData.com!.dgTags.contains(EfDG1.TAG)) { mrtdData.dg1 = await passport.readEfDG1(); print(mrtdData.dg1?.mrz); } if (mrtdData.com!.dgTags.contains(EfDG2.TAG)) { mrtdData.dg2 = await passport.readEfDG2(); print("DG2 deviceType: ${mrtdData.dg2?.deviceType}"); print("DG2 expression: ${mrtdData.dg2?.expression}"); print("DG2 eyeColor: ${mrtdData.dg2?.eyeColor}"); print("DG2 faceImageType: ${mrtdData.dg2?.faceImageType}"); print("DG2 facialRecordDataLength: ${mrtdData.dg2?.facialRecordDataLength}"); print("DG2 featureMask: ${mrtdData.dg2?.featureMask}"); print("DG2 fid: ${mrtdData.dg2?.fid}"); print("DG2 gender: ${mrtdData.dg2?.gender}"); print("DG2 hairColor: ${mrtdData.dg2?.hairColor}"); print("DG2 imageColorSpace: ${mrtdData.dg2?.imageColorSpace}"); print("DG2 imageData: ${mrtdData.dg2?.imageData}"); print("DG2 imageHeight: ${mrtdData.dg2?.imageHeight}"); print("DG2 imageType: ${mrtdData.dg2?.imageType}"); print("DG2 imageWidth: ${mrtdData.dg2?.imageWidth}"); print("DG2 nrFeaturePoints: ${mrtdData.dg2?.nrFeaturePoints}"); print("DG2 numberOfFacialImages: ${mrtdData.dg2?.numberOfFacialImages}"); print("DG2 lengthOfRecord: ${mrtdData.dg2?.lengthOfRecord}"); print("DG2 poseAngle: ${mrtdData.dg2?.poseAngle}"); print("DG2 poseAngleUncertainty: ${mrtdData.dg2?.poseAngleUncertainty}"); print("DG2 quality: ${mrtdData.dg2?.quality}"); print("DG2 sourceType: ${mrtdData.dg2?.sourceType}"); print("DG2 versionNumber: ${mrtdData.dg2?.versionNumber}"); print("DG2 tag: ${mrtdData.dg2?.tag}"); print("DG2 sfi: ${mrtdData.dg2?.sfi}"); } // To read DG3 and DG4 session has to be established with CVCA certificate (not supported). // if(mrtdData.com!.dgTags.contains(EfDG3.TAG)) { // mrtdData.dg3 = await passport.readEfDG3(); // } // if(mrtdData.com!.dgTags.contains(EfDG4.TAG)) { // mrtdData.dg4 = await passport.readEfDG4(); // } if (mrtdData.com!.dgTags.contains(EfDG5.TAG)) { mrtdData.dg5 = await passport.readEfDG5(); } if (mrtdData.com!.dgTags.contains(EfDG6.TAG)) { mrtdData.dg6 = await passport.readEfDG6(); } if (mrtdData.com!.dgTags.contains(EfDG7.TAG)) { mrtdData.dg7 = await passport.readEfDG7(); } if (mrtdData.com!.dgTags.contains(EfDG8.TAG)) { mrtdData.dg8 = await passport.readEfDG8(); } if (mrtdData.com!.dgTags.contains(EfDG9.TAG)) { mrtdData.dg9 = await passport.readEfDG9(); } if (mrtdData.com!.dgTags.contains(EfDG10.TAG)) { mrtdData.dg10 = await passport.readEfDG10(); } if (mrtdData.com!.dgTags.contains(EfDG11.TAG)) { mrtdData.dg11 = await passport.readEfDG11(); print("DG11 custodyInformation: ${mrtdData.dg11?.custodyInformation}"); print("DG11 ersonalSummary: ${mrtdData.dg11?.ersonalSummary}"); print("DG11 fullDateOfBirth: ${mrtdData.dg11?.fullDateOfBirth}"); print("DG11 nameOfHolder: ${mrtdData.dg11?.nameOfHolder}"); print("DG11 otherNames: ${mrtdData.dg11?.otherNames}"); print("DG11 otherValidTDNumbers: ${mrtdData.dg11?.otherValidTDNumbers}"); print("DG11 permanentAddress: ${mrtdData.dg11?.permanentAddress}"); print("DG11 personalNumber: ${mrtdData.dg11?.personalNumber}"); print("DG11 placeOfBirth: ${mrtdData.dg11?.placeOfBirth}"); print("DG11 profession: ${mrtdData.dg11?.profession}"); print("DG11 proofOfCitizenship: ${mrtdData.dg11?.proofOfCitizenship}"); print("DG11 telephone: ${mrtdData.dg11?.telephone}"); print("DG11 title: ${mrtdData.dg11?.title}"); } if (mrtdData.com!.dgTags.contains(EfDG12.TAG)) { mrtdData.dg12 = await passport.readEfDG12(); print("DG12 dateOfIssue: ${mrtdData.dg12?.dateOfIssue}"); print("DG12 issuingAuthority: ${mrtdData.dg12?.issuingAuthority}"); } if (mrtdData.com!.dgTags.contains(EfDG13.TAG)) { mrtdData.dg13 = await passport.readEfDG13(); } if (mrtdData.com!.dgTags.contains(EfDG14.TAG)) { mrtdData.dg14 = await passport.readEfDG14(); } if (mrtdData.com!.dgTags.contains(EfDG15.TAG)) { mrtdData.dg15 = await passport.readEfDG15(); print("DG12 issuingAuthority: ${mrtdData.dg15?.aaPublicKey}"); _nfc.setIosAlertMessage(formatProgressMsg("Doing AA ...", 60)); mrtdData.aaSig = await passport.activeAuthenticate(Uint8List(8)); } if (mrtdData.com!.dgTags.contains(EfDG16.TAG)) { mrtdData.dg16 = await passport.readEfDG16(); } _nfc.setIosAlertMessage(formatProgressMsg("Reading EF.SOD ...", 80)); mrtdData.sod = await passport.readEfSOD(); setState(() { _mrtdData = mrtdData; }); setState(() { _alertMessage = ""; }); } on Exception catch (e) { final se = e.toString().toLowerCase(); String alertMsg = "An error has occurred while reading Passport!"; if (e is PassportError) { if (se.contains("security status not satisfied")) { alertMsg = "Failed to initiate session with passport.\nCheck input data!"; } print("PassportError: ${e.message}"); } else { print("An exception was encountered while trying to read Passport: $e"); } if (se.contains('timeout')) { alertMsg = "Timeout while waiting for Passport tag"; } else if (se.contains("tag was lost")) { alertMsg = "Tag was lost. Please try again!"; } else if (se.contains("invalidated by user")) { alertMsg = ""; } setState(() { _alertMessage = alertMsg; }); } finally { if (_alertMessage.isNotEmpty) { await _nfc.disconnect(iosErrorMessage: _alertMessage); } else { await _nfc.disconnect( iosAlertMessage: formatProgressMsg("Finished", 100)); } setState(() { _isReading = false; }); } } void _buttonPressed() async { String _can = ""; print("Button pressed"); //Check on what tab we are if (true) { //DBA tab // String errorText = ""; // if (_doe.text.isEmpty) { // errorText += "Please enter date of expiry!\n"; // } // if (_dob.text.isEmpty) { // errorText += "Please enter date of birth!\n"; // } // if (_docNumber.text.isEmpty) { // errorText += "Please enter passport number!"; // } // setState(() { // _alertMessage = errorText; // }); // //If there is an error, just jump out of the function // if (errorText.isNotEmpty) return; final bacKeySeed = DBAKey(id, dateOfBirth, dateOfExpiry, paceMode: true); _readMRTD(accessKey: bacKeySeed, isPace: true); } else { // PACE tab // String errorText = ""; // if (_can.isEmpty) { // errorText = "Please enter CAN number!"; // } else if (_can.text.length != 6) { // errorText = "CAN number must be exactly 6 digits long!"; // } // setState(() { // _alertMessage = errorText; // }); // //If there is an error, just jump out of the function // if (errorText.isNotEmpty) return; // final canKeySeed = CanKey(_can.text); final canKeySeed = CanKey(id); _readMRTD(accessKey: canKeySeed, isPace: true); } } } class MrtdData { EfCardAccess? cardAccess; EfCardSecurity? cardSecurity; EfCOM? com; EfSOD? sod; EfDG1? dg1; EfDG2? dg2; EfDG3? dg3; EfDG4? dg4; EfDG5? dg5; EfDG6? dg6; EfDG7? dg7; EfDG8? dg8; EfDG9? dg9; EfDG10? dg10; EfDG11? dg11; EfDG12? dg12; EfDG13? dg13; EfDG14? dg14; EfDG15? dg15; EfDG16? dg16; Uint8List? aaSig; bool? isPACE; bool? isDBA; } final Map dgTagToString = { EfDG1.TAG: 'EF.DG1', EfDG2.TAG: 'EF.DG2', EfDG3.TAG: 'EF.DG3', EfDG4.TAG: 'EF.DG4', EfDG5.TAG: 'EF.DG5', EfDG6.TAG: 'EF.DG6', EfDG7.TAG: 'EF.DG7', EfDG8.TAG: 'EF.DG8', EfDG9.TAG: 'EF.DG9', EfDG10.TAG: 'EF.DG10', EfDG11.TAG: 'EF.DG11', EfDG12.TAG: 'EF.DG12', EfDG13.TAG: 'EF.DG13', EfDG14.TAG: 'EF.DG14', EfDG15.TAG: 'EF.DG15', EfDG16.TAG: 'EF.DG16' };