Skip to content

Commit 5798d90

Browse files
committed
Read correct length from files until error
Fixes problem with VASCO Digipass 870 aka belfius card reader
1 parent b385ede commit 5798d90

File tree

2 files changed

+142
-81
lines changed

2 files changed

+142
-81
lines changed

eidReader/AppDelegate.swift

+12-7
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,8 @@ class AppDelegate: NSObject, NSApplicationDelegate {
113113
}
114114
}
115115

116-
func updateImageProgress(progress: Double) {
117-
DispatchQueue.main.async { self.viewController?.imageProgressIndicator.doubleValue = progress }
116+
func updateImageProgress(progress: UInt8) {
117+
DispatchQueue.main.async { self.viewController?.imageProgressIndicator.doubleValue = Double(progress) }
118118
}
119119

120120
func clearCard() {
@@ -164,12 +164,17 @@ class AppDelegate: NSObject, NSApplicationDelegate {
164164
}
165165
} else {
166166
card.getBasicInfo { (basicInfo, error) in
167-
card.getProfileImage(updateProgress: self.updateImageProgress) { (imageData, error) in
168-
if let imageData = imageData, let image = NSImage(data: imageData) {
169-
DispatchQueue.main.async {
170-
self.profileImage = image
171-
self.viewController?.profileImage.image = image
167+
card.getProfileImage(updateProgress: self.updateImageProgress) { response in
168+
switch response {
169+
case .data(let imageData):
170+
if let image = NSImage(data: imageData) {
171+
DispatchQueue.main.async {
172+
self.profileImage = image
173+
self.viewController?.profileImage.image = image
174+
}
172175
}
176+
case .error(_):
177+
break // TODO: add error handling
173178
}
174179
card.endSession()
175180
}

eidReader/BEIDCard.swift

+130-74
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,12 @@
88

99
import Foundation
1010
import CryptoTokenKit
11-
import AppKit
1211
import MapKit
1312

14-
let idFile: [UInt8] = [0x3F, 0x00, 0xDF, 0x01, 0x40, 0x38]
15-
let photoFile: [UInt8] = [0x3F, 0x00, 0xDF, 0x01, 0x40, 0x35]
16-
let addressFile: [UInt8] = [0x3F, 0x00, 0xDF, 0x01, 0x40, 0x33]
17-
let basicInfoFile: [UInt8] = [0x3F, 0x00, 0xDF, 0x01, 0x40, 0x31]
13+
let idFile: [UInt8] = [0xDF, 0x01, 0x40, 0x38]
14+
let photoFile: [UInt8] = [0xDF, 0x01, 0x40, 0x35]
15+
let addressFile: [UInt8] = [0xDF, 0x01, 0x40, 0x33]
16+
let basicInfoFile: [UInt8] = [0xDF, 0x01, 0x40, 0x31]
1817
let selectFile: [UInt8] = [0, 0xA4, 0x08, 0x0C]
1918
let readBinary: [UInt8] = [0, 0xB0]
2019

@@ -48,7 +47,7 @@ class Address: NSObject, MKAnnotation, NSCoding {
4847
postalCode = aDecoder.decodeObject(forKey: ArchiveKey.postalColde.rawValue) as! String
4948
city = aDecoder.decodeObject(forKey: ArchiveKey.city.rawValue) as! String
5049
title = aDecoder.decodeObject(forKey: ArchiveKey.title.rawValue) as? String
51-
50+
5251
let latitude = aDecoder.decodeDouble(forKey: ArchiveKey.latitude.rawValue)
5352
let longitude = aDecoder.decodeDouble(forKey: ArchiveKey.longitude.rawValue)
5453
coordinate = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
@@ -126,17 +125,17 @@ class BasicInfo: NSObject, NSCoding {
126125
return aDecoder.decodeObject(forKey: key.rawValue) as! String
127126
}
128127

129-
cardNumber = string(with: .cardNumber)
128+
cardNumber = string(with: .cardNumber)
130129
releasePlace = string(with: .releasePlace)
131-
firstName = string(with: .firstName)
132-
lastName = string(with: .lastName)
133-
otherName = string(with: .otherName)
134-
nationality = string(with: .nationality)
135-
birthPlace = string(with: .birthPlace)
130+
firstName = string(with: .firstName)
131+
lastName = string(with: .lastName)
132+
otherName = string(with: .otherName)
133+
nationality = string(with: .nationality)
134+
birthPlace = string(with: .birthPlace)
136135

137136
validityStart = aDecoder.decodeObject(forKey: ArchiveKey.validityStart.rawValue) as! Date
138-
validityEnd = aDecoder.decodeObject(forKey: ArchiveKey.validityEnd.rawValue) as! Date
139-
birthday = aDecoder.decodeObject(forKey: ArchiveKey.birthday.rawValue) as! Date
137+
validityEnd = aDecoder.decodeObject(forKey: ArchiveKey.validityEnd.rawValue) as! Date
138+
birthday = aDecoder.decodeObject(forKey: ArchiveKey.birthday.rawValue) as! Date
140139

141140
birthNumber = aDecoder.decodeObject(forKey: ArchiveKey.birthNumber.rawValue) as! UInt16
142141
}
@@ -192,102 +191,155 @@ extension TKSmartCard {
192191
case SecurityStatusNotSatisfied, IncorrectLength(expected: UInt8)
193192
}
194193

195-
func read(file: [UInt8], length: UInt8, offset: UInt16 = 0, reply: @escaping (Data?, Error?)->Void) {
194+
enum ReadResponse {
195+
case data(Data)
196+
case error(Error)
197+
}
198+
199+
struct UnknownError: Error {}
200+
201+
/// Select a file on the card by path as described in ISO 7816-4
202+
///
203+
/// - Parameter dedicatedFile: Absolute path to dedicated file without the MF Identifier
204+
func select(dedicatedFile file: [UInt8], handler reply: @escaping (Error?)->Void) {
196205
self.transmit(Data(bytes: selectFile + [UInt8(file.count)] + file)) { (selectFileReply, error) in
197206
if let error = error {
198-
reply(nil, error)
207+
reply(error)
199208
} else if let selectFileReply = selectFileReply {
200209
switch (selectFileReply[0], selectFileReply[1]) {
201210
case (0x62, 0x83):
202-
reply(nil, SelectFileError.SelectedFileNotActivated)
211+
reply(SelectFileError.SelectedFileNotActivated)
203212
case (0x64, 0):
204-
reply(nil, CardError.NoPreciseDiagnostic)
213+
reply(CardError.NoPreciseDiagnostic)
205214
case (0x65, 0x81):
206-
reply(nil, CardError.EepromCorrupted)
215+
reply(CardError.EepromCorrupted)
207216
case (0x6A, 0x82):
208-
reply(nil, SelectFileError.FileNotFound)
217+
reply(SelectFileError.FileNotFound)
209218
case (0x6A, 0x86):
210-
reply(nil, CardError.WrongParameterP1P2)
219+
reply(CardError.WrongParameterP1P2)
211220
case (0x6A, 0x87):
212-
reply(nil, SelectFileError.LcInconsistentWithP1P2)
221+
reply(SelectFileError.LcInconsistentWithP1P2)
213222
case (0x69, 0x99), (0x69, 0x85):
214-
reply(nil, SelectFileError.AttemptToSelectForbiddenLogicalChannel)
223+
reply(SelectFileError.AttemptToSelectForbiddenLogicalChannel)
215224
case (0x6D, 0):
216-
reply(nil, CardError.CommandNotAvailableWithinCurrentLifeCycle)
225+
reply(CardError.CommandNotAvailableWithinCurrentLifeCycle)
217226
case (0x6E, 0):
218-
reply(nil, SelectFileError.ClaNotSupported)
227+
reply(SelectFileError.ClaNotSupported)
219228
case (0x90, 0):
220-
self.transmit(Data(bytes: readBinary + [UInt8(offset >> 8), UInt8(offset & 0xff), length])) { (binaryReply, error) in
221-
if let error = error {
222-
print(error)
223-
} else if let binaryReply = binaryReply {
224-
let statusBytes = (binaryReply[binaryReply.endIndex-2], binaryReply.last!)
225-
switch statusBytes {
226-
case (0x64, 0):
227-
reply(nil, CardError.NoPreciseDiagnostic)
228-
case (0x65, 0x81):
229-
reply(nil, CardError.EepromCorrupted)
230-
case (0x6B, 0):
231-
reply(nil, CardError.WrongParameterP1P2)
232-
case (0x6D, 0):
233-
reply(nil, CardError.CommandNotAvailableWithinCurrentLifeCycle)
234-
case (0x69, 0x82):
235-
reply(nil, ReadBinaryError.SecurityStatusNotSatisfied)
236-
case (0x6C, _):
237-
reply(nil, ReadBinaryError.IncorrectLength(expected: binaryReply.last!))
238-
case (0x90, 0):
239-
reply(Data(binaryReply.dropLast(2)), nil)
240-
default: break
241-
}
242-
}
243-
}
244-
default: break
229+
reply(nil)
230+
default:
231+
reply(UnknownError())
245232
}
246233
} else {
247-
// TODO: cleanup
234+
fatalError("transmit must either have a response or an error")
248235
}
249236
}
250237
}
251238

252-
func readUntilError(file: [UInt8], data: Data = Data(bytes:[]), counter: UInt8 = 0, updateProgress: ((Double)->Void)? = nil, reply: @escaping (Data?, Error?)->Void) {
253-
let offset = UInt16(counter) << 8
254-
updateProgress?(Double(counter))
255-
read(file: file, length: 0, offset: offset) { (newData, error) in
239+
240+
/// Read bytes from current file
241+
/// as described in section "7.2.3 READ BINARY command" of ISO 7816-4
242+
///
243+
/// - Parameters:
244+
/// - length: number of bytes to read (Le)
245+
/// - offset: number of bytes to skip (15-bit unsigned integer, ranging from 0 to 32 767)
246+
/// - handler: function to execute after completion
247+
func readBytes(length: UInt8, offset: UInt16 = 0, handler: @escaping (ReadResponse) -> Void) {
248+
self.transmit(Data(bytes: readBinary + [UInt8(offset >> 8), UInt8(offset & 0xff), length])) { (binaryReply, error) in
256249
if let error = error {
250+
print(error)
251+
} else if let binaryReply = binaryReply {
252+
let statusBytes = (binaryReply[binaryReply.endIndex-2], binaryReply.last!)
253+
switch statusBytes {
254+
case (0x64, 0):
255+
handler(.error(CardError.NoPreciseDiagnostic) )
256+
case (0x65, 0x81):
257+
handler(.error(CardError.EepromCorrupted) )
258+
case (0x6B, 0):
259+
handler(.error(CardError.WrongParameterP1P2) )
260+
case (0x6D, 0):
261+
handler(.error(CardError.CommandNotAvailableWithinCurrentLifeCycle) )
262+
case (0x69, 0x82):
263+
handler(.error(ReadBinaryError.SecurityStatusNotSatisfied) )
264+
case (0x6C, _):
265+
handler(.error(ReadBinaryError.IncorrectLength(expected: binaryReply.last!)) )
266+
case (0x90, 0):
267+
handler(.data(
268+
Data(binaryReply.dropLast(2))
269+
))
270+
default:
271+
handler(
272+
.error( UnknownError() )
273+
)
274+
}
275+
}
276+
}
277+
}
278+
279+
func readBytesUntilError(data: Data = Data(bytes:[]), updateProgress: ((UInt8)->Void)? = nil, handler reply: @escaping (ReadResponse)->Void) {
280+
let offset = UInt16(data.count)
281+
updateProgress?(UInt8(offset/256))
282+
readBytes(length: 0, offset: offset) {
283+
switch $0 {
284+
case .error(let error):
257285
switch error {
258286
case ReadBinaryError.IncorrectLength(let expectedLength):
259-
self.read(file: file, length: expectedLength, offset: offset) { (newData, error) in
260-
updateProgress?(Double(counter+1))
261-
if let error = error {
262-
reply(data, error)
263-
} else if let newData = newData {
287+
self.readBytes(length: expectedLength, offset: offset) { response in
288+
updateProgress?(UInt8(offset/256))
289+
switch response {
290+
case .error(let error):
291+
reply(.error(error))
292+
case .data(let newData):
264293
var concat = Data()
265294
concat.reserveCapacity(data.count+newData.count)
266295
concat.append(data)
267296
concat.append(newData)
268-
reply(concat, nil)
297+
reply(.data(concat))
269298
}
270299
}
300+
case CardError.WrongParameterP1P2:
301+
if data.count > 0 {
302+
reply(.data(data))
303+
} else {
304+
reply(.error(error))
305+
}
271306
default:
272-
reply(data, error)
307+
reply(.error(error))
273308
}
274-
} else if let newData = newData {
309+
case .data(let newData):
275310
var concat = Data()
276311
concat.reserveCapacity(data.count+newData.count)
277312
concat.append(data)
278313
concat.append(newData)
279-
self.readUntilError(file: file, data: concat, counter: counter+1, updateProgress: updateProgress) { (data, error) in
280-
reply(data, error)
314+
if newData.count < 256 {
315+
reply(.data(concat))
316+
} else {
317+
self.readBytesUntilError(data: concat, updateProgress: updateProgress, handler: reply)
281318
}
282319
}
283320
}
284321
}
285322

323+
/// Select dedicated file and read all its bytes. This is a helper function that combines the SELECT (section 7.1.1) & READ BINARY (section 7.2.3) commands from ISO 7816-4.
324+
///
325+
/// - Parameters:
326+
/// - file: Absolute path to dedicated file without the MF Identifier
327+
/// - updateProgress: function that gets called while reading the large files and informs the progress of the read command
328+
/// - reply: function that gets called when the file is read
329+
func read(file: [UInt8], updateProgress: ((UInt8)->Void)? = nil, reply: @escaping (ReadResponse)->Void) {
330+
select(dedicatedFile: file) {
331+
if let error = $0 {
332+
reply(.error(error) )
333+
} else {
334+
self.readBytesUntilError(updateProgress: updateProgress, handler: reply)
335+
}
336+
}
337+
}
338+
286339
func getAddress(geocodeCompletionHandler: @escaping CLGeocodeCompletionHandler = {(_,_) in}, reply: @escaping (_ address: Address?, _ error: Error?) -> Void) {
287-
readUntilError(file: addressFile) { (data, error) in
288-
if let error = error {
289-
reply(nil, error)
290-
} else if let data = data {
340+
read(file: addressFile) { response in
341+
switch response {
342+
case .data(let data):
291343
let street = 2 ..< Int(data[1]) + 2
292344
let postalCode = street.upperBound + 2 ..< street.upperBound + Int(data[street.upperBound+1]) + 2
293345
let city = postalCode.upperBound + 2 ..< postalCode.upperBound + Int(data[postalCode.upperBound+1]) + 2
@@ -297,23 +349,26 @@ extension TKSmartCard {
297349
String(bytes: data[postalCode], encoding: .utf8)!,
298350
String(bytes: data[city], encoding: .utf8)!
299351
), title: NSLocalizedString("Domicile", comment: "Domicile"), geocodeCompletionHandler: geocodeCompletionHandler), nil)
352+
case .error(let error):
353+
reply(nil, error)
300354
}
301355
}
302356
}
303357

304358

305359
func getBasicInfo(reply: @escaping (BasicInfo?, Error?)->Void) {
306-
readUntilError(file: basicInfoFile) { (data, error) in
307-
if let error = error {
360+
read(file: basicInfoFile) { response in
361+
switch response {
362+
case .error(let error):
308363
reply(nil, error)
309-
} else if let data = data {
364+
case .data(let data):
310365
reply(BasicInfo(from: data), nil)
311366
}
312367
}
313368
}
314369

315-
func getProfileImage(updateProgress: ((Double)->Void)? = nil, reply: @escaping (Data?, Error?)->Void) {
316-
readUntilError(file: photoFile, updateProgress: updateProgress, reply: reply)
370+
func getProfileImage(updateProgress: ((UInt8)->Void)? = nil, reply: @escaping (ReadResponse)->Void) {
371+
read(file: photoFile, updateProgress: updateProgress, reply: reply)
317372
}
318373
}
319374

@@ -339,3 +394,4 @@ extension DateFormatter {
339394
self.dateStyle = style
340395
}
341396
}
397+

0 commit comments

Comments
 (0)