Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

release(v0.1.1): exposed data and bug fix patch #3

Merged
merged 6 commits into from
Dec 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion Sources/NFCPassportReader/Models/DocumentDetails.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,17 @@ public struct DocumentDetails {
private var details: [ASN1Tag: String] = [:]

public var iussingAuthority: String? { details[0x5F19] }
public var dateOfIssue: String? { details[0x5F26] }

public var dateOfIssue: String? {
if let date = details[0x5F26] {
let strategy = Date.ParseStrategy(
format: "\(year: .padded(4))\(month: .twoDigits)\(day: .twoDigits)",
timeZone: TimeZone(identifier: "UTC")!
)
return try? Date(date, strategy: strategy).formatted(date: .abbreviated, time: .omitted)
} else { return details[0x5F26] }
}

public var otherPersonDetails: String? { details[0xA0] }
public var endorsementsOrObservations: String? { details[0x5F1B] }
public var taxOrExitRequirements: String? { details[0x5F1C] }
Expand Down
75 changes: 57 additions & 18 deletions Sources/NFCPassportReader/Models/MRZ.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,89 +28,117 @@ public struct MRZ {
private var bytes: [UInt8]
private var type: TDType

var code: String? { String(bytes: bytes, encoding: .utf8) }
public var code: String? { String(bytes: bytes, encoding: .utf8) }

var documentCode: String? {
public var documentCode: String? {
String(bytes: bytes[0..<2], encoding:.utf8)
}

var issuingState: String? {
public var issuingState: String? {
String(bytes: bytes[2..<5], encoding:.utf8)
}

var holderName: String? {
switch self.type {
public var holderName: String? {
let data = switch self.type {
case .TD1: String(bytes: bytes[60...], encoding:.utf8)
case .TD2: String(bytes: bytes[5..<36], encoding:.utf8)
case .TD3: String(bytes: bytes[5..<44], encoding:.utf8)
}

if let components = data?.components(separatedBy: "<<") {
let surname = components[0].components(separatedBy: "<").joined(separator: " ")
let name = components[1].components(separatedBy: "<").joined(separator: " ")
return [surname, name].joined(separator: " ")
} else { return data }
}

internal var surname: String? {
if let components = holderName?.components(separatedBy: "<<") {
let surname = components[0].components(separatedBy: "<").joined(separator: " ")
return surname
} else { return nil }
}

internal var name: String? {
if let components = holderName?.components(separatedBy: "<<") {
let name = components[1].components(separatedBy: "<").joined(separator: " ")
return name
} else { return nil }
}

var documentNumber: String? {
public var documentNumber: String? {
switch self.type {
case .TD1: String(bytes: bytes[5..<14], encoding:.utf8)
case .TD2: String(bytes: bytes[36..<45], encoding:.utf8)
case .TD3: String(bytes: bytes[44..<53], encoding:.utf8)
}
}

var documentNumberCheckDigit: String? {
public var documentNumberCheckDigit: String? {
switch type {
case .TD1: String(bytes: bytes[14..<15], encoding:.utf8)
case .TD2: String(bytes: bytes[45..<46], encoding:.utf8)
case .TD3: String(bytes: bytes[53..<54], encoding:.utf8)
}
}

var nationality: String? {
public var nationality: String? {
switch self.type {
case .TD1: String(bytes: bytes[45..<48], encoding:.utf8)
case .TD2: String(bytes: bytes[46..<49], encoding:.utf8)
case .TD3: String(bytes: bytes[54..<57], encoding:.utf8)
}
}

var dateOfBirth: String? {
switch self.type {
public var dateOfBirth: String? {
let date = switch self.type {
case .TD1: String(bytes: bytes[30..<36], encoding:.utf8)
case .TD2: String(bytes: bytes[49..<55], encoding:.utf8)
case .TD3: String(bytes: bytes[57..<63], encoding:.utf8)
}

if let date = date {
return self.parseDate(date: date)
} else { return date }
}

var dateOfBirthCheckDigit: String? {
public var dateOfBirthCheckDigit: String? {
switch self.type {
case .TD1: String(bytes: [bytes[36]], encoding:.utf8)
case .TD2: String(bytes: [bytes[55]], encoding:.utf8)
case .TD3: String(bytes: [bytes[63]], encoding:.utf8)
}
}

var sex: String? {
public var sex: String? {
switch self.type {
case .TD1: String(bytes: [bytes[37]], encoding:.utf8)
case .TD2: String(bytes: [bytes[56]], encoding:.utf8)
case .TD3: String(bytes: [bytes[64]], encoding:.utf8)
}
}

var dateOfExpiry: String? {
switch self.type {
public var dateOfExpiry: String? {
let date = switch self.type {
case .TD1: String(bytes: bytes[38..<44], encoding:.utf8)
case .TD2: String(bytes: bytes[57..<63], encoding:.utf8)
case .TD3: String(bytes: bytes[65..<71], encoding:.utf8)
}

if let date = date {
return self.parseDate(date: date)
} else { return date }
}

var dateOfExpiryCheckDigit: String? {
public var dateOfExpiryCheckDigit: String? {
switch self.type {
case .TD1: String(bytes: [bytes[44]], encoding:.utf8)
case .TD2: String(bytes: [bytes[63]], encoding:.utf8)
case .TD3: String(bytes: [bytes[71]], encoding:.utf8)
}
}

var optionalData: String? {
public var optionalData: String? {
switch self.type {
case .TD1:
(String(bytes: bytes[15..<30], encoding:.utf8) ?? "") +
Expand All @@ -120,13 +148,13 @@ public struct MRZ {
}
}

var checkDigit: String? {
public var checkDigit: String? {
if self.type == .TD3 {
String(bytes: [bytes[86]], encoding:.utf8)
} else { nil }
}

var compositeCheckDigit: String? {
public var compositeCheckDigit: String? {
switch self.type {
case .TD1: nil
case .TD2: String(bytes: bytes[71..<72], encoding:.utf8)
Expand All @@ -142,3 +170,14 @@ public struct MRZ {
self.type = type
}
}

internal extension MRZ {
func parseDate(date: String) -> String? {
let strategy = Date.ParseStrategy(
format: "\(year: .twoDigits)\(month: .twoDigits)\(day: .twoDigits)",
timeZone: TimeZone(identifier: "UTC")!
)

return try? Date(date, strategy: strategy).formatted(date: .abbreviated, time: .omitted)
}
}
55 changes: 51 additions & 4 deletions Sources/NFCPassportReader/Models/PersonalDetails.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,58 @@ public struct PersonalDetails {

private var details: [ASN1Tag: String] = [:]

public var fullName: String? { details[0x5F0E] }
public var fullName: String? {
if let components = details[0x5F0E]?.components(separatedBy: "<<") {
let surname = components[0].components(separatedBy: "<").joined(separator: " ")
let name = components[1].components(separatedBy: "<").joined(separator: " ")
return [surname, name].joined(separator: " ")
} else { return details[0x5F0E] }
}

internal var surname: String? {
if let components = details[0x5F0E]?.components(separatedBy: "<<") {
let surname = components[0].components(separatedBy: "<").joined(separator: " ")
return surname
} else { return details[0x5F0E] }
}

internal var name: String? {
if let components = details[0x5F0E]?.components(separatedBy: "<<") {
let name = components[1].components(separatedBy: "<").joined(separator: " ")
return name
} else { return details[0x5F0E] }
}

public var personalNumber: String? { details[0x5F10] }
public var dateOfBirth: String? { details[0x5F2B] }
public var placeOfBirth: String? { details[0x5F11] }
public var address: String? { details[0x5F42] }

public var dateOfBirth: String? {
if let date = details[0x5F2B] {
let strategy = Date.ParseStrategy(
format: "\(year: .padded(4))\(month: .twoDigits)\(day: .twoDigits)",
timeZone: TimeZone(identifier: "UTC")!
)
return try? Date(date, strategy: strategy).formatted(date: .abbreviated, time: .omitted)
} else { return details[0x5F2B] }
}

public var placeOfBirth: String? {
if let components = details[0x5F11]?.components(separatedBy: "<") {
let city = components[0]
let province = "\(components[1])"
return "\(city) (\(province))"
} else { return details[0x5F11] }

}

public var address: String? {
if let components = details[0x5F42]?.components(separatedBy: "<") {
let address = components[0]
let city = components[1]
let province = components[2]
return "\(address) \(city) (\(province))"
} else { return details[0x5F42] }
}

public var telephone: String? { details[0x5F12] }
public var profession: String? { details[0x5F13] }
public var title: String? { details[0x5F14] }
Expand Down
68 changes: 62 additions & 6 deletions Sources/NFCPassportReader/NFCPassportModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,17 +48,73 @@ public struct NFCPassportModel {
(self.dataGroupsRead[.DG12] as? DataGroup12)?.documentDetails
}

public var firstName: String {
personalDetails?.fullName?.components(separatedBy: "<<")[0] ??
travelDocument?.mrz.holderName?.components(separatedBy: "<<")[0] ?? "Unkown"
public var certificateDetails: X509CertificateDetails? {
(self.dataGroupsRead[.SOD] as? SOD)?.certs.first?.details
}

public var lastName: String {
personalDetails?.fullName?.components(separatedBy: "<<")[1] ??
travelDocument?.mrz.holderName?.components(separatedBy: "<<")[1] ?? "Unkown"
public var lastName: String? {
personalDetails?.surname ??
travelDocument?.mrz.surname
}

public var firstName: String? {
personalDetails?.name ??
travelDocument?.mrz.name
}

public var holderPicture: UIImage? {
self.faceBiometricDataEncoding?.image
}

public var availableDataGroups: [String]? {
(self.dataGroupsRead[.COM] as? COM)?.availableDataGroups.map { $0.name }
}

public var dataGroupsReadNames: [String] {
self.dataGroupsRead.map { $0.key.name }
}

public var paceSecurityProtocol: String? {
if let paceInfo = (self.dataGroupsRead[.DG14] as? DataGroup14)?
.securityInfos
.first(where: { $0 is PACEInfo }) as? PACEInfo {
return "\(paceInfo.securityProtocol)"
} else { return nil }
}

public var chipAuthenticationSecurityProtocol: String? {
if let caInfo = (self.dataGroupsRead[.DG14] as? DataGroup14)?
.securityInfos
.first(where: { $0 is ChipAuthenticationInfo }) as? ChipAuthenticationInfo {
return "\(caInfo.securityProtocol)"
} else if let caPubKeyInfo = (self.dataGroupsRead[.DG14] as? DataGroup14)?
.securityInfos
.first(where: { $0 is ChipAuthenticationPublicKeyInfo }) as? ChipAuthenticationPublicKeyInfo {
return "\(caPubKeyInfo.securityProtocol.defaultChipAuthenticationSecurityProtocol)"
} else { return nil }
}

public var chipAuthenticationPublicKeySecurityProtocol: String? {
if let caPubKeyInfo = (self.dataGroupsRead[.DG14] as? DataGroup14)?
.securityInfos
.first(where: { $0 is ChipAuthenticationPublicKeyInfo }) as? ChipAuthenticationPublicKeyInfo {
return "\(caPubKeyInfo.securityProtocol)"
} else { return nil }
}

public var sodHashes: [String: String] {
if let sod = (self.dataGroupsRead[.SOD] as? SOD) {
var hashes: [String: String] = [:]
sod.signedData.encapContentInfo.forEach {
hashes.updateValue(
BytesRepresentationConverter.convertToHexRepresentation(from: $1),
forKey: $0.name
)}
return hashes
} else { return [:] }
}

public var sodPemCertificate: String? {
(self.dataGroupsRead[.SOD] as? SOD)?.certs.first?.pem
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ internal final class PassiveAuthenticationHandler {
throw NFCPassportReaderError.PassiveAuthenticationFailed("Data group hash not found in SOD")
}

let computedHash = try HashAlgorithm.hash([UInt8](dataGroup.data.encodedBytes), with: sod.signedData.digestAlgorithm)
let computedHash = try HashAlgorithm.hash([UInt8](dataGroup.data.encodedBytes), with: sod.signedData.digestAlgorithm ?? .SHA1 )

if computedHash != currentSodHash {
throw NFCPassportReaderError.PassiveAuthenticationFailed("\(String(describing: dataGroup.identifier)) hash not match")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ internal final class SignedData {
private var data: ASN1NodeCollection

/// The hash algorithm used for signing.
private(set) var digestAlgorithm: HashAlgorithm!
private(set) var digestAlgorithm: HashAlgorithm?

/// A dictionary representing encapsulated content information,
/// where the key is the ``DGTag`` and the value is the data group hash.
Expand Down
Loading