Skip to content

Instantly share code, notes, and snippets.

@treastrain
Last active December 7, 2019 11:21
Show Gist options
  • Save treastrain/5a6196915374f430951a4a768a1b1402 to your computer and use it in GitHub Desktop.
Save treastrain/5a6196915374f430951a4a768a1b1402 to your computer and use it in GitHub Desktop.
Core NFC (iOS 13.0 以降) で運転免許証の本籍を読み取る
//
// Extensions.swift
// driversLicenceReader
//
// Created by treastrain on 2019/12/06.
// Copyright © 2019 treastrain / Tanaka Ryoga. All rights reserved.
//
import Foundation
extension String {
func convertToJISX0201() -> [UInt8] {
if self.count != 4 {
fatalError("暗証番号が4ケタではない")
}
let pinStringArray = Array(self)
let pinSet = Set(pinStringArray)
let enterableNumberSet: Set<String.Element> = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "*"]
if !pinSet.isSubset(of: enterableNumberSet) {
fatalError("暗証番号で使用できない文字が含まれている")
}
let pin = pinStringArray.map { (c) -> UInt8 in
self.encodeToJISX0201(c)
}
return pin
}
func encodeToJISX0201(_ c: Character) -> UInt8 {
switch c {
case "0":
return 0x30
case "1":
return 0x31
case "2":
return 0x32
case "3":
return 0x33
case "4":
return 0x34
case "5":
return 0x35
case "6":
return 0x36
case "7":
return 0x37
case "8":
return 0x38
case "9":
return 0x39
case "*":
return 0x2A
default:
fatalError()
}
}
init(jisX0208Data: [Data]) {
guard let path = Bundle.main.path(forResource: "JIS0208", ofType: "TXT") else {
fatalError("JIS0208.TXT が見つかりません")
}
let contents = try! String(contentsOfFile: path, encoding: .utf8)
let tableStrings = contents.components(separatedBy: .newlines)
var tableJISX0208ToUnicode: [Data : Data] = [:]
for row in tableStrings {
if row.first != "#" {
let col = row.components(separatedBy: .whitespaces)
if col.count > 2 {
let col1 = col[1].hexData
let col2 = col[2].hexData
tableJISX0208ToUnicode[col1] = col2
}
}
}
var string = ""
for data in jisX0208Data {
if let unicodeData = tableJISX0208ToUnicode[data], let s = String(data: unicodeData, encoding: .unicode) {
string += s
} else {
switch data {
case Data([0xFF, 0xF1]):
string += "(外字1)"
case Data([0xFF, 0xF2]):
string += "(外字2)"
case Data([0xFF, 0xF3]):
string += "(外字3)"
case Data([0xFF, 0xF4]):
string += "(外字4)"
case Data([0xFF, 0xF5]):
string += "(外字5)"
case Data([0xFF, 0xF6]):
string += "(外字6)"
case Data([0xFF, 0xF7]):
string += "(外字7)"
case Data([0xFF, 0xFA]):
string += "(欠字)"
default:
string += "(未定義)"
}
}
}
self = string
}
// 参考: https://gist.github.com/eligoptimove/09ee57ac2e0c5d7889f761f40c73e9a6
var bytes: [UInt8] {
var i = self.startIndex
return (0..<self.count/2).compactMap { _ in
defer { i = self.index(i, offsetBy: 2) }
return UInt8(self[i...index(after: i)], radix: 16)
}
}
var hexData: Data {
return Data(self.bytes)
}
}
//
// ViewController.swift
// driversLicenceReader
//
// Created by treastrain on 2019/12/06.
// Copyright © 2019 treastrain / Tanaka Ryoga. All rights reserved.
//
import UIKit
import CoreNFC
class ViewController: UIViewController, NFCTagReaderSessionDelegate {
var session: NFCTagReaderSession?
//// 暗証番号1 **絶対に間違えないで**
var pin1 = "1234"
//// 暗証番号2 **絶対に間違えないで**
var pin2 = "5678"
override func viewDidLoad() {
super.viewDidLoad()
guard NFCTagReaderSession.readingAvailable else {
print("NFC タグの読み取りに非対応のデバイス")
return
}
self.session = NFCTagReaderSession(pollingOption: .iso14443, delegate: self)
self.session?.alertMessage = "運転免許証の上に iPhone の上部を載せてください"
self.session?.begin()
}
func tagReaderSessionDidBecomeActive(_ session: NFCTagReaderSession) {
print("tagReaderSessionDidBecomeActive(_:)")
}
func tagReaderSession(_ session: NFCTagReaderSession, didInvalidateWithError error: Error) {
let readerError = error as! NFCReaderError
print(readerError.code, readerError.localizedDescription)
}
func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) {
print("tagReaderSession(_:didDetect:)")
let tag = tags.first!
session.connect(to: tag) { (error) in
if let error = error {
session.invalidate(errorMessage: error.localizedDescription)
return
}
guard case NFCTag.iso7816(let driversLicenseCardTag) = tag else {
session.invalidate(errorMessage: "ISO 7816 準拠ではない")
return
}
session.alertMessage = "運転免許証を読み取っています…"
/// MF を選択
let adpu = NFCISO7816APDU(instructionClass: 0x00, instructionCode: 0xA4, p1Parameter: 0x00, p2Parameter: 0x00, data: Data([]), expectedResponseLength: -1)
driversLicenseCardTag.sendCommand(apdu: adpu) { (responseData, sw1, sw2, error) in
if let error = error {
session.invalidate(errorMessage: error.localizedDescription)
return
}
if sw1 != 0x90 {
session.invalidate(errorMessage: "MF の選択でエラー: ステータス \(sw1), \(sw2)")
return
}
/// IEF01 を選択
let adpu = NFCISO7816APDU(instructionClass: 0x00, instructionCode: 0xA4, p1Parameter: 0x02, p2Parameter: 0x0C, data: Data([0x00, 0x01]), expectedResponseLength: -1)
driversLicenseCardTag.sendCommand(apdu: adpu) { (responseData, sw1, sw2, error) in
if let error = error {
session.invalidate(errorMessage: error.localizedDescription)
return
}
if sw1 != 0x90 {
session.invalidate(errorMessage: "IEF01 の選択でエラー: ステータス \(sw1), \(sw2)")
return
}
/// 照合
let adpu = NFCISO7816APDU(instructionClass: 0x00, instructionCode: 0x20, p1Parameter: 0x00, p2Parameter: 0x80, data: Data(self.pin1.convertToJISX0201()), expectedResponseLength: -1)
driversLicenseCardTag.sendCommand(apdu: adpu) { (responseData, sw1, sw2, error) in
if let error = error {
session.invalidate(errorMessage: error.localizedDescription)
return
}
if sw1 != 0x90 {
if sw1 == 0x63 {
if sw2 == 0x00 {
session.invalidate(errorMessage: "暗証番号1の照合でエラー: 照合の不一致である")
} else {
let remaining = sw2 - 0xC0
session.invalidate(errorMessage: "暗証番号1の照合でエラー: 照合の不一致である 残り試行回数: \(remaining)")
}
} else {
session.invalidate(errorMessage: "暗証番号1の照合でエラー: ステータス \(sw1), \(sw2)")
}
return
}
/// IEF02 を選択
let adpu = NFCISO7816APDU(instructionClass: 0x00, instructionCode: 0xA4, p1Parameter: 0x02, p2Parameter: 0x0C, data: Data([0x00, 0x02]), expectedResponseLength: -1)
driversLicenseCardTag.sendCommand(apdu: adpu) { (responseData, sw1, sw2, error) in
if let error = error {
session.invalidate(errorMessage: error.localizedDescription)
return
}
if sw1 != 0x90 {
session.invalidate(errorMessage: "IEF02 の選択でエラー: ステータス \(sw1), \(sw2)")
return
}
/// 照合
let adpu = NFCISO7816APDU(instructionClass: 0x00, instructionCode: 0x20, p1Parameter: 0x00, p2Parameter: 0x80, data: Data(self.pin2.convertToJISX0201()), expectedResponseLength: -1)
driversLicenseCardTag.sendCommand(apdu: adpu) { (responseData, sw1, sw2, error) in
if let error = error {
session.invalidate(errorMessage: error.localizedDescription)
return
}
if sw1 != 0x90 {
if sw1 == 0x63 {
if sw2 == 0x00 {
session.invalidate(errorMessage: "暗証番号2の照合でエラー: 照合の不一致である")
} else {
let remaining = sw2 - 0xC0
session.invalidate(errorMessage: "暗証番号2の照合でエラー: 照合の不一致である 残り試行回数: \(remaining)")
}
} else {
session.invalidate(errorMessage: "暗証番号2の照合でエラー: ステータス \(sw1), \(sw2)")
}
return
}
/// DF1 を選択
let adpu = NFCISO7816APDU(instructionClass: 0x00, instructionCode: 0xA4, p1Parameter: 0x04, p2Parameter: 0x0C, data: Data([0xA0, 0x00, 0x00, 0x02, 0x31, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), expectedResponseLength: -1)
driversLicenseCardTag.sendCommand(apdu: adpu) { (responseData, sw1, sw2, error) in
if let error = error {
session.invalidate(errorMessage: error.localizedDescription)
return
}
if sw1 != 0x90 {
session.invalidate(errorMessage: "DF1 の選択でエラー: ステータス \(sw1), \(sw2)")
return
}
/// EF02 を選択
let adpu = NFCISO7816APDU(instructionClass: 0x00, instructionCode: 0xA4, p1Parameter: 0x02, p2Parameter: 0x0C, data: Data([0x00, 0x02]), expectedResponseLength: -1)
driversLicenseCardTag.sendCommand(apdu: adpu) { (responseData, sw1, sw2, error) in
if let error = error {
session.invalidate(errorMessage: error.localizedDescription)
return
}
if sw1 != 0x90 {
session.invalidate(errorMessage: "DF1/EF02 の選択でエラー: ステータス \(sw1), \(sw2)")
return
}
/// バイナリを読み取る
let adpu = NFCISO7816APDU(instructionClass: 0x00, instructionCode: 0xB0, p1Parameter: 0x00, p2Parameter: 0x00, data: Data([]), expectedResponseLength: 82)
driversLicenseCardTag.sendCommand(apdu: adpu) { (responseData, sw1, sw2, error) in
if let error = error {
session.invalidate(errorMessage: error.localizedDescription)
return
}
if sw1 != 0x90 {
session.invalidate(errorMessage: "バイナリの読み取りでエラー: ステータス \(sw1), \(sw2)")
return
}
/// TLV フィールド
let tag = responseData[0]
let length = Int(responseData[1])
let value = responseData[2..<responseData.count].map { $0 }
let registeredDomicileData = stride(from: 0, to: length, by: 2).map { (i) -> Data in
var bytes = (UInt16(value[i + 1]) << 8) + UInt16(value[i])
return Data(bytes: &bytes, count: MemoryLayout<UInt16>.size)
}
let registeredDomicile = String(jisX0208Data: registeredDomicileData)
print(registeredDomicile)
session.alertMessage = "完了"
session.invalidate()
}
}
}
}
}
}
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment