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

CAN support for PACE #106

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
4 changes: 4 additions & 0 deletions Example/NFCPassportReaderApp.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
A1FDC53225D3F19E00D22FF4 /* ViewModifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1FDC53125D3F19E00D22FF4 /* ViewModifiers.swift */; };
A1FDC53425D3F1DE00D22FF4 /* CheckBoxView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1FDC53325D3F1DE00D22FF4 /* CheckBoxView.swift */; };
A1FDC53625D43AB400D22FF4 /* MRZEntryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1FDC53525D43AB400D22FF4 /* MRZEntryView.swift */; };
F6BB3B65264092C8008E30E7 /* CANEntryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6BB3B64264092C8008E30E7 /* CANEntryView.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -87,6 +88,7 @@
A7BD003B33886ED0F3F03325 /* Pods_NFCPassportReaderApp.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_NFCPassportReaderApp.framework; sourceTree = BUILT_PRODUCTS_DIR; };
B239DD4BD3AA9ECF4F595D22 /* Pods-NFCPassportReaderApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NFCPassportReaderApp.release.xcconfig"; path = "Target Support Files/Pods-NFCPassportReaderApp/Pods-NFCPassportReaderApp.release.xcconfig"; sourceTree = "<group>"; };
DA39DA603B1A7EBD8FC38112 /* Pods_NFCPassportReaderAppTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_NFCPassportReaderAppTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
F6BB3B64264092C8008E30E7 /* CANEntryView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CANEntryView.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -194,6 +196,7 @@
A1298C9B25D539CD00F5713E /* HelperViews */,
A17134BD22C8EF7100C457C3 /* MainView.swift */,
A1FDC53525D43AB400D22FF4 /* MRZEntryView.swift */,
F6BB3B64264092C8008E30E7 /* CANEntryView.swift */,
A1FDC52D25D3F15D00D22FF4 /* SettingsView.swift */,
A1816E9B22C9059F00F546A0 /* PassportView.swift */,
A1C234C125DD19DE003FFD79 /* PassportSummaryView.swift */,
Expand Down Expand Up @@ -404,6 +407,7 @@
A1816E9C22C9059F00F546A0 /* PassportView.swift in Sources */,
A1769DF725D3E318006002D1 /* SettingsStore.swift in Sources */,
A1298C9F25D53A5C00F5713E /* ViewExt.swift in Sources */,
F6BB3B65264092C8008E30E7 /* CANEntryView.swift in Sources */,
A182DE6325DD730D00341204 /* FileManagerExt.swift in Sources */,
A1FDC52E25D3F15D00D22FF4 /* SettingsView.swift in Sources */,
A182DE6125DD6F1300341204 /* StoredPassportView.swift in Sources */,
Expand Down
9 changes: 8 additions & 1 deletion Example/NFCPassportReaderApp/Model/SettingsStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ final class SettingsStore: ObservableObject {
static let passportNumber = "passportNumber"
static let dateOfBirth = "dateOfBirth"
static let dateOfExpiry = "dateOfExpiry"
static let cardAccessNumber = "can"

static let allVals = [captureLog, logLevel, useNewVerification, passportNumber, dateOfBirth, dateOfExpiry]
static let allVals = [captureLog, logLevel, useNewVerification, passportNumber, dateOfBirth, dateOfExpiry, cardAccessNumber]
}

private let cancellable: Cancellable
Expand All @@ -41,6 +42,7 @@ final class SettingsStore: ObservableObject {
Keys.passportNumber: "",
Keys.dateOfBirth: Date().timeIntervalSince1970,
Keys.dateOfExpiry: Date().timeIntervalSince1970,
Keys.cardAccessNumber: "",
])

cancellable = NotificationCenter.default
Expand Down Expand Up @@ -102,6 +104,11 @@ final class SettingsStore: ObservableObject {
return d
}
}

var cardAccessNumber: String {
set { defaults.set(newValue, forKey: Keys.cardAccessNumber) }
get { defaults.string(forKey: Keys.cardAccessNumber) ?? "" }
}

@Published var passport : NFCPassportModel?
}
59 changes: 59 additions & 0 deletions Example/NFCPassportReaderApp/Views/CANEntryView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//
// CANEntryView.swift
// NFCPassportReaderApp
//
// Created by Tim Wilbrink on 23.04.21.
//

import SwiftUI

struct CANEntryView : View {
@EnvironmentObject var settings: SettingsStore

// These will be removed once DatePicker inline works correctly
@State private var editDOB = false
@State private var editDOE = false
@State private var editDateTitle : String = ""

var body : some View {
let canBinding = Binding<String>(get: {
settings.cardAccessNumber
}, set: {
settings.cardAccessNumber = $0.uppercased()
})
VStack {

TextField("CAN", text: canBinding)
.textCase(.uppercase)
.modifier(ClearButton(text: canBinding))
.textContentType(.name)
.foregroundColor(Color.primary)
.padding([.leading, .trailing])
.ignoresSafeArea(.keyboard, edges: .all)

Divider()


}
.ignoresSafeArea(.keyboard, edges: .bottom)
}
}


#if DEBUG
struct CANEntryView_Previews : PreviewProvider {

static var previews: some View {
let settings = SettingsStore()

return
Group {
NavigationView {
CANEntryView()
}
.environmentObject(settings)
.environment( \.colorScheme, .light)
}
}
}
#endif
15 changes: 13 additions & 2 deletions Example/NFCPassportReaderApp/Views/MainView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ struct MainView : View {
}.padding([.top, .trailing])
}
MRZEntryView()
Divider()
CANEntryView()

Button(action: {
self.scanPassport()
Expand Down Expand Up @@ -132,6 +134,8 @@ extension MainView {
let dob = df.string(from:settings.dateOfBirth)
let doe = df.string(from:settings.dateOfExpiry)

let can = settings.cardAccessNumber

let passportUtils = PassportUtils()
let mrzKey = passportUtils.getMRZKey( passportNumber: pptNr, dateOfBirth: dob, dateOfExpiry: doe)

Expand All @@ -149,9 +153,16 @@ extension MainView {
Log.logLevel = settings.logLevel
Log.storeLogs = settings.shouldCaptureLogs
Log.clearStoredLogs()


var accessKey = mrzKey
var paceKeyReference : UInt8 = PACEHandler.MRZ_PACE_KEY_REFERENCE
if (can != "") {
accessKey = can
paceKeyReference = PACEHandler.CAN_PACE_KEY_REFERENCE
}

// This is also how you can override the default messages displayed by the NFC View Controller
passportReader.readPassport(mrzKey: mrzKey, customDisplayMessage: { (displayMessage) in
passportReader.readPassport(accessKey: accessKey, paceKeyReference: paceKeyReference, customDisplayMessage: { (displayMessage) in
switch displayMessage {
case .requestPresentPassport:
return "Hold your iPhone near an NFC enabled passport."
Expand Down
40 changes: 30 additions & 10 deletions Sources/NFCPassportReader/PACEHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ extension PACEHandlerError: LocalizedError {
@available(iOS 13, *)
public class PACEHandler {


private static let MRZ_PACE_KEY_REFERENCE : UInt8 = 0x01
private static let CAN_PACE_KEY_REFERENCE : UInt8 = 0x02 // Not currently supported
public static let NO_PACE_KEY_REFERENCE : UInt8 = 0x00
public static let MRZ_PACE_KEY_REFERENCE : UInt8 = 0x01
public static let CAN_PACE_KEY_REFERENCE : UInt8 = 0x02
private static let PIN_PACE_KEY_REFERENCE : UInt8 = 0x03 // Not currently supported
private static let CUK_PACE_KEY_REFERENCE : UInt8 = 0x04 // Not currently supported

Expand Down Expand Up @@ -72,7 +72,7 @@ public class PACEHandler {
isPACESupported = true
}

public func doPACE( mrzKey : String, completed: @escaping (Bool)->() ) {
public func doPACE( paceKeySeed : String, paceKeyReference: UInt8, completed: @escaping (Bool)->() ) {
guard isPACESupported else {
completed( false )
return
Expand All @@ -92,9 +92,18 @@ public class PACEHandler {
digestAlg = try paceInfo.getDigestAlgorithm() // Either SHA-1 or SHA-256.
keyLength = try paceInfo.getKeyLength() // Get key length the enc cipher. Either 128, 192, or 256.

paceKeyType = PACEHandler.MRZ_PACE_KEY_REFERENCE
paceKey = try createPaceKey( from: mrzKey )

switch paceKeyReference {
case PACEHandler.MRZ_PACE_KEY_REFERENCE:
paceKeyType = PACEHandler.MRZ_PACE_KEY_REFERENCE
paceKey = try createPaceKey( from: paceKeySeed )
case PACEHandler.CAN_PACE_KEY_REFERENCE:
paceKeyType = PACEHandler.CAN_PACE_KEY_REFERENCE
paceKey = try createPaceCanKey( from: paceKeySeed )
default:
return handleError( "PACE", "PACE Key Reference not supported (\(paceKeyReference)). Currently only 0x01 (MRZ) and 0x02 (CAN) may be used." )
}


// Temporary logging
Log.verbose("doPace - inpit parameters" )
Log.verbose("paceOID - \(paceOID)" )
Expand All @@ -104,15 +113,14 @@ public class PACEHandler {
Log.verbose("cipherAlg - \(cipherAlg)" )
Log.verbose("digestAlg - \(digestAlg)" )
Log.verbose("keyLength - \(keyLength)" )
Log.verbose("keyLength - \(mrzKey)" )
Log.verbose("paceKeyReference - \(paceKeyReference)" )
Log.verbose("paceKey - \(binToHexRep(paceKey, asArray:true))" )

// First start the initial auth call
tagReader.sendMSESetATMutualAuth(oid: paceOID, keyType: paceKeyType, completed: { [unowned self] response, error in
if let error = error {
return handleError( "MSESatATMutualAuth", "Error - \(error.localizedDescription)" )
}

}
self.doStep1()
})

Expand Down Expand Up @@ -637,10 +645,22 @@ extension PACEHandler {
let buf: [UInt8] = Array(mrzKey.utf8)
let hash = calcSHA1Hash(buf)


let smskg = SecureMessagingSessionKeyGenerator()
let key = try smskg.deriveKey(keySeed: hash, cipherAlgName: cipherAlg, keyLength: keyLength, nonce: nil, mode: .PACE_MODE, paceKeyReference: paceKeyType)
return key
}

/// Computes a key seed based on an CAN key
/// - Parameter the CAN key
/// - Returns a encoded key based on the CAN key that can be used for PACE
func createPaceCanKey( from canKey: String ) throws -> [UInt8] {
var buf: [UInt8] = Array(canKey.utf8)

let smskg = SecureMessagingSessionKeyGenerator()
let key = try smskg.deriveKey(keySeed: buf, cipherAlgName: cipherAlg, keyLength: keyLength, nonce: nil, mode: .PACE_MODE, paceKeyReference: paceKeyType)
return key
}

/// Performs the ECDH PACE GM key agreement protocol by multiplying a private key with a public key
/// - Parameters:
Expand Down
37 changes: 24 additions & 13 deletions Sources/NFCPassportReader/PassportReader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ public class PassportReader : NSObject {
private var bacHandler : BACHandler?
private var caHandler : ChipAuthenticationHandler?
private var paceHandler : PACEHandler?
private var mrzKey : String = ""
private var accessKey : String = ""
private var paceKeyReference : UInt8?
private var dataAmountToReadOverride : Int? = nil

private var scanCompletedHandler: ((NFCPassportModel?, NFCPassportReaderError?)->())!
Expand Down Expand Up @@ -58,9 +59,10 @@ public class PassportReader : NSObject {
dataAmountToReadOverride = amount
}

public func readPassport( mrzKey : String, tags: [DataGroupId] = [], skipSecureElements :Bool = true, customDisplayMessage: ((NFCViewDisplayMessage) -> String?)? = nil, completed: @escaping (NFCPassportModel?, NFCPassportReaderError?)->()) {
public func readPassport( accessKey : String, paceKeyReference: UInt8, tags: [DataGroupId] = [], skipSecureElements :Bool = true, customDisplayMessage: ((NFCViewDisplayMessage) -> String?)? = nil, completed: @escaping (NFCPassportModel?, NFCPassportReaderError?)->()) {
self.passport = NFCPassportModel()
self.mrzKey = mrzKey
self.accessKey = accessKey
self.paceKeyReference = paceKeyReference

self.dataGroupsToRead.removeAll()
self.dataGroupsToRead.append( contentsOf:tags)
Expand Down Expand Up @@ -212,7 +214,7 @@ extension PassportReader {
print( "Error reading CardAccess - \(error)" )
}
}

// If we managed to read the CardAccess the PACE should be supported otherwise drop back to BAC
if let cardAccess = ca {
passport.cardAccess = cardAccess
Expand All @@ -226,12 +228,12 @@ extension PassportReader {
}

func doPACEAuthentication(cardAccess:CardAccess) {
self.handlePACE(cardAccess:cardAccess, completed: { [weak self] error in
self.handlePACE(cardAccess:cardAccess, paceKeyReference: paceKeyReference ?? PACEHandler.NO_PACE_KEY_REFERENCE, completed: { [weak self] error in
if error == nil {
Log.info( "PACE Successful" )
self?.passport.PACEStatus = .success

// At this point, BAC Has been done and the TagReader has been set up with the SecureMessaging
// At this point, PACE has been done and the TagReader has been set up with the SecureMessaging
// session keys
self?.tagReader?.selectPassportApplication(completed: { response, error in

Expand All @@ -241,10 +243,18 @@ extension PassportReader {
} else if let error = error {
Log.info( "PACE Failed - \(error.localizedDescription)" )
self?.passport.PACEStatus = .failed
self?.tagReader?.selectPassportApplication(completed: { response, error in
self?.doBACAuthentication()
})
// let displayMessage = NFCViewDisplayMessage.error(error)

// Try BAC after failed PACE attempt. But only if MRZ is used as Key. BAC does not support CAN or similar.
// If BAC is no option, return an error
if (self?.paceKeyReference == PACEHandler.NO_PACE_KEY_REFERENCE || self?.paceKeyReference == PACEHandler.MRZ_PACE_KEY_REFERENCE) {
self?.tagReader?.selectPassportApplication(completed: { response, error in
self?.doBACAuthentication()
})
} else {
let displayMessage = NFCViewDisplayMessage.error(error)
self?.invalidateSession(errorMessage: displayMessage, error: error)
}
// let displayMessage = NFCViewDisplayMessage.error(error)
// self?.invalidateSession(errorMessage: displayMessage, error: error)
}
})
Expand Down Expand Up @@ -344,13 +354,13 @@ extension PassportReader {
Log.info( "Starting Basic Access Control (BAC)" )

self.bacHandler = BACHandler( tagReader: tagReader )
bacHandler?.performBACAndGetSessionKeys( mrzKey: mrzKey ) { error in
bacHandler?.performBACAndGetSessionKeys( mrzKey: accessKey ) { error in
self.bacHandler = nil
completed(error)
}
}

func handlePACE( cardAccess:CardAccess, completed: @escaping (NFCPassportReaderError?)->()) {
func handlePACE( cardAccess:CardAccess, paceKeyReference: UInt8 ,completed: @escaping (NFCPassportReaderError?)->()) {
guard let tagReader = self.tagReader else {
completed(NFCPassportReaderError.NoConnectedTag)
return
Expand All @@ -360,7 +370,8 @@ extension PassportReader {

do {
self.paceHandler = try PACEHandler( cardAccess: cardAccess, tagReader: tagReader )
paceHandler?.doPACE(mrzKey: mrzKey ) { paceSucceeded in

paceHandler?.doPACE(paceKeySeed: accessKey, paceKeyReference: paceKeyReference) { paceSucceeded in
if paceSucceeded {
completed(nil)
} else {
Expand Down