DOT iOS NFC library

v2.2.1

Introduction

DOT iOS NFC provides a component for NFC reading which is easy to integrate into an iOS application. Supported documents are those which implement Machine Readable Travel Document (MRTD) standards as specified by International Civil Aviation Organization (ICAO).

Requirements

  • Xcode 11.4+

  • Framework supports iOS 13+. However, it can be added to projects with minimum deployment target iOS 11.0+. This allows you to put DOT iOS NFC code in @available block.

  • Swift or Objective-C support

  • CocoaPods

Distribution

Cocoapods

DOT iOS NFC is distributed as a XCFramework - DotNfc.xcframework using Cocoapods with its dependencies stored in our public github repository. It can be easily integrated into XCode with custom definition of podspecs. First step is to insert following line of code on top of you Podfile.

Podfile
source 'https://github.com/innovatrics/innovatrics-podspecs'

Then DOT iOS NFC dependency must be specified in Podfile. Dependencies of DOT iOS NFC will be downloaded alongside it.

Podfile
source 'https://github.com/innovatrics/innovatrics-podspecs'

use_frameworks!

target 'YOUR_TARGET' do

pod 'dot-nfc'

end

In case of CocoaPods problem with pod install, try to clone the private pod repository manually.

pod repo remove innovatrics
pod repo add innovatrics https://github.com/innovatrics/innovatrics-podspecs

Supported Architectures

DOT iOS NFC provides all supported architectures in the distributed XCFramework package. Device binary contains: arm64. Simulator binary contains: x86_64, arm64.

Permissions

  • Add capability to your Xcode target: Near Field Communication Tag Reading

  • DOT iOS NFC requires following keys in Info.plist:

Info.plist
<key>NFCReaderUsageDescription</key>
<string>NFC tag to read NDEF messages into the application</string>

<key>com.apple.developer.nfc.readersession.iso7816.select-identifiers</key>
<array>
	<string>A0000002471001</string>
</array>

Basic Setup

Logging

DOT iOS NFC supports logging using a global Logger class. You can set the log level as follows:

import DotNfc

Logger.logLevel = .debug

Log levels:

  • info

  • debug

  • warning

  • error

  • none

Each log message contains dot-nfc tag. Keep in mind that logging should be used just for debugging purposes.

Components

Overview

DOT iOS NFC provides a non-UI component for NFC reading. You may customize native UI which is presented during the NFC reading on the iOS platform.

List of Non-UI Components

NFC TRAVEL DOCUMENT READER

The component for reading NFC enabled travel documents.

FLAT TAG STRUCTURE PARSER

The component for parsing flat tag structure in a data group as defined in Doc 9303.

Non-UI Components

NFC Travel Document Reader

The NfcTravelDocumentReader class provides NFC reading functionality.

Create NfcTravelDocumentReader:

let authorityCertificatesUrl = Bundle.main.url(forResource: "masterList", withExtension: "pem")
let configuration = NfcTravelDocumentReaderConfiguration(authorityCertificatesUrl: authorityCertificatesUrl)
let nfcTravelDocumentReader = NfcTravelDocumentReader(configuration: configuration)
nfcTravelDocumentReader.setDelegate(self)
NfcTravelDocumentReaderConfiguration
  • (Optional) [-] authorityCertificatesUrl: URL? – URL to certificates of trusted authorities. Certificates are required to successfully execute Passive Authentication.

To read NFC data, call the following method:

nfcTravelDocumentReader.read(nfcKey: nfcKey)

NfcKey is the access key to NFC data.

You can handle the NfcTravelDocumentReader events using its delegate NfcTravelDocumentReaderDelegate.

@objc(DOTNfcTravelDocumentReaderDelegate) public protocol NfcTravelDocumentReaderDelegate {

    /// Tells the delegate that NFC reading has finished successfully with travel document as a result.
    @objc func nfcTravelDocumentReader(_ nfcTravelDocumentReader: NfcTravelDocumentReader, succeeded travelDocument: TravelDocument)

    /// Tells the delegate that NFC reading has failed with an error.
    @objc func nfcTravelDocumentReader(_ nfcTravelDocumentReader: NfcTravelDocumentReader, failed error: NfcTravelDocumentReaderError)
}
NfcKey

The NfcKey object is created from the travel document number, date of birth and date of expiry.

let nfcKey = try NfcKey(documentNumber: documentNumber, dateOfExpiry: dateOfExpiry, dateOfBirth: dateOfBirth)
Reading Process

Travel document reading process consists of three steps: Access Establishment, Passive Authentication, Active Authentication.

Access Establishment

There are two Access Establishment protocols Basic Access Control - BAC and Password Authenticated Connection Establishment - PACE. DOT iOS NFC supports only BAC.

  • BAC: In order to access document using BAC, NFC Key is required. This NFC Key is created from the document number, date of birth and date of expiry.

  • PACE: The newer and more secure version of Access Establishment protocols. It uses NFC Key (weak password with low entropy) and generates cryptographically strong session keys.

Passive Authentication

The purpose of Passive Authentication is to validate the integrity of data stored on NFC chip. In other words, it verifies that data stored on NFC chip has not been altered. Passive Authentication has the following steps:

  • extract and validate Document Signing Certificate with CSCA Certificates from master list

  • verify that Security Data (EF.SOD) has been correctly signed by Document Signing Certificate

  • verify that hashes stored in EF.SOD are valid, i.e. hashes stored in EF.SOD are equal to hashes computed from data groups present on the document

Active Authentication

The purpose of Active Authentication is to verify that document is genuine, i.e. it is not a copy. Active Authentication has the following steps:

  • generate random challenge

  • request a signature for this challenge from the NFC chip

  • verify the signature using the public key stored in Data Group 15 (DG15)

The public key stored in DG15 can be RSA or ECDSA. DOT iOS NFC supports both RSA and ECDSA.

Flat Tag Structure Parser

The FlatTagStructureParser class parses input data into a dictionary of data elements.

Create a FlatTagStructureParser:

let flatTagStructureParser = FlatTagStructureParser()

To parse a Data Group value, call the following method:

let elements = try flatTagStructureParser.parse(value: bytes)

Following data structure is expected as an input:

ELEMENT_1_TAG, ELEMENT_1_LENGTH, ELEMENT_1_VALUE, ..., ELEMENT_N_TAG, ELEMENT_N_LENGTH, ELEMENT_N_VALUE

Such a value is parsed into this map:

Table 1. Map of Data Elements

Tag

Value

ELEMENT_1_TAG

ELEMENT_1_VALUE

…​

…​

ELEMENT_N_TAG

ELEMENT_N_VALUE

This is an example of a resulting map:

Table 2. Example of a map of Data Elements

Tag

Length

Value

0x5C

0x02

0x5F_5B

0x5F_5B

0x08

SPECIMEN (shown as text for readability)

Some country authorities may use this structure in the Optional Details. This data is present in TravelDocument.optionalDetails as a result of the NFC reading.

Customization of native UI

Localization

String resources can be overridden in your application and alternative strings for supported languages can be provided following these two steps:

  1. Add your own Localizable.strings file to your project using standard iOS localization mechanism. To change a specific text override corresponding key in this Localizable.strings file.

  2. Set the localization bundle to the bundle of your application (preferably during the application launch in your AppDelegate).

Use this setup if you want to use standard iOS localization mechanism, which means your iOS application uses system defined locale.

import DotNfc

Localization.bundle = .main
Custom Localization

You can override standard iOS localization mechanism by providing your own translation dictionary and setting the Localization.useLocalizationDictionary flag to true. Use this setup if you do not want to use standard iOS localization mechanism, which means your iOS application ignores system defined locale and uses its own custom locale.

import DotNfc

guard let localizableUrl = Bundle.main.url(forResource: "Localizable", withExtension: "strings", subdirectory: nil, localization: "de"),
      let dictionary = NSDictionary(contentsOf: localizableUrl) as? [String: String]
else { return }

Localization.useLocalizationDictionary = true
Localization.localizationDictionary = dictionary
Localizable.strings
"dot.nfc_travel_document_reader.instruction.beginReading" = "Hold your iPhone near an NFC enabled document.";
"dot.nfc_travel_document_reader.instruction.establishAccessControl" = "Establishing access control with the document...";
"dot.nfc_travel_document_reader.instruction.readingSuccessful" = "Document reading was successful.";

"dot.nfc_travel_document_reader.instruction.accessControlError" = "There was a problem establishing access control with the document.";
"dot.nfc_travel_document_reader.instruction.readingError" = "There was an error reading the document.";

"dot.nfc_travel_document_reader.instruction.readingCom" = "Reading COM...";
"dot.nfc_travel_document_reader.instruction.readingSod" = "Reading SOD...";
"dot.nfc_travel_document_reader.instruction.readingDG1" = "Reading DG1...";
"dot.nfc_travel_document_reader.instruction.readingDG2" = "Reading DG2...";
"dot.nfc_travel_document_reader.instruction.readingDG3" = "Reading DG3...";
"dot.nfc_travel_document_reader.instruction.readingDG4" = "Reading DG4...";
"dot.nfc_travel_document_reader.instruction.readingDG5" = "Reading DG5...";
"dot.nfc_travel_document_reader.instruction.readingDG6" = "Reading DG6...";
"dot.nfc_travel_document_reader.instruction.readingDG7" = "Reading DG7...";
"dot.nfc_travel_document_reader.instruction.readingDG8" = "Reading DG8...";
"dot.nfc_travel_document_reader.instruction.readingDG9" = "Reading DG9...";
"dot.nfc_travel_document_reader.instruction.readingDG10" = "Reading DG10...";
"dot.nfc_travel_document_reader.instruction.readingDG11" = "Reading DG11...";
"dot.nfc_travel_document_reader.instruction.readingDG12" = "Reading DG12...";
"dot.nfc_travel_document_reader.instruction.readingDG13" = "Reading DG13...";
"dot.nfc_travel_document_reader.instruction.readingDG14" = "Reading DG14...";
"dot.nfc_travel_document_reader.instruction.readingDG15" = "Reading DG15...";
"dot.nfc_travel_document_reader.instruction.readingDG16" = "Reading DG16...";
"dot.nfc_travel_document_reader.instruction.readingUnknownDG" = "Reading unknown data group...";

Appendix

Changelog

2.2.0 - 2022-01-21

Added
  • FlatTagStructureParser

2.1.0 - 2022-01-19

Added
  • OptionalDetails

  • TravelDocument.optionalDetails

Changed
  • AdditionalPersonalDetails.nameOfHolder type from String? to NameOfHolder?

Fixed
  • resolving of JPEG image format, using ImageFormat.jpeg

2.0.2 - 2021-12-13

Fixed
  • parsing of AdditionalDocumentDetails.dateOfIssue

2.0.1 - 2021-12-13

Fixed
  • wrong Passive Authentication flag returned

  • Image Objective-C class name changed from DOTImage to DOTNImage

2.0.0 - 2021-12-02

Added
  • NfcKey

  • NfcTravelDocumentReaderConfiguration

  • NfcTravelDocumentReaderDelegate

  • NfcTravelDocumentReaderError

  • PassiveAuthenticationStatus

  • ActiveAuthenticationStatus

  • Image

  • MachineReadableZoneInformation

  • EncodedIdentificationFeaturesFace

  • DisplayedSignatureOrUsualMark

  • AdditionalPersonalDetails

  • AdditionalDocumentDetails

  • NameOfHolder

  • AccessControlProtocol

  • Logger

Changed
  • minimal required iOS version to iOS 11.0

  • added support for simulator build

  • added support for bitcode, ENABLE_BITCODE = YES

  • updated OpenSSL to version 1.1.1l

  • added support for Active Authentication with RSA public keys

  • removed default master list

  • renamed module to DotNfc

  • renamed DotNfcLocalization to Localization

  • updated localization keys

  • renamed NFCPassportReader to NfcTravelDocumentReader

  • NfcTravelDocumentReader is now using delegate pattern

  • changed NFCPassportReader.readPassport(mrzKey: String, result: @escaping ReadPassportResult) → Void to NfcTravelDocumentReader.read(nfcKey: NfcKey)

  • renamed Passport to TravelDocument

Removed
  • NFCFormatter

1.2.1 - 2021-10-01

Changed
  • release library as a XCFramework package

1.2.0 - 2021-03-04

Added
  • DotNfcLocalization class to enable localization of alert messages

1.1.1 - 2020-10-23

Fixed
  • removed force unwraps and added better error handling

1.1.0 - 2020-07-30

Added
  • Released as a new component dot-nfc

1.0.3 - 2020-03-31

Changed
  • Renamed class NFCReader to NFCPassportReader

1.0.2 - 2020-03-31

Added
  • BUILD_LIBRARY_FOR_DISTRIBUTION = YES

1.0.1 - 2020-03-12

Added
  • OpenSSL dependancy as an external pod dependancy innovatrics-openssl

1.0.0 - 2020-03-02

Added
  • faceImage and signatureImage to Passport struct