diff --git a/Sources/eudiWalletOidcIos/Service/CredentialRevocationUtil.swift b/Sources/eudiWalletOidcIos/Service/CredentialRevocationUtil.swift new file mode 100644 index 0000000..cdc5f37 --- /dev/null +++ b/Sources/eudiWalletOidcIos/Service/CredentialRevocationUtil.swift @@ -0,0 +1,83 @@ +// +// File.swift +// +// +// Created by iGrant on 02/12/24. +// + +import Foundation +import Compression +import zlib + +public class CredentialRevocationUtil { + + public init() {} + + public func getRevokedCredentials(credentialList: [String]) async -> [String]{ + var revokedCredentials: [String] = [] + var statusList: [String] = [] + for item in credentialList { + if let statusUri = getStatusDetailsFromStatusList(jwt: item).0 { + statusList.append(statusUri) + } + } + let uiniqueStatusListArray = Array(Set(statusList)) + let statusModelList = await fetchStatusModel(statusList: uiniqueStatusListArray) + for item in credentialList { + let statusIndex = getStatusDetailsFromStatusList(jwt: item).1 ?? 0 + let statusUri = getStatusDetailsFromStatusList(jwt: item).0 + for data in statusModelList { + if statusUri == data.satausUri { + let statusList = data.bitsArray?[statusIndex] + if statusList == 1 { + revokedCredentials.append(item) + } + } + } + } + return revokedCredentials + } + + func getStatusDetailsFromStatusList(jwt: String?) -> (String?, Int?) { + guard let split = jwt?.split(separator: "."), split.count > 1 else { return (nil, nil)} + let jsonString = "\(split[1])".decodeBase64() ?? "" + let dict = UIApplicationUtils.shared.convertStringToDictionary(text: jsonString) + guard let statusData = dict?["status"] as? [String: Any] else { return (nil, nil) } + if let statusListDict = statusData["status_list"] as? [String: Any], let statusIndex = statusListDict["idx"] as? Int , let statusUri = statusListDict["uri"] as? String { + return (statusUri, statusIndex) + } + return (nil, nil) + } + + func fetchStatusModel(statusList: [String]) async -> [StatusListModel]{ + var statusModel: [StatusListModel] = [] + for uri in statusList { + var request = URLRequest(url: URL(string: uri)!) + request.httpMethod = "GET" + request.setValue("application/statuslist+jwt", forHTTPHeaderField: "Accept") + do { + let (data, _) = try await URLSession.shared.data(for: request) + let stringData = String.init(data: data, encoding: .utf8) + let split = stringData?.split(separator: ".") + let jsonString = "\(split?[1] ?? "")".decodeBase64() ?? "" + let statusDict = UIApplicationUtils.shared.convertStringToDictionary(text: jsonString) + let statusListDict = statusDict?["status_list"] as? [String: Any] + let bits = statusListDict?["bits"] as? Int + let lst = statusListDict?["lst"] as? String ?? "" + let statusList = StatusList.fromEncoded(lst, bits: bits ?? 0) + let bitsArray = statusList.decodedValues() + let statusValues = StatusListModel(satausUri: uri, bitsArray: bitsArray) + statusModel.append(statusValues) + } catch { + print("error") + } + } + return statusModel + } + +} + +struct StatusListModel { + let satausUri: String? + let bitsArray: [Int]? +} diff --git a/Sources/eudiWalletOidcIos/Service/StatusList.swift b/Sources/eudiWalletOidcIos/Service/StatusList.swift new file mode 100644 index 0000000..342a5f5 --- /dev/null +++ b/Sources/eudiWalletOidcIos/Service/StatusList.swift @@ -0,0 +1,87 @@ +import Foundation +import zlib + +class StatusList { + private var list: [UInt8] + private let bits: Int + private let divisor: Int + var size: Int + + init(size: Int, bits: Int) { + self.size = size + self.bits = bits + self.divisor = 8 / bits + self.list = Array(repeating: 0, count: size / divisor) + } + + static func fromEncoded(_ encoded: String, bits: Int = 1) -> StatusList { + let newInstance = StatusList(size: 0, bits: bits) + newInstance.decode(encoded) + return newInstance + } + + func decode(_ input: String) { + let paddedInput = input.padding(toLength: ((input.count + 3) / 4) * 4, withPad: "=", startingAt: 0) + let base64 = paddedInput + .replacingOccurrences(of: "-", with: "+") + .replacingOccurrences(of: "_", with: "/") + guard let decodedData = Data(base64Encoded: base64) else { + fatalError("Invalid Base64 string.") + } + guard let decompressedData = decompress(data: decodedData) else { + fatalError("Decompression failed.") + } + self.list = Array(decompressedData) // Update the list with decompressed data + self.size = self.list.count * divisor // Update the size based on decompressed data + print("Decoded list size: \(self.size)") // Debug: Check the size + } + + func get(_ pos: Int) -> Int { + let rest = pos % divisor + let floored = pos / divisor + let shift = rest * bits + let mask = ((1 << bits) - 1) << shift + return Int((list[floored] & UInt8(mask)) >> shift) + } + + func decodedValues() -> [Int] { + var values = [Int]() + for pos in 0.. Data? { + var decompressed = Data() + var stream = z_stream() + stream.next_in = UnsafeMutablePointer(mutating: (data as NSData).bytes.bindMemory(to: UInt8.self, capacity: data.count)) + stream.avail_in = uint(data.count) + + guard inflateInit_(&stream, ZLIB_VERSION, Int32(MemoryLayout.size)) == Z_OK else { return nil } + + let bufferSize = 4096 + let buffer = UnsafeMutablePointer.allocate(capacity: bufferSize) + defer { buffer.deallocate() } + + repeat { + stream.next_out = buffer + stream.avail_out = uint(bufferSize) + + let status = inflate(&stream, Z_NO_FLUSH) + if status == Z_STREAM_END { + decompressed.append(buffer, count: bufferSize - Int(stream.avail_out)) + break + } else if status != Z_OK { + inflateEnd(&stream) + return nil + } + + decompressed.append(buffer, count: bufferSize - Int(stream.avail_out)) + } while stream.avail_out == 0 + + inflateEnd(&stream) + return decompressed + } + +}