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