mirror of
https://github.com/selfxyz/self.git
synced 2026-02-08 13:25:59 -05:00
passport read e2e rsa from rust
This commit is contained in:
@@ -1,9 +1,3 @@
|
||||
//
|
||||
// PassportReader.swift
|
||||
// AwesomeProject
|
||||
//
|
||||
// Created by Y E on 27/07/2023.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import React
|
||||
@@ -12,6 +6,55 @@ import NFCPassportReader
|
||||
@available(iOS 15, *)
|
||||
@objc(PassportReader)
|
||||
class PassportReader: NSObject{
|
||||
|
||||
private typealias NFCCheckedContinuation = CheckedContinuation<NFCPassportModel, Error>
|
||||
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()
|
||||
|
||||
@@ -87,15 +130,204 @@ 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 )
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user