passport read e2e rsa from rust

This commit is contained in:
0xturboblitz
2023-10-22 14:52:20 +02:00
parent 1d76de2807
commit f6bc02bc2d
2 changed files with 246 additions and 14 deletions

View File

@@ -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 )
}
}
}
}