From 8b4eaabb3d38a2338176256acf533c2f5b8d9f7a Mon Sep 17 00:00:00 2001 From: "Josep Milan K.A" Date: Thu, 10 Oct 2024 13:40:06 +0530 Subject: [PATCH] Fix: Migration to EWC RFC 001 - V2 --- ...risationServerWellKnownConfiguration.swift | 12 +- .../Model/CredentialOffer.swift | 42 +- .../Model/CredentialOfferResponse.swift | 7 + .../Model/CredentialOfferV2.swift | 61 ++ .../Model/CredentialResponse.swift | 36 +- .../Model/CredentialResponseV1.swift | 23 + .../Model/CredentialResponseV2.swift | 23 + .../Model/IssuerWellKnownConfiguration.swift | 44 ++ ...IssuerWellKnownConfigurationResponse.swift | 19 + ...suerWellKnownConfigurationResponseV2.swift | 57 ++ .../Service/DiscoveryService.swift | 21 +- .../Service/IssueService.swift | 623 +++++++++++------- .../Service/IssueServiceProtocol.swift | 46 +- .../Service/VerificationService.swift | 253 ++++--- 14 files changed, 847 insertions(+), 420 deletions(-) create mode 100644 Sources/eudiWalletOidcIos/Model/CredentialOfferV2.swift create mode 100644 Sources/eudiWalletOidcIos/Model/CredentialResponseV1.swift create mode 100644 Sources/eudiWalletOidcIos/Model/CredentialResponseV2.swift create mode 100644 Sources/eudiWalletOidcIos/Model/IssuerWellKnownConfigurationResponseV2.swift diff --git a/Sources/eudiWalletOidcIos/Model/AuthorisationServerWellKnownConfiguration.swift b/Sources/eudiWalletOidcIos/Model/AuthorisationServerWellKnownConfiguration.swift index 0b848cb..f0ced3b 100644 --- a/Sources/eudiWalletOidcIos/Model/AuthorisationServerWellKnownConfiguration.swift +++ b/Sources/eudiWalletOidcIos/Model/AuthorisationServerWellKnownConfiguration.swift @@ -4,9 +4,7 @@ // // Created by Mumthasir mohammed on 11/03/24. // - import Foundation - // MARK: - AuthorisationServerWellKnownConfiguration public struct AuthorisationServerWellKnownConfiguration: Codable { public var redirectUris: [String]? @@ -18,6 +16,8 @@ public struct AuthorisationServerWellKnownConfiguration: Codable { public var requestAuthenticationMethodsSupported: RequestAuthenticationMethodsSupported? public var vpFormatsSupported: VpFormatsSupported? public var subjectSyntaxTypesSupported, subjectSyntaxTypesDiscriminations, subjectTrustFrameworksSupported, idTokenTypesSupported: [String]? + public var requirePushedAuthorizationRequests: Bool? + public var pushedAuthorizationRequestEndpoint: String? public var error: EUDIError? enum CodingKeys: String, CodingKey { @@ -42,32 +42,28 @@ public struct AuthorisationServerWellKnownConfiguration: Codable { case subjectSyntaxTypesDiscriminations = "subject_syntax_types_discriminations" case subjectTrustFrameworksSupported = "subject_trust_frameworks_supported" case idTokenTypesSupported = "id_token_types_supported" + case requirePushedAuthorizationRequests = "require_pushed_authorization_requests" + case pushedAuthorizationRequestEndpoint = "pushed_authorization_request_endpoint" } } - // MARK: - RequestAuthenticationMethodsSupported public struct RequestAuthenticationMethodsSupported: Codable { public var authorizationEndpoint: [String]? - enum CodingKeys: String, CodingKey { case authorizationEndpoint = "authorization_endpoint" } } - // MARK: - VpFormatsSupported public struct VpFormatsSupported: Codable { public var jwtVp, jwtVc: JwtV? - enum CodingKeys: String, CodingKey { case jwtVp = "jwt_vp" case jwtVc = "jwt_vc" } } - // MARK: - JwtV public struct JwtV: Codable { var algValuesSupported: [String]? - enum CodingKeys: String, CodingKey { case algValuesSupported = "alg_values_supported" } diff --git a/Sources/eudiWalletOidcIos/Model/CredentialOffer.swift b/Sources/eudiWalletOidcIos/Model/CredentialOffer.swift index ea077c7..6f7463f 100644 --- a/Sources/eudiWalletOidcIos/Model/CredentialOffer.swift +++ b/Sources/eudiWalletOidcIos/Model/CredentialOffer.swift @@ -4,21 +4,19 @@ // // Created by Mumthasir mohammed on 07/03/24. // - import Foundation - // MARK: - CredentialOffer model public struct CredentialOffer { public var credentialIssuer: String? public var credentials: [Credential]? public var grants: Grants? public var error: EUDIError? + public var version: String? public init(from: CredentialOfferResponse) { credentialIssuer = from.credentialIssuer if let credentialList = from.credentials, credentialList.count > 0{ - if let strCredentialList = credentialList as? [String]{ credentials = [Credential(fromTypes: strCredentialList)] } else if let objCredentialList = credentialList as? [CredentialDataResponse]{ @@ -29,7 +27,28 @@ public struct CredentialOffer { } } grants = from.grants == nil ? nil : Grants(from: from.grants!) + grants?.urnIETFParamsOauthGrantTypePreAuthorizedCode?.txCode = from.grants?.urnIETFParamsOauthGrantTypePreAuthorizedCode?.userPinRequired ?? false ? TransactionCode(length: 4, inputMode: "numeric", description: "") : nil + grants?.authorizationCode?.authorizationServer = nil error = from.error == nil ? nil : EUDIError(from: from.error!) + version = "v1" + } + + public init(from: CredentialOfferV2) { + credentialIssuer = from.credentialIssuer + + if let credentialList = from.credentialConfigurationIds, credentialList.count > 0{ + if let strCredentialList = credentialList as? [String]{ + credentials = [Credential(fromTypes: strCredentialList)] + } else if let objCredentialList = credentialList as? [CredentialDataResponse]{ + credentials = credentialList.map({ + let obj = $0 as! CredentialDataResponse + return Credential(from: obj) + }) + } + } + grants = from.grants == nil ? nil : Grants(from: from.grants!) + error = from.error == nil ? nil : EUDIError(from: from.error!) + version = "v2" } public init(fromError: EUDIError) { @@ -37,7 +56,6 @@ public struct CredentialOffer { } } - // MARK: - Credential public struct Credential { public let format: String? @@ -59,7 +77,6 @@ public struct Credential { credentialDefinition = nil } } - // MARK: - CredentialDefinition public struct CredentialDefinition { public var context: [String]? @@ -70,7 +87,6 @@ public struct CredentialDefinition { types = from.types } } - // MARK: - TrustFramework public struct TrustFramework { public let name, type, uri: String? @@ -81,33 +97,35 @@ public struct TrustFramework { uri = from.uri } } - public struct Grants { - public let authorizationCode: AuthorizationCode? - public let urnIETFParamsOauthGrantTypePreAuthorizedCode: UrnIETFParamsOauthGrantTypePreAuthorizedCode? + public var authorizationCode: AuthorizationCode? + public var urnIETFParamsOauthGrantTypePreAuthorizedCode: UrnIETFParamsOauthGrantTypePreAuthorizedCode? init(from: GrantsResponse) { authorizationCode = from.authorizationCode == nil ? nil : AuthorizationCode(from: from.authorizationCode!) urnIETFParamsOauthGrantTypePreAuthorizedCode = from.urnIETFParamsOauthGrantTypePreAuthorizedCode == nil ? nil : UrnIETFParamsOauthGrantTypePreAuthorizedCode(from: from.urnIETFParamsOauthGrantTypePreAuthorizedCode!) } } - // MARK: - AuthorizationCode public struct AuthorizationCode { public let issuerState: String? - + public var authorizationServer: String? init(from: AuthorizationCodeResponse) { issuerState = from.issuerState + authorizationServer = from.authorizationServer } } - // MARK: - UrnIETFParamsOauthGrantTypePreAuthorizedCode public struct UrnIETFParamsOauthGrantTypePreAuthorizedCode { public let preAuthorizedCode: String? public let userPinRequired: Bool? + public var txCode: TransactionCode? + public let authorizationServer: String? init(from: UrnIETFParamsOauthGrantTypePreAuthorizedCodeResponse) { preAuthorizedCode = from.preAuthorizedCode userPinRequired = from.userPinRequired + txCode = from.txCode + authorizationServer = from.authorizationServer } } diff --git a/Sources/eudiWalletOidcIos/Model/CredentialOfferResponse.swift b/Sources/eudiWalletOidcIos/Model/CredentialOfferResponse.swift index cb517e0..cacf59d 100644 --- a/Sources/eudiWalletOidcIos/Model/CredentialOfferResponse.swift +++ b/Sources/eudiWalletOidcIos/Model/CredentialOfferResponse.swift @@ -90,9 +90,11 @@ struct GrantsResponse: Codable { // MARK: - AuthorizationCode struct AuthorizationCodeResponse: Codable { let issuerState: String? + let authorizationServer: String? enum CodingKeys: String, CodingKey { case issuerState = "issuer_state" + case authorizationServer = "authorization_server" } } @@ -100,9 +102,14 @@ struct AuthorizationCodeResponse: Codable { struct UrnIETFParamsOauthGrantTypePreAuthorizedCodeResponse: Codable { let preAuthorizedCode: String? let userPinRequired: Bool? + let txCode: TransactionCode? + let authorizationServer: String? enum CodingKeys: String, CodingKey { case preAuthorizedCode = "pre-authorized_code" case userPinRequired = "user_pin_required" + case txCode = "tx_code" + case authorizationServer = "authorization_server" } } + diff --git a/Sources/eudiWalletOidcIos/Model/CredentialOfferV2.swift b/Sources/eudiWalletOidcIos/Model/CredentialOfferV2.swift new file mode 100644 index 0000000..3f0b890 --- /dev/null +++ b/Sources/eudiWalletOidcIos/Model/CredentialOfferV2.swift @@ -0,0 +1,61 @@ +// +// File.swift +// +// +// Created by oem on 10/10/24. +// + +import Foundation + +public struct CredentialOfferV2: Codable { + var credentialIssuer: String? + var credentialConfigurationIds: [AnyObject]? + var grants: GrantsResponse? + var error: ErrorResponse? + + enum CodingKeys: String, CodingKey { + case credentialIssuer = "credential_issuer" + case credentialConfigurationIds = "credential_configuration_ids" + case grants, error + } + + + public func encode(to encoder: Encoder) throws { + + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.credentialIssuer = try container.decodeIfPresent(String.self, forKey: .credentialIssuer) + if let stringArray = try? container.decode([String].self, forKey: .credentialConfigurationIds) { + credentialConfigurationIds = stringArray as? [AnyObject] + } else if let credentialArray = try? container.decode([CredentialDataResponse].self, forKey: .credentialConfigurationIds) { + credentialConfigurationIds = credentialArray as? [AnyObject] + } else { + credentialConfigurationIds = nil + } + self.grants = try container.decodeIfPresent(GrantsResponse.self, forKey: .grants) + self.error = try container.decodeIfPresent(ErrorResponse.self, forKey: .error) + } + +} + +public struct TransactionCode: Codable { + public let length: Int? + public let inputMode: String? + public let description: String? + + enum CodingKeys: String, CodingKey { + case length + case inputMode = "input_mode" + case description + } + + init(length: Int? = 0, inputMode: String = "", description: String = "") { + self.length = length + self.inputMode = inputMode + self.description = description + } +} + + diff --git a/Sources/eudiWalletOidcIos/Model/CredentialResponse.swift b/Sources/eudiWalletOidcIos/Model/CredentialResponse.swift index ec5bdec..b4a86ce 100644 --- a/Sources/eudiWalletOidcIos/Model/CredentialResponse.swift +++ b/Sources/eudiWalletOidcIos/Model/CredentialResponse.swift @@ -4,11 +4,9 @@ // // Created by Mumthasir mohammed on 11/03/24. // - import Foundation - // MARK: - CredentialResponse -public struct CredentialResponse: Codable { +public struct CredentialResponse { public var format, credential, acceptanceToken: String? public var isDeferred, isPinRequired: Bool? public var issuerConfig: IssuerWellKnownConfiguration? @@ -16,9 +14,33 @@ public struct CredentialResponse: Codable { public var credentialOffer: CredentialOffer? public var error: EUDIError? - enum CodingKeys: String, CodingKey { - case acceptanceToken = "acceptance_token" - case format = "format" - case credential = "credential" + + public init(from: CredentialResponseV1) { + format = from.format + credential = from.credential + acceptanceToken = from.acceptanceToken + isDeferred = from.isDeferred + isPinRequired = from.isPinRequired + issuerConfig = from.issuerConfig + authorizationConfig = from.authorizationConfig + credentialOffer = from.credentialOffer + error = from.error + } + + + public init(from: CredentialResponseV2) { + format = from.format + credential = from.credential + acceptanceToken = from.acceptanceToken + isDeferred = from.isDeferred + isPinRequired = from.isPinRequired + issuerConfig = from.issuerConfig + authorizationConfig = from.authorizationConfig + credentialOffer = from.credentialOffer + error = from.error + } + + public init(fromError: EUDIError) { + error = fromError } } diff --git a/Sources/eudiWalletOidcIos/Model/CredentialResponseV1.swift b/Sources/eudiWalletOidcIos/Model/CredentialResponseV1.swift new file mode 100644 index 0000000..7921377 --- /dev/null +++ b/Sources/eudiWalletOidcIos/Model/CredentialResponseV1.swift @@ -0,0 +1,23 @@ +// +// File.swift +// +// +// Created by oem on 10/10/24. +// + +import Foundation + +public struct CredentialResponseV1: Codable { + public var format, credential, acceptanceToken: String? + public var isDeferred, isPinRequired: Bool? + public var issuerConfig: IssuerWellKnownConfiguration? + public var authorizationConfig: AuthorisationServerWellKnownConfiguration? + public var credentialOffer: CredentialOffer? + public var error: EUDIError? + + enum CodingKeys: String, CodingKey { + case acceptanceToken = "acceptance_token" + case format = "format" + case credential = "credential" + } +} diff --git a/Sources/eudiWalletOidcIos/Model/CredentialResponseV2.swift b/Sources/eudiWalletOidcIos/Model/CredentialResponseV2.swift new file mode 100644 index 0000000..02d4207 --- /dev/null +++ b/Sources/eudiWalletOidcIos/Model/CredentialResponseV2.swift @@ -0,0 +1,23 @@ +// +// File.swift +// +// +// Created by oem on 10/10/24. +// + +import Foundation + +public struct CredentialResponseV2: Codable { + public var format, credential, acceptanceToken: String? + public var isDeferred, isPinRequired: Bool? + public var issuerConfig: IssuerWellKnownConfiguration? + public var authorizationConfig: AuthorisationServerWellKnownConfiguration? + public var credentialOffer: CredentialOffer? + public var error: EUDIError? + + enum CodingKeys: String, CodingKey { + case acceptanceToken = "transaction_id" + case format = "format" + case credential = "credential" + } +} diff --git a/Sources/eudiWalletOidcIos/Model/IssuerWellKnownConfiguration.swift b/Sources/eudiWalletOidcIos/Model/IssuerWellKnownConfiguration.swift index 51e4370..7654b3c 100644 --- a/Sources/eudiWalletOidcIos/Model/IssuerWellKnownConfiguration.swift +++ b/Sources/eudiWalletOidcIos/Model/IssuerWellKnownConfiguration.swift @@ -61,6 +61,13 @@ public struct CredentialSupportedObject { DataSharing(from: $0) }) + } + init(from: [String:DataSharingResponseV2]) { + + dataSharing = from.mapValues({ + DataSharing(from: $0) + }) + } init(from: [DataSharingOldFormatResponse]) { @@ -106,6 +113,7 @@ public struct DataSharing { public var trustFramework: TrustFramework? public var credentialDefinition: IssuerCredentialDefinition? public var docType: String? + public var vct: String? init(from: DataSharingResponse) { format = from.format @@ -118,6 +126,18 @@ public struct DataSharing { credentialDefinition = from.credentialDefinition == nil ? nil : IssuerCredentialDefinition(from: from.credentialDefinition!) docType = from.docType } + init(from: DataSharingResponseV2) { + format = from.format + scope = from.scope + cryptographicBindingMethodsSupported = from.cryptographicBindingMethodsSupported + cryptographicSuitesSupported = from.cryptographicSuitesSupported + if let dataSharingDisplayList = from.display, dataSharingDisplayList.count > 0{ + display = dataSharingDisplayList.map({ Display(from: $0) }) + } + credentialDefinition = from.credentialDefinition == nil ? nil : IssuerCredentialDefinition(from: from.credentialDefinition!) + vct = from.vct + docType = from.docType + } init(from: DataSharingOldFormatResponse) { format = from.format @@ -185,6 +205,30 @@ public struct IssuerWellKnownConfiguration { error = nil } +public init(from: IssuerWellKnownConfigurationResponseV2) { + credentialIssuer = from.credentialIssuer + authorizationServer = from.authorizationServer ?? from.authorizationServers?[0] ?? "" + credentialEndpoint = from.credentialEndpoint + deferredCredentialEndpoint = from.deferredCredentialEndpoint + + if let displayList = from.display as? [DisplayResponse] + { + display = displayList.map({ Display(from: $0) }) + } else{ + display = nil + } + + if let credentialsSupportList = from.credentialsSupported as? [[String:DataSharingResponseV2]], let firstObj = credentialsSupportList.first{ + credentialsSupported = CredentialSupportedObject(from: firstObj) + } else if let credentialsSupportListOldFormat = from.credentialsSupported as? [DataSharingOldFormatResponse]{ + credentialsSupported = CredentialSupportedObject(from: credentialsSupportListOldFormat) + } else{ + credentialsSupported = nil + } + + error = nil + + } public init(mCredentialIssuer: String?, mAuthorizationServer: String?, diff --git a/Sources/eudiWalletOidcIos/Model/IssuerWellKnownConfigurationResponse.swift b/Sources/eudiWalletOidcIos/Model/IssuerWellKnownConfigurationResponse.swift index 30db294..f8f33c6 100644 --- a/Sources/eudiWalletOidcIos/Model/IssuerWellKnownConfigurationResponse.swift +++ b/Sources/eudiWalletOidcIos/Model/IssuerWellKnownConfigurationResponse.swift @@ -130,6 +130,25 @@ struct DataSharingOldFormatResponse: Codable { } } + +struct DataSharingResponseV2: Codable { + var format, scope: String? + var cryptographicBindingMethodsSupported, cryptographicSuitesSupported: [String]? + var display: [DisplayResponse]? + var credentialDefinition: IssuerCredentialDefinitionResponse? + var vct: String? + var docType: String? + + enum CodingKeys: String, CodingKey { + case format, scope, vct + case cryptographicBindingMethodsSupported = "cryptographic_binding_methods_supported" + case cryptographicSuitesSupported = "credential_signing_alg_values_supported" + case credentialDefinition = "credential_definition" + case display + case docType = "doctype" + } +} + // MARK: - DataSharingDisplay struct DataSharingDisplayResponse: Codable { var name, locale, backgroundColor, textColor: String? diff --git a/Sources/eudiWalletOidcIos/Model/IssuerWellKnownConfigurationResponseV2.swift b/Sources/eudiWalletOidcIos/Model/IssuerWellKnownConfigurationResponseV2.swift new file mode 100644 index 0000000..7acd176 --- /dev/null +++ b/Sources/eudiWalletOidcIos/Model/IssuerWellKnownConfigurationResponseV2.swift @@ -0,0 +1,57 @@ +// +// File.swift +// +// +// Created by oem on 10/10/24. +// + +import Foundation + +public struct IssuerWellKnownConfigurationResponseV2: Codable { + let credentialIssuer: String? + let authorizationServer: String? + let authorizationServers: [String]? + let credentialEndpoint: String? + let deferredCredentialEndpoint: String? + let display: [AnyObject]? + let credentialsSupported: [AnyObject]? + + enum CodingKeys: String, CodingKey { + case credentialIssuer = "credential_issuer" + case authorizationServer = "authorization_server" + case authorizationServers = "authorization_servers" + case credentialEndpoint = "credential_endpoint" + case deferredCredentialEndpoint = "deferred_credential_endpoint" + case display = "display" + case credentialsSupported = "credential_configurations_supported" + } + + public func encode(to encoder: Encoder) throws { + + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + credentialIssuer = try? container.decode(String.self, forKey: .credentialIssuer) + authorizationServer = try? container.decode(String.self, forKey: .authorizationServer) + authorizationServers = try? container.decode([String].self, forKey: .authorizationServers) + credentialEndpoint = try? container.decode(String.self, forKey: .credentialEndpoint) + deferredCredentialEndpoint = try? container.decode(String.self, forKey: .deferredCredentialEndpoint) + + if let singleCredentialSupported = try? container.decode([String:DataSharingResponseV2].self, forKey: .credentialsSupported) { + credentialsSupported = [singleCredentialSupported] as? [AnyObject] + } else if let credentialSupportedArray = try? container.decode([DataSharingOldFormatResponse].self, forKey: .credentialsSupported) { + credentialsSupported = credentialSupportedArray as? [AnyObject] + } else { + credentialsSupported = [] + } + + if let singleDisplay = try? container.decode(DisplayResponse.self, forKey: .display) { + display = [singleDisplay] as? [AnyObject] + } else if let displayArray = try? container.decode([DisplayResponse].self, forKey: .display) { + display = displayArray as? [AnyObject] + } else { + display = [] + } + } +} diff --git a/Sources/eudiWalletOidcIos/Service/DiscoveryService.swift b/Sources/eudiWalletOidcIos/Service/DiscoveryService.swift index 993af3c..613f5f5 100644 --- a/Sources/eudiWalletOidcIos/Service/DiscoveryService.swift +++ b/Sources/eudiWalletOidcIos/Service/DiscoveryService.swift @@ -4,10 +4,8 @@ // // Created by Mumthasir mohammed on 08/03/24. // - import Foundation import CryptoKit - public class DiscoveryService: DiscoveryServiceProtocol { public static var shared = DiscoveryService() @@ -32,8 +30,19 @@ public class DiscoveryService: DiscoveryServiceProtocol { let (data, _) = try await URLSession.shared.data(for: request) do { - let model = try jsonDecoder.decode(IssuerWellKnownConfigurationResponse.self, from: data) - return IssuerWellKnownConfiguration(from: model) + guard let jsonObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else { + return nil + + } + if jsonObject["credentials_supported"] != nil { + let model = try jsonDecoder.decode(IssuerWellKnownConfigurationResponse.self, from: data) + return IssuerWellKnownConfiguration(from: model) + } else if jsonObject["credential_configurations_supported"] != nil { + let model = try jsonDecoder.decode(IssuerWellKnownConfigurationResponseV2.self, from: data) + return IssuerWellKnownConfiguration(from: model) + } else { + return nil + } } catch { debugPrint("Get Issuer config failed: \(error)") let nsError = error as NSError @@ -61,6 +70,10 @@ public class DiscoveryService: DiscoveryServiceProtocol { let (data, _) = try await URLSession.shared.data(for: request) do { + guard let jsonObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else { + return nil + + } let model = try jsonDecoder.decode(AuthorisationServerWellKnownConfiguration.self, from: data) return model } catch { diff --git a/Sources/eudiWalletOidcIos/Service/IssueService.swift b/Sources/eudiWalletOidcIos/Service/IssueService.swift index f02354b..a7e2956 100644 --- a/Sources/eudiWalletOidcIos/Service/IssueService.swift +++ b/Sources/eudiWalletOidcIos/Service/IssueService.swift @@ -11,7 +11,7 @@ import CryptoKit import CryptoSwift public class IssueService: NSObject, IssueServiceProtocol { - + var session: URLSession? var keyHandler: SecureKeyProtocol! @@ -44,12 +44,36 @@ public class IssueService: NSObject, IssueServiceProtocol { let (data, _) = try await URLSession.shared.data(for: request) do { - let model = try? jsonDecoder.decode(CredentialOfferResponse.self, from: data) - if model?.credentialIssuer == nil { - let error = EUDIError(from: ErrorResponse(message:"Invalid DID", code: nil)) + guard let jsonObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else { + return nil + //throw EUDIError(from: ErrorResponse(message: "Invalid JSON format", code: nil)) + } + + // Check for specific keys to determine the model type + if jsonObject["credentials"] != nil { + // If the key 'credentialIssuer' is present, decode as CredentialOfferResponse + let model = try jsonDecoder.decode(CredentialOfferResponse.self, from: data) + + if model.credentialIssuer == nil { + let error = EUDIError(from: ErrorResponse(message: "Invalid DID", code: nil)) + return CredentialOffer(fromError: error) + } + return CredentialOffer(from: model) + + } else if jsonObject["credential_configuration_ids"] != nil { + // If the key 'issuer' is present, decode as CredentialOfferV2 + let modelV2 = try jsonDecoder.decode(CredentialOfferV2.self, from: data) + + if modelV2.credentialIssuer == nil { + let error = EUDIError(from: ErrorResponse(message: "Invalid DID", code: nil)) + return CredentialOffer(fromError: error) + } + return CredentialOffer(from: modelV2) + } else { + // If neither key is present, return an error + let error = EUDIError(from: ErrorResponse(message: "Invalid data format", code: nil)) return CredentialOffer(fromError: error) } - return (model == nil ? nil : CredentialOffer(from: model!)) } } else { guard let credentialOffer = credentialOfferUrl?.queryParameters?["credential_offer"] else { return nil } @@ -57,12 +81,26 @@ public class IssueService: NSObject, IssueServiceProtocol { if credentialOffer != "" { do { - let model = try? jsonDecoder.decode(CredentialOfferResponse.self, from: jsonData) - if model?.credentialIssuer == nil { - let error = EUDIError(from: ErrorResponse(message:"Invalid DID", code: nil)) + if let model = try? jsonDecoder.decode(CredentialOfferResponse.self, from: jsonData) { + if model.credentialIssuer == nil { + let error = EUDIError(from: ErrorResponse(message: "Invalid DID", code: nil)) + return CredentialOffer(fromError: error) + } + return CredentialOffer(from: model) + } + + else if let modelV2 = try? jsonDecoder.decode(CredentialOfferV2.self, from: jsonData) { + if modelV2.credentialIssuer == nil { + let error = EUDIError(from: ErrorResponse(message: "Invalid DID", code: nil)) + return CredentialOffer(fromError: error) + } + return CredentialOffer(from: modelV2) + } + + else { + let error = EUDIError(from: ErrorResponse(message: "Invalid data format", code: nil)) return CredentialOffer(fromError: error) } - return (model == nil ? nil : CredentialOffer(from: model!)) } } else { return nil @@ -70,21 +108,21 @@ public class IssueService: NSObject, IssueServiceProtocol { } } - private func buildAuthorizationRequest(credentialOffer: CredentialOffer?, docType: String, format: String) -> String { + private func buildAuthorizationRequestV1(credentialOffer: CredentialOffer?, docType: String, format: String) -> String { var authorizationDetails = if format == "mso_mdoc" { "[" + (([ - "format": format, + "format": format, "doctype": docType, "locations": [credentialOffer?.credentialIssuer ?? ""] ] as [String : Any]).toString() ?? "") + "]" } else if credentialOffer?.credentials?[0].trustFramework == nil { - "[" + (([ + "[" + (([ "type": "openid_credential", "format": "jwt_vc_json", "credential_definition": ["type":credentialOffer?.credentials?[0].types ?? []], "locations": [credentialOffer?.credentialIssuer ?? ""] ] as [String : Any]).toString() ?? "") + "]" - } else { + } else { "[" + (([ "type": "openid_credential", "format": "jwt_vc", @@ -96,6 +134,48 @@ public class IssueService: NSObject, IssueServiceProtocol { return authorizationDetails } + private func buildAuthorizationRequestV2(credentialOffer: CredentialOffer?, docType: String, format: String, issuerConfig: IssuerWellKnownConfiguration?) -> String { + let credentialConfigID = credentialOffer?.credentials?.first?.types?.first ?? nil + var authorizationDetails = if format == "mso_mdoc" { + "[" + (([ + "format": format, + "doctype": docType, + "credential_configuration_id": credentialConfigID, + "locations": [credentialOffer?.credentialIssuer ?? ""] + + ] as [String : Any]).toString() ?? "") + "]" + } else if format.contains("sd-jwt"){ + + + "[" + (([ + "type": "openid_credential", + "format": format, + "credential_configuration_id": credentialConfigID, + //pass issur config + "vct": getTypesFromIssuerConfig(issuerConfig: issuerConfig, type: credentialConfigID) + ] as [String : Any]).toString() ?? "") + "]" + } + else { + "[" + (([ + "type": "openid_credential", + "format": format, + "credential_definition": ["type": getTypesFromIssuerConfig(issuerConfig: issuerConfig, type: credentialConfigID)] + ] as [String : Any]).toString() ?? "") + "]" + } + + return authorizationDetails + } + + + private func buildAuthorizationRequest(credentialOffer: CredentialOffer?, docType: String, format: String, issuerConfig: IssuerWellKnownConfiguration?) -> String { + if credentialOffer?.version == "v1" { + return buildAuthorizationRequestV1(credentialOffer: credentialOffer, docType: docType, format: format) + + } else { + return buildAuthorizationRequestV2(credentialOffer: credentialOffer, docType: docType, format: format, issuerConfig: issuerConfig) + } + } + // MARK: - To process the authorisation request, The authorisation request is to grant access to the credential endpoint. /// - Parameters: /// - did - DID created for the issuance @@ -108,7 +188,7 @@ public class IssueService: NSObject, IssueServiceProtocol { secureKey: SecureKeyData, credentialOffer: CredentialOffer, codeVerifier: String, - authServer: AuthorisationServerWellKnownConfiguration, credentialFormat: String, docType: String) async -> String? { + authServer: AuthorisationServerWellKnownConfiguration, credentialFormat: String, docType: String, issuerConfig: IssuerWellKnownConfiguration?) async -> String? { guard let authorizationEndpoint = authServer.authorizationEndpoint else { return nil } let redirectUri = "http://localhost:8080" @@ -118,41 +198,88 @@ public class IssueService: NSObject, IssueServiceProtocol { let scope = credentialFormat == "mso_mdoc" ? credentialFormat + "openid" : "openid" let state = UUID().uuidString let docType = credentialFormat == "mso_mdoc" ? docType : "" - let authorizationDetails = buildAuthorizationRequest(credentialOffer: credentialOffer, docType: docType, format: credentialFormat) + let authorizationDetails = buildAuthorizationRequest(credentialOffer: credentialOffer, docType: docType, format: credentialFormat, issuerConfig: issuerConfig) let nonce = UUID().uuidString let codeChallenge = CodeVerifierService.shared.generateCodeChallenge(codeVerifier: codeVerifier) let codeChallengeMethod = "S256" let clientMetadata = credentialFormat == "mso_mdoc" ? "" : ([ - "vp_formats_supported": [ - "jwt_vp": [ "alg": ["ES256"] ], - "jwt_vc": [ "alg": ["ES256"] ] - ], - "response_types_supported": ["vp_token", "id_token"], - "authorization_endpoint": "\(redirectUri)" + "vp_formats_supported": [ + "jwt_vp": [ "alg": ["ES256"] ], + "jwt_vc": [ "alg": ["ES256"] ] + ], + "response_types_supported": ["vp_token", "id_token"], + "authorization_endpoint": "\(redirectUri)" ] as [String : Any]).toString() // Validate required parameters if responseType == "", did == "" { return nil } - - // Construct the authorization URL - var authorizationURLComponents = URLComponents(string: authorizationEndpoint) - authorizationURLComponents?.queryItems = [ - URLQueryItem(name: "response_type", value: responseType), - URLQueryItem(name: "scope", value: scope), - URLQueryItem(name: "state", value: state), - URLQueryItem(name: "client_id", value: did), - URLQueryItem(name: "authorization_details", value: authorizationDetails), - URLQueryItem(name: "redirect_uri", value: redirectUri), - URLQueryItem(name: "nonce", value: nonce), - URLQueryItem(name: "code_challenge", value: codeChallenge), - URLQueryItem(name: "code_challenge_method", value: codeChallengeMethod), - URLQueryItem(name: "client_metadata", value: clientMetadata), - URLQueryItem(name: "issuer_state", value: credentialOffer.grants?.authorizationCode?.issuerState) - ] + var authorizationURLComponents: URLComponents? + if authServer.requirePushedAuthorizationRequests == true { + let parEndpoint = authServer.pushedAuthorizationRequestEndpoint ?? "" + var request = URLRequest(url: URL(string: parEndpoint)!) + request.httpMethod = "POST" + + let bodyParameters = [ + "response_type": responseType, + "client_id": did, + "code_challenge": codeChallenge ?? "", + "code_challenge_method": codeChallengeMethod, + "redirect_uri": redirectUri, + "authorization_details": authorizationDetails, + "scope": scope, + "state": state, + "nonce": nonce, + "client_metadata": clientMetadata ?? "", + "issuer_state": credentialOffer.grants?.authorizationCode?.issuerState ?? "" + ] as [String: Any] + + let postString = UIApplicationUtils.shared.getPostString(params: bodyParameters) + let parameter = postString.replacingOccurrences(of: "+", with: "%2B") + request.httpBody = parameter.data(using: .utf8) + request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") + + do { + let (data, response) = try await session!.data(for: request) + guard let authorization_response = String.init(data: data, encoding: .utf8) else { return nil } + if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 || httpResponse.statusCode == 201 { + if let jsonResponse = try? JSONSerialization.jsonObject(with: data, options: []), + let jsonDict = jsonResponse as? [String: Any], + let requestURI = jsonDict["request_uri"] as? String { + + authorizationURLComponents = URLComponents(string: authorizationEndpoint) + authorizationURLComponents?.queryItems = [ + URLQueryItem(name: "client_id", value: did), + URLQueryItem(name: "request_uri", value: requestURI) + ] + } + } else { + debugPrint("Failed to get request_uri from the PAR response.") + } + } catch { + debugPrint("Error in making PAR request: \(error.localizedDescription)") + } + + } else { + // Construct the authorization URL + authorizationURLComponents = URLComponents(string: authorizationEndpoint) + authorizationURLComponents?.queryItems = [ + URLQueryItem(name: "response_type", value: responseType), + URLQueryItem(name: "scope", value: scope), + URLQueryItem(name: "state", value: state), + URLQueryItem(name: "client_id", value: did), + URLQueryItem(name: "authorization_details", value: authorizationDetails), + URLQueryItem(name: "redirect_uri", value: redirectUri), + URLQueryItem(name: "nonce", value: nonce), + URLQueryItem(name: "code_challenge", value: codeChallenge), + URLQueryItem(name: "code_challenge_method", value: codeChallengeMethod), + URLQueryItem(name: "client_metadata", value: clientMetadata), + URLQueryItem(name: "issuer_state", value: credentialOffer.grants?.authorizationCode?.issuerState) + ] + } // Validate the constructed authorization URL guard let authorizationURL = authorizationURLComponents?.url else { @@ -196,17 +323,17 @@ public class IssueService: NSObject, IssueServiceProtocol { (responseUrl.contains("request_uri=") && !responseUrl.contains("response_type=") && !responseUrl.contains("state=")){ return responseUrl } else { - // if 'code' is not present + // if 'code' is not present let url = URL(string: responseUrl) let state = url?.queryParameters?["state"] let nonce = url?.queryParameters?["nonce"] let redirectUri = url?.queryParameters?["redirect_uri"] - + let uri = redirectUri?.replacingOccurrences(of: "\n", with: "") ?? "" let code = await processAuthorisationRequestUsingIdToken( did: did, secureKey: secureKey, authServerWellKnownConfig: authServer, - redirectURI: redirectUri ?? "", + redirectURI: uri.trimmingCharacters(in: .whitespaces) , nonce: nonce ?? "", state: state ?? "") return code @@ -227,36 +354,36 @@ public class IssueService: NSObject, IssueServiceProtocol { let header = ([ - "typ": "JWT", - "alg": "ES256", - "kid": "\(did)#\(did.replacingOccurrences(of: "did:key:", with: ""))" + "typ": "JWT", + "alg": "ES256", + "kid": "\(did)#\(did.replacingOccurrences(of: "did:key:", with: ""))" ]).toString() ?? "" - + // Generate JWT payload let currentTime = Int(Date().timeIntervalSince1970) let payload = ([ - "iss": "\(did)", - "sub": "\(did)", - "aud": "\(authorizationEndpoint)", - "exp": currentTime + 3600, - "iat": currentTime, - "nonce": "\(nonce)" + "iss": "\(did)", + "sub": "\(did)", + "aud": "\(authorizationEndpoint)", + "exp": currentTime + 3600, + "iat": currentTime, + "nonce": "\(nonce)" ] as [String : Any]).toString() ?? "" // Create JWT token let headerData = Data(header.utf8) - + //let payloadData = Data(payload.utf8) //let unsignedToken = "\(headerData.base64URLEncodedString()).\(payloadData.base64URLEncodedString())" - + guard let idToken = keyHandler.sign(payload: payload, header: headerData, withKey: secureKey.privateKey) else{return nil} //guard let signature = keyHandler.sign(data: unsignedToken.data(using: .utf8)!, withKey: secureKey.privateKey) else{return nil} //let idToken = "\(unsignedToken).\(signature.base64URLEncodedString())" print(idToken) guard let urlComponents = URLComponents(string: redirectURI) else { return nil } - + // Create the URL with the added query parameters guard let url = urlComponents.url else { return nil } @@ -268,7 +395,7 @@ public class IssueService: NSObject, IssueServiceProtocol { "id_token" : idToken, "state" : state ] as [String: Any] - + let postString = UIApplicationUtils.shared.getPostString(params: params) request.httpBody = postString.data(using: .utf8) @@ -291,8 +418,8 @@ public class IssueService: NSObject, IssueServiceProtocol { responseUrl = authorization_response } -// let authorization_response = String.init(data: data, encoding: .utf8) ?? "" -// guard let authorisation_url = URL(string: authorization_response) else { return nil } + // let authorization_response = String.init(data: data, encoding: .utf8) ?? "" + // guard let authorisation_url = URL(string: authorization_response) else { return nil } guard let authorisation_url = URL(string: responseUrl) else { return nil } if let components = URLComponents(url: authorisation_url, resolvingAgainstBaseURL: false), let auth_code = components.queryItems?.first(where: { $0.name == "code" })?.value { @@ -310,13 +437,13 @@ public class IssueService: NSObject, IssueServiceProtocol { // MARK: - Processes the token request to obtain the access token. /** - Parameters - - authServerWellKnownConfig: The well-known configuration of the authorization server. - - code: If the credential offer is pre authorised, then use the pre authorised code from the credential offer - else use the code from the previous function - processAuthorisationRequest - - did: The identifier for the DID key. - - isPreAuthorisedCodeFlow: A boolean indicating if it's a pre-authorized code flow. - - preAuthCode: The pre-authorization code for the token request. - - userPin: The user's PIN, if required. + - authServerWellKnownConfig: The well-known configuration of the authorization server. + - code: If the credential offer is pre authorised, then use the pre authorised code from the credential offer + else use the code from the previous function - processAuthorisationRequest + - did: The identifier for the DID key. + - isPreAuthorisedCodeFlow: A boolean indicating if it's a pre-authorized code flow. + - preAuthCode: The pre-authorization code for the token request. + - userPin: The user's PIN, if required. - Returns: A `TokenResponse` object if the request is successful, otherwise `nil`. */ @@ -326,28 +453,28 @@ public class IssueService: NSObject, IssueServiceProtocol { code: String, codeVerifier: String, isPreAuthorisedCodeFlow: Bool = false, - userPin: String?) async -> TokenResponse? { - + userPin: String?, version: String?) async -> TokenResponse? { + if isPreAuthorisedCodeFlow { - let tokenResponse = await getAccessTokenForPreAuthCredential(preAuthCode: code, otpVal: userPin ?? "", tokenEndpoint: tokenEndPoint ?? "") + let tokenResponse = await getAccessTokenForPreAuthCredential(preAuthCode: code, otpVal: userPin ?? "", tokenEndpoint: tokenEndPoint ?? "", version: version) return tokenResponse } else { let codeVal = code.removingPercentEncoding ?? "" let tokenResponse = await getAccessToken(didKeyIdentifier: did, codeVerifier: codeVerifier, authCode: codeVal, tokenEndpoint: tokenEndPoint ?? "") return tokenResponse } - } + } // MARK: Processes a credential request to the specified credential endpoint. /** - Parameters - - did: The identifier for the DID key. - - secureKey: A wrapper object containing the public and private encryption keys - - credentialOffer: The credential offer object containing offer details. - - credentialEndpointUrlString: The URL string of the credential endpoint. - - c_nonce: The nonce value for the credential request. - - accessToken: The access token for authentication. - + - did: The identifier for the DID key. + - secureKey: A wrapper object containing the public and private encryption keys + - credentialOffer: The credential offer object containing offer details. + - credentialEndpointUrlString: The URL string of the credential endpoint. + - c_nonce: The nonce value for the credential request. + - accessToken: The access token for authentication. + - Returns: A `CredentialResponse` object if the request is successful, otherwise `nil`. */ public func processCredentialRequest( @@ -358,26 +485,26 @@ public class IssueService: NSObject, IssueServiceProtocol { issuerConfig: IssuerWellKnownConfiguration, accessToken: String, format: String) async -> CredentialResponse? { - + let jsonDecoder = JSONDecoder() let methodSpecificId = did.replacingOccurrences(of: "did:key:", with: "") - - + + // Generate JWT header let header = ([ - "typ": "openid4vci-proof+jwt", - "alg": "ES256", - "kid": "\(did)#\(methodSpecificId)" + "typ": "openid4vci-proof+jwt", + "alg": "ES256", + "kid": "\(did)#\(methodSpecificId)" ]).toString() ?? "" // Generate JWT payload let currentTime = Int(Date().epochTime) ?? 0 let payload = ([ - "iss": "\(did)", - "iat": currentTime, - "aud": "\(credentialOffer.credentialIssuer ?? "")", - "exp": currentTime + 86400, - "nonce": "\(nonce)" + "iss": "\(did)", + "iat": currentTime, + "aud": "\(credentialOffer.credentialIssuer ?? "")", + "exp": currentTime + 86400, + "nonce": "\(nonce)" ] as [String : Any]).toString() ?? "" guard let url = URL(string: issuerConfig.credentialEndpoint ?? "") else { return nil } @@ -388,22 +515,22 @@ public class IssueService: NSObject, IssueServiceProtocol { // Create JWT token let headerData = Data(header.utf8) - + //let payloadData = Data(payload.utf8) //let unsignedToken = "\(headerData.base64URLEncodedString()).\(payloadData.base64URLEncodedString())" // sign the data to be encrypted and exchanged guard let idToken = keyHandler.sign(payload: payload, header: headerData, withKey: secureKey.privateKey) else{return nil} //guard let signature = keyHandler.sign(data: unsignedToken.data(using: .utf8)!, withKey: secureKey.privateKey) else{return nil} //let idToken = "\(unsignedToken).\(signature.base64URLEncodedString())" - + let credentialTypes = credentialOffer.credentials?[0].types ?? [] let types = getTypesFromIssuerConfig(issuerConfig: issuerConfig, type: credentialTypes.last ?? "") let formatT = getFormatFromIssuerConfig(issuerConfig: issuerConfig, type: credentialTypes.last) - let doctType = getDocTypeFromIssuerConfig(issuerConfig: issuerConfig, type: credentialTypes.last) + let doctType = getDocTypeFromIssuerConfig(issuerConfig: issuerConfig, type: credentialTypes.last) var params: [String: Any] = [:] - if formatT == "mso_mdoc" { - params = [ + if formatT == "mso_mdoc" { + params = [ "doctype": doctType, "format": formatT, "proof": [ @@ -411,65 +538,65 @@ public class IssueService: NSObject, IssueServiceProtocol { "jwt": idToken ] ] - } else { - if types is String { - params = [ - "vct": types ?? "", - "format": format ?? "jwt_vc", - "proof": [ - "proof_type": "jwt", - "jwt": idToken + } else { + if types is String { + params = [ + "vct": types ?? "", + "format": format ?? "jwt_vc", + "proof": [ + "proof_type": "jwt", + "jwt": idToken + ] ] - ] - }else{ - params = [ - "credential_definition": [ - "type": types - ], - "format": format ?? "jwt_vc", - "proof": [ - "proof_type": "jwt", - "jwt": idToken + }else{ + params = [ + "credential_definition": [ + "type": types + ], + "format": format ?? "jwt_vc", + "proof": [ + "proof_type": "jwt", + "jwt": idToken + ] ] - ] - } - if credentialOffer.credentials?[0].trustFramework != nil { - params = [ - "types": credentialTypes, - "format": formatT, - "proof": [ - "proof_type": "jwt", - "jwt": idToken + } + if credentialOffer.credentials?[0].trustFramework != nil { + params = [ + "types": credentialTypes, + "format": formatT, + "proof": [ + "proof_type": "jwt", + "jwt": idToken + ] ] - ] - } else { - - if let data = getTypesFromIssuerConfig(issuerConfig: issuerConfig, type: credentialTypes.last ?? "") { - if let dataArray = data as? [String] { - params = [ - "credential_definition": [ - "type": dataArray - ], - "format": formatT, - "proof": [ - "proof_type": "jwt", - "jwt": idToken + } else { + + if let data = getTypesFromIssuerConfig(issuerConfig: issuerConfig, type: credentialTypes.last ?? "") { + if let dataArray = data as? [String] { + params = [ + "credential_definition": [ + "type": dataArray + ], + "format": formatT, + "proof": [ + "proof_type": "jwt", + "jwt": idToken + ] ] - ] - } else if let dataString = data as? String { - params = [ - "vct": dataString, - "format": formatT, - "proof": [ - "proof_type": "jwt", - "jwt": idToken + } else if let dataString = data as? String { + params = [ + "vct": dataString, + "format": formatT, + "proof": [ + "proof_type": "jwt", + "jwt": idToken + ] ] - ] + } } } } - } - + // Create URL for the credential endpoint guard let url = URL(string: issuerConfig.credentialEndpoint ?? "") else { return nil } @@ -478,7 +605,7 @@ public class IssueService: NSObject, IssueServiceProtocol { request.setValue("application/json", forHTTPHeaderField: "Content-Type") request.setValue( "Bearer \(accessToken)", forHTTPHeaderField: "Authorization") request.httpMethod = "POST" - + // Convert the parameters to JSON data and set it as the request body let requestBodyData = try? JSONSerialization.data(withJSONObject: params) request.httpBody = requestBodyData @@ -486,53 +613,93 @@ public class IssueService: NSObject, IssueServiceProtocol { // Perform the request and handle the response do { let (data, response) = try await URLSession.shared.data(for: request) - let model = try jsonDecoder.decode(CredentialResponse.self, from: data) - return model + guard let jsonObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else { return nil } + if jsonObject["acceptance_token"] != nil { + let model = try jsonDecoder.decode(CredentialResponseV1.self, from: data) + return CredentialResponse(from: model) + + } else if jsonObject["transaction_id"] != nil { + let modelV2 = try jsonDecoder.decode(CredentialResponseV2.self, from: data) + return CredentialResponse(from: modelV2) + } else if jsonObject["acceptance_token"] == nil && jsonObject["transaction_id"] == nil { + let model = try jsonDecoder.decode(CredentialResponseV1.self, from: data) + return CredentialResponse(from: model) + } + else { + let error = EUDIError(from: ErrorResponse(message: "Invalid data format", code: nil)) + return CredentialResponse(fromError: error) + } } catch { debugPrint("Process credential request failed: \(error)") let nsError = error as NSError let errorCode = nsError.code let error = EUDIError(from: ErrorResponse(message:error.localizedDescription, code: errorCode)) - return CredentialResponse(error: error) + return CredentialResponse(fromError: error) } - } + } -// MARK: - Processes a deferred credential request to obtain the credential response in deffered manner. - + // MARK: - Processes a deferred credential request to obtain the credential response in deffered manner. + /** - Parameters - - acceptanceToken - token which we got from credential request - - deferredCredentialEndPoint - end point to call the deferred credential - **/ -// - Returns: A `CredentialResponse` object if the request is successful, otherwise `nil`. - + - acceptanceToken - token which we got from credential request + - deferredCredentialEndPoint - end point to call the deferred credential + **/ + // - Returns: A `CredentialResponse` object if the request is successful, otherwise `nil`. + public func processDeferredCredentialRequest( acceptanceToken: String, - deferredCredentialEndPoint: String) async -> CredentialResponse? { + deferredCredentialEndPoint: String, version: String?, accessToken: String?) async -> CredentialResponse? { let jsonDecoder = JSONDecoder() guard let url = URL(string: deferredCredentialEndPoint) else { return nil } var request = URLRequest(url: url) request.httpMethod = "POST" request.setValue("application/json", forHTTPHeaderField: "Content-Type") - request.setValue( "Bearer \(acceptanceToken)", forHTTPHeaderField: "Authorization") - let params = "{}" - let data = params.data(using: .utf8) - request.httpBody = data - // Perform the request and handle the response - do { - let (data, _) = try await URLSession.shared.data(for: request) - let model = try jsonDecoder.decode(CredentialResponse.self, from: data) - return model - } catch { - debugPrint("Process deferred credential request failed: \(error)") - let nsError = error as NSError - let errorCode = nsError.code - let error = EUDIError(from: ErrorResponse(message:error.localizedDescription, code: errorCode)) - return CredentialResponse(error: error) + if version == "v1" { + request.setValue( "Bearer \(acceptanceToken)", forHTTPHeaderField: "Authorization") + let params = "{}" + let data = params.data(using: .utf8) + request.httpBody = data + } else if version == "v2" { + var params: [String: Any] = [:] + request.setValue( "Bearer \(accessToken ?? "")", forHTTPHeaderField: "Authorization") + params = ["transaction_id": acceptanceToken ?? ""] + let requestBodyData = try? JSONSerialization.data(withJSONObject: params) + request.httpBody = requestBodyData + } + + + // Perform the request and handle the response + do { + let (data, _) = try await URLSession.shared.data(for: request) + guard let jsonObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else { + return nil + } + if jsonObject["acceptance_token"] != nil { + let model = try jsonDecoder.decode(CredentialResponseV1.self, from: data) + return CredentialResponse(from: model) + + } else if jsonObject["transaction_id"] != nil { + let modelV2 = try jsonDecoder.decode(CredentialResponseV2.self, from: data) + return CredentialResponse(from: modelV2) + } else if jsonObject["acceptance_token"] == nil && jsonObject["transaction_id"] == nil { + let model = try jsonDecoder.decode(CredentialResponseV1.self, from: data) + return CredentialResponse(from: model) + } + else { + let error = EUDIError(from: ErrorResponse(message: "Invalid data format", code: nil)) + return CredentialResponse(fromError: error) + } + } catch { + debugPrint("Process deferred credential request failed: \(error)") + let nsError = error as NSError + let errorCode = nsError.code + let error = EUDIError(from: ErrorResponse(message:error.localizedDescription, code: errorCode)) + return CredentialResponse(fromError: error) + } } - } // MARK: - Private methods @@ -540,34 +707,40 @@ public class IssueService: NSObject, IssueServiceProtocol { private func getAccessTokenForPreAuthCredential( preAuthCode: String, otpVal: String , - tokenEndpoint: String) async -> TokenResponse? { + tokenEndpoint: String, version: String?) async -> TokenResponse? { - let jsonDecoder = JSONDecoder() - let grantType = "urn:ietf:params:oauth:grant-type:pre-authorized_code" - - // Constructing parameters for the token request - let params = ["grant_type": grantType, "pre-authorized_code":preAuthCode, "user_pin": otpVal] as [String: Any] + let jsonDecoder = JSONDecoder() + let grantType = "urn:ietf:params:oauth:grant-type:pre-authorized_code" + + // Constructing parameters for the token request + var params: [String: Any] = [:] + // Constructing parameters for the token request + if version == "v1" { + params = ["grant_type": grantType, "pre-authorized_code":preAuthCode, "user_pin": otpVal] as [String: Any] + } else if version == "v2" { + params = ["grant_type": grantType, "pre-authorized_code":preAuthCode, "tx_code": otpVal] as [String: Any] + } let postString = UIApplicationUtils.shared.getPostString(params: params) - - // Creating the request - var request = URLRequest(url: URL(string: tokenEndpoint)!) - request.httpMethod = "POST" - request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") - request.httpBody = postString.data(using: .utf8) - - // Performing the token request - do { - let (data, _) = try await URLSession.shared.data(for: request) - let model = try jsonDecoder.decode(TokenResponse.self, from: data) - return model - } catch { - debugPrint("Get access token for preauth credential failed: \(error)") - let nsError = error as NSError - let errorCode = nsError.code - let error = EUDIError(from: ErrorResponse(message:error.localizedDescription, code: errorCode)) - return TokenResponse(error: error) + + // Creating the request + var request = URLRequest(url: URL(string: tokenEndpoint)!) + request.httpMethod = "POST" + request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") + request.httpBody = postString.data(using: .utf8) + + // Performing the token request + do { + let (data, _) = try await URLSession.shared.data(for: request) + let model = try jsonDecoder.decode(TokenResponse.self, from: data) + return model + } catch { + debugPrint("Get access token for preauth credential failed: \(error)") + let nsError = error as NSError + let errorCode = nsError.code + let error = EUDIError(from: ErrorResponse(message:error.localizedDescription, code: errorCode)) + return TokenResponse(error: error) + } } - } // Retrieves the access token using the provided parameters. private func getAccessToken( @@ -576,33 +749,33 @@ public class IssueService: NSObject, IssueServiceProtocol { authCode: String, tokenEndpoint: String) async -> TokenResponse? { - let jsonDecoder = JSONDecoder() - let grantType = "authorization_code" - - // Constructing parameters for the token request - let params = ["grant_type": grantType, "code":authCode, "client_id": didKeyIdentifier, "code_verifier": codeVerifier] as [String: Any] + let jsonDecoder = JSONDecoder() + let grantType = "authorization_code" + + // Constructing parameters for the token request + let params = ["grant_type": grantType, "code":authCode, "client_id": didKeyIdentifier, "code_verifier": codeVerifier] as [String: Any] let postString = UIApplicationUtils.shared.getPostString(params: params) - // Creating the request - var request = URLRequest(url: URL(string: tokenEndpoint)!) - request.httpMethod = "POST" - request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") - request.httpBody = postString.data(using: .utf8) - - // Performing the token request - do { - let (data, response) = try await URLSession.shared.data(for: request) - debugPrint(response) - let model = try jsonDecoder.decode(TokenResponse.self, from: data) - return model - } catch { - debugPrint("Get access token for preauth credential failed: \(error)") - let nsError = error as NSError - let errorCode = nsError.code - let error = EUDIError(from: ErrorResponse(message:error.localizedDescription, code: errorCode)) - return TokenResponse(error: error) + // Creating the request + var request = URLRequest(url: URL(string: tokenEndpoint)!) + request.httpMethod = "POST" + request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") + request.httpBody = postString.data(using: .utf8) + + // Performing the token request + do { + let (data, response) = try await URLSession.shared.data(for: request) + debugPrint(response) + let model = try jsonDecoder.decode(TokenResponse.self, from: data) + return model + } catch { + debugPrint("Get access token for preauth credential failed: \(error)") + let nsError = error as NSError + let errorCode = nsError.code + let error = EUDIError(from: ErrorResponse(message:error.localizedDescription, code: errorCode)) + return TokenResponse(error: error) + } } - } public func getFormatFromIssuerConfig(issuerConfig: IssuerWellKnownConfiguration?, type: String?) -> String? { guard let issuerConfig = issuerConfig else { return nil } @@ -629,7 +802,7 @@ public class IssueService: NSObject, IssueServiceProtocol { if let credentialSupported = issuerConfig.credentialsSupported?.dataSharing?[type ?? ""] { if credentialSupported.format == "vc+sd-jwt" { - return credentialSupported.credentialDefinition?.vct + return credentialSupported.credentialDefinition?.vct ?? credentialSupported.vct } else { return credentialSupported.credentialDefinition?.type } @@ -657,9 +830,9 @@ public class IssueService: NSObject, IssueServiceProtocol { return nil } } - + public func getDocTypeFromIssuerConfig(issuerConfig: IssuerWellKnownConfiguration?, type: String?) -> String? { - guard let issuerConfig = issuerConfig else { return nil } + guard let issuerConfig = issuerConfig else { return nil } if let credentialSupported = issuerConfig.credentialsSupported?.dataSharing?[type ?? ""] { return credentialSupported.docType ?? nil @@ -672,7 +845,7 @@ public class IssueService: NSObject, IssueServiceProtocol { extension IssueService: URLSessionDelegate, URLSessionTaskDelegate { public func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @escaping (URLRequest?) -> Void) { - // Stops the redirection, and returns (internally) the response body. - completionHandler(nil) - } + // Stops the redirection, and returns (internally) the response body. + completionHandler(nil) } +} diff --git a/Sources/eudiWalletOidcIos/Service/IssueServiceProtocol.swift b/Sources/eudiWalletOidcIos/Service/IssueServiceProtocol.swift index 983ca29..c55155e 100644 --- a/Sources/eudiWalletOidcIos/Service/IssueServiceProtocol.swift +++ b/Sources/eudiWalletOidcIos/Service/IssueServiceProtocol.swift @@ -23,48 +23,48 @@ protocol IssueServiceProtocol { /// - authServer: The authorization server configuration. /// - codeVerifier - to build the authorisation request /// - Returns: code if successful; otherwise, nil. - func processAuthorisationRequest(did: String, secureKey: SecureKeyData, credentialOffer: CredentialOffer, codeVerifier: String, authServer: AuthorisationServerWellKnownConfiguration, credentialFormat: String, docType: String) async -> String? + func processAuthorisationRequest(did: String, secureKey: SecureKeyData, credentialOffer: CredentialOffer, codeVerifier: String, authServer: AuthorisationServerWellKnownConfiguration, credentialFormat: String, docType: String, issuerConfig: IssuerWellKnownConfiguration?) async -> String? // Processes the token request to obtain the access token. /** - Parameters - - authServerWellKnownConfig: The well-known configuration of the authorization server. - - code: If the credential offer is pre authorised, then use the pre authorised code from the credential offer - else use the code from the previous function - processAuthorisationRequest - - did: The identifier for the DID key. - - codeverifier: - - isPreAuthorisedCodeFlow: A boolean indicating if it's a pre-authorized code flow. - - preAuthCode: The pre-authorization code for the token request. - - userPin: The user's PIN, if required. + - authServerWellKnownConfig: The well-known configuration of the authorization server. + - code: If the credential offer is pre authorised, then use the pre authorised code from the credential offer + else use the code from the previous function - processAuthorisationRequest + - did: The identifier for the DID key. + - codeverifier: + - isPreAuthorisedCodeFlow: A boolean indicating if it's a pre-authorized code flow. + - preAuthCode: The pre-authorization code for the token request. + - userPin: The user's PIN, if required. - Returns: A `TokenResponse` object if the request is successful, otherwise `nil`. */ - func processTokenRequest(did: String, tokenEndPoint: String?, code: String, codeVerifier: String, isPreAuthorisedCodeFlow: Bool, userPin: String?) async -> TokenResponse? + func processTokenRequest(did: String, tokenEndPoint: String?, code: String, codeVerifier: String, isPreAuthorisedCodeFlow: Bool, userPin: String?, version: String?) async -> TokenResponse? // Processes a credential request to the specified credential endpoint. /** - Parameters - - did: The identifier for the DID key. - - secureKey: A wrapper object containing the public and private encryption keys - - credentialOffer: The credential offer object containing offer details. - - credentialEndpointUrlString: The URL string of the credential endpoint. - - c_nonce: The nonce value for the credential request. - - accessToken: The access token for authentication. + - did: The identifier for the DID key. + - secureKey: A wrapper object containing the public and private encryption keys + - credentialOffer: The credential offer object containing offer details. + - credentialEndpointUrlString: The URL string of the credential endpoint. + - c_nonce: The nonce value for the credential request. + - accessToken: The access token for authentication. - Returns: A `CredentialResponse` object if the request is successful, otherwise `nil`. */ func processCredentialRequest(did: String, secureKey: SecureKeyData, nonce: String, credentialOffer: CredentialOffer, issuerConfig: IssuerWellKnownConfiguration, accessToken: String, format: String) async -> CredentialResponse? // Processes a deferred credential request to obtain the credential response in deffered manner. - /** - Parameters - - acceptanceToken - token which we got from credential request - - deferredCredentialEndPoint - end point to call the deferred credential - **/ + /** - Parameters + - acceptanceToken - token which we got from credential request + - deferredCredentialEndPoint - end point to call the deferred credential + **/ // - Returns: A `CredentialResponse` object if the request is successful, otherwise `nil`. - func processDeferredCredentialRequest(acceptanceToken: String, deferredCredentialEndPoint: String) async -> CredentialResponse? + func processDeferredCredentialRequest(acceptanceToken: String, deferredCredentialEndPoint: String, version: String?, accessToken: String?) async -> CredentialResponse? func getFormatFromIssuerConfig( - issuerConfig: IssuerWellKnownConfiguration?, - type: String?) -> String? + issuerConfig: IssuerWellKnownConfiguration?, + type: String?) -> String? func getTypesFromCredentialOffer(credentialOffer: CredentialOffer?) -> [String]? func getTypesFromIssuerConfig(issuerConfig: IssuerWellKnownConfiguration?, type: String?) -> Any? diff --git a/Sources/eudiWalletOidcIos/Service/VerificationService.swift b/Sources/eudiWalletOidcIos/Service/VerificationService.swift index 0627090..e499e55 100644 --- a/Sources/eudiWalletOidcIos/Service/VerificationService.swift +++ b/Sources/eudiWalletOidcIos/Service/VerificationService.swift @@ -38,8 +38,14 @@ public class VerificationService: NSObject, VerificationServiceProtocol { // Generate JWT payload let payload = generateJWTPayload(did: did, nonce: presentationRequest?.nonce ?? "", credentialsList: credentialsList ?? [], state: presentationRequest?.state ?? "", clientID: presentationRequest?.clientId ?? "") debugPrint("payload:\(payload)") + var presentationDefinition :PresentationDefinitionModel? = nil + do { + presentationDefinition = try VerificationService.processPresentationDefinition(presentationRequest?.presentationDefinition) + } catch { + presentationDefinition = nil + } - let vpToken = format == "mso_mdoc" ? generateVPTokenForMdoc(credential: credentialsList ?? []) :generateVPToken(header: header, payload: payload, secureKey: secureKey) + let vpToken = format == "mso_mdoc" ? generateVPTokenForMdoc(credential: credentialsList ?? [], presentationDefinition: presentationDefinition) :generateVPToken(header: header, payload: payload, secureKey: secureKey) // Presentation Submission model guard let presentationSubmission = preparePresentationSubmission(presentationRequest: presentationRequest) else { return nil } @@ -180,14 +186,37 @@ public class VerificationService: NSObject, VerificationServiceProtocol { return idToken//"\(unsignedToken).\(signature.base64URLEncodedString() ?? "")" } - private func generateVPTokenForMdoc(credential: [String]) -> String { + private func generateVPTokenForMdoc(credential: [String], presentationDefinition: PresentationDefinitionModel?) -> String { var cborString: String = "" var base64StringWithoutPadding = "" + var requestedParams: [String] = [] + var limitDisclosure: Bool = false + if let fields = presentationDefinition?.inputDescriptors?.first?.constraints?.fields { + for field in fields { + let components = field.path?.first?.components(separatedBy: ["[", "]", "'"]) + + let filteredComponents = components?.filter { !$0.isEmpty } + + if let identifier = filteredComponents?.last { + requestedParams.append(String(identifier)) + } + } + } + if presentationDefinition?.inputDescriptors?.first?.constraints?.limitDisclosure == nil { + limitDisclosure = false + } else { + limitDisclosure = true + } for cred in credential { - if let issuerAuthData = getIssuerAuth(credential: cred), let nameSpaceData = getNameSpaces(credential: cred) { - let dummy = getLimitDisclosureCBOR(index: 0, cborData: nameSpaceData) + if let issuerAuthData = getIssuerAuth(credential: cred), let cborNameSpace = getNameSpaces(credential: cred, presentationDefinition: presentationDefinition) { + var nameSpaceData: CBOR? = nil + if limitDisclosure { + nameSpaceData = filterNameSpaces(nameSpacesValue: cborNameSpace, requestedParams: requestedParams) + } else { + nameSpaceData = cborNameSpace + } let docType = getDocTypeFromIssuerAuth(cborData: issuerAuthData) - let docFiltered = [Document(docType: docType ?? "", issuerSigned: IssuerSigned(nameSpaces: nameSpaceData, issuerAuth: issuerAuthData))] + let docFiltered = [Document(docType: docType ?? "", issuerSigned: IssuerSigned(nameSpaces: nameSpaceData ?? nil, issuerAuth: issuerAuthData))] let documentsToAdd = docFiltered.count == 0 ? nil : docFiltered let deviceResponseToSend = DeviceResponse(version: "1.0", documents: documentsToAdd,status: 0) @@ -487,7 +516,19 @@ func extractDocType(cborData: CBOR) -> String? { return nil // Return nil if "issuerAuth" is not found } - func getNameSpaces(credential: String) -> CBOR? { + func getNameSpaces(credential: String, presentationDefinition: PresentationDefinitionModel?) -> CBOR? { + var requestedParams: [String] = [] + if let fields = presentationDefinition?.inputDescriptors?.first?.constraints?.fields { + for field in fields { + let components = field.path?.first?.components(separatedBy: ["[", "]", "'"]) + + let filteredComponents = components?.filter { !$0.isEmpty } + + if let identifier = filteredComponents?.last { + requestedParams.append(String(identifier)) + } + } + } // Convert the base64 URL encoded credential to Data if let data = Data(base64URLEncoded: credential) { do { @@ -514,54 +555,36 @@ func extractDocType(cborData: CBOR) -> String? { -func getLimitDisclosureCBOR(index: Int, cborData: CBOR) -> CBOR? { - // Ensure the input is a map type - guard case let CBOR.map(map) = cborData else { - print("Input data is not a CBOR map.") - return nil - } - - // Iterate over the map to locate the target key - var modifiedMap = map - for (key, value) in map { - // Check if the key is the target key and its value is an array - if case let CBOR.utf8String(keyString) = key, keyString == "org.iso.18013.5.1.pID" { - if case let CBOR.array(array) = value { - // Validate the index - guard index >= 0 && index < array.count else { - print("Index is out of bounds.") - return nil - } - - // Extract the element at the given index - let selectedElement = array[index] - - // Replace the original array with the new one containing only the selected element - modifiedMap[key] = CBOR.array([selectedElement]) - } else { - print("Value associated with key is not an array.") - return nil + public func getFilteredCbor(credential: String, presentationDefinition: PresentationDefinitionModel?) -> CBOR? { + var requestedParams: [String] = [] + var limitDisclosure: Bool = false + if let fields = presentationDefinition?.inputDescriptors?.first?.constraints?.fields { + for field in fields { + let components = field.path?.first?.components(separatedBy: ["[", "]", "'"]) + let filteredComponents = components?.filter { !$0.isEmpty } + if let identifier = filteredComponents?.last { + requestedParams.append(String(identifier)) } } } - // Return the modified CBOR object - return CBOR.map(modifiedMap) -} - - - func extractIssuerAuth(credential: String) -> Any? { - // Convert the base64 URL encoded credential to Data + if presentationDefinition?.inputDescriptors?.first?.constraints?.limitDisclosure == nil { + limitDisclosure = false + } else { + limitDisclosure = true + } + if let data = Data(base64URLEncoded: credential) { do { - // Decode the CBOR data into a dictionary let decodedCBOR = try CBOR.decode([UInt8](data)) - if let dictionary = decodedCBOR { - // Check for the presence of "issuerAuth" in the dictionary - if let issuerAuthValue = dictionary[CBOR.utf8String("issuerAuth")] { - return issuerAuthValue // Return the issuerAuth value directly - } + //if let nameSpacesValue = dictionary[CBOR.utf8String("nameSpaces")] { + if limitDisclosure { + return filterCBORWithRequestedParams(cborData: dictionary, requestedParams: requestedParams) + } else { + return dictionary + } + // } } } catch { print("Error decoding CBOR: \(error)") @@ -572,60 +595,62 @@ func getLimitDisclosureCBOR(index: Int, cborData: CBOR) -> CBOR? { return nil } - return nil // Return nil if "issuerAuth" is not found + return nil } +public func filterCBORWithRequestedParams(cborData: CBOR, requestedParams: [String]) -> CBOR? { + guard case let CBOR.map(cborMap) = cborData else { return nil } - func convertCBORToHumanReadable(_ cbor: CBOR) -> [String: Any] { - var result: [String: Any] = [:] - - switch cbor { - case let .map(map): - for (key, value) in map { - let keyString = cborToString(key) - let valueReadable = cborToHumanReadable(value) - result[keyString] = valueReadable + var modifiedCBORMap = cborMap + + if let namespacesValue = modifiedCBORMap[CBOR.utf8String("nameSpaces")] { + if let filteredNameSpaces = filterNameSpaces(nameSpacesValue: namespacesValue, requestedParams: requestedParams) { + modifiedCBORMap[CBOR.utf8String("nameSpaces")] = filteredNameSpaces } - default: - print("Unhandled CBOR type") } - - return result + + return CBOR.map(modifiedCBORMap) } -func cborToHumanReadable(_ cborValue: CBOR) -> Any { - switch cborValue { - case let .negativeInt(value): - // Convert the UInt64 negative integer to an Int safely - if let intValue = Int(exactly: value) { - return -(intValue + 1) - } else { - return "NegativeInt out of bounds" - } +public func filterNameSpaces(nameSpacesValue: CBOR, requestedParams: [String]) -> CBOR? { + if case let CBOR.map(nameSpaces) = nameSpacesValue { + var filteredNameSpaces: OrderedDictionary = [:] - case let .unsignedInt(value): - // Handle unsigned integer safely - if let intValue = Int(exactly: value) { - return intValue - } else { - return value // return UInt64 if it's too large for Int + for (key, namespaceValue) in nameSpaces { + var valuesArray: [CBOR] = [] + + if case let CBOR.array(orgValues) = namespaceValue { + for value in orgValues { + if case let CBOR.tagged(tag, taggedValue) = value, tag.rawValue == 24 { + if case let CBOR.byteString(byteString) = taggedValue { + let data = Data(byteString) + if let decodedInnerCBOR = try? CBOR.decode([UInt8](data)), + case let CBOR.map(decodedMap) = decodedInnerCBOR { + if let identifier = decodedMap[CBOR.utf8String("elementIdentifier")], + let value = decodedMap[CBOR.utf8String("elementValue")], + case let CBOR.utf8String(identifierString) = identifier { + if requestedParams.contains(identifierString) { + valuesArray.append(CBOR.tagged(tag, CBOR.byteString(byteString))) + } + } + } + } + } + } + } + + if !valuesArray.isEmpty { + filteredNameSpaces[key] = CBOR.array(valuesArray) + } } - - case let .utf8String(value): - return value - case let .byteString(data): - return Data(data).base64EncodedString() // Convert byteString to base64 for readability - - case let .map(map): - return convertCBORToHumanReadable(.map(map)) - - default: - return "UnsupportedType" + return CBOR.map(filteredNameSpaces) } -} + return nil +} + func convertCBORtoJson(credential: String) -> String? { if let data = Data(base64URLEncoded: credential) { do { @@ -787,60 +812,6 @@ func cborToHumanReadable(_ cborValue: CBOR) -> Any { return CBOR.array(cborArray) } - func sample() { - - let dataToEncode: [String: Any] = [ - "version": 1.0, - "documents": [ - [ - "docType": "eu.europa.ec.eudi.pid.1", - "issuerSigned": [ - "nameSpaces": [ - "eu.europa.ec.eudi.pid.1": [ - "co.nstant.in.cbor.model.ByteString@3d1629c9", - "co.nstant.in.cbor.model.ByteString@257ff3de", - "co.nstant.in.cbor.model.ByteString@6c3d9b4a", - "co.nstant.in.cbor.model.ByteString@1fa16c0e", - "co.nstant.in.cbor.model.ByteString@97ab30de", - "co.nstant.in.cbor.model.ByteString@cd5ee3ea", - "co.nstant.in.cbor.model.ByteString@a66e7ef3", - "co.nstant.in.cbor.model.ByteString@1efddce" - ] - ], - "issuerAuth": [ - "co.nstant.in.cbor.model.ByteString@2e86f342", - [ - "33": "co.nstant.in.cbor.model.ByteString@e9a6a3e5" - ], - "co.nstant.in.cbor.model.ByteString@fcdfe913", - "co.nstant.in.cbor.model.ByteString@6316f4" - ] - ], - "deviceSigned": [ - "nameSpaces": "co.nstant.in.cbor.model.ByteString@158a8b25", - "deviceAuth": [ - "deviceSignature": [ - "co.nstant.in.cbor.model.ByteString@2e86f342", - [:], // Empty dictionary - "NULL", // Special NULL value - "co.nstant.in.cbor.model.ByteString@d95bfc90" - ] - ] - ] - ] - ], - "status": 0 - ] - // Encode the data into CBOR format - if let cborData = encodeToCBOR(dataToEncode) { - // CBOR-encoded data as byte array -// let encodedBytes = CBOR.encode(cborData) - // Print encoded CBOR bytes in hex format - print(Data(cborData.encode()).base64EncodedString()) - } else { - print("Failed to encode data") - } - } private func processCredentialsToJsonString(credentialList: [String?]) -> [String] { var processedCredentials = [String]()