apdu.swift (11411B)
1 // 2 // apdu.swift 3 // HCEtest 4 // 5 // Created by Marc Stibane on 2024-07-27. 6 // 7 8 import Foundation 9 import os.log 10 11 @MainActor 12 class APDU { 13 let talerUri: String 14 nonisolated let logger = Logger(subsystem: "net.taler.gnu", category: "APDU") 15 private var readCapabilityContainerCheck = false 16 17 init(_ talerUri: String) { 18 self.talerUri = talerUri 19 } 20 21 private var APDU_SELECT: Data { 22 Data(fromUInt8Array: [ 23 0x00, // CLA - Class - Class of instruction 24 0xA4, // INS - Instruction - Instruction code 25 0x04, // P1 - Parameter 1 - Instruction parameter 1 26 0x00, // P2 - Parameter 2 - Instruction parameter 2 27 0x07, // Lc field - Number of bytes present in the data field of the command 28 0xD2, // NDEF Tag Application name 29 0x76, 30 0x00, 31 0x00, 32 0x85, 33 0x01, 34 0x01, 35 0x00, // Le field - Maximum number of bytes expected in the data field of the response to the command 36 ]) 37 } // 00a4 0400 07 d2760000850101 00 38 39 private var CAPABILITY_CONTAINER_OK: Data { 40 Data(fromUInt8Array: [ 41 0x00, // CLA - Class - Class of instruction 42 0xA4, // INS - Instruction - Instruction code 43 0x00, // P1 - Parameter 1 - Instruction parameter 1 44 0x0C, // P2 - Parameter 2 - Instruction parameter 2 45 0x02, // Lc field - Number of bytes present in the data field of the command 46 0xE1, // file identifier of the CC file 47 0x03, 48 ]) 49 } // 00a4 000c 02 e103 50 51 private var READ_CAPABILITY_CONTAINER: Data { 52 Data(fromUInt8Array: [ 53 0x00, // CLA - Class - Class of instruction 54 0xB0, // INS - Instruction - Instruction code 55 0x00, // P1 - Parameter 1 - Instruction parameter 1 56 0x00, // P2 - Parameter 2 - Instruction parameter 2 57 0x0F, // Lc field - Number of bytes present in the data field of the command 58 ]) 59 } // 00b0 0000 0f 60 61 private var READ_CAPABILITY_CONTAINER_RESPONSE: Data { 62 Data(fromUInt8Array: [ 63 0x00, 0x0f, // CCLEN length of the CC file 64 0x20, // Mapping Version 2.0 65 0x00, 0x3B, // MLe maximum 66 0x00, 0x34, // MLc maximum 67 0x04, // T field of the NDEF File Control TLV 68 0x06, // L field of the NDEF File Control TLV 69 0xE1, 0x04, // File Identifier of NDEF file 70 0x00, 0xFE, // Maximum NDEF file size of 65534 bytes // 254 bytes 71 0x00, // Read access without any security 72 0xFF, // Write access without any security 73 0x90, 0x00, // A_OKAY 74 ]) 75 } //000f 20 003b 0034 04 06 e104 00ff 00ff9000 76 77 private var NDEF_SELECT_OK: Data { 78 Data(fromUInt8Array: [ 79 0x00, // CLA - Class - Class of instruction 80 0xA4, // Instruction byte (INS) for Select command 81 0x00, // Parameter byte (P1), select by identifier 82 0x0C, // Parameter byte (P2), select by identifier 83 0x02, // Lc field - Number of bytes present in the data field of the command 84 0xE1, 85 0x04, // file identifier of the NDEF file retrieved from the CC file 86 ]) 87 } // 00a4 000c 02 e104 88 89 private var NDEF_READ_BINARY_NLEN: Data { 90 Data(fromUInt8Array: [ 91 0x00, // Class byte (CLA) 92 0xB0, // Instruction byte (INS) for ReadBinary command 93 0x00, 94 0x00, // Parameter byte (P1, P2), offset inside the CC file 95 0x02, // Le field 96 ]) 97 } // 00b0 0000 02 98 99 private var NDEF_READ_BINARY: Data { 100 Data(fromUInt8Array: [ 101 0x00, // Class byte (CLA) 102 0xB0, // Instruction byte (INS) for ReadBinary command 103 ]) 104 } // 00b0 105 // private let NDEF_READ_BINARY_DATA = Data(fromUInt8Array: NDEF_READ_BINARY) 106 107 private var NDEF_RECORD_HEADER_SHORT: Data { 108 Data(fromUInt8Array: [ 109 0xD1, // 1101 + 0001 = MessageBegin, MessageEnd, ShortRecord + Format "Well-Known" (but no ID Length) 110 0x01, // TypeLength 111 // 0x00, // Payload Length 1 byte only since ShortRecord is true 112 // no ID Length byte since the IDlength flag is false 113 // 0x55, // Payload Type = “U” for URI, 1 byte as specified in the TypeLength field 114 // since we have no ID length, there is no Payload ID 115 ]) 116 } // d101 117 118 private var A_OKAY_ARRAY: [UInt8] { 119 [ 120 0x90, // SW1 Status byte 1 - Command processing status 121 0x00, // SW2 Status byte 2 - Command processing qualifier 122 ] 123 } // 9000 = OK 124 private var A_OKAY: Data { 125 Data(fromUInt8Array: A_OKAY_ARRAY) 126 } 127 128 private var A_ERROR: Data { 129 Data(fromUInt8Array: [ 130 0x6A, // SW1 Status byte 1 - Command processing status 131 0x82, // SW2 Status byte 2 - Command processing qualifier 132 ]) 133 } // 6A82 = File not found 134 135 private var APPLICATION_ERROR: Data { 136 Data(fromUInt8Array: [ 137 0x6A, // SW1 Status byte 1 - Command processing status 138 0x88, // SW2 Status byte 2 - Command processing qualifier 139 ]) 140 } // 6A88 = no application ID 141 142 private var GET_VERSION: Data { 143 Data(fromUInt8Array: [ 144 0x90, // CLA - Class - Class of instruction 145 0x60, // INS - Instruction - Instruction code 146 0x00, // P1 - Parameter 1 - Instruction parameter 1 147 0x00, // P2 - Parameter 2 - Instruction parameter 2 148 0x00, // Lc field - Number of bytes present in the data field of the command 149 ]) 150 } // 9060 0000 00 151 152 private var GET_VERSION_RESPONSE: Data { 153 Data(fromUInt8Array: [ 154 0x00, // fixed header 155 0x04, // vendor ID (04 = NXP Semiconductors) 156 0x04, // product type (04 = NTAG) 157 0x02, // product subtype (02 = NTAG215) 158 0x01, // major product version 159 0x00, // minor product version 160 0x11, // storage size 161 0x03, // protocol type: ISO/IEC 14443-3 compliant 162 0x90, 0x00, // A_OKAY 163 ]) 164 } // 165 166 private var MORE_INFO: Data { 167 Data(fromUInt8Array: [ 168 0x90, // CLA - Class - Class of instruction 169 0xAF, // INS - Instruction - Instruction code 170 0x00, // P1 - Parameter 1 - Instruction parameter 1 171 0x00, // P2 - Parameter 2 - Instruction parameter 2 172 0x00, // Lc field - Number of bytes present in the data field of the command 173 ]) 174 } // 90af 0000 00 175 176 /// process the received data and return a response as Data. 177 func processAPDU(_ commandApdu: Data) -> Data { 178 /// The following flow is based on Appendix E "Example of Mapping Version 2.0 Command Flow" 179 /// in the NFC Forum specification 180 logger.info("incoming commandApdu: \(commandApdu.hexEncodedString())") 181 182 /// First command: NDEF Tag Application select 183 if APDU_SELECT == commandApdu { 184 logger.info("APDU_SELECT triggered. Our Response: A_OKAY") 185 return A_OKAY 186 } // 00a4040007d276000085010100 187 188 /// Second command: Capability Container select 189 if CAPABILITY_CONTAINER_OK == commandApdu { 190 logger.info("CAPABILITY_CONTAINER_OK triggered. Our Response: A_OKAY") 191 return A_OKAY 192 } // 00a4000c02e103 193 194 /// Third command: ReadBinary data from CC file 195 if READ_CAPABILITY_CONTAINER == commandApdu && !readCapabilityContainerCheck { 196 logger.info("READ_CAPABILITY_CONTAINER triggered. Our Response: READ_CAPABILITY_CONTAINER_RESPONSE") 197 readCapabilityContainerCheck = true 198 return READ_CAPABILITY_CONTAINER_RESPONSE 199 } // 00b000000f 200 201 /// Fourth command: NDEF Select command 202 if NDEF_SELECT_OK == commandApdu { 203 logger.info("NDEF_SELECT_OK triggered. Our Response: A_OKAY") 204 return A_OKAY 205 } // 00a4000c02e104 206 207 let uriCount = talerUri.count 208 let ndefLen = UInt16(uriCount + 5) // NDEF_RECORD_HEADER is 4 bytes 209 /// Fifth Command: Read the Length of the NDEF File // 00b0 00 00 02 210 if NDEF_READ_BINARY_NLEN == commandApdu { 211 logger.info("NDEF_READ_BINARY_NLEN triggered. Our Response: length \(ndefLen) + A_OKAY") 212 readCapabilityContainerCheck = false 213 214 let ndefLenHi = UInt8(ndefLen >> 8) 215 let ndefLenLo = UInt8(ndefLen & 0xff) 216 let ndefLenLoData = withUnsafeBytes(of: ndefLenLo) { Data($0) } 217 var lenData = withUnsafeBytes(of: ndefLenHi) { Data($0) } 218 lenData.append(ndefLenLoData) 219 lenData.append(A_OKAY_ARRAY, count: 2) 220 return lenData 221 } // 00b0000002 222 223 /// Sixth (and seventh) Command: Read the NDEF File // 00b0 00 02 3b, and 00b0 00 3d 27 224 if NDEF_READ_BINARY == commandApdu[0..<2] { 225 let count = commandApdu.count 226 if count > 4 { 227 var arrayApdu = Array<UInt8>(repeating: 0, count: count) 228 _ = arrayApdu.withUnsafeMutableBytes { commandApdu.copyBytes(to: $0) } 229 230 let wantedLength = Int(arrayApdu[4]) 231 let offset = Int(arrayApdu[3]) + (Int(arrayApdu[2]) << 8) 232 logger.info("NDEF_READ_BINARY offset \(offset), length \(wantedLength) triggered. Our Response: data + A_OKAY") 233 234 var data = NDEF_RECORD_HEADER_SHORT // 2 bytes 235 let uriCount8 = UInt8((uriCount + 1) & 0xff) 236 let lenData = withUnsafeBytes(of: uriCount8) { Data($0) } 237 data.append(lenData) // 1 byte 238 data.append("U".data(using: .utf8)!) // 1 byte 239 let zero: UInt8 = 0 240 let zeroData = withUnsafeBytes(of: zero) { Data($0) } 241 data.append(zeroData) // 1 byte 242 data.append(talerUri.data(using: .utf8)!) 243 244 var result = Data(fromData: data, 245 offset: Int(offset) - 2, // subtract 2 length bytes 246 size: wantedLength) 247 result.append(A_OKAY) // 2 bytes 248 249 readCapabilityContainerCheck = false 250 return result 251 } 252 } 253 254 // Reject GET_VERSION and MORE_INFO 255 if GET_VERSION == commandApdu { 256 logger.info("❗️GET_VERSION triggered. Our Response: GET_VERSION_RESPONSE❗️") 257 return GET_VERSION_RESPONSE 258 } 259 if MORE_INFO == commandApdu { 260 logger.info("❗️MORE_INFO triggered. Our Response: A_OKAY❗️") 261 return A_OKAY 262 } 263 264 // 265 // We're doing something outside our scope 266 // 267 logger.error("❗️processAPDU() | not yet implemented!") 268 return A_ERROR 269 } // processAPDU() 270 271 }