taler-ios

iOS apps for GNU Taler (wallet)
Log | Files | Refs | README | LICENSE

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 }