diff --git a/README.md b/README.md index bfd310d92..a79b7b047 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ As a first application, users who can prove they indeed hold a valid passport ca - ✅ Basic passport verifier circuit - 🚧 Optimization -- ✅ Selective disclosure +- 🚧 Selective disclosure - ✅ Basic react native frontend - ✅ Passport verification pipeline, android - 🚧 Passport verification pipeline, iOS diff --git a/app/ios/PassportReader.swift b/app/ios/PassportReader.swift index 8db10d953..2b2b33fa7 100644 --- a/app/ios/PassportReader.swift +++ b/app/ios/PassportReader.swift @@ -1,3 +1,9 @@ +// +// PassportReader.swift +// AwesomeProject +// +// Created by Y E on 27/07/2023. +// import Foundation import React @@ -6,55 +12,6 @@ import NFCPassportReader @available(iOS 15, *) @objc(PassportReader) class PassportReader: NSObject{ - - private typealias NFCCheckedContinuation = CheckedContinuation - private var nfcContinuation: NFCCheckedContinuation? - - private var passport : NFCPassportModel = NFCPassportModel() - - private var readerSession: NFCTagReaderSession? - private var currentlyReadingDataGroup : DataGroupId? - - private var dataGroupsToRead : [DataGroupId] = [] - private var readAllDatagroups = false - private var skipSecureElements = true - private var skipCA = false - private var skipPACE = false - - private var bacHandler : BACHandler? - private var caHandler : ChipAuthenticationHandler? - private var paceHandler : PACEHandler? - private var mrzKey : String = "" - private var dataAmountToReadOverride : Int? = nil - - private var scanCompletedHandler: ((NFCPassportModel?, NFCPassportReaderError?)->())! - private var nfcViewDisplayMessageHandler: ((NFCViewDisplayMessage) -> String?)? - private var masterListURL : URL? - private var shouldNotReportNextReaderSessionInvalidationErrorUserCanceled : Bool = false - - // By default, Passive Authentication uses the new RFS5652 method to verify the SOD, but can be switched to use - // the previous OpenSSL CMS verification if necessary - public var passiveAuthenticationUsesOpenSSL : Bool = false - - public init( logLevel: LogLevel = .info, masterListURL: URL? = nil ) { - super.init() - - Log.logLevel = logLevel - self.masterListURL = masterListURL - } - - public func setMasterListURL( _ masterListURL : URL ) { - self.masterListURL = masterListURL - } - - // This function allows you to override the amount of data the TagReader tries to read from the NFC - // chip. NOTE - this really shouldn't be used for production but is useful for testing as different - // passports support different data amounts. - // It appears that the most reliable is 0xA0 (160 chars) but some will support arbitary reads (0xFF or 256) - public func overrideNFCDataAmountToRead( amount: Int ) { - dataAmountToReadOverride = amount - } - private let passportReader = NFCPassportReader.PassportReader() @@ -130,204 +87,15 @@ class PassportReader: NSObject{ } } - @objc(scanPassport:dateOfBirth:dateOfExpiry:resolve:reject:) - func scanPassport(_ passportNumber: String, dateOfBirth: String, dateOfExpiry: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { - let concatenatedString = "\(passportNumber), \(dateOfBirth), \(dateOfExpiry)" - resolve(concatenatedString) - } +// @objc(scanPassport:dateOfBirth:dateOfExpiry:resolve:reject:) +// func scanPassport(_ passportNumber: String, dateOfBirth: String, dateOfExpiry: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { +// let concatenatedString = "\(passportNumber), \(dateOfBirth), \(dateOfExpiry)" +// resolve(concatenatedString) +// } @objc static func requiresMainQueueSetup() -> Bool { return true } - - - - public func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) { - Log.debug( "tagReaderSession:didDetect - \(tags[0])" ) - if tags.count > 1 { - Log.debug( "tagReaderSession:more than 1 tag detected! - \(tags)" ) - - let errorMessage = NFCViewDisplayMessage.error(.MoreThanOneTagFound) - self.invalidateSession(errorMessage: errorMessage, error: NFCPassportReaderError.MoreThanOneTagFound) - return - } - - let tag = tags.first! - var passportTag: NFCISO7816Tag - switch tags.first! { - case let .iso7816(tag): - passportTag = tag - default: - Log.debug( "tagReaderSession:invalid tag detected!!!" ) - - let errorMessage = NFCViewDisplayMessage.error(NFCPassportReaderError.TagNotValid) - self.invalidateSession(errorMessage:errorMessage, error: NFCPassportReaderError.TagNotValid) - return - } - - Task { [passportTag] in - do { - try await session.connect(to: tag) - - Log.debug( "tagReaderSession:connected to tag - starting authentication" ) - self.updateReaderSessionMessage( alertMessage: NFCViewDisplayMessage.authenticatingWithPassport(0) ) - - let tagReader = TagReader(tag:passportTag) - - if let newAmount = self.dataAmountToReadOverride { - tagReader.overrideDataAmountToRead(newAmount: newAmount) - } - - tagReader.progress = { [unowned self] (progress) in - if let dgId = self.currentlyReadingDataGroup { - self.updateReaderSessionMessage( alertMessage: NFCViewDisplayMessage.readingDataGroupProgress(dgId, progress) ) - } else { - self.updateReaderSessionMessage( alertMessage: NFCViewDisplayMessage.authenticatingWithPassport(progress) ) - } - } - - let passportModel = try await self.startReading( tagReader : tagReader) - nfcContinuation?.resume(returning: passportModel) - nfcContinuation = nil - - - } catch let error as NFCPassportReaderError { - let errorMessage = NFCViewDisplayMessage.error(error) - self.invalidateSession(errorMessage: errorMessage, error: error) - } catch let error { - - nfcContinuation?.resume(throwing: error) - nfcContinuation = nil - Log.debug( "tagReaderSession:failed to connect to tag - \(error.localizedDescription)" ) - let errorMessage = NFCViewDisplayMessage.error(NFCPassportReaderError.ConnectionError) - self.invalidateSession(errorMessage: errorMessage, error: NFCPassportReaderError.ConnectionError) - } - } - } - - func updateReaderSessionMessage(alertMessage: NFCViewDisplayMessage ) { - self.readerSession?.alertMessage = self.nfcViewDisplayMessageHandler?(alertMessage) ?? alertMessage.description - } -} - -@available(iOS 15, *) -extension PassportReader { - - func startReading(tagReader : TagReader) async throws -> NFCPassportModel { - - if !skipPACE { - do { - let data = try await tagReader.readCardAccess() - Log.verbose( "Read CardAccess - data \(binToHexRep(data))" ) - let cardAccess = try CardAccess(data) - passport.cardAccess = cardAccess - - Log.info( "Starting Password Authenticated Connection Establishment (PACE)" ) - - let paceHandler = try PACEHandler( cardAccess: cardAccess, tagReader: tagReader ) - try await paceHandler.doPACE(mrzKey: mrzKey ) - passport.PACEStatus = .success - Log.debug( "PACE Succeeded" ) - } catch { - passport.PACEStatus = .failed - Log.error( "PACE Failed - falling back to BAC" ) - } - - _ = try await tagReader.selectPassportApplication() - } - - // If either PACE isn't supported, we failed whilst doing PACE or we didn't even attempt it, then fall back to BAC - if passport.PACEStatus != .success { - try await doBACAuthentication(tagReader : tagReader) - } - - // Now to read the datagroups - try await readDataGroups(tagReader: tagReader) - - self.updateReaderSessionMessage(alertMessage: NFCViewDisplayMessage.successfulRead) - - try await doActiveAuthenticationIfNeccessary(tagReader : tagReader) - self.shouldNotReportNextReaderSessionInvalidationErrorUserCanceled = true - self.readerSession?.invalidate() - - // If we have a masterlist url set then use that and verify the passport now - self.passport.verifyPassport(masterListURL: self.masterListURL, useCMSVerification: self.passiveAuthenticationUsesOpenSSL) - - return self.passport - } - - - - func doBACAuthentication(tagReader : TagReader) async throws { - self.currentlyReadingDataGroup = nil - - Log.info( "Starting Basic Access Control (BAC)" ) - - self.passport.BACStatus = .failed - - self.bacHandler = BACHandler( tagReader: tagReader ) - try await bacHandler?.performBACAndGetSessionKeys( mrzKey: mrzKey ) - Log.info( "Basic Access Control (BAC) - SUCCESS!" ) - - self.passport.BACStatus = .success - } - - func readDataGroups( tagReader: TagReader ) async throws { - - // Read COM - var DGsToRead = [DataGroupId]() - - self.updateReaderSessionMessage( alertMessage: NFCViewDisplayMessage.readingDataGroupProgress(.COM, 0) ) - if let com = try await readDataGroup(tagReader:tagReader, dgId:.COM) as? COM { - self.passport.addDataGroup( .COM, dataGroup:com ) - - // SOD and COM shouldn't be present in the DG list but just in case (worst case here we read the sod twice) - DGsToRead = [.SOD] + com.dataGroupsPresent.map { DataGroupId.getIDFromName(name:$0) } - DGsToRead.removeAll { $0 == .COM } - } - - if DGsToRead.contains( .DG14 ) { - DGsToRead.removeAll { $0 == .DG14 } - - if !skipCA { - // Do Chip Authentication - if let dg14 = try await readDataGroup(tagReader:tagReader, dgId:.DG14) as? DataGroup14 { - self.passport.addDataGroup( .DG14, dataGroup:dg14 ) - let caHandler = ChipAuthenticationHandler(dg14: dg14, tagReader: tagReader) - - if caHandler.isChipAuthenticationSupported { - do { - // Do Chip authentication and then continue reading datagroups - try await caHandler.doChipAuthentication() - self.passport.chipAuthenticationStatus = .success - } catch { - Log.info( "Chip Authentication failed - re-establishing BAC") - self.passport.chipAuthenticationStatus = .failed - - // Failed Chip Auth, need to re-establish BAC - try await doBACAuthentication(tagReader: tagReader) - } - } - } - } - } - - // If we are skipping secure elements then remove .DG3 and .DG4 - if self.skipSecureElements { - DGsToRead = DGsToRead.filter { $0 != .DG3 && $0 != .DG4 } - } - - if self.readAllDatagroups != true { - DGsToRead = DGsToRead.filter { dataGroupsToRead.contains($0) } - } - for dgId in DGsToRead { - self.updateReaderSessionMessage( alertMessage: NFCViewDisplayMessage.readingDataGroupProgress(dgId, 0) ) - if let dg = try await readDataGroup(tagReader:tagReader, dgId:dgId) { - self.passport.addDataGroup( dgId, dataGroup:dg ) - } - } - } - }