Appearance
Usage Guide
Quick Start
Initialize EmrtdReader, load a CSCA master list, and call readAndVerify:
swift
import KinegramEmrtd
// master list containing Country Certificates from a trusted source.
let masterListURL = Bundle.main.url(forResource: "masterlist", withExtension: "ml")!
// Initialize EmrtdReader
let emrtdReader = EmrtdReader()
try emrtdReader.readMasterlist(from: masterListURL)
// Access Key values from the MRZ
let mrzKey = MRZKey(
documentNumber: "123456789",
birthDateyyMMdd: "970101",
expiryDateyyMMdd: "211212"
)
do {
let emrtdResult = try await emrtdReader.readAndVerify(accessKey: mrzKey)
// MRZ Info from mandatory DataGroup 1
if let dg1File: DataGroup1File = emrtdResult.dg1File {
let documentNumber = dg1File.documentNumber
// ...
}
// Photo of the face from mandatory DataGroup 2
if let faceInfo: BiometricFaceImageInfo = emrtdResult.dg2File?.faceInfos?.first {
let faceUIImage: UIImage? = faceInfo.uiImage
}
// Integrity and Authenticity of the read DataGroups
let passiveAuthentication = emrtdResult.passiveAuthenticationResult
// Active Authentication Result { SUCCESS, FAILED, UNAVAILABLE }
let activeAuthentication = emrtdResult.activeAuthenticationResult
// Chip Authentication Result { SUCCESS, FAILED, UNAVAILABLE }
let chipAuthentication = emrtdResult.chipAuthenticationResult
} catch {
// Handle EmrtdReaderError
print("Error reading eMRTD: \(error)")
}INFO
CSCA master list You must provide a CSCA master list to verify the eMRTD (ICAO Doc 9303 Part 12). The German Federal Office for Information Security (BSI) publishes an extensive and regularly updated CSCA master list.
Using With CAN
For documents that support PACE with a Card Access Number (a 6-digit number printed on the document):
swift
let canKey = CANKey(can: "123456")
let emrtdResult = try await emrtdReader.readAndVerify(accessKey: canKey)INFO
CAN authentication only works with documents that support PACE (Password Authenticated Connection Establishment). It cannot be used with BAC (Basic Access Control).
Reading Without Verification
If you only need to read the data without verifying authenticity:
swift
let emrtdResult = try await emrtdReader.read(accessKey: mrzKey)Reading Selected Data Groups
By default, a read returns DG1 (MRZ), DG2 (face image), and the optional data groups a document commonly carries. If you only need some of them, pass a DataGroupSet — reading less is faster, because DG2 alone accounts for most of the reading time:
swift
// MRZ and face image only
let emrtdResult = try await emrtdReader.readAndVerify(
accessKey: mrzKey,
filesToRead: .minimalKYC
)The same parameter is available on read. Besides the predefined selections you can combine data groups freely, e.g. [.dg1, .dg11].
| Selection | Data Groups | Use Case |
|---|---|---|
.minimal | DG1 | MRZ data alone, no biometrics |
.minimalKYC | DG1, DG2 | MRZ plus face image |
.standard | DG1, DG2, DG7, DG11, DG12 | Same as a read without a selection |
.all | DG1 – DG16 | All sixteen data groups |
How a selection behaves:
- The SOD is always read, and Passive Authentication covers the data groups that were actually read.
- Selected data groups the document does not contain are skipped.
- DG1 and DG2 must read successfully while they are part of the selection, otherwise the call fails with
EmrtdReaderError.IncompleteRead.
INFO
DG14 and DG15 are inputs to Chip and Active Authentication: readAndVerify reads them whenever the document carries them, no matter what the selection says. Only the non-verifying read treats them as ordinary data files that are read when selected — .standard does not include them.
See Relevant Data Groups for what each data group contains.
Reading PACE-Enabled Documents
Some identity documents require PACE polling to be detected. This includes French ID cards (FRA ID), Omani ID cards (OMN ID), and newer generation Netherlands passports.
To read these documents, use the usePACEPolling parameter (requires iOS 16+):
swift
let canKey = CANKey(can: "123456")
do {
let emrtdResult = try await emrtdReader.readAndVerify(
accessKey: canKey,
usePACEPolling: true
)
// Process result...
} catch EmrtdReaderError.PACEPollingNotAvailable {
// PACE polling requires iOS 16 or later
print("PACE polling is not available on this iOS version")
} catch {
print("Error reading document: \(error)")
}WARNING
PACE polling is only available on iOS 16 and later. The SDK will throw a PACEPollingNotAvailable error if you attempt to use it on iOS 15 or earlier. PACE polling cannot detect standard passports, use it only when you know the document requires it.
Error Handling
The SDK uses Swift's native error handling. Errors are thrown as EmrtdReaderError:
swift
do {
let emrtdResult = try await emrtdReader.readAndVerify(accessKey: mrzKey)
} catch EmrtdReaderError.IncorrectAccessKey {
print("Wrong MRZ/CAN values")
} catch EmrtdReaderError.ConnectionLost {
print("NFC connection lost - hold phone steady")
} catch EmrtdReaderError.PaceOrBacFailed(let error) {
print("Access Control failed: \(error)")
} catch EmrtdReaderError.FileReadFailed(let error, let files) {
print("Failed to read files: \(files)")
} catch {
print("Error: \(error)")
}Error Types
| Error | Description |
|---|---|
NFCNotSupported | NFC is not supported on this device |
MoreThanOneTagFound | More than one NFC tag was found |
WrongTag | Wrong NFC tag was found (expected ISO 7816 tag) |
UserInvalidatedSession | Session was canceled by the user |
SessionInvalidated | NFC session was invalidated (e.g. timeout) |
ConnectingFailed | Failed to connect to chip |
ConnectionLost | Chip connection lost |
PaceOrBacFailed | Access Control failed |
IncorrectAccessKey | Access Key is incorrect |
FileReadFailed | Failed to read file(s) from the chip |
IncompleteRead | Mandatory file(s) missing after reading |
PACEPollingNotAvailable | PACE polling requires iOS 16 or later |
Localization
The SDK displays English messages in the NFC dialog by default. You can customize these by providing localization closures when initializing EmrtdReader:
swift
func errorLocalization(error: EmrtdReaderError) -> String {
switch error {
case .NFCNotSupported(_):
return ""
case .MoreThanOneTagFound:
return ""
case .WrongTag:
return ""
case .UserInvalidatedSession:
return ""
case .SessionInvalidated(let errorCode):
return ""
case .ConnectingFailed(let error):
return ""
case .ConnectionLost:
return ""
case .PaceOrBacFailed(let error):
return ""
case .FileReadFailed(let error, let files):
return ""
case .IncorrectAccessKey:
return ""
case .PACEPollingNotAvailable:
return ""
@unknown default:
return "Unknown Error"
}
}
func stepLocalization(step: ReadAndVerifyStep) -> String {
switch step {
case .waitingForPassport:
return ""
case .readFileAtrInfo:
return ""
case .readFileCardAccess:
return ""
case .doPaceOrBac(_):
return ""
case .readFileSOD:
return ""
case .readFileDG14:
return ""
case .doChipAuthenticationIfAvailable(_):
return ""
case .readFileDG15:
return ""
case .doActiveAuthentication(_):
return ""
case .readRemainingElementaryFiles:
return ""
case .doPassiveAuthentication:
return ""
case .done:
return ""
@unknown default:
return "Unknown Step"
}
}
func fileReadProgressLocalization(
fileName: ElementaryFileName,
readBytes: Int,
totalBytes: Int) -> String {
return "Reading File \(fileName) (\(readBytes)/\(totalBytes) Bytes)"
}
let emrtdReader = EmrtdReader(
errorLocalization: errorLocalization,
stepLocalization: stepLocalization,
fileReadProgressLocalization: fileReadProgressLocalization
)Default Step Messages
| Step | Default Message |
|---|---|
waitingForPassport | "Place the iPhone with direct contact to the document" |
readFileAtrInfo | "Reading File AtrInfo" |
readFileCardAccess | "Reading File CardAccess" |
doPaceOrBac | "Performing Access Control" |
readFileSOD | "Reading File SOD" |
readFileDG14 | "Reading File DG14" |
doChipAuthenticationIfAvailable | "Performing Chip Authentication" |
readFileDG15 | "Reading File DG15" |
doActiveAuthentication | "Performing Active Authentication" |
readRemainingElementaryFiles | "Reading Elementary Files" |
doPassiveAuthentication | "Finishing Verification" |
done | "Done" |
