AndyQ / NFCPassportReader

NFCPassportReader for iOS 13
MIT License
749 stars 242 forks source link

Unable to read German passport with BAC - mutual authenticate issue #169

Closed kirillivonin closed 1 year ago

kirillivonin commented 1 year ago

Hi,

I've recently updated to the newest version of NFCPassportReader and started having issues with German passports using BAC. Basically, I'm having the same issue as here: Reference data not found, so it appears the issue wasn't solved :(

Here's the log of the event from start to error:

` 2023-01-11 16:22:33.4820 - tagReaderSessionDidBecomeActive

2023-01-11 16:22:33.6260 - tagReaderSession:didDetect - iso7816(<NFCISO7816Tag: 0x282b22eb0>)

2023-01-11 16:22:33.6270 - tagReaderSession:connected to tag - starting authentication

2023-01-11 16:22:33.627622+0100 TestApp[7324:797609] [CoreNFC] 00000002 817b1170 -[NFCTagReaderSession setAlertMessage:]:90 (null)

2023-01-11 16:22:33.6280 - Starting Basic Access Control (BAC)

2023-01-11 16:22:33.6280 - BACHandler - deriving Document Basic Access Keys

2023-01-11 16:22:33.6280 - Calculate the SHA-1 hash of MRZ_information

2023-01-11 16:22:33.6280 - MRZ KEY - C15NRGH4X481031693204245

2023-01-11 16:22:33.6300 - sha1(MRZ_information): 04B8B3B52D8210189D2F0E9FF1BE9CBA7193C1A0

2023-01-11 16:22:33.6300 - Take the most significant 16 bytes to form the Kseed

2023-01-11 16:22:33.6300 - Kseed: 04B8B3B52D8210189D2F0E9FF1BE9CBA

2023-01-11 16:22:33.6300 - Calculate the Basic Access Keys (Kenc and Kmac) using TR-SAC 1.01, 4.2

2023-01-11 16:22:33.6310 - BACHandler - Getting initial challenge

2023-01-11 16:22:33.6310 - TagReader - sending [0x00, 0x84, 0x00, 0x00, 0x08]

2023-01-11 16:22:33.6790 - TagReader - Received response

2023-01-11 16:22:33.6790 - TagReader [unprotected] [0x0d, 0x8e, 0x79, 0x87, 0xd7, 0x19, 0xc5, 0x40, ], sw1:0x90 sw2:0x00

2023-01-11 16:22:33.6800 - DATA - [13, 142, 121, 135, 215, 25, 197, 64]

2023-01-11 16:22:33.6800 - BACHandler - Doing mutual authentication

2023-01-11 16:22:33.6800 - Request an 8 byte random number from the MRTD's chip

2023-01-11 16:22:33.6800 - RND.ICC: 0D8E7987D719C540

2023-01-11 16:22:33.6800 - Generate an 8 byte random and a 16 byte random

2023-01-11 16:22:33.6800 - RND.IFD: DB36560D477D3564

2023-01-11 16:22:33.6800 - RND.Kifd: AFD1D04E48B6CF693FBBBEDE5D1A30F8

2023-01-11 16:22:33.6800 - Concatenate RND.IFD, RND.ICC and Kifd

2023-01-11 16:22:33.6810 - S: DB36560D477D35640D8E7987D719C540AFD1D04E48B6CF693FBBBEDE5D1A30F8

2023-01-11 16:22:33.6810 - Encrypt S with TDES key Kenc as calculated in Appendix 5.2

2023-01-11 16:22:33.6820 - Eifd: 75D4D946BA168A9D461E40943F2EC26F820620324D3D9492534F54DC880694BC

2023-01-11 16:22:33.6820 - Calc mac

2023-01-11 16:22:33.6820 - x0: 75D4D946BA168A9D

2023-01-11 16:22:33.6820 - y0: 16B7947C7F42FA86

2023-01-11 16:22:33.6820 - x1: 461E40943F2EC26F

2023-01-11 16:22:33.6820 - y1: 02F65983ACF65715

2023-01-11 16:22:33.6820 - x2: 820620324D3D9492

2023-01-11 16:22:33.6830 - y2: 72F11D3375CFDD59

2023-01-11 16:22:33.6830 - x3: 534F54DC880694BC

2023-01-11 16:22:33.6830 - y3: 9794054327BC9B49

2023-01-11 16:22:33.6830 - x4: 8000000000000000

2023-01-11 16:22:33.6830 - y4: 7DB41E0D07AED8C8

2023-01-11 16:22:33.6840 - y: 7DB41E0D07AED8C8

2023-01-11 16:22:33.6840 - bkey: DE6C7FA82991F89C

2023-01-11 16:22:33.6840 - akey: 99ED5FAEEEE601D5

2023-01-11 16:22:33.6840 - b: 836776C237C70800

2023-01-11 16:22:33.6840 - a: 6DDE4E3848A2FDE7

2023-01-11 16:22:33.6850 - Compute MAC over eifd with TDES key Kmac as calculated in-Appendix 5.2

2023-01-11 16:22:33.6850 - Mifd: 6DDE4E3848A2FDE7

2023-01-11 16:22:33.6850 - Construct command data for MUTUAL AUTHENTICATE

2023-01-11 16:22:33.6850 - cmd_data: 75D4D946BA168A9D461E40943F2EC26F820620324D3D9492534F54DC880694BC6DDE4E3848A2FDE7

2023-01-11 16:22:33.6860 - TagReader - sending [0x00, 0x82, 0x00, 0x00, 0x28, 0x75, 0xD4, 0xD9, 0x46, 0xBA, 0x16, 0x8A, 0x9D, 0x46, 0x1E, 0x40, 0x94, 0x3F, 0x2E, 0xC2, 0x6F, 0x82, 0x06, 0x20, 0x32, 0x4D, 0x3D, 0x94, 0x92, 0x53, 0x4F, 0x54, 0xDC, 0x88, 0x06, 0x94, 0xBC, 0x6D, 0xDE, 0x4E, 0x38, 0x48, 0xA2, 0xFD, 0xE7, 0x00]

2023-01-11 16:22:33.7840 - TagReader - Received response

2023-01-11 16:22:33.7850 - TagReader [unprotected] [], sw1:0x6a sw2:0x88

2023-01-11 16:22:33.7850 - Error reading tag: sw1 - 0x6A, sw2 - 0x88

2023-01-11 16:22:33.7860 - reason: Referenced data not found `

fuggly commented 1 year ago

@kirillivonin @AndyQ I had exactly the same problem with some swiss passports. Newest version of the passport, supporting PACE works perfectly with 2.0.1, but older which do not support PACE fail with - Error reading tag: sw1 - 0x6A, sw2 - 0x80 (the last is different). However.. when I reverted the change in TagReader to the one from version 1.1.9 (the two expectedSize from -1 back to 256), everything worked seamlessly again. Maybe that helps

danydev commented 1 year ago

@kirillivonin did you try reverting the fix? did it work for you? Since it looks like some passports fail with the former and some with the latter, we may try doing first a try with 256 and then fallback to -1.

AndyQ commented 1 year ago

So it sounds like it's older passports that have issues with a -1 length. Could you let me know the expiry date of any passports that fail with the -1?

Will have a think how best to approach this. Not sure yet if I can assume that if the issue date is < xxx then default to 256 otherwise use -1.

It will be a bit tricky for me to test as all my available passports work fine with both -1 and 256! Also, I'm not yet sure if when this fails, I'd need to totally restart the connection (right from initial passport detection) as with some passports if they get an error then they just won't process anything else on that session.

Will have a think.

Thormeard commented 1 year ago

Hi, I'm also have the same problem with a German passport. I have tried both 1.1.9 version (the one im using im my app) and your example app (latest version, 2.0.1). Would be happy to debug/help if needed Passport will expire in 2032 (I think it was issued in 2022)

Here is .debug logs (1.1.9):

tagReaderSession: didDetect - iso7816(<NFCIS07816Tag: 0x282a296b0>)
tagReaderSession: connecting to tag tagReaderSession: connected to tag -iso7816(<NFCIS07816Tag: 0x282a296b0>)
tagReaderSession: connected to tag - starting authentication
[CoreNFC] - [NFCTagReaderSession setAlertMessage:]:101 (null)
Error reading tag: sw1 - 0x67, sw2 - 0x00 reason: Wrong length
Re-selecting eMRTD Application
Error reading tag: sw1 - 0x67, sw2 - 0x00 reason: Wrong length
Starting Basic Access Control (BAC)
BACHandler - deriving Document Basic Access Keys
BACHandler - Getting initial challenge
BACHandler - Doing mutual authentication
Error reading tag: sw1 - 0x6A, Sw2 - 0x88
reason: Referenced data not found
ERROR - Referenced data not found
BAC Failed
Error: Optional (NFCPassportReader.NCPassportReaderError.ResponseError("Referenceddatanotfound", 106, 136))
Thormeard commented 1 year ago

More logs. This ones is from example app (which uses latest 2.0.1 version)

Using version 1.1.4.1
tagReaderSessionDidBecomeActive
tagReaderSession:didDetect - iso7816(<NFCISO7816Tag: 0x282408780>)
tagReaderSession:connected to tag - starting authentication
TagReader - Number of data bytes to read - 40
Starting Password Authenticated Connection Establishment (PACE)
Performing PACE with id-PACE-ECDH-GM-AES-CBC-CMAC-128
Doing PACE Step1...
Doing PACE Step2...
   Using General Mapping (GM)...
Generating ECDH mapping keys from parameterSpec - 927
Sending public mapping key to passport..
Received passports public mapping key
Doing ECDH Mapping agreement
Doing PACE Step3 - Key Exchange
Generated Ephemeral key pair
Sending ephemeral public key to passport
Doing PACE Step4 Key Agreement...
Computing shared secret...
Deriving ksEnc and ksMac keys from shared secret
Generating authentication token
Sending auth token to passport
Auth token from passport matches expected token!
Restarting secure messaging using AES encryption
PACE SUCCESSFUL
PACE Succeeded
Re-selecting eMRTD Application
Reading tag - COM
Error reading tag: sw1 - 0x6A, sw2 - 0x82
reason: File not found
TagError reading tag - ResponseError("File not found", 106, 130)
ERROR - File not found
Starting Basic Access Control (BAC)
BACHandler - deriving Document Basic Access Keys
BACHandler - Getting initial challenge
BACHandler - Doing mutual authentication
Error reading tag: sw1 - 0x6A, sw2 - 0x81
reason: Function not supported
danydev commented 1 year ago

So we have two different problems:

@fuggly can you tell us who issued your documents along with date? Thanks

@AndyQ given Thormeard is able to do tests with german passport, what do you recommend to provide in order to understand the issue better? Would be really convenient being able to work with German passports as well.

AndyQ commented 1 year ago

@danydev @Thormeard, so for the 1st try (1.1.9), that is failing BAC - its not even attempting PACE as the first step fails, but not quite sure why (not enough info). Can you increase the logging level to debug?

The 2nd try (2.0.1), PACE succeeds, but it looks like it fails to read the COM data group - file not found! (which is where the supported Datagroups are held). I thought that COM was a mandatory data group.

Then, it retries BAC but I suspect at that point the passport has decided enough is enough and drops its keys and connection resulting in that failing then. Again, not enough info though.

@Thormeard Can you try the ReadID app and see if that can read it OK? and if so, what Datagroups does it find?

Thormeard commented 1 year ago

@AndyQ @danydev I managed to fix it! What I did is changed this line: https://github.com/AndyQ/NFCPassportReader/blob/main/Sources/NFCPassportReader/TagReader.swift#L237 from 256 to -1 And it works with latest version (2.0.1)! I suspect expectedResponseLength is about hash length? Maybe newer passports have longer hashes? Also tried Russian and Dutch passports, they still work with this fix (but they worked earlier anyway with both versions)

To make it work on 1.1.9 I need to apply both -1 changes too.

Let me know if you need extra information from me

Thormeard commented 1 year ago

I also found this closed merge request https://github.com/AndyQ/NFCPassportReader/pull/153 And this is the same fix I did to make German passport work. There was 3 changes to -1, but in the last commit there was only 2 https://github.com/AndyQ/NFCPassportReader/commit/43892bbd3efd46daae97fe844d1b28baeb8e8208 I think we just missing the 3rd one to make German passports work

danydev commented 1 year ago

that's cool! Maybe there is a reason, let's wait for @AndyQ

@fuggly would you be able to test with such fix?

@kirillivonin are you able to test it as well?

AndyQ commented 1 year ago

@Thormeard Good spot! I must have missed that. I'll fix that and release a new version shortly!

AndyQ commented 1 year ago

OK v2.0.2 is up - with that fix in. I've tested it against all the passports I have and all worked fine (selectApplication, PACE, etc). @fuggly @Thormeard, are you able to test this and confirm this fixes the issue?

Thanks very much!

fuggly commented 1 year ago

@AndyQ I have to tell you next week (currently not working on this), but I will let you know with he passports I have to use

Thormeard commented 1 year ago

@AndyQ Just tested with the sample app, works fine (for all 3 passports I have)! Thank you!

AndyQ commented 1 year ago

I'll close this now - please reopen if it there is still an issue

emirbeytekin commented 1 year ago

Hello again, i read all comments but i have an error scan to German passport. I can't change my min target (ios13.1) and error is continue to me. It's my TagReader.swift file, what can i need to change ?

` // // TagHandler.swift // NFCTest // // Created by Andy Qua on 09/06/2019. // Copyright © 2019 Andy Qua. All rights reserved. //

import Foundation

if !os(macOS)

import CoreNFC

@available(iOS 13, *) public class TagReader { var tag : NFCISO7816Tag var secureMessaging : SecureMessaging? var maxDataLengthToRead : Int = 0xA0 // Should be able to use 256 to read arbitrary amounts of data at full speed BUT this isn't supported across all passports so for reliability just use the smaller amount.

var progress : ((Int)->())?

init( tag: NFCISO7816Tag ) {
    self.tag = tag
}

func overrideDataAmountToRead( newAmount : Int ) {
    maxDataLengthToRead = newAmount
}

func reduceDataReadingAmount() {
    if maxDataLengthToRead > 0xA0 {
        maxDataLengthToRead = 0xA0
    }
}

func readDataGroup( dataGroup: DataGroupId, completed: @escaping ([UInt8]?, NFCPassportReaderError?)->() )  {
    guard let tag = dataGroup.getFileIDTag() else {
        completed(nil, NFCPassportReaderError.UnsupportedDataGroup)
        return
    }

    selectFileAndRead(tag: tag, completed:completed )
}

func getChallenge( completed: @escaping (ResponseAPDU?, NFCPassportReaderError?)->() ) {
    let cmd : NFCISO7816APDU = NFCISO7816APDU(instructionClass: 00, instructionCode: 0x84, p1Parameter: 0, p2Parameter: 0, data: Data(), expectedResponseLength: 8)

    send( cmd: cmd, completed: completed )
}

func doInternalAuthentication( challenge: [UInt8], completed: @escaping (ResponseAPDU?, NFCPassportReaderError?)->() ) {
    let randNonce = Data(challenge)

    let cmd = NFCISO7816APDU(instructionClass: 00, instructionCode: 0x88, p1Parameter: 0, p2Parameter: 0, data: randNonce, expectedResponseLength: 256)

    send( cmd: cmd, completed: completed )
}

func doMutualAuthentication( cmdData : Data, completed: @escaping (ResponseAPDU?, NFCPassportReaderError?)->() ) {
    let cmd : NFCISO7816APDU = NFCISO7816APDU(instructionClass: 00, instructionCode: 0x82, p1Parameter: 0, p2Parameter: 0, data: cmdData, expectedResponseLength: 256)

    send( cmd: cmd, completed: completed )
}

/// The MSE KAT APDU, see EAC 1.11 spec, Section B.1.
/// This command is sent in the "DESede" case.
/// - Parameter keyData key data object (tag 0x91)
/// - Parameter idData key id data object (tag 0x84), can be null
/// - Parameter completed the complete handler - returns the success response or an error
func sendMSEKAT( keyData : Data, idData: Data?, completed: @escaping (ResponseAPDU?, NFCPassportReaderError?)->() ) {

    var data = keyData
    if let idData = idData {
        data += idData
    }

    let cmd : NFCISO7816APDU = NFCISO7816APDU(instructionClass: 00, instructionCode: 0x22, p1Parameter: 0x41, p2Parameter: 0xA6, data: data, expectedResponseLength: 256)

    send( cmd: cmd, completed: completed )
}

/// The  MSE Set AT for Chip Authentication.
/// This command is the first command that is sent in the "AES" case.
/// For Chip Authentication. We prefix 0x80 for OID and 0x84 for keyId.
///
/// NOTE THIS IS CURRENTLY UNTESTED
/// - Parameter oid the OID
/// - Parameter keyId the keyId or {@code null}
/// - Parameter completed the complete handler - returns the success response or an error
func sendMSESetATIntAuth( oid: String, keyId: Int?, completed: @escaping (ResponseAPDU?, NFCPassportReaderError?)->() ) {

    let cmd : NFCISO7816APDU
    let oidBytes = oidToBytes(oid: oid, replaceTag: true)

    if let keyId = keyId, keyId != 0 {
        let keyIdBytes = wrapDO(b:0x84, arr:intToBytes(val:keyId, removePadding: true))
        let data = oidBytes + keyIdBytes

        cmd = NFCISO7816APDU(instructionClass: 00, instructionCode: 0x22, p1Parameter: 0x41, p2Parameter: 0xA4, data: Data(data), expectedResponseLength: 256)

    } else {
        cmd = NFCISO7816APDU(instructionClass: 00, instructionCode: 0x22, p1Parameter: 0x41, p2Parameter: 0xA4, data: Data(oidBytes), expectedResponseLength: 256)
    }

    send( cmd: cmd, completed: completed )
}

func sendMSESetATMutualAuth( oid: String, keyType: UInt8, completed: @escaping (ResponseAPDU?, NFCPassportReaderError?)->() ) {

    let oidBytes = oidToBytes(oid: oid, replaceTag: true)
    let keyTypeBytes = wrapDO( b: 0x83, arr:[keyType])

    let data = oidBytes + keyTypeBytes

    let cmd = NFCISO7816APDU(instructionClass: 00, instructionCode: 0x22, p1Parameter: 0xC1, p2Parameter: 0xA4, data: Data(data), expectedResponseLength: -1)

    send( cmd: cmd, completed: completed )
}

/// Sends a General Authenticate command.
/// This command is the second command that is sent in the "AES" case.
/// - Parameter data data to be sent, without the {@code 0x7C} prefix (this method will add it)
/// - Parameter lengthExpected the expected length defaults to 256
/// - Parameter isLast indicates whether this is the last command in the chain
/// - Parameter completed the complete handler - returns the dynamic authentication data without the {@code 0x7C} prefix (this method will remove it) or an error
func sendGeneralAuthenticate( data : [UInt8], lengthExpected : Int = 256, isLast: Bool, completed: @escaping (ResponseAPDU?, NFCPassportReaderError?)->() ) {

    let wrappedData = wrapDO(b:0x7C, arr:data)
    let commandData = Data(wrappedData)

     // NOTE: Support of Protocol Response Data is CONDITIONAL:
     // It MUST be provided for version 2 but MUST NOT be provided for version 1.
     // So, we are expecting 0x7C (= tag), 0x00 (= length) here.

    // 0x10 is class command chaining
    let instructionClass : UInt8 = isLast ? 0x00 : 0x10
    let INS_BSI_GENERAL_AUTHENTICATE : UInt8 = 0x86

    let cmd : NFCISO7816APDU = NFCISO7816APDU(instructionClass: instructionClass, instructionCode: INS_BSI_GENERAL_AUTHENTICATE, p1Parameter: 0x00, p2Parameter: 0x00, data: commandData, expectedResponseLength: lengthExpected)
    send( cmd: cmd, completed: { [unowned self] (response, error) in
        // Check for error
        if let error = error {
            // If wrong length error
            if case NFCPassportReaderError.ResponseError(_, let sw1, let sw2) = error,
               sw1 == 0x67, sw2 == 0x00 {

                // Resend
                let cmd : NFCISO7816APDU = NFCISO7816APDU(instructionClass: instructionClass, instructionCode: INS_BSI_GENERAL_AUTHENTICATE, p1Parameter: 0x00, p2Parameter: 0x00, data: commandData, expectedResponseLength: 256)
                send( cmd: cmd, completed: { (response, error) in
                    if let response = response {
                        // Success
                        do {
                            var retResponse = response
                            retResponse.data = try unwrapDO( tag:0x7c, wrappedData:retResponse.data)

                            completed( retResponse, nil)
                        } catch {
                            completed( nil, NFCPassportReaderError.InvalidASN1Value)
                        }
                    } else {
                        completed( nil, error)
                    }
                })
            } else {
                completed( nil, error)
            }
        } else {
            // Success
            if let response = response {
                do {
                    var retResponse = response
                    retResponse.data = try unwrapDO( tag:0x7c, wrappedData:retResponse.data)

                    completed( retResponse, nil)
                } catch {
                    completed( nil, NFCPassportReaderError.InvalidASN1Value)
                }
            } else {
                completed( nil, error)
            }
        }
    })
}

var header = [UInt8]()
func selectFileAndRead( tag: [UInt8], completed: @escaping ([UInt8]?, NFCPassportReaderError?)->() ) {
    selectFile(tag: tag ) { [unowned self] (resp,err) in
        if let error = err {
            completed( nil, error)
            return
        }

        // Read first 4 bytes of header to see how big the data structure is
        let data : [UInt8] = [0x00, 0xB0, 0x00, 0x00, 0x00, 0x00,0x04]
        //print( "--------------------------------------\nSending \(binToHexRep(data))" )
        let cmd = NFCISO7816APDU(data:Data(data))!
        self.send( cmd: cmd ) { [unowned self] (resp,err) in
            guard let response = resp else {
                completed( nil, err)
                return
            }
            // Header looks like:  <tag><length of data><nextTag> e.g.60145F01 -
            // the total length is the 2nd value plus the two header 2 bytes
            // We've read 4 bytes so we now need to read the remaining bytes from offset 4
            var leftToRead = 0

            let (len, o) = try! asn1Length([UInt8](response.data[1..<4]))
            leftToRead = Int(len)
            let offset = o + 1

            //print( "Got \(binToHexRep(response.data)) which is \(leftToRead) bytes with offset \(o)" )
            self.header = [UInt8](response.data[..<offset])//response.data

            Log.debug( "TagReader - Number of data bytes to read - \(leftToRead)" )
            self.readBinaryData(leftToRead: leftToRead, amountRead: offset, completed: completed)

        }
    }
}

func readCardAccess( completed: @escaping ([UInt8]?, NFCPassportReaderError?)->() ) {
    // Info provided by @smulu
    // By default NFCISO7816Tag requirers a list of ISO/IEC 7816 applets (AIDs). Upon discovery of NFC tag the first found applet from this list is automatically selected (and you have no way of changing this).
    // This is a problem for PACE protocol becaues it requires reading parameters from file EF.CardAccess which lies outside of eMRTD applet (AID: A0000002471001) in the master file.

    // Now, the ICAO 9303 standard does specify command for selecting master file by sending SELECT APDU with P1=0x00, P2=0x0C and empty data field (see part 10 page 8). But after some testing I found out this command doesn't work on some passports (European passports) and although receiving success (sw=9000) from passport the master file is not selected.

    // After a bit of researching standard ISO/IEC 7816 I found there is an alternative SELECT command for selecting master file. The command doesn't differ much from the command specified in ICAO 9303 doc with only difference that data field is set to: 0x3F00. See section 6.11.3 of ISO/IEC 7816-4.
    // By executing above SELECT command (with data=0x3F00) master file should be selected and you should be able to read EF.CardAccess from passport.

    // First select master file
    let cmd : NFCISO7816APDU = NFCISO7816APDU(instructionClass: 0x00, instructionCode: 0xA4, p1Parameter: 0x00, p2Parameter: 0x0C, data: Data([0x3f,0x00]), expectedResponseLength: -1)

    send( cmd: cmd) { response, error in
        if let error = error {
            completed( nil, error )
            return
        }

        // Now read EC.CardAccess
        self.selectFileAndRead(tag: [0x01,0x1C]) { data, error in
            completed( data, error)
        }
    }
}

func selectPassportApplication( completed: @escaping (ResponseAPDU?, NFCPassportReaderError?)->() ) {
    // Finally reselect the eMRTD application so the rest of the reading works as normal
    Log.debug( "Re-selecting eMRTD Application" )
    let cmd : NFCISO7816APDU = NFCISO7816APDU(instructionClass: 0x00, instructionCode: 0xA4, p1Parameter: 0x04, p2Parameter: 0x0C, data: Data([0xA0, 0x00, 0x00, 0x02, 0x47, 0x10, 0x01]), expectedResponseLength: -1)

    self.send( cmd: cmd) { response, error in
        completed( response, nil)
    }

}

func selectFile( tag: [UInt8], completed: @escaping (ResponseAPDU?, NFCPassportReaderError?)->() ) {

    let data : [UInt8] = [0x00, 0xA4, 0x02, 0x0C, 0x02] + tag
    let cmd = NFCISO7816APDU(data:Data(data))!

    send( cmd: cmd, completed: completed )
}

func readBinaryData( leftToRead: Int, amountRead : Int, completed: @escaping ([UInt8]?, NFCPassportReaderError?)->() ) {
    var readAmount : Int = maxDataLengthToRead
    if maxDataLengthToRead != 256 && leftToRead < maxDataLengthToRead {
        readAmount = leftToRead
    }

    self.progress?( Int(Float(amountRead) / Float(leftToRead+amountRead ) * 100))
    let offset = intToBin(amountRead, pad:4)

    let cmd = NFCISO7816APDU(
        instructionClass: 00,
        instructionCode: 0xB0,
        p1Parameter: offset[0],
        p2Parameter: offset[1],
        data: Data(),
        expectedResponseLength: readAmount
    )

    Log.verbose( "TagReader - data bytes remaining: \(leftToRead), will read : \(readAmount)" )
    self.send( cmd: cmd ) { (resp,err) in
        guard let response = resp else {
            completed( nil, err)
            return
        }
        Log.verbose( "TagReader - got resp - \(response)" )
        self.header += response.data

        let remaining = leftToRead - response.data.count
        Log.verbose( "TagReader - Amount of data left to read - \(remaining)" )
        if remaining > 0 {
            self.readBinaryData(leftToRead: remaining, amountRead: amountRead + response.data.count, completed: completed )
        } else {
            completed( self.header, err )
        }

    }
}

func send( cmd: NFCISO7816APDU, completed: @escaping (ResponseAPDU?, NFCPassportReaderError?)->() ) {

    Log.verbose( "TagReader - sending \(cmd)" )
    var toSend = cmd
    if let sm = secureMessaging {
        do {
            toSend = try sm.protect(apdu:cmd)
        } catch {
            completed( nil, NFCPassportReaderError.UnableToProtectAPDU )
        }
        Log.verbose("TagReader - [SM] \(toSend)" )
    }

    tag.sendCommand(apdu: toSend) { [unowned self] (data, sw1, sw2, error) in
        if let error = error {
            Log.error( "TagReader - Error reading tag - \(error.localizedDescription))" )
            completed( nil, NFCPassportReaderError.ResponseError( error.localizedDescription, sw1, sw2 ) )
        } else {
            Log.verbose( "TagReader - Received response" )
            var rep = ResponseAPDU(data: [UInt8](data), sw1: sw1, sw2: sw2)

            if let sm = self.secureMessaging {
                do {
                    rep = try sm.unprotect(rapdu:rep)
                    Log.verbose(String(format:"TagReader [SM - unprotected] \(binToHexRep(rep.data, asArray:true)), sw1:0x%02x sw2:0x%02x", rep.sw1, rep.sw2) )
                } catch {
                    completed( nil, NFCPassportReaderError.UnableToUnprotectAPDU )
                    return
                }
            } else {
                Log.verbose(String(format:"TagReader [unprotected] \(binToHexRep(rep.data, asArray:true)), sw1:0x%02x sw2:0x%02x", rep.sw1, rep.sw2) )

            }

            if rep.sw1 == 0x90 && rep.sw2 == 0x00 {
                completed( rep, nil )
            } else {
                Log.error( "Error reading tag: sw1 - 0x\(binToHexRep(sw1)), sw2 - 0x\(binToHexRep(sw2))" )
                let tagError: NFCPassportReaderError
                if (rep.sw1 == 0x63 && rep.sw2 == 0x00) {
                    tagError = NFCPassportReaderError.InvalidMRZKey
                } else {
                    let errorMsg = self.decodeError(sw1: rep.sw1, sw2: rep.sw2)
                    Log.error( "reason: \(errorMsg)" )
                    tagError = NFCPassportReaderError.ResponseError( errorMsg, sw1, sw2 )
                }
                completed( nil, tagError)
            }
        }
    }
}

private func decodeError( sw1: UInt8, sw2:UInt8 ) -> String {

    let errors : [UInt8 : [UInt8:String]] = [
        0x62: [0x00:"No information given",
               0x81:"Part of returned data may be corrupted",
               0x82:"End of file/record reached before reading Le bytes",
               0x83:"Selected file invalidated",
               0x84:"FCI not formatted according to ISO7816-4 section 5.1.5"],

        0x63: [0x81:"File filled up by the last write",
               0x82:"Card Key not supported",
               0x83:"Reader Key not supported",
               0x84:"Plain transmission not supported",
               0x85:"Secured Transmission not supported",
               0x86:"Volatile memory not available",
               0x87:"Non Volatile memory not available",
               0x88:"Key number not valid",
               0x89:"Key length is not correct",
               0xC:"Counter provided by X (valued from 0 to 15) (exact meaning depending on the command)"],
        0x65: [0x00:"No information given",
               0x81:"Memory failure"],
        0x67: [0x00:"Wrong length"],
        0x68: [0x00:"No information given",
               0x81:"Logical channel not supported",
               0x82:"Secure messaging not supported",
               0x83:"Last command of the chain expected",
               0x84:"Command chaining not supported"],
        0x69: [0x00:"No information given",
               0x81:"Command incompatible with file structure",
               0x82:"Security status not satisfied",
               0x83:"Authentication method blocked",
               0x84:"Referenced data invalidated",
               0x85:"Conditions of use not satisfied",
               0x86:"Command not allowed (no current EF)",
               0x87:"Expected SM data objects missing",
               0x88:"SM data objects incorrect"],
        0x6A: [0x00:"No information given",
               0x80:"Incorrect parameters in the data field",
               0x81:"Function not supported",
               0x82:"File not found",
               0x83:"Record not found",
               0x84:"Not enough memory space in the file",
               0x85:"Lc inconsistent with TLV structure",
               0x86:"Incorrect parameters P1-P2",
               0x87:"Lc inconsistent with P1-P2",
               0x88:"Referenced data not found"],
        0x6B: [0x00:"Wrong parameter(s) P1-P2]"],
        0x6D: [0x00:"Instruction code not supported or invalid"],
        0x6E: [0x00:"Class not supported"],
        0x6F: [0x00:"No precise diagnosis"],
        0x90: [0x00:"Success"] //No further qualification
    ]

    // Special cases - where sw2 isn't an error but contains a value
    if sw1 == 0x61 {
        return "SW2 indicates the number of response bytes still available - (\(sw2) bytes still available)"
    } else if sw1 == 0x64 {
        return "State of non-volatile memory unchanged (SW2=00, other values are RFU)"
    } else if sw1 == 0x6C {
        return "Wrong length Le: SW2 indicates the exact length - (exact length :\(sw2))"
    }

    if let dict = errors[sw1], let errorMsg = dict[sw2] {
        return errorMsg
    }

    return "Unknown error - sw1: 0x\(binToHexRep(sw1)), sw2 - 0x\(binToHexRep(sw2)) "
}

}

endif

`

and my log is here tagReaderSessionDidBecomeActive tagReaderSession:didDetect - iso7816(<NFCISO7816Tag: 0x14a126270>) tagReaderSession:connecting to tag - iso7816(<NFCISO7816Tag: 0x14a126270>) tagReaderSession:connected to tag - starting authentication Starting Basic Access Control (BAC) BACHandler - deriving Document Basic Access Keys BACHandler - Getting initial challenge BACHandler - Doing mutual authentication Error reading tag: sw1 - 0x69, sw2 - 0x82 reason: Security status not satisfied ERROR - Security status not satisfied BAC Failed